• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1from fontTools.misc.fixedTools import otRound
2from fontTools.misc.testTools import getXML, parseXML
3from fontTools.pens.ttGlyphPen import TTGlyphPen
4from fontTools.pens.recordingPen import RecordingPen, RecordingPointPen
5from fontTools.pens.pointPen import PointToSegmentPen
6from fontTools.ttLib import TTFont, newTable, TTLibError
7from fontTools.ttLib.tables._g_l_y_f import (
8    Glyph,
9    GlyphCoordinates,
10    GlyphComponent,
11    ARGS_ARE_XY_VALUES,
12    SCALED_COMPONENT_OFFSET,
13    UNSCALED_COMPONENT_OFFSET,
14    WE_HAVE_A_SCALE,
15    WE_HAVE_A_TWO_BY_TWO,
16    WE_HAVE_AN_X_AND_Y_SCALE,
17)
18from fontTools.ttLib.tables import ttProgram
19import sys
20import array
21from io import StringIO
22import itertools
23import pytest
24import re
25import os
26import unittest
27
28
29class GlyphCoordinatesTest(object):
30
31    def test_translate(self):
32        g = GlyphCoordinates([(1,2)])
33        g.translate((.5,0))
34        assert g == GlyphCoordinates([(1.5,2.0)])
35
36    def test_scale(self):
37        g = GlyphCoordinates([(1,2)])
38        g.scale((.5,0))
39        assert g == GlyphCoordinates([(0.5,0.0)])
40
41    def test_transform(self):
42        g = GlyphCoordinates([(1,2)])
43        g.transform(((.5,0),(.2,.5)))
44        assert g[0] == GlyphCoordinates([(0.9,1.0)])[0]
45
46    def test__eq__(self):
47        g = GlyphCoordinates([(1,2)])
48        g2 = GlyphCoordinates([(1.0,2)])
49        g3 = GlyphCoordinates([(1.5,2)])
50        assert g == g2
51        assert not g == g3
52        assert not g2 == g3
53        assert not g == object()
54
55    def test__ne__(self):
56        g = GlyphCoordinates([(1,2)])
57        g2 = GlyphCoordinates([(1.0,2)])
58        g3 = GlyphCoordinates([(1.5,2)])
59        assert not (g != g2)
60        assert g != g3
61        assert g2 != g3
62        assert g != object()
63
64    def test__pos__(self):
65        g = GlyphCoordinates([(1,2)])
66        g2 = +g
67        assert g == g2
68
69    def test__neg__(self):
70        g = GlyphCoordinates([(1,2)])
71        g2 = -g
72        assert g2 == GlyphCoordinates([(-1, -2)])
73
74    @pytest.mark.skipif(sys.version_info[0] < 3,
75                        reason="__round___ requires Python 3")
76    def test__round__(self):
77        g = GlyphCoordinates([(-1.5,2)])
78        g2 = round(g)
79        assert g2 == GlyphCoordinates([(-1,2)])
80
81    def test__add__(self):
82        g1 = GlyphCoordinates([(1,2)])
83        g2 = GlyphCoordinates([(3,4)])
84        g3 = GlyphCoordinates([(4,6)])
85        assert g1 + g2 == g3
86        assert g1 + (1, 1) == GlyphCoordinates([(2,3)])
87        with pytest.raises(TypeError) as excinfo:
88            assert g1 + object()
89        assert 'unsupported operand' in str(excinfo.value)
90
91    def test__sub__(self):
92        g1 = GlyphCoordinates([(1,2)])
93        g2 = GlyphCoordinates([(3,4)])
94        g3 = GlyphCoordinates([(-2,-2)])
95        assert g1 - g2 == g3
96        assert g1 - (1, 1) == GlyphCoordinates([(0,1)])
97        with pytest.raises(TypeError) as excinfo:
98            assert g1 - object()
99        assert 'unsupported operand' in str(excinfo.value)
100
101    def test__rsub__(self):
102        g = GlyphCoordinates([(1,2)])
103        # other + (-self)
104        assert (1, 1) - g == GlyphCoordinates([(0,-1)])
105
106    def test__mul__(self):
107        g = GlyphCoordinates([(1,2)])
108        assert g * 3 == GlyphCoordinates([(3,6)])
109        assert g * (3,2) == GlyphCoordinates([(3,4)])
110        assert g * (1,1) == g
111        with pytest.raises(TypeError) as excinfo:
112            assert g * object()
113        assert 'unsupported operand' in str(excinfo.value)
114
115    def test__truediv__(self):
116        g = GlyphCoordinates([(1,2)])
117        assert g / 2 == GlyphCoordinates([(.5,1)])
118        assert g / (1, 2) == GlyphCoordinates([(1,1)])
119        assert g / (1, 1) == g
120        with pytest.raises(TypeError) as excinfo:
121            assert g / object()
122        assert 'unsupported operand' in str(excinfo.value)
123
124    def test__iadd__(self):
125        g = GlyphCoordinates([(1,2)])
126        g += (.5,0)
127        assert g == GlyphCoordinates([(1.5, 2.0)])
128        g2 = GlyphCoordinates([(3,4)])
129        g += g2
130        assert g == GlyphCoordinates([(4.5, 6.0)])
131
132    def test__isub__(self):
133        g = GlyphCoordinates([(1,2)])
134        g -= (.5, 0)
135        assert g == GlyphCoordinates([(0.5, 2.0)])
136        g2 = GlyphCoordinates([(3,4)])
137        g -= g2
138        assert g == GlyphCoordinates([(-2.5, -2.0)])
139
140    def __test__imul__(self):
141        g = GlyphCoordinates([(1,2)])
142        g *= (2,.5)
143        g *= 2
144        assert g == GlyphCoordinates([(4.0, 2.0)])
145        g = GlyphCoordinates([(1,2)])
146        g *= 2
147        assert g == GlyphCoordinates([(2, 4)])
148
149    def test__itruediv__(self):
150        g = GlyphCoordinates([(1,3)])
151        g /= (.5,1.5)
152        g /= 2
153        assert g == GlyphCoordinates([(1.0, 1.0)])
154
155    def test__bool__(self):
156        g = GlyphCoordinates([])
157        assert bool(g) == False
158        g = GlyphCoordinates([(0,0), (0.,0)])
159        assert bool(g) == True
160        g = GlyphCoordinates([(0,0), (1,0)])
161        assert bool(g) == True
162        g = GlyphCoordinates([(0,.5), (0,0)])
163        assert bool(g) == True
164
165    def test_double_precision_float(self):
166        # https://github.com/fonttools/fonttools/issues/963
167        afloat = 242.50000000000003
168        g = GlyphCoordinates([(afloat, 0)])
169        g.toInt()
170        # this would return 242 if the internal array.array typecode is 'f',
171        # since the Python float is truncated to a C float.
172        # when using typecode 'd' it should return the correct value 243
173        assert g[0][0] == otRound(afloat)
174
175    def test__checkFloat_overflow(self):
176        g = GlyphCoordinates([(1, 1)])
177        g.append((0x8000, 0))
178        assert list(g.array) == [1.0, 1.0, 32768.0, 0.0]
179
180
181CURR_DIR = os.path.abspath(os.path.dirname(os.path.realpath(__file__)))
182DATA_DIR = os.path.join(CURR_DIR, 'data')
183
184GLYF_TTX = os.path.join(DATA_DIR, "_g_l_y_f_outline_flag_bit6.ttx")
185GLYF_BIN = os.path.join(DATA_DIR, "_g_l_y_f_outline_flag_bit6.glyf.bin")
186HEAD_BIN = os.path.join(DATA_DIR, "_g_l_y_f_outline_flag_bit6.head.bin")
187LOCA_BIN = os.path.join(DATA_DIR, "_g_l_y_f_outline_flag_bit6.loca.bin")
188MAXP_BIN = os.path.join(DATA_DIR, "_g_l_y_f_outline_flag_bit6.maxp.bin")
189
190
191def strip_ttLibVersion(string):
192    return re.sub(' ttLibVersion=".*"', '', string)
193
194
195class GlyfTableTest(unittest.TestCase):
196
197    def __init__(self, methodName):
198        unittest.TestCase.__init__(self, methodName)
199        # Python 3 renamed assertRaisesRegexp to assertRaisesRegex,
200        # and fires deprecation warnings if a program uses the old name.
201        if not hasattr(self, "assertRaisesRegex"):
202            self.assertRaisesRegex = self.assertRaisesRegexp
203
204    @classmethod
205    def setUpClass(cls):
206        with open(GLYF_BIN, 'rb') as f:
207            cls.glyfData = f.read()
208        with open(HEAD_BIN, 'rb') as f:
209            cls.headData = f.read()
210        with open(LOCA_BIN, 'rb') as f:
211            cls.locaData = f.read()
212        with open(MAXP_BIN, 'rb') as f:
213            cls.maxpData = f.read()
214        with open(GLYF_TTX, 'r') as f:
215            cls.glyfXML = strip_ttLibVersion(f.read()).splitlines()
216
217    def test_toXML(self):
218        font = TTFont(sfntVersion="\x00\x01\x00\x00")
219        glyfTable = font['glyf'] = newTable('glyf')
220        font['head'] = newTable('head')
221        font['loca'] = newTable('loca')
222        font['maxp'] = newTable('maxp')
223        font['maxp'].decompile(self.maxpData, font)
224        font['head'].decompile(self.headData, font)
225        font['loca'].decompile(self.locaData, font)
226        glyfTable.decompile(self.glyfData, font)
227        out = StringIO()
228        font.saveXML(out)
229        glyfXML = strip_ttLibVersion(out.getvalue()).splitlines()
230        self.assertEqual(glyfXML, self.glyfXML)
231
232    def test_fromXML(self):
233        font = TTFont(sfntVersion="\x00\x01\x00\x00")
234        font.importXML(GLYF_TTX)
235        glyfTable = font['glyf']
236        glyfData = glyfTable.compile(font)
237        self.assertEqual(glyfData, self.glyfData)
238
239    def test_recursiveComponent(self):
240        glyphSet = {}
241        pen_dummy = TTGlyphPen(glyphSet)
242        glyph_dummy = pen_dummy.glyph()
243        glyphSet["A"] = glyph_dummy
244        glyphSet["B"] = glyph_dummy
245        pen_A = TTGlyphPen(glyphSet)
246        pen_A.addComponent("B", (1, 0, 0, 1, 0, 0))
247        pen_B = TTGlyphPen(glyphSet)
248        pen_B.addComponent("A", (1, 0, 0, 1, 0, 0))
249        glyph_A = pen_A.glyph()
250        glyph_B = pen_B.glyph()
251        glyphSet["A"] = glyph_A
252        glyphSet["B"] = glyph_B
253        with self.assertRaisesRegex(TTLibError, "glyph '.' contains a recursive component reference"):
254            glyph_A.getCoordinates(glyphSet)
255
256    def test_trim_remove_hinting_composite_glyph(self):
257        glyphSet = {"dummy": TTGlyphPen(None).glyph()}
258
259        pen = TTGlyphPen(glyphSet)
260        pen.addComponent("dummy", (1, 0, 0, 1, 0, 0))
261        composite = pen.glyph()
262        p = ttProgram.Program()
263        p.fromAssembly(['SVTCA[0]'])
264        composite.program = p
265        glyphSet["composite"] = composite
266
267        glyfTable = newTable("glyf")
268        glyfTable.glyphs = glyphSet
269        glyfTable.glyphOrder = sorted(glyphSet)
270
271        composite.compact(glyfTable)
272
273        self.assertTrue(hasattr(composite, "data"))
274
275        # remove hinting from the compacted composite glyph, without expanding it
276        composite.trim(remove_hinting=True)
277
278        # check that, after expanding the glyph, we have no instructions
279        composite.expand(glyfTable)
280        self.assertFalse(hasattr(composite, "program"))
281
282        # now remove hinting from expanded composite glyph
283        composite.program = p
284        composite.trim(remove_hinting=True)
285
286        # check we have no instructions
287        self.assertFalse(hasattr(composite, "program"))
288
289        composite.compact(glyfTable)
290
291    def test_bit6_draw_to_pen_issue1771(self):
292        # https://github.com/fonttools/fonttools/issues/1771
293        font = TTFont(sfntVersion="\x00\x01\x00\x00")
294        # glyph00003 contains a bit 6 flag on the first point,
295        # which triggered the issue
296        font.importXML(GLYF_TTX)
297        glyfTable = font['glyf']
298        pen = RecordingPen()
299        glyfTable["glyph00003"].draw(pen, glyfTable=glyfTable)
300        expected = [('moveTo', ((501, 1430),)),
301                    ('lineTo', ((683, 1430),)),
302                    ('lineTo', ((1172, 0),)),
303                    ('lineTo', ((983, 0),)),
304                    ('lineTo', ((591, 1193),)),
305                    ('lineTo', ((199, 0),)),
306                    ('lineTo', ((12, 0),)),
307                    ('closePath', ()),
308                    ('moveTo', ((249, 514),)),
309                    ('lineTo', ((935, 514),)),
310                    ('lineTo', ((935, 352),)),
311                    ('lineTo', ((249, 352),)),
312                    ('closePath', ())]
313        self.assertEqual(pen.value, expected)
314
315    def test_bit6_draw_to_pointpen(self):
316        # https://github.com/fonttools/fonttools/issues/1771
317        font = TTFont(sfntVersion="\x00\x01\x00\x00")
318        # glyph00003 contains a bit 6 flag on the first point
319        # which triggered the issue
320        font.importXML(GLYF_TTX)
321        glyfTable = font['glyf']
322        pen = RecordingPointPen()
323        glyfTable["glyph00003"].drawPoints(pen, glyfTable=glyfTable)
324        expected = [
325            ('beginPath', (), {}),
326            ('addPoint', ((501, 1430), 'line', False, None), {}),
327            ('addPoint', ((683, 1430), 'line', False, None), {}),
328            ('addPoint', ((1172, 0), 'line', False, None), {}),
329            ('addPoint', ((983, 0), 'line', False, None), {}),
330        ]
331        self.assertEqual(pen.value[:len(expected)], expected)
332
333    def test_draw_vs_drawpoints(self):
334        font = TTFont(sfntVersion="\x00\x01\x00\x00")
335        font.importXML(GLYF_TTX)
336        glyfTable = font['glyf']
337        pen1 = RecordingPen()
338        pen2 = RecordingPen()
339        glyfTable["glyph00003"].draw(pen1, glyfTable)
340        glyfTable["glyph00003"].drawPoints(PointToSegmentPen(pen2), glyfTable)
341        self.assertEqual(pen1.value, pen2.value)
342
343    def test_compile_empty_table(self):
344        font = TTFont(sfntVersion="\x00\x01\x00\x00")
345        font.importXML(GLYF_TTX)
346        glyfTable = font['glyf']
347        # set all glyphs to zero contours
348        glyfTable.glyphs = {glyphName: Glyph() for glyphName in font.getGlyphOrder()}
349        glyfData = glyfTable.compile(font)
350        self.assertEqual(glyfData, b"\x00")
351        self.assertEqual(list(font["loca"]), [0] * (font["maxp"].numGlyphs+1))
352
353    def test_decompile_empty_table(self):
354        font = TTFont()
355        glyphNames = [".notdef", "space"]
356        font.setGlyphOrder(glyphNames)
357        font["loca"] = newTable("loca")
358        font["loca"].locations = [0] * (len(glyphNames) + 1)
359        font["glyf"] = newTable("glyf")
360        font["glyf"].decompile(b"\x00", font)
361        self.assertEqual(len(font["glyf"]), 2)
362        self.assertEqual(font["glyf"][".notdef"].numberOfContours, 0)
363        self.assertEqual(font["glyf"]["space"].numberOfContours, 0)
364
365    def test_getPhantomPoints(self):
366        # https://github.com/fonttools/fonttools/issues/2295
367        font = TTFont()
368        glyphNames = [".notdef"]
369        font.setGlyphOrder(glyphNames)
370        font["loca"] = newTable("loca")
371        font["loca"].locations = [0] * (len(glyphNames) + 1)
372        font["glyf"] = newTable("glyf")
373        font["glyf"].decompile(b"\x00", font)
374        font["hmtx"] = newTable("hmtx")
375        font["hmtx"].metrics = {".notdef": (100,0)}
376        font["head"] = newTable("head")
377        font["head"].unitsPerEm = 1000
378        self.assertEqual(
379            font["glyf"].getPhantomPoints(".notdef", font, 0),
380            [(0, 0), (100, 0), (0, 0), (0, -1000)]
381        )
382
383class GlyphTest:
384
385    def test_getCoordinates(self):
386        glyphSet = {}
387        pen = TTGlyphPen(glyphSet)
388        pen.moveTo((0, 0))
389        pen.lineTo((100, 0))
390        pen.lineTo((100, 100))
391        pen.lineTo((0, 100))
392        pen.closePath()
393        # simple contour glyph
394        glyphSet["a"] = a = pen.glyph()
395
396        assert a.getCoordinates(glyphSet) == (
397            GlyphCoordinates([(0, 0), (100, 0), (100, 100), (0, 100)]),
398            [3],
399            array.array("B", [1, 1, 1, 1]),
400        )
401
402        # composite glyph with only XY offset
403        pen = TTGlyphPen(glyphSet)
404        pen.addComponent("a", (1, 0, 0, 1, 10, 20))
405        glyphSet["b"] = b = pen.glyph()
406
407        assert b.getCoordinates(glyphSet) == (
408            GlyphCoordinates([(10, 20), (110, 20), (110, 120), (10, 120)]),
409            [3],
410            array.array("B", [1, 1, 1, 1]),
411        )
412
413        # composite glyph with a scale (and referencing another composite glyph)
414        pen = TTGlyphPen(glyphSet)
415        pen.addComponent("b", (0.5, 0, 0, 0.5, 0, 0))
416        glyphSet["c"] = c = pen.glyph()
417
418        assert c.getCoordinates(glyphSet) == (
419            GlyphCoordinates([(5, 10), (55, 10), (55, 60), (5, 60)]),
420            [3],
421            array.array("B", [1, 1, 1, 1]),
422        )
423
424        # composite glyph with unscaled offset (MS-style)
425        pen = TTGlyphPen(glyphSet)
426        pen.addComponent("a", (0.5, 0, 0, 0.5, 10, 20))
427        glyphSet["d"] = d = pen.glyph()
428        d.components[0].flags |= UNSCALED_COMPONENT_OFFSET
429
430        assert d.getCoordinates(glyphSet) == (
431            GlyphCoordinates([(10, 20), (60, 20), (60, 70), (10, 70)]),
432            [3],
433            array.array("B", [1, 1, 1, 1]),
434        )
435
436        # composite glyph with a scaled offset (Apple-style)
437        pen = TTGlyphPen(glyphSet)
438        pen.addComponent("a", (0.5, 0, 0, 0.5, 10, 20))
439        glyphSet["e"] = e = pen.glyph()
440        e.components[0].flags |= SCALED_COMPONENT_OFFSET
441
442        assert e.getCoordinates(glyphSet) == (
443            GlyphCoordinates([(5, 10), (55, 10), (55, 60), (5, 60)]),
444            [3],
445            array.array("B", [1, 1, 1, 1]),
446        )
447
448        # composite glyph where the 2nd and 3rd components use anchor points
449        pen = TTGlyphPen(glyphSet)
450        pen.addComponent("a", (1, 0, 0, 1, 0, 0))
451        glyphSet["f"] = f = pen.glyph()
452
453        comp1 = GlyphComponent()
454        comp1.glyphName = "a"
455        # aling the new component's pt 0 to pt 2 of contour points added so far
456        comp1.firstPt = 2
457        comp1.secondPt = 0
458        comp1.flags = 0
459        f.components.append(comp1)
460
461        comp2 = GlyphComponent()
462        comp2.glyphName = "a"
463        # aling the new component's pt 0 to pt 6 of contour points added so far
464        comp2.firstPt = 6
465        comp2.secondPt = 0
466        comp2.transform = [[0.707107, 0.707107], [-0.707107, 0.707107]]  # rotate 45 deg
467        comp2.flags = WE_HAVE_A_TWO_BY_TWO
468        f.components.append(comp2)
469
470        coords, end_pts, flags = f.getCoordinates(glyphSet)
471        assert end_pts == [3, 7, 11]
472        assert flags == array.array("B", [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1])
473        assert list(sum(coords, ())) == pytest.approx(
474            [
475                0, 0,
476                100, 0,
477                100, 100,
478                0, 100,
479                100, 100,
480                200, 100,
481                200, 200,
482                100, 200,
483                200, 200,
484                270.7107, 270.7107,
485                200.0, 341.4214,
486                129.2893, 270.7107,
487            ]
488        )
489
490    def test_getCompositeMaxpValues(self):
491        # https://github.com/fonttools/fonttools/issues/2044
492        glyphSet = {}
493        pen = TTGlyphPen(glyphSet)  # empty non-composite glyph
494        glyphSet["fraction"] = pen.glyph()
495        glyphSet["zero.numr"] = pen.glyph()
496        pen = TTGlyphPen(glyphSet)
497        pen.addComponent("zero.numr", (1, 0, 0, 1, 0, 0))
498        glyphSet["zero.dnom"] = pen.glyph()
499        pen = TTGlyphPen(glyphSet)
500        pen.addComponent("zero.numr", (1, 0, 0, 1, 0, 0))
501        pen.addComponent("fraction", (1, 0, 0, 1, 0, 0))
502        pen.addComponent("zero.dnom", (1, 0, 0, 1, 0, 0))
503        glyphSet["percent"] = pen.glyph()
504        pen = TTGlyphPen(glyphSet)
505        pen.addComponent("zero.numr", (1, 0, 0, 1, 0, 0))
506        pen.addComponent("fraction", (1, 0, 0, 1, 0, 0))
507        pen.addComponent("zero.dnom", (1, 0, 0, 1, 0, 0))
508        pen.addComponent("zero.dnom", (1, 0, 0, 1, 0, 0))
509        glyphSet["perthousand"] = pen.glyph()
510        assert glyphSet["zero.dnom"].getCompositeMaxpValues(glyphSet)[2] == 1
511        assert glyphSet["percent"].getCompositeMaxpValues(glyphSet)[2] == 2
512        assert glyphSet["perthousand"].getCompositeMaxpValues(glyphSet)[2] == 2
513
514
515class GlyphComponentTest:
516
517    def test_toXML_no_transform(self):
518        comp = GlyphComponent()
519        comp.glyphName = "a"
520        comp.flags = ARGS_ARE_XY_VALUES
521        comp.x, comp.y = 1, 2
522
523        assert getXML(comp.toXML) == [
524            '<component glyphName="a" x="1" y="2" flags="0x2"/>'
525        ]
526
527    def test_toXML_transform_scale(self):
528        comp = GlyphComponent()
529        comp.glyphName = "a"
530        comp.flags = ARGS_ARE_XY_VALUES | WE_HAVE_A_SCALE
531        comp.x, comp.y = 1, 2
532
533        comp.transform = [[0.2999878, 0], [0, 0.2999878]]
534        assert getXML(comp.toXML) == [
535            '<component glyphName="a" x="1" y="2" scale="0.3" flags="0xa"/>'
536        ]
537
538    def test_toXML_transform_xy_scale(self):
539        comp = GlyphComponent()
540        comp.glyphName = "a"
541        comp.flags = ARGS_ARE_XY_VALUES | WE_HAVE_AN_X_AND_Y_SCALE
542        comp.x, comp.y = 1, 2
543
544        comp.transform = [[0.5999756, 0], [0, 0.2999878]]
545        assert getXML(comp.toXML) == [
546            '<component glyphName="a" x="1" y="2" scalex="0.6" '
547            'scaley="0.3" flags="0x42"/>'
548        ]
549
550    def test_toXML_transform_2x2_scale(self):
551        comp = GlyphComponent()
552        comp.glyphName = "a"
553        comp.flags = ARGS_ARE_XY_VALUES | WE_HAVE_A_TWO_BY_TWO
554        comp.x, comp.y = 1, 2
555
556        comp.transform = [[0.5999756, -0.2000122], [0.2000122, 0.2999878]]
557        assert getXML(comp.toXML) == [
558            '<component glyphName="a" x="1" y="2" scalex="0.6" scale01="-0.2" '
559            'scale10="0.2" scaley="0.3" flags="0x82"/>'
560        ]
561
562    def test_fromXML_no_transform(self):
563        comp = GlyphComponent()
564        for name, attrs, content in parseXML(
565            ['<component glyphName="a" x="1" y="2" flags="0x2"/>']
566        ):
567            comp.fromXML(name, attrs, content, ttFont=None)
568
569        assert comp.glyphName == "a"
570        assert comp.flags & ARGS_ARE_XY_VALUES != 0
571        assert (comp.x, comp.y) == (1, 2)
572        assert not hasattr(comp, "transform")
573
574    def test_fromXML_transform_scale(self):
575        comp = GlyphComponent()
576        for name, attrs, content in parseXML(
577            ['<component glyphName="a" x="1" y="2" scale="0.3" flags="0xa"/>']
578        ):
579            comp.fromXML(name, attrs, content, ttFont=None)
580
581        assert comp.glyphName == "a"
582        assert comp.flags & ARGS_ARE_XY_VALUES != 0
583        assert comp.flags & WE_HAVE_A_SCALE != 0
584        assert (comp.x, comp.y) == (1, 2)
585        assert hasattr(comp, "transform")
586        for value, expected in zip(
587            itertools.chain(*comp.transform), [0.2999878, 0, 0, 0.2999878]
588        ):
589            assert value == pytest.approx(expected)
590
591    def test_fromXML_transform_xy_scale(self):
592        comp = GlyphComponent()
593        for name, attrs, content in parseXML(
594            [
595                '<component glyphName="a" x="1" y="2" scalex="0.6" '
596                'scaley="0.3" flags="0x42"/>'
597            ]
598        ):
599            comp.fromXML(name, attrs, content, ttFont=None)
600
601        assert comp.glyphName == "a"
602        assert comp.flags & ARGS_ARE_XY_VALUES != 0
603        assert comp.flags & WE_HAVE_AN_X_AND_Y_SCALE != 0
604        assert (comp.x, comp.y) == (1, 2)
605        assert hasattr(comp, "transform")
606        for value, expected in zip(
607            itertools.chain(*comp.transform), [0.5999756, 0, 0, 0.2999878]
608        ):
609            assert value == pytest.approx(expected)
610
611    def test_fromXML_transform_2x2_scale(self):
612        comp = GlyphComponent()
613        for name, attrs, content in parseXML(
614            [
615                '<component glyphName="a" x="1" y="2" scalex="0.6" scale01="-0.2" '
616                'scale10="0.2" scaley="0.3" flags="0x82"/>'
617            ]
618        ):
619            comp.fromXML(name, attrs, content, ttFont=None)
620
621        assert comp.glyphName == "a"
622        assert comp.flags & ARGS_ARE_XY_VALUES != 0
623        assert comp.flags & WE_HAVE_A_TWO_BY_TWO != 0
624        assert (comp.x, comp.y) == (1, 2)
625        assert hasattr(comp, "transform")
626        for value, expected in zip(
627            itertools.chain(*comp.transform),
628            [0.5999756, -0.2000122, 0.2000122, 0.2999878]
629        ):
630            assert value == pytest.approx(expected)
631
632    def test_toXML_reference_points(self):
633        comp = GlyphComponent()
634        comp.glyphName = "a"
635        comp.flags = 0
636        comp.firstPt = 1
637        comp.secondPt = 2
638
639        assert getXML(comp.toXML) == [
640            '<component glyphName="a" firstPt="1" secondPt="2" flags="0x0"/>'
641        ]
642
643    def test_fromXML_reference_points(self):
644        comp = GlyphComponent()
645        for name, attrs, content in parseXML(
646            ['<component glyphName="a" firstPt="1" secondPt="2" flags="0x0"/>']
647        ):
648            comp.fromXML(name, attrs, content, ttFont=None)
649
650        assert comp.glyphName == "a"
651        assert comp.flags == 0
652        assert (comp.firstPt, comp.secondPt) == (1, 2)
653        assert not hasattr(comp, "transform")
654
655
656if __name__ == "__main__":
657    import sys
658    sys.exit(unittest.main())
659