xref: /aosp_15_r20/external/fonttools/Tests/ttLib/tables/tables_test.py (revision e1fe3e4ad2793916b15cccdc4a7da52a7e1dd0e9)
1from fontTools.ttLib import TTFont, tagToXML
2from io import StringIO
3import os
4import sys
5import re
6import contextlib
7import pytest
8
9try:
10    import unicodedata2
11except ImportError:
12    if sys.version_info[:2] < (3, 6):
13        unicodedata2 = None
14    else:
15        # on 3.6 the built-in unicodedata is the same as unicodedata2 backport
16        import unicodedata
17
18        unicodedata2 = unicodedata
19
20
21# Font files in data/*.{o,t}tf; output gets compared to data/*.ttx.*
22TESTS = {
23    "aots/base.otf": (
24        "CFF ",
25        "cmap",
26        "head",
27        "hhea",
28        "hmtx",
29        "maxp",
30        "name",
31        "OS/2",
32        "post",
33    ),
34    "aots/classdef1_font1.otf": ("GSUB",),
35    "aots/classdef1_font2.otf": ("GSUB",),
36    "aots/classdef1_font3.otf": ("GSUB",),
37    "aots/classdef1_font4.otf": ("GSUB",),
38    "aots/classdef2_font1.otf": ("GSUB",),
39    "aots/classdef2_font2.otf": ("GSUB",),
40    "aots/classdef2_font3.otf": ("GSUB",),
41    "aots/classdef2_font4.otf": ("GSUB",),
42    "aots/cmap0_font1.otf": ("cmap",),
43    "aots/cmap10_font1.otf": ("cmap",),
44    "aots/cmap10_font2.otf": ("cmap",),
45    "aots/cmap12_font1.otf": ("cmap",),
46    "aots/cmap14_font1.otf": ("cmap",),
47    "aots/cmap2_font1.otf": ("cmap",),
48    "aots/cmap4_font1.otf": ("cmap",),
49    "aots/cmap4_font2.otf": ("cmap",),
50    "aots/cmap4_font3.otf": ("cmap",),
51    "aots/cmap4_font4.otf": ("cmap",),
52    "aots/cmap6_font1.otf": ("cmap",),
53    "aots/cmap6_font2.otf": ("cmap",),
54    "aots/cmap8_font1.otf": ("cmap",),
55    "aots/cmap_composition_font1.otf": ("cmap",),
56    "aots/cmap_subtableselection_font1.otf": ("cmap",),
57    "aots/cmap_subtableselection_font2.otf": ("cmap",),
58    "aots/cmap_subtableselection_font3.otf": ("cmap",),
59    "aots/cmap_subtableselection_font4.otf": ("cmap",),
60    "aots/cmap_subtableselection_font5.otf": ("cmap",),
61    "aots/gpos1_1_lookupflag_f1.otf": ("GDEF", "GPOS"),
62    "aots/gpos1_1_simple_f1.otf": ("GPOS",),
63    "aots/gpos1_1_simple_f2.otf": ("GPOS",),
64    "aots/gpos1_1_simple_f3.otf": ("GPOS",),
65    "aots/gpos1_1_simple_f4.otf": ("GPOS",),
66    "aots/gpos1_2_font1.otf": ("GPOS",),
67    "aots/gpos1_2_font2.otf": ("GDEF", "GPOS"),
68    "aots/gpos2_1_font6.otf": ("GPOS",),
69    "aots/gpos2_1_font7.otf": ("GPOS",),
70    "aots/gpos2_1_lookupflag_f1.otf": ("GDEF", "GPOS"),
71    "aots/gpos2_1_lookupflag_f2.otf": ("GDEF", "GPOS"),
72    "aots/gpos2_1_next_glyph_f1.otf": ("GPOS",),
73    "aots/gpos2_1_next_glyph_f2.otf": ("GPOS",),
74    "aots/gpos2_1_simple_f1.otf": ("GPOS",),
75    "aots/gpos2_2_font1.otf": ("GPOS",),
76    "aots/gpos2_2_font2.otf": ("GDEF", "GPOS"),
77    "aots/gpos2_2_font3.otf": ("GDEF", "GPOS"),
78    "aots/gpos2_2_font4.otf": ("GPOS",),
79    "aots/gpos2_2_font5.otf": ("GPOS",),
80    "aots/gpos3_font1.otf": ("GPOS",),
81    "aots/gpos3_font2.otf": ("GDEF", "GPOS"),
82    "aots/gpos3_font3.otf": ("GDEF", "GPOS"),
83    "aots/gpos4_lookupflag_f1.otf": ("GDEF", "GPOS"),
84    "aots/gpos4_lookupflag_f2.otf": ("GDEF", "GPOS"),
85    "aots/gpos4_multiple_anchors_1.otf": ("GDEF", "GPOS"),
86    "aots/gpos4_simple_1.otf": ("GDEF", "GPOS"),
87    "aots/gpos5_font1.otf": ("GDEF", "GPOS", "GSUB"),
88    "aots/gpos6_font1.otf": ("GDEF", "GPOS"),
89    "aots/gpos7_1_font1.otf": ("GPOS",),
90    "aots/gpos9_font1.otf": ("GPOS",),
91    "aots/gpos9_font2.otf": ("GPOS",),
92    "aots/gpos_chaining1_boundary_f1.otf": ("GDEF", "GPOS"),
93    "aots/gpos_chaining1_boundary_f2.otf": ("GDEF", "GPOS"),
94    "aots/gpos_chaining1_boundary_f3.otf": ("GDEF", "GPOS"),
95    "aots/gpos_chaining1_boundary_f4.otf": ("GDEF", "GPOS"),
96    "aots/gpos_chaining1_lookupflag_f1.otf": ("GDEF", "GPOS"),
97    "aots/gpos_chaining1_multiple_subrules_f1.otf": ("GDEF", "GPOS"),
98    "aots/gpos_chaining1_multiple_subrules_f2.otf": ("GDEF", "GPOS"),
99    "aots/gpos_chaining1_next_glyph_f1.otf": ("GDEF", "GPOS"),
100    "aots/gpos_chaining1_simple_f1.otf": ("GDEF", "GPOS"),
101    "aots/gpos_chaining1_simple_f2.otf": ("GDEF", "GPOS"),
102    "aots/gpos_chaining1_successive_f1.otf": ("GDEF", "GPOS"),
103    "aots/gpos_chaining2_boundary_f1.otf": ("GDEF", "GPOS"),
104    "aots/gpos_chaining2_boundary_f2.otf": ("GDEF", "GPOS"),
105    "aots/gpos_chaining2_boundary_f3.otf": ("GDEF", "GPOS"),
106    "aots/gpos_chaining2_boundary_f4.otf": ("GDEF", "GPOS"),
107    "aots/gpos_chaining2_lookupflag_f1.otf": ("GDEF", "GPOS"),
108    "aots/gpos_chaining2_multiple_subrules_f1.otf": ("GDEF", "GPOS"),
109    "aots/gpos_chaining2_multiple_subrules_f2.otf": ("GDEF", "GPOS"),
110    "aots/gpos_chaining2_next_glyph_f1.otf": ("GDEF", "GPOS"),
111    "aots/gpos_chaining2_simple_f1.otf": ("GDEF", "GPOS"),
112    "aots/gpos_chaining2_simple_f2.otf": ("GDEF", "GPOS"),
113    "aots/gpos_chaining2_successive_f1.otf": ("GDEF", "GPOS"),
114    "aots/gpos_chaining3_boundary_f1.otf": ("GDEF", "GPOS"),
115    "aots/gpos_chaining3_boundary_f2.otf": ("GDEF", "GPOS"),
116    "aots/gpos_chaining3_boundary_f3.otf": ("GDEF", "GPOS"),
117    "aots/gpos_chaining3_boundary_f4.otf": ("GDEF", "GPOS"),
118    "aots/gpos_chaining3_lookupflag_f1.otf": ("GDEF", "GPOS"),
119    "aots/gpos_chaining3_next_glyph_f1.otf": ("GDEF", "GPOS"),
120    "aots/gpos_chaining3_simple_f1.otf": ("GDEF", "GPOS"),
121    "aots/gpos_chaining3_simple_f2.otf": ("GDEF", "GPOS"),
122    "aots/gpos_chaining3_successive_f1.otf": ("GDEF", "GPOS"),
123    "aots/gpos_context1_boundary_f1.otf": ("GDEF", "GPOS"),
124    "aots/gpos_context1_boundary_f2.otf": ("GDEF", "GPOS"),
125    "aots/gpos_context1_expansion_f1.otf": ("GDEF", "GPOS"),
126    "aots/gpos_context1_lookupflag_f1.otf": ("GDEF", "GPOS"),
127    "aots/gpos_context1_lookupflag_f2.otf": ("GDEF", "GPOS"),
128    "aots/gpos_context1_multiple_subrules_f1.otf": ("GDEF", "GPOS"),
129    "aots/gpos_context1_multiple_subrules_f2.otf": ("GDEF", "GPOS"),
130    "aots/gpos_context1_next_glyph_f1.otf": ("GDEF", "GPOS"),
131    "aots/gpos_context1_simple_f1.otf": ("GDEF", "GPOS"),
132    "aots/gpos_context1_simple_f2.otf": ("GDEF", "GPOS"),
133    "aots/gpos_context1_successive_f1.otf": ("GDEF", "GPOS"),
134    "aots/gpos_context2_boundary_f1.otf": ("GDEF", "GPOS"),
135    "aots/gpos_context2_boundary_f2.otf": ("GDEF", "GPOS"),
136    "aots/gpos_context2_classes_f1.otf": ("GDEF", "GPOS"),
137    "aots/gpos_context2_classes_f2.otf": ("GDEF", "GPOS"),
138    "aots/gpos_context2_expansion_f1.otf": ("GDEF", "GPOS"),
139    "aots/gpos_context2_lookupflag_f1.otf": ("GDEF", "GPOS"),
140    "aots/gpos_context2_lookupflag_f2.otf": ("GDEF", "GPOS"),
141    "aots/gpos_context2_multiple_subrules_f1.otf": ("GDEF", "GPOS"),
142    "aots/gpos_context2_multiple_subrules_f2.otf": ("GDEF", "GPOS"),
143    "aots/gpos_context2_next_glyph_f1.otf": ("GDEF", "GPOS"),
144    "aots/gpos_context2_simple_f1.otf": ("GDEF", "GPOS"),
145    "aots/gpos_context2_simple_f2.otf": ("GDEF", "GPOS"),
146    "aots/gpos_context2_successive_f1.otf": ("GDEF", "GPOS"),
147    "aots/gpos_context3_boundary_f1.otf": ("GDEF", "GPOS"),
148    "aots/gpos_context3_boundary_f2.otf": ("GDEF", "GPOS"),
149    "aots/gpos_context3_lookupflag_f1.otf": ("GDEF", "GPOS"),
150    "aots/gpos_context3_lookupflag_f2.otf": ("GDEF", "GPOS"),
151    "aots/gpos_context3_next_glyph_f1.otf": ("GDEF", "GPOS"),
152    "aots/gpos_context3_simple_f1.otf": ("GDEF", "GPOS"),
153    "aots/gpos_context3_successive_f1.otf": ("GDEF", "GPOS"),
154    "aots/gsub1_1_lookupflag_f1.otf": ("GDEF", "GSUB"),
155    "aots/gsub1_1_modulo_f1.otf": ("GSUB",),
156    "aots/gsub1_1_simple_f1.otf": ("GSUB",),
157    "aots/gsub1_2_lookupflag_f1.otf": ("GDEF", "GSUB"),
158    "aots/gsub1_2_simple_f1.otf": ("GSUB",),
159    "aots/gsub2_1_lookupflag_f1.otf": ("GDEF", "GSUB"),
160    "aots/gsub2_1_multiple_sequences_f1.otf": ("GSUB",),
161    "aots/gsub2_1_simple_f1.otf": ("GSUB",),
162    "aots/gsub3_1_lookupflag_f1.otf": ("GDEF", "GSUB"),
163    "aots/gsub3_1_multiple_f1.otf": ("GSUB",),
164    "aots/gsub3_1_simple_f1.otf": ("GSUB",),
165    "aots/gsub4_1_lookupflag_f1.otf": ("GDEF", "GSUB"),
166    "aots/gsub4_1_multiple_ligatures_f1.otf": ("GSUB",),
167    "aots/gsub4_1_multiple_ligatures_f2.otf": ("GSUB",),
168    "aots/gsub4_1_multiple_ligsets_f1.otf": ("GSUB",),
169    "aots/gsub4_1_simple_f1.otf": ("GSUB",),
170    "aots/gsub7_font1.otf": ("GSUB",),
171    "aots/gsub7_font2.otf": ("GSUB",),
172    "aots/gsub_chaining1_boundary_f1.otf": ("GDEF", "GSUB"),
173    "aots/gsub_chaining1_boundary_f2.otf": ("GDEF", "GSUB"),
174    "aots/gsub_chaining1_boundary_f3.otf": ("GDEF", "GSUB"),
175    "aots/gsub_chaining1_boundary_f4.otf": ("GDEF", "GSUB"),
176    "aots/gsub_chaining1_lookupflag_f1.otf": ("GDEF", "GSUB"),
177    "aots/gsub_chaining1_multiple_subrules_f1.otf": ("GDEF", "GSUB"),
178    "aots/gsub_chaining1_multiple_subrules_f2.otf": ("GDEF", "GSUB"),
179    "aots/gsub_chaining1_next_glyph_f1.otf": ("GDEF", "GSUB"),
180    "aots/gsub_chaining1_simple_f1.otf": ("GDEF", "GSUB"),
181    "aots/gsub_chaining1_simple_f2.otf": ("GDEF", "GSUB"),
182    "aots/gsub_chaining1_successive_f1.otf": ("GDEF", "GSUB"),
183    "aots/gsub_chaining2_boundary_f1.otf": ("GDEF", "GSUB"),
184    "aots/gsub_chaining2_boundary_f2.otf": ("GDEF", "GSUB"),
185    "aots/gsub_chaining2_boundary_f3.otf": ("GDEF", "GSUB"),
186    "aots/gsub_chaining2_boundary_f4.otf": ("GDEF", "GSUB"),
187    "aots/gsub_chaining2_lookupflag_f1.otf": ("GDEF", "GSUB"),
188    "aots/gsub_chaining2_multiple_subrules_f1.otf": ("GDEF", "GSUB"),
189    "aots/gsub_chaining2_multiple_subrules_f2.otf": ("GDEF", "GSUB"),
190    "aots/gsub_chaining2_next_glyph_f1.otf": ("GDEF", "GSUB"),
191    "aots/gsub_chaining2_simple_f1.otf": ("GDEF", "GSUB"),
192    "aots/gsub_chaining2_simple_f2.otf": ("GDEF", "GSUB"),
193    "aots/gsub_chaining2_successive_f1.otf": ("GDEF", "GSUB"),
194    "aots/gsub_chaining3_boundary_f1.otf": ("GDEF", "GSUB"),
195    "aots/gsub_chaining3_boundary_f2.otf": ("GDEF", "GSUB"),
196    "aots/gsub_chaining3_boundary_f3.otf": ("GDEF", "GSUB"),
197    "aots/gsub_chaining3_boundary_f4.otf": ("GDEF", "GSUB"),
198    "aots/gsub_chaining3_lookupflag_f1.otf": ("GDEF", "GSUB"),
199    "aots/gsub_chaining3_next_glyph_f1.otf": ("GDEF", "GSUB"),
200    "aots/gsub_chaining3_simple_f1.otf": ("GDEF", "GSUB"),
201    "aots/gsub_chaining3_simple_f2.otf": ("GDEF", "GSUB"),
202    "aots/gsub_chaining3_successive_f1.otf": ("GDEF", "GSUB"),
203    "aots/gsub_context1_boundary_f1.otf": ("GDEF", "GSUB"),
204    "aots/gsub_context1_boundary_f2.otf": ("GDEF", "GSUB"),
205    "aots/gsub_context1_expansion_f1.otf": ("GDEF", "GSUB"),
206    "aots/gsub_context1_lookupflag_f1.otf": ("GDEF", "GSUB"),
207    "aots/gsub_context1_lookupflag_f2.otf": ("GDEF", "GSUB"),
208    "aots/gsub_context1_multiple_subrules_f1.otf": ("GDEF", "GSUB"),
209    "aots/gsub_context1_multiple_subrules_f2.otf": ("GDEF", "GSUB"),
210    "aots/gsub_context1_next_glyph_f1.otf": ("GDEF", "GSUB"),
211    "aots/gsub_context1_simple_f1.otf": ("GDEF", "GSUB"),
212    "aots/gsub_context1_simple_f2.otf": ("GDEF", "GSUB"),
213    "aots/gsub_context1_successive_f1.otf": ("GDEF", "GSUB"),
214    "aots/gsub_context2_boundary_f1.otf": ("GDEF", "GSUB"),
215    "aots/gsub_context2_boundary_f2.otf": ("GDEF", "GSUB"),
216    "aots/gsub_context2_classes_f1.otf": ("GDEF", "GSUB"),
217    "aots/gsub_context2_classes_f2.otf": ("GDEF", "GSUB"),
218    "aots/gsub_context2_expansion_f1.otf": ("GDEF", "GSUB"),
219    "aots/gsub_context2_lookupflag_f1.otf": ("GDEF", "GSUB"),
220    "aots/gsub_context2_lookupflag_f2.otf": ("GDEF", "GSUB"),
221    "aots/gsub_context2_multiple_subrules_f1.otf": ("GDEF", "GSUB"),
222    "aots/gsub_context2_multiple_subrules_f2.otf": ("GDEF", "GSUB"),
223    "aots/gsub_context2_next_glyph_f1.otf": ("GDEF", "GSUB"),
224    "aots/gsub_context2_simple_f1.otf": ("GDEF", "GSUB"),
225    "aots/gsub_context2_simple_f2.otf": ("GDEF", "GSUB"),
226    "aots/gsub_context2_successive_f1.otf": ("GDEF", "GSUB"),
227    "aots/gsub_context3_boundary_f1.otf": ("GDEF", "GSUB"),
228    "aots/gsub_context3_boundary_f2.otf": ("GDEF", "GSUB"),
229    "aots/gsub_context3_lookupflag_f1.otf": ("GDEF", "GSUB"),
230    "aots/gsub_context3_lookupflag_f2.otf": ("GDEF", "GSUB"),
231    "aots/gsub_context3_next_glyph_f1.otf": ("GDEF", "GSUB"),
232    "aots/gsub_context3_simple_f1.otf": ("GDEF", "GSUB"),
233    "aots/gsub_context3_successive_f1.otf": ("GDEF", "GSUB"),
234    "aots/lookupflag_ignore_attach_f1.otf": ("GDEF", "GSUB"),
235    "aots/lookupflag_ignore_base_f1.otf": ("GDEF", "GSUB"),
236    "aots/lookupflag_ignore_combination_f1.otf": ("GDEF", "GSUB"),
237    "aots/lookupflag_ignore_ligatures_f1.otf": ("GDEF", "GSUB"),
238    "aots/lookupflag_ignore_marks_f1.otf": ("GDEF", "GSUB"),
239    "graphite/graphite_tests.ttf": ("Silf", "Glat", "Feat", "Sill"),
240}
241
242
243TEST_REQUIREMENTS = {
244    "aots/cmap4_font4.otf": ("unicodedata2",),
245}
246
247
248ttLibVersion_RE = re.compile(r' ttLibVersion=".*"')
249
250
251def getpath(testfile):
252    path = os.path.dirname(__file__)
253    return os.path.join(path, "data", testfile)
254
255
256def read_expected_ttx(testfile, tableTag):
257    name = os.path.splitext(testfile)[0]
258    xml_expected_path = getpath("%s.ttx.%s" % (name, tagToXML(tableTag)))
259    with open(xml_expected_path, "r", encoding="utf-8") as xml_file:
260        xml_expected = ttLibVersion_RE.sub("", xml_file.read())
261    return xml_expected
262
263
264def dump_ttx(font, tableTag):
265    f = StringIO()
266    font.saveXML(f, tables=[tableTag])
267    return ttLibVersion_RE.sub("", f.getvalue())
268
269
270def load_ttx(ttx):
271    f = StringIO()
272    f.write(ttx)
273    f.seek(0)
274    font = TTFont()
275    font.importXML(f)
276    return font
277
278
279@contextlib.contextmanager
280def open_font(testfile):
281    font = TTFont(getpath(testfile))
282    try:
283        yield font
284    finally:
285        font.close()
286
287
288def _skip_if_requirement_missing(testfile):
289    if testfile in TEST_REQUIREMENTS:
290        for req in TEST_REQUIREMENTS[testfile]:
291            if globals()[req] is None:
292                pytest.skip("%s not installed" % req)
293
294
295def test_xml_from_binary(testfile, tableTag):
296    """Check XML from decompiled object."""
297    _skip_if_requirement_missing(testfile)
298
299    xml_expected = read_expected_ttx(testfile, tableTag)
300
301    with open_font(testfile) as font:
302        xml_from_binary = dump_ttx(font, tableTag)
303
304    assert xml_expected == xml_from_binary
305
306
307def test_xml_from_xml(testfile, tableTag):
308    """Check XML from object read from XML."""
309    _skip_if_requirement_missing(testfile)
310
311    xml_expected = read_expected_ttx(testfile, tableTag)
312
313    font = load_ttx(xml_expected)
314    name = os.path.splitext(testfile)[0]
315    setupfile = getpath("%s.ttx.%s.setup" % (name, tagToXML(tableTag)))
316    if os.path.exists(setupfile):
317        #        import pdb; pdb.set_trace()
318        font.importXML(setupfile)
319    xml_from_xml = dump_ttx(font, tableTag)
320
321    assert xml_expected == xml_from_xml
322
323
324def pytest_generate_tests(metafunc):
325    # http://doc.pytest.org/en/latest/parametrize.html#basic-pytest-generate-tests-example
326    fixturenames = metafunc.fixturenames
327    argnames = ("testfile", "tableTag")
328    if all(fn in fixturenames for fn in argnames):
329        argvalues = [
330            (testfile, tableTag)
331            for testfile, tableTags in sorted(TESTS.items())
332            for tableTag in tableTags
333        ]
334        metafunc.parametrize(argnames, argvalues)
335
336
337if __name__ == "__main__":
338    sys.exit(pytest.main(sys.argv))
339