xref: /aosp_15_r20/frameworks/base/tools/fonts/fontchain_linter.py (revision d57664e9bc4670b3ecf6748a746a57c557b6bc9e)
1*d57664e9SAndroid Build Coastguard Worker#!/usr/bin/env python
2*d57664e9SAndroid Build Coastguard Worker
3*d57664e9SAndroid Build Coastguard Workerimport collections
4*d57664e9SAndroid Build Coastguard Workerimport copy
5*d57664e9SAndroid Build Coastguard Workerimport glob
6*d57664e9SAndroid Build Coastguard Workerfrom os import path
7*d57664e9SAndroid Build Coastguard Workerimport re
8*d57664e9SAndroid Build Coastguard Workerimport sys
9*d57664e9SAndroid Build Coastguard Workerfrom xml.etree import ElementTree
10*d57664e9SAndroid Build Coastguard Worker
11*d57664e9SAndroid Build Coastguard Workerfrom fontTools import ttLib
12*d57664e9SAndroid Build Coastguard Worker
13*d57664e9SAndroid Build Coastguard Worker# TODO(nona): Remove hard coded font version and unicode versions.
14*d57664e9SAndroid Build Coastguard Worker# Figure out a way of giving this information with command lines.
15*d57664e9SAndroid Build Coastguard WorkerEMOJI_FONT_TO_UNICODE_MAP = {
16*d57664e9SAndroid Build Coastguard Worker    '2.034': 15.0,
17*d57664e9SAndroid Build Coastguard Worker    '2.042': 15.1,
18*d57664e9SAndroid Build Coastguard Worker    '2.047': 16.0,
19*d57664e9SAndroid Build Coastguard Worker}
20*d57664e9SAndroid Build Coastguard Worker
21*d57664e9SAndroid Build Coastguard WorkerEMOJI_VS = 0xFE0F
22*d57664e9SAndroid Build Coastguard Worker
23*d57664e9SAndroid Build Coastguard WorkerLANG_TO_SCRIPT = {
24*d57664e9SAndroid Build Coastguard Worker    'af': 'Latn',
25*d57664e9SAndroid Build Coastguard Worker    'as': 'Beng',
26*d57664e9SAndroid Build Coastguard Worker    'am': 'Latn',
27*d57664e9SAndroid Build Coastguard Worker    'be': 'Cyrl',
28*d57664e9SAndroid Build Coastguard Worker    'bg': 'Cyrl',
29*d57664e9SAndroid Build Coastguard Worker    'bn': 'Beng',
30*d57664e9SAndroid Build Coastguard Worker    'cs': 'Latn',
31*d57664e9SAndroid Build Coastguard Worker    'cu': 'Cyrl',
32*d57664e9SAndroid Build Coastguard Worker    'cy': 'Latn',
33*d57664e9SAndroid Build Coastguard Worker    'da': 'Latn',
34*d57664e9SAndroid Build Coastguard Worker    'de': 'Latn',
35*d57664e9SAndroid Build Coastguard Worker    'el': 'Latn',
36*d57664e9SAndroid Build Coastguard Worker    'en': 'Latn',
37*d57664e9SAndroid Build Coastguard Worker    'es': 'Latn',
38*d57664e9SAndroid Build Coastguard Worker    'et': 'Latn',
39*d57664e9SAndroid Build Coastguard Worker    'eu': 'Latn',
40*d57664e9SAndroid Build Coastguard Worker    'fr': 'Latn',
41*d57664e9SAndroid Build Coastguard Worker    'ga': 'Latn',
42*d57664e9SAndroid Build Coastguard Worker    'gl': 'Latn',
43*d57664e9SAndroid Build Coastguard Worker    'gu': 'Gujr',
44*d57664e9SAndroid Build Coastguard Worker    'hi': 'Deva',
45*d57664e9SAndroid Build Coastguard Worker    'hr': 'Latn',
46*d57664e9SAndroid Build Coastguard Worker    'hu': 'Latn',
47*d57664e9SAndroid Build Coastguard Worker    'hy': 'Armn',
48*d57664e9SAndroid Build Coastguard Worker    'it': 'Latn',
49*d57664e9SAndroid Build Coastguard Worker    'ja': 'Jpan',
50*d57664e9SAndroid Build Coastguard Worker    'ka': 'Latn',
51*d57664e9SAndroid Build Coastguard Worker    'kn': 'Knda',
52*d57664e9SAndroid Build Coastguard Worker    'ko': 'Kore',
53*d57664e9SAndroid Build Coastguard Worker    'la': 'Latn',
54*d57664e9SAndroid Build Coastguard Worker    'lt': 'Latn',
55*d57664e9SAndroid Build Coastguard Worker    'lv': 'Latn',
56*d57664e9SAndroid Build Coastguard Worker    'ml': 'Mlym',
57*d57664e9SAndroid Build Coastguard Worker    'mn': 'Cyrl',
58*d57664e9SAndroid Build Coastguard Worker    'mr': 'Deva',
59*d57664e9SAndroid Build Coastguard Worker    'nb': 'Latn',
60*d57664e9SAndroid Build Coastguard Worker    'nl': 'Latn',
61*d57664e9SAndroid Build Coastguard Worker    'nn': 'Latn',
62*d57664e9SAndroid Build Coastguard Worker    'or': 'Orya',
63*d57664e9SAndroid Build Coastguard Worker    'pa': 'Guru',
64*d57664e9SAndroid Build Coastguard Worker    'pt': 'Latn',
65*d57664e9SAndroid Build Coastguard Worker    'pl': 'Latn',
66*d57664e9SAndroid Build Coastguard Worker    'ru': 'Latn',
67*d57664e9SAndroid Build Coastguard Worker    'sk': 'Latn',
68*d57664e9SAndroid Build Coastguard Worker    'sl': 'Latn',
69*d57664e9SAndroid Build Coastguard Worker    'sq': 'Latn',
70*d57664e9SAndroid Build Coastguard Worker    'sv': 'Latn',
71*d57664e9SAndroid Build Coastguard Worker    'ta': 'Taml',
72*d57664e9SAndroid Build Coastguard Worker    'te': 'Telu',
73*d57664e9SAndroid Build Coastguard Worker    'tk': 'Latn',
74*d57664e9SAndroid Build Coastguard Worker    'uk': 'Latn',
75*d57664e9SAndroid Build Coastguard Worker}
76*d57664e9SAndroid Build Coastguard Worker
77*d57664e9SAndroid Build Coastguard Workerdef lang_to_script(lang_code):
78*d57664e9SAndroid Build Coastguard Worker    lang = lang_code.lower()
79*d57664e9SAndroid Build Coastguard Worker    while lang not in LANG_TO_SCRIPT:
80*d57664e9SAndroid Build Coastguard Worker        hyphen_idx = lang.rfind('-')
81*d57664e9SAndroid Build Coastguard Worker        assert hyphen_idx != -1, (
82*d57664e9SAndroid Build Coastguard Worker            'We do not know what script the "%s" language is written in.'
83*d57664e9SAndroid Build Coastguard Worker            % lang_code)
84*d57664e9SAndroid Build Coastguard Worker        assumed_script = lang[hyphen_idx+1:]
85*d57664e9SAndroid Build Coastguard Worker        if len(assumed_script) == 4 and assumed_script.isalpha():
86*d57664e9SAndroid Build Coastguard Worker            # This is actually the script
87*d57664e9SAndroid Build Coastguard Worker            return assumed_script.title()
88*d57664e9SAndroid Build Coastguard Worker        lang = lang[:hyphen_idx]
89*d57664e9SAndroid Build Coastguard Worker    return LANG_TO_SCRIPT[lang]
90*d57664e9SAndroid Build Coastguard Worker
91*d57664e9SAndroid Build Coastguard Worker
92*d57664e9SAndroid Build Coastguard Workerdef printable(inp):
93*d57664e9SAndroid Build Coastguard Worker    if type(inp) is set:  # set of character sequences
94*d57664e9SAndroid Build Coastguard Worker        return '{' + ', '.join([printable(seq) for seq in inp]) + '}'
95*d57664e9SAndroid Build Coastguard Worker    if type(inp) is tuple:  # character sequence
96*d57664e9SAndroid Build Coastguard Worker        return '<' + (', '.join([printable(ch) for ch in inp])) + '>'
97*d57664e9SAndroid Build Coastguard Worker    else:  # single character
98*d57664e9SAndroid Build Coastguard Worker        return 'U+%04X' % inp
99*d57664e9SAndroid Build Coastguard Worker
100*d57664e9SAndroid Build Coastguard Worker
101*d57664e9SAndroid Build Coastguard Workerdef open_font(font):
102*d57664e9SAndroid Build Coastguard Worker    font_file, index = font
103*d57664e9SAndroid Build Coastguard Worker    font_path = path.join(_fonts_dir, font_file)
104*d57664e9SAndroid Build Coastguard Worker    if index is not None:
105*d57664e9SAndroid Build Coastguard Worker        return ttLib.TTFont(font_path, fontNumber=index)
106*d57664e9SAndroid Build Coastguard Worker    else:
107*d57664e9SAndroid Build Coastguard Worker        return ttLib.TTFont(font_path)
108*d57664e9SAndroid Build Coastguard Worker
109*d57664e9SAndroid Build Coastguard Worker
110*d57664e9SAndroid Build Coastguard Workerdef get_best_cmap(font):
111*d57664e9SAndroid Build Coastguard Worker    ttfont = open_font(font)
112*d57664e9SAndroid Build Coastguard Worker    all_unicode_cmap = None
113*d57664e9SAndroid Build Coastguard Worker    bmp_cmap = None
114*d57664e9SAndroid Build Coastguard Worker    for cmap in ttfont['cmap'].tables:
115*d57664e9SAndroid Build Coastguard Worker        specifier = (cmap.format, cmap.platformID, cmap.platEncID)
116*d57664e9SAndroid Build Coastguard Worker        if specifier == (4, 3, 1):
117*d57664e9SAndroid Build Coastguard Worker            assert bmp_cmap is None, 'More than one BMP cmap in %s' % (font, )
118*d57664e9SAndroid Build Coastguard Worker            bmp_cmap = cmap
119*d57664e9SAndroid Build Coastguard Worker        elif specifier == (12, 3, 10):
120*d57664e9SAndroid Build Coastguard Worker            assert all_unicode_cmap is None, (
121*d57664e9SAndroid Build Coastguard Worker                'More than one UCS-4 cmap in %s' % (font, ))
122*d57664e9SAndroid Build Coastguard Worker            all_unicode_cmap = cmap
123*d57664e9SAndroid Build Coastguard Worker
124*d57664e9SAndroid Build Coastguard Worker    return all_unicode_cmap.cmap if all_unicode_cmap else bmp_cmap.cmap
125*d57664e9SAndroid Build Coastguard Worker
126*d57664e9SAndroid Build Coastguard Worker
127*d57664e9SAndroid Build Coastguard Workerdef get_variation_sequences_cmap(font):
128*d57664e9SAndroid Build Coastguard Worker    ttfont = open_font(font)
129*d57664e9SAndroid Build Coastguard Worker    vs_cmap = None
130*d57664e9SAndroid Build Coastguard Worker    for cmap in ttfont['cmap'].tables:
131*d57664e9SAndroid Build Coastguard Worker        specifier = (cmap.format, cmap.platformID, cmap.platEncID)
132*d57664e9SAndroid Build Coastguard Worker        if specifier == (14, 0, 5):
133*d57664e9SAndroid Build Coastguard Worker            assert vs_cmap is None, 'More than one VS cmap in %s' % (font, )
134*d57664e9SAndroid Build Coastguard Worker            vs_cmap = cmap
135*d57664e9SAndroid Build Coastguard Worker    return vs_cmap
136*d57664e9SAndroid Build Coastguard Worker
137*d57664e9SAndroid Build Coastguard Worker
138*d57664e9SAndroid Build Coastguard Workerdef get_emoji_map(font):
139*d57664e9SAndroid Build Coastguard Worker    # Add normal characters
140*d57664e9SAndroid Build Coastguard Worker    emoji_map = copy.copy(get_best_cmap(font))
141*d57664e9SAndroid Build Coastguard Worker    reverse_cmap = {glyph: code for code, glyph in emoji_map.items() if not contains_pua(code) }
142*d57664e9SAndroid Build Coastguard Worker
143*d57664e9SAndroid Build Coastguard Worker    # Add variation sequences
144*d57664e9SAndroid Build Coastguard Worker    vs_cmap = get_variation_sequences_cmap(font)
145*d57664e9SAndroid Build Coastguard Worker    if vs_cmap:
146*d57664e9SAndroid Build Coastguard Worker        for vs in vs_cmap.uvsDict:
147*d57664e9SAndroid Build Coastguard Worker            for base, glyph in vs_cmap.uvsDict[vs]:
148*d57664e9SAndroid Build Coastguard Worker                if glyph is None:
149*d57664e9SAndroid Build Coastguard Worker                    emoji_map[(base, vs)] = emoji_map[base]
150*d57664e9SAndroid Build Coastguard Worker                else:
151*d57664e9SAndroid Build Coastguard Worker                    emoji_map[(base, vs)] = glyph
152*d57664e9SAndroid Build Coastguard Worker
153*d57664e9SAndroid Build Coastguard Worker    # Add GSUB rules
154*d57664e9SAndroid Build Coastguard Worker    ttfont = open_font(font)
155*d57664e9SAndroid Build Coastguard Worker    for lookup in ttfont['GSUB'].table.LookupList.Lookup:
156*d57664e9SAndroid Build Coastguard Worker        if lookup.LookupType != 4:
157*d57664e9SAndroid Build Coastguard Worker            # Other lookups are used in the emoji font for fallback.
158*d57664e9SAndroid Build Coastguard Worker            # We ignore them for now.
159*d57664e9SAndroid Build Coastguard Worker            continue
160*d57664e9SAndroid Build Coastguard Worker        for subtable in lookup.SubTable:
161*d57664e9SAndroid Build Coastguard Worker            ligatures = subtable.ligatures
162*d57664e9SAndroid Build Coastguard Worker            for first_glyph in ligatures:
163*d57664e9SAndroid Build Coastguard Worker                for ligature in ligatures[first_glyph]:
164*d57664e9SAndroid Build Coastguard Worker                    sequence = [first_glyph] + ligature.Component
165*d57664e9SAndroid Build Coastguard Worker                    sequence = [reverse_cmap[glyph] for glyph in sequence]
166*d57664e9SAndroid Build Coastguard Worker                    sequence = tuple(sequence)
167*d57664e9SAndroid Build Coastguard Worker                    # Make sure no starting subsequence of 'sequence' has been
168*d57664e9SAndroid Build Coastguard Worker                    # seen before.
169*d57664e9SAndroid Build Coastguard Worker                    for sub_len in range(2, len(sequence)+1):
170*d57664e9SAndroid Build Coastguard Worker                        subsequence = sequence[:sub_len]
171*d57664e9SAndroid Build Coastguard Worker                        assert subsequence not in emoji_map
172*d57664e9SAndroid Build Coastguard Worker                    emoji_map[sequence] = ligature.LigGlyph
173*d57664e9SAndroid Build Coastguard Worker
174*d57664e9SAndroid Build Coastguard Worker    return emoji_map
175*d57664e9SAndroid Build Coastguard Worker
176*d57664e9SAndroid Build Coastguard Worker
177*d57664e9SAndroid Build Coastguard Workerdef assert_font_supports_any_of_chars(font, chars):
178*d57664e9SAndroid Build Coastguard Worker    best_cmap = get_best_cmap(font)
179*d57664e9SAndroid Build Coastguard Worker    for char in chars:
180*d57664e9SAndroid Build Coastguard Worker        if char in best_cmap:
181*d57664e9SAndroid Build Coastguard Worker            return
182*d57664e9SAndroid Build Coastguard Worker    sys.exit('None of characters in %s were found in %s' % (chars, font))
183*d57664e9SAndroid Build Coastguard Worker
184*d57664e9SAndroid Build Coastguard Worker
185*d57664e9SAndroid Build Coastguard Workerdef assert_font_supports_all_of_chars(font, chars):
186*d57664e9SAndroid Build Coastguard Worker    best_cmap = get_best_cmap(font)
187*d57664e9SAndroid Build Coastguard Worker    for char in chars:
188*d57664e9SAndroid Build Coastguard Worker        assert char in best_cmap, (
189*d57664e9SAndroid Build Coastguard Worker            'U+%04X was not found in %s' % (char, font))
190*d57664e9SAndroid Build Coastguard Worker
191*d57664e9SAndroid Build Coastguard Worker
192*d57664e9SAndroid Build Coastguard Workerdef assert_font_supports_none_of_chars(font, chars, fallbackName):
193*d57664e9SAndroid Build Coastguard Worker    best_cmap = get_best_cmap(font)
194*d57664e9SAndroid Build Coastguard Worker    for char in chars:
195*d57664e9SAndroid Build Coastguard Worker        if fallbackName:
196*d57664e9SAndroid Build Coastguard Worker            assert char not in best_cmap, 'U+%04X was found in %s' % (char, font)
197*d57664e9SAndroid Build Coastguard Worker        else:
198*d57664e9SAndroid Build Coastguard Worker            assert char not in best_cmap, (
199*d57664e9SAndroid Build Coastguard Worker                'U+%04X was found in %s in fallback %s' % (char, font, fallbackName))
200*d57664e9SAndroid Build Coastguard Worker
201*d57664e9SAndroid Build Coastguard Worker
202*d57664e9SAndroid Build Coastguard Workerdef assert_font_supports_all_sequences(font, sequences):
203*d57664e9SAndroid Build Coastguard Worker    vs_dict = get_variation_sequences_cmap(font).uvsDict
204*d57664e9SAndroid Build Coastguard Worker    for base, vs in sorted(sequences):
205*d57664e9SAndroid Build Coastguard Worker        assert vs in vs_dict and (base, None) in vs_dict[vs], (
206*d57664e9SAndroid Build Coastguard Worker            '<U+%04X, U+%04X> was not found in %s' % (base, vs, font))
207*d57664e9SAndroid Build Coastguard Worker
208*d57664e9SAndroid Build Coastguard Worker
209*d57664e9SAndroid Build Coastguard Workerdef check_hyphens(hyphens_dir):
210*d57664e9SAndroid Build Coastguard Worker    # Find all the scripts that need automatic hyphenation
211*d57664e9SAndroid Build Coastguard Worker    scripts = set()
212*d57664e9SAndroid Build Coastguard Worker    for hyb_file in glob.iglob(path.join(hyphens_dir, '*.hyb')):
213*d57664e9SAndroid Build Coastguard Worker        hyb_file = path.basename(hyb_file)
214*d57664e9SAndroid Build Coastguard Worker        assert hyb_file.startswith('hyph-'), (
215*d57664e9SAndroid Build Coastguard Worker            'Unknown hyphenation file %s' % hyb_file)
216*d57664e9SAndroid Build Coastguard Worker        lang_code = hyb_file[hyb_file.index('-')+1:hyb_file.index('.')]
217*d57664e9SAndroid Build Coastguard Worker        scripts.add(lang_to_script(lang_code))
218*d57664e9SAndroid Build Coastguard Worker
219*d57664e9SAndroid Build Coastguard Worker    HYPHENS = {0x002D, 0x2010}
220*d57664e9SAndroid Build Coastguard Worker    for script in scripts:
221*d57664e9SAndroid Build Coastguard Worker        fonts = _script_to_font_map[script]
222*d57664e9SAndroid Build Coastguard Worker        assert fonts, 'No fonts found for the "%s" script' % script
223*d57664e9SAndroid Build Coastguard Worker        for font in fonts:
224*d57664e9SAndroid Build Coastguard Worker            assert_font_supports_any_of_chars(font, HYPHENS)
225*d57664e9SAndroid Build Coastguard Worker
226*d57664e9SAndroid Build Coastguard Worker
227*d57664e9SAndroid Build Coastguard Workerclass FontRecord(object):
228*d57664e9SAndroid Build Coastguard Worker    def __init__(self, name, scripts, variant, weight, style, fallback_for, font):
229*d57664e9SAndroid Build Coastguard Worker        self.name = name
230*d57664e9SAndroid Build Coastguard Worker        self.scripts = scripts
231*d57664e9SAndroid Build Coastguard Worker        self.variant = variant
232*d57664e9SAndroid Build Coastguard Worker        self.weight = weight
233*d57664e9SAndroid Build Coastguard Worker        self.style = style
234*d57664e9SAndroid Build Coastguard Worker        self.fallback_for = fallback_for
235*d57664e9SAndroid Build Coastguard Worker        self.font = font
236*d57664e9SAndroid Build Coastguard Worker
237*d57664e9SAndroid Build Coastguard Worker
238*d57664e9SAndroid Build Coastguard Workerdef parse_fonts_xml(fonts_xml_path):
239*d57664e9SAndroid Build Coastguard Worker    global _script_to_font_map, _fallback_chains, _all_fonts
240*d57664e9SAndroid Build Coastguard Worker    _script_to_font_map = collections.defaultdict(set)
241*d57664e9SAndroid Build Coastguard Worker    _fallback_chains = {}
242*d57664e9SAndroid Build Coastguard Worker    _all_fonts = []
243*d57664e9SAndroid Build Coastguard Worker    tree = ElementTree.parse(fonts_xml_path)
244*d57664e9SAndroid Build Coastguard Worker    families = tree.findall('family')
245*d57664e9SAndroid Build Coastguard Worker    # Minikin supports up to 254 but users can place their own font at the first
246*d57664e9SAndroid Build Coastguard Worker    # place. Thus, 253 is the maximum allowed number of font families in the
247*d57664e9SAndroid Build Coastguard Worker    # default collection.
248*d57664e9SAndroid Build Coastguard Worker    assert len(families) < 254, (
249*d57664e9SAndroid Build Coastguard Worker        'System font collection can contains up to 253 font families.')
250*d57664e9SAndroid Build Coastguard Worker    for family in families:
251*d57664e9SAndroid Build Coastguard Worker        name = family.get('name')
252*d57664e9SAndroid Build Coastguard Worker        variant = family.get('variant')
253*d57664e9SAndroid Build Coastguard Worker        langs = family.get('lang')
254*d57664e9SAndroid Build Coastguard Worker        ignoreAttr = family.get('ignore')
255*d57664e9SAndroid Build Coastguard Worker
256*d57664e9SAndroid Build Coastguard Worker        if name:
257*d57664e9SAndroid Build Coastguard Worker            assert variant is None, (
258*d57664e9SAndroid Build Coastguard Worker                'No variant expected for LGC font %s.' % name)
259*d57664e9SAndroid Build Coastguard Worker            assert langs is None, (
260*d57664e9SAndroid Build Coastguard Worker                'No language expected for LGC fonts %s.' % name)
261*d57664e9SAndroid Build Coastguard Worker            assert name not in _fallback_chains, 'Duplicated name entry %s' % name
262*d57664e9SAndroid Build Coastguard Worker            _fallback_chains[name] = []
263*d57664e9SAndroid Build Coastguard Worker        else:
264*d57664e9SAndroid Build Coastguard Worker            assert variant in {None, 'elegant', 'compact'}, (
265*d57664e9SAndroid Build Coastguard Worker                'Unexpected value for variant: %s' % variant)
266*d57664e9SAndroid Build Coastguard Worker
267*d57664e9SAndroid Build Coastguard Worker    trim_re = re.compile(r"^[ \n\r\t]*(.+)[ \n\r\t]*$")
268*d57664e9SAndroid Build Coastguard Worker    for family in families:
269*d57664e9SAndroid Build Coastguard Worker        name = family.get('name')
270*d57664e9SAndroid Build Coastguard Worker        variant = family.get('variant')
271*d57664e9SAndroid Build Coastguard Worker        langs = family.get('lang')
272*d57664e9SAndroid Build Coastguard Worker        ignoreAttr = family.get('ignore')
273*d57664e9SAndroid Build Coastguard Worker        ignore = ignoreAttr == 'true' or ignoreAttr == '1'
274*d57664e9SAndroid Build Coastguard Worker
275*d57664e9SAndroid Build Coastguard Worker        if ignore:
276*d57664e9SAndroid Build Coastguard Worker            continue
277*d57664e9SAndroid Build Coastguard Worker
278*d57664e9SAndroid Build Coastguard Worker        if langs:
279*d57664e9SAndroid Build Coastguard Worker            langs = langs.split()
280*d57664e9SAndroid Build Coastguard Worker            scripts = {lang_to_script(lang) for lang in langs}
281*d57664e9SAndroid Build Coastguard Worker        else:
282*d57664e9SAndroid Build Coastguard Worker            scripts = set()
283*d57664e9SAndroid Build Coastguard Worker
284*d57664e9SAndroid Build Coastguard Worker        for child in family:
285*d57664e9SAndroid Build Coastguard Worker            assert child.tag == 'font', (
286*d57664e9SAndroid Build Coastguard Worker                'Unknown tag <%s>' % child.tag)
287*d57664e9SAndroid Build Coastguard Worker            font_file = child.text.rstrip()
288*d57664e9SAndroid Build Coastguard Worker
289*d57664e9SAndroid Build Coastguard Worker            m = trim_re.match(font_file)
290*d57664e9SAndroid Build Coastguard Worker            font_file = m.group(1)
291*d57664e9SAndroid Build Coastguard Worker
292*d57664e9SAndroid Build Coastguard Worker            # In case of variable font and it supports `wght` axis, the weight attribute can be
293*d57664e9SAndroid Build Coastguard Worker            # dropped which is automatically adjusted at runtime.
294*d57664e9SAndroid Build Coastguard Worker            if 'weight' in child:
295*d57664e9SAndroid Build Coastguard Worker                weight = int(child.get('weight'))
296*d57664e9SAndroid Build Coastguard Worker                assert weight % 100 == 0, (
297*d57664e9SAndroid Build Coastguard Worker                    'Font weight "%d" is not a multiple of 100.' % weight)
298*d57664e9SAndroid Build Coastguard Worker            else:
299*d57664e9SAndroid Build Coastguard Worker                weight = None
300*d57664e9SAndroid Build Coastguard Worker
301*d57664e9SAndroid Build Coastguard Worker            # In case of variable font and it supports `ital` or `slnt` axes, the style attribute
302*d57664e9SAndroid Build Coastguard Worker            # can be dropped which is automatically adjusted at runtime.
303*d57664e9SAndroid Build Coastguard Worker            if 'style' in child:
304*d57664e9SAndroid Build Coastguard Worker                style = child.get('style')
305*d57664e9SAndroid Build Coastguard Worker                assert style in {'normal', 'italic'}, (
306*d57664e9SAndroid Build Coastguard Worker                    'Unknown style "%s"' % style)
307*d57664e9SAndroid Build Coastguard Worker            else:
308*d57664e9SAndroid Build Coastguard Worker                style = None
309*d57664e9SAndroid Build Coastguard Worker
310*d57664e9SAndroid Build Coastguard Worker            fallback_for = child.get('fallbackFor')
311*d57664e9SAndroid Build Coastguard Worker
312*d57664e9SAndroid Build Coastguard Worker            assert not name or not fallback_for, (
313*d57664e9SAndroid Build Coastguard Worker                'name and fallbackFor cannot be present at the same time')
314*d57664e9SAndroid Build Coastguard Worker            assert not fallback_for or fallback_for in _fallback_chains, (
315*d57664e9SAndroid Build Coastguard Worker                'Unknown fallback name: %s' % fallback_for)
316*d57664e9SAndroid Build Coastguard Worker
317*d57664e9SAndroid Build Coastguard Worker            index = child.get('index')
318*d57664e9SAndroid Build Coastguard Worker            if index:
319*d57664e9SAndroid Build Coastguard Worker                index = int(index)
320*d57664e9SAndroid Build Coastguard Worker
321*d57664e9SAndroid Build Coastguard Worker            if not path.exists(path.join(_fonts_dir, m.group(1))):
322*d57664e9SAndroid Build Coastguard Worker                continue # Missing font is a valid case. Just ignore the missing font files.
323*d57664e9SAndroid Build Coastguard Worker
324*d57664e9SAndroid Build Coastguard Worker            record = FontRecord(
325*d57664e9SAndroid Build Coastguard Worker                name,
326*d57664e9SAndroid Build Coastguard Worker                frozenset(scripts),
327*d57664e9SAndroid Build Coastguard Worker                variant,
328*d57664e9SAndroid Build Coastguard Worker                weight,
329*d57664e9SAndroid Build Coastguard Worker                style,
330*d57664e9SAndroid Build Coastguard Worker                fallback_for,
331*d57664e9SAndroid Build Coastguard Worker                (font_file, index))
332*d57664e9SAndroid Build Coastguard Worker
333*d57664e9SAndroid Build Coastguard Worker            _all_fonts.append(record)
334*d57664e9SAndroid Build Coastguard Worker
335*d57664e9SAndroid Build Coastguard Worker            if not fallback_for:
336*d57664e9SAndroid Build Coastguard Worker                if not name or name == 'sans-serif':
337*d57664e9SAndroid Build Coastguard Worker                    for _, fallback in _fallback_chains.items():
338*d57664e9SAndroid Build Coastguard Worker                        fallback.append(record)
339*d57664e9SAndroid Build Coastguard Worker                else:
340*d57664e9SAndroid Build Coastguard Worker                    _fallback_chains[name].append(record)
341*d57664e9SAndroid Build Coastguard Worker            else:
342*d57664e9SAndroid Build Coastguard Worker                _fallback_chains[fallback_for].append(record)
343*d57664e9SAndroid Build Coastguard Worker
344*d57664e9SAndroid Build Coastguard Worker            if name: # non-empty names are used for default LGC fonts
345*d57664e9SAndroid Build Coastguard Worker                map_scripts = {'Latn', 'Grek', 'Cyrl'}
346*d57664e9SAndroid Build Coastguard Worker            else:
347*d57664e9SAndroid Build Coastguard Worker                map_scripts = scripts
348*d57664e9SAndroid Build Coastguard Worker            for script in map_scripts:
349*d57664e9SAndroid Build Coastguard Worker                _script_to_font_map[script].add((font_file, index))
350*d57664e9SAndroid Build Coastguard Worker
351*d57664e9SAndroid Build Coastguard Worker
352*d57664e9SAndroid Build Coastguard Workerdef check_emoji_coverage(all_emoji, equivalent_emoji):
353*d57664e9SAndroid Build Coastguard Worker    emoji_fonts = get_emoji_fonts()
354*d57664e9SAndroid Build Coastguard Worker    check_emoji_font_coverage(emoji_fonts, all_emoji, equivalent_emoji)
355*d57664e9SAndroid Build Coastguard Worker
356*d57664e9SAndroid Build Coastguard Worker
357*d57664e9SAndroid Build Coastguard Workerdef get_emoji_fonts():
358*d57664e9SAndroid Build Coastguard Worker    return [ record.font for record in _all_fonts if 'Zsye' in record.scripts ]
359*d57664e9SAndroid Build Coastguard Worker
360*d57664e9SAndroid Build Coastguard Workerdef seq_any(sequence, pred):
361*d57664e9SAndroid Build Coastguard Worker  if type(sequence) is tuple:
362*d57664e9SAndroid Build Coastguard Worker    return any([pred(x) for x in sequence])
363*d57664e9SAndroid Build Coastguard Worker  else:
364*d57664e9SAndroid Build Coastguard Worker    return pred(sequence)
365*d57664e9SAndroid Build Coastguard Worker
366*d57664e9SAndroid Build Coastguard Workerdef seq_all(sequence, pred):
367*d57664e9SAndroid Build Coastguard Worker  if type(sequence) is tuple:
368*d57664e9SAndroid Build Coastguard Worker    return all([pred(x) for x in sequence])
369*d57664e9SAndroid Build Coastguard Worker  else:
370*d57664e9SAndroid Build Coastguard Worker    return pred(sequence)
371*d57664e9SAndroid Build Coastguard Worker
372*d57664e9SAndroid Build Coastguard Workerdef is_regional_indicator(x):
373*d57664e9SAndroid Build Coastguard Worker    # regional indicator A..Z
374*d57664e9SAndroid Build Coastguard Worker    return 0x1F1E6 <= x <= 0x1F1FF
375*d57664e9SAndroid Build Coastguard Worker
376*d57664e9SAndroid Build Coastguard Workerdef is_flag_sequence(seq):
377*d57664e9SAndroid Build Coastguard Worker    if type(seq) == int:
378*d57664e9SAndroid Build Coastguard Worker      return False
379*d57664e9SAndroid Build Coastguard Worker    len(seq) == 2 and is_regional_indicator(seq[0]) and is_regional_indicator(seq[1])
380*d57664e9SAndroid Build Coastguard Worker
381*d57664e9SAndroid Build Coastguard Workerdef is_tag(x):
382*d57664e9SAndroid Build Coastguard Worker    # tag block
383*d57664e9SAndroid Build Coastguard Worker    return 0xE0000 <= x <= 0xE007F
384*d57664e9SAndroid Build Coastguard Worker
385*d57664e9SAndroid Build Coastguard Workerdef is_pua(x):
386*d57664e9SAndroid Build Coastguard Worker    return 0xE000 <= x <= 0xF8FF or 0xF0000 <= x <= 0xFFFFD or 0x100000 <= x <= 0x10FFFD
387*d57664e9SAndroid Build Coastguard Worker
388*d57664e9SAndroid Build Coastguard Workerdef contains_pua(sequence):
389*d57664e9SAndroid Build Coastguard Worker    return seq_any(sequence, is_pua)
390*d57664e9SAndroid Build Coastguard Worker
391*d57664e9SAndroid Build Coastguard Workerdef contains_regional_indicator(sequence):
392*d57664e9SAndroid Build Coastguard Worker    return seq_any(sequence, is_regional_indicator)
393*d57664e9SAndroid Build Coastguard Worker
394*d57664e9SAndroid Build Coastguard Workerdef only_tags(sequence):
395*d57664e9SAndroid Build Coastguard Worker    return seq_all(sequence, is_tag)
396*d57664e9SAndroid Build Coastguard Worker
397*d57664e9SAndroid Build Coastguard Workerdef get_psname(ttf):
398*d57664e9SAndroid Build Coastguard Worker    return str(next(x for x in ttf['name'].names
399*d57664e9SAndroid Build Coastguard Worker        if x.platformID == 3 and x.platEncID == 1 and x.nameID == 6))
400*d57664e9SAndroid Build Coastguard Worker
401*d57664e9SAndroid Build Coastguard Workerdef hex_strs(sequence):
402*d57664e9SAndroid Build Coastguard Worker    if type(sequence) is tuple:
403*d57664e9SAndroid Build Coastguard Worker        return tuple(f"{s:X}" for s in sequence)
404*d57664e9SAndroid Build Coastguard Worker    return hex(sequence)
405*d57664e9SAndroid Build Coastguard Worker
406*d57664e9SAndroid Build Coastguard Workerdef check_emoji_not_compat(all_emoji, equivalent_emoji):
407*d57664e9SAndroid Build Coastguard Worker    compat_psnames = set()
408*d57664e9SAndroid Build Coastguard Worker    for emoji_font in get_emoji_fonts():
409*d57664e9SAndroid Build Coastguard Worker        ttf = open_font(emoji_font)
410*d57664e9SAndroid Build Coastguard Worker        psname = get_psname(ttf)
411*d57664e9SAndroid Build Coastguard Worker
412*d57664e9SAndroid Build Coastguard Worker        if "meta" in ttf:
413*d57664e9SAndroid Build Coastguard Worker            assert 'Emji' not in ttf["meta"].data, 'NotoColorEmoji MUST be a compat font'
414*d57664e9SAndroid Build Coastguard Worker
415*d57664e9SAndroid Build Coastguard Workerdef is_flag_emoji(font):
416*d57664e9SAndroid Build Coastguard Worker    return 0x1F1E6 in get_best_cmap(font)
417*d57664e9SAndroid Build Coastguard Worker
418*d57664e9SAndroid Build Coastguard Workerdef emoji_font_version_to_unicode_version(font_version):
419*d57664e9SAndroid Build Coastguard Worker    version_str = '%.3f' % font_version
420*d57664e9SAndroid Build Coastguard Worker    assert version_str in EMOJI_FONT_TO_UNICODE_MAP, 'Unknown emoji font verion: %s' % version_str
421*d57664e9SAndroid Build Coastguard Worker    return EMOJI_FONT_TO_UNICODE_MAP[version_str]
422*d57664e9SAndroid Build Coastguard Worker
423*d57664e9SAndroid Build Coastguard Worker
424*d57664e9SAndroid Build Coastguard Workerdef check_emoji_font_coverage(emoji_fonts, all_emoji, equivalent_emoji):
425*d57664e9SAndroid Build Coastguard Worker    coverages = []
426*d57664e9SAndroid Build Coastguard Worker    emoji_font_version = 0
427*d57664e9SAndroid Build Coastguard Worker    emoji_flag_font_version = 0
428*d57664e9SAndroid Build Coastguard Worker    for emoji_font in emoji_fonts:
429*d57664e9SAndroid Build Coastguard Worker        coverages.append(get_emoji_map(emoji_font))
430*d57664e9SAndroid Build Coastguard Worker
431*d57664e9SAndroid Build Coastguard Worker        # Find the largest version of the installed emoji font.
432*d57664e9SAndroid Build Coastguard Worker        version = open_font(emoji_font)['head'].fontRevision
433*d57664e9SAndroid Build Coastguard Worker        if is_flag_emoji(emoji_font):
434*d57664e9SAndroid Build Coastguard Worker          emoji_flag_font_version = max(emoji_flag_font_version, version)
435*d57664e9SAndroid Build Coastguard Worker        else:
436*d57664e9SAndroid Build Coastguard Worker          emoji_font_version = max(emoji_font_version, version)
437*d57664e9SAndroid Build Coastguard Worker
438*d57664e9SAndroid Build Coastguard Worker    emoji_flag_unicode_version = emoji_font_version_to_unicode_version(emoji_flag_font_version)
439*d57664e9SAndroid Build Coastguard Worker    emoji_unicode_version = emoji_font_version_to_unicode_version(emoji_font_version)
440*d57664e9SAndroid Build Coastguard Worker
441*d57664e9SAndroid Build Coastguard Worker    errors = []
442*d57664e9SAndroid Build Coastguard Worker
443*d57664e9SAndroid Build Coastguard Worker    for sequence in all_emoji:
444*d57664e9SAndroid Build Coastguard Worker        if all([sequence not in coverage for coverage in coverages]):
445*d57664e9SAndroid Build Coastguard Worker            sequence_version = float(_age_by_chars[sequence])
446*d57664e9SAndroid Build Coastguard Worker            if is_flag_sequence(sequence):
447*d57664e9SAndroid Build Coastguard Worker                if sequence_version <= emoji_flag_unicode_version:
448*d57664e9SAndroid Build Coastguard Worker                    errors.append('%s is not supported in the emoji font.' % printable(sequence))
449*d57664e9SAndroid Build Coastguard Worker            else:
450*d57664e9SAndroid Build Coastguard Worker                if sequence_version <= emoji_unicode_version:
451*d57664e9SAndroid Build Coastguard Worker                    errors.append('%s is not supported in the emoji font.' % printable(sequence))
452*d57664e9SAndroid Build Coastguard Worker
453*d57664e9SAndroid Build Coastguard Worker    for coverage in coverages:
454*d57664e9SAndroid Build Coastguard Worker        for sequence in coverage:
455*d57664e9SAndroid Build Coastguard Worker            if sequence in {0x0000, 0x000D, 0x0020}:
456*d57664e9SAndroid Build Coastguard Worker                # The font needs to support a few extra characters, which is OK
457*d57664e9SAndroid Build Coastguard Worker                continue
458*d57664e9SAndroid Build Coastguard Worker
459*d57664e9SAndroid Build Coastguard Worker            if contains_pua(sequence):
460*d57664e9SAndroid Build Coastguard Worker                # The font needs to have some PUA for EmojiCompat library.
461*d57664e9SAndroid Build Coastguard Worker                continue
462*d57664e9SAndroid Build Coastguard Worker
463*d57664e9SAndroid Build Coastguard Worker            if sequence not in all_emoji:
464*d57664e9SAndroid Build Coastguard Worker                errors.append('%s support unexpected in the emoji font.' % printable(sequence))
465*d57664e9SAndroid Build Coastguard Worker
466*d57664e9SAndroid Build Coastguard Worker    for first, second in equivalent_emoji.items():
467*d57664e9SAndroid Build Coastguard Worker        for coverage in coverages:
468*d57664e9SAndroid Build Coastguard Worker            if first not in coverage or second not in coverage:
469*d57664e9SAndroid Build Coastguard Worker                continue  # sequence will be reported missing
470*d57664e9SAndroid Build Coastguard Worker            if coverage[first] != coverage[second]:
471*d57664e9SAndroid Build Coastguard Worker                errors.append('%s and %s should map to the same glyph.' % (
472*d57664e9SAndroid Build Coastguard Worker                    printable(first),
473*d57664e9SAndroid Build Coastguard Worker                    printable(second)))
474*d57664e9SAndroid Build Coastguard Worker
475*d57664e9SAndroid Build Coastguard Worker    for coverage in coverages:
476*d57664e9SAndroid Build Coastguard Worker        for glyph in set(coverage.values()):
477*d57664e9SAndroid Build Coastguard Worker            maps_to_glyph = [
478*d57664e9SAndroid Build Coastguard Worker                seq for seq in coverage if coverage[seq] == glyph and not contains_pua(seq) ]
479*d57664e9SAndroid Build Coastguard Worker            if len(maps_to_glyph) > 1:
480*d57664e9SAndroid Build Coastguard Worker                # There are more than one sequences mapping to the same glyph. We
481*d57664e9SAndroid Build Coastguard Worker                # need to make sure they were expected to be equivalent.
482*d57664e9SAndroid Build Coastguard Worker                equivalent_seqs = set()
483*d57664e9SAndroid Build Coastguard Worker                for seq in maps_to_glyph:
484*d57664e9SAndroid Build Coastguard Worker                    equivalent_seq = seq
485*d57664e9SAndroid Build Coastguard Worker                    while equivalent_seq in equivalent_emoji:
486*d57664e9SAndroid Build Coastguard Worker                        equivalent_seq = equivalent_emoji[equivalent_seq]
487*d57664e9SAndroid Build Coastguard Worker                    equivalent_seqs.add(equivalent_seq)
488*d57664e9SAndroid Build Coastguard Worker                if len(equivalent_seqs) != 1:
489*d57664e9SAndroid Build Coastguard Worker                    errors.append('The sequences %s should not result in the same glyph %s' % (
490*d57664e9SAndroid Build Coastguard Worker                        printable(equivalent_seqs),
491*d57664e9SAndroid Build Coastguard Worker                        glyph))
492*d57664e9SAndroid Build Coastguard Worker
493*d57664e9SAndroid Build Coastguard Worker    assert not errors, '%d emoji font errors:\n%s\n%d emoji font coverage errors' % (len(errors), '\n'.join(errors), len(errors))
494*d57664e9SAndroid Build Coastguard Worker
495*d57664e9SAndroid Build Coastguard Worker
496*d57664e9SAndroid Build Coastguard Workerdef check_emoji_defaults(default_emoji):
497*d57664e9SAndroid Build Coastguard Worker    missing_text_chars = _emoji_properties['Emoji'] - default_emoji
498*d57664e9SAndroid Build Coastguard Worker    for name, fallback_chain in _fallback_chains.items():
499*d57664e9SAndroid Build Coastguard Worker        emoji_font_seen = False
500*d57664e9SAndroid Build Coastguard Worker        for record in fallback_chain:
501*d57664e9SAndroid Build Coastguard Worker            if 'Zsye' in record.scripts:
502*d57664e9SAndroid Build Coastguard Worker                emoji_font_seen = True
503*d57664e9SAndroid Build Coastguard Worker                # No need to check the emoji font
504*d57664e9SAndroid Build Coastguard Worker                continue
505*d57664e9SAndroid Build Coastguard Worker            # For later fonts, we only check them if they have a script
506*d57664e9SAndroid Build Coastguard Worker            # defined, since the defined script may get them to a higher
507*d57664e9SAndroid Build Coastguard Worker            # score even if they appear after the emoji font. However,
508*d57664e9SAndroid Build Coastguard Worker            # we should skip checking the text symbols font, since
509*d57664e9SAndroid Build Coastguard Worker            # symbol fonts should be able to override the emoji display
510*d57664e9SAndroid Build Coastguard Worker            # style when 'Zsym' is explicitly specified by the user.
511*d57664e9SAndroid Build Coastguard Worker            if emoji_font_seen and (not record.scripts or 'Zsym' in record.scripts):
512*d57664e9SAndroid Build Coastguard Worker                continue
513*d57664e9SAndroid Build Coastguard Worker
514*d57664e9SAndroid Build Coastguard Worker            # Check default emoji-style characters
515*d57664e9SAndroid Build Coastguard Worker            assert_font_supports_none_of_chars(record.font, default_emoji, name)
516*d57664e9SAndroid Build Coastguard Worker
517*d57664e9SAndroid Build Coastguard Worker            # Mark default text-style characters appearing in fonts above the emoji
518*d57664e9SAndroid Build Coastguard Worker            # font as seen
519*d57664e9SAndroid Build Coastguard Worker            if not emoji_font_seen:
520*d57664e9SAndroid Build Coastguard Worker                missing_text_chars -= set(get_best_cmap(record.font))
521*d57664e9SAndroid Build Coastguard Worker
522*d57664e9SAndroid Build Coastguard Worker        # Noto does not have monochrome glyphs for Unicode 7.0 wingdings and
523*d57664e9SAndroid Build Coastguard Worker        # webdings yet.
524*d57664e9SAndroid Build Coastguard Worker        missing_text_chars -= _chars_by_age['7.0']
525*d57664e9SAndroid Build Coastguard Worker        assert missing_text_chars == set(), (
526*d57664e9SAndroid Build Coastguard Worker            'Text style version of some emoji characters are missing: ' +
527*d57664e9SAndroid Build Coastguard Worker                repr(missing_text_chars))
528*d57664e9SAndroid Build Coastguard Worker
529*d57664e9SAndroid Build Coastguard Worker
530*d57664e9SAndroid Build Coastguard Workerdef parse_unicode_seq(chars):
531*d57664e9SAndroid Build Coastguard Worker    if ' ' in chars:  # character sequence
532*d57664e9SAndroid Build Coastguard Worker        sequence = [int(ch, 16) for ch in chars.split(' ')]
533*d57664e9SAndroid Build Coastguard Worker        additions = [tuple(sequence)]
534*d57664e9SAndroid Build Coastguard Worker    elif '..' in chars:  # character range
535*d57664e9SAndroid Build Coastguard Worker        char_start, char_end = chars.split('..')
536*d57664e9SAndroid Build Coastguard Worker        char_start = int(char_start, 16)
537*d57664e9SAndroid Build Coastguard Worker        char_end = int(char_end, 16)
538*d57664e9SAndroid Build Coastguard Worker        additions = range(char_start, char_end+1)
539*d57664e9SAndroid Build Coastguard Worker    else:  # single character
540*d57664e9SAndroid Build Coastguard Worker        additions = [int(chars, 16)]
541*d57664e9SAndroid Build Coastguard Worker    return additions
542*d57664e9SAndroid Build Coastguard Worker
543*d57664e9SAndroid Build Coastguard Worker# Setting reverse to true returns a dictionary that maps the values to sets of
544*d57664e9SAndroid Build Coastguard Worker# characters, useful for some binary properties. Otherwise, we get a
545*d57664e9SAndroid Build Coastguard Worker# dictionary that maps characters to the property values, assuming there's only
546*d57664e9SAndroid Build Coastguard Worker# one property in the file.
547*d57664e9SAndroid Build Coastguard Workerdef parse_unicode_datafile(file_path, reverse=False):
548*d57664e9SAndroid Build Coastguard Worker    if reverse:
549*d57664e9SAndroid Build Coastguard Worker        output_dict = collections.defaultdict(set)
550*d57664e9SAndroid Build Coastguard Worker    else:
551*d57664e9SAndroid Build Coastguard Worker        output_dict = {}
552*d57664e9SAndroid Build Coastguard Worker    with open(file_path) as datafile:
553*d57664e9SAndroid Build Coastguard Worker        for line in datafile:
554*d57664e9SAndroid Build Coastguard Worker            if '#' in line:
555*d57664e9SAndroid Build Coastguard Worker                line = line[:line.index('#')]
556*d57664e9SAndroid Build Coastguard Worker            line = line.strip()
557*d57664e9SAndroid Build Coastguard Worker            if not line:
558*d57664e9SAndroid Build Coastguard Worker                continue
559*d57664e9SAndroid Build Coastguard Worker
560*d57664e9SAndroid Build Coastguard Worker            chars, prop = line.split(';')[:2]
561*d57664e9SAndroid Build Coastguard Worker            chars = chars.strip()
562*d57664e9SAndroid Build Coastguard Worker            prop = prop.strip()
563*d57664e9SAndroid Build Coastguard Worker
564*d57664e9SAndroid Build Coastguard Worker            additions = parse_unicode_seq(chars)
565*d57664e9SAndroid Build Coastguard Worker
566*d57664e9SAndroid Build Coastguard Worker            if reverse:
567*d57664e9SAndroid Build Coastguard Worker                output_dict[prop].update(additions)
568*d57664e9SAndroid Build Coastguard Worker            else:
569*d57664e9SAndroid Build Coastguard Worker                for addition in additions:
570*d57664e9SAndroid Build Coastguard Worker                    assert addition not in output_dict
571*d57664e9SAndroid Build Coastguard Worker                    output_dict[addition] = prop
572*d57664e9SAndroid Build Coastguard Worker    return output_dict
573*d57664e9SAndroid Build Coastguard Worker
574*d57664e9SAndroid Build Coastguard Workerdef parse_sequence_age(file_path):
575*d57664e9SAndroid Build Coastguard Worker    VERSION_RE = re.compile(r'E([\d\.]+)')
576*d57664e9SAndroid Build Coastguard Worker    output_dict = {}
577*d57664e9SAndroid Build Coastguard Worker    with open(file_path) as datafile:
578*d57664e9SAndroid Build Coastguard Worker        for line in datafile:
579*d57664e9SAndroid Build Coastguard Worker            comment = ''
580*d57664e9SAndroid Build Coastguard Worker            if '#' in line:
581*d57664e9SAndroid Build Coastguard Worker                hash_pos = line.index('#')
582*d57664e9SAndroid Build Coastguard Worker                comment = line[hash_pos + 1:].strip()
583*d57664e9SAndroid Build Coastguard Worker                line = line[:hash_pos]
584*d57664e9SAndroid Build Coastguard Worker            line = line.strip()
585*d57664e9SAndroid Build Coastguard Worker            if not line:
586*d57664e9SAndroid Build Coastguard Worker                continue
587*d57664e9SAndroid Build Coastguard Worker
588*d57664e9SAndroid Build Coastguard Worker            chars = line[:line.index(';')].strip()
589*d57664e9SAndroid Build Coastguard Worker
590*d57664e9SAndroid Build Coastguard Worker            m = VERSION_RE.match(comment)
591*d57664e9SAndroid Build Coastguard Worker            assert m, 'Version not found: unknown format: %s' % line
592*d57664e9SAndroid Build Coastguard Worker            version = m.group(1)
593*d57664e9SAndroid Build Coastguard Worker
594*d57664e9SAndroid Build Coastguard Worker            additions = parse_unicode_seq(chars)
595*d57664e9SAndroid Build Coastguard Worker
596*d57664e9SAndroid Build Coastguard Worker            for addition in additions:
597*d57664e9SAndroid Build Coastguard Worker                assert addition not in output_dict
598*d57664e9SAndroid Build Coastguard Worker                output_dict[addition] = version
599*d57664e9SAndroid Build Coastguard Worker    return output_dict
600*d57664e9SAndroid Build Coastguard Worker
601*d57664e9SAndroid Build Coastguard Workerdef parse_emoji_variants(file_path):
602*d57664e9SAndroid Build Coastguard Worker    emoji_set = set()
603*d57664e9SAndroid Build Coastguard Worker    text_set = set()
604*d57664e9SAndroid Build Coastguard Worker    with open(file_path) as datafile:
605*d57664e9SAndroid Build Coastguard Worker        for line in datafile:
606*d57664e9SAndroid Build Coastguard Worker            if '#' in line:
607*d57664e9SAndroid Build Coastguard Worker                line = line[:line.index('#')]
608*d57664e9SAndroid Build Coastguard Worker            line = line.strip()
609*d57664e9SAndroid Build Coastguard Worker            if not line:
610*d57664e9SAndroid Build Coastguard Worker                continue
611*d57664e9SAndroid Build Coastguard Worker            sequence, description, _ = line.split(';')
612*d57664e9SAndroid Build Coastguard Worker            sequence = sequence.strip().split(' ')
613*d57664e9SAndroid Build Coastguard Worker            base = int(sequence[0], 16)
614*d57664e9SAndroid Build Coastguard Worker            vs = int(sequence[1], 16)
615*d57664e9SAndroid Build Coastguard Worker            description = description.strip()
616*d57664e9SAndroid Build Coastguard Worker            if description == 'text style':
617*d57664e9SAndroid Build Coastguard Worker                text_set.add((base, vs))
618*d57664e9SAndroid Build Coastguard Worker            elif description == 'emoji style':
619*d57664e9SAndroid Build Coastguard Worker                emoji_set.add((base, vs))
620*d57664e9SAndroid Build Coastguard Worker    return text_set, emoji_set
621*d57664e9SAndroid Build Coastguard Worker
622*d57664e9SAndroid Build Coastguard Worker
623*d57664e9SAndroid Build Coastguard Workerdef parse_ucd(ucd_path):
624*d57664e9SAndroid Build Coastguard Worker    global _emoji_properties, _chars_by_age, _age_by_chars
625*d57664e9SAndroid Build Coastguard Worker    global _text_variation_sequences, _emoji_variation_sequences
626*d57664e9SAndroid Build Coastguard Worker    global _emoji_sequences, _emoji_zwj_sequences
627*d57664e9SAndroid Build Coastguard Worker    _emoji_properties = parse_unicode_datafile(
628*d57664e9SAndroid Build Coastguard Worker        path.join(ucd_path, 'emoji-data.txt'), reverse=True)
629*d57664e9SAndroid Build Coastguard Worker    emoji_properties_additions = parse_unicode_datafile(
630*d57664e9SAndroid Build Coastguard Worker        path.join(ucd_path, 'additions', 'emoji-data.txt'), reverse=True)
631*d57664e9SAndroid Build Coastguard Worker    for prop in emoji_properties_additions.keys():
632*d57664e9SAndroid Build Coastguard Worker        _emoji_properties[prop].update(emoji_properties_additions[prop])
633*d57664e9SAndroid Build Coastguard Worker
634*d57664e9SAndroid Build Coastguard Worker    _chars_by_age = parse_unicode_datafile(
635*d57664e9SAndroid Build Coastguard Worker        path.join(ucd_path, 'DerivedAge.txt'), reverse=True)
636*d57664e9SAndroid Build Coastguard Worker    _age_by_chars = parse_unicode_datafile(
637*d57664e9SAndroid Build Coastguard Worker        path.join(ucd_path, 'DerivedAge.txt'))
638*d57664e9SAndroid Build Coastguard Worker    _age_by_chars.update(parse_sequence_age(
639*d57664e9SAndroid Build Coastguard Worker        path.join(ucd_path, 'emoji-sequences.txt')))
640*d57664e9SAndroid Build Coastguard Worker    sequences = parse_emoji_variants(
641*d57664e9SAndroid Build Coastguard Worker        path.join(ucd_path, 'emoji-variation-sequences.txt'))
642*d57664e9SAndroid Build Coastguard Worker    _text_variation_sequences, _emoji_variation_sequences = sequences
643*d57664e9SAndroid Build Coastguard Worker    _emoji_sequences = parse_unicode_datafile(
644*d57664e9SAndroid Build Coastguard Worker        path.join(ucd_path, 'emoji-sequences.txt'))
645*d57664e9SAndroid Build Coastguard Worker    _emoji_sequences.update(parse_unicode_datafile(
646*d57664e9SAndroid Build Coastguard Worker        path.join(ucd_path, 'additions', 'emoji-sequences.txt')))
647*d57664e9SAndroid Build Coastguard Worker    _emoji_zwj_sequences = parse_unicode_datafile(
648*d57664e9SAndroid Build Coastguard Worker        path.join(ucd_path, 'emoji-zwj-sequences.txt'))
649*d57664e9SAndroid Build Coastguard Worker    _emoji_zwj_sequences.update(parse_unicode_datafile(
650*d57664e9SAndroid Build Coastguard Worker        path.join(ucd_path, 'additions', 'emoji-zwj-sequences.txt')))
651*d57664e9SAndroid Build Coastguard Worker
652*d57664e9SAndroid Build Coastguard Worker    exclusions = parse_unicode_datafile(path.join(ucd_path, 'additions', 'emoji-exclusions.txt'))
653*d57664e9SAndroid Build Coastguard Worker    _emoji_sequences = remove_emoji_exclude(_emoji_sequences, exclusions)
654*d57664e9SAndroid Build Coastguard Worker    _emoji_zwj_sequences = remove_emoji_exclude(_emoji_zwj_sequences, exclusions)
655*d57664e9SAndroid Build Coastguard Worker    _emoji_variation_sequences = remove_emoji_variation_exclude(_emoji_variation_sequences, exclusions)
656*d57664e9SAndroid Build Coastguard Worker    # Unicode 12.0 adds Basic_Emoji in emoji-sequences.txt. We ignore them here since we are already
657*d57664e9SAndroid Build Coastguard Worker    # checking the emoji presentations with emoji-variation-sequences.txt.
658*d57664e9SAndroid Build Coastguard Worker    # Please refer to http://unicode.org/reports/tr51/#def_basic_emoji_set .
659*d57664e9SAndroid Build Coastguard Worker    _emoji_sequences = {k: v for k, v in _emoji_sequences.items() if not v == 'Basic_Emoji' }
660*d57664e9SAndroid Build Coastguard Worker
661*d57664e9SAndroid Build Coastguard Worker
662*d57664e9SAndroid Build Coastguard Workerdef remove_emoji_variation_exclude(source, items):
663*d57664e9SAndroid Build Coastguard Worker    return source.difference(items.keys())
664*d57664e9SAndroid Build Coastguard Worker
665*d57664e9SAndroid Build Coastguard Workerdef remove_emoji_exclude(source, items):
666*d57664e9SAndroid Build Coastguard Worker    return {k: v for k, v in source.items() if k not in items}
667*d57664e9SAndroid Build Coastguard Worker
668*d57664e9SAndroid Build Coastguard Workerdef flag_sequence(territory_code):
669*d57664e9SAndroid Build Coastguard Worker    return tuple(0x1F1E6 + ord(ch) - ord('A') for ch in territory_code)
670*d57664e9SAndroid Build Coastguard Worker
671*d57664e9SAndroid Build Coastguard WorkerEQUIVALENT_FLAGS = {
672*d57664e9SAndroid Build Coastguard Worker    flag_sequence('BV'): flag_sequence('NO'),
673*d57664e9SAndroid Build Coastguard Worker    flag_sequence('CP'): flag_sequence('FR'),
674*d57664e9SAndroid Build Coastguard Worker    flag_sequence('HM'): flag_sequence('AU'),
675*d57664e9SAndroid Build Coastguard Worker    flag_sequence('SJ'): flag_sequence('NO'),
676*d57664e9SAndroid Build Coastguard Worker    flag_sequence('UM'): flag_sequence('US'),
677*d57664e9SAndroid Build Coastguard Worker}
678*d57664e9SAndroid Build Coastguard Worker
679*d57664e9SAndroid Build Coastguard WorkerCOMBINING_KEYCAP = 0x20E3
680*d57664e9SAndroid Build Coastguard Worker
681*d57664e9SAndroid Build Coastguard WorkerLEGACY_ANDROID_EMOJI = {
682*d57664e9SAndroid Build Coastguard Worker    0xFE4E5: flag_sequence('JP'),
683*d57664e9SAndroid Build Coastguard Worker    0xFE4E6: flag_sequence('US'),
684*d57664e9SAndroid Build Coastguard Worker    0xFE4E7: flag_sequence('FR'),
685*d57664e9SAndroid Build Coastguard Worker    0xFE4E8: flag_sequence('DE'),
686*d57664e9SAndroid Build Coastguard Worker    0xFE4E9: flag_sequence('IT'),
687*d57664e9SAndroid Build Coastguard Worker    0xFE4EA: flag_sequence('GB'),
688*d57664e9SAndroid Build Coastguard Worker    0xFE4EB: flag_sequence('ES'),
689*d57664e9SAndroid Build Coastguard Worker    0xFE4EC: flag_sequence('RU'),
690*d57664e9SAndroid Build Coastguard Worker    0xFE4ED: flag_sequence('CN'),
691*d57664e9SAndroid Build Coastguard Worker    0xFE4EE: flag_sequence('KR'),
692*d57664e9SAndroid Build Coastguard Worker    0xFE82C: (ord('#'), COMBINING_KEYCAP),
693*d57664e9SAndroid Build Coastguard Worker    0xFE82E: (ord('1'), COMBINING_KEYCAP),
694*d57664e9SAndroid Build Coastguard Worker    0xFE82F: (ord('2'), COMBINING_KEYCAP),
695*d57664e9SAndroid Build Coastguard Worker    0xFE830: (ord('3'), COMBINING_KEYCAP),
696*d57664e9SAndroid Build Coastguard Worker    0xFE831: (ord('4'), COMBINING_KEYCAP),
697*d57664e9SAndroid Build Coastguard Worker    0xFE832: (ord('5'), COMBINING_KEYCAP),
698*d57664e9SAndroid Build Coastguard Worker    0xFE833: (ord('6'), COMBINING_KEYCAP),
699*d57664e9SAndroid Build Coastguard Worker    0xFE834: (ord('7'), COMBINING_KEYCAP),
700*d57664e9SAndroid Build Coastguard Worker    0xFE835: (ord('8'), COMBINING_KEYCAP),
701*d57664e9SAndroid Build Coastguard Worker    0xFE836: (ord('9'), COMBINING_KEYCAP),
702*d57664e9SAndroid Build Coastguard Worker    0xFE837: (ord('0'), COMBINING_KEYCAP),
703*d57664e9SAndroid Build Coastguard Worker}
704*d57664e9SAndroid Build Coastguard Worker
705*d57664e9SAndroid Build Coastguard Worker# This is used to define the emoji that should have the same glyph.
706*d57664e9SAndroid Build Coastguard Worker# i.e. previously we had gender based Kiss (0x1F48F), which had the same glyph
707*d57664e9SAndroid Build Coastguard Worker# with Kiss: Woman, Man (0x1F469, 0x200D, 0x2764, 0x200D, 0x1F48B, 0x200D, 0x1F468)
708*d57664e9SAndroid Build Coastguard Worker# in that case a valid row would be:
709*d57664e9SAndroid Build Coastguard Worker# (0x1F469, 0x200D, 0x2764, 0x200D, 0x1F48B, 0x200D, 0x1F468): 0x1F48F,
710*d57664e9SAndroid Build Coastguard WorkerZWJ_IDENTICALS = {
711*d57664e9SAndroid Build Coastguard Worker}
712*d57664e9SAndroid Build Coastguard Worker
713*d57664e9SAndroid Build Coastguard WorkerSAME_FLAG_MAPPINGS = [
714*d57664e9SAndroid Build Coastguard Worker    # Diego Garcia and British Indian Ocean Territory
715*d57664e9SAndroid Build Coastguard Worker    ((0x1F1EE, 0x1F1F4), (0x1F1E9, 0x1F1EC)),
716*d57664e9SAndroid Build Coastguard Worker    # St. Martin and France
717*d57664e9SAndroid Build Coastguard Worker    ((0x1F1F2, 0x1F1EB), (0x1F1EB, 0x1F1F7)),
718*d57664e9SAndroid Build Coastguard Worker    # Spain and Ceuta & Melilla
719*d57664e9SAndroid Build Coastguard Worker    ((0x1F1EA, 0x1F1F8), (0x1F1EA, 0x1F1E6)),
720*d57664e9SAndroid Build Coastguard Worker]
721*d57664e9SAndroid Build Coastguard Worker
722*d57664e9SAndroid Build Coastguard WorkerZWJ = 0x200D
723*d57664e9SAndroid Build Coastguard Worker
724*d57664e9SAndroid Build Coastguard WorkerEMPTY_FLAG_SEQUENCE = (0x1F3F4, 0xE007F)
725*d57664e9SAndroid Build Coastguard Worker
726*d57664e9SAndroid Build Coastguard Workerdef is_fitzpatrick_modifier(cp):
727*d57664e9SAndroid Build Coastguard Worker    return 0x1F3FB <= cp <= 0x1F3FF
728*d57664e9SAndroid Build Coastguard Worker
729*d57664e9SAndroid Build Coastguard Worker
730*d57664e9SAndroid Build Coastguard Workerdef reverse_emoji(seq):
731*d57664e9SAndroid Build Coastguard Worker    rev = list(reversed(seq))
732*d57664e9SAndroid Build Coastguard Worker    # if there are fitzpatrick modifiers in the sequence, keep them after
733*d57664e9SAndroid Build Coastguard Worker    # the emoji they modify
734*d57664e9SAndroid Build Coastguard Worker    for i in range(1, len(rev)):
735*d57664e9SAndroid Build Coastguard Worker        if is_fitzpatrick_modifier(rev[i-1]):
736*d57664e9SAndroid Build Coastguard Worker            rev[i], rev[i-1] = rev[i-1], rev[i]
737*d57664e9SAndroid Build Coastguard Worker    return tuple(rev)
738*d57664e9SAndroid Build Coastguard Worker
739*d57664e9SAndroid Build Coastguard Worker
740*d57664e9SAndroid Build Coastguard Workerdef compute_expected_emoji():
741*d57664e9SAndroid Build Coastguard Worker    equivalent_emoji = {}
742*d57664e9SAndroid Build Coastguard Worker    sequence_pieces = set()
743*d57664e9SAndroid Build Coastguard Worker    all_sequences = set()
744*d57664e9SAndroid Build Coastguard Worker    all_sequences.update(_emoji_variation_sequences)
745*d57664e9SAndroid Build Coastguard Worker
746*d57664e9SAndroid Build Coastguard Worker    # add zwj sequences not in the current emoji-zwj-sequences.txt
747*d57664e9SAndroid Build Coastguard Worker    adjusted_emoji_zwj_sequences = dict(_emoji_zwj_sequences)
748*d57664e9SAndroid Build Coastguard Worker    adjusted_emoji_zwj_sequences.update(_emoji_zwj_sequences)
749*d57664e9SAndroid Build Coastguard Worker
750*d57664e9SAndroid Build Coastguard Worker    # Add empty flag tag sequence that is supported as fallback
751*d57664e9SAndroid Build Coastguard Worker    _emoji_sequences[EMPTY_FLAG_SEQUENCE] = 'Emoji_Tag_Sequence'
752*d57664e9SAndroid Build Coastguard Worker
753*d57664e9SAndroid Build Coastguard Worker    for sequence in _emoji_sequences.keys():
754*d57664e9SAndroid Build Coastguard Worker        sequence = tuple(ch for ch in sequence if ch != EMOJI_VS)
755*d57664e9SAndroid Build Coastguard Worker        all_sequences.add(sequence)
756*d57664e9SAndroid Build Coastguard Worker        sequence_pieces.update(sequence)
757*d57664e9SAndroid Build Coastguard Worker
758*d57664e9SAndroid Build Coastguard Worker    for sequence in adjusted_emoji_zwj_sequences.keys():
759*d57664e9SAndroid Build Coastguard Worker        sequence = tuple(ch for ch in sequence if ch != EMOJI_VS)
760*d57664e9SAndroid Build Coastguard Worker        all_sequences.add(sequence)
761*d57664e9SAndroid Build Coastguard Worker        sequence_pieces.update(sequence)
762*d57664e9SAndroid Build Coastguard Worker
763*d57664e9SAndroid Build Coastguard Worker    for first, second in SAME_FLAG_MAPPINGS:
764*d57664e9SAndroid Build Coastguard Worker        equivalent_emoji[first] = second
765*d57664e9SAndroid Build Coastguard Worker
766*d57664e9SAndroid Build Coastguard Worker    # Add all tag characters used in flags
767*d57664e9SAndroid Build Coastguard Worker    sequence_pieces.update(range(0xE0030, 0xE0039 + 1))
768*d57664e9SAndroid Build Coastguard Worker    sequence_pieces.update(range(0xE0061, 0xE007A + 1))
769*d57664e9SAndroid Build Coastguard Worker
770*d57664e9SAndroid Build Coastguard Worker    all_emoji = (
771*d57664e9SAndroid Build Coastguard Worker        _emoji_properties['Emoji'] |
772*d57664e9SAndroid Build Coastguard Worker        all_sequences |
773*d57664e9SAndroid Build Coastguard Worker        sequence_pieces |
774*d57664e9SAndroid Build Coastguard Worker        set(LEGACY_ANDROID_EMOJI.keys()))
775*d57664e9SAndroid Build Coastguard Worker    default_emoji = (
776*d57664e9SAndroid Build Coastguard Worker        _emoji_properties['Emoji_Presentation'] |
777*d57664e9SAndroid Build Coastguard Worker        all_sequences |
778*d57664e9SAndroid Build Coastguard Worker        set(LEGACY_ANDROID_EMOJI.keys()))
779*d57664e9SAndroid Build Coastguard Worker
780*d57664e9SAndroid Build Coastguard Worker    equivalent_emoji.update(EQUIVALENT_FLAGS)
781*d57664e9SAndroid Build Coastguard Worker    equivalent_emoji.update(LEGACY_ANDROID_EMOJI)
782*d57664e9SAndroid Build Coastguard Worker    equivalent_emoji.update(ZWJ_IDENTICALS)
783*d57664e9SAndroid Build Coastguard Worker
784*d57664e9SAndroid Build Coastguard Worker    for seq in _emoji_variation_sequences:
785*d57664e9SAndroid Build Coastguard Worker        equivalent_emoji[seq] = seq[0]
786*d57664e9SAndroid Build Coastguard Worker
787*d57664e9SAndroid Build Coastguard Worker    return all_emoji, default_emoji, equivalent_emoji
788*d57664e9SAndroid Build Coastguard Worker
789*d57664e9SAndroid Build Coastguard Worker
790*d57664e9SAndroid Build Coastguard Workerdef check_compact_only_fallback():
791*d57664e9SAndroid Build Coastguard Worker    for name, fallback_chain in _fallback_chains.items():
792*d57664e9SAndroid Build Coastguard Worker        for record in fallback_chain:
793*d57664e9SAndroid Build Coastguard Worker            if record.variant == 'compact':
794*d57664e9SAndroid Build Coastguard Worker                same_script_elegants = [x for x in fallback_chain
795*d57664e9SAndroid Build Coastguard Worker                    if x.scripts == record.scripts and x.variant == 'elegant']
796*d57664e9SAndroid Build Coastguard Worker                assert same_script_elegants, (
797*d57664e9SAndroid Build Coastguard Worker                    '%s must be in elegant of %s as fallback of "%s" too' % (
798*d57664e9SAndroid Build Coastguard Worker                    record.font, record.scripts, record.fallback_for),)
799*d57664e9SAndroid Build Coastguard Worker
800*d57664e9SAndroid Build Coastguard Worker
801*d57664e9SAndroid Build Coastguard Workerdef check_vertical_metrics():
802*d57664e9SAndroid Build Coastguard Worker    for record in _all_fonts:
803*d57664e9SAndroid Build Coastguard Worker        if record.name in ['sans-serif', 'sans-serif-condensed']:
804*d57664e9SAndroid Build Coastguard Worker            font = open_font(record.font)
805*d57664e9SAndroid Build Coastguard Worker            assert font['head'].yMax == 2163 and font['head'].yMin == -555, (
806*d57664e9SAndroid Build Coastguard Worker                'yMax and yMin of %s do not match expected values.' % (
807*d57664e9SAndroid Build Coastguard Worker                record.font,))
808*d57664e9SAndroid Build Coastguard Worker
809*d57664e9SAndroid Build Coastguard Worker        if record.name in ['sans-serif', 'sans-serif-condensed',
810*d57664e9SAndroid Build Coastguard Worker                           'serif', 'monospace']:
811*d57664e9SAndroid Build Coastguard Worker            font = open_font(record.font)
812*d57664e9SAndroid Build Coastguard Worker            assert (font['hhea'].ascent == 1900 and
813*d57664e9SAndroid Build Coastguard Worker                    font['hhea'].descent == -500), (
814*d57664e9SAndroid Build Coastguard Worker                        'ascent and descent of %s do not match expected '
815*d57664e9SAndroid Build Coastguard Worker                        'values.' % (record.font,))
816*d57664e9SAndroid Build Coastguard Worker
817*d57664e9SAndroid Build Coastguard Worker
818*d57664e9SAndroid Build Coastguard Workerdef check_cjk_punctuation():
819*d57664e9SAndroid Build Coastguard Worker    cjk_scripts = {'Hans', 'Hant', 'Jpan', 'Kore'}
820*d57664e9SAndroid Build Coastguard Worker    cjk_punctuation = range(0x3000, 0x301F + 1)
821*d57664e9SAndroid Build Coastguard Worker    for name, fallback_chain in _fallback_chains.items():
822*d57664e9SAndroid Build Coastguard Worker        for record in fallback_chain:
823*d57664e9SAndroid Build Coastguard Worker            if record.scripts.intersection(cjk_scripts):
824*d57664e9SAndroid Build Coastguard Worker                # CJK font seen. Stop checking the rest of the fonts.
825*d57664e9SAndroid Build Coastguard Worker                break
826*d57664e9SAndroid Build Coastguard Worker            assert_font_supports_none_of_chars(record.font, cjk_punctuation, name)
827*d57664e9SAndroid Build Coastguard Worker
828*d57664e9SAndroid Build Coastguard Workerdef main():
829*d57664e9SAndroid Build Coastguard Worker    global _fonts_dir
830*d57664e9SAndroid Build Coastguard Worker    target_out = sys.argv[1]
831*d57664e9SAndroid Build Coastguard Worker    _fonts_dir = path.join(target_out, 'fonts')
832*d57664e9SAndroid Build Coastguard Worker
833*d57664e9SAndroid Build Coastguard Worker    fonts_xml_path = path.join(target_out, 'etc', 'font_fallback.xml')
834*d57664e9SAndroid Build Coastguard Worker
835*d57664e9SAndroid Build Coastguard Worker    parse_fonts_xml(fonts_xml_path)
836*d57664e9SAndroid Build Coastguard Worker
837*d57664e9SAndroid Build Coastguard Worker    check_compact_only_fallback()
838*d57664e9SAndroid Build Coastguard Worker
839*d57664e9SAndroid Build Coastguard Worker    check_vertical_metrics()
840*d57664e9SAndroid Build Coastguard Worker
841*d57664e9SAndroid Build Coastguard Worker    hyphens_dir = path.join(target_out, 'usr', 'hyphen-data')
842*d57664e9SAndroid Build Coastguard Worker    check_hyphens(hyphens_dir)
843*d57664e9SAndroid Build Coastguard Worker
844*d57664e9SAndroid Build Coastguard Worker    check_cjk_punctuation()
845*d57664e9SAndroid Build Coastguard Worker
846*d57664e9SAndroid Build Coastguard Worker    check_emoji = sys.argv[2]
847*d57664e9SAndroid Build Coastguard Worker    if check_emoji == 'true':
848*d57664e9SAndroid Build Coastguard Worker        ucd_path = sys.argv[3]
849*d57664e9SAndroid Build Coastguard Worker        parse_ucd(ucd_path)
850*d57664e9SAndroid Build Coastguard Worker        all_emoji, default_emoji, equivalent_emoji = compute_expected_emoji()
851*d57664e9SAndroid Build Coastguard Worker        check_emoji_not_compat(all_emoji, equivalent_emoji)
852*d57664e9SAndroid Build Coastguard Worker        check_emoji_coverage(all_emoji, equivalent_emoji)
853*d57664e9SAndroid Build Coastguard Worker        check_emoji_defaults(default_emoji)
854*d57664e9SAndroid Build Coastguard Worker
855*d57664e9SAndroid Build Coastguard Worker
856*d57664e9SAndroid Build Coastguard Workerif __name__ == '__main__':
857*d57664e9SAndroid Build Coastguard Worker    main()
858