1from fontTools.cffLib import PrivateDict 2from fontTools.cffLib.specializer import stringToProgram 3from fontTools.misc.testTools import getXML, parseXML 4from fontTools.misc.psCharStrings import ( 5 T2CharString, 6 encodeFloat, 7 encodeFixed, 8 read_fixed1616, 9 read_realNumber, 10) 11from fontTools.pens.recordingPen import RecordingPen 12import unittest 13 14 15def hexenc(s): 16 return ' '.join('%02x' % x for x in s) 17 18 19class T2CharStringTest(unittest.TestCase): 20 21 @classmethod 22 def stringToT2CharString(cls, string): 23 return T2CharString(program=stringToProgram(string), private=PrivateDict()) 24 25 def test_calcBounds_empty(self): 26 cs = self.stringToT2CharString("endchar") 27 bounds = cs.calcBounds(None) 28 self.assertEqual(bounds, None) 29 30 def test_calcBounds_line(self): 31 cs = self.stringToT2CharString("100 100 rmoveto 40 10 rlineto -20 50 rlineto endchar") 32 bounds = cs.calcBounds(None) 33 self.assertEqual(bounds, (100, 100, 140, 160)) 34 35 def test_calcBounds_curve(self): 36 cs = self.stringToT2CharString("100 100 rmoveto -50 -150 200 0 -50 150 rrcurveto endchar") 37 bounds = cs.calcBounds(None) 38 self.assertEqual(bounds, (91.90524980688875, -12.5, 208.09475019311125, 100)) 39 40 def test_charstring_bytecode_optimization(self): 41 cs = self.stringToT2CharString( 42 "100.0 100 rmoveto -50.0 -150 200.5 0.0 -50 150 rrcurveto endchar") 43 cs.isCFF2 = False 44 cs.private._isCFF2 = False 45 cs.compile() 46 cs.decompile() 47 self.assertEqual( 48 cs.program, [100, 100, 'rmoveto', -50, -150, 200.5, 0, -50, 150, 49 'rrcurveto', 'endchar']) 50 51 cs2 = self.stringToT2CharString( 52 "100.0 rmoveto -50.0 -150 200.5 0.0 -50 150 rrcurveto") 53 cs2.isCFF2 = True 54 cs2.private._isCFF2 = True 55 cs2.compile(isCFF2=True) 56 cs2.decompile() 57 self.assertEqual( 58 cs2.program, [100, 'rmoveto', -50, -150, 200.5, 0, -50, 150, 59 'rrcurveto']) 60 61 def test_encodeFloat(self): 62 testNums = [ 63 # value expected result 64 (-9.399999999999999, '1e e9 a4 ff'), # -9.4 65 (9.399999999999999999, '1e 9a 4f'), # 9.4 66 (456.8, '1e 45 6a 8f'), # 456.8 67 (0.0, '1e 0f'), # 0 68 (-0.0, '1e 0f'), # 0 69 (1.0, '1e 1f'), # 1 70 (-1.0, '1e e1 ff'), # -1 71 (98765.37e2, '1e 98 76 53 7f'), # 9876537 72 (1234567890.0, '1e 1a 23 45 67 9b 09 ff'), # 1234567890 73 (9.876537e-4, '1e a0 00 98 76 53 7f'), # 9.876537e-24 74 (9.876537e+4, '1e 98 76 5a 37 ff'), # 9.876537e+24 75 ] 76 77 for sample in testNums: 78 encoded_result = encodeFloat(sample[0]) 79 80 # check to see if we got the expected bytes 81 self.assertEqual(hexenc(encoded_result), sample[1]) 82 83 # check to see if we get the same value by decoding the data 84 decoded_result = read_realNumber( 85 None, 86 None, 87 encoded_result, 88 1, 89 ) 90 self.assertEqual(decoded_result[0], float('%.8g' % sample[0])) 91 # We limit to 8 digits of precision to match the implementation 92 # of encodeFloat. 93 94 def test_encode_decode_fixed(self): 95 testNums = [ 96 # value expected hex expected float 97 (-9.399999999999999, 'ff ff f6 99 9a', -9.3999939), 98 (-9.4, 'ff ff f6 99 9a', -9.3999939), 99 (9.399999999999999999, 'ff 00 09 66 66', 9.3999939), 100 (9.4, 'ff 00 09 66 66', 9.3999939), 101 (456.8, 'ff 01 c8 cc cd', 456.8000031), 102 (-456.8, 'ff fe 37 33 33', -456.8000031), 103 ] 104 105 for (value, expected_hex, expected_float) in testNums: 106 encoded_result = encodeFixed(value) 107 108 # check to see if we got the expected bytes 109 self.assertEqual(hexenc(encoded_result), expected_hex) 110 111 # check to see if we get the same value by decoding the data 112 decoded_result = read_fixed1616( 113 None, 114 None, 115 encoded_result, 116 1, 117 ) 118 self.assertAlmostEqual(decoded_result[0], expected_float) 119 120 def test_toXML(self): 121 program = [ 122 '107 53.4004 166.199 hstem', 123 '174.6 163.801 vstem', 124 '338.4 142.8 rmoveto', 125 '28 0 21.9 9 15.8 18 15.8 18 7.9 20.79959 0 23.6 rrcurveto', 126 'endchar' 127 ] 128 cs = self.stringToT2CharString(" ".join(program)) 129 130 self.assertEqual(getXML(cs.toXML), program) 131 132 def test_fromXML(self): 133 cs = T2CharString() 134 for name, attrs, content in parseXML( 135 [ 136 '<CharString name="period">' 137 ' 338.4 142.8 rmoveto', 138 ' 28 0 21.9 9 15.8 18 15.8 18 7.9 20.79959 0 23.6 rrcurveto', 139 ' endchar' 140 '</CharString>' 141 ] 142 ): 143 cs.fromXML(name, attrs, content) 144 145 expected_program = [ 146 338.3999939, 142.8000031, 'rmoveto', 147 28, 0, 21.8999939, 9, 15.8000031, 148 18, 15.8000031, 18, 7.8999939, 149 20.7995911, 0, 23.6000061, 'rrcurveto', 150 'endchar' 151 ] 152 153 self.assertEqual(len(cs.program), len(expected_program)) 154 for arg, expected_arg in zip(cs.program, expected_program): 155 if isinstance(arg, str): 156 self.assertIsInstance(expected_arg, str) 157 self.assertEqual(arg, expected_arg) 158 else: 159 self.assertNotIsInstance(expected_arg, str) 160 self.assertAlmostEqual(arg, expected_arg) 161 162 def test_pen_closePath(self): 163 # Test CFF2/T2 charstring: it does NOT end in "endchar" 164 # https://github.com/fonttools/fonttools/issues/2455 165 cs = self.stringToT2CharString("100 100 rmoveto -50 -150 200 0 -50 150 rrcurveto") 166 pen = RecordingPen() 167 cs.draw(pen) 168 self.assertEqual(pen.value[-1], ('closePath', ())) 169 170 171if __name__ == "__main__": 172 import sys 173 sys.exit(unittest.main()) 174