aboutsummaryrefslogtreecommitdiff
path: root/Tests/misc/psCharStrings_test.py
blob: 5e36fe73c2e6a9047f7ff760255363d73aa1b683 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
from fontTools.cffLib import PrivateDict
from fontTools.cffLib.specializer import stringToProgram
from fontTools.misc.testTools import getXML, parseXML
from fontTools.misc.psCharStrings import (
    T2CharString,
    encodeFloat,
    encodeFixed,
    read_fixed1616,
    read_realNumber,
)
from fontTools.pens.recordingPen import RecordingPen
import unittest


def hexenc(s):
    return ' '.join('%02x' % x for x in s)


class T2CharStringTest(unittest.TestCase):

    @classmethod
    def stringToT2CharString(cls, string):
        return T2CharString(program=stringToProgram(string), private=PrivateDict())

    def test_calcBounds_empty(self):
        cs = self.stringToT2CharString("endchar")
        bounds = cs.calcBounds(None)
        self.assertEqual(bounds, None)

    def test_calcBounds_line(self):
        cs = self.stringToT2CharString("100 100 rmoveto 40 10 rlineto -20 50 rlineto endchar")
        bounds = cs.calcBounds(None)
        self.assertEqual(bounds, (100, 100, 140, 160))

    def test_calcBounds_curve(self):
        cs = self.stringToT2CharString("100 100 rmoveto -50 -150 200 0 -50 150 rrcurveto endchar")
        bounds = cs.calcBounds(None)
        self.assertEqual(bounds, (91.90524980688875, -12.5, 208.09475019311125, 100))

    def test_charstring_bytecode_optimization(self):
        cs = self.stringToT2CharString(
            "100.0 100 rmoveto -50.0 -150 200.5 0.0 -50 150 rrcurveto endchar")
        cs.isCFF2 = False
        cs.private._isCFF2 = False
        cs.compile()
        cs.decompile()
        self.assertEqual(
            cs.program, [100, 100, 'rmoveto', -50, -150, 200.5, 0, -50, 150,
                         'rrcurveto', 'endchar'])

        cs2 = self.stringToT2CharString(
            "100.0 rmoveto -50.0 -150 200.5 0.0 -50 150 rrcurveto")
        cs2.isCFF2 = True
        cs2.private._isCFF2 = True
        cs2.compile(isCFF2=True)
        cs2.decompile()
        self.assertEqual(
            cs2.program, [100, 'rmoveto', -50, -150, 200.5, 0, -50, 150,
                          'rrcurveto'])

    def test_encodeFloat(self):
        testNums = [
            # value                expected result
            (-9.399999999999999,   '1e e9 a4 ff'),  # -9.4
            (9.399999999999999999, '1e 9a 4f'),  # 9.4
            (456.8,                '1e 45 6a 8f'),  # 456.8
            (0.0,                  '1e 0f'),  # 0
            (-0.0,                 '1e 0f'),  # 0
            (1.0,                  '1e 1f'),  # 1
            (-1.0,                 '1e e1 ff'),  # -1
            (98765.37e2,           '1e 98 76 53 7f'),  # 9876537
            (1234567890.0,         '1e 1a 23 45 67 9b 09 ff'),  # 1234567890
            (9.876537e-4,          '1e a0 00 98 76 53 7f'),  # 9.876537e-24
            (9.876537e+4,          '1e 98 76 5a 37 ff'),  # 9.876537e+24
        ]

        for sample in testNums:
            encoded_result = encodeFloat(sample[0])

            # check to see if we got the expected bytes
            self.assertEqual(hexenc(encoded_result), sample[1])

            # check to see if we get the same value by decoding the data
            decoded_result = read_realNumber(
                None,
                None,
                encoded_result,
                1,
            )
            self.assertEqual(decoded_result[0], float('%.8g' % sample[0]))
            # We limit to 8 digits of precision to match the implementation
            # of encodeFloat.

    def test_encode_decode_fixed(self):
        testNums = [
            # value                expected hex      expected float
            (-9.399999999999999,   'ff ff f6 99 9a', -9.3999939),
            (-9.4,                 'ff ff f6 99 9a', -9.3999939),
            (9.399999999999999999, 'ff 00 09 66 66', 9.3999939),
            (9.4,                  'ff 00 09 66 66', 9.3999939),
            (456.8,                'ff 01 c8 cc cd', 456.8000031),
            (-456.8,               'ff fe 37 33 33', -456.8000031),
        ]

        for (value, expected_hex, expected_float) in testNums:
            encoded_result = encodeFixed(value)

            # check to see if we got the expected bytes
            self.assertEqual(hexenc(encoded_result), expected_hex)

            # check to see if we get the same value by decoding the data
            decoded_result = read_fixed1616(
                None,
                None,
                encoded_result,
                1,
            )
            self.assertAlmostEqual(decoded_result[0], expected_float)

    def test_toXML(self):
        program = [
            '107 53.4004 166.199 hstem',
            '174.6 163.801 vstem',
            '338.4 142.8 rmoveto',
            '28 0 21.9 9 15.8 18 15.8 18 7.9 20.79959 0 23.6 rrcurveto',
            'endchar'
        ]
        cs = self.stringToT2CharString(" ".join(program))

        self.assertEqual(getXML(cs.toXML), program)

    def test_fromXML(self):
        cs = T2CharString()
        for name, attrs, content in parseXML(
            [
                '<CharString name="period">'
                '  338.4 142.8 rmoveto',
                '  28 0 21.9 9 15.8 18 15.8 18 7.9 20.79959 0 23.6 rrcurveto',
                '  endchar'
                '</CharString>'
            ]
        ):
            cs.fromXML(name, attrs, content)

        expected_program = [
            338.3999939, 142.8000031, 'rmoveto',
            28, 0, 21.8999939, 9, 15.8000031,
            18, 15.8000031, 18, 7.8999939,
            20.7995911, 0, 23.6000061, 'rrcurveto',
            'endchar'
        ]

        self.assertEqual(len(cs.program), len(expected_program))
        for arg, expected_arg in zip(cs.program, expected_program):
            if isinstance(arg, str):
                self.assertIsInstance(expected_arg, str)
                self.assertEqual(arg, expected_arg)
            else:
                self.assertNotIsInstance(expected_arg, str)
                self.assertAlmostEqual(arg, expected_arg)

    def test_pen_closePath(self):
        # Test CFF2/T2 charstring: it does NOT end in "endchar"
        # https://github.com/fonttools/fonttools/issues/2455
        cs = self.stringToT2CharString("100 100 rmoveto -50 -150 200 0 -50 150 rrcurveto")
        pen = RecordingPen()
        cs.draw(pen)
        self.assertEqual(pen.value[-1], ('closePath', ()))


if __name__ == "__main__":
    import sys
    sys.exit(unittest.main())