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