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