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( [ '' " 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.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())