1import unittest 2import os 3import sys 4from fontTools import t1Lib 5from fontTools.pens.basePen import NullPen 6from fontTools.misc.psCharStrings import T1CharString 7import random 8 9 10CWD = os.path.abspath(os.path.dirname(__file__)) 11DATADIR = os.path.join(CWD, "data") 12# I used `tx` to convert PFA to LWFN (stored in the data fork) 13LWFN = os.path.join(DATADIR, "TestT1-Regular.lwfn") 14PFA = os.path.join(DATADIR, "TestT1-Regular.pfa") 15PFB = os.path.join(DATADIR, "TestT1-Regular.pfb") 16WEIRD_ZEROS = os.path.join(DATADIR, "TestT1-weird-zeros.pfa") 17# ellipsis is hinted with 55 131 296 131 537 131 vstem3 0 122 hstem 18ELLIPSIS_HINTED = os.path.join(DATADIR, "TestT1-ellipsis-hinted.pfa") 19 20 21class FindEncryptedChunksTest(unittest.TestCase): 22 def test_findEncryptedChunks(self): 23 with open(PFA, "rb") as f: 24 data = f.read() 25 chunks = t1Lib.findEncryptedChunks(data) 26 self.assertEqual(len(chunks), 3) 27 self.assertFalse(chunks[0][0]) 28 # the second chunk is encrypted 29 self.assertTrue(chunks[1][0]) 30 self.assertFalse(chunks[2][0]) 31 32 def test_findEncryptedChunks_weird_zeros(self): 33 with open(WEIRD_ZEROS, "rb") as f: 34 data = f.read() 35 36 # Just assert that this doesn't raise any exception for not finding the 37 # end of eexec 38 t1Lib.findEncryptedChunks(data) 39 40 41class DecryptType1Test(unittest.TestCase): 42 def test_decryptType1(self): 43 with open(PFA, "rb") as f: 44 data = f.read() 45 decrypted = t1Lib.decryptType1(data) 46 self.assertNotEqual(decrypted, data) 47 48 49class ReadWriteTest(unittest.TestCase): 50 def test_read_pfa_write_pfb(self): 51 font = t1Lib.T1Font(PFA) 52 data = self.write(font, "PFB") 53 self.assertEqual(font.getData(), data) 54 55 def test_read_and_parse_pfa_write_pfb(self): 56 font = t1Lib.T1Font(PFA) 57 font.parse() 58 saved_font = self.write(font, "PFB", dohex=False, doparse=True) 59 self.assertTrue(same_dicts(font.font, saved_font)) 60 61 def test_read_pfb_write_pfa(self): 62 font = t1Lib.T1Font(PFB) 63 # 'OTHER' == 'PFA' 64 data = self.write(font, "OTHER", dohex=True) 65 self.assertEqual(font.getData(), data) 66 67 def test_read_and_parse_pfb_write_pfa(self): 68 font = t1Lib.T1Font(PFB) 69 font.parse() 70 # 'OTHER' == 'PFA' 71 saved_font = self.write(font, "OTHER", dohex=True, doparse=True) 72 self.assertTrue(same_dicts(font.font, saved_font)) 73 74 def test_read_with_path(self): 75 import pathlib 76 77 font = t1Lib.T1Font(pathlib.Path(PFB)) 78 79 @staticmethod 80 def write(font, outtype, dohex=False, doparse=False): 81 temp = os.path.join(DATADIR, "temp." + outtype.lower()) 82 try: 83 font.saveAs(temp, outtype, dohex=dohex) 84 newfont = t1Lib.T1Font(temp) 85 if doparse: 86 newfont.parse() 87 data = newfont.font 88 else: 89 data = newfont.getData() 90 finally: 91 if os.path.exists(temp): 92 os.remove(temp) 93 return data 94 95 96class T1FontTest(unittest.TestCase): 97 def test_parse_lwfn(self): 98 # the extended attrs are lost on git so we can't auto-detect 'LWFN' 99 font = t1Lib.T1Font(LWFN, kind="LWFN") 100 font.parse() 101 self.assertEqual(font["FontName"], "TestT1-Regular") 102 self.assertTrue("Subrs" in font["Private"]) 103 104 def test_parse_pfa(self): 105 font = t1Lib.T1Font(PFA) 106 font.parse() 107 self.assertEqual(font["FontName"], "TestT1-Regular") 108 self.assertTrue("Subrs" in font["Private"]) 109 110 def test_parse_pfb(self): 111 font = t1Lib.T1Font(PFB) 112 font.parse() 113 self.assertEqual(font["FontName"], "TestT1-Regular") 114 self.assertTrue("Subrs" in font["Private"]) 115 116 def test_getGlyphSet(self): 117 font = t1Lib.T1Font(PFA) 118 glyphs = font.getGlyphSet() 119 i = random.randrange(len(glyphs)) 120 aglyph = list(glyphs.values())[i] 121 self.assertTrue(hasattr(aglyph, "draw")) 122 self.assertFalse(hasattr(aglyph, "width")) 123 aglyph.draw(NullPen()) 124 self.assertTrue(hasattr(aglyph, "width")) 125 126 127class EditTest(unittest.TestCase): 128 def test_edit_pfa(self): 129 font = t1Lib.T1Font(PFA) 130 ellipsis = font.getGlyphSet()["ellipsis"] 131 ellipsis.decompile() 132 program = [] 133 for v in ellipsis.program: 134 try: 135 program.append(int(v)) 136 except: 137 program.append(v) 138 if v == "hsbw": 139 hints = [55, 131, 296, 131, 537, 131, "vstem3", 0, 122, "hstem"] 140 program.extend(hints) 141 ellipsis.program = program 142 # 'OTHER' == 'PFA' 143 saved_font = self.write(font, "OTHER", dohex=True, doparse=True) 144 hinted_font = t1Lib.T1Font(ELLIPSIS_HINTED) 145 hinted_font.parse() 146 self.assertTrue(same_dicts(hinted_font.font, saved_font)) 147 148 @staticmethod 149 def write(font, outtype, dohex=False, doparse=False): 150 temp = os.path.join(DATADIR, "temp." + outtype.lower()) 151 try: 152 font.saveAs(temp, outtype, dohex=dohex) 153 newfont = t1Lib.T1Font(temp) 154 if doparse: 155 newfont.parse() 156 data = newfont.font 157 else: 158 data = newfont.getData() 159 finally: 160 if os.path.exists(temp): 161 os.remove(temp) 162 return data 163 164 165def same_dicts(dict1, dict2): 166 if dict1.keys() != dict2.keys(): 167 return False 168 for key, value in dict1.items(): 169 if isinstance(value, dict): 170 if not same_dicts(value, dict2[key]): 171 return False 172 elif isinstance(value, list): 173 if len(value) != len(dict2[key]): 174 return False 175 for elem1, elem2 in zip(value, dict2[key]): 176 if isinstance(elem1, T1CharString): 177 elem1.compile() 178 elem2.compile() 179 if elem1.bytecode != elem2.bytecode: 180 return False 181 else: 182 if elem1 != elem2: 183 return False 184 elif isinstance(value, T1CharString): 185 value.compile() 186 dict2[key].compile() 187 if value.bytecode != dict2[key].bytecode: 188 return False 189 else: 190 if value != dict2[key]: 191 return False 192 return True 193 194 195if __name__ == "__main__": 196 import sys 197 198 sys.exit(unittest.main()) 199