1from __future__ import print_function, division, absolute_import
2from fontTools.misc.py23 import *
3from fontTools.misc.fixedTools import otRound
4from fontTools.pens.ttGlyphPen import TTGlyphPen
5from fontTools.ttLib import TTFont, newTable, TTLibError
6from fontTools.ttLib.tables._g_l_y_f import GlyphCoordinates
7import sys
8import array
9import pytest
10import re
11import os
12import unittest
13
14
15class GlyphCoordinatesTest(object):
16
17    def test_translate(self):
18        g = GlyphCoordinates([(1,2)])
19        g.translate((.5,0))
20        assert g == GlyphCoordinates([(1.5,2.0)])
21
22    def test_scale(self):
23        g = GlyphCoordinates([(1,2)])
24        g.scale((.5,0))
25        assert g == GlyphCoordinates([(0.5,0.0)])
26
27    def test_transform(self):
28        g = GlyphCoordinates([(1,2)])
29        g.transform(((.5,0),(.2,.5)))
30        assert g[0] == GlyphCoordinates([(0.9,1.0)])[0]
31
32    def test__eq__(self):
33        g = GlyphCoordinates([(1,2)])
34        g2 = GlyphCoordinates([(1.0,2)])
35        g3 = GlyphCoordinates([(1.5,2)])
36        assert g == g2
37        assert not g == g3
38        assert not g2 == g3
39        assert not g == object()
40
41    def test__ne__(self):
42        g = GlyphCoordinates([(1,2)])
43        g2 = GlyphCoordinates([(1.0,2)])
44        g3 = GlyphCoordinates([(1.5,2)])
45        assert not (g != g2)
46        assert g != g3
47        assert g2 != g3
48        assert g != object()
49
50    def test__pos__(self):
51        g = GlyphCoordinates([(1,2)])
52        g2 = +g
53        assert g == g2
54
55    def test__neg__(self):
56        g = GlyphCoordinates([(1,2)])
57        g2 = -g
58        assert g2 == GlyphCoordinates([(-1, -2)])
59
60    @pytest.mark.skipif(sys.version_info[0] < 3,
61                        reason="__round___ requires Python 3")
62    def test__round__(self):
63        g = GlyphCoordinates([(-1.5,2)])
64        g2 = round(g)
65        assert g2 == GlyphCoordinates([(-1,2)])
66
67    def test__add__(self):
68        g1 = GlyphCoordinates([(1,2)])
69        g2 = GlyphCoordinates([(3,4)])
70        g3 = GlyphCoordinates([(4,6)])
71        assert g1 + g2 == g3
72        assert g1 + (1, 1) == GlyphCoordinates([(2,3)])
73        with pytest.raises(TypeError) as excinfo:
74            assert g1 + object()
75        assert 'unsupported operand' in str(excinfo.value)
76
77    def test__sub__(self):
78        g1 = GlyphCoordinates([(1,2)])
79        g2 = GlyphCoordinates([(3,4)])
80        g3 = GlyphCoordinates([(-2,-2)])
81        assert g1 - g2 == g3
82        assert g1 - (1, 1) == GlyphCoordinates([(0,1)])
83        with pytest.raises(TypeError) as excinfo:
84            assert g1 - object()
85        assert 'unsupported operand' in str(excinfo.value)
86
87    def test__rsub__(self):
88        g = GlyphCoordinates([(1,2)])
89        # other + (-self)
90        assert (1, 1) - g == GlyphCoordinates([(0,-1)])
91
92    def test__mul__(self):
93        g = GlyphCoordinates([(1,2)])
94        assert g * 3 == GlyphCoordinates([(3,6)])
95        assert g * (3,2) == GlyphCoordinates([(3,4)])
96        assert g * (1,1) == g
97        with pytest.raises(TypeError) as excinfo:
98            assert g * object()
99        assert 'unsupported operand' in str(excinfo.value)
100
101    def test__truediv__(self):
102        g = GlyphCoordinates([(1,2)])
103        assert g / 2 == GlyphCoordinates([(.5,1)])
104        assert g / (1, 2) == GlyphCoordinates([(1,1)])
105        assert g / (1, 1) == g
106        with pytest.raises(TypeError) as excinfo:
107            assert g / object()
108        assert 'unsupported operand' in str(excinfo.value)
109
110    def test__iadd__(self):
111        g = GlyphCoordinates([(1,2)])
112        g += (.5,0)
113        assert g == GlyphCoordinates([(1.5, 2.0)])
114        g2 = GlyphCoordinates([(3,4)])
115        g += g2
116        assert g == GlyphCoordinates([(4.5, 6.0)])
117
118    def test__isub__(self):
119        g = GlyphCoordinates([(1,2)])
120        g -= (.5, 0)
121        assert g == GlyphCoordinates([(0.5, 2.0)])
122        g2 = GlyphCoordinates([(3,4)])
123        g -= g2
124        assert g == GlyphCoordinates([(-2.5, -2.0)])
125
126    def __test__imul__(self):
127        g = GlyphCoordinates([(1,2)])
128        g *= (2,.5)
129        g *= 2
130        assert g == GlyphCoordinates([(4.0, 2.0)])
131        g = GlyphCoordinates([(1,2)])
132        g *= 2
133        assert g == GlyphCoordinates([(2, 4)])
134
135    def test__itruediv__(self):
136        g = GlyphCoordinates([(1,3)])
137        g /= (.5,1.5)
138        g /= 2
139        assert g == GlyphCoordinates([(1.0, 1.0)])
140
141    def test__bool__(self):
142        g = GlyphCoordinates([])
143        assert bool(g) == False
144        g = GlyphCoordinates([(0,0), (0.,0)])
145        assert bool(g) == True
146        g = GlyphCoordinates([(0,0), (1,0)])
147        assert bool(g) == True
148        g = GlyphCoordinates([(0,.5), (0,0)])
149        assert bool(g) == True
150
151    def test_double_precision_float(self):
152        # https://github.com/fonttools/fonttools/issues/963
153        afloat = 242.50000000000003
154        g = GlyphCoordinates([(afloat, 0)])
155        g.toInt()
156        # this would return 242 if the internal array.array typecode is 'f',
157        # since the Python float is truncated to a C float.
158        # when using typecode 'd' it should return the correct value 243
159        assert g[0][0] == otRound(afloat)
160
161    def test__checkFloat_overflow(self):
162        g = GlyphCoordinates([(1, 1)], typecode="h")
163        g.append((0x8000, 0))
164        assert g.array.typecode == "d"
165        assert g.array == array.array("d", [1.0, 1.0, 32768.0, 0.0])
166
167
168CURR_DIR = os.path.abspath(os.path.dirname(os.path.realpath(__file__)))
169DATA_DIR = os.path.join(CURR_DIR, 'data')
170
171GLYF_TTX = os.path.join(DATA_DIR, "_g_l_y_f_outline_flag_bit6.ttx")
172GLYF_BIN = os.path.join(DATA_DIR, "_g_l_y_f_outline_flag_bit6.glyf.bin")
173HEAD_BIN = os.path.join(DATA_DIR, "_g_l_y_f_outline_flag_bit6.head.bin")
174LOCA_BIN = os.path.join(DATA_DIR, "_g_l_y_f_outline_flag_bit6.loca.bin")
175MAXP_BIN = os.path.join(DATA_DIR, "_g_l_y_f_outline_flag_bit6.maxp.bin")
176
177
178def strip_ttLibVersion(string):
179    return re.sub(' ttLibVersion=".*"', '', string)
180
181
182class glyfTableTest(unittest.TestCase):
183
184    def __init__(self, methodName):
185        unittest.TestCase.__init__(self, methodName)
186        # Python 3 renamed assertRaisesRegexp to assertRaisesRegex,
187        # and fires deprecation warnings if a program uses the old name.
188        if not hasattr(self, "assertRaisesRegex"):
189            self.assertRaisesRegex = self.assertRaisesRegexp
190
191    @classmethod
192    def setUpClass(cls):
193        with open(GLYF_BIN, 'rb') as f:
194            cls.glyfData = f.read()
195        with open(HEAD_BIN, 'rb') as f:
196            cls.headData = f.read()
197        with open(LOCA_BIN, 'rb') as f:
198            cls.locaData = f.read()
199        with open(MAXP_BIN, 'rb') as f:
200            cls.maxpData = f.read()
201        with open(GLYF_TTX, 'r') as f:
202            cls.glyfXML = strip_ttLibVersion(f.read()).splitlines()
203
204    def test_toXML(self):
205        font = TTFont(sfntVersion="\x00\x01\x00\x00")
206        glyfTable = font['glyf'] = newTable('glyf')
207        font['head'] = newTable('head')
208        font['loca'] = newTable('loca')
209        font['maxp'] = newTable('maxp')
210        font['maxp'].decompile(self.maxpData, font)
211        font['head'].decompile(self.headData, font)
212        font['loca'].decompile(self.locaData, font)
213        glyfTable.decompile(self.glyfData, font)
214        out = UnicodeIO()
215        font.saveXML(out)
216        glyfXML = strip_ttLibVersion(out.getvalue()).splitlines()
217        self.assertEqual(glyfXML, self.glyfXML)
218
219    def test_fromXML(self):
220        font = TTFont(sfntVersion="\x00\x01\x00\x00")
221        font.importXML(GLYF_TTX)
222        glyfTable = font['glyf']
223        glyfData = glyfTable.compile(font)
224        self.assertEqual(glyfData, self.glyfData)
225
226    def test_recursiveComponent(self):
227        glyphSet = {}
228        pen_dummy = TTGlyphPen(glyphSet)
229        glyph_dummy = pen_dummy.glyph()
230        glyphSet["A"] = glyph_dummy
231        glyphSet["B"] = glyph_dummy
232        pen_A = TTGlyphPen(glyphSet)
233        pen_A.addComponent("B", (1, 0, 0, 1, 0, 0))
234        pen_B = TTGlyphPen(glyphSet)
235        pen_B.addComponent("A", (1, 0, 0, 1, 0, 0))
236        glyph_A = pen_A.glyph()
237        glyph_B = pen_B.glyph()
238        glyphSet["A"] = glyph_A
239        glyphSet["B"] = glyph_B
240        with self.assertRaisesRegex(TTLibError, "glyph '.' contains a recursive component reference"):
241            glyph_A.getCoordinates(glyphSet)
242
243
244if __name__ == "__main__":
245    import sys
246    sys.exit(unittest.main())
247