| 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.876537e4, "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()) |