• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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