xref: /aosp_15_r20/external/fonttools/Tests/ttLib/woff2_test.py (revision e1fe3e4ad2793916b15cccdc4a7da52a7e1dd0e9)
1*e1fe3e4aSElliott Hughesfrom fontTools import ttLib
2*e1fe3e4aSElliott Hughesfrom fontTools.ttLib import woff2
3*e1fe3e4aSElliott Hughesfrom fontTools.ttLib.tables import _g_l_y_f
4*e1fe3e4aSElliott Hughesfrom fontTools.ttLib.woff2 import (
5*e1fe3e4aSElliott Hughes    WOFF2Reader,
6*e1fe3e4aSElliott Hughes    woff2DirectorySize,
7*e1fe3e4aSElliott Hughes    woff2DirectoryFormat,
8*e1fe3e4aSElliott Hughes    woff2FlagsSize,
9*e1fe3e4aSElliott Hughes    woff2UnknownTagSize,
10*e1fe3e4aSElliott Hughes    woff2Base128MaxSize,
11*e1fe3e4aSElliott Hughes    WOFF2DirectoryEntry,
12*e1fe3e4aSElliott Hughes    getKnownTagIndex,
13*e1fe3e4aSElliott Hughes    packBase128,
14*e1fe3e4aSElliott Hughes    base128Size,
15*e1fe3e4aSElliott Hughes    woff2UnknownTagIndex,
16*e1fe3e4aSElliott Hughes    WOFF2FlavorData,
17*e1fe3e4aSElliott Hughes    woff2TransformedTableTags,
18*e1fe3e4aSElliott Hughes    WOFF2GlyfTable,
19*e1fe3e4aSElliott Hughes    WOFF2LocaTable,
20*e1fe3e4aSElliott Hughes    WOFF2HmtxTable,
21*e1fe3e4aSElliott Hughes    WOFF2Writer,
22*e1fe3e4aSElliott Hughes    unpackBase128,
23*e1fe3e4aSElliott Hughes    unpack255UShort,
24*e1fe3e4aSElliott Hughes    pack255UShort,
25*e1fe3e4aSElliott Hughes)
26*e1fe3e4aSElliott Hughesimport unittest
27*e1fe3e4aSElliott Hughesfrom fontTools.misc import sstruct
28*e1fe3e4aSElliott Hughesfrom fontTools.misc.textTools import Tag, bytechr, byteord
29*e1fe3e4aSElliott Hughesfrom fontTools import fontBuilder
30*e1fe3e4aSElliott Hughesfrom fontTools.pens.ttGlyphPen import TTGlyphPen
31*e1fe3e4aSElliott Hughesfrom fontTools.pens.recordingPen import RecordingPen
32*e1fe3e4aSElliott Hughesfrom io import BytesIO
33*e1fe3e4aSElliott Hughesimport struct
34*e1fe3e4aSElliott Hughesimport os
35*e1fe3e4aSElliott Hughesimport random
36*e1fe3e4aSElliott Hughesimport copy
37*e1fe3e4aSElliott Hughesfrom collections import OrderedDict
38*e1fe3e4aSElliott Hughesfrom functools import partial
39*e1fe3e4aSElliott Hughesimport pytest
40*e1fe3e4aSElliott Hughes
41*e1fe3e4aSElliott HugheshaveBrotli = False
42*e1fe3e4aSElliott Hughestry:
43*e1fe3e4aSElliott Hughes    try:
44*e1fe3e4aSElliott Hughes        import brotlicffi as brotli
45*e1fe3e4aSElliott Hughes    except ImportError:
46*e1fe3e4aSElliott Hughes        import brotli
47*e1fe3e4aSElliott Hughes    haveBrotli = True
48*e1fe3e4aSElliott Hughesexcept ImportError:
49*e1fe3e4aSElliott Hughes    pass
50*e1fe3e4aSElliott Hughes
51*e1fe3e4aSElliott Hughes
52*e1fe3e4aSElliott Hughes# Python 3 renamed 'assertRaisesRegexp' to 'assertRaisesRegex', and fires
53*e1fe3e4aSElliott Hughes# deprecation warnings if a program uses the old name.
54*e1fe3e4aSElliott Hughesif not hasattr(unittest.TestCase, "assertRaisesRegex"):
55*e1fe3e4aSElliott Hughes    unittest.TestCase.assertRaisesRegex = unittest.TestCase.assertRaisesRegexp
56*e1fe3e4aSElliott Hughes
57*e1fe3e4aSElliott Hughes
58*e1fe3e4aSElliott Hughescurrent_dir = os.path.abspath(os.path.dirname(os.path.realpath(__file__)))
59*e1fe3e4aSElliott Hughesdata_dir = os.path.join(current_dir, "data")
60*e1fe3e4aSElliott HughesTTX = os.path.join(data_dir, "TestTTF-Regular.ttx")
61*e1fe3e4aSElliott HughesOTX = os.path.join(data_dir, "TestOTF-Regular.otx")
62*e1fe3e4aSElliott HughesMETADATA = os.path.join(data_dir, "test_woff2_metadata.xml")
63*e1fe3e4aSElliott Hughes
64*e1fe3e4aSElliott HughesTT_WOFF2 = BytesIO()
65*e1fe3e4aSElliott HughesCFF_WOFF2 = BytesIO()
66*e1fe3e4aSElliott Hughes
67*e1fe3e4aSElliott Hughes
68*e1fe3e4aSElliott Hughesdef setUpModule():
69*e1fe3e4aSElliott Hughes    if not haveBrotli:
70*e1fe3e4aSElliott Hughes        raise unittest.SkipTest("No module named brotli")
71*e1fe3e4aSElliott Hughes    assert os.path.exists(TTX)
72*e1fe3e4aSElliott Hughes    assert os.path.exists(OTX)
73*e1fe3e4aSElliott Hughes    # import TT-flavoured test font and save it as WOFF2
74*e1fe3e4aSElliott Hughes    ttf = ttLib.TTFont(recalcBBoxes=False, recalcTimestamp=False)
75*e1fe3e4aSElliott Hughes    ttf.importXML(TTX)
76*e1fe3e4aSElliott Hughes    ttf.flavor = "woff2"
77*e1fe3e4aSElliott Hughes    ttf.save(TT_WOFF2, reorderTables=None)
78*e1fe3e4aSElliott Hughes    # import CFF-flavoured test font and save it as WOFF2
79*e1fe3e4aSElliott Hughes    otf = ttLib.TTFont(recalcBBoxes=False, recalcTimestamp=False)
80*e1fe3e4aSElliott Hughes    otf.importXML(OTX)
81*e1fe3e4aSElliott Hughes    otf.flavor = "woff2"
82*e1fe3e4aSElliott Hughes    otf.save(CFF_WOFF2, reorderTables=None)
83*e1fe3e4aSElliott Hughes
84*e1fe3e4aSElliott Hughes
85*e1fe3e4aSElliott Hughesclass WOFF2ReaderTest(unittest.TestCase):
86*e1fe3e4aSElliott Hughes    @classmethod
87*e1fe3e4aSElliott Hughes    def setUpClass(cls):
88*e1fe3e4aSElliott Hughes        cls.file = BytesIO(CFF_WOFF2.getvalue())
89*e1fe3e4aSElliott Hughes        cls.font = ttLib.TTFont(recalcBBoxes=False, recalcTimestamp=False)
90*e1fe3e4aSElliott Hughes        cls.font.importXML(OTX)
91*e1fe3e4aSElliott Hughes
92*e1fe3e4aSElliott Hughes    def setUp(self):
93*e1fe3e4aSElliott Hughes        self.file.seek(0)
94*e1fe3e4aSElliott Hughes
95*e1fe3e4aSElliott Hughes    def test_bad_signature(self):
96*e1fe3e4aSElliott Hughes        with self.assertRaisesRegex(ttLib.TTLibError, "bad signature"):
97*e1fe3e4aSElliott Hughes            WOFF2Reader(BytesIO(b"wOFF"))
98*e1fe3e4aSElliott Hughes
99*e1fe3e4aSElliott Hughes    def test_not_enough_data_header(self):
100*e1fe3e4aSElliott Hughes        incomplete_header = self.file.read(woff2DirectorySize - 1)
101*e1fe3e4aSElliott Hughes        with self.assertRaisesRegex(ttLib.TTLibError, "not enough data"):
102*e1fe3e4aSElliott Hughes            WOFF2Reader(BytesIO(incomplete_header))
103*e1fe3e4aSElliott Hughes
104*e1fe3e4aSElliott Hughes    def test_incorrect_compressed_size(self):
105*e1fe3e4aSElliott Hughes        data = self.file.read(woff2DirectorySize)
106*e1fe3e4aSElliott Hughes        header = sstruct.unpack(woff2DirectoryFormat, data)
107*e1fe3e4aSElliott Hughes        header["totalCompressedSize"] = 0
108*e1fe3e4aSElliott Hughes        data = sstruct.pack(woff2DirectoryFormat, header)
109*e1fe3e4aSElliott Hughes        with self.assertRaises((brotli.error, ttLib.TTLibError)):
110*e1fe3e4aSElliott Hughes            WOFF2Reader(BytesIO(data + self.file.read()))
111*e1fe3e4aSElliott Hughes
112*e1fe3e4aSElliott Hughes    def test_incorrect_uncompressed_size(self):
113*e1fe3e4aSElliott Hughes        decompress_backup = brotli.decompress
114*e1fe3e4aSElliott Hughes        brotli.decompress = lambda data: b""  # return empty byte string
115*e1fe3e4aSElliott Hughes        with self.assertRaisesRegex(
116*e1fe3e4aSElliott Hughes            ttLib.TTLibError, "unexpected size for decompressed"
117*e1fe3e4aSElliott Hughes        ):
118*e1fe3e4aSElliott Hughes            WOFF2Reader(self.file)
119*e1fe3e4aSElliott Hughes        brotli.decompress = decompress_backup
120*e1fe3e4aSElliott Hughes
121*e1fe3e4aSElliott Hughes    def test_incorrect_file_size(self):
122*e1fe3e4aSElliott Hughes        data = self.file.read(woff2DirectorySize)
123*e1fe3e4aSElliott Hughes        header = sstruct.unpack(woff2DirectoryFormat, data)
124*e1fe3e4aSElliott Hughes        header["length"] -= 1
125*e1fe3e4aSElliott Hughes        data = sstruct.pack(woff2DirectoryFormat, header)
126*e1fe3e4aSElliott Hughes        with self.assertRaisesRegex(
127*e1fe3e4aSElliott Hughes            ttLib.TTLibError, "doesn't match the actual file size"
128*e1fe3e4aSElliott Hughes        ):
129*e1fe3e4aSElliott Hughes            WOFF2Reader(BytesIO(data + self.file.read()))
130*e1fe3e4aSElliott Hughes
131*e1fe3e4aSElliott Hughes    def test_num_tables(self):
132*e1fe3e4aSElliott Hughes        tags = [t for t in self.font.keys() if t not in ("GlyphOrder", "DSIG")]
133*e1fe3e4aSElliott Hughes        data = self.file.read(woff2DirectorySize)
134*e1fe3e4aSElliott Hughes        header = sstruct.unpack(woff2DirectoryFormat, data)
135*e1fe3e4aSElliott Hughes        self.assertEqual(header["numTables"], len(tags))
136*e1fe3e4aSElliott Hughes
137*e1fe3e4aSElliott Hughes    def test_table_tags(self):
138*e1fe3e4aSElliott Hughes        tags = set([t for t in self.font.keys() if t not in ("GlyphOrder", "DSIG")])
139*e1fe3e4aSElliott Hughes        reader = WOFF2Reader(self.file)
140*e1fe3e4aSElliott Hughes        self.assertEqual(set(reader.keys()), tags)
141*e1fe3e4aSElliott Hughes
142*e1fe3e4aSElliott Hughes    def test_get_normal_tables(self):
143*e1fe3e4aSElliott Hughes        woff2Reader = WOFF2Reader(self.file)
144*e1fe3e4aSElliott Hughes        specialTags = woff2TransformedTableTags + ("head", "GlyphOrder", "DSIG")
145*e1fe3e4aSElliott Hughes        for tag in [t for t in self.font.keys() if t not in specialTags]:
146*e1fe3e4aSElliott Hughes            origData = self.font.getTableData(tag)
147*e1fe3e4aSElliott Hughes            decompressedData = woff2Reader[tag]
148*e1fe3e4aSElliott Hughes            self.assertEqual(origData, decompressedData)
149*e1fe3e4aSElliott Hughes
150*e1fe3e4aSElliott Hughes    def test_reconstruct_unknown(self):
151*e1fe3e4aSElliott Hughes        reader = WOFF2Reader(self.file)
152*e1fe3e4aSElliott Hughes        with self.assertRaisesRegex(ttLib.TTLibError, "transform for table .* unknown"):
153*e1fe3e4aSElliott Hughes            reader.reconstructTable("head")
154*e1fe3e4aSElliott Hughes
155*e1fe3e4aSElliott Hughes
156*e1fe3e4aSElliott Hughesclass WOFF2ReaderTTFTest(WOFF2ReaderTest):
157*e1fe3e4aSElliott Hughes    """Tests specific to TT-flavored fonts."""
158*e1fe3e4aSElliott Hughes
159*e1fe3e4aSElliott Hughes    @classmethod
160*e1fe3e4aSElliott Hughes    def setUpClass(cls):
161*e1fe3e4aSElliott Hughes        cls.file = BytesIO(TT_WOFF2.getvalue())
162*e1fe3e4aSElliott Hughes        cls.font = ttLib.TTFont(recalcBBoxes=False, recalcTimestamp=False)
163*e1fe3e4aSElliott Hughes        cls.font.importXML(TTX)
164*e1fe3e4aSElliott Hughes
165*e1fe3e4aSElliott Hughes    def setUp(self):
166*e1fe3e4aSElliott Hughes        self.file.seek(0)
167*e1fe3e4aSElliott Hughes
168*e1fe3e4aSElliott Hughes    def test_reconstruct_glyf(self):
169*e1fe3e4aSElliott Hughes        woff2Reader = WOFF2Reader(self.file)
170*e1fe3e4aSElliott Hughes        reconstructedData = woff2Reader["glyf"]
171*e1fe3e4aSElliott Hughes        self.assertEqual(self.font.getTableData("glyf"), reconstructedData)
172*e1fe3e4aSElliott Hughes
173*e1fe3e4aSElliott Hughes    def test_reconstruct_loca(self):
174*e1fe3e4aSElliott Hughes        woff2Reader = WOFF2Reader(self.file)
175*e1fe3e4aSElliott Hughes        reconstructedData = woff2Reader["loca"]
176*e1fe3e4aSElliott Hughes        self.font.getTableData("glyf")  # 'glyf' needs to be compiled before 'loca'
177*e1fe3e4aSElliott Hughes        self.assertEqual(self.font.getTableData("loca"), reconstructedData)
178*e1fe3e4aSElliott Hughes        self.assertTrue(hasattr(woff2Reader.tables["glyf"], "data"))
179*e1fe3e4aSElliott Hughes
180*e1fe3e4aSElliott Hughes    def test_reconstruct_loca_not_match_orig_size(self):
181*e1fe3e4aSElliott Hughes        reader = WOFF2Reader(self.file)
182*e1fe3e4aSElliott Hughes        reader.tables["loca"].origLength -= 1
183*e1fe3e4aSElliott Hughes        with self.assertRaisesRegex(
184*e1fe3e4aSElliott Hughes            ttLib.TTLibError, "'loca' table doesn't match original size"
185*e1fe3e4aSElliott Hughes        ):
186*e1fe3e4aSElliott Hughes            reader.reconstructTable("loca")
187*e1fe3e4aSElliott Hughes
188*e1fe3e4aSElliott Hughes
189*e1fe3e4aSElliott Hughesdef normalise_table(font, tag, padding=4):
190*e1fe3e4aSElliott Hughes    """Return normalised table data. Keep 'font' instance unmodified."""
191*e1fe3e4aSElliott Hughes    assert tag in ("glyf", "loca", "head")
192*e1fe3e4aSElliott Hughes    assert tag in font
193*e1fe3e4aSElliott Hughes    if tag == "head":
194*e1fe3e4aSElliott Hughes        origHeadFlags = font["head"].flags
195*e1fe3e4aSElliott Hughes        font["head"].flags |= 1 << 11
196*e1fe3e4aSElliott Hughes        tableData = font["head"].compile(font)
197*e1fe3e4aSElliott Hughes    if font.sfntVersion in ("\x00\x01\x00\x00", "true"):
198*e1fe3e4aSElliott Hughes        assert {"glyf", "loca", "head"}.issubset(font.keys())
199*e1fe3e4aSElliott Hughes        origIndexFormat = font["head"].indexToLocFormat
200*e1fe3e4aSElliott Hughes        if hasattr(font["loca"], "locations"):
201*e1fe3e4aSElliott Hughes            origLocations = font["loca"].locations[:]
202*e1fe3e4aSElliott Hughes        else:
203*e1fe3e4aSElliott Hughes            origLocations = []
204*e1fe3e4aSElliott Hughes        glyfTable = ttLib.newTable("glyf")
205*e1fe3e4aSElliott Hughes        glyfTable.decompile(font.getTableData("glyf"), font)
206*e1fe3e4aSElliott Hughes        glyfTable.padding = padding
207*e1fe3e4aSElliott Hughes        if tag == "glyf":
208*e1fe3e4aSElliott Hughes            tableData = glyfTable.compile(font)
209*e1fe3e4aSElliott Hughes        elif tag == "loca":
210*e1fe3e4aSElliott Hughes            glyfTable.compile(font)
211*e1fe3e4aSElliott Hughes            tableData = font["loca"].compile(font)
212*e1fe3e4aSElliott Hughes        if tag == "head":
213*e1fe3e4aSElliott Hughes            glyfTable.compile(font)
214*e1fe3e4aSElliott Hughes            font["loca"].compile(font)
215*e1fe3e4aSElliott Hughes            tableData = font["head"].compile(font)
216*e1fe3e4aSElliott Hughes        font["head"].indexToLocFormat = origIndexFormat
217*e1fe3e4aSElliott Hughes        font["loca"].set(origLocations)
218*e1fe3e4aSElliott Hughes    if tag == "head":
219*e1fe3e4aSElliott Hughes        font["head"].flags = origHeadFlags
220*e1fe3e4aSElliott Hughes    return tableData
221*e1fe3e4aSElliott Hughes
222*e1fe3e4aSElliott Hughes
223*e1fe3e4aSElliott Hughesdef normalise_font(font, padding=4):
224*e1fe3e4aSElliott Hughes    """Return normalised font data. Keep 'font' instance unmodified."""
225*e1fe3e4aSElliott Hughes    # drop DSIG but keep a copy
226*e1fe3e4aSElliott Hughes    DSIG_copy = copy.deepcopy(font["DSIG"])
227*e1fe3e4aSElliott Hughes    del font["DSIG"]
228*e1fe3e4aSElliott Hughes    # override TTFont attributes
229*e1fe3e4aSElliott Hughes    origFlavor = font.flavor
230*e1fe3e4aSElliott Hughes    origRecalcBBoxes = font.recalcBBoxes
231*e1fe3e4aSElliott Hughes    origRecalcTimestamp = font.recalcTimestamp
232*e1fe3e4aSElliott Hughes    origLazy = font.lazy
233*e1fe3e4aSElliott Hughes    font.flavor = None
234*e1fe3e4aSElliott Hughes    font.recalcBBoxes = False
235*e1fe3e4aSElliott Hughes    font.recalcTimestamp = False
236*e1fe3e4aSElliott Hughes    font.lazy = True
237*e1fe3e4aSElliott Hughes    # save font to temporary stream
238*e1fe3e4aSElliott Hughes    infile = BytesIO()
239*e1fe3e4aSElliott Hughes    font.save(infile)
240*e1fe3e4aSElliott Hughes    infile.seek(0)
241*e1fe3e4aSElliott Hughes    # reorder tables alphabetically
242*e1fe3e4aSElliott Hughes    outfile = BytesIO()
243*e1fe3e4aSElliott Hughes    reader = ttLib.sfnt.SFNTReader(infile)
244*e1fe3e4aSElliott Hughes    writer = ttLib.sfnt.SFNTWriter(
245*e1fe3e4aSElliott Hughes        outfile,
246*e1fe3e4aSElliott Hughes        len(reader.tables),
247*e1fe3e4aSElliott Hughes        reader.sfntVersion,
248*e1fe3e4aSElliott Hughes        reader.flavor,
249*e1fe3e4aSElliott Hughes        reader.flavorData,
250*e1fe3e4aSElliott Hughes    )
251*e1fe3e4aSElliott Hughes    for tag in sorted(reader.keys()):
252*e1fe3e4aSElliott Hughes        if tag in woff2TransformedTableTags + ("head",):
253*e1fe3e4aSElliott Hughes            writer[tag] = normalise_table(font, tag, padding)
254*e1fe3e4aSElliott Hughes        else:
255*e1fe3e4aSElliott Hughes            writer[tag] = reader[tag]
256*e1fe3e4aSElliott Hughes    writer.close()
257*e1fe3e4aSElliott Hughes    # restore font attributes
258*e1fe3e4aSElliott Hughes    font["DSIG"] = DSIG_copy
259*e1fe3e4aSElliott Hughes    font.flavor = origFlavor
260*e1fe3e4aSElliott Hughes    font.recalcBBoxes = origRecalcBBoxes
261*e1fe3e4aSElliott Hughes    font.recalcTimestamp = origRecalcTimestamp
262*e1fe3e4aSElliott Hughes    font.lazy = origLazy
263*e1fe3e4aSElliott Hughes    return outfile.getvalue()
264*e1fe3e4aSElliott Hughes
265*e1fe3e4aSElliott Hughes
266*e1fe3e4aSElliott Hughesclass WOFF2DirectoryEntryTest(unittest.TestCase):
267*e1fe3e4aSElliott Hughes    def setUp(self):
268*e1fe3e4aSElliott Hughes        self.entry = WOFF2DirectoryEntry()
269*e1fe3e4aSElliott Hughes
270*e1fe3e4aSElliott Hughes    def test_not_enough_data_table_flags(self):
271*e1fe3e4aSElliott Hughes        with self.assertRaisesRegex(ttLib.TTLibError, "can't read table 'flags'"):
272*e1fe3e4aSElliott Hughes            self.entry.fromString(b"")
273*e1fe3e4aSElliott Hughes
274*e1fe3e4aSElliott Hughes    def test_not_enough_data_table_tag(self):
275*e1fe3e4aSElliott Hughes        incompleteData = bytearray([0x3F, 0, 0, 0])
276*e1fe3e4aSElliott Hughes        with self.assertRaisesRegex(ttLib.TTLibError, "can't read table 'tag'"):
277*e1fe3e4aSElliott Hughes            self.entry.fromString(bytes(incompleteData))
278*e1fe3e4aSElliott Hughes
279*e1fe3e4aSElliott Hughes    def test_loca_zero_transformLength(self):
280*e1fe3e4aSElliott Hughes        data = bytechr(getKnownTagIndex("loca"))  # flags
281*e1fe3e4aSElliott Hughes        data += packBase128(random.randint(1, 100))  # origLength
282*e1fe3e4aSElliott Hughes        data += packBase128(1)  # non-zero transformLength
283*e1fe3e4aSElliott Hughes        with self.assertRaisesRegex(
284*e1fe3e4aSElliott Hughes            ttLib.TTLibError, "transformLength of the 'loca' table must be 0"
285*e1fe3e4aSElliott Hughes        ):
286*e1fe3e4aSElliott Hughes            self.entry.fromString(data)
287*e1fe3e4aSElliott Hughes
288*e1fe3e4aSElliott Hughes    def test_fromFile(self):
289*e1fe3e4aSElliott Hughes        unknownTag = Tag("ZZZZ")
290*e1fe3e4aSElliott Hughes        data = bytechr(getKnownTagIndex(unknownTag))
291*e1fe3e4aSElliott Hughes        data += unknownTag.tobytes()
292*e1fe3e4aSElliott Hughes        data += packBase128(random.randint(1, 100))
293*e1fe3e4aSElliott Hughes        expectedPos = len(data)
294*e1fe3e4aSElliott Hughes        f = BytesIO(data + b"\0" * 100)
295*e1fe3e4aSElliott Hughes        self.entry.fromFile(f)
296*e1fe3e4aSElliott Hughes        self.assertEqual(f.tell(), expectedPos)
297*e1fe3e4aSElliott Hughes
298*e1fe3e4aSElliott Hughes    def test_transformed_toString(self):
299*e1fe3e4aSElliott Hughes        self.entry.tag = Tag("glyf")
300*e1fe3e4aSElliott Hughes        self.entry.flags = getKnownTagIndex(self.entry.tag)
301*e1fe3e4aSElliott Hughes        self.entry.origLength = random.randint(101, 200)
302*e1fe3e4aSElliott Hughes        self.entry.length = random.randint(1, 100)
303*e1fe3e4aSElliott Hughes        expectedSize = (
304*e1fe3e4aSElliott Hughes            woff2FlagsSize
305*e1fe3e4aSElliott Hughes            + base128Size(self.entry.origLength)
306*e1fe3e4aSElliott Hughes            + base128Size(self.entry.length)
307*e1fe3e4aSElliott Hughes        )
308*e1fe3e4aSElliott Hughes        data = self.entry.toString()
309*e1fe3e4aSElliott Hughes        self.assertEqual(len(data), expectedSize)
310*e1fe3e4aSElliott Hughes
311*e1fe3e4aSElliott Hughes    def test_known_toString(self):
312*e1fe3e4aSElliott Hughes        self.entry.tag = Tag("head")
313*e1fe3e4aSElliott Hughes        self.entry.flags = getKnownTagIndex(self.entry.tag)
314*e1fe3e4aSElliott Hughes        self.entry.origLength = 54
315*e1fe3e4aSElliott Hughes        expectedSize = woff2FlagsSize + base128Size(self.entry.origLength)
316*e1fe3e4aSElliott Hughes        data = self.entry.toString()
317*e1fe3e4aSElliott Hughes        self.assertEqual(len(data), expectedSize)
318*e1fe3e4aSElliott Hughes
319*e1fe3e4aSElliott Hughes    def test_unknown_toString(self):
320*e1fe3e4aSElliott Hughes        self.entry.tag = Tag("ZZZZ")
321*e1fe3e4aSElliott Hughes        self.entry.flags = woff2UnknownTagIndex
322*e1fe3e4aSElliott Hughes        self.entry.origLength = random.randint(1, 100)
323*e1fe3e4aSElliott Hughes        expectedSize = (
324*e1fe3e4aSElliott Hughes            woff2FlagsSize + woff2UnknownTagSize + base128Size(self.entry.origLength)
325*e1fe3e4aSElliott Hughes        )
326*e1fe3e4aSElliott Hughes        data = self.entry.toString()
327*e1fe3e4aSElliott Hughes        self.assertEqual(len(data), expectedSize)
328*e1fe3e4aSElliott Hughes
329*e1fe3e4aSElliott Hughes    def test_glyf_loca_transform_flags(self):
330*e1fe3e4aSElliott Hughes        for tag in ("glyf", "loca"):
331*e1fe3e4aSElliott Hughes            entry = WOFF2DirectoryEntry()
332*e1fe3e4aSElliott Hughes            entry.tag = Tag(tag)
333*e1fe3e4aSElliott Hughes            entry.flags = getKnownTagIndex(entry.tag)
334*e1fe3e4aSElliott Hughes
335*e1fe3e4aSElliott Hughes            self.assertEqual(entry.transformVersion, 0)
336*e1fe3e4aSElliott Hughes            self.assertTrue(entry.transformed)
337*e1fe3e4aSElliott Hughes
338*e1fe3e4aSElliott Hughes            entry.transformed = False
339*e1fe3e4aSElliott Hughes
340*e1fe3e4aSElliott Hughes            self.assertEqual(entry.transformVersion, 3)
341*e1fe3e4aSElliott Hughes            self.assertEqual(entry.flags & 0b11000000, (3 << 6))
342*e1fe3e4aSElliott Hughes            self.assertFalse(entry.transformed)
343*e1fe3e4aSElliott Hughes
344*e1fe3e4aSElliott Hughes    def test_other_transform_flags(self):
345*e1fe3e4aSElliott Hughes        entry = WOFF2DirectoryEntry()
346*e1fe3e4aSElliott Hughes        entry.tag = Tag("ZZZZ")
347*e1fe3e4aSElliott Hughes        entry.flags = woff2UnknownTagIndex
348*e1fe3e4aSElliott Hughes
349*e1fe3e4aSElliott Hughes        self.assertEqual(entry.transformVersion, 0)
350*e1fe3e4aSElliott Hughes        self.assertFalse(entry.transformed)
351*e1fe3e4aSElliott Hughes
352*e1fe3e4aSElliott Hughes        entry.transformed = True
353*e1fe3e4aSElliott Hughes
354*e1fe3e4aSElliott Hughes        self.assertEqual(entry.transformVersion, 1)
355*e1fe3e4aSElliott Hughes        self.assertEqual(entry.flags & 0b11000000, (1 << 6))
356*e1fe3e4aSElliott Hughes        self.assertTrue(entry.transformed)
357*e1fe3e4aSElliott Hughes
358*e1fe3e4aSElliott Hughes
359*e1fe3e4aSElliott Hughesclass DummyReader(WOFF2Reader):
360*e1fe3e4aSElliott Hughes    def __init__(self, file, checkChecksums=1, fontNumber=-1):
361*e1fe3e4aSElliott Hughes        self.file = file
362*e1fe3e4aSElliott Hughes        for attr in (
363*e1fe3e4aSElliott Hughes            "majorVersion",
364*e1fe3e4aSElliott Hughes            "minorVersion",
365*e1fe3e4aSElliott Hughes            "metaOffset",
366*e1fe3e4aSElliott Hughes            "metaLength",
367*e1fe3e4aSElliott Hughes            "metaOrigLength",
368*e1fe3e4aSElliott Hughes            "privLength",
369*e1fe3e4aSElliott Hughes            "privOffset",
370*e1fe3e4aSElliott Hughes        ):
371*e1fe3e4aSElliott Hughes            setattr(self, attr, 0)
372*e1fe3e4aSElliott Hughes        self.tables = {}
373*e1fe3e4aSElliott Hughes
374*e1fe3e4aSElliott Hughes
375*e1fe3e4aSElliott Hughesclass WOFF2FlavorDataTest(unittest.TestCase):
376*e1fe3e4aSElliott Hughes    @classmethod
377*e1fe3e4aSElliott Hughes    def setUpClass(cls):
378*e1fe3e4aSElliott Hughes        assert os.path.exists(METADATA)
379*e1fe3e4aSElliott Hughes        with open(METADATA, "rb") as f:
380*e1fe3e4aSElliott Hughes            cls.xml_metadata = f.read()
381*e1fe3e4aSElliott Hughes        cls.compressed_metadata = brotli.compress(
382*e1fe3e4aSElliott Hughes            cls.xml_metadata, mode=brotli.MODE_TEXT
383*e1fe3e4aSElliott Hughes        )
384*e1fe3e4aSElliott Hughes        # make random byte strings; font data must be 4-byte aligned
385*e1fe3e4aSElliott Hughes        cls.fontdata = bytes(bytearray(random.sample(range(0, 256), 80)))
386*e1fe3e4aSElliott Hughes        cls.privData = bytes(bytearray(random.sample(range(0, 256), 20)))
387*e1fe3e4aSElliott Hughes
388*e1fe3e4aSElliott Hughes    def setUp(self):
389*e1fe3e4aSElliott Hughes        self.file = BytesIO(self.fontdata)
390*e1fe3e4aSElliott Hughes        self.file.seek(0, 2)
391*e1fe3e4aSElliott Hughes
392*e1fe3e4aSElliott Hughes    def test_get_metaData_no_privData(self):
393*e1fe3e4aSElliott Hughes        self.file.write(self.compressed_metadata)
394*e1fe3e4aSElliott Hughes        reader = DummyReader(self.file)
395*e1fe3e4aSElliott Hughes        reader.metaOffset = len(self.fontdata)
396*e1fe3e4aSElliott Hughes        reader.metaLength = len(self.compressed_metadata)
397*e1fe3e4aSElliott Hughes        reader.metaOrigLength = len(self.xml_metadata)
398*e1fe3e4aSElliott Hughes        flavorData = WOFF2FlavorData(reader)
399*e1fe3e4aSElliott Hughes        self.assertEqual(self.xml_metadata, flavorData.metaData)
400*e1fe3e4aSElliott Hughes
401*e1fe3e4aSElliott Hughes    def test_get_privData_no_metaData(self):
402*e1fe3e4aSElliott Hughes        self.file.write(self.privData)
403*e1fe3e4aSElliott Hughes        reader = DummyReader(self.file)
404*e1fe3e4aSElliott Hughes        reader.privOffset = len(self.fontdata)
405*e1fe3e4aSElliott Hughes        reader.privLength = len(self.privData)
406*e1fe3e4aSElliott Hughes        flavorData = WOFF2FlavorData(reader)
407*e1fe3e4aSElliott Hughes        self.assertEqual(self.privData, flavorData.privData)
408*e1fe3e4aSElliott Hughes
409*e1fe3e4aSElliott Hughes    def test_get_metaData_and_privData(self):
410*e1fe3e4aSElliott Hughes        self.file.write(self.compressed_metadata + self.privData)
411*e1fe3e4aSElliott Hughes        reader = DummyReader(self.file)
412*e1fe3e4aSElliott Hughes        reader.metaOffset = len(self.fontdata)
413*e1fe3e4aSElliott Hughes        reader.metaLength = len(self.compressed_metadata)
414*e1fe3e4aSElliott Hughes        reader.metaOrigLength = len(self.xml_metadata)
415*e1fe3e4aSElliott Hughes        reader.privOffset = reader.metaOffset + reader.metaLength
416*e1fe3e4aSElliott Hughes        reader.privLength = len(self.privData)
417*e1fe3e4aSElliott Hughes        flavorData = WOFF2FlavorData(reader)
418*e1fe3e4aSElliott Hughes        self.assertEqual(self.xml_metadata, flavorData.metaData)
419*e1fe3e4aSElliott Hughes        self.assertEqual(self.privData, flavorData.privData)
420*e1fe3e4aSElliott Hughes
421*e1fe3e4aSElliott Hughes    def test_get_major_minorVersion(self):
422*e1fe3e4aSElliott Hughes        reader = DummyReader(self.file)
423*e1fe3e4aSElliott Hughes        reader.majorVersion = reader.minorVersion = 1
424*e1fe3e4aSElliott Hughes        flavorData = WOFF2FlavorData(reader)
425*e1fe3e4aSElliott Hughes        self.assertEqual(flavorData.majorVersion, 1)
426*e1fe3e4aSElliott Hughes        self.assertEqual(flavorData.minorVersion, 1)
427*e1fe3e4aSElliott Hughes
428*e1fe3e4aSElliott Hughes    def test_mutually_exclusive_args(self):
429*e1fe3e4aSElliott Hughes        msg = "arguments are mutually exclusive"
430*e1fe3e4aSElliott Hughes        reader = DummyReader(self.file)
431*e1fe3e4aSElliott Hughes        with self.assertRaisesRegex(TypeError, msg):
432*e1fe3e4aSElliott Hughes            WOFF2FlavorData(reader, transformedTables={"hmtx"})
433*e1fe3e4aSElliott Hughes        with self.assertRaisesRegex(TypeError, msg):
434*e1fe3e4aSElliott Hughes            WOFF2FlavorData(reader, data=WOFF2FlavorData())
435*e1fe3e4aSElliott Hughes
436*e1fe3e4aSElliott Hughes    def test_transformedTables_default(self):
437*e1fe3e4aSElliott Hughes        flavorData = WOFF2FlavorData()
438*e1fe3e4aSElliott Hughes        self.assertEqual(flavorData.transformedTables, set(woff2TransformedTableTags))
439*e1fe3e4aSElliott Hughes
440*e1fe3e4aSElliott Hughes    def test_transformedTables_invalid(self):
441*e1fe3e4aSElliott Hughes        msg = r"'glyf' and 'loca' must be transformed \(or not\) together"
442*e1fe3e4aSElliott Hughes
443*e1fe3e4aSElliott Hughes        with self.assertRaisesRegex(ValueError, msg):
444*e1fe3e4aSElliott Hughes            WOFF2FlavorData(transformedTables={"glyf"})
445*e1fe3e4aSElliott Hughes
446*e1fe3e4aSElliott Hughes        with self.assertRaisesRegex(ValueError, msg):
447*e1fe3e4aSElliott Hughes            WOFF2FlavorData(transformedTables={"loca"})
448*e1fe3e4aSElliott Hughes
449*e1fe3e4aSElliott Hughes
450*e1fe3e4aSElliott Hughesclass WOFF2WriterTest(unittest.TestCase):
451*e1fe3e4aSElliott Hughes    @classmethod
452*e1fe3e4aSElliott Hughes    def setUpClass(cls):
453*e1fe3e4aSElliott Hughes        cls.font = ttLib.TTFont(
454*e1fe3e4aSElliott Hughes            recalcBBoxes=False, recalcTimestamp=False, flavor="woff2"
455*e1fe3e4aSElliott Hughes        )
456*e1fe3e4aSElliott Hughes        cls.font.importXML(OTX)
457*e1fe3e4aSElliott Hughes        cls.tags = sorted(t for t in cls.font.keys() if t != "GlyphOrder")
458*e1fe3e4aSElliott Hughes        cls.numTables = len(cls.tags)
459*e1fe3e4aSElliott Hughes        cls.file = BytesIO(CFF_WOFF2.getvalue())
460*e1fe3e4aSElliott Hughes        cls.file.seek(0, 2)
461*e1fe3e4aSElliott Hughes        cls.length = (cls.file.tell() + 3) & ~3
462*e1fe3e4aSElliott Hughes        cls.setUpFlavorData()
463*e1fe3e4aSElliott Hughes
464*e1fe3e4aSElliott Hughes    @classmethod
465*e1fe3e4aSElliott Hughes    def setUpFlavorData(cls):
466*e1fe3e4aSElliott Hughes        assert os.path.exists(METADATA)
467*e1fe3e4aSElliott Hughes        with open(METADATA, "rb") as f:
468*e1fe3e4aSElliott Hughes            cls.xml_metadata = f.read()
469*e1fe3e4aSElliott Hughes        cls.compressed_metadata = brotli.compress(
470*e1fe3e4aSElliott Hughes            cls.xml_metadata, mode=brotli.MODE_TEXT
471*e1fe3e4aSElliott Hughes        )
472*e1fe3e4aSElliott Hughes        cls.privData = bytes(bytearray(random.sample(range(0, 256), 20)))
473*e1fe3e4aSElliott Hughes
474*e1fe3e4aSElliott Hughes    def setUp(self):
475*e1fe3e4aSElliott Hughes        self.file.seek(0)
476*e1fe3e4aSElliott Hughes        self.writer = WOFF2Writer(BytesIO(), self.numTables, self.font.sfntVersion)
477*e1fe3e4aSElliott Hughes
478*e1fe3e4aSElliott Hughes    def test_DSIG_dropped(self):
479*e1fe3e4aSElliott Hughes        self.writer["DSIG"] = b"\0"
480*e1fe3e4aSElliott Hughes        self.assertEqual(len(self.writer.tables), 0)
481*e1fe3e4aSElliott Hughes        self.assertEqual(self.writer.numTables, self.numTables - 1)
482*e1fe3e4aSElliott Hughes
483*e1fe3e4aSElliott Hughes    def test_no_rewrite_table(self):
484*e1fe3e4aSElliott Hughes        self.writer["ZZZZ"] = b"\0"
485*e1fe3e4aSElliott Hughes        with self.assertRaisesRegex(ttLib.TTLibError, "cannot rewrite"):
486*e1fe3e4aSElliott Hughes            self.writer["ZZZZ"] = b"\0"
487*e1fe3e4aSElliott Hughes
488*e1fe3e4aSElliott Hughes    def test_num_tables(self):
489*e1fe3e4aSElliott Hughes        self.writer["ABCD"] = b"\0"
490*e1fe3e4aSElliott Hughes        with self.assertRaisesRegex(ttLib.TTLibError, "wrong number of tables"):
491*e1fe3e4aSElliott Hughes            self.writer.close()
492*e1fe3e4aSElliott Hughes
493*e1fe3e4aSElliott Hughes    def test_required_tables(self):
494*e1fe3e4aSElliott Hughes        font = ttLib.TTFont(flavor="woff2")
495*e1fe3e4aSElliott Hughes        with self.assertRaisesRegex(ttLib.TTLibError, "missing required table"):
496*e1fe3e4aSElliott Hughes            font.save(BytesIO())
497*e1fe3e4aSElliott Hughes
498*e1fe3e4aSElliott Hughes    def test_head_transform_flag(self):
499*e1fe3e4aSElliott Hughes        headData = self.font.getTableData("head")
500*e1fe3e4aSElliott Hughes        origFlags = byteord(headData[16])
501*e1fe3e4aSElliott Hughes        woff2font = ttLib.TTFont(self.file)
502*e1fe3e4aSElliott Hughes        newHeadData = woff2font.getTableData("head")
503*e1fe3e4aSElliott Hughes        modifiedFlags = byteord(newHeadData[16])
504*e1fe3e4aSElliott Hughes        self.assertNotEqual(origFlags, modifiedFlags)
505*e1fe3e4aSElliott Hughes        restoredFlags = modifiedFlags & ~0x08  # turn off bit 11
506*e1fe3e4aSElliott Hughes        self.assertEqual(origFlags, restoredFlags)
507*e1fe3e4aSElliott Hughes
508*e1fe3e4aSElliott Hughes    def test_tables_sorted_alphabetically(self):
509*e1fe3e4aSElliott Hughes        expected = sorted([t for t in self.tags if t != "DSIG"])
510*e1fe3e4aSElliott Hughes        woff2font = ttLib.TTFont(self.file)
511*e1fe3e4aSElliott Hughes        self.assertEqual(expected, list(woff2font.reader.keys()))
512*e1fe3e4aSElliott Hughes
513*e1fe3e4aSElliott Hughes    def test_checksums(self):
514*e1fe3e4aSElliott Hughes        normFile = BytesIO(normalise_font(self.font, padding=4))
515*e1fe3e4aSElliott Hughes        normFile.seek(0)
516*e1fe3e4aSElliott Hughes        normFont = ttLib.TTFont(normFile, checkChecksums=2)
517*e1fe3e4aSElliott Hughes        w2font = ttLib.TTFont(self.file)
518*e1fe3e4aSElliott Hughes        # force reconstructing glyf table using 4-byte padding
519*e1fe3e4aSElliott Hughes        w2font.reader.padding = 4
520*e1fe3e4aSElliott Hughes        for tag in [t for t in self.tags if t != "DSIG"]:
521*e1fe3e4aSElliott Hughes            w2data = w2font.reader[tag]
522*e1fe3e4aSElliott Hughes            normData = normFont.reader[tag]
523*e1fe3e4aSElliott Hughes            if tag == "head":
524*e1fe3e4aSElliott Hughes                w2data = w2data[:8] + b"\0\0\0\0" + w2data[12:]
525*e1fe3e4aSElliott Hughes                normData = normData[:8] + b"\0\0\0\0" + normData[12:]
526*e1fe3e4aSElliott Hughes            w2CheckSum = ttLib.sfnt.calcChecksum(w2data)
527*e1fe3e4aSElliott Hughes            normCheckSum = ttLib.sfnt.calcChecksum(normData)
528*e1fe3e4aSElliott Hughes            self.assertEqual(w2CheckSum, normCheckSum)
529*e1fe3e4aSElliott Hughes        normCheckSumAdjustment = normFont["head"].checkSumAdjustment
530*e1fe3e4aSElliott Hughes        self.assertEqual(normCheckSumAdjustment, w2font["head"].checkSumAdjustment)
531*e1fe3e4aSElliott Hughes
532*e1fe3e4aSElliott Hughes    def test_calcSFNTChecksumsLengthsAndOffsets(self):
533*e1fe3e4aSElliott Hughes        normFont = ttLib.TTFont(BytesIO(normalise_font(self.font, padding=4)))
534*e1fe3e4aSElliott Hughes        for tag in self.tags:
535*e1fe3e4aSElliott Hughes            self.writer[tag] = self.font.getTableData(tag)
536*e1fe3e4aSElliott Hughes        self.writer._normaliseGlyfAndLoca(padding=4)
537*e1fe3e4aSElliott Hughes        self.writer._setHeadTransformFlag()
538*e1fe3e4aSElliott Hughes        self.writer.tables = OrderedDict(sorted(self.writer.tables.items()))
539*e1fe3e4aSElliott Hughes        self.writer._calcSFNTChecksumsLengthsAndOffsets()
540*e1fe3e4aSElliott Hughes        for tag, entry in normFont.reader.tables.items():
541*e1fe3e4aSElliott Hughes            self.assertEqual(entry.offset, self.writer.tables[tag].origOffset)
542*e1fe3e4aSElliott Hughes            self.assertEqual(entry.length, self.writer.tables[tag].origLength)
543*e1fe3e4aSElliott Hughes            self.assertEqual(entry.checkSum, self.writer.tables[tag].checkSum)
544*e1fe3e4aSElliott Hughes
545*e1fe3e4aSElliott Hughes    def test_bad_sfntVersion(self):
546*e1fe3e4aSElliott Hughes        for i in range(self.numTables):
547*e1fe3e4aSElliott Hughes            self.writer[bytechr(65 + i) * 4] = b"\0"
548*e1fe3e4aSElliott Hughes        self.writer.sfntVersion = "ZZZZ"
549*e1fe3e4aSElliott Hughes        with self.assertRaisesRegex(ttLib.TTLibError, "bad sfntVersion"):
550*e1fe3e4aSElliott Hughes            self.writer.close()
551*e1fe3e4aSElliott Hughes
552*e1fe3e4aSElliott Hughes    def test_calcTotalSize_no_flavorData(self):
553*e1fe3e4aSElliott Hughes        expected = self.length
554*e1fe3e4aSElliott Hughes        self.writer.file = BytesIO()
555*e1fe3e4aSElliott Hughes        for tag in self.tags:
556*e1fe3e4aSElliott Hughes            self.writer[tag] = self.font.getTableData(tag)
557*e1fe3e4aSElliott Hughes        self.writer.close()
558*e1fe3e4aSElliott Hughes        self.assertEqual(expected, self.writer.length)
559*e1fe3e4aSElliott Hughes        self.assertEqual(expected, self.writer.file.tell())
560*e1fe3e4aSElliott Hughes
561*e1fe3e4aSElliott Hughes    def test_calcTotalSize_with_metaData(self):
562*e1fe3e4aSElliott Hughes        expected = self.length + len(self.compressed_metadata)
563*e1fe3e4aSElliott Hughes        flavorData = self.writer.flavorData = WOFF2FlavorData()
564*e1fe3e4aSElliott Hughes        flavorData.metaData = self.xml_metadata
565*e1fe3e4aSElliott Hughes        self.writer.file = BytesIO()
566*e1fe3e4aSElliott Hughes        for tag in self.tags:
567*e1fe3e4aSElliott Hughes            self.writer[tag] = self.font.getTableData(tag)
568*e1fe3e4aSElliott Hughes        self.writer.close()
569*e1fe3e4aSElliott Hughes        self.assertEqual(expected, self.writer.length)
570*e1fe3e4aSElliott Hughes        self.assertEqual(expected, self.writer.file.tell())
571*e1fe3e4aSElliott Hughes
572*e1fe3e4aSElliott Hughes    def test_calcTotalSize_with_privData(self):
573*e1fe3e4aSElliott Hughes        expected = self.length + len(self.privData)
574*e1fe3e4aSElliott Hughes        flavorData = self.writer.flavorData = WOFF2FlavorData()
575*e1fe3e4aSElliott Hughes        flavorData.privData = self.privData
576*e1fe3e4aSElliott Hughes        self.writer.file = BytesIO()
577*e1fe3e4aSElliott Hughes        for tag in self.tags:
578*e1fe3e4aSElliott Hughes            self.writer[tag] = self.font.getTableData(tag)
579*e1fe3e4aSElliott Hughes        self.writer.close()
580*e1fe3e4aSElliott Hughes        self.assertEqual(expected, self.writer.length)
581*e1fe3e4aSElliott Hughes        self.assertEqual(expected, self.writer.file.tell())
582*e1fe3e4aSElliott Hughes
583*e1fe3e4aSElliott Hughes    def test_calcTotalSize_with_metaData_and_privData(self):
584*e1fe3e4aSElliott Hughes        metaDataLength = (len(self.compressed_metadata) + 3) & ~3
585*e1fe3e4aSElliott Hughes        expected = self.length + metaDataLength + len(self.privData)
586*e1fe3e4aSElliott Hughes        flavorData = self.writer.flavorData = WOFF2FlavorData()
587*e1fe3e4aSElliott Hughes        flavorData.metaData = self.xml_metadata
588*e1fe3e4aSElliott Hughes        flavorData.privData = self.privData
589*e1fe3e4aSElliott Hughes        self.writer.file = BytesIO()
590*e1fe3e4aSElliott Hughes        for tag in self.tags:
591*e1fe3e4aSElliott Hughes            self.writer[tag] = self.font.getTableData(tag)
592*e1fe3e4aSElliott Hughes        self.writer.close()
593*e1fe3e4aSElliott Hughes        self.assertEqual(expected, self.writer.length)
594*e1fe3e4aSElliott Hughes        self.assertEqual(expected, self.writer.file.tell())
595*e1fe3e4aSElliott Hughes
596*e1fe3e4aSElliott Hughes    def test_getVersion(self):
597*e1fe3e4aSElliott Hughes        # no version
598*e1fe3e4aSElliott Hughes        self.assertEqual((0, 0), self.writer._getVersion())
599*e1fe3e4aSElliott Hughes        # version from head.fontRevision
600*e1fe3e4aSElliott Hughes        fontRevision = self.font["head"].fontRevision
601*e1fe3e4aSElliott Hughes        versionTuple = tuple(int(i) for i in str(fontRevision).split("."))
602*e1fe3e4aSElliott Hughes        entry = self.writer.tables["head"] = ttLib.newTable("head")
603*e1fe3e4aSElliott Hughes        entry.data = self.font.getTableData("head")
604*e1fe3e4aSElliott Hughes        self.assertEqual(versionTuple, self.writer._getVersion())
605*e1fe3e4aSElliott Hughes        # version from writer.flavorData
606*e1fe3e4aSElliott Hughes        flavorData = self.writer.flavorData = WOFF2FlavorData()
607*e1fe3e4aSElliott Hughes        flavorData.majorVersion, flavorData.minorVersion = (10, 11)
608*e1fe3e4aSElliott Hughes        self.assertEqual((10, 11), self.writer._getVersion())
609*e1fe3e4aSElliott Hughes
610*e1fe3e4aSElliott Hughes    def test_hmtx_trasform(self):
611*e1fe3e4aSElliott Hughes        tableTransforms = {"glyf", "loca", "hmtx"}
612*e1fe3e4aSElliott Hughes
613*e1fe3e4aSElliott Hughes        writer = WOFF2Writer(BytesIO(), self.numTables, self.font.sfntVersion)
614*e1fe3e4aSElliott Hughes        writer.flavorData = WOFF2FlavorData(transformedTables=tableTransforms)
615*e1fe3e4aSElliott Hughes
616*e1fe3e4aSElliott Hughes        for tag in self.tags:
617*e1fe3e4aSElliott Hughes            writer[tag] = self.font.getTableData(tag)
618*e1fe3e4aSElliott Hughes        writer.close()
619*e1fe3e4aSElliott Hughes
620*e1fe3e4aSElliott Hughes        # enabling hmtx transform has no effect when font has no glyf table
621*e1fe3e4aSElliott Hughes        self.assertEqual(writer.file.getvalue(), CFF_WOFF2.getvalue())
622*e1fe3e4aSElliott Hughes
623*e1fe3e4aSElliott Hughes    def test_no_transforms(self):
624*e1fe3e4aSElliott Hughes        writer = WOFF2Writer(BytesIO(), self.numTables, self.font.sfntVersion)
625*e1fe3e4aSElliott Hughes        writer.flavorData = WOFF2FlavorData(transformedTables=())
626*e1fe3e4aSElliott Hughes
627*e1fe3e4aSElliott Hughes        for tag in self.tags:
628*e1fe3e4aSElliott Hughes            writer[tag] = self.font.getTableData(tag)
629*e1fe3e4aSElliott Hughes        writer.close()
630*e1fe3e4aSElliott Hughes
631*e1fe3e4aSElliott Hughes        # transforms settings have no effect when font is CFF-flavored, since
632*e1fe3e4aSElliott Hughes        # all the current transforms only apply to TrueType-flavored fonts.
633*e1fe3e4aSElliott Hughes        self.assertEqual(writer.file.getvalue(), CFF_WOFF2.getvalue())
634*e1fe3e4aSElliott Hughes
635*e1fe3e4aSElliott Hughes
636*e1fe3e4aSElliott Hughesclass WOFF2WriterTTFTest(WOFF2WriterTest):
637*e1fe3e4aSElliott Hughes    @classmethod
638*e1fe3e4aSElliott Hughes    def setUpClass(cls):
639*e1fe3e4aSElliott Hughes        cls.font = ttLib.TTFont(
640*e1fe3e4aSElliott Hughes            recalcBBoxes=False, recalcTimestamp=False, flavor="woff2"
641*e1fe3e4aSElliott Hughes        )
642*e1fe3e4aSElliott Hughes        cls.font.importXML(TTX)
643*e1fe3e4aSElliott Hughes        cls.tags = sorted(t for t in cls.font.keys() if t != "GlyphOrder")
644*e1fe3e4aSElliott Hughes        cls.numTables = len(cls.tags)
645*e1fe3e4aSElliott Hughes        cls.file = BytesIO(TT_WOFF2.getvalue())
646*e1fe3e4aSElliott Hughes        cls.file.seek(0, 2)
647*e1fe3e4aSElliott Hughes        cls.length = (cls.file.tell() + 3) & ~3
648*e1fe3e4aSElliott Hughes        cls.setUpFlavorData()
649*e1fe3e4aSElliott Hughes
650*e1fe3e4aSElliott Hughes    def test_normaliseGlyfAndLoca(self):
651*e1fe3e4aSElliott Hughes        normTables = {}
652*e1fe3e4aSElliott Hughes        for tag in ("head", "loca", "glyf"):
653*e1fe3e4aSElliott Hughes            normTables[tag] = normalise_table(self.font, tag, padding=4)
654*e1fe3e4aSElliott Hughes        for tag in self.tags:
655*e1fe3e4aSElliott Hughes            tableData = self.font.getTableData(tag)
656*e1fe3e4aSElliott Hughes            self.writer[tag] = tableData
657*e1fe3e4aSElliott Hughes            if tag in normTables:
658*e1fe3e4aSElliott Hughes                self.assertNotEqual(tableData, normTables[tag])
659*e1fe3e4aSElliott Hughes        self.writer._normaliseGlyfAndLoca(padding=4)
660*e1fe3e4aSElliott Hughes        self.writer._setHeadTransformFlag()
661*e1fe3e4aSElliott Hughes        for tag in normTables:
662*e1fe3e4aSElliott Hughes            self.assertEqual(self.writer.tables[tag].data, normTables[tag])
663*e1fe3e4aSElliott Hughes
664*e1fe3e4aSElliott Hughes    def test_hmtx_trasform(self):
665*e1fe3e4aSElliott Hughes        def compile_hmtx(compressed):
666*e1fe3e4aSElliott Hughes            tableTransforms = woff2TransformedTableTags
667*e1fe3e4aSElliott Hughes            if compressed:
668*e1fe3e4aSElliott Hughes                tableTransforms += ("hmtx",)
669*e1fe3e4aSElliott Hughes            writer = WOFF2Writer(BytesIO(), self.numTables, self.font.sfntVersion)
670*e1fe3e4aSElliott Hughes            writer.flavorData = WOFF2FlavorData(transformedTables=tableTransforms)
671*e1fe3e4aSElliott Hughes            for tag in self.tags:
672*e1fe3e4aSElliott Hughes                writer[tag] = self.font.getTableData(tag)
673*e1fe3e4aSElliott Hughes            writer.close()
674*e1fe3e4aSElliott Hughes            return writer.tables["hmtx"].length
675*e1fe3e4aSElliott Hughes
676*e1fe3e4aSElliott Hughes        uncompressed_length = compile_hmtx(compressed=False)
677*e1fe3e4aSElliott Hughes        compressed_length = compile_hmtx(compressed=True)
678*e1fe3e4aSElliott Hughes
679*e1fe3e4aSElliott Hughes        # enabling optional hmtx transform shaves off a few bytes
680*e1fe3e4aSElliott Hughes        self.assertLess(compressed_length, uncompressed_length)
681*e1fe3e4aSElliott Hughes
682*e1fe3e4aSElliott Hughes    def test_no_transforms(self):
683*e1fe3e4aSElliott Hughes        writer = WOFF2Writer(BytesIO(), self.numTables, self.font.sfntVersion)
684*e1fe3e4aSElliott Hughes        writer.flavorData = WOFF2FlavorData(transformedTables=())
685*e1fe3e4aSElliott Hughes
686*e1fe3e4aSElliott Hughes        for tag in self.tags:
687*e1fe3e4aSElliott Hughes            writer[tag] = self.font.getTableData(tag)
688*e1fe3e4aSElliott Hughes        writer.close()
689*e1fe3e4aSElliott Hughes
690*e1fe3e4aSElliott Hughes        self.assertNotEqual(writer.file.getvalue(), TT_WOFF2.getvalue())
691*e1fe3e4aSElliott Hughes
692*e1fe3e4aSElliott Hughes        writer.file.seek(0)
693*e1fe3e4aSElliott Hughes        reader = WOFF2Reader(writer.file)
694*e1fe3e4aSElliott Hughes        self.assertEqual(len(reader.flavorData.transformedTables), 0)
695*e1fe3e4aSElliott Hughes
696*e1fe3e4aSElliott Hughes
697*e1fe3e4aSElliott Hughesclass WOFF2LocaTableTest(unittest.TestCase):
698*e1fe3e4aSElliott Hughes    def setUp(self):
699*e1fe3e4aSElliott Hughes        self.font = font = ttLib.TTFont(recalcBBoxes=False, recalcTimestamp=False)
700*e1fe3e4aSElliott Hughes        font["head"] = ttLib.newTable("head")
701*e1fe3e4aSElliott Hughes        font["loca"] = WOFF2LocaTable()
702*e1fe3e4aSElliott Hughes        font["glyf"] = WOFF2GlyfTable()
703*e1fe3e4aSElliott Hughes
704*e1fe3e4aSElliott Hughes    def test_compile_short_loca(self):
705*e1fe3e4aSElliott Hughes        locaTable = self.font["loca"]
706*e1fe3e4aSElliott Hughes        locaTable.set(list(range(0, 0x20000, 2)))
707*e1fe3e4aSElliott Hughes        self.font["glyf"].indexFormat = 0
708*e1fe3e4aSElliott Hughes        locaData = locaTable.compile(self.font)
709*e1fe3e4aSElliott Hughes        self.assertEqual(len(locaData), 0x20000)
710*e1fe3e4aSElliott Hughes
711*e1fe3e4aSElliott Hughes    def test_compile_short_loca_overflow(self):
712*e1fe3e4aSElliott Hughes        locaTable = self.font["loca"]
713*e1fe3e4aSElliott Hughes        locaTable.set(list(range(0x20000 + 1)))
714*e1fe3e4aSElliott Hughes        self.font["glyf"].indexFormat = 0
715*e1fe3e4aSElliott Hughes        with self.assertRaisesRegex(
716*e1fe3e4aSElliott Hughes            ttLib.TTLibError, "indexFormat is 0 but local offsets > 0x20000"
717*e1fe3e4aSElliott Hughes        ):
718*e1fe3e4aSElliott Hughes            locaTable.compile(self.font)
719*e1fe3e4aSElliott Hughes
720*e1fe3e4aSElliott Hughes    def test_compile_short_loca_not_multiples_of_2(self):
721*e1fe3e4aSElliott Hughes        locaTable = self.font["loca"]
722*e1fe3e4aSElliott Hughes        locaTable.set([1, 3, 5, 7])
723*e1fe3e4aSElliott Hughes        self.font["glyf"].indexFormat = 0
724*e1fe3e4aSElliott Hughes        with self.assertRaisesRegex(ttLib.TTLibError, "offsets not multiples of 2"):
725*e1fe3e4aSElliott Hughes            locaTable.compile(self.font)
726*e1fe3e4aSElliott Hughes
727*e1fe3e4aSElliott Hughes    def test_compile_long_loca(self):
728*e1fe3e4aSElliott Hughes        locaTable = self.font["loca"]
729*e1fe3e4aSElliott Hughes        locaTable.set(list(range(0x20001)))
730*e1fe3e4aSElliott Hughes        self.font["glyf"].indexFormat = 1
731*e1fe3e4aSElliott Hughes        locaData = locaTable.compile(self.font)
732*e1fe3e4aSElliott Hughes        self.assertEqual(len(locaData), 0x20001 * 4)
733*e1fe3e4aSElliott Hughes
734*e1fe3e4aSElliott Hughes    def test_compile_set_indexToLocFormat_0(self):
735*e1fe3e4aSElliott Hughes        locaTable = self.font["loca"]
736*e1fe3e4aSElliott Hughes        # offsets are all multiples of 2 and max length is < 0x10000
737*e1fe3e4aSElliott Hughes        locaTable.set(list(range(0, 0x20000, 2)))
738*e1fe3e4aSElliott Hughes        locaTable.compile(self.font)
739*e1fe3e4aSElliott Hughes        newIndexFormat = self.font["head"].indexToLocFormat
740*e1fe3e4aSElliott Hughes        self.assertEqual(0, newIndexFormat)
741*e1fe3e4aSElliott Hughes
742*e1fe3e4aSElliott Hughes    def test_compile_set_indexToLocFormat_1(self):
743*e1fe3e4aSElliott Hughes        locaTable = self.font["loca"]
744*e1fe3e4aSElliott Hughes        # offsets are not multiples of 2
745*e1fe3e4aSElliott Hughes        locaTable.set(list(range(10)))
746*e1fe3e4aSElliott Hughes        locaTable.compile(self.font)
747*e1fe3e4aSElliott Hughes        newIndexFormat = self.font["head"].indexToLocFormat
748*e1fe3e4aSElliott Hughes        self.assertEqual(1, newIndexFormat)
749*e1fe3e4aSElliott Hughes        # max length is >= 0x10000
750*e1fe3e4aSElliott Hughes        locaTable.set(list(range(0, 0x20000 + 1, 2)))
751*e1fe3e4aSElliott Hughes        locaTable.compile(self.font)
752*e1fe3e4aSElliott Hughes        newIndexFormat = self.font["head"].indexToLocFormat
753*e1fe3e4aSElliott Hughes        self.assertEqual(1, newIndexFormat)
754*e1fe3e4aSElliott Hughes
755*e1fe3e4aSElliott Hughes
756*e1fe3e4aSElliott Hughesclass WOFF2GlyfTableTest(unittest.TestCase):
757*e1fe3e4aSElliott Hughes    @classmethod
758*e1fe3e4aSElliott Hughes    def setUpClass(cls):
759*e1fe3e4aSElliott Hughes        font = ttLib.TTFont(recalcBBoxes=False, recalcTimestamp=False)
760*e1fe3e4aSElliott Hughes        font.importXML(TTX)
761*e1fe3e4aSElliott Hughes        cls.tables = {}
762*e1fe3e4aSElliott Hughes        cls.transformedTags = ("maxp", "head", "loca", "glyf")
763*e1fe3e4aSElliott Hughes        for tag in reversed(cls.transformedTags):  # compile in inverse order
764*e1fe3e4aSElliott Hughes            cls.tables[tag] = font.getTableData(tag)
765*e1fe3e4aSElliott Hughes        infile = BytesIO(TT_WOFF2.getvalue())
766*e1fe3e4aSElliott Hughes        reader = WOFF2Reader(infile)
767*e1fe3e4aSElliott Hughes        cls.transformedGlyfData = reader.tables["glyf"].loadData(reader.transformBuffer)
768*e1fe3e4aSElliott Hughes        cls.glyphOrder = [".notdef"] + [
769*e1fe3e4aSElliott Hughes            "glyph%.5d" % i for i in range(1, font["maxp"].numGlyphs)
770*e1fe3e4aSElliott Hughes        ]
771*e1fe3e4aSElliott Hughes
772*e1fe3e4aSElliott Hughes    def setUp(self):
773*e1fe3e4aSElliott Hughes        self.font = font = ttLib.TTFont(recalcBBoxes=False, recalcTimestamp=False)
774*e1fe3e4aSElliott Hughes        font.setGlyphOrder(self.glyphOrder)
775*e1fe3e4aSElliott Hughes        font["head"] = ttLib.newTable("head")
776*e1fe3e4aSElliott Hughes        font["maxp"] = ttLib.newTable("maxp")
777*e1fe3e4aSElliott Hughes        font["loca"] = WOFF2LocaTable()
778*e1fe3e4aSElliott Hughes        font["glyf"] = WOFF2GlyfTable()
779*e1fe3e4aSElliott Hughes        for tag in self.transformedTags:
780*e1fe3e4aSElliott Hughes            font[tag].decompile(self.tables[tag], font)
781*e1fe3e4aSElliott Hughes
782*e1fe3e4aSElliott Hughes    def test_reconstruct_glyf_padded_4(self):
783*e1fe3e4aSElliott Hughes        glyfTable = WOFF2GlyfTable()
784*e1fe3e4aSElliott Hughes        glyfTable.reconstruct(self.transformedGlyfData, self.font)
785*e1fe3e4aSElliott Hughes        glyfTable.padding = 4
786*e1fe3e4aSElliott Hughes        data = glyfTable.compile(self.font)
787*e1fe3e4aSElliott Hughes        normGlyfData = normalise_table(self.font, "glyf", glyfTable.padding)
788*e1fe3e4aSElliott Hughes        self.assertEqual(normGlyfData, data)
789*e1fe3e4aSElliott Hughes
790*e1fe3e4aSElliott Hughes    def test_reconstruct_glyf_padded_2(self):
791*e1fe3e4aSElliott Hughes        glyfTable = WOFF2GlyfTable()
792*e1fe3e4aSElliott Hughes        glyfTable.reconstruct(self.transformedGlyfData, self.font)
793*e1fe3e4aSElliott Hughes        glyfTable.padding = 2
794*e1fe3e4aSElliott Hughes        data = glyfTable.compile(self.font)
795*e1fe3e4aSElliott Hughes        normGlyfData = normalise_table(self.font, "glyf", glyfTable.padding)
796*e1fe3e4aSElliott Hughes        self.assertEqual(normGlyfData, data)
797*e1fe3e4aSElliott Hughes
798*e1fe3e4aSElliott Hughes    def test_reconstruct_glyf_unpadded(self):
799*e1fe3e4aSElliott Hughes        glyfTable = WOFF2GlyfTable()
800*e1fe3e4aSElliott Hughes        glyfTable.reconstruct(self.transformedGlyfData, self.font)
801*e1fe3e4aSElliott Hughes        data = glyfTable.compile(self.font)
802*e1fe3e4aSElliott Hughes        self.assertEqual(self.tables["glyf"], data)
803*e1fe3e4aSElliott Hughes
804*e1fe3e4aSElliott Hughes    def test_reconstruct_glyf_incorrect_glyphOrder(self):
805*e1fe3e4aSElliott Hughes        glyfTable = WOFF2GlyfTable()
806*e1fe3e4aSElliott Hughes        badGlyphOrder = self.font.getGlyphOrder()[:-1]
807*e1fe3e4aSElliott Hughes        self.font.setGlyphOrder(badGlyphOrder)
808*e1fe3e4aSElliott Hughes        with self.assertRaisesRegex(ttLib.TTLibError, "incorrect glyphOrder"):
809*e1fe3e4aSElliott Hughes            glyfTable.reconstruct(self.transformedGlyfData, self.font)
810*e1fe3e4aSElliott Hughes
811*e1fe3e4aSElliott Hughes    def test_reconstruct_glyf_missing_glyphOrder(self):
812*e1fe3e4aSElliott Hughes        glyfTable = WOFF2GlyfTable()
813*e1fe3e4aSElliott Hughes        del self.font.glyphOrder
814*e1fe3e4aSElliott Hughes        numGlyphs = self.font["maxp"].numGlyphs
815*e1fe3e4aSElliott Hughes        del self.font["maxp"]
816*e1fe3e4aSElliott Hughes        glyfTable.reconstruct(self.transformedGlyfData, self.font)
817*e1fe3e4aSElliott Hughes        expected = [".notdef"]
818*e1fe3e4aSElliott Hughes        expected.extend(["glyph%.5d" % i for i in range(1, numGlyphs)])
819*e1fe3e4aSElliott Hughes        self.assertEqual(expected, glyfTable.glyphOrder)
820*e1fe3e4aSElliott Hughes
821*e1fe3e4aSElliott Hughes    def test_reconstruct_loca_padded_4(self):
822*e1fe3e4aSElliott Hughes        locaTable = self.font["loca"] = WOFF2LocaTable()
823*e1fe3e4aSElliott Hughes        glyfTable = self.font["glyf"] = WOFF2GlyfTable()
824*e1fe3e4aSElliott Hughes        glyfTable.reconstruct(self.transformedGlyfData, self.font)
825*e1fe3e4aSElliott Hughes        glyfTable.padding = 4
826*e1fe3e4aSElliott Hughes        glyfTable.compile(self.font)
827*e1fe3e4aSElliott Hughes        data = locaTable.compile(self.font)
828*e1fe3e4aSElliott Hughes        normLocaData = normalise_table(self.font, "loca", glyfTable.padding)
829*e1fe3e4aSElliott Hughes        self.assertEqual(normLocaData, data)
830*e1fe3e4aSElliott Hughes
831*e1fe3e4aSElliott Hughes    def test_reconstruct_loca_padded_2(self):
832*e1fe3e4aSElliott Hughes        locaTable = self.font["loca"] = WOFF2LocaTable()
833*e1fe3e4aSElliott Hughes        glyfTable = self.font["glyf"] = WOFF2GlyfTable()
834*e1fe3e4aSElliott Hughes        glyfTable.reconstruct(self.transformedGlyfData, self.font)
835*e1fe3e4aSElliott Hughes        glyfTable.padding = 2
836*e1fe3e4aSElliott Hughes        glyfTable.compile(self.font)
837*e1fe3e4aSElliott Hughes        data = locaTable.compile(self.font)
838*e1fe3e4aSElliott Hughes        normLocaData = normalise_table(self.font, "loca", glyfTable.padding)
839*e1fe3e4aSElliott Hughes        self.assertEqual(normLocaData, data)
840*e1fe3e4aSElliott Hughes
841*e1fe3e4aSElliott Hughes    def test_reconstruct_loca_unpadded(self):
842*e1fe3e4aSElliott Hughes        locaTable = self.font["loca"] = WOFF2LocaTable()
843*e1fe3e4aSElliott Hughes        glyfTable = self.font["glyf"] = WOFF2GlyfTable()
844*e1fe3e4aSElliott Hughes        glyfTable.reconstruct(self.transformedGlyfData, self.font)
845*e1fe3e4aSElliott Hughes        glyfTable.compile(self.font)
846*e1fe3e4aSElliott Hughes        data = locaTable.compile(self.font)
847*e1fe3e4aSElliott Hughes        self.assertEqual(self.tables["loca"], data)
848*e1fe3e4aSElliott Hughes
849*e1fe3e4aSElliott Hughes    def test_reconstruct_glyf_header_not_enough_data(self):
850*e1fe3e4aSElliott Hughes        with self.assertRaisesRegex(ttLib.TTLibError, "not enough 'glyf' data"):
851*e1fe3e4aSElliott Hughes            WOFF2GlyfTable().reconstruct(b"", self.font)
852*e1fe3e4aSElliott Hughes
853*e1fe3e4aSElliott Hughes    def test_reconstruct_glyf_table_incorrect_size(self):
854*e1fe3e4aSElliott Hughes        msg = "incorrect size of transformed 'glyf'"
855*e1fe3e4aSElliott Hughes        with self.assertRaisesRegex(ttLib.TTLibError, msg):
856*e1fe3e4aSElliott Hughes            WOFF2GlyfTable().reconstruct(self.transformedGlyfData + b"\x00", self.font)
857*e1fe3e4aSElliott Hughes        with self.assertRaisesRegex(ttLib.TTLibError, msg):
858*e1fe3e4aSElliott Hughes            WOFF2GlyfTable().reconstruct(self.transformedGlyfData[:-1], self.font)
859*e1fe3e4aSElliott Hughes
860*e1fe3e4aSElliott Hughes    def test_transform_glyf(self):
861*e1fe3e4aSElliott Hughes        glyfTable = self.font["glyf"]
862*e1fe3e4aSElliott Hughes        data = glyfTable.transform(self.font)
863*e1fe3e4aSElliott Hughes        self.assertEqual(self.transformedGlyfData, data)
864*e1fe3e4aSElliott Hughes
865*e1fe3e4aSElliott Hughes    def test_roundtrip_glyf_reconstruct_and_transform(self):
866*e1fe3e4aSElliott Hughes        glyfTable = WOFF2GlyfTable()
867*e1fe3e4aSElliott Hughes        glyfTable.reconstruct(self.transformedGlyfData, self.font)
868*e1fe3e4aSElliott Hughes        data = glyfTable.transform(self.font)
869*e1fe3e4aSElliott Hughes        self.assertEqual(self.transformedGlyfData, data)
870*e1fe3e4aSElliott Hughes
871*e1fe3e4aSElliott Hughes    def test_roundtrip_glyf_transform_and_reconstruct(self):
872*e1fe3e4aSElliott Hughes        glyfTable = self.font["glyf"]
873*e1fe3e4aSElliott Hughes        transformedData = glyfTable.transform(self.font)
874*e1fe3e4aSElliott Hughes        newGlyfTable = WOFF2GlyfTable()
875*e1fe3e4aSElliott Hughes        newGlyfTable.reconstruct(transformedData, self.font)
876*e1fe3e4aSElliott Hughes        newGlyfTable.padding = 4
877*e1fe3e4aSElliott Hughes        reconstructedData = newGlyfTable.compile(self.font)
878*e1fe3e4aSElliott Hughes        normGlyfData = normalise_table(self.font, "glyf", newGlyfTable.padding)
879*e1fe3e4aSElliott Hughes        self.assertEqual(normGlyfData, reconstructedData)
880*e1fe3e4aSElliott Hughes
881*e1fe3e4aSElliott Hughes
882*e1fe3e4aSElliott Hughes@pytest.fixture(scope="module")
883*e1fe3e4aSElliott Hughesdef fontfile():
884*e1fe3e4aSElliott Hughes    class Glyph(object):
885*e1fe3e4aSElliott Hughes        def __init__(self, empty=False, **kwargs):
886*e1fe3e4aSElliott Hughes            if not empty:
887*e1fe3e4aSElliott Hughes                self.draw = partial(self.drawRect, **kwargs)
888*e1fe3e4aSElliott Hughes            else:
889*e1fe3e4aSElliott Hughes                self.draw = lambda pen: None
890*e1fe3e4aSElliott Hughes
891*e1fe3e4aSElliott Hughes        @staticmethod
892*e1fe3e4aSElliott Hughes        def drawRect(pen, xMin, xMax):
893*e1fe3e4aSElliott Hughes            pen.moveTo((xMin, 0))
894*e1fe3e4aSElliott Hughes            pen.lineTo((xMin, 1000))
895*e1fe3e4aSElliott Hughes            pen.lineTo((xMax, 1000))
896*e1fe3e4aSElliott Hughes            pen.lineTo((xMax, 0))
897*e1fe3e4aSElliott Hughes            pen.closePath()
898*e1fe3e4aSElliott Hughes
899*e1fe3e4aSElliott Hughes    class CompositeGlyph(object):
900*e1fe3e4aSElliott Hughes        def __init__(self, components):
901*e1fe3e4aSElliott Hughes            self.components = components
902*e1fe3e4aSElliott Hughes
903*e1fe3e4aSElliott Hughes        def draw(self, pen):
904*e1fe3e4aSElliott Hughes            for baseGlyph, (offsetX, offsetY) in self.components:
905*e1fe3e4aSElliott Hughes                pen.addComponent(baseGlyph, (1, 0, 0, 1, offsetX, offsetY))
906*e1fe3e4aSElliott Hughes
907*e1fe3e4aSElliott Hughes    fb = fontBuilder.FontBuilder(unitsPerEm=1000, isTTF=True)
908*e1fe3e4aSElliott Hughes    fb.setupGlyphOrder(
909*e1fe3e4aSElliott Hughes        [".notdef", "space", "A", "acutecomb", "Aacute", "zero", "one", "two"]
910*e1fe3e4aSElliott Hughes    )
911*e1fe3e4aSElliott Hughes    fb.setupCharacterMap(
912*e1fe3e4aSElliott Hughes        {
913*e1fe3e4aSElliott Hughes            0x20: "space",
914*e1fe3e4aSElliott Hughes            0x41: "A",
915*e1fe3e4aSElliott Hughes            0x0301: "acutecomb",
916*e1fe3e4aSElliott Hughes            0xC1: "Aacute",
917*e1fe3e4aSElliott Hughes            0x30: "zero",
918*e1fe3e4aSElliott Hughes            0x31: "one",
919*e1fe3e4aSElliott Hughes            0x32: "two",
920*e1fe3e4aSElliott Hughes        }
921*e1fe3e4aSElliott Hughes    )
922*e1fe3e4aSElliott Hughes    fb.setupHorizontalMetrics(
923*e1fe3e4aSElliott Hughes        {
924*e1fe3e4aSElliott Hughes            ".notdef": (500, 50),
925*e1fe3e4aSElliott Hughes            "space": (600, 0),
926*e1fe3e4aSElliott Hughes            "A": (550, 40),
927*e1fe3e4aSElliott Hughes            "acutecomb": (0, -40),
928*e1fe3e4aSElliott Hughes            "Aacute": (550, 40),
929*e1fe3e4aSElliott Hughes            "zero": (500, 30),
930*e1fe3e4aSElliott Hughes            "one": (500, 50),
931*e1fe3e4aSElliott Hughes            "two": (500, 40),
932*e1fe3e4aSElliott Hughes        }
933*e1fe3e4aSElliott Hughes    )
934*e1fe3e4aSElliott Hughes    fb.setupHorizontalHeader(ascent=1000, descent=-200)
935*e1fe3e4aSElliott Hughes
936*e1fe3e4aSElliott Hughes    srcGlyphs = {
937*e1fe3e4aSElliott Hughes        ".notdef": Glyph(xMin=50, xMax=450),
938*e1fe3e4aSElliott Hughes        "space": Glyph(empty=True),
939*e1fe3e4aSElliott Hughes        "A": Glyph(xMin=40, xMax=510),
940*e1fe3e4aSElliott Hughes        "acutecomb": Glyph(xMin=-40, xMax=60),
941*e1fe3e4aSElliott Hughes        "Aacute": CompositeGlyph([("A", (0, 0)), ("acutecomb", (200, 0))]),
942*e1fe3e4aSElliott Hughes        "zero": Glyph(xMin=30, xMax=470),
943*e1fe3e4aSElliott Hughes        "one": Glyph(xMin=50, xMax=450),
944*e1fe3e4aSElliott Hughes        "two": Glyph(xMin=40, xMax=460),
945*e1fe3e4aSElliott Hughes    }
946*e1fe3e4aSElliott Hughes    pen = TTGlyphPen(srcGlyphs)
947*e1fe3e4aSElliott Hughes    glyphSet = {}
948*e1fe3e4aSElliott Hughes    for glyphName, glyph in srcGlyphs.items():
949*e1fe3e4aSElliott Hughes        glyph.draw(pen)
950*e1fe3e4aSElliott Hughes        glyphSet[glyphName] = pen.glyph()
951*e1fe3e4aSElliott Hughes    fb.setupGlyf(glyphSet)
952*e1fe3e4aSElliott Hughes
953*e1fe3e4aSElliott Hughes    fb.setupNameTable(
954*e1fe3e4aSElliott Hughes        {
955*e1fe3e4aSElliott Hughes            "familyName": "TestWOFF2",
956*e1fe3e4aSElliott Hughes            "styleName": "Regular",
957*e1fe3e4aSElliott Hughes            "uniqueFontIdentifier": "TestWOFF2 Regular; Version 1.000; ABCD",
958*e1fe3e4aSElliott Hughes            "fullName": "TestWOFF2 Regular",
959*e1fe3e4aSElliott Hughes            "version": "Version 1.000",
960*e1fe3e4aSElliott Hughes            "psName": "TestWOFF2-Regular",
961*e1fe3e4aSElliott Hughes        }
962*e1fe3e4aSElliott Hughes    )
963*e1fe3e4aSElliott Hughes    fb.setupOS2()
964*e1fe3e4aSElliott Hughes    fb.setupPost()
965*e1fe3e4aSElliott Hughes
966*e1fe3e4aSElliott Hughes    buf = BytesIO()
967*e1fe3e4aSElliott Hughes    fb.save(buf)
968*e1fe3e4aSElliott Hughes    buf.seek(0)
969*e1fe3e4aSElliott Hughes
970*e1fe3e4aSElliott Hughes    assert fb.font["maxp"].numGlyphs == 8
971*e1fe3e4aSElliott Hughes    assert fb.font["hhea"].numberOfHMetrics == 6
972*e1fe3e4aSElliott Hughes    for glyphName in fb.font.getGlyphOrder():
973*e1fe3e4aSElliott Hughes        xMin = getattr(fb.font["glyf"][glyphName], "xMin", 0)
974*e1fe3e4aSElliott Hughes        assert xMin == fb.font["hmtx"][glyphName][1]
975*e1fe3e4aSElliott Hughes
976*e1fe3e4aSElliott Hughes    return buf
977*e1fe3e4aSElliott Hughes
978*e1fe3e4aSElliott Hughes
979*e1fe3e4aSElliott Hughes@pytest.fixture
980*e1fe3e4aSElliott Hughesdef ttFont(fontfile):
981*e1fe3e4aSElliott Hughes    return ttLib.TTFont(fontfile, recalcBBoxes=False, recalcTimestamp=False)
982*e1fe3e4aSElliott Hughes
983*e1fe3e4aSElliott Hughes
984*e1fe3e4aSElliott Hughesclass WOFF2HmtxTableTest(object):
985*e1fe3e4aSElliott Hughes    def test_transform_no_sidebearings(self, ttFont):
986*e1fe3e4aSElliott Hughes        hmtxTable = WOFF2HmtxTable()
987*e1fe3e4aSElliott Hughes        hmtxTable.metrics = ttFont["hmtx"].metrics
988*e1fe3e4aSElliott Hughes
989*e1fe3e4aSElliott Hughes        data = hmtxTable.transform(ttFont)
990*e1fe3e4aSElliott Hughes
991*e1fe3e4aSElliott Hughes        assert data == (
992*e1fe3e4aSElliott Hughes            b"\x03"  # 00000011 | bits 0 and 1 are set (no sidebearings arrays)
993*e1fe3e4aSElliott Hughes            # advanceWidthArray
994*e1fe3e4aSElliott Hughes            b"\x01\xf4"  # .notdef: 500
995*e1fe3e4aSElliott Hughes            b"\x02X"  # space: 600
996*e1fe3e4aSElliott Hughes            b"\x02&"  # A: 550
997*e1fe3e4aSElliott Hughes            b"\x00\x00"  # acutecomb: 0
998*e1fe3e4aSElliott Hughes            b"\x02&"  # Aacute: 550
999*e1fe3e4aSElliott Hughes            b"\x01\xf4"  # zero: 500
1000*e1fe3e4aSElliott Hughes        )
1001*e1fe3e4aSElliott Hughes
1002*e1fe3e4aSElliott Hughes    def test_transform_proportional_sidebearings(self, ttFont):
1003*e1fe3e4aSElliott Hughes        hmtxTable = WOFF2HmtxTable()
1004*e1fe3e4aSElliott Hughes        metrics = ttFont["hmtx"].metrics
1005*e1fe3e4aSElliott Hughes        # force one of the proportional glyphs to have its left sidebearing be
1006*e1fe3e4aSElliott Hughes        # different from its xMin (40)
1007*e1fe3e4aSElliott Hughes        metrics["A"] = (550, 39)
1008*e1fe3e4aSElliott Hughes        hmtxTable.metrics = metrics
1009*e1fe3e4aSElliott Hughes
1010*e1fe3e4aSElliott Hughes        assert ttFont["glyf"]["A"].xMin != metrics["A"][1]
1011*e1fe3e4aSElliott Hughes
1012*e1fe3e4aSElliott Hughes        data = hmtxTable.transform(ttFont)
1013*e1fe3e4aSElliott Hughes
1014*e1fe3e4aSElliott Hughes        assert data == (
1015*e1fe3e4aSElliott Hughes            b"\x02"  # 00000010 | bits 0 unset: explicit proportional sidebearings
1016*e1fe3e4aSElliott Hughes            # advanceWidthArray
1017*e1fe3e4aSElliott Hughes            b"\x01\xf4"  # .notdef: 500
1018*e1fe3e4aSElliott Hughes            b"\x02X"  # space: 600
1019*e1fe3e4aSElliott Hughes            b"\x02&"  # A: 550
1020*e1fe3e4aSElliott Hughes            b"\x00\x00"  # acutecomb: 0
1021*e1fe3e4aSElliott Hughes            b"\x02&"  # Aacute: 550
1022*e1fe3e4aSElliott Hughes            b"\x01\xf4"  # zero: 500
1023*e1fe3e4aSElliott Hughes            # lsbArray
1024*e1fe3e4aSElliott Hughes            b"\x002"  # .notdef: 50
1025*e1fe3e4aSElliott Hughes            b"\x00\x00"  # space: 0
1026*e1fe3e4aSElliott Hughes            b"\x00'"  # A: 39 (xMin: 40)
1027*e1fe3e4aSElliott Hughes            b"\xff\xd8"  # acutecomb: -40
1028*e1fe3e4aSElliott Hughes            b"\x00("  # Aacute: 40
1029*e1fe3e4aSElliott Hughes            b"\x00\x1e"  # zero: 30
1030*e1fe3e4aSElliott Hughes        )
1031*e1fe3e4aSElliott Hughes
1032*e1fe3e4aSElliott Hughes    def test_transform_monospaced_sidebearings(self, ttFont):
1033*e1fe3e4aSElliott Hughes        hmtxTable = WOFF2HmtxTable()
1034*e1fe3e4aSElliott Hughes        metrics = ttFont["hmtx"].metrics
1035*e1fe3e4aSElliott Hughes        hmtxTable.metrics = metrics
1036*e1fe3e4aSElliott Hughes
1037*e1fe3e4aSElliott Hughes        # force one of the monospaced glyphs at the end of hmtx table to have
1038*e1fe3e4aSElliott Hughes        # its xMin different from its left sidebearing (50)
1039*e1fe3e4aSElliott Hughes        ttFont["glyf"]["one"].xMin = metrics["one"][1] + 1
1040*e1fe3e4aSElliott Hughes
1041*e1fe3e4aSElliott Hughes        data = hmtxTable.transform(ttFont)
1042*e1fe3e4aSElliott Hughes
1043*e1fe3e4aSElliott Hughes        assert data == (
1044*e1fe3e4aSElliott Hughes            b"\x01"  # 00000001 | bits 1 unset: explicit monospaced sidebearings
1045*e1fe3e4aSElliott Hughes            # advanceWidthArray
1046*e1fe3e4aSElliott Hughes            b"\x01\xf4"  # .notdef: 500
1047*e1fe3e4aSElliott Hughes            b"\x02X"  # space: 600
1048*e1fe3e4aSElliott Hughes            b"\x02&"  # A: 550
1049*e1fe3e4aSElliott Hughes            b"\x00\x00"  # acutecomb: 0
1050*e1fe3e4aSElliott Hughes            b"\x02&"  # Aacute: 550
1051*e1fe3e4aSElliott Hughes            b"\x01\xf4"  # zero: 500
1052*e1fe3e4aSElliott Hughes            # leftSideBearingArray
1053*e1fe3e4aSElliott Hughes            b"\x002"  # one: 50 (xMin: 51)
1054*e1fe3e4aSElliott Hughes            b"\x00("  # two: 40
1055*e1fe3e4aSElliott Hughes        )
1056*e1fe3e4aSElliott Hughes
1057*e1fe3e4aSElliott Hughes    def test_transform_not_applicable(self, ttFont):
1058*e1fe3e4aSElliott Hughes        hmtxTable = WOFF2HmtxTable()
1059*e1fe3e4aSElliott Hughes        metrics = ttFont["hmtx"].metrics
1060*e1fe3e4aSElliott Hughes        # force both a proportional and monospaced glyph to have sidebearings
1061*e1fe3e4aSElliott Hughes        # different from the respective xMin coordinates
1062*e1fe3e4aSElliott Hughes        metrics["A"] = (550, 39)
1063*e1fe3e4aSElliott Hughes        metrics["one"] = (500, 51)
1064*e1fe3e4aSElliott Hughes        hmtxTable.metrics = metrics
1065*e1fe3e4aSElliott Hughes
1066*e1fe3e4aSElliott Hughes        # 'None' signals to fall back using untransformed hmtx table data
1067*e1fe3e4aSElliott Hughes        assert hmtxTable.transform(ttFont) is None
1068*e1fe3e4aSElliott Hughes
1069*e1fe3e4aSElliott Hughes    def test_reconstruct_no_sidebearings(self, ttFont):
1070*e1fe3e4aSElliott Hughes        hmtxTable = WOFF2HmtxTable()
1071*e1fe3e4aSElliott Hughes
1072*e1fe3e4aSElliott Hughes        data = (
1073*e1fe3e4aSElliott Hughes            b"\x03"  # 00000011 | bits 0 and 1 are set (no sidebearings arrays)
1074*e1fe3e4aSElliott Hughes            # advanceWidthArray
1075*e1fe3e4aSElliott Hughes            b"\x01\xf4"  # .notdef: 500
1076*e1fe3e4aSElliott Hughes            b"\x02X"  # space: 600
1077*e1fe3e4aSElliott Hughes            b"\x02&"  # A: 550
1078*e1fe3e4aSElliott Hughes            b"\x00\x00"  # acutecomb: 0
1079*e1fe3e4aSElliott Hughes            b"\x02&"  # Aacute: 550
1080*e1fe3e4aSElliott Hughes            b"\x01\xf4"  # zero: 500
1081*e1fe3e4aSElliott Hughes        )
1082*e1fe3e4aSElliott Hughes
1083*e1fe3e4aSElliott Hughes        hmtxTable.reconstruct(data, ttFont)
1084*e1fe3e4aSElliott Hughes
1085*e1fe3e4aSElliott Hughes        assert hmtxTable.metrics == {
1086*e1fe3e4aSElliott Hughes            ".notdef": (500, 50),
1087*e1fe3e4aSElliott Hughes            "space": (600, 0),
1088*e1fe3e4aSElliott Hughes            "A": (550, 40),
1089*e1fe3e4aSElliott Hughes            "acutecomb": (0, -40),
1090*e1fe3e4aSElliott Hughes            "Aacute": (550, 40),
1091*e1fe3e4aSElliott Hughes            "zero": (500, 30),
1092*e1fe3e4aSElliott Hughes            "one": (500, 50),
1093*e1fe3e4aSElliott Hughes            "two": (500, 40),
1094*e1fe3e4aSElliott Hughes        }
1095*e1fe3e4aSElliott Hughes
1096*e1fe3e4aSElliott Hughes    def test_reconstruct_proportional_sidebearings(self, ttFont):
1097*e1fe3e4aSElliott Hughes        hmtxTable = WOFF2HmtxTable()
1098*e1fe3e4aSElliott Hughes
1099*e1fe3e4aSElliott Hughes        data = (
1100*e1fe3e4aSElliott Hughes            b"\x02"  # 00000010 | bits 0 unset: explicit proportional sidebearings
1101*e1fe3e4aSElliott Hughes            # advanceWidthArray
1102*e1fe3e4aSElliott Hughes            b"\x01\xf4"  # .notdef: 500
1103*e1fe3e4aSElliott Hughes            b"\x02X"  # space: 600
1104*e1fe3e4aSElliott Hughes            b"\x02&"  # A: 550
1105*e1fe3e4aSElliott Hughes            b"\x00\x00"  # acutecomb: 0
1106*e1fe3e4aSElliott Hughes            b"\x02&"  # Aacute: 550
1107*e1fe3e4aSElliott Hughes            b"\x01\xf4"  # zero: 500
1108*e1fe3e4aSElliott Hughes            # lsbArray
1109*e1fe3e4aSElliott Hughes            b"\x002"  # .notdef: 50
1110*e1fe3e4aSElliott Hughes            b"\x00\x00"  # space: 0
1111*e1fe3e4aSElliott Hughes            b"\x00'"  # A: 39 (xMin: 40)
1112*e1fe3e4aSElliott Hughes            b"\xff\xd8"  # acutecomb: -40
1113*e1fe3e4aSElliott Hughes            b"\x00("  # Aacute: 40
1114*e1fe3e4aSElliott Hughes            b"\x00\x1e"  # zero: 30
1115*e1fe3e4aSElliott Hughes        )
1116*e1fe3e4aSElliott Hughes
1117*e1fe3e4aSElliott Hughes        hmtxTable.reconstruct(data, ttFont)
1118*e1fe3e4aSElliott Hughes
1119*e1fe3e4aSElliott Hughes        assert hmtxTable.metrics == {
1120*e1fe3e4aSElliott Hughes            ".notdef": (500, 50),
1121*e1fe3e4aSElliott Hughes            "space": (600, 0),
1122*e1fe3e4aSElliott Hughes            "A": (550, 39),
1123*e1fe3e4aSElliott Hughes            "acutecomb": (0, -40),
1124*e1fe3e4aSElliott Hughes            "Aacute": (550, 40),
1125*e1fe3e4aSElliott Hughes            "zero": (500, 30),
1126*e1fe3e4aSElliott Hughes            "one": (500, 50),
1127*e1fe3e4aSElliott Hughes            "two": (500, 40),
1128*e1fe3e4aSElliott Hughes        }
1129*e1fe3e4aSElliott Hughes
1130*e1fe3e4aSElliott Hughes        assert ttFont["glyf"]["A"].xMin == 40
1131*e1fe3e4aSElliott Hughes
1132*e1fe3e4aSElliott Hughes    def test_reconstruct_monospaced_sidebearings(self, ttFont):
1133*e1fe3e4aSElliott Hughes        hmtxTable = WOFF2HmtxTable()
1134*e1fe3e4aSElliott Hughes
1135*e1fe3e4aSElliott Hughes        data = (
1136*e1fe3e4aSElliott Hughes            b"\x01"  # 00000001 | bits 1 unset: explicit monospaced sidebearings
1137*e1fe3e4aSElliott Hughes            # advanceWidthArray
1138*e1fe3e4aSElliott Hughes            b"\x01\xf4"  # .notdef: 500
1139*e1fe3e4aSElliott Hughes            b"\x02X"  # space: 600
1140*e1fe3e4aSElliott Hughes            b"\x02&"  # A: 550
1141*e1fe3e4aSElliott Hughes            b"\x00\x00"  # acutecomb: 0
1142*e1fe3e4aSElliott Hughes            b"\x02&"  # Aacute: 550
1143*e1fe3e4aSElliott Hughes            b"\x01\xf4"  # zero: 500
1144*e1fe3e4aSElliott Hughes            # leftSideBearingArray
1145*e1fe3e4aSElliott Hughes            b"\x003"  # one: 51 (xMin: 50)
1146*e1fe3e4aSElliott Hughes            b"\x00("  # two: 40
1147*e1fe3e4aSElliott Hughes        )
1148*e1fe3e4aSElliott Hughes
1149*e1fe3e4aSElliott Hughes        hmtxTable.reconstruct(data, ttFont)
1150*e1fe3e4aSElliott Hughes
1151*e1fe3e4aSElliott Hughes        assert hmtxTable.metrics == {
1152*e1fe3e4aSElliott Hughes            ".notdef": (500, 50),
1153*e1fe3e4aSElliott Hughes            "space": (600, 0),
1154*e1fe3e4aSElliott Hughes            "A": (550, 40),
1155*e1fe3e4aSElliott Hughes            "acutecomb": (0, -40),
1156*e1fe3e4aSElliott Hughes            "Aacute": (550, 40),
1157*e1fe3e4aSElliott Hughes            "zero": (500, 30),
1158*e1fe3e4aSElliott Hughes            "one": (500, 51),
1159*e1fe3e4aSElliott Hughes            "two": (500, 40),
1160*e1fe3e4aSElliott Hughes        }
1161*e1fe3e4aSElliott Hughes
1162*e1fe3e4aSElliott Hughes        assert ttFont["glyf"]["one"].xMin == 50
1163*e1fe3e4aSElliott Hughes
1164*e1fe3e4aSElliott Hughes    def test_reconstruct_flags_reserved_bits(self):
1165*e1fe3e4aSElliott Hughes        hmtxTable = WOFF2HmtxTable()
1166*e1fe3e4aSElliott Hughes
1167*e1fe3e4aSElliott Hughes        with pytest.raises(
1168*e1fe3e4aSElliott Hughes            ttLib.TTLibError, match="Bits 2-7 of 'hmtx' flags are reserved"
1169*e1fe3e4aSElliott Hughes        ):
1170*e1fe3e4aSElliott Hughes            hmtxTable.reconstruct(b"\xFF", ttFont=None)
1171*e1fe3e4aSElliott Hughes
1172*e1fe3e4aSElliott Hughes    def test_reconstruct_flags_required_bits(self):
1173*e1fe3e4aSElliott Hughes        hmtxTable = WOFF2HmtxTable()
1174*e1fe3e4aSElliott Hughes
1175*e1fe3e4aSElliott Hughes        with pytest.raises(ttLib.TTLibError, match="either bits 0 or 1 .* must set"):
1176*e1fe3e4aSElliott Hughes            hmtxTable.reconstruct(b"\x00", ttFont=None)
1177*e1fe3e4aSElliott Hughes
1178*e1fe3e4aSElliott Hughes    def test_reconstruct_too_much_data(self, ttFont):
1179*e1fe3e4aSElliott Hughes        ttFont["hhea"].numberOfHMetrics = 2
1180*e1fe3e4aSElliott Hughes        data = b"\x03\x01\xf4\x02X\x02&"
1181*e1fe3e4aSElliott Hughes        hmtxTable = WOFF2HmtxTable()
1182*e1fe3e4aSElliott Hughes
1183*e1fe3e4aSElliott Hughes        with pytest.raises(ttLib.TTLibError, match="too much 'hmtx' table data"):
1184*e1fe3e4aSElliott Hughes            hmtxTable.reconstruct(data, ttFont)
1185*e1fe3e4aSElliott Hughes
1186*e1fe3e4aSElliott Hughes
1187*e1fe3e4aSElliott Hughesclass WOFF2RoundtripTest(object):
1188*e1fe3e4aSElliott Hughes    @staticmethod
1189*e1fe3e4aSElliott Hughes    def roundtrip(infile):
1190*e1fe3e4aSElliott Hughes        infile.seek(0)
1191*e1fe3e4aSElliott Hughes        ttFont = ttLib.TTFont(infile, recalcBBoxes=False, recalcTimestamp=False)
1192*e1fe3e4aSElliott Hughes        outfile = BytesIO()
1193*e1fe3e4aSElliott Hughes        ttFont.save(outfile)
1194*e1fe3e4aSElliott Hughes        return outfile, ttFont
1195*e1fe3e4aSElliott Hughes
1196*e1fe3e4aSElliott Hughes    def test_roundtrip_default_transforms(self, ttFont):
1197*e1fe3e4aSElliott Hughes        ttFont.flavor = "woff2"
1198*e1fe3e4aSElliott Hughes        # ttFont.flavorData = None
1199*e1fe3e4aSElliott Hughes        tmp = BytesIO()
1200*e1fe3e4aSElliott Hughes        ttFont.save(tmp)
1201*e1fe3e4aSElliott Hughes
1202*e1fe3e4aSElliott Hughes        tmp2, ttFont2 = self.roundtrip(tmp)
1203*e1fe3e4aSElliott Hughes
1204*e1fe3e4aSElliott Hughes        assert tmp.getvalue() == tmp2.getvalue()
1205*e1fe3e4aSElliott Hughes        assert ttFont2.reader.flavorData.transformedTables == {"glyf", "loca"}
1206*e1fe3e4aSElliott Hughes
1207*e1fe3e4aSElliott Hughes    def test_roundtrip_no_transforms(self, ttFont):
1208*e1fe3e4aSElliott Hughes        ttFont.flavor = "woff2"
1209*e1fe3e4aSElliott Hughes        ttFont.flavorData = WOFF2FlavorData(transformedTables=[])
1210*e1fe3e4aSElliott Hughes        tmp = BytesIO()
1211*e1fe3e4aSElliott Hughes        ttFont.save(tmp)
1212*e1fe3e4aSElliott Hughes
1213*e1fe3e4aSElliott Hughes        tmp2, ttFont2 = self.roundtrip(tmp)
1214*e1fe3e4aSElliott Hughes
1215*e1fe3e4aSElliott Hughes        assert tmp.getvalue() == tmp2.getvalue()
1216*e1fe3e4aSElliott Hughes        assert not ttFont2.reader.flavorData.transformedTables
1217*e1fe3e4aSElliott Hughes
1218*e1fe3e4aSElliott Hughes    def test_roundtrip_all_transforms(self, ttFont):
1219*e1fe3e4aSElliott Hughes        ttFont.flavor = "woff2"
1220*e1fe3e4aSElliott Hughes        ttFont.flavorData = WOFF2FlavorData(transformedTables=["glyf", "loca", "hmtx"])
1221*e1fe3e4aSElliott Hughes        tmp = BytesIO()
1222*e1fe3e4aSElliott Hughes        ttFont.save(tmp)
1223*e1fe3e4aSElliott Hughes
1224*e1fe3e4aSElliott Hughes        tmp2, ttFont2 = self.roundtrip(tmp)
1225*e1fe3e4aSElliott Hughes
1226*e1fe3e4aSElliott Hughes        assert tmp.getvalue() == tmp2.getvalue()
1227*e1fe3e4aSElliott Hughes        assert ttFont2.reader.flavorData.transformedTables == {"glyf", "loca", "hmtx"}
1228*e1fe3e4aSElliott Hughes
1229*e1fe3e4aSElliott Hughes    def test_roundtrip_only_hmtx_no_glyf_transform(self, ttFont):
1230*e1fe3e4aSElliott Hughes        ttFont.flavor = "woff2"
1231*e1fe3e4aSElliott Hughes        ttFont.flavorData = WOFF2FlavorData(transformedTables=["hmtx"])
1232*e1fe3e4aSElliott Hughes        tmp = BytesIO()
1233*e1fe3e4aSElliott Hughes        ttFont.save(tmp)
1234*e1fe3e4aSElliott Hughes
1235*e1fe3e4aSElliott Hughes        tmp2, ttFont2 = self.roundtrip(tmp)
1236*e1fe3e4aSElliott Hughes
1237*e1fe3e4aSElliott Hughes        assert tmp.getvalue() == tmp2.getvalue()
1238*e1fe3e4aSElliott Hughes        assert ttFont2.reader.flavorData.transformedTables == {"hmtx"}
1239*e1fe3e4aSElliott Hughes
1240*e1fe3e4aSElliott Hughes    def test_roundtrip_no_glyf_and_loca_tables(self):
1241*e1fe3e4aSElliott Hughes        ttx = os.path.join(
1242*e1fe3e4aSElliott Hughes            os.path.dirname(current_dir), "subset", "data", "google_color.ttx"
1243*e1fe3e4aSElliott Hughes        )
1244*e1fe3e4aSElliott Hughes        ttFont = ttLib.TTFont()
1245*e1fe3e4aSElliott Hughes        ttFont.importXML(ttx)
1246*e1fe3e4aSElliott Hughes
1247*e1fe3e4aSElliott Hughes        assert "glyf" not in ttFont
1248*e1fe3e4aSElliott Hughes        assert "loca" not in ttFont
1249*e1fe3e4aSElliott Hughes
1250*e1fe3e4aSElliott Hughes        ttFont.flavor = "woff2"
1251*e1fe3e4aSElliott Hughes        tmp = BytesIO()
1252*e1fe3e4aSElliott Hughes        ttFont.save(tmp)
1253*e1fe3e4aSElliott Hughes
1254*e1fe3e4aSElliott Hughes        tmp2, ttFont2 = self.roundtrip(tmp)
1255*e1fe3e4aSElliott Hughes        assert tmp.getvalue() == tmp2.getvalue()
1256*e1fe3e4aSElliott Hughes        assert ttFont.flavor == "woff2"
1257*e1fe3e4aSElliott Hughes
1258*e1fe3e4aSElliott Hughes    def test_roundtrip_off_curve_despite_overlap_bit(self):
1259*e1fe3e4aSElliott Hughes        ttx = os.path.join(data_dir, "woff2_overlap_offcurve_in.ttx")
1260*e1fe3e4aSElliott Hughes        ttFont = ttLib.TTFont()
1261*e1fe3e4aSElliott Hughes        ttFont.importXML(ttx)
1262*e1fe3e4aSElliott Hughes
1263*e1fe3e4aSElliott Hughes        assert ttFont["glyf"]["A"].flags[0] == _g_l_y_f.flagOverlapSimple
1264*e1fe3e4aSElliott Hughes
1265*e1fe3e4aSElliott Hughes        ttFont.flavor = "woff2"
1266*e1fe3e4aSElliott Hughes        tmp = BytesIO()
1267*e1fe3e4aSElliott Hughes        ttFont.save(tmp)
1268*e1fe3e4aSElliott Hughes
1269*e1fe3e4aSElliott Hughes        _, ttFont2 = self.roundtrip(tmp)
1270*e1fe3e4aSElliott Hughes        assert ttFont2.flavor == "woff2"
1271*e1fe3e4aSElliott Hughes        # check that the off-curve point is still there
1272*e1fe3e4aSElliott Hughes        assert ttFont2["glyf"]["A"].flags[0] & _g_l_y_f.flagOnCurve == 0
1273*e1fe3e4aSElliott Hughes        # check that the overlap bit is still there
1274*e1fe3e4aSElliott Hughes        assert ttFont2["glyf"]["A"].flags[0] & _g_l_y_f.flagOverlapSimple != 0
1275*e1fe3e4aSElliott Hughes
1276*e1fe3e4aSElliott Hughes
1277*e1fe3e4aSElliott Hughesclass MainTest(object):
1278*e1fe3e4aSElliott Hughes    @staticmethod
1279*e1fe3e4aSElliott Hughes    def make_ttf(tmpdir):
1280*e1fe3e4aSElliott Hughes        ttFont = ttLib.TTFont(recalcBBoxes=False, recalcTimestamp=False)
1281*e1fe3e4aSElliott Hughes        ttFont.importXML(TTX)
1282*e1fe3e4aSElliott Hughes        filename = str(tmpdir / "TestTTF-Regular.ttf")
1283*e1fe3e4aSElliott Hughes        ttFont.save(filename)
1284*e1fe3e4aSElliott Hughes        return filename
1285*e1fe3e4aSElliott Hughes
1286*e1fe3e4aSElliott Hughes    def test_compress_ttf(self, tmpdir):
1287*e1fe3e4aSElliott Hughes        input_file = self.make_ttf(tmpdir)
1288*e1fe3e4aSElliott Hughes
1289*e1fe3e4aSElliott Hughes        assert woff2.main(["compress", input_file]) is None
1290*e1fe3e4aSElliott Hughes
1291*e1fe3e4aSElliott Hughes        assert (tmpdir / "TestTTF-Regular.woff2").check(file=True)
1292*e1fe3e4aSElliott Hughes
1293*e1fe3e4aSElliott Hughes    def test_compress_ttf_no_glyf_transform(self, tmpdir):
1294*e1fe3e4aSElliott Hughes        input_file = self.make_ttf(tmpdir)
1295*e1fe3e4aSElliott Hughes
1296*e1fe3e4aSElliott Hughes        assert woff2.main(["compress", "--no-glyf-transform", input_file]) is None
1297*e1fe3e4aSElliott Hughes
1298*e1fe3e4aSElliott Hughes        assert (tmpdir / "TestTTF-Regular.woff2").check(file=True)
1299*e1fe3e4aSElliott Hughes
1300*e1fe3e4aSElliott Hughes    def test_compress_ttf_hmtx_transform(self, tmpdir):
1301*e1fe3e4aSElliott Hughes        input_file = self.make_ttf(tmpdir)
1302*e1fe3e4aSElliott Hughes
1303*e1fe3e4aSElliott Hughes        assert woff2.main(["compress", "--hmtx-transform", input_file]) is None
1304*e1fe3e4aSElliott Hughes
1305*e1fe3e4aSElliott Hughes        assert (tmpdir / "TestTTF-Regular.woff2").check(file=True)
1306*e1fe3e4aSElliott Hughes
1307*e1fe3e4aSElliott Hughes    def test_compress_ttf_no_glyf_transform_hmtx_transform(self, tmpdir):
1308*e1fe3e4aSElliott Hughes        input_file = self.make_ttf(tmpdir)
1309*e1fe3e4aSElliott Hughes
1310*e1fe3e4aSElliott Hughes        assert (
1311*e1fe3e4aSElliott Hughes            woff2.main(
1312*e1fe3e4aSElliott Hughes                ["compress", "--no-glyf-transform", "--hmtx-transform", input_file]
1313*e1fe3e4aSElliott Hughes            )
1314*e1fe3e4aSElliott Hughes            is None
1315*e1fe3e4aSElliott Hughes        )
1316*e1fe3e4aSElliott Hughes
1317*e1fe3e4aSElliott Hughes        assert (tmpdir / "TestTTF-Regular.woff2").check(file=True)
1318*e1fe3e4aSElliott Hughes
1319*e1fe3e4aSElliott Hughes    def test_compress_output_file(self, tmpdir):
1320*e1fe3e4aSElliott Hughes        input_file = self.make_ttf(tmpdir)
1321*e1fe3e4aSElliott Hughes        output_file = tmpdir / "TestTTF.woff2"
1322*e1fe3e4aSElliott Hughes
1323*e1fe3e4aSElliott Hughes        assert woff2.main(["compress", "-o", str(output_file), str(input_file)]) is None
1324*e1fe3e4aSElliott Hughes
1325*e1fe3e4aSElliott Hughes        assert output_file.check(file=True)
1326*e1fe3e4aSElliott Hughes
1327*e1fe3e4aSElliott Hughes    def test_compress_otf(self, tmpdir):
1328*e1fe3e4aSElliott Hughes        ttFont = ttLib.TTFont(recalcBBoxes=False, recalcTimestamp=False)
1329*e1fe3e4aSElliott Hughes        ttFont.importXML(OTX)
1330*e1fe3e4aSElliott Hughes        input_file = str(tmpdir / "TestOTF-Regular.otf")
1331*e1fe3e4aSElliott Hughes        ttFont.save(input_file)
1332*e1fe3e4aSElliott Hughes
1333*e1fe3e4aSElliott Hughes        assert woff2.main(["compress", input_file]) is None
1334*e1fe3e4aSElliott Hughes
1335*e1fe3e4aSElliott Hughes        assert (tmpdir / "TestOTF-Regular.woff2").check(file=True)
1336*e1fe3e4aSElliott Hughes
1337*e1fe3e4aSElliott Hughes    def test_recompress_woff2_keeps_flavorData(self, tmpdir):
1338*e1fe3e4aSElliott Hughes        woff2_font = ttLib.TTFont(BytesIO(TT_WOFF2.getvalue()))
1339*e1fe3e4aSElliott Hughes        woff2_font.flavorData.privData = b"FOOBAR"
1340*e1fe3e4aSElliott Hughes        woff2_file = tmpdir / "TestTTF-Regular.woff2"
1341*e1fe3e4aSElliott Hughes        woff2_font.save(str(woff2_file))
1342*e1fe3e4aSElliott Hughes
1343*e1fe3e4aSElliott Hughes        assert woff2_font.flavorData.transformedTables == {"glyf", "loca"}
1344*e1fe3e4aSElliott Hughes
1345*e1fe3e4aSElliott Hughes        woff2.main(["compress", "--hmtx-transform", str(woff2_file)])
1346*e1fe3e4aSElliott Hughes
1347*e1fe3e4aSElliott Hughes        output_file = tmpdir / "TestTTF-Regular#1.woff2"
1348*e1fe3e4aSElliott Hughes        assert output_file.check(file=True)
1349*e1fe3e4aSElliott Hughes
1350*e1fe3e4aSElliott Hughes        new_woff2_font = ttLib.TTFont(str(output_file))
1351*e1fe3e4aSElliott Hughes
1352*e1fe3e4aSElliott Hughes        assert new_woff2_font.flavorData.transformedTables == {"glyf", "loca", "hmtx"}
1353*e1fe3e4aSElliott Hughes        assert new_woff2_font.flavorData.privData == b"FOOBAR"
1354*e1fe3e4aSElliott Hughes
1355*e1fe3e4aSElliott Hughes    def test_decompress_ttf(self, tmpdir):
1356*e1fe3e4aSElliott Hughes        input_file = tmpdir / "TestTTF-Regular.woff2"
1357*e1fe3e4aSElliott Hughes        input_file.write_binary(TT_WOFF2.getvalue())
1358*e1fe3e4aSElliott Hughes
1359*e1fe3e4aSElliott Hughes        assert woff2.main(["decompress", str(input_file)]) is None
1360*e1fe3e4aSElliott Hughes
1361*e1fe3e4aSElliott Hughes        assert (tmpdir / "TestTTF-Regular.ttf").check(file=True)
1362*e1fe3e4aSElliott Hughes
1363*e1fe3e4aSElliott Hughes    def test_decompress_otf(self, tmpdir):
1364*e1fe3e4aSElliott Hughes        input_file = tmpdir / "TestTTF-Regular.woff2"
1365*e1fe3e4aSElliott Hughes        input_file.write_binary(CFF_WOFF2.getvalue())
1366*e1fe3e4aSElliott Hughes
1367*e1fe3e4aSElliott Hughes        assert woff2.main(["decompress", str(input_file)]) is None
1368*e1fe3e4aSElliott Hughes
1369*e1fe3e4aSElliott Hughes        assert (tmpdir / "TestTTF-Regular.otf").check(file=True)
1370*e1fe3e4aSElliott Hughes
1371*e1fe3e4aSElliott Hughes    def test_decompress_output_file(self, tmpdir):
1372*e1fe3e4aSElliott Hughes        input_file = tmpdir / "TestTTF-Regular.woff2"
1373*e1fe3e4aSElliott Hughes        input_file.write_binary(TT_WOFF2.getvalue())
1374*e1fe3e4aSElliott Hughes        output_file = tmpdir / "TestTTF.ttf"
1375*e1fe3e4aSElliott Hughes
1376*e1fe3e4aSElliott Hughes        assert (
1377*e1fe3e4aSElliott Hughes            woff2.main(["decompress", "-o", str(output_file), str(input_file)]) is None
1378*e1fe3e4aSElliott Hughes        )
1379*e1fe3e4aSElliott Hughes
1380*e1fe3e4aSElliott Hughes        assert output_file.check(file=True)
1381*e1fe3e4aSElliott Hughes
1382*e1fe3e4aSElliott Hughes    def test_no_subcommand_show_help(self, capsys):
1383*e1fe3e4aSElliott Hughes        with pytest.raises(SystemExit):
1384*e1fe3e4aSElliott Hughes            woff2.main(["--help"])
1385*e1fe3e4aSElliott Hughes
1386*e1fe3e4aSElliott Hughes        captured = capsys.readouterr()
1387*e1fe3e4aSElliott Hughes        assert "usage: fonttools ttLib.woff2" in captured.out
1388*e1fe3e4aSElliott Hughes
1389*e1fe3e4aSElliott Hughes
1390*e1fe3e4aSElliott Hughesclass Base128Test(unittest.TestCase):
1391*e1fe3e4aSElliott Hughes    def test_unpackBase128(self):
1392*e1fe3e4aSElliott Hughes        self.assertEqual(unpackBase128(b"\x3f\x00\x00"), (63, b"\x00\x00"))
1393*e1fe3e4aSElliott Hughes        self.assertEqual(unpackBase128(b"\x8f\xff\xff\xff\x7f")[0], 4294967295)
1394*e1fe3e4aSElliott Hughes
1395*e1fe3e4aSElliott Hughes        self.assertRaisesRegex(
1396*e1fe3e4aSElliott Hughes            ttLib.TTLibError,
1397*e1fe3e4aSElliott Hughes            "UIntBase128 value must not start with leading zeros",
1398*e1fe3e4aSElliott Hughes            unpackBase128,
1399*e1fe3e4aSElliott Hughes            b"\x80\x80\x3f",
1400*e1fe3e4aSElliott Hughes        )
1401*e1fe3e4aSElliott Hughes
1402*e1fe3e4aSElliott Hughes        self.assertRaisesRegex(
1403*e1fe3e4aSElliott Hughes            ttLib.TTLibError,
1404*e1fe3e4aSElliott Hughes            "UIntBase128-encoded sequence is longer than 5 bytes",
1405*e1fe3e4aSElliott Hughes            unpackBase128,
1406*e1fe3e4aSElliott Hughes            b"\x8f\xff\xff\xff\xff\x7f",
1407*e1fe3e4aSElliott Hughes        )
1408*e1fe3e4aSElliott Hughes
1409*e1fe3e4aSElliott Hughes        self.assertRaisesRegex(
1410*e1fe3e4aSElliott Hughes            ttLib.TTLibError,
1411*e1fe3e4aSElliott Hughes            r"UIntBase128 value exceeds 2\*\*32-1",
1412*e1fe3e4aSElliott Hughes            unpackBase128,
1413*e1fe3e4aSElliott Hughes            b"\x90\x80\x80\x80\x00",
1414*e1fe3e4aSElliott Hughes        )
1415*e1fe3e4aSElliott Hughes
1416*e1fe3e4aSElliott Hughes        self.assertRaisesRegex(
1417*e1fe3e4aSElliott Hughes            ttLib.TTLibError,
1418*e1fe3e4aSElliott Hughes            "not enough data to unpack UIntBase128",
1419*e1fe3e4aSElliott Hughes            unpackBase128,
1420*e1fe3e4aSElliott Hughes            b"",
1421*e1fe3e4aSElliott Hughes        )
1422*e1fe3e4aSElliott Hughes
1423*e1fe3e4aSElliott Hughes    def test_base128Size(self):
1424*e1fe3e4aSElliott Hughes        self.assertEqual(base128Size(0), 1)
1425*e1fe3e4aSElliott Hughes        self.assertEqual(base128Size(24567), 3)
1426*e1fe3e4aSElliott Hughes        self.assertEqual(base128Size(2**32 - 1), 5)
1427*e1fe3e4aSElliott Hughes
1428*e1fe3e4aSElliott Hughes    def test_packBase128(self):
1429*e1fe3e4aSElliott Hughes        self.assertEqual(packBase128(63), b"\x3f")
1430*e1fe3e4aSElliott Hughes        self.assertEqual(packBase128(2**32 - 1), b"\x8f\xff\xff\xff\x7f")
1431*e1fe3e4aSElliott Hughes        self.assertRaisesRegex(
1432*e1fe3e4aSElliott Hughes            ttLib.TTLibError,
1433*e1fe3e4aSElliott Hughes            r"UIntBase128 format requires 0 <= integer <= 2\*\*32-1",
1434*e1fe3e4aSElliott Hughes            packBase128,
1435*e1fe3e4aSElliott Hughes            2**32 + 1,
1436*e1fe3e4aSElliott Hughes        )
1437*e1fe3e4aSElliott Hughes        self.assertRaisesRegex(
1438*e1fe3e4aSElliott Hughes            ttLib.TTLibError,
1439*e1fe3e4aSElliott Hughes            r"UIntBase128 format requires 0 <= integer <= 2\*\*32-1",
1440*e1fe3e4aSElliott Hughes            packBase128,
1441*e1fe3e4aSElliott Hughes            -1,
1442*e1fe3e4aSElliott Hughes        )
1443*e1fe3e4aSElliott Hughes
1444*e1fe3e4aSElliott Hughes
1445*e1fe3e4aSElliott Hughesclass UShort255Test(unittest.TestCase):
1446*e1fe3e4aSElliott Hughes    def test_unpack255UShort(self):
1447*e1fe3e4aSElliott Hughes        self.assertEqual(unpack255UShort(bytechr(252))[0], 252)
1448*e1fe3e4aSElliott Hughes        # some numbers (e.g. 506) can have multiple encodings
1449*e1fe3e4aSElliott Hughes        self.assertEqual(unpack255UShort(struct.pack(b"BB", 254, 0))[0], 506)
1450*e1fe3e4aSElliott Hughes        self.assertEqual(unpack255UShort(struct.pack(b"BB", 255, 253))[0], 506)
1451*e1fe3e4aSElliott Hughes        self.assertEqual(unpack255UShort(struct.pack(b"BBB", 253, 1, 250))[0], 506)
1452*e1fe3e4aSElliott Hughes
1453*e1fe3e4aSElliott Hughes        self.assertRaisesRegex(
1454*e1fe3e4aSElliott Hughes            ttLib.TTLibError,
1455*e1fe3e4aSElliott Hughes            "not enough data to unpack 255UInt16",
1456*e1fe3e4aSElliott Hughes            unpack255UShort,
1457*e1fe3e4aSElliott Hughes            struct.pack(b"BB", 253, 0),
1458*e1fe3e4aSElliott Hughes        )
1459*e1fe3e4aSElliott Hughes
1460*e1fe3e4aSElliott Hughes        self.assertRaisesRegex(
1461*e1fe3e4aSElliott Hughes            ttLib.TTLibError,
1462*e1fe3e4aSElliott Hughes            "not enough data to unpack 255UInt16",
1463*e1fe3e4aSElliott Hughes            unpack255UShort,
1464*e1fe3e4aSElliott Hughes            struct.pack(b"B", 254),
1465*e1fe3e4aSElliott Hughes        )
1466*e1fe3e4aSElliott Hughes
1467*e1fe3e4aSElliott Hughes        self.assertRaisesRegex(
1468*e1fe3e4aSElliott Hughes            ttLib.TTLibError,
1469*e1fe3e4aSElliott Hughes            "not enough data to unpack 255UInt16",
1470*e1fe3e4aSElliott Hughes            unpack255UShort,
1471*e1fe3e4aSElliott Hughes            struct.pack(b"B", 255),
1472*e1fe3e4aSElliott Hughes        )
1473*e1fe3e4aSElliott Hughes
1474*e1fe3e4aSElliott Hughes    def test_pack255UShort(self):
1475*e1fe3e4aSElliott Hughes        self.assertEqual(pack255UShort(252), b"\xfc")
1476*e1fe3e4aSElliott Hughes        self.assertEqual(pack255UShort(505), b"\xff\xfc")
1477*e1fe3e4aSElliott Hughes        self.assertEqual(pack255UShort(506), b"\xfe\x00")
1478*e1fe3e4aSElliott Hughes        self.assertEqual(pack255UShort(762), b"\xfd\x02\xfa")
1479*e1fe3e4aSElliott Hughes
1480*e1fe3e4aSElliott Hughes        self.assertRaisesRegex(
1481*e1fe3e4aSElliott Hughes            ttLib.TTLibError,
1482*e1fe3e4aSElliott Hughes            "255UInt16 format requires 0 <= integer <= 65535",
1483*e1fe3e4aSElliott Hughes            pack255UShort,
1484*e1fe3e4aSElliott Hughes            -1,
1485*e1fe3e4aSElliott Hughes        )
1486*e1fe3e4aSElliott Hughes
1487*e1fe3e4aSElliott Hughes        self.assertRaisesRegex(
1488*e1fe3e4aSElliott Hughes            ttLib.TTLibError,
1489*e1fe3e4aSElliott Hughes            "255UInt16 format requires 0 <= integer <= 65535",
1490*e1fe3e4aSElliott Hughes            pack255UShort,
1491*e1fe3e4aSElliott Hughes            0xFFFF + 1,
1492*e1fe3e4aSElliott Hughes        )
1493*e1fe3e4aSElliott Hughes
1494*e1fe3e4aSElliott Hughes
1495*e1fe3e4aSElliott Hughesclass VarCompositeTest(unittest.TestCase):
1496*e1fe3e4aSElliott Hughes    def test_var_composite(self):
1497*e1fe3e4aSElliott Hughes        input_path = os.path.join(data_dir, "varc-ac00-ac01.ttf")
1498*e1fe3e4aSElliott Hughes        ttf = ttLib.TTFont(input_path)
1499*e1fe3e4aSElliott Hughes        ttf.flavor = "woff2"
1500*e1fe3e4aSElliott Hughes        out = BytesIO()
1501*e1fe3e4aSElliott Hughes        ttf.save(out)
1502*e1fe3e4aSElliott Hughes
1503*e1fe3e4aSElliott Hughes        ttf = ttLib.TTFont(out)
1504*e1fe3e4aSElliott Hughes        ttf.flavor = None
1505*e1fe3e4aSElliott Hughes        out = BytesIO()
1506*e1fe3e4aSElliott Hughes        ttf.save(out)
1507*e1fe3e4aSElliott Hughes
1508*e1fe3e4aSElliott Hughes
1509*e1fe3e4aSElliott Hughesclass CubicTest(unittest.TestCase):
1510*e1fe3e4aSElliott Hughes    def test_cubic(self):
1511*e1fe3e4aSElliott Hughes        input_path = os.path.join(
1512*e1fe3e4aSElliott Hughes            data_dir, "..", "tables", "data", "NotoSans-VF-cubic.subset.ttf"
1513*e1fe3e4aSElliott Hughes        )
1514*e1fe3e4aSElliott Hughes        ttf = ttLib.TTFont(input_path)
1515*e1fe3e4aSElliott Hughes        pen1 = RecordingPen()
1516*e1fe3e4aSElliott Hughes        ttf.getGlyphSet()["a"].draw(pen1)
1517*e1fe3e4aSElliott Hughes        ttf.flavor = "woff2"
1518*e1fe3e4aSElliott Hughes        out = BytesIO()
1519*e1fe3e4aSElliott Hughes        ttf.save(out)
1520*e1fe3e4aSElliott Hughes
1521*e1fe3e4aSElliott Hughes        ttf = ttLib.TTFont(out)
1522*e1fe3e4aSElliott Hughes        ttf.flavor = None
1523*e1fe3e4aSElliott Hughes        pen2 = RecordingPen()
1524*e1fe3e4aSElliott Hughes        ttf.getGlyphSet()["a"].draw(pen2)
1525*e1fe3e4aSElliott Hughes        out = BytesIO()
1526*e1fe3e4aSElliott Hughes        ttf.save(out)
1527*e1fe3e4aSElliott Hughes
1528*e1fe3e4aSElliott Hughes        assert pen1.value == pen2.value
1529*e1fe3e4aSElliott Hughes
1530*e1fe3e4aSElliott Hughes
1531*e1fe3e4aSElliott Hughesif __name__ == "__main__":
1532*e1fe3e4aSElliott Hughes    import sys
1533*e1fe3e4aSElliott Hughes
1534*e1fe3e4aSElliott Hughes    sys.exit(unittest.main())
1535