xref: /aosp_15_r20/external/fonttools/Tests/ttx/ttx_test.py (revision e1fe3e4ad2793916b15cccdc4a7da52a7e1dd0e9)
1*e1fe3e4aSElliott Hughesfrom fontTools.misc.testTools import parseXML
2*e1fe3e4aSElliott Hughesfrom fontTools.misc.timeTools import timestampSinceEpoch
3*e1fe3e4aSElliott Hughesfrom fontTools.ttLib import TTFont, TTLibError
4*e1fe3e4aSElliott Hughesfrom fontTools.ttLib.tables.DefaultTable import DefaultTable
5*e1fe3e4aSElliott Hughesfrom fontTools import ttx
6*e1fe3e4aSElliott Hughesimport base64
7*e1fe3e4aSElliott Hughesimport getopt
8*e1fe3e4aSElliott Hughesimport logging
9*e1fe3e4aSElliott Hughesimport os
10*e1fe3e4aSElliott Hughesimport shutil
11*e1fe3e4aSElliott Hughesimport subprocess
12*e1fe3e4aSElliott Hughesimport sys
13*e1fe3e4aSElliott Hughesimport tempfile
14*e1fe3e4aSElliott Hughesimport unittest
15*e1fe3e4aSElliott Hughesfrom pathlib import Path
16*e1fe3e4aSElliott Hughes
17*e1fe3e4aSElliott Hughesimport pytest
18*e1fe3e4aSElliott Hughes
19*e1fe3e4aSElliott Hughestry:
20*e1fe3e4aSElliott Hughes    import zopfli
21*e1fe3e4aSElliott Hughesexcept ImportError:
22*e1fe3e4aSElliott Hughes    zopfli = None
23*e1fe3e4aSElliott Hughestry:
24*e1fe3e4aSElliott Hughes    try:
25*e1fe3e4aSElliott Hughes        import brotlicffi as brotli
26*e1fe3e4aSElliott Hughes    except ImportError:
27*e1fe3e4aSElliott Hughes        import brotli
28*e1fe3e4aSElliott Hughesexcept ImportError:
29*e1fe3e4aSElliott Hughes    brotli = None
30*e1fe3e4aSElliott Hughes
31*e1fe3e4aSElliott Hughes
32*e1fe3e4aSElliott Hughesclass TTXTest(unittest.TestCase):
33*e1fe3e4aSElliott Hughes    def __init__(self, methodName):
34*e1fe3e4aSElliott Hughes        unittest.TestCase.__init__(self, methodName)
35*e1fe3e4aSElliott Hughes        # Python 3 renamed assertRaisesRegexp to assertRaisesRegex,
36*e1fe3e4aSElliott Hughes        # and fires deprecation warnings if a program uses the old name.
37*e1fe3e4aSElliott Hughes        if not hasattr(self, "assertRaisesRegex"):
38*e1fe3e4aSElliott Hughes            self.assertRaisesRegex = self.assertRaisesRegexp
39*e1fe3e4aSElliott Hughes
40*e1fe3e4aSElliott Hughes    def setUp(self):
41*e1fe3e4aSElliott Hughes        self.tempdir = None
42*e1fe3e4aSElliott Hughes        self.num_tempfiles = 0
43*e1fe3e4aSElliott Hughes
44*e1fe3e4aSElliott Hughes    def tearDown(self):
45*e1fe3e4aSElliott Hughes        if self.tempdir:
46*e1fe3e4aSElliott Hughes            shutil.rmtree(self.tempdir)
47*e1fe3e4aSElliott Hughes
48*e1fe3e4aSElliott Hughes    @staticmethod
49*e1fe3e4aSElliott Hughes    def getpath(testfile):
50*e1fe3e4aSElliott Hughes        path, _ = os.path.split(__file__)
51*e1fe3e4aSElliott Hughes        return os.path.join(path, "data", testfile)
52*e1fe3e4aSElliott Hughes
53*e1fe3e4aSElliott Hughes    def temp_dir(self):
54*e1fe3e4aSElliott Hughes        if not self.tempdir:
55*e1fe3e4aSElliott Hughes            self.tempdir = tempfile.mkdtemp()
56*e1fe3e4aSElliott Hughes
57*e1fe3e4aSElliott Hughes    def temp_font(self, font_path, file_name):
58*e1fe3e4aSElliott Hughes        self.temp_dir()
59*e1fe3e4aSElliott Hughes        temppath = os.path.join(self.tempdir, file_name)
60*e1fe3e4aSElliott Hughes        shutil.copy2(font_path, temppath)
61*e1fe3e4aSElliott Hughes        return temppath
62*e1fe3e4aSElliott Hughes
63*e1fe3e4aSElliott Hughes    @staticmethod
64*e1fe3e4aSElliott Hughes    def read_file(file_path):
65*e1fe3e4aSElliott Hughes        with open(file_path, "r", encoding="utf-8") as f:
66*e1fe3e4aSElliott Hughes            return f.readlines()
67*e1fe3e4aSElliott Hughes
68*e1fe3e4aSElliott Hughes    # -----
69*e1fe3e4aSElliott Hughes    # Tests
70*e1fe3e4aSElliott Hughes    # -----
71*e1fe3e4aSElliott Hughes
72*e1fe3e4aSElliott Hughes    def test_parseOptions_no_args(self):
73*e1fe3e4aSElliott Hughes        with self.assertRaises(getopt.GetoptError) as cm:
74*e1fe3e4aSElliott Hughes            ttx.parseOptions([])
75*e1fe3e4aSElliott Hughes        self.assertTrue("Must specify at least one input file" in str(cm.exception))
76*e1fe3e4aSElliott Hughes
77*e1fe3e4aSElliott Hughes    def test_parseOptions_invalid_path(self):
78*e1fe3e4aSElliott Hughes        file_path = "invalid_font_path"
79*e1fe3e4aSElliott Hughes        with self.assertRaises(getopt.GetoptError) as cm:
80*e1fe3e4aSElliott Hughes            ttx.parseOptions([file_path])
81*e1fe3e4aSElliott Hughes        self.assertTrue('File not found: "%s"' % file_path in str(cm.exception))
82*e1fe3e4aSElliott Hughes
83*e1fe3e4aSElliott Hughes    def test_parseOptions_font2ttx_1st_time(self):
84*e1fe3e4aSElliott Hughes        file_name = "TestOTF.otf"
85*e1fe3e4aSElliott Hughes        font_path = self.getpath(file_name)
86*e1fe3e4aSElliott Hughes        temp_path = self.temp_font(font_path, file_name)
87*e1fe3e4aSElliott Hughes        jobs, _ = ttx.parseOptions([temp_path])
88*e1fe3e4aSElliott Hughes        self.assertEqual(jobs[0][0].__name__, "ttDump")
89*e1fe3e4aSElliott Hughes        self.assertEqual(
90*e1fe3e4aSElliott Hughes            jobs[0][1:],
91*e1fe3e4aSElliott Hughes            (
92*e1fe3e4aSElliott Hughes                os.path.join(self.tempdir, file_name),
93*e1fe3e4aSElliott Hughes                os.path.join(self.tempdir, file_name.split(".")[0] + ".ttx"),
94*e1fe3e4aSElliott Hughes            ),
95*e1fe3e4aSElliott Hughes        )
96*e1fe3e4aSElliott Hughes
97*e1fe3e4aSElliott Hughes    def test_parseOptions_font2ttx_2nd_time(self):
98*e1fe3e4aSElliott Hughes        file_name = "TestTTF.ttf"
99*e1fe3e4aSElliott Hughes        font_path = self.getpath(file_name)
100*e1fe3e4aSElliott Hughes        temp_path = self.temp_font(font_path, file_name)
101*e1fe3e4aSElliott Hughes        _, _ = ttx.parseOptions([temp_path])  # this is NOT a mistake
102*e1fe3e4aSElliott Hughes        jobs, _ = ttx.parseOptions([temp_path])
103*e1fe3e4aSElliott Hughes        self.assertEqual(jobs[0][0].__name__, "ttDump")
104*e1fe3e4aSElliott Hughes        self.assertEqual(
105*e1fe3e4aSElliott Hughes            jobs[0][1:],
106*e1fe3e4aSElliott Hughes            (
107*e1fe3e4aSElliott Hughes                os.path.join(self.tempdir, file_name),
108*e1fe3e4aSElliott Hughes                os.path.join(self.tempdir, file_name.split(".")[0] + "#1.ttx"),
109*e1fe3e4aSElliott Hughes            ),
110*e1fe3e4aSElliott Hughes        )
111*e1fe3e4aSElliott Hughes
112*e1fe3e4aSElliott Hughes    def test_parseOptions_ttx2font_1st_time(self):
113*e1fe3e4aSElliott Hughes        file_name = "TestTTF.ttx"
114*e1fe3e4aSElliott Hughes        font_path = self.getpath(file_name)
115*e1fe3e4aSElliott Hughes        temp_path = self.temp_font(font_path, file_name)
116*e1fe3e4aSElliott Hughes        jobs, _ = ttx.parseOptions([temp_path])
117*e1fe3e4aSElliott Hughes        self.assertEqual(jobs[0][0].__name__, "ttCompile")
118*e1fe3e4aSElliott Hughes        self.assertEqual(
119*e1fe3e4aSElliott Hughes            jobs[0][1:],
120*e1fe3e4aSElliott Hughes            (
121*e1fe3e4aSElliott Hughes                os.path.join(self.tempdir, file_name),
122*e1fe3e4aSElliott Hughes                os.path.join(self.tempdir, file_name.split(".")[0] + ".ttf"),
123*e1fe3e4aSElliott Hughes            ),
124*e1fe3e4aSElliott Hughes        )
125*e1fe3e4aSElliott Hughes
126*e1fe3e4aSElliott Hughes    def test_parseOptions_ttx2font_2nd_time(self):
127*e1fe3e4aSElliott Hughes        file_name = "TestOTF.ttx"
128*e1fe3e4aSElliott Hughes        font_path = self.getpath(file_name)
129*e1fe3e4aSElliott Hughes        temp_path = self.temp_font(font_path, file_name)
130*e1fe3e4aSElliott Hughes        _, _ = ttx.parseOptions([temp_path])  # this is NOT a mistake
131*e1fe3e4aSElliott Hughes        jobs, _ = ttx.parseOptions([temp_path])
132*e1fe3e4aSElliott Hughes        self.assertEqual(jobs[0][0].__name__, "ttCompile")
133*e1fe3e4aSElliott Hughes        self.assertEqual(
134*e1fe3e4aSElliott Hughes            jobs[0][1:],
135*e1fe3e4aSElliott Hughes            (
136*e1fe3e4aSElliott Hughes                os.path.join(self.tempdir, file_name),
137*e1fe3e4aSElliott Hughes                os.path.join(self.tempdir, file_name.split(".")[0] + "#1.otf"),
138*e1fe3e4aSElliott Hughes            ),
139*e1fe3e4aSElliott Hughes        )
140*e1fe3e4aSElliott Hughes
141*e1fe3e4aSElliott Hughes    def test_parseOptions_multiple_fonts(self):
142*e1fe3e4aSElliott Hughes        file_names = ["TestOTF.otf", "TestTTF.ttf"]
143*e1fe3e4aSElliott Hughes        font_paths = [self.getpath(file_name) for file_name in file_names]
144*e1fe3e4aSElliott Hughes        temp_paths = [
145*e1fe3e4aSElliott Hughes            self.temp_font(font_path, file_name)
146*e1fe3e4aSElliott Hughes            for font_path, file_name in zip(font_paths, file_names)
147*e1fe3e4aSElliott Hughes        ]
148*e1fe3e4aSElliott Hughes        jobs, _ = ttx.parseOptions(temp_paths)
149*e1fe3e4aSElliott Hughes        for i in range(len(jobs)):
150*e1fe3e4aSElliott Hughes            self.assertEqual(jobs[i][0].__name__, "ttDump")
151*e1fe3e4aSElliott Hughes            self.assertEqual(
152*e1fe3e4aSElliott Hughes                jobs[i][1:],
153*e1fe3e4aSElliott Hughes                (
154*e1fe3e4aSElliott Hughes                    os.path.join(self.tempdir, file_names[i]),
155*e1fe3e4aSElliott Hughes                    os.path.join(self.tempdir, file_names[i].split(".")[0] + ".ttx"),
156*e1fe3e4aSElliott Hughes                ),
157*e1fe3e4aSElliott Hughes            )
158*e1fe3e4aSElliott Hughes
159*e1fe3e4aSElliott Hughes    def test_parseOptions_mixed_files(self):
160*e1fe3e4aSElliott Hughes        operations = ["ttDump", "ttCompile"]
161*e1fe3e4aSElliott Hughes        extensions = [".ttx", ".ttf"]
162*e1fe3e4aSElliott Hughes        file_names = ["TestOTF.otf", "TestTTF.ttx"]
163*e1fe3e4aSElliott Hughes        font_paths = [self.getpath(file_name) for file_name in file_names]
164*e1fe3e4aSElliott Hughes        temp_paths = [
165*e1fe3e4aSElliott Hughes            self.temp_font(font_path, file_name)
166*e1fe3e4aSElliott Hughes            for font_path, file_name in zip(font_paths, file_names)
167*e1fe3e4aSElliott Hughes        ]
168*e1fe3e4aSElliott Hughes        jobs, _ = ttx.parseOptions(temp_paths)
169*e1fe3e4aSElliott Hughes        for i in range(len(jobs)):
170*e1fe3e4aSElliott Hughes            self.assertEqual(jobs[i][0].__name__, operations[i])
171*e1fe3e4aSElliott Hughes            self.assertEqual(
172*e1fe3e4aSElliott Hughes                jobs[i][1:],
173*e1fe3e4aSElliott Hughes                (
174*e1fe3e4aSElliott Hughes                    os.path.join(self.tempdir, file_names[i]),
175*e1fe3e4aSElliott Hughes                    os.path.join(
176*e1fe3e4aSElliott Hughes                        self.tempdir,
177*e1fe3e4aSElliott Hughes                        file_names[i].split(".")[0] + extensions[i],
178*e1fe3e4aSElliott Hughes                    ),
179*e1fe3e4aSElliott Hughes                ),
180*e1fe3e4aSElliott Hughes            )
181*e1fe3e4aSElliott Hughes
182*e1fe3e4aSElliott Hughes    def test_parseOptions_splitTables(self):
183*e1fe3e4aSElliott Hughes        file_name = "TestTTF.ttf"
184*e1fe3e4aSElliott Hughes        font_path = self.getpath(file_name)
185*e1fe3e4aSElliott Hughes        temp_path = self.temp_font(font_path, file_name)
186*e1fe3e4aSElliott Hughes        args = ["-s", temp_path]
187*e1fe3e4aSElliott Hughes
188*e1fe3e4aSElliott Hughes        jobs, options = ttx.parseOptions(args)
189*e1fe3e4aSElliott Hughes
190*e1fe3e4aSElliott Hughes        ttx_file_path = jobs[0][2]
191*e1fe3e4aSElliott Hughes        temp_folder = os.path.dirname(ttx_file_path)
192*e1fe3e4aSElliott Hughes        self.assertTrue(options.splitTables)
193*e1fe3e4aSElliott Hughes        self.assertTrue(os.path.exists(ttx_file_path))
194*e1fe3e4aSElliott Hughes
195*e1fe3e4aSElliott Hughes        ttx.process(jobs, options)
196*e1fe3e4aSElliott Hughes
197*e1fe3e4aSElliott Hughes        # Read the TTX file but strip the first two and the last lines:
198*e1fe3e4aSElliott Hughes        # <?xml version="1.0" encoding="UTF-8"?>
199*e1fe3e4aSElliott Hughes        # <ttFont sfntVersion="\x00\x01\x00\x00" ttLibVersion="3.22">
200*e1fe3e4aSElliott Hughes        # ...
201*e1fe3e4aSElliott Hughes        # </ttFont>
202*e1fe3e4aSElliott Hughes        parsed_xml = parseXML(self.read_file(ttx_file_path)[2:-1])
203*e1fe3e4aSElliott Hughes        for item in parsed_xml:
204*e1fe3e4aSElliott Hughes            if not isinstance(item, tuple):
205*e1fe3e4aSElliott Hughes                continue
206*e1fe3e4aSElliott Hughes            # the tuple looks like this:
207*e1fe3e4aSElliott Hughes            # (u'head', {u'src': u'TestTTF._h_e_a_d.ttx'}, [])
208*e1fe3e4aSElliott Hughes            table_file_name = item[1].get("src")
209*e1fe3e4aSElliott Hughes            table_file_path = os.path.join(temp_folder, table_file_name)
210*e1fe3e4aSElliott Hughes            self.assertTrue(os.path.exists(table_file_path))
211*e1fe3e4aSElliott Hughes
212*e1fe3e4aSElliott Hughes    def test_parseOptions_splitGlyphs(self):
213*e1fe3e4aSElliott Hughes        file_name = "TestTTF.ttf"
214*e1fe3e4aSElliott Hughes        font_path = self.getpath(file_name)
215*e1fe3e4aSElliott Hughes        temp_path = self.temp_font(font_path, file_name)
216*e1fe3e4aSElliott Hughes        args = ["-g", temp_path]
217*e1fe3e4aSElliott Hughes
218*e1fe3e4aSElliott Hughes        jobs, options = ttx.parseOptions(args)
219*e1fe3e4aSElliott Hughes
220*e1fe3e4aSElliott Hughes        ttx_file_path = jobs[0][2]
221*e1fe3e4aSElliott Hughes        temp_folder = os.path.dirname(ttx_file_path)
222*e1fe3e4aSElliott Hughes        self.assertTrue(options.splitGlyphs)
223*e1fe3e4aSElliott Hughes        # splitGlyphs also forces splitTables
224*e1fe3e4aSElliott Hughes        self.assertTrue(options.splitTables)
225*e1fe3e4aSElliott Hughes        self.assertTrue(os.path.exists(ttx_file_path))
226*e1fe3e4aSElliott Hughes
227*e1fe3e4aSElliott Hughes        ttx.process(jobs, options)
228*e1fe3e4aSElliott Hughes
229*e1fe3e4aSElliott Hughes        # Read the TTX file but strip the first two and the last lines:
230*e1fe3e4aSElliott Hughes        # <?xml version="1.0" encoding="UTF-8"?>
231*e1fe3e4aSElliott Hughes        # <ttFont sfntVersion="\x00\x01\x00\x00" ttLibVersion="3.22">
232*e1fe3e4aSElliott Hughes        # ...
233*e1fe3e4aSElliott Hughes        # </ttFont>
234*e1fe3e4aSElliott Hughes        for item in parseXML(self.read_file(ttx_file_path)[2:-1]):
235*e1fe3e4aSElliott Hughes            if not isinstance(item, tuple):
236*e1fe3e4aSElliott Hughes                continue
237*e1fe3e4aSElliott Hughes            # the tuple looks like this:
238*e1fe3e4aSElliott Hughes            # (u'head', {u'src': u'TestTTF._h_e_a_d.ttx'}, [])
239*e1fe3e4aSElliott Hughes            table_tag = item[0]
240*e1fe3e4aSElliott Hughes            table_file_name = item[1].get("src")
241*e1fe3e4aSElliott Hughes            table_file_path = os.path.join(temp_folder, table_file_name)
242*e1fe3e4aSElliott Hughes            self.assertTrue(os.path.exists(table_file_path))
243*e1fe3e4aSElliott Hughes            if table_tag != "glyf":
244*e1fe3e4aSElliott Hughes                continue
245*e1fe3e4aSElliott Hughes            # also strip the enclosing 'glyf' element
246*e1fe3e4aSElliott Hughes            for item in parseXML(self.read_file(table_file_path)[4:-3]):
247*e1fe3e4aSElliott Hughes                if not isinstance(item, tuple):
248*e1fe3e4aSElliott Hughes                    continue
249*e1fe3e4aSElliott Hughes                # glyphs without outline data only have 'name' attribute
250*e1fe3e4aSElliott Hughes                glyph_file_name = item[1].get("src")
251*e1fe3e4aSElliott Hughes                if glyph_file_name is not None:
252*e1fe3e4aSElliott Hughes                    glyph_file_path = os.path.join(temp_folder, glyph_file_name)
253*e1fe3e4aSElliott Hughes                    self.assertTrue(os.path.exists(glyph_file_path))
254*e1fe3e4aSElliott Hughes
255*e1fe3e4aSElliott Hughes    def test_guessFileType_ttf(self):
256*e1fe3e4aSElliott Hughes        file_name = "TestTTF.ttf"
257*e1fe3e4aSElliott Hughes        font_path = self.getpath(file_name)
258*e1fe3e4aSElliott Hughes        self.assertEqual(ttx.guessFileType(font_path), "TTF")
259*e1fe3e4aSElliott Hughes
260*e1fe3e4aSElliott Hughes    def test_guessFileType_otf(self):
261*e1fe3e4aSElliott Hughes        file_name = "TestOTF.otf"
262*e1fe3e4aSElliott Hughes        font_path = self.getpath(file_name)
263*e1fe3e4aSElliott Hughes        self.assertEqual(ttx.guessFileType(font_path), "OTF")
264*e1fe3e4aSElliott Hughes
265*e1fe3e4aSElliott Hughes    def test_guessFileType_woff(self):
266*e1fe3e4aSElliott Hughes        file_name = "TestWOFF.woff"
267*e1fe3e4aSElliott Hughes        font_path = self.getpath(file_name)
268*e1fe3e4aSElliott Hughes        self.assertEqual(ttx.guessFileType(font_path), "WOFF")
269*e1fe3e4aSElliott Hughes
270*e1fe3e4aSElliott Hughes    def test_guessFileType_woff2(self):
271*e1fe3e4aSElliott Hughes        file_name = "TestWOFF2.woff2"
272*e1fe3e4aSElliott Hughes        font_path = self.getpath(file_name)
273*e1fe3e4aSElliott Hughes        self.assertEqual(ttx.guessFileType(font_path), "WOFF2")
274*e1fe3e4aSElliott Hughes
275*e1fe3e4aSElliott Hughes    def test_guessFileType_ttc(self):
276*e1fe3e4aSElliott Hughes        file_name = "TestTTC.ttc"
277*e1fe3e4aSElliott Hughes        font_path = self.getpath(file_name)
278*e1fe3e4aSElliott Hughes        self.assertEqual(ttx.guessFileType(font_path), "TTC")
279*e1fe3e4aSElliott Hughes
280*e1fe3e4aSElliott Hughes    def test_guessFileType_dfont(self):
281*e1fe3e4aSElliott Hughes        file_name = "TestDFONT.dfont"
282*e1fe3e4aSElliott Hughes        font_path = self.getpath(file_name)
283*e1fe3e4aSElliott Hughes        self.assertEqual(ttx.guessFileType(font_path), "TTF")
284*e1fe3e4aSElliott Hughes
285*e1fe3e4aSElliott Hughes    def test_guessFileType_ttx_ttf(self):
286*e1fe3e4aSElliott Hughes        file_name = "TestTTF.ttx"
287*e1fe3e4aSElliott Hughes        font_path = self.getpath(file_name)
288*e1fe3e4aSElliott Hughes        self.assertEqual(ttx.guessFileType(font_path), "TTX")
289*e1fe3e4aSElliott Hughes
290*e1fe3e4aSElliott Hughes    def test_guessFileType_ttx_otf(self):
291*e1fe3e4aSElliott Hughes        file_name = "TestOTF.ttx"
292*e1fe3e4aSElliott Hughes        font_path = self.getpath(file_name)
293*e1fe3e4aSElliott Hughes        self.assertEqual(ttx.guessFileType(font_path), "OTX")
294*e1fe3e4aSElliott Hughes
295*e1fe3e4aSElliott Hughes    def test_guessFileType_ttx_bom(self):
296*e1fe3e4aSElliott Hughes        file_name = "TestBOM.ttx"
297*e1fe3e4aSElliott Hughes        font_path = self.getpath(file_name)
298*e1fe3e4aSElliott Hughes        self.assertEqual(ttx.guessFileType(font_path), "TTX")
299*e1fe3e4aSElliott Hughes
300*e1fe3e4aSElliott Hughes    def test_guessFileType_ttx_no_sfntVersion(self):
301*e1fe3e4aSElliott Hughes        file_name = "TestNoSFNT.ttx"
302*e1fe3e4aSElliott Hughes        font_path = self.getpath(file_name)
303*e1fe3e4aSElliott Hughes        self.assertEqual(ttx.guessFileType(font_path), "TTX")
304*e1fe3e4aSElliott Hughes
305*e1fe3e4aSElliott Hughes    def test_guessFileType_ttx_no_xml(self):
306*e1fe3e4aSElliott Hughes        file_name = "TestNoXML.ttx"
307*e1fe3e4aSElliott Hughes        font_path = self.getpath(file_name)
308*e1fe3e4aSElliott Hughes        self.assertIsNone(ttx.guessFileType(font_path))
309*e1fe3e4aSElliott Hughes
310*e1fe3e4aSElliott Hughes    def test_guessFileType_invalid_path(self):
311*e1fe3e4aSElliott Hughes        font_path = "invalid_font_path"
312*e1fe3e4aSElliott Hughes        self.assertIsNone(ttx.guessFileType(font_path))
313*e1fe3e4aSElliott Hughes
314*e1fe3e4aSElliott Hughes
315*e1fe3e4aSElliott Hughes# -----------------------
316*e1fe3e4aSElliott Hughes# ttx.Options class tests
317*e1fe3e4aSElliott Hughes# -----------------------
318*e1fe3e4aSElliott Hughes
319*e1fe3e4aSElliott Hughes
320*e1fe3e4aSElliott Hughesdef test_options_flag_h(capsys):
321*e1fe3e4aSElliott Hughes    with pytest.raises(SystemExit):
322*e1fe3e4aSElliott Hughes        ttx.Options([("-h", None)], 1)
323*e1fe3e4aSElliott Hughes
324*e1fe3e4aSElliott Hughes    out, err = capsys.readouterr()
325*e1fe3e4aSElliott Hughes    assert "TTX -- From OpenType To XML And Back" in out
326*e1fe3e4aSElliott Hughes
327*e1fe3e4aSElliott Hughes
328*e1fe3e4aSElliott Hughesdef test_options_flag_version(capsys):
329*e1fe3e4aSElliott Hughes    with pytest.raises(SystemExit):
330*e1fe3e4aSElliott Hughes        ttx.Options([("--version", None)], 1)
331*e1fe3e4aSElliott Hughes
332*e1fe3e4aSElliott Hughes    out, err = capsys.readouterr()
333*e1fe3e4aSElliott Hughes    version_list = out.split(".")
334*e1fe3e4aSElliott Hughes    assert len(version_list) >= 3
335*e1fe3e4aSElliott Hughes    assert version_list[0].isdigit()
336*e1fe3e4aSElliott Hughes    assert version_list[1].isdigit()
337*e1fe3e4aSElliott Hughes    assert version_list[2].strip().isdigit()
338*e1fe3e4aSElliott Hughes
339*e1fe3e4aSElliott Hughes
340*e1fe3e4aSElliott Hughesdef test_options_d_goodpath(tmpdir):
341*e1fe3e4aSElliott Hughes    temp_dir_path = str(tmpdir)
342*e1fe3e4aSElliott Hughes    tto = ttx.Options([("-d", temp_dir_path)], 1)
343*e1fe3e4aSElliott Hughes    assert tto.outputDir == temp_dir_path
344*e1fe3e4aSElliott Hughes
345*e1fe3e4aSElliott Hughes
346*e1fe3e4aSElliott Hughesdef test_options_d_badpath():
347*e1fe3e4aSElliott Hughes    with pytest.raises(getopt.GetoptError):
348*e1fe3e4aSElliott Hughes        ttx.Options([("-d", "bogusdir")], 1)
349*e1fe3e4aSElliott Hughes
350*e1fe3e4aSElliott Hughes
351*e1fe3e4aSElliott Hughesdef test_options_o():
352*e1fe3e4aSElliott Hughes    tto = ttx.Options([("-o", "testfile.ttx")], 1)
353*e1fe3e4aSElliott Hughes    assert tto.outputFile == "testfile.ttx"
354*e1fe3e4aSElliott Hughes
355*e1fe3e4aSElliott Hughes
356*e1fe3e4aSElliott Hughesdef test_options_f():
357*e1fe3e4aSElliott Hughes    tto = ttx.Options([("-f", "")], 1)
358*e1fe3e4aSElliott Hughes    assert tto.overWrite is True
359*e1fe3e4aSElliott Hughes
360*e1fe3e4aSElliott Hughes
361*e1fe3e4aSElliott Hughesdef test_options_v():
362*e1fe3e4aSElliott Hughes    tto = ttx.Options([("-v", "")], 1)
363*e1fe3e4aSElliott Hughes    assert tto.verbose is True
364*e1fe3e4aSElliott Hughes    assert tto.logLevel == logging.DEBUG
365*e1fe3e4aSElliott Hughes
366*e1fe3e4aSElliott Hughes
367*e1fe3e4aSElliott Hughesdef test_options_q():
368*e1fe3e4aSElliott Hughes    tto = ttx.Options([("-q", "")], 1)
369*e1fe3e4aSElliott Hughes    assert tto.quiet is True
370*e1fe3e4aSElliott Hughes    assert tto.logLevel == logging.WARNING
371*e1fe3e4aSElliott Hughes
372*e1fe3e4aSElliott Hughes
373*e1fe3e4aSElliott Hughesdef test_options_l():
374*e1fe3e4aSElliott Hughes    tto = ttx.Options([("-l", "")], 1)
375*e1fe3e4aSElliott Hughes    assert tto.listTables is True
376*e1fe3e4aSElliott Hughes
377*e1fe3e4aSElliott Hughes
378*e1fe3e4aSElliott Hughesdef test_options_t_nopadding():
379*e1fe3e4aSElliott Hughes    tto = ttx.Options([("-t", "CFF2")], 1)
380*e1fe3e4aSElliott Hughes    assert len(tto.onlyTables) == 1
381*e1fe3e4aSElliott Hughes    assert tto.onlyTables[0] == "CFF2"
382*e1fe3e4aSElliott Hughes
383*e1fe3e4aSElliott Hughes
384*e1fe3e4aSElliott Hughesdef test_options_t_withpadding():
385*e1fe3e4aSElliott Hughes    tto = ttx.Options([("-t", "CFF")], 1)
386*e1fe3e4aSElliott Hughes    assert len(tto.onlyTables) == 1
387*e1fe3e4aSElliott Hughes    assert tto.onlyTables[0] == "CFF "
388*e1fe3e4aSElliott Hughes
389*e1fe3e4aSElliott Hughes
390*e1fe3e4aSElliott Hughesdef test_options_s():
391*e1fe3e4aSElliott Hughes    tto = ttx.Options([("-s", "")], 1)
392*e1fe3e4aSElliott Hughes    assert tto.splitTables is True
393*e1fe3e4aSElliott Hughes    assert tto.splitGlyphs is False
394*e1fe3e4aSElliott Hughes
395*e1fe3e4aSElliott Hughes
396*e1fe3e4aSElliott Hughesdef test_options_g():
397*e1fe3e4aSElliott Hughes    tto = ttx.Options([("-g", "")], 1)
398*e1fe3e4aSElliott Hughes    assert tto.splitGlyphs is True
399*e1fe3e4aSElliott Hughes    assert tto.splitTables is True
400*e1fe3e4aSElliott Hughes
401*e1fe3e4aSElliott Hughes
402*e1fe3e4aSElliott Hughesdef test_options_i():
403*e1fe3e4aSElliott Hughes    tto = ttx.Options([("-i", "")], 1)
404*e1fe3e4aSElliott Hughes    assert tto.disassembleInstructions is False
405*e1fe3e4aSElliott Hughes
406*e1fe3e4aSElliott Hughes
407*e1fe3e4aSElliott Hughesdef test_options_z_validoptions():
408*e1fe3e4aSElliott Hughes    valid_options = ("raw", "row", "bitwise", "extfile")
409*e1fe3e4aSElliott Hughes    for option in valid_options:
410*e1fe3e4aSElliott Hughes        tto = ttx.Options([("-z", option)], 1)
411*e1fe3e4aSElliott Hughes        assert tto.bitmapGlyphDataFormat == option
412*e1fe3e4aSElliott Hughes
413*e1fe3e4aSElliott Hughes
414*e1fe3e4aSElliott Hughesdef test_options_z_invalidoption():
415*e1fe3e4aSElliott Hughes    with pytest.raises(getopt.GetoptError):
416*e1fe3e4aSElliott Hughes        ttx.Options([("-z", "bogus")], 1)
417*e1fe3e4aSElliott Hughes
418*e1fe3e4aSElliott Hughes
419*e1fe3e4aSElliott Hughesdef test_options_y_validvalue():
420*e1fe3e4aSElliott Hughes    tto = ttx.Options([("-y", "1")], 1)
421*e1fe3e4aSElliott Hughes    assert tto.fontNumber == 1
422*e1fe3e4aSElliott Hughes
423*e1fe3e4aSElliott Hughes
424*e1fe3e4aSElliott Hughesdef test_options_y_invalidvalue():
425*e1fe3e4aSElliott Hughes    with pytest.raises(ValueError):
426*e1fe3e4aSElliott Hughes        ttx.Options([("-y", "A")], 1)
427*e1fe3e4aSElliott Hughes
428*e1fe3e4aSElliott Hughes
429*e1fe3e4aSElliott Hughesdef test_options_m():
430*e1fe3e4aSElliott Hughes    tto = ttx.Options([("-m", "testfont.ttf")], 1)
431*e1fe3e4aSElliott Hughes    assert tto.mergeFile == "testfont.ttf"
432*e1fe3e4aSElliott Hughes
433*e1fe3e4aSElliott Hughes
434*e1fe3e4aSElliott Hughesdef test_options_b():
435*e1fe3e4aSElliott Hughes    tto = ttx.Options([("-b", "")], 1)
436*e1fe3e4aSElliott Hughes    assert tto.recalcBBoxes is False
437*e1fe3e4aSElliott Hughes
438*e1fe3e4aSElliott Hughes
439*e1fe3e4aSElliott Hughesdef test_options_e():
440*e1fe3e4aSElliott Hughes    tto = ttx.Options([("-e", "")], 1)
441*e1fe3e4aSElliott Hughes    assert tto.ignoreDecompileErrors is False
442*e1fe3e4aSElliott Hughes
443*e1fe3e4aSElliott Hughes
444*e1fe3e4aSElliott Hughesdef test_options_unicodedata():
445*e1fe3e4aSElliott Hughes    tto = ttx.Options([("--unicodedata", "UnicodeData.txt")], 1)
446*e1fe3e4aSElliott Hughes    assert tto.unicodedata == "UnicodeData.txt"
447*e1fe3e4aSElliott Hughes
448*e1fe3e4aSElliott Hughes
449*e1fe3e4aSElliott Hughesdef test_options_newline_lf():
450*e1fe3e4aSElliott Hughes    tto = ttx.Options([("--newline", "LF")], 1)
451*e1fe3e4aSElliott Hughes    assert tto.newlinestr == "\n"
452*e1fe3e4aSElliott Hughes
453*e1fe3e4aSElliott Hughes
454*e1fe3e4aSElliott Hughesdef test_options_newline_cr():
455*e1fe3e4aSElliott Hughes    tto = ttx.Options([("--newline", "CR")], 1)
456*e1fe3e4aSElliott Hughes    assert tto.newlinestr == "\r"
457*e1fe3e4aSElliott Hughes
458*e1fe3e4aSElliott Hughes
459*e1fe3e4aSElliott Hughesdef test_options_newline_crlf():
460*e1fe3e4aSElliott Hughes    tto = ttx.Options([("--newline", "CRLF")], 1)
461*e1fe3e4aSElliott Hughes    assert tto.newlinestr == "\r\n"
462*e1fe3e4aSElliott Hughes
463*e1fe3e4aSElliott Hughes
464*e1fe3e4aSElliott Hughesdef test_options_newline_invalid():
465*e1fe3e4aSElliott Hughes    with pytest.raises(getopt.GetoptError):
466*e1fe3e4aSElliott Hughes        ttx.Options([("--newline", "BOGUS")], 1)
467*e1fe3e4aSElliott Hughes
468*e1fe3e4aSElliott Hughes
469*e1fe3e4aSElliott Hughesdef test_options_recalc_timestamp():
470*e1fe3e4aSElliott Hughes    tto = ttx.Options([("--recalc-timestamp", "")], 1)
471*e1fe3e4aSElliott Hughes    assert tto.recalcTimestamp is True
472*e1fe3e4aSElliott Hughes
473*e1fe3e4aSElliott Hughes
474*e1fe3e4aSElliott Hughesdef test_options_recalc_timestamp():
475*e1fe3e4aSElliott Hughes    tto = ttx.Options([("--no-recalc-timestamp", "")], 1)
476*e1fe3e4aSElliott Hughes    assert tto.recalcTimestamp is False
477*e1fe3e4aSElliott Hughes
478*e1fe3e4aSElliott Hughes
479*e1fe3e4aSElliott Hughesdef test_options_flavor():
480*e1fe3e4aSElliott Hughes    tto = ttx.Options([("--flavor", "woff")], 1)
481*e1fe3e4aSElliott Hughes    assert tto.flavor == "woff"
482*e1fe3e4aSElliott Hughes
483*e1fe3e4aSElliott Hughes
484*e1fe3e4aSElliott Hughesdef test_options_with_zopfli():
485*e1fe3e4aSElliott Hughes    tto = ttx.Options([("--with-zopfli", ""), ("--flavor", "woff")], 1)
486*e1fe3e4aSElliott Hughes    assert tto.useZopfli is True
487*e1fe3e4aSElliott Hughes
488*e1fe3e4aSElliott Hughes
489*e1fe3e4aSElliott Hughesdef test_options_with_zopfli_fails_without_woff_flavor():
490*e1fe3e4aSElliott Hughes    with pytest.raises(getopt.GetoptError):
491*e1fe3e4aSElliott Hughes        ttx.Options([("--with-zopfli", "")], 1)
492*e1fe3e4aSElliott Hughes
493*e1fe3e4aSElliott Hughes
494*e1fe3e4aSElliott Hughesdef test_options_quiet_and_verbose_shouldfail():
495*e1fe3e4aSElliott Hughes    with pytest.raises(getopt.GetoptError):
496*e1fe3e4aSElliott Hughes        ttx.Options([("-q", ""), ("-v", "")], 1)
497*e1fe3e4aSElliott Hughes
498*e1fe3e4aSElliott Hughes
499*e1fe3e4aSElliott Hughesdef test_options_mergefile_and_flavor_shouldfail():
500*e1fe3e4aSElliott Hughes    with pytest.raises(getopt.GetoptError):
501*e1fe3e4aSElliott Hughes        ttx.Options([("-m", "testfont.ttf"), ("--flavor", "woff")], 1)
502*e1fe3e4aSElliott Hughes
503*e1fe3e4aSElliott Hughes
504*e1fe3e4aSElliott Hughesdef test_options_onlytables_and_skiptables_shouldfail():
505*e1fe3e4aSElliott Hughes    with pytest.raises(getopt.GetoptError):
506*e1fe3e4aSElliott Hughes        ttx.Options([("-t", "CFF"), ("-x", "CFF2")], 1)
507*e1fe3e4aSElliott Hughes
508*e1fe3e4aSElliott Hughes
509*e1fe3e4aSElliott Hughesdef test_options_mergefile_and_multiplefiles_shouldfail():
510*e1fe3e4aSElliott Hughes    with pytest.raises(getopt.GetoptError):
511*e1fe3e4aSElliott Hughes        ttx.Options([("-m", "testfont.ttf")], 2)
512*e1fe3e4aSElliott Hughes
513*e1fe3e4aSElliott Hughes
514*e1fe3e4aSElliott Hughesdef test_options_woff2_and_zopfli_shouldfail():
515*e1fe3e4aSElliott Hughes    with pytest.raises(getopt.GetoptError):
516*e1fe3e4aSElliott Hughes        ttx.Options([("--with-zopfli", ""), ("--flavor", "woff2")], 1)
517*e1fe3e4aSElliott Hughes
518*e1fe3e4aSElliott Hughes
519*e1fe3e4aSElliott Hughes# ----------------------------
520*e1fe3e4aSElliott Hughes# ttx.ttCompile function tests
521*e1fe3e4aSElliott Hughes# ----------------------------
522*e1fe3e4aSElliott Hughes
523*e1fe3e4aSElliott Hughes
524*e1fe3e4aSElliott Hughesdef test_ttcompile_otf_compile_default(tmpdir):
525*e1fe3e4aSElliott Hughes    inttx = os.path.join("Tests", "ttx", "data", "TestOTF.ttx")
526*e1fe3e4aSElliott Hughes    # outotf = os.path.join(str(tmpdir), "TestOTF.otf")
527*e1fe3e4aSElliott Hughes    outotf = tmpdir.join("TestOTF.ttx")
528*e1fe3e4aSElliott Hughes    default_options = ttx.Options([], 1)
529*e1fe3e4aSElliott Hughes    ttx.ttCompile(inttx, str(outotf), default_options)
530*e1fe3e4aSElliott Hughes    # confirm that font was built
531*e1fe3e4aSElliott Hughes    assert outotf.check(file=True)
532*e1fe3e4aSElliott Hughes    # confirm that it is valid OTF file, can instantiate a TTFont, has expected OpenType tables
533*e1fe3e4aSElliott Hughes    ttf = TTFont(str(outotf))
534*e1fe3e4aSElliott Hughes    expected_tables = (
535*e1fe3e4aSElliott Hughes        "head",
536*e1fe3e4aSElliott Hughes        "hhea",
537*e1fe3e4aSElliott Hughes        "maxp",
538*e1fe3e4aSElliott Hughes        "OS/2",
539*e1fe3e4aSElliott Hughes        "name",
540*e1fe3e4aSElliott Hughes        "cmap",
541*e1fe3e4aSElliott Hughes        "post",
542*e1fe3e4aSElliott Hughes        "CFF ",
543*e1fe3e4aSElliott Hughes        "hmtx",
544*e1fe3e4aSElliott Hughes        "DSIG",
545*e1fe3e4aSElliott Hughes    )
546*e1fe3e4aSElliott Hughes    for table in expected_tables:
547*e1fe3e4aSElliott Hughes        assert table in ttf
548*e1fe3e4aSElliott Hughes
549*e1fe3e4aSElliott Hughes
550*e1fe3e4aSElliott Hughesdef test_ttcompile_otf_to_woff_without_zopfli(tmpdir):
551*e1fe3e4aSElliott Hughes    inttx = os.path.join("Tests", "ttx", "data", "TestOTF.ttx")
552*e1fe3e4aSElliott Hughes    outwoff = tmpdir.join("TestOTF.woff")
553*e1fe3e4aSElliott Hughes    options = ttx.Options([], 1)
554*e1fe3e4aSElliott Hughes    options.flavor = "woff"
555*e1fe3e4aSElliott Hughes    ttx.ttCompile(inttx, str(outwoff), options)
556*e1fe3e4aSElliott Hughes    # confirm that font was built
557*e1fe3e4aSElliott Hughes    assert outwoff.check(file=True)
558*e1fe3e4aSElliott Hughes    # confirm that it is valid TTF file, can instantiate a TTFont, has expected OpenType tables
559*e1fe3e4aSElliott Hughes    ttf = TTFont(str(outwoff))
560*e1fe3e4aSElliott Hughes    expected_tables = (
561*e1fe3e4aSElliott Hughes        "head",
562*e1fe3e4aSElliott Hughes        "hhea",
563*e1fe3e4aSElliott Hughes        "maxp",
564*e1fe3e4aSElliott Hughes        "OS/2",
565*e1fe3e4aSElliott Hughes        "name",
566*e1fe3e4aSElliott Hughes        "cmap",
567*e1fe3e4aSElliott Hughes        "post",
568*e1fe3e4aSElliott Hughes        "CFF ",
569*e1fe3e4aSElliott Hughes        "hmtx",
570*e1fe3e4aSElliott Hughes        "DSIG",
571*e1fe3e4aSElliott Hughes    )
572*e1fe3e4aSElliott Hughes    for table in expected_tables:
573*e1fe3e4aSElliott Hughes        assert table in ttf
574*e1fe3e4aSElliott Hughes
575*e1fe3e4aSElliott Hughes
576*e1fe3e4aSElliott Hughes@pytest.mark.skipif(zopfli is None, reason="zopfli not installed")
577*e1fe3e4aSElliott Hughesdef test_ttcompile_otf_to_woff_with_zopfli(tmpdir):
578*e1fe3e4aSElliott Hughes    inttx = os.path.join("Tests", "ttx", "data", "TestOTF.ttx")
579*e1fe3e4aSElliott Hughes    outwoff = tmpdir.join("TestOTF.woff")
580*e1fe3e4aSElliott Hughes    options = ttx.Options([], 1)
581*e1fe3e4aSElliott Hughes    options.flavor = "woff"
582*e1fe3e4aSElliott Hughes    options.useZopfli = True
583*e1fe3e4aSElliott Hughes    ttx.ttCompile(inttx, str(outwoff), options)
584*e1fe3e4aSElliott Hughes    # confirm that font was built
585*e1fe3e4aSElliott Hughes    assert outwoff.check(file=True)
586*e1fe3e4aSElliott Hughes    # confirm that it is valid TTF file, can instantiate a TTFont, has expected OpenType tables
587*e1fe3e4aSElliott Hughes    ttf = TTFont(str(outwoff))
588*e1fe3e4aSElliott Hughes    expected_tables = (
589*e1fe3e4aSElliott Hughes        "head",
590*e1fe3e4aSElliott Hughes        "hhea",
591*e1fe3e4aSElliott Hughes        "maxp",
592*e1fe3e4aSElliott Hughes        "OS/2",
593*e1fe3e4aSElliott Hughes        "name",
594*e1fe3e4aSElliott Hughes        "cmap",
595*e1fe3e4aSElliott Hughes        "post",
596*e1fe3e4aSElliott Hughes        "CFF ",
597*e1fe3e4aSElliott Hughes        "hmtx",
598*e1fe3e4aSElliott Hughes        "DSIG",
599*e1fe3e4aSElliott Hughes    )
600*e1fe3e4aSElliott Hughes    for table in expected_tables:
601*e1fe3e4aSElliott Hughes        assert table in ttf
602*e1fe3e4aSElliott Hughes
603*e1fe3e4aSElliott Hughes
604*e1fe3e4aSElliott Hughes@pytest.mark.skipif(brotli is None, reason="brotli not installed")
605*e1fe3e4aSElliott Hughesdef test_ttcompile_otf_to_woff2(tmpdir):
606*e1fe3e4aSElliott Hughes    inttx = os.path.join("Tests", "ttx", "data", "TestOTF.ttx")
607*e1fe3e4aSElliott Hughes    outwoff2 = tmpdir.join("TestTTF.woff2")
608*e1fe3e4aSElliott Hughes    options = ttx.Options([], 1)
609*e1fe3e4aSElliott Hughes    options.flavor = "woff2"
610*e1fe3e4aSElliott Hughes    ttx.ttCompile(inttx, str(outwoff2), options)
611*e1fe3e4aSElliott Hughes    # confirm that font was built
612*e1fe3e4aSElliott Hughes    assert outwoff2.check(file=True)
613*e1fe3e4aSElliott Hughes    # confirm that it is valid TTF file, can instantiate a TTFont, has expected OpenType tables
614*e1fe3e4aSElliott Hughes    ttf = TTFont(str(outwoff2))
615*e1fe3e4aSElliott Hughes    # DSIG should not be included from original ttx as per woff2 spec (https://dev.w3.org/webfonts/WOFF2/spec/)
616*e1fe3e4aSElliott Hughes    assert "DSIG" not in ttf
617*e1fe3e4aSElliott Hughes    expected_tables = (
618*e1fe3e4aSElliott Hughes        "head",
619*e1fe3e4aSElliott Hughes        "hhea",
620*e1fe3e4aSElliott Hughes        "maxp",
621*e1fe3e4aSElliott Hughes        "OS/2",
622*e1fe3e4aSElliott Hughes        "name",
623*e1fe3e4aSElliott Hughes        "cmap",
624*e1fe3e4aSElliott Hughes        "post",
625*e1fe3e4aSElliott Hughes        "CFF ",
626*e1fe3e4aSElliott Hughes        "hmtx",
627*e1fe3e4aSElliott Hughes    )
628*e1fe3e4aSElliott Hughes    for table in expected_tables:
629*e1fe3e4aSElliott Hughes        assert table in ttf
630*e1fe3e4aSElliott Hughes
631*e1fe3e4aSElliott Hughes
632*e1fe3e4aSElliott Hughesdef test_ttcompile_ttf_compile_default(tmpdir):
633*e1fe3e4aSElliott Hughes    inttx = os.path.join("Tests", "ttx", "data", "TestTTF.ttx")
634*e1fe3e4aSElliott Hughes    outttf = tmpdir.join("TestTTF.ttf")
635*e1fe3e4aSElliott Hughes    default_options = ttx.Options([], 1)
636*e1fe3e4aSElliott Hughes    ttx.ttCompile(inttx, str(outttf), default_options)
637*e1fe3e4aSElliott Hughes    # confirm that font was built
638*e1fe3e4aSElliott Hughes    assert outttf.check(file=True)
639*e1fe3e4aSElliott Hughes    # confirm that it is valid TTF file, can instantiate a TTFont, has expected OpenType tables
640*e1fe3e4aSElliott Hughes    ttf = TTFont(str(outttf))
641*e1fe3e4aSElliott Hughes    expected_tables = (
642*e1fe3e4aSElliott Hughes        "head",
643*e1fe3e4aSElliott Hughes        "hhea",
644*e1fe3e4aSElliott Hughes        "maxp",
645*e1fe3e4aSElliott Hughes        "OS/2",
646*e1fe3e4aSElliott Hughes        "name",
647*e1fe3e4aSElliott Hughes        "cmap",
648*e1fe3e4aSElliott Hughes        "hmtx",
649*e1fe3e4aSElliott Hughes        "fpgm",
650*e1fe3e4aSElliott Hughes        "prep",
651*e1fe3e4aSElliott Hughes        "cvt ",
652*e1fe3e4aSElliott Hughes        "loca",
653*e1fe3e4aSElliott Hughes        "glyf",
654*e1fe3e4aSElliott Hughes        "post",
655*e1fe3e4aSElliott Hughes        "gasp",
656*e1fe3e4aSElliott Hughes        "DSIG",
657*e1fe3e4aSElliott Hughes    )
658*e1fe3e4aSElliott Hughes    for table in expected_tables:
659*e1fe3e4aSElliott Hughes        assert table in ttf
660*e1fe3e4aSElliott Hughes
661*e1fe3e4aSElliott Hughes
662*e1fe3e4aSElliott Hughesdef test_ttcompile_ttf_to_woff_without_zopfli(tmpdir):
663*e1fe3e4aSElliott Hughes    inttx = os.path.join("Tests", "ttx", "data", "TestTTF.ttx")
664*e1fe3e4aSElliott Hughes    outwoff = tmpdir.join("TestTTF.woff")
665*e1fe3e4aSElliott Hughes    options = ttx.Options([], 1)
666*e1fe3e4aSElliott Hughes    options.flavor = "woff"
667*e1fe3e4aSElliott Hughes    ttx.ttCompile(inttx, str(outwoff), options)
668*e1fe3e4aSElliott Hughes    # confirm that font was built
669*e1fe3e4aSElliott Hughes    assert outwoff.check(file=True)
670*e1fe3e4aSElliott Hughes    # confirm that it is valid TTF file, can instantiate a TTFont, has expected OpenType tables
671*e1fe3e4aSElliott Hughes    ttf = TTFont(str(outwoff))
672*e1fe3e4aSElliott Hughes    expected_tables = (
673*e1fe3e4aSElliott Hughes        "head",
674*e1fe3e4aSElliott Hughes        "hhea",
675*e1fe3e4aSElliott Hughes        "maxp",
676*e1fe3e4aSElliott Hughes        "OS/2",
677*e1fe3e4aSElliott Hughes        "name",
678*e1fe3e4aSElliott Hughes        "cmap",
679*e1fe3e4aSElliott Hughes        "hmtx",
680*e1fe3e4aSElliott Hughes        "fpgm",
681*e1fe3e4aSElliott Hughes        "prep",
682*e1fe3e4aSElliott Hughes        "cvt ",
683*e1fe3e4aSElliott Hughes        "loca",
684*e1fe3e4aSElliott Hughes        "glyf",
685*e1fe3e4aSElliott Hughes        "post",
686*e1fe3e4aSElliott Hughes        "gasp",
687*e1fe3e4aSElliott Hughes        "DSIG",
688*e1fe3e4aSElliott Hughes    )
689*e1fe3e4aSElliott Hughes    for table in expected_tables:
690*e1fe3e4aSElliott Hughes        assert table in ttf
691*e1fe3e4aSElliott Hughes
692*e1fe3e4aSElliott Hughes
693*e1fe3e4aSElliott Hughes@pytest.mark.skipif(zopfli is None, reason="zopfli not installed")
694*e1fe3e4aSElliott Hughesdef test_ttcompile_ttf_to_woff_with_zopfli(tmpdir):
695*e1fe3e4aSElliott Hughes    inttx = os.path.join("Tests", "ttx", "data", "TestTTF.ttx")
696*e1fe3e4aSElliott Hughes    outwoff = tmpdir.join("TestTTF.woff")
697*e1fe3e4aSElliott Hughes    options = ttx.Options([], 1)
698*e1fe3e4aSElliott Hughes    options.flavor = "woff"
699*e1fe3e4aSElliott Hughes    options.useZopfli = True
700*e1fe3e4aSElliott Hughes    ttx.ttCompile(inttx, str(outwoff), options)
701*e1fe3e4aSElliott Hughes    # confirm that font was built
702*e1fe3e4aSElliott Hughes    assert outwoff.check(file=True)
703*e1fe3e4aSElliott Hughes    # confirm that it is valid TTF file, can instantiate a TTFont, has expected OpenType tables
704*e1fe3e4aSElliott Hughes    ttf = TTFont(str(outwoff))
705*e1fe3e4aSElliott Hughes    expected_tables = (
706*e1fe3e4aSElliott Hughes        "head",
707*e1fe3e4aSElliott Hughes        "hhea",
708*e1fe3e4aSElliott Hughes        "maxp",
709*e1fe3e4aSElliott Hughes        "OS/2",
710*e1fe3e4aSElliott Hughes        "name",
711*e1fe3e4aSElliott Hughes        "cmap",
712*e1fe3e4aSElliott Hughes        "hmtx",
713*e1fe3e4aSElliott Hughes        "fpgm",
714*e1fe3e4aSElliott Hughes        "prep",
715*e1fe3e4aSElliott Hughes        "cvt ",
716*e1fe3e4aSElliott Hughes        "loca",
717*e1fe3e4aSElliott Hughes        "glyf",
718*e1fe3e4aSElliott Hughes        "post",
719*e1fe3e4aSElliott Hughes        "gasp",
720*e1fe3e4aSElliott Hughes        "DSIG",
721*e1fe3e4aSElliott Hughes    )
722*e1fe3e4aSElliott Hughes    for table in expected_tables:
723*e1fe3e4aSElliott Hughes        assert table in ttf
724*e1fe3e4aSElliott Hughes
725*e1fe3e4aSElliott Hughes
726*e1fe3e4aSElliott Hughes@pytest.mark.skipif(brotli is None, reason="brotli not installed")
727*e1fe3e4aSElliott Hughesdef test_ttcompile_ttf_to_woff2(tmpdir):
728*e1fe3e4aSElliott Hughes    inttx = os.path.join("Tests", "ttx", "data", "TestTTF.ttx")
729*e1fe3e4aSElliott Hughes    outwoff2 = tmpdir.join("TestTTF.woff2")
730*e1fe3e4aSElliott Hughes    options = ttx.Options([], 1)
731*e1fe3e4aSElliott Hughes    options.flavor = "woff2"
732*e1fe3e4aSElliott Hughes    ttx.ttCompile(inttx, str(outwoff2), options)
733*e1fe3e4aSElliott Hughes    # confirm that font was built
734*e1fe3e4aSElliott Hughes    assert outwoff2.check(file=True)
735*e1fe3e4aSElliott Hughes    # confirm that it is valid TTF file, can instantiate a TTFont, has expected OpenType tables
736*e1fe3e4aSElliott Hughes    ttf = TTFont(str(outwoff2))
737*e1fe3e4aSElliott Hughes    # DSIG should not be included from original ttx as per woff2 spec (https://dev.w3.org/webfonts/WOFF2/spec/)
738*e1fe3e4aSElliott Hughes    assert "DSIG" not in ttf
739*e1fe3e4aSElliott Hughes    expected_tables = (
740*e1fe3e4aSElliott Hughes        "head",
741*e1fe3e4aSElliott Hughes        "hhea",
742*e1fe3e4aSElliott Hughes        "maxp",
743*e1fe3e4aSElliott Hughes        "OS/2",
744*e1fe3e4aSElliott Hughes        "name",
745*e1fe3e4aSElliott Hughes        "cmap",
746*e1fe3e4aSElliott Hughes        "hmtx",
747*e1fe3e4aSElliott Hughes        "fpgm",
748*e1fe3e4aSElliott Hughes        "prep",
749*e1fe3e4aSElliott Hughes        "cvt ",
750*e1fe3e4aSElliott Hughes        "loca",
751*e1fe3e4aSElliott Hughes        "glyf",
752*e1fe3e4aSElliott Hughes        "post",
753*e1fe3e4aSElliott Hughes        "gasp",
754*e1fe3e4aSElliott Hughes    )
755*e1fe3e4aSElliott Hughes    for table in expected_tables:
756*e1fe3e4aSElliott Hughes        assert table in ttf
757*e1fe3e4aSElliott Hughes
758*e1fe3e4aSElliott Hughes
759*e1fe3e4aSElliott Hughes@pytest.mark.parametrize(
760*e1fe3e4aSElliott Hughes    "inpath, outpath1, outpath2",
761*e1fe3e4aSElliott Hughes    [
762*e1fe3e4aSElliott Hughes        ("TestTTF.ttx", "TestTTF1.ttf", "TestTTF2.ttf"),
763*e1fe3e4aSElliott Hughes        ("TestOTF.ttx", "TestOTF1.otf", "TestOTF2.otf"),
764*e1fe3e4aSElliott Hughes    ],
765*e1fe3e4aSElliott Hughes)
766*e1fe3e4aSElliott Hughesdef test_ttcompile_timestamp_calcs(inpath, outpath1, outpath2, tmpdir):
767*e1fe3e4aSElliott Hughes    inttx = os.path.join("Tests", "ttx", "data", inpath)
768*e1fe3e4aSElliott Hughes    outttf1 = tmpdir.join(outpath1)
769*e1fe3e4aSElliott Hughes    outttf2 = tmpdir.join(outpath2)
770*e1fe3e4aSElliott Hughes    options = ttx.Options([], 1)
771*e1fe3e4aSElliott Hughes    # build with default options = do not recalculate timestamp
772*e1fe3e4aSElliott Hughes    ttx.ttCompile(inttx, str(outttf1), options)
773*e1fe3e4aSElliott Hughes    # confirm that font was built
774*e1fe3e4aSElliott Hughes    assert outttf1.check(file=True)
775*e1fe3e4aSElliott Hughes    # confirm that timestamp is same as modified time on ttx file
776*e1fe3e4aSElliott Hughes    mtime = os.path.getmtime(inttx)
777*e1fe3e4aSElliott Hughes    epochtime = timestampSinceEpoch(mtime)
778*e1fe3e4aSElliott Hughes    ttf = TTFont(str(outttf1))
779*e1fe3e4aSElliott Hughes    assert ttf["head"].modified == epochtime
780*e1fe3e4aSElliott Hughes
781*e1fe3e4aSElliott Hughes    # reset options to recalculate the timestamp and compile new font
782*e1fe3e4aSElliott Hughes    options.recalcTimestamp = True
783*e1fe3e4aSElliott Hughes    ttx.ttCompile(inttx, str(outttf2), options)
784*e1fe3e4aSElliott Hughes    # confirm that font was built
785*e1fe3e4aSElliott Hughes    assert outttf2.check(file=True)
786*e1fe3e4aSElliott Hughes    # confirm that timestamp is more recent than modified time on ttx file
787*e1fe3e4aSElliott Hughes    mtime = os.path.getmtime(inttx)
788*e1fe3e4aSElliott Hughes    epochtime = timestampSinceEpoch(mtime)
789*e1fe3e4aSElliott Hughes    ttf = TTFont(str(outttf2))
790*e1fe3e4aSElliott Hughes    assert ttf["head"].modified > epochtime
791*e1fe3e4aSElliott Hughes
792*e1fe3e4aSElliott Hughes    # --no-recalc-timestamp will keep original timestamp
793*e1fe3e4aSElliott Hughes    options.recalcTimestamp = False
794*e1fe3e4aSElliott Hughes    ttx.ttCompile(inttx, str(outttf2), options)
795*e1fe3e4aSElliott Hughes    assert outttf2.check(file=True)
796*e1fe3e4aSElliott Hughes    inttf = TTFont()
797*e1fe3e4aSElliott Hughes    inttf.importXML(inttx)
798*e1fe3e4aSElliott Hughes    assert inttf["head"].modified == TTFont(str(outttf2))["head"].modified
799*e1fe3e4aSElliott Hughes
800*e1fe3e4aSElliott Hughes
801*e1fe3e4aSElliott Hughes# -------------------------
802*e1fe3e4aSElliott Hughes# ttx.ttList function tests
803*e1fe3e4aSElliott Hughes# -------------------------
804*e1fe3e4aSElliott Hughes
805*e1fe3e4aSElliott Hughes
806*e1fe3e4aSElliott Hughesdef test_ttlist_ttf(capsys, tmpdir):
807*e1fe3e4aSElliott Hughes    inpath = os.path.join("Tests", "ttx", "data", "TestTTF.ttf")
808*e1fe3e4aSElliott Hughes    fakeoutpath = tmpdir.join("TestTTF.ttx")
809*e1fe3e4aSElliott Hughes    options = ttx.Options([], 1)
810*e1fe3e4aSElliott Hughes    options.listTables = True
811*e1fe3e4aSElliott Hughes    ttx.ttList(inpath, str(fakeoutpath), options)
812*e1fe3e4aSElliott Hughes    out, err = capsys.readouterr()
813*e1fe3e4aSElliott Hughes    expected_tables = (
814*e1fe3e4aSElliott Hughes        "head",
815*e1fe3e4aSElliott Hughes        "hhea",
816*e1fe3e4aSElliott Hughes        "maxp",
817*e1fe3e4aSElliott Hughes        "OS/2",
818*e1fe3e4aSElliott Hughes        "name",
819*e1fe3e4aSElliott Hughes        "cmap",
820*e1fe3e4aSElliott Hughes        "hmtx",
821*e1fe3e4aSElliott Hughes        "fpgm",
822*e1fe3e4aSElliott Hughes        "prep",
823*e1fe3e4aSElliott Hughes        "cvt ",
824*e1fe3e4aSElliott Hughes        "loca",
825*e1fe3e4aSElliott Hughes        "glyf",
826*e1fe3e4aSElliott Hughes        "post",
827*e1fe3e4aSElliott Hughes        "gasp",
828*e1fe3e4aSElliott Hughes        "DSIG",
829*e1fe3e4aSElliott Hughes    )
830*e1fe3e4aSElliott Hughes    # confirm that expected tables are printed to stdout
831*e1fe3e4aSElliott Hughes    for table in expected_tables:
832*e1fe3e4aSElliott Hughes        assert table in out
833*e1fe3e4aSElliott Hughes    # test for one of the expected tag/checksum/length/offset strings
834*e1fe3e4aSElliott Hughes    assert "OS/2  0x67230FF8        96       376" in out
835*e1fe3e4aSElliott Hughes
836*e1fe3e4aSElliott Hughes
837*e1fe3e4aSElliott Hughesdef test_ttlist_otf(capsys, tmpdir):
838*e1fe3e4aSElliott Hughes    inpath = os.path.join("Tests", "ttx", "data", "TestOTF.otf")
839*e1fe3e4aSElliott Hughes    fakeoutpath = tmpdir.join("TestOTF.ttx")
840*e1fe3e4aSElliott Hughes    options = ttx.Options([], 1)
841*e1fe3e4aSElliott Hughes    options.listTables = True
842*e1fe3e4aSElliott Hughes    ttx.ttList(inpath, str(fakeoutpath), options)
843*e1fe3e4aSElliott Hughes    out, err = capsys.readouterr()
844*e1fe3e4aSElliott Hughes    expected_tables = (
845*e1fe3e4aSElliott Hughes        "head",
846*e1fe3e4aSElliott Hughes        "hhea",
847*e1fe3e4aSElliott Hughes        "maxp",
848*e1fe3e4aSElliott Hughes        "OS/2",
849*e1fe3e4aSElliott Hughes        "name",
850*e1fe3e4aSElliott Hughes        "cmap",
851*e1fe3e4aSElliott Hughes        "post",
852*e1fe3e4aSElliott Hughes        "CFF ",
853*e1fe3e4aSElliott Hughes        "hmtx",
854*e1fe3e4aSElliott Hughes        "DSIG",
855*e1fe3e4aSElliott Hughes    )
856*e1fe3e4aSElliott Hughes    # confirm that expected tables are printed to stdout
857*e1fe3e4aSElliott Hughes    for table in expected_tables:
858*e1fe3e4aSElliott Hughes        assert table in out
859*e1fe3e4aSElliott Hughes    # test for one of the expected tag/checksum/length/offset strings
860*e1fe3e4aSElliott Hughes    assert "OS/2  0x67230FF8        96       272" in out
861*e1fe3e4aSElliott Hughes
862*e1fe3e4aSElliott Hughes
863*e1fe3e4aSElliott Hughesdef test_ttlist_woff(capsys, tmpdir):
864*e1fe3e4aSElliott Hughes    inpath = os.path.join("Tests", "ttx", "data", "TestWOFF.woff")
865*e1fe3e4aSElliott Hughes    fakeoutpath = tmpdir.join("TestWOFF.ttx")
866*e1fe3e4aSElliott Hughes    options = ttx.Options([], 1)
867*e1fe3e4aSElliott Hughes    options.listTables = True
868*e1fe3e4aSElliott Hughes    options.flavor = "woff"
869*e1fe3e4aSElliott Hughes    ttx.ttList(inpath, str(fakeoutpath), options)
870*e1fe3e4aSElliott Hughes    out, err = capsys.readouterr()
871*e1fe3e4aSElliott Hughes    expected_tables = (
872*e1fe3e4aSElliott Hughes        "head",
873*e1fe3e4aSElliott Hughes        "hhea",
874*e1fe3e4aSElliott Hughes        "maxp",
875*e1fe3e4aSElliott Hughes        "OS/2",
876*e1fe3e4aSElliott Hughes        "name",
877*e1fe3e4aSElliott Hughes        "cmap",
878*e1fe3e4aSElliott Hughes        "post",
879*e1fe3e4aSElliott Hughes        "CFF ",
880*e1fe3e4aSElliott Hughes        "hmtx",
881*e1fe3e4aSElliott Hughes        "DSIG",
882*e1fe3e4aSElliott Hughes    )
883*e1fe3e4aSElliott Hughes    # confirm that expected tables are printed to stdout
884*e1fe3e4aSElliott Hughes    for table in expected_tables:
885*e1fe3e4aSElliott Hughes        assert table in out
886*e1fe3e4aSElliott Hughes    # test for one of the expected tag/checksum/length/offset strings
887*e1fe3e4aSElliott Hughes    assert "OS/2  0x67230FF8        84       340" in out
888*e1fe3e4aSElliott Hughes
889*e1fe3e4aSElliott Hughes
890*e1fe3e4aSElliott Hughes@pytest.mark.skipif(brotli is None, reason="brotli not installed")
891*e1fe3e4aSElliott Hughesdef test_ttlist_woff2(capsys, tmpdir):
892*e1fe3e4aSElliott Hughes    inpath = os.path.join("Tests", "ttx", "data", "TestWOFF2.woff2")
893*e1fe3e4aSElliott Hughes    fakeoutpath = tmpdir.join("TestWOFF2.ttx")
894*e1fe3e4aSElliott Hughes    options = ttx.Options([], 1)
895*e1fe3e4aSElliott Hughes    options.listTables = True
896*e1fe3e4aSElliott Hughes    options.flavor = "woff2"
897*e1fe3e4aSElliott Hughes    ttx.ttList(inpath, str(fakeoutpath), options)
898*e1fe3e4aSElliott Hughes    out, err = capsys.readouterr()
899*e1fe3e4aSElliott Hughes    expected_tables = (
900*e1fe3e4aSElliott Hughes        "head",
901*e1fe3e4aSElliott Hughes        "hhea",
902*e1fe3e4aSElliott Hughes        "maxp",
903*e1fe3e4aSElliott Hughes        "OS/2",
904*e1fe3e4aSElliott Hughes        "name",
905*e1fe3e4aSElliott Hughes        "cmap",
906*e1fe3e4aSElliott Hughes        "hmtx",
907*e1fe3e4aSElliott Hughes        "fpgm",
908*e1fe3e4aSElliott Hughes        "prep",
909*e1fe3e4aSElliott Hughes        "cvt ",
910*e1fe3e4aSElliott Hughes        "loca",
911*e1fe3e4aSElliott Hughes        "glyf",
912*e1fe3e4aSElliott Hughes        "post",
913*e1fe3e4aSElliott Hughes        "gasp",
914*e1fe3e4aSElliott Hughes    )
915*e1fe3e4aSElliott Hughes    # confirm that expected tables are printed to stdout
916*e1fe3e4aSElliott Hughes    for table in expected_tables:
917*e1fe3e4aSElliott Hughes        assert table in out
918*e1fe3e4aSElliott Hughes    # test for one of the expected tag/checksum/length/offset strings
919*e1fe3e4aSElliott Hughes    assert "OS/2  0x67230FF8        96         0" in out
920*e1fe3e4aSElliott Hughes
921*e1fe3e4aSElliott Hughes
922*e1fe3e4aSElliott Hughes# -------------------
923*e1fe3e4aSElliott Hughes# main function tests
924*e1fe3e4aSElliott Hughes# -------------------
925*e1fe3e4aSElliott Hughes
926*e1fe3e4aSElliott Hughes
927*e1fe3e4aSElliott Hughesdef test_main_default_ttf_dump_to_ttx(tmpdir):
928*e1fe3e4aSElliott Hughes    inpath = os.path.join("Tests", "ttx", "data", "TestTTF.ttf")
929*e1fe3e4aSElliott Hughes    outpath = tmpdir.join("TestTTF.ttx")
930*e1fe3e4aSElliott Hughes    args = ["-o", str(outpath), inpath]
931*e1fe3e4aSElliott Hughes    ttx.main(args)
932*e1fe3e4aSElliott Hughes    assert outpath.check(file=True)
933*e1fe3e4aSElliott Hughes
934*e1fe3e4aSElliott Hughes
935*e1fe3e4aSElliott Hughesdef test_main_default_ttx_compile_to_ttf(tmpdir):
936*e1fe3e4aSElliott Hughes    inpath = os.path.join("Tests", "ttx", "data", "TestTTF.ttx")
937*e1fe3e4aSElliott Hughes    outpath = tmpdir.join("TestTTF.ttf")
938*e1fe3e4aSElliott Hughes    args = ["-o", str(outpath), inpath]
939*e1fe3e4aSElliott Hughes    ttx.main(args)
940*e1fe3e4aSElliott Hughes    assert outpath.check(file=True)
941*e1fe3e4aSElliott Hughes
942*e1fe3e4aSElliott Hughes
943*e1fe3e4aSElliott Hughesdef test_main_getopterror_missing_directory():
944*e1fe3e4aSElliott Hughes    with pytest.raises(SystemExit):
945*e1fe3e4aSElliott Hughes        with pytest.raises(getopt.GetoptError):
946*e1fe3e4aSElliott Hughes            inpath = os.path.join("Tests", "ttx", "data", "TestTTF.ttf")
947*e1fe3e4aSElliott Hughes            args = ["-d", "bogusdir", inpath]
948*e1fe3e4aSElliott Hughes            ttx.main(args)
949*e1fe3e4aSElliott Hughes
950*e1fe3e4aSElliott Hughes
951*e1fe3e4aSElliott Hughesdef test_main_keyboard_interrupt(tmpdir, monkeypatch, caplog):
952*e1fe3e4aSElliott Hughes    with pytest.raises(SystemExit):
953*e1fe3e4aSElliott Hughes        inpath = os.path.join("Tests", "ttx", "data", "TestTTF.ttx")
954*e1fe3e4aSElliott Hughes        outpath = tmpdir.join("TestTTF.ttf")
955*e1fe3e4aSElliott Hughes        args = ["-o", str(outpath), inpath]
956*e1fe3e4aSElliott Hughes        monkeypatch.setattr(
957*e1fe3e4aSElliott Hughes            ttx, "process", (lambda x, y: raise_exception(KeyboardInterrupt))
958*e1fe3e4aSElliott Hughes        )
959*e1fe3e4aSElliott Hughes        ttx.main(args)
960*e1fe3e4aSElliott Hughes
961*e1fe3e4aSElliott Hughes    assert "(Cancelled.)" in caplog.text
962*e1fe3e4aSElliott Hughes
963*e1fe3e4aSElliott Hughes
964*e1fe3e4aSElliott Hughesdef test_main_system_exit(tmpdir, monkeypatch):
965*e1fe3e4aSElliott Hughes    with pytest.raises(SystemExit):
966*e1fe3e4aSElliott Hughes        inpath = os.path.join("Tests", "ttx", "data", "TestTTF.ttx")
967*e1fe3e4aSElliott Hughes        outpath = tmpdir.join("TestTTF.ttf")
968*e1fe3e4aSElliott Hughes        args = ["-o", str(outpath), inpath]
969*e1fe3e4aSElliott Hughes        monkeypatch.setattr(ttx, "process", (lambda x, y: raise_exception(SystemExit)))
970*e1fe3e4aSElliott Hughes        ttx.main(args)
971*e1fe3e4aSElliott Hughes
972*e1fe3e4aSElliott Hughes
973*e1fe3e4aSElliott Hughesdef test_main_ttlib_error(tmpdir, monkeypatch, caplog):
974*e1fe3e4aSElliott Hughes    with pytest.raises(SystemExit):
975*e1fe3e4aSElliott Hughes        inpath = os.path.join("Tests", "ttx", "data", "TestTTF.ttx")
976*e1fe3e4aSElliott Hughes        outpath = tmpdir.join("TestTTF.ttf")
977*e1fe3e4aSElliott Hughes        args = ["-o", str(outpath), inpath]
978*e1fe3e4aSElliott Hughes        monkeypatch.setattr(
979*e1fe3e4aSElliott Hughes            ttx,
980*e1fe3e4aSElliott Hughes            "process",
981*e1fe3e4aSElliott Hughes            (lambda x, y: raise_exception(TTLibError("Test error"))),
982*e1fe3e4aSElliott Hughes        )
983*e1fe3e4aSElliott Hughes        ttx.main(args)
984*e1fe3e4aSElliott Hughes
985*e1fe3e4aSElliott Hughes    assert "Test error" in caplog.text
986*e1fe3e4aSElliott Hughes
987*e1fe3e4aSElliott Hughes
988*e1fe3e4aSElliott Hughesdef test_main_base_exception(tmpdir, monkeypatch, caplog):
989*e1fe3e4aSElliott Hughes    with pytest.raises(SystemExit):
990*e1fe3e4aSElliott Hughes        inpath = os.path.join("Tests", "ttx", "data", "TestTTF.ttx")
991*e1fe3e4aSElliott Hughes        outpath = tmpdir.join("TestTTF.ttf")
992*e1fe3e4aSElliott Hughes        args = ["-o", str(outpath), inpath]
993*e1fe3e4aSElliott Hughes        monkeypatch.setattr(
994*e1fe3e4aSElliott Hughes            ttx,
995*e1fe3e4aSElliott Hughes            "process",
996*e1fe3e4aSElliott Hughes            (lambda x, y: raise_exception(Exception("Test error"))),
997*e1fe3e4aSElliott Hughes        )
998*e1fe3e4aSElliott Hughes        ttx.main(args)
999*e1fe3e4aSElliott Hughes
1000*e1fe3e4aSElliott Hughes    assert "Unhandled exception has occurred" in caplog.text
1001*e1fe3e4aSElliott Hughes
1002*e1fe3e4aSElliott Hughes
1003*e1fe3e4aSElliott Hughesdef test_main_ttf_dump_stdin_to_stdout(tmp_path):
1004*e1fe3e4aSElliott Hughes    inpath = Path("Tests").joinpath("ttx", "data", "TestTTF.ttf")
1005*e1fe3e4aSElliott Hughes    outpath = tmp_path / "TestTTF.ttx"
1006*e1fe3e4aSElliott Hughes    args = [sys.executable, "-m", "fontTools.ttx", "-q", "-o", "-", "-"]
1007*e1fe3e4aSElliott Hughes    with inpath.open("rb") as infile, outpath.open("w", encoding="utf-8") as outfile:
1008*e1fe3e4aSElliott Hughes        subprocess.run(args, check=True, stdin=infile, stdout=outfile)
1009*e1fe3e4aSElliott Hughes    assert outpath.is_file()
1010*e1fe3e4aSElliott Hughes
1011*e1fe3e4aSElliott Hughes
1012*e1fe3e4aSElliott Hughesdef test_main_ttx_compile_stdin_to_stdout(tmp_path):
1013*e1fe3e4aSElliott Hughes    inpath = Path("Tests").joinpath("ttx", "data", "TestTTF.ttx")
1014*e1fe3e4aSElliott Hughes    outpath = tmp_path / "TestTTF.ttf"
1015*e1fe3e4aSElliott Hughes    args = [sys.executable, "-m", "fontTools.ttx", "-q", "-o", "-", "-"]
1016*e1fe3e4aSElliott Hughes    with inpath.open("r", encoding="utf-8") as infile, outpath.open("wb") as outfile:
1017*e1fe3e4aSElliott Hughes        subprocess.run(args, check=True, stdin=infile, stdout=outfile)
1018*e1fe3e4aSElliott Hughes    assert outpath.is_file()
1019*e1fe3e4aSElliott Hughes
1020*e1fe3e4aSElliott Hughes
1021*e1fe3e4aSElliott Hughesdef test_roundtrip_DSIG_split_at_XML_parse_buffer_size(tmp_path):
1022*e1fe3e4aSElliott Hughes    inpath = Path("Tests").joinpath(
1023*e1fe3e4aSElliott Hughes        "ttx", "data", "roundtrip_DSIG_split_at_XML_parse_buffer_size.ttx"
1024*e1fe3e4aSElliott Hughes    )
1025*e1fe3e4aSElliott Hughes    font = TTFont()
1026*e1fe3e4aSElliott Hughes    font.importXML(inpath)
1027*e1fe3e4aSElliott Hughes    font["DMMY"] = DefaultTable(tag="DMMY")
1028*e1fe3e4aSElliott Hughes    # just enough dummy bytes to hit the cut off point whereby DSIG data gets
1029*e1fe3e4aSElliott Hughes    # split into two chunks and triggers the bug from
1030*e1fe3e4aSElliott Hughes    # https://github.com/fonttools/fonttools/issues/2614
1031*e1fe3e4aSElliott Hughes    font["DMMY"].data = b"\x01\x02\x03\x04" * 2438
1032*e1fe3e4aSElliott Hughes    font.saveXML(tmp_path / "roundtrip_DSIG_split_at_XML_parse_buffer_size.ttx")
1033*e1fe3e4aSElliott Hughes
1034*e1fe3e4aSElliott Hughes    outpath = tmp_path / "font.ttf"
1035*e1fe3e4aSElliott Hughes    args = [
1036*e1fe3e4aSElliott Hughes        sys.executable,
1037*e1fe3e4aSElliott Hughes        "-m",
1038*e1fe3e4aSElliott Hughes        "fontTools.ttx",
1039*e1fe3e4aSElliott Hughes        "-q",
1040*e1fe3e4aSElliott Hughes        "-o",
1041*e1fe3e4aSElliott Hughes        str(outpath),
1042*e1fe3e4aSElliott Hughes        str(tmp_path / "roundtrip_DSIG_split_at_XML_parse_buffer_size.ttx"),
1043*e1fe3e4aSElliott Hughes    ]
1044*e1fe3e4aSElliott Hughes    subprocess.run(args, check=True)
1045*e1fe3e4aSElliott Hughes
1046*e1fe3e4aSElliott Hughes    assert outpath.is_file()
1047*e1fe3e4aSElliott Hughes    assert TTFont(outpath)["DSIG"].signatureRecords[0].pkcs7 == base64.b64decode(
1048*e1fe3e4aSElliott Hughes        b"0000000100000000"
1049*e1fe3e4aSElliott Hughes    )
1050*e1fe3e4aSElliott Hughes
1051*e1fe3e4aSElliott Hughes
1052*e1fe3e4aSElliott Hughes# ---------------------------
1053*e1fe3e4aSElliott Hughes# support functions for tests
1054*e1fe3e4aSElliott Hughes# ---------------------------
1055*e1fe3e4aSElliott Hughes
1056*e1fe3e4aSElliott Hughes
1057*e1fe3e4aSElliott Hughesdef raise_exception(exception):
1058*e1fe3e4aSElliott Hughes    raise exception
1059