1 #!/usr/bin/python 2 # 3 # Copyright (C) 2017 The Android Open Source Project 4 # 5 # Licensed under the Apache License, Version 2.0 (the "License"); 6 # you may not use this file except in compliance with the License. 7 # You may obtain a copy of the License at 8 # 9 # http://www.apache.org/licenses/LICENSE-2.0 10 # 11 # Unless required by applicable law or agreed to in writing, software 12 # distributed under the License is distributed on an "AS IS" BASIS, 13 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 # See the License for the specific language governing permissions and 15 # limitations under the License. 16 17 """ 18 Creates the EmojiCompat font with the metadata. Metadata is embedded in FlatBuffers binary format 19 under a meta tag with name 'Emji'. 20 21 In order to create the final font the followings are used as inputs: 22 23 - NotoColorEmoji.ttf: Emoji font in the Android framework. Currently at 24 external/noto-fonts/emoji/NotoColorEmoji.ttf 25 26 - Unicode files: Unicode files that are in the framework, and lists information about all the 27 emojis. These files are emoji-data.txt, emoji-sequences.txt, emoji-zwj-sequences.txt, 28 and emoji-variation-sequences.txt. Currently at external/unicode/. 29 30 - additions/emoji-zwj-sequences.txt: Includes emojis that are not defined in Unicode files, but are 31 in the Android font. Resides in framework and currently under external/unicode/. 32 33 - ../third_party/unicode/emoji_metadata.txt: The file that includes the id, codepoints, the first 34 Android OS version that the emoji was added (sdkAdded), and finally the first EmojiCompat font 35 version that the emoji was added (compatAdded). Updated when the script is executed. 36 37 - data/emoji_metadata.fbs: The flatbuffer schema file. See http://google.github.io/flatbuffers/. 38 39 After execution the following files are generated if they don't exist otherwise, they are updated: 40 - font/NotoColorEmojiCompat.ttf 41 - supported-emojis/emojis.txt 42 - data/emoji_metadata.txt 43 - src/java/android/support/text/emoji/flatbuffer/* 44 """ 45 46 from __future__ import print_function 47 48 import contextlib 49 import csv 50 import json 51 import os 52 import shutil 53 import sys 54 import tempfile 55 from fontTools import ttLib 56 57 58 ########### UPDATE OR CHECK WHEN A NEW FONT IS BEING GENERATED ########### 59 # Last Android SDK Version 60 SDK_VERSION = 26 61 # metadata version that will be embedded into font. 62 METADATA_VERSION = 2 63 64 ####### main directories where output files are created ####### 65 SCRIPT_DIR = os.path.abspath(os.path.dirname(__file__)) 66 FONT_DIR = os.path.join(SCRIPT_DIR, 'font') 67 DATA_DIR = os.path.join(SCRIPT_DIR, 'data') 68 SUPPORTED_EMOJIS_DIR = os.path.join(SCRIPT_DIR, 'supported-emojis') 69 JAVA_SRC_DIR = os.path.join(SCRIPT_DIR, 'src', 'java') 70 ####### output files ####### 71 # font file 72 FONT_PATH = os.path.join(FONT_DIR, 'NotoColorEmojiCompat.ttf') 73 # emoji metadata json output file 74 OUTPUT_META_FILE = os.path.join(DATA_DIR, 'emoji_metadata.txt') 75 # emojis test file 76 TEST_DATA_PATH = os.path.join(SUPPORTED_EMOJIS_DIR, 'emojis.txt') 77 ####### input files ####### 78 # Unicode file names to read emoji data 79 EMOJI_DATA_FILE = 'emoji-data.txt' 80 EMOJI_SEQ_FILE = 'emoji-sequences.txt' 81 EMOJI_ZWJ_FILE = 'emoji-zwj-sequences.txt' 82 EMOJI_VARIATION_SEQ_FILE = 'emoji-variation-sequences.txt' 83 # Android OS emoji file for emojis that are not in Unicode files 84 ANDROID_EMOJI_ZWJ_SEQ_FILE = os.path.join('additions', 'emoji-zwj-sequences.txt') 85 ANDROID_EMOJIS_SEQ_FILE = os.path.join('additions', 'emoji-sequences.txt') 86 # Android OS emoji style override file. Codepoints that are rendered with emoji style by default 87 # even though not defined so in <code>emoji-data.txt</code>. 88 EMOJI_STYLE_OVERRIDE_FILE = os.path.join('additions', 'emoji-data.txt') 89 # emoji metadata file 90 INPUT_META_FILE = OUTPUT_META_FILE 91 # flatbuffer schema 92 FLATBUFFER_SCHEMA = os.path.join(DATA_DIR, 'emoji_metadata.fbs') 93 # file path for java header, it will be prepended to flatbuffer java files 94 FLATBUFFER_HEADER = os.path.join(DATA_DIR, "flatbuffer_header.txt") 95 # temporary emoji metadata json output file 96 OUTPUT_JSON_FILE_NAME = 'emoji_metadata.json' 97 # temporary binary file generated by flatbuffer 98 FLATBUFFER_BIN = 'emoji_metadata.bin' 99 # directory representation for flatbuffer java package 100 FLATBUFFER_PACKAGE_PATH = os.path.join('android', 'support', 'text', 'emoji', 'flatbuffer', '') 101 # temporary directory that contains flatbuffer java files 102 FLATBUFFER_JAVA_PATH = os.path.join(FLATBUFFER_PACKAGE_PATH) 103 FLATBUFFER_METADATA_LIST_JAVA = "MetadataList.java" 104 FLATBUFFER_METADATA_ITEM_JAVA = "MetadataItem.java" 105 # directory under source where flatbuffer java files will be copied into 106 FLATBUFFER_JAVA_TARGET = os.path.join(JAVA_SRC_DIR, FLATBUFFER_PACKAGE_PATH) 107 # meta tag name used in the font to embed the emoji metadata. This value is also used in 108 # MetadataListReader.java in order to locate the metadata location. 109 EMOJI_META_TAG_NAME = 'Emji' 110 111 EMOJI_PRESENTATION_STR = 'EMOJI_PRESENTATION' 112 STD_VARIANTS_EMOJI_STYLE = 'EMOJI STYLE' 113 114 DEFAULT_EMOJI_ID = 0xF0001 115 EMOJI_STYLE_VS = 0xFE0F 116 117 def to_hex_str(value): 118 """Converts given int value to hex without the 0x prefix""" 119 return format(value, 'X') 120 121 def hex_str_to_int(string): 122 """Convert a hex string into int""" 123 return int(string, 16) 124 125 def codepoint_to_string(codepoints): 126 """Converts a list of codepoints into a string separated with space.""" 127 return ' '.join([to_hex_str(x) for x in codepoints]) 128 129 def prepend_header_to_file(file_path): 130 """Prepends the header to the file. Used to update flatbuffer java files with header, comments 131 and annotations.""" 132 with open(file_path, "r+") as original_file: 133 with open(FLATBUFFER_HEADER, "r") as copyright_file: 134 original_content = original_file.read() 135 start_index = original_content.index("public final class") 136 original_file.seek(0) 137 original_file.write(copyright_file.read() + "\n" + original_content[start_index:]) 138 139 140 def update_flatbuffer_java_files(flatbuffer_java_dir): 141 """Prepends headers to flatbuffer java files and copies to the final destination""" 142 tmp_metadata_list = flatbuffer_java_dir + FLATBUFFER_METADATA_LIST_JAVA 143 tmp_metadata_item = flatbuffer_java_dir + FLATBUFFER_METADATA_ITEM_JAVA 144 prepend_header_to_file(tmp_metadata_list) 145 prepend_header_to_file(tmp_metadata_item) 146 147 if not os.path.exists(FLATBUFFER_JAVA_TARGET): 148 os.makedirs(FLATBUFFER_JAVA_TARGET) 149 150 shutil.copy(tmp_metadata_list, FLATBUFFER_JAVA_TARGET + FLATBUFFER_METADATA_LIST_JAVA) 151 shutil.copy(tmp_metadata_item, FLATBUFFER_JAVA_TARGET + FLATBUFFER_METADATA_ITEM_JAVA) 152 153 def create_test_data(unicode_path): 154 """Read all the emojis in the unicode files and update the test file""" 155 lines = read_emoji_lines(os.path.join(unicode_path, EMOJI_ZWJ_FILE)) 156 lines += read_emoji_lines(os.path.join(unicode_path, EMOJI_SEQ_FILE)) 157 158 lines += read_emoji_lines(os.path.join(unicode_path, ANDROID_EMOJI_ZWJ_SEQ_FILE), optional=True) 159 lines += read_emoji_lines(os.path.join(unicode_path, ANDROID_EMOJIS_SEQ_FILE), optional=True) 160 161 # standardized variants contains a huge list of sequences, only read the ones that are emojis 162 # and also the ones with FE0F (emoji style) 163 standardized_variants_lines = read_emoji_lines( 164 os.path.join(unicode_path, EMOJI_VARIATION_SEQ_FILE)) 165 for line in standardized_variants_lines: 166 if STD_VARIANTS_EMOJI_STYLE in line: 167 lines.append(line) 168 169 emojis_set = set() 170 for line in lines: 171 codepoints = [hex_str_to_int(x) for x in line.split(';')[0].strip().split(' ')] 172 emojis_set.add(codepoint_to_string(codepoints).upper()) 173 174 emoji_data_lines = read_emoji_lines(os.path.join(unicode_path, EMOJI_DATA_FILE)) 175 for line in emoji_data_lines: 176 codepoints_range, emoji_property = codepoints_and_emoji_prop(line) 177 is_emoji_style = emoji_property == EMOJI_PRESENTATION_STR 178 if is_emoji_style: 179 codepoints = [to_hex_str(x) for x in 180 codepoints_for_emojirange(codepoints_range)] 181 emojis_set.update(codepoints) 182 183 emoji_style_exceptions = get_emoji_style_exceptions(unicode_path) 184 # finally add the android default emoji exceptions 185 emojis_set.update([to_hex_str(x) for x in emoji_style_exceptions]) 186 187 emojis_list = list(emojis_set) 188 emojis_list.sort() 189 with open(TEST_DATA_PATH, "w") as test_file: 190 for line in emojis_list: 191 test_file.write("%s\n" % line) 192 193 class _EmojiData(object): 194 """Holds the information about a single emoji.""" 195 196 def __init__(self, codepoints, is_emoji_style): 197 self.codepoints = codepoints 198 self.emoji_style = is_emoji_style 199 self.emoji_id = 0 200 self.width = 0 201 self.height = 0 202 self.sdk_added = SDK_VERSION 203 self.compat_added = METADATA_VERSION 204 205 def update_metrics(self, metrics): 206 """Updates width/height instance variables with the values given in metrics dictionary. 207 :param metrics: a dictionary object that has width and height values. 208 """ 209 self.width = metrics.width 210 self.height = metrics.height 211 212 def __repr__(self): 213 return '<EmojiData {0} - {1}>'.format(self.emoji_style, 214 codepoint_to_string(self.codepoints)) 215 216 def create_json_element(self): 217 """Creates the json representation of EmojiData.""" 218 json_element = {} 219 json_element['id'] = self.emoji_id 220 json_element['emojiStyle'] = self.emoji_style 221 json_element['sdkAdded'] = self.sdk_added 222 json_element['compatAdded'] = self.compat_added 223 json_element['width'] = self.width 224 json_element['height'] = self.height 225 json_element['codepoints'] = self.codepoints 226 return json_element 227 228 def create_txt_row(self): 229 """Creates array of values for CSV of EmojiData.""" 230 row = [to_hex_str(self.emoji_id), self.sdk_added, self.compat_added] 231 row += [to_hex_str(x) for x in self.codepoints] 232 return row 233 234 def update(self, emoji_id, sdk_added, compat_added): 235 """Updates current EmojiData with the values in a json element""" 236 self.emoji_id = emoji_id 237 self.sdk_added = sdk_added 238 self.compat_added = compat_added 239 240 241 def read_emoji_lines(file_path, optional=False): 242 """Read all lines in an unicode emoji file into a list of uppercase strings. Ignore the empty 243 lines and comments 244 :param file_path: unicode emoji file path 245 :param optional: if True no exception is raised when the file cannot be read 246 :return: list of uppercase strings 247 """ 248 result = [] 249 try: 250 with open(file_path) as file_stream: 251 for line in file_stream: 252 line = line.strip() 253 if line and not line.startswith('#'): 254 result.append(line.upper()) 255 except IOError: 256 if optional: 257 pass 258 else: 259 raise 260 261 return result 262 263 def get_emoji_style_exceptions(unicode_path): 264 """Read EMOJI_STYLE_OVERRIDE_FILE and return the codepoints as integers""" 265 lines = read_emoji_lines(os.path.join(unicode_path, EMOJI_STYLE_OVERRIDE_FILE)) 266 exceptions = [] 267 for line in lines: 268 codepoint = hex_str_to_int(codepoints_and_emoji_prop(line)[0]) 269 exceptions.append(codepoint) 270 return exceptions 271 272 def codepoints_for_emojirange(codepoints_range): 273 """ Return codepoints given in emoji files. Expand the codepoints that are given as a range 274 such as XYZ ... UVT 275 """ 276 codepoints = [] 277 if '..' in codepoints_range: 278 range_start, range_end = codepoints_range.split('..') 279 codepoints_range = range(hex_str_to_int(range_start), 280 hex_str_to_int(range_end) + 1) 281 codepoints.extend(codepoints_range) 282 else: 283 codepoints.append(hex_str_to_int(codepoints_range)) 284 return codepoints 285 286 def codepoints_and_emoji_prop(line): 287 """For a given emoji file line, return codepoints and emoji property in the line. 288 1F93C..1F93E ; [Emoji|Emoji_Presentation|Emoji_Modifier_Base] # [...]""" 289 line = line.strip() 290 if '#' in line: 291 line = line[:line.index('#')] 292 else: 293 raise ValueError("Line is expected to have # in it") 294 line = line.split(';') 295 codepoints_range = line[0].strip() 296 emoji_property = line[1].strip() 297 298 return codepoints_range, emoji_property 299 300 def read_emoji_intervals(emoji_data_map, file_path, emoji_style_exceptions): 301 """Read unicode lines of unicode emoji file in which each line describes a set of codepoint 302 intervals. Expands the interval on a line and inserts related EmojiDatas into emoji_data_map. 303 A line format that is expected is as follows: 304 1F93C..1F93E ; [Emoji|Emoji_Presentation|Emoji_Modifier_Base] # [...]""" 305 lines = read_emoji_lines(file_path) 306 307 for line in lines: 308 codepoints_range, emoji_property = codepoints_and_emoji_prop(line) 309 is_emoji_style = emoji_property == EMOJI_PRESENTATION_STR 310 codepoints = codepoints_for_emojirange(codepoints_range) 311 312 for codepoint in codepoints: 313 key = codepoint_to_string([codepoint]) 314 codepoint_is_emoji_style = is_emoji_style or codepoint in emoji_style_exceptions 315 if key in emoji_data_map: 316 # since there are multiple definitions of emojis, only update when emoji style is 317 # True 318 if codepoint_is_emoji_style: 319 emoji_data_map[key].emoji_style = True 320 else: 321 emoji_data = _EmojiData([codepoint], codepoint_is_emoji_style) 322 emoji_data_map[key] = emoji_data 323 324 325 def read_emoji_sequences(emoji_data_map, file_path, optional=False): 326 """Reads the content of the file which contains emoji sequences. Creates EmojiData for each 327 line and puts into emoji_data_map.""" 328 lines = read_emoji_lines(file_path, optional) 329 # 1F1E6 1F1E8 ; Name ; [...] 330 for line in lines: 331 codepoints = [hex_str_to_int(x) for x in line.split(';')[0].strip().split(' ')] 332 codepoints = [x for x in codepoints if x != EMOJI_STYLE_VS] 333 key = codepoint_to_string(codepoints) 334 if not key in emoji_data_map: 335 emoji_data = _EmojiData(codepoints, False) 336 emoji_data_map[key] = emoji_data 337 338 339 def load_emoji_data_map(unicode_path): 340 """Reads the emoji data files, constructs a map of space separated codepoints to EmojiData. 341 :return: map of space separated codepoints to EmojiData 342 """ 343 emoji_data_map = {} 344 emoji_style_exceptions = get_emoji_style_exceptions(unicode_path) 345 read_emoji_intervals(emoji_data_map, os.path.join(unicode_path, EMOJI_DATA_FILE), 346 emoji_style_exceptions) 347 read_emoji_sequences(emoji_data_map, os.path.join(unicode_path, EMOJI_ZWJ_FILE)) 348 read_emoji_sequences(emoji_data_map, os.path.join(unicode_path, EMOJI_SEQ_FILE)) 349 350 # Add the optional ANDROID_EMOJI_ZWJ_SEQ_FILE if it exists. 351 read_emoji_sequences(emoji_data_map, os.path.join(unicode_path, ANDROID_EMOJI_ZWJ_SEQ_FILE), 352 optional=True) 353 # Add the optional ANDROID_EMOJIS_SEQ_FILE if it exists. 354 read_emoji_sequences(emoji_data_map, os.path.join(unicode_path, ANDROID_EMOJIS_SEQ_FILE), 355 optional=True) 356 357 return emoji_data_map 358 359 360 def load_previous_metadata(emoji_data_map): 361 """Updates emoji data elements in emoji_data_map using the id, sdk_added and compat_added fields 362 in emoji_metadata.txt. Returns the smallest available emoji id to use. i.e. if the largest 363 emoji id emoji_metadata.txt is 1, function would return 2. If emoji_metadata.txt does not 364 exist, or contains no emojis defined returns DEFAULT_EMOJI_ID""" 365 current_emoji_id = DEFAULT_EMOJI_ID 366 if os.path.isfile(INPUT_META_FILE): 367 with open(INPUT_META_FILE) as csvfile: 368 reader = csv.reader(csvfile, delimiter=' ') 369 for row in reader: 370 if row[0].startswith('#'): 371 continue 372 emoji_id = hex_str_to_int(row[0]) 373 sdk_added = int(row[1]) 374 compat_added = int(row[2]) 375 key = codepoint_to_string(hex_str_to_int(x) for x in row[3:]) 376 if key in emoji_data_map: 377 emoji_data = emoji_data_map[key] 378 emoji_data.update(emoji_id, sdk_added, compat_added) 379 if emoji_data.emoji_id >= current_emoji_id: 380 current_emoji_id = emoji_data.emoji_id + 1 381 382 return current_emoji_id 383 384 385 def update_ttlib_orig_sort(): 386 """Updates the ttLib tag sort with a closure that makes the meta table first.""" 387 orig_sort = ttLib.sortedTagList 388 389 def meta_first_table_sort(tag_list, table_order=None): 390 """Sorts the tables with the original ttLib sort, then makes the meta table first.""" 391 tag_list = orig_sort(tag_list, table_order) 392 tag_list.remove('meta') 393 tag_list.insert(0, 'meta') 394 return tag_list 395 396 ttLib.sortedTagList = meta_first_table_sort 397 398 399 def inject_meta_into_font(ttf, flatbuffer_bin_filename): 400 """inject metadata binary into font""" 401 if not 'meta' in ttf: 402 ttf['meta'] = ttLib.getTableClass('meta')() 403 meta = ttf['meta'] 404 with contextlib.closing(open(flatbuffer_bin_filename)) as flatbuffer_bin_file: 405 meta.data[EMOJI_META_TAG_NAME] = flatbuffer_bin_file.read() 406 407 # sort meta tables for faster access 408 update_ttlib_orig_sort() 409 410 411 def validate_input_files(font_path, unicode_path): 412 """Validate the existence of font file and the unicode files""" 413 if not os.path.isfile(font_path): 414 raise ValueError("Font file does not exist: " + font_path) 415 416 if not os.path.isdir(unicode_path): 417 raise ValueError( 418 "Unicode directory does not exist or is not a directory " + unicode_path) 419 420 emoji_filenames = [os.path.join(unicode_path, EMOJI_DATA_FILE), 421 os.path.join(unicode_path, EMOJI_ZWJ_FILE), 422 os.path.join(unicode_path, EMOJI_SEQ_FILE)] 423 for emoji_filename in emoji_filenames: 424 if not os.path.isfile(emoji_filename): 425 raise ValueError("Unicode emoji data file does not exist: " + emoji_filename) 426 427 428 class EmojiFontCreator(object): 429 """Creates the EmojiCompat font""" 430 431 def __init__(self, font_path, unicode_path): 432 validate_input_files(font_path, unicode_path) 433 434 self.font_path = font_path 435 self.unicode_path = unicode_path 436 self.emoji_data_map = {} 437 self.remapped_codepoints = {} 438 self.glyph_to_image_metrics_map = {} 439 # set default emoji id to start of Supplemental Private Use Area-A 440 self.emoji_id = DEFAULT_EMOJI_ID 441 442 def update_emoji_data(self, codepoints, glyph_name): 443 """Updates the existing EmojiData identified with codepoints. The fields that are set are: 444 - emoji_id (if it does not exist) 445 - image width/height""" 446 key = codepoint_to_string(codepoints) 447 if key in self.emoji_data_map: 448 # add emoji to final data 449 emoji_data = self.emoji_data_map[key] 450 emoji_data.update_metrics(self.glyph_to_image_metrics_map[glyph_name]) 451 if emoji_data.emoji_id == 0: 452 emoji_data.emoji_id = self.emoji_id 453 self.emoji_id = self.emoji_id + 1 454 self.remapped_codepoints[emoji_data.emoji_id] = glyph_name 455 456 def read_cbdt(self, ttf): 457 """Read image size data from CBDT.""" 458 cbdt = ttf['CBDT'] 459 for strike_data in cbdt.strikeData: 460 for key, data in strike_data.iteritems(): 461 data.decompile() 462 self.glyph_to_image_metrics_map[key] = data.metrics 463 464 def read_cmap12(self, ttf, glyph_to_codepoint_map): 465 """Reads single code point emojis that are in cmap12, updates glyph_to_codepoint_map and 466 finally clears all elements in CMAP 12""" 467 cmap = ttf['cmap'] 468 for table in cmap.tables: 469 if table.format == 12 and table.platformID == 3 and table.platEncID == 10: 470 for codepoint, glyph_name in table.cmap.iteritems(): 471 glyph_to_codepoint_map[glyph_name] = codepoint 472 self.update_emoji_data([codepoint], glyph_name) 473 return table 474 raise ValueError("Font doesn't contain cmap with format:12, platformID:3 and platEncID:10") 475 476 def read_gsub(self, ttf, glyph_to_codepoint_map): 477 """Reads the emoji sequences defined in GSUB and clear all elements under GSUB""" 478 gsub = ttf['GSUB'] 479 for lookup in gsub.table.LookupList.Lookup: 480 for subtable in lookup.SubTable: 481 if hasattr(subtable, 'ligatures'): 482 for name, ligatures in subtable.ligatures.iteritems(): 483 for ligature in ligatures: 484 glyph_names = [name] + ligature.Component 485 codepoints = [glyph_to_codepoint_map[x] for x in glyph_names] 486 self.update_emoji_data(codepoints, ligature.LigGlyph) 487 488 def write_metadata_json(self, output_json_file_path): 489 """Writes the emojis into a json file""" 490 output_json = {} 491 output_json['version'] = METADATA_VERSION 492 output_json['list'] = [] 493 494 emoji_data_list = sorted(self.emoji_data_map.values(), key=lambda x: x.emoji_id) 495 496 total_emoji_count = 0 497 for emoji_data in emoji_data_list: 498 element = emoji_data.create_json_element() 499 output_json['list'].append(element) 500 total_emoji_count = total_emoji_count + 1 501 502 # write the new json file to be processed by FlatBuffers 503 with open(output_json_file_path, 'w') as json_file: 504 print(json.dumps(output_json, indent=4, sort_keys=True, separators=(',', ':')), 505 file=json_file) 506 507 return total_emoji_count 508 509 def write_metadata_csv(self): 510 """Writes emoji metadata into space separated file""" 511 with open(OUTPUT_META_FILE, 'w') as csvfile: 512 csvwriter = csv.writer(csvfile, delimiter=' ') 513 emoji_data_list = sorted(self.emoji_data_map.values(), key=lambda x: x.emoji_id) 514 csvwriter.writerow(['#id', 'sdkAdded', 'compatAdded', 'codepoints']) 515 for emoji_data in emoji_data_list: 516 csvwriter.writerow(emoji_data.create_txt_row()) 517 518 def create_font(self): 519 """Creates the EmojiCompat font. 520 :param font_path: path to Android NotoColorEmoji font 521 :param unicode_path: path to directory that contains unicode files 522 """ 523 524 tmp_dir = tempfile.mkdtemp() 525 526 # create emoji codepoints to EmojiData map 527 self.emoji_data_map = load_emoji_data_map(self.unicode_path) 528 529 # read previous metadata file to update id, sdkAdded and compatAdded. emoji id that is 530 # returned is either default or 1 greater than the largest id in previous data 531 self.emoji_id = load_previous_metadata(self.emoji_data_map) 532 533 # recalcTimestamp parameter will keep the modified field same as the original font. Changing 534 # the modified field in the font causes the font ttf file to change, which makes it harder 535 # to understand if something really changed in the font. 536 with contextlib.closing(ttLib.TTFont(self.font_path, recalcTimestamp=False)) as ttf: 537 # read image size data 538 self.read_cbdt(ttf) 539 540 # glyph name to codepoint map 541 glyph_to_codepoint_map = {} 542 543 # read single codepoint emojis under cmap12 and clear the table contents 544 cmap12_table = self.read_cmap12(ttf, glyph_to_codepoint_map) 545 546 # read emoji sequences gsub and clear the table contents 547 self.read_gsub(ttf, glyph_to_codepoint_map) 548 549 # add all new codepoint to glyph mappings 550 cmap12_table.cmap.update(self.remapped_codepoints) 551 552 output_json_file = os.path.join(tmp_dir, OUTPUT_JSON_FILE_NAME) 553 flatbuffer_bin_file = os.path.join(tmp_dir, FLATBUFFER_BIN) 554 flatbuffer_java_dir = os.path.join(tmp_dir, FLATBUFFER_JAVA_PATH) 555 556 total_emoji_count = self.write_metadata_json(output_json_file) 557 self.write_metadata_csv() 558 559 # create the flatbuffers binary and java classes 560 sys_command = 'flatc -o {0} -b -j {1} {2}' 561 os.system(sys_command.format(tmp_dir, FLATBUFFER_SCHEMA, output_json_file)) 562 563 # inject metadata binary into font 564 inject_meta_into_font(ttf, flatbuffer_bin_file) 565 566 # save the new font 567 ttf.save(FONT_PATH) 568 569 update_flatbuffer_java_files(flatbuffer_java_dir) 570 571 create_test_data(self.unicode_path) 572 573 # clear the tmp output directory 574 shutil.rmtree(tmp_dir, ignore_errors=True) 575 576 print( 577 "{0} emojis are written to\n{1}".format(total_emoji_count, FONT_DIR)) 578 579 580 def print_usage(): 581 """Prints how to use the script.""" 582 print("Please specify a path to font and unicode files.\n" 583 "usage: createfont.py noto-color-emoji-path unicode-dir-path") 584 585 586 if __name__ == '__main__': 587 if len(sys.argv) < 3: 588 print_usage() 589 sys.exit(1) 590 EmojiFontCreator(sys.argv[1], sys.argv[2]).create_font() 591