• Home
  • History
  • Annotate
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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"""
18Creates the EmojiCompat font with the metadata. Metadata is embedded in FlatBuffers binary format
19under a meta tag with name 'Emji'.
20
21In order to create the final font the followings are used as inputs:
22
23- NotoColorEmoji.ttf: Emoji font in the Android framework. Currently at
24external/noto-fonts/emoji/NotoColorEmoji.ttf
25
26- Unicode files: Unicode files that are in the framework, and lists information about all the
27emojis. These files are emoji-data.txt, emoji-sequences.txt, emoji-zwj-sequences.txt,
28and 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
31in 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
34Android OS version that the emoji was added (sdkAdded), and finally the first EmojiCompat font
35version 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
39After 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
46from __future__ import print_function
47
48import contextlib
49import csv
50import json
51import os
52import shutil
53import sys
54import tempfile
55from fontTools import ttLib
56
57
58########### UPDATE OR CHECK WHEN A NEW FONT IS BEING GENERATED ###########
59# Last Android SDK Version
60SDK_VERSION = 26
61# metadata version that will be embedded into font.
62METADATA_VERSION = 2
63
64####### main directories where output files are created #######
65SCRIPT_DIR = os.path.abspath(os.path.dirname(__file__))
66FONT_DIR = os.path.join(SCRIPT_DIR, 'font')
67DATA_DIR = os.path.join(SCRIPT_DIR, 'data')
68SUPPORTED_EMOJIS_DIR = os.path.join(SCRIPT_DIR, 'supported-emojis')
69JAVA_SRC_DIR = os.path.join(SCRIPT_DIR, 'src', 'java')
70####### output files #######
71# font file
72FONT_PATH = os.path.join(FONT_DIR, 'NotoColorEmojiCompat.ttf')
73# emoji metadata json output file
74OUTPUT_META_FILE = os.path.join(DATA_DIR, 'emoji_metadata.txt')
75# emojis test file
76TEST_DATA_PATH = os.path.join(SUPPORTED_EMOJIS_DIR, 'emojis.txt')
77####### input files #######
78# Unicode file names to read emoji data
79EMOJI_DATA_FILE = 'emoji-data.txt'
80EMOJI_SEQ_FILE = 'emoji-sequences.txt'
81EMOJI_ZWJ_FILE = 'emoji-zwj-sequences.txt'
82EMOJI_VARIATION_SEQ_FILE = 'emoji-variation-sequences.txt'
83# Android OS emoji file for emojis that are not in Unicode files
84ANDROID_EMOJI_ZWJ_SEQ_FILE = os.path.join('additions', 'emoji-zwj-sequences.txt')
85ANDROID_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>.
88EMOJI_STYLE_OVERRIDE_FILE = os.path.join('additions', 'emoji-data.txt')
89# emoji metadata file
90INPUT_META_FILE = OUTPUT_META_FILE
91# flatbuffer schema
92FLATBUFFER_SCHEMA = os.path.join(DATA_DIR, 'emoji_metadata.fbs')
93# file path for java header, it will be prepended to flatbuffer java files
94FLATBUFFER_HEADER = os.path.join(DATA_DIR, "flatbuffer_header.txt")
95# temporary emoji metadata json output file
96OUTPUT_JSON_FILE_NAME = 'emoji_metadata.json'
97# temporary binary file generated by flatbuffer
98FLATBUFFER_BIN = 'emoji_metadata.bin'
99# directory representation for flatbuffer java package
100FLATBUFFER_PACKAGE_PATH = os.path.join('android', 'support', 'text', 'emoji', 'flatbuffer', '')
101# temporary directory that contains flatbuffer java files
102FLATBUFFER_JAVA_PATH = os.path.join(FLATBUFFER_PACKAGE_PATH)
103FLATBUFFER_METADATA_LIST_JAVA = "MetadataList.java"
104FLATBUFFER_METADATA_ITEM_JAVA = "MetadataItem.java"
105# directory under source where flatbuffer java files will be copied into
106FLATBUFFER_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.
109EMOJI_META_TAG_NAME = 'Emji'
110
111EMOJI_PRESENTATION_STR = 'EMOJI_PRESENTATION'
112STD_VARIANTS_EMOJI_STYLE = 'EMOJI STYLE'
113
114DEFAULT_EMOJI_ID = 0xF0001
115EMOJI_STYLE_VS = 0xFE0F
116
117def to_hex_str(value):
118    """Converts given int value to hex without the 0x prefix"""
119    return format(value, 'X')
120
121def hex_str_to_int(string):
122    """Convert a hex string into int"""
123    return int(string, 16)
124
125def 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
129def 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
140def 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
153def 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
193class _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
241def 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
263def 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
272def 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
286def 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
300def 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
325def 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
339def 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
360def 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
385def 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
399def 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
411def 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
428class 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
580def 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
586if __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