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