• 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)], typecode="h")
177        g.append((0x8000, 0))
178        assert g.array.typecode == "d"
179        assert g.array == array.array("d", [1.0, 1.0, 32768.0, 0.0])
180
181
182CURR_DIR = os.path.abspath(os.path.dirname(os.path.realpath(__file__)))
183DATA_DIR = os.path.join(CURR_DIR, 'data')
184
185GLYF_TTX = os.path.join(DATA_DIR, "_g_l_y_f_outline_flag_bit6.ttx")
186GLYF_BIN = os.path.join(DATA_DIR, "_g_l_y_f_outline_flag_bit6.glyf.bin")
187HEAD_BIN = os.path.join(DATA_DIR, "_g_l_y_f_outline_flag_bit6.head.bin")
188LOCA_BIN = os.path.join(DATA_DIR, "_g_l_y_f_outline_flag_bit6.loca.bin")
189MAXP_BIN = os.path.join(DATA_DIR, "_g_l_y_f_outline_flag_bit6.maxp.bin")
190
191
192def strip_ttLibVersion(string):
193    return re.sub(' ttLibVersion=".*"', '', string)
194
195
196class GlyfTableTest(unittest.TestCase):
197
198    def __init__(self, methodName):
199        unittest.TestCase.__init__(self, methodName)
200        # Python 3 renamed assertRaisesRegexp to assertRaisesRegex,
201        # and fires deprecation warnings if a program uses the old name.
202        if not hasattr(self, "assertRaisesRegex"):
203            self.assertRaisesRegex = self.assertRaisesRegexp
204
205    @classmethod
206    def setUpClass(cls):
207        with open(GLYF_BIN, 'rb') as f:
208            cls.glyfData = f.read()
209        with open(HEAD_BIN, 'rb') as f:
210            cls.headData = f.read()
211        with open(LOCA_BIN, 'rb') as f:
212            cls.locaData = f.read()
213        with open(MAXP_BIN, 'rb') as f:
214            cls.maxpData = f.read()
215        with open(GLYF_TTX, 'r') as f:
216            cls.glyfXML = strip_ttLibVersion(f.read()).splitlines()
217
218    def test_toXML(self):
219        font = TTFont(sfntVersion="\x00\x01\x00\x00")
220        glyfTable = font['glyf'] = newTable('glyf')
221        font['head'] = newTable('head')
222        font['loca'] = newTable('loca')
223        font['maxp'] = newTable('maxp')
224        font['maxp'].decompile(self.maxpData, font)
225        font['head'].decompile(self.headData, font)
226        font['loca'].decompile(self.locaData, font)
227        glyfTable.decompile(self.glyfData, font)
228        out = StringIO()
229        font.saveXML(out)
230        glyfXML = strip_ttLibVersion(out.getvalue()).splitlines()
231        self.assertEqual(glyfXML, self.glyfXML)
232
233    def test_fromXML(self):
234        font = TTFont(sfntVersion="\x00\x01\x00\x00")
235        font.importXML(GLYF_TTX)
236        glyfTable = font['glyf']
237        glyfData = glyfTable.compile(font)
238        self.assertEqual(glyfData, self.glyfData)
239
240    def test_recursiveComponent(self):
241        glyphSet = {}
242        pen_dummy = TTGlyphPen(glyphSet)
243        glyph_dummy = pen_dummy.glyph()
244        glyphSet["A"] = glyph_dummy
245        glyphSet["B"] = glyph_dummy
246        pen_A = TTGlyphPen(glyphSet)
247        pen_A.addComponent("B", (1, 0, 0, 1, 0, 0))
248        pen_B = TTGlyphPen(glyphSet)
249        pen_B.addComponent("A", (1, 0, 0, 1, 0, 0))
250        glyph_A = pen_A.glyph()
251        glyph_B = pen_B.glyph()
252        glyphSet["A"] = glyph_A
253        glyphSet["B"] = glyph_B
254        with self.assertRaisesRegex(TTLibError, "glyph '.' contains a recursive component reference"):
255            glyph_A.getCoordinates(glyphSet)
256
257    def test_trim_remove_hinting_composite_glyph(self):
258        glyphSet = {"dummy": TTGlyphPen(None).glyph()}
259
260        pen = TTGlyphPen(glyphSet)
261        pen.addComponent("dummy", (1, 0, 0, 1, 0, 0))
262        composite = pen.glyph()
263        p = ttProgram.Program()
264        p.fromAssembly(['SVTCA[0]'])
265        composite.program = p
266        glyphSet["composite"] = composite
267
268        glyfTable = newTable("glyf")
269        glyfTable.glyphs = glyphSet
270        glyfTable.glyphOrder = sorted(glyphSet)
271
272        composite.compact(glyfTable)
273
274        self.assertTrue(hasattr(composite, "data"))
275
276        # remove hinting from the compacted composite glyph, without expanding it
277        composite.trim(remove_hinting=True)
278
279        # check that, after expanding the glyph, we have no instructions
280        composite.expand(glyfTable)
281        self.assertFalse(hasattr(composite, "program"))
282
283        # now remove hinting from expanded composite glyph
284        composite.program = p
285        composite.trim(remove_hinting=True)
286
287        # check we have no instructions
288        self.assertFalse(hasattr(composite, "program"))
289
290        composite.compact(glyfTable)
291
292    def test_bit6_draw_to_pen_issue1771(self):
293        # https://github.com/fonttools/fonttools/issues/1771
294        font = TTFont(sfntVersion="\x00\x01\x00\x00")
295        # glyph00003 contains a bit 6 flag on the first point,
296        # which triggered the issue
297        font.importXML(GLYF_TTX)
298        glyfTable = font['glyf']
299        pen = RecordingPen()
300        glyfTable["glyph00003"].draw(pen, glyfTable=glyfTable)
301        expected = [('moveTo', ((501, 1430),)),
302                    ('lineTo', ((683, 1430),)),
303                    ('lineTo', ((1172, 0),)),
304                    ('lineTo', ((983, 0),)),
305                    ('lineTo', ((591, 1193),)),
306                    ('lineTo', ((199, 0),)),
307                    ('lineTo', ((12, 0),)),
308                    ('closePath', ()),
309                    ('moveTo', ((249, 514),)),
310                    ('lineTo', ((935, 514),)),
311                    ('lineTo', ((935, 352),)),
312                    ('lineTo', ((249, 352),)),
313                    ('closePath', ())]
314        self.assertEqual(pen.value, expected)
315
316    def test_bit6_draw_to_pointpen(self):
317        # https://github.com/fonttools/fonttools/issues/1771
318        font = TTFont(sfntVersion="\x00\x01\x00\x00")
319        # glyph00003 contains a bit 6 flag on the first point
320        # which triggered the issue
321        font.importXML(GLYF_TTX)
322        glyfTable = font['glyf']
323        pen = RecordingPointPen()
324        glyfTable["glyph00003"].drawPoints(pen, glyfTable=glyfTable)
325        expected = [
326            ('beginPath', (), {}),
327            ('addPoint', ((501, 1430), 'line', False, None), {}),
328            ('addPoint', ((683, 1430), 'line', False, None), {}),
329            ('addPoint', ((1172, 0), 'line', False, None), {}),
330            ('addPoint', ((983, 0), 'line', False, None), {}),
331        ]
332        self.assertEqual(pen.value[:len(expected)], expected)
333
334    def test_draw_vs_drawpoints(self):
335        font = TTFont(sfntVersion="\x00\x01\x00\x00")
336        font.importXML(GLYF_TTX)
337        glyfTable = font['glyf']
338        pen1 = RecordingPen()
339        pen2 = RecordingPen()
340        glyfTable["glyph00003"].draw(pen1, glyfTable)
341        glyfTable["glyph00003"].drawPoints(PointToSegmentPen(pen2), glyfTable)
342        self.assertEqual(pen1.value, pen2.value)
343
344    def test_compile_empty_table(self):
345        font = TTFont(sfntVersion="\x00\x01\x00\x00")
346        font.importXML(GLYF_TTX)
347        glyfTable = font['glyf']
348        # set all glyphs to zero contours
349        glyfTable.glyphs = {glyphName: Glyph() for glyphName in font.getGlyphOrder()}
350        glyfData = glyfTable.compile(font)
351        self.assertEqual(glyfData, b"\x00")
352        self.assertEqual(list(font["loca"]), [0] * (font["maxp"].numGlyphs+1))
353
354    def test_decompile_empty_table(self):
355        font = TTFont()
356        glyphNames = [".notdef", "space"]
357        font.setGlyphOrder(glyphNames)
358        font["loca"] = newTable("loca")
359        font["loca"].locations = [0] * (len(glyphNames) + 1)
360        font["glyf"] = newTable("glyf")
361        font["glyf"].decompile(b"\x00", font)
362        self.assertEqual(len(font["glyf"]), 2)
363        self.assertEqual(font["glyf"][".notdef"].numberOfContours, 0)
364        self.assertEqual(font["glyf"]["space"].numberOfContours, 0)
365
366
367class GlyphTest:
368
369    def test_getCoordinates(self):
370        glyphSet = {}
371        pen = TTGlyphPen(glyphSet)
372        pen.moveTo((0, 0))
373        pen.lineTo((100, 0))
374        pen.lineTo((100, 100))
375        pen.lineTo((0, 100))
376        pen.closePath()
377        # simple contour glyph
378        glyphSet["a"] = a = pen.glyph()
379
380        assert a.getCoordinates(glyphSet) == (
381            GlyphCoordinates([(0, 0), (100, 0), (100, 100), (0, 100)]),
382            [3],
383            array.array("B", [1, 1, 1, 1]),
384        )
385
386        # composite glyph with only XY offset
387        pen = TTGlyphPen(glyphSet)
388        pen.addComponent("a", (1, 0, 0, 1, 10, 20))
389        glyphSet["b"] = b = pen.glyph()
390
391        assert b.getCoordinates(glyphSet) == (
392            GlyphCoordinates([(10, 20), (110, 20), (110, 120), (10, 120)]),
393            [3],
394            array.array("B", [1, 1, 1, 1]),
395        )
396
397        # composite glyph with a scale (and referencing another composite glyph)
398        pen = TTGlyphPen(glyphSet)
399        pen.addComponent("b", (0.5, 0, 0, 0.5, 0, 0))
400        glyphSet["c"] = c = pen.glyph()
401
402        assert c.getCoordinates(glyphSet) == (
403            GlyphCoordinates([(5, 10), (55, 10), (55, 60), (5, 60)]),
404            [3],
405            array.array("B", [1, 1, 1, 1]),
406        )
407
408        # composite glyph with unscaled offset (MS-style)
409        pen = TTGlyphPen(glyphSet)
410        pen.addComponent("a", (0.5, 0, 0, 0.5, 10, 20))
411        glyphSet["d"] = d = pen.glyph()
412        d.components[0].flags |= UNSCALED_COMPONENT_OFFSET
413
414        assert d.getCoordinates(glyphSet) == (
415            GlyphCoordinates([(10, 20), (60, 20), (60, 70), (10, 70)]),
416            [3],
417            array.array("B", [1, 1, 1, 1]),
418        )
419
420        # composite glyph with a scaled offset (Apple-style)
421        pen = TTGlyphPen(glyphSet)
422        pen.addComponent("a", (0.5, 0, 0, 0.5, 10, 20))
423        glyphSet["e"] = e = pen.glyph()
424        e.components[0].flags |= SCALED_COMPONENT_OFFSET
425
426        assert e.getCoordinates(glyphSet) == (
427            GlyphCoordinates([(5, 10), (55, 10), (55, 60), (5, 60)]),
428            [3],
429            array.array("B", [1, 1, 1, 1]),
430        )
431
432        # composite glyph where the 2nd and 3rd components use anchor points
433        pen = TTGlyphPen(glyphSet)
434        pen.addComponent("a", (1, 0, 0, 1, 0, 0))
435        glyphSet["f"] = f = pen.glyph()
436
437        comp1 = GlyphComponent()
438        comp1.glyphName = "a"
439        # aling the new component's pt 0 to pt 2 of contour points added so far
440        comp1.firstPt = 2
441        comp1.secondPt = 0
442        comp1.flags = 0
443        f.components.append(comp1)
444
445        comp2 = GlyphComponent()
446        comp2.glyphName = "a"
447        # aling the new component's pt 0 to pt 6 of contour points added so far
448        comp2.firstPt = 6
449        comp2.secondPt = 0
450        comp2.transform = [[0.707107, 0.707107], [-0.707107, 0.707107]]  # rotate 45 deg
451        comp2.flags = WE_HAVE_A_TWO_BY_TWO
452        f.components.append(comp2)
453
454        coords, end_pts, flags = f.getCoordinates(glyphSet)
455        assert end_pts == [3, 7, 11]
456        assert flags == array.array("B", [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1])
457        assert list(sum(coords, ())) == pytest.approx(
458            [
459                0, 0,
460                100, 0,
461                100, 100,
462                0, 100,
463                100, 100,
464                200, 100,
465                200, 200,
466                100, 200,
467                200, 200,
468                270.7107, 270.7107,
469                200.0, 341.4214,
470                129.2893, 270.7107,
471            ]
472        )
473
474    def test_getCompositeMaxpValues(self):
475        # https://github.com/fonttools/fonttools/issues/2044
476        glyphSet = {}
477        pen = TTGlyphPen(glyphSet)  # empty non-composite glyph
478        glyphSet["fraction"] = pen.glyph()
479        glyphSet["zero.numr"] = pen.glyph()
480        pen = TTGlyphPen(glyphSet)
481        pen.addComponent("zero.numr", (1, 0, 0, 1, 0, 0))
482        glyphSet["zero.dnom"] = pen.glyph()
483        pen = TTGlyphPen(glyphSet)
484        pen.addComponent("zero.numr", (1, 0, 0, 1, 0, 0))
485        pen.addComponent("fraction", (1, 0, 0, 1, 0, 0))
486        pen.addComponent("zero.dnom", (1, 0, 0, 1, 0, 0))
487        glyphSet["percent"] = pen.glyph()
488        pen = TTGlyphPen(glyphSet)
489        pen.addComponent("zero.numr", (1, 0, 0, 1, 0, 0))
490        pen.addComponent("fraction", (1, 0, 0, 1, 0, 0))
491        pen.addComponent("zero.dnom", (1, 0, 0, 1, 0, 0))
492        pen.addComponent("zero.dnom", (1, 0, 0, 1, 0, 0))
493        glyphSet["perthousand"] = pen.glyph()
494        assert glyphSet["zero.dnom"].getCompositeMaxpValues(glyphSet)[2] == 1
495        assert glyphSet["percent"].getCompositeMaxpValues(glyphSet)[2] == 2
496        assert glyphSet["perthousand"].getCompositeMaxpValues(glyphSet)[2] == 2
497
498
499class GlyphComponentTest:
500
501    def test_toXML_no_transform(self):
502        comp = GlyphComponent()
503        comp.glyphName = "a"
504        comp.flags = ARGS_ARE_XY_VALUES
505        comp.x, comp.y = 1, 2
506
507        assert getXML(comp.toXML) == [
508            '<component glyphName="a" x="1" y="2" flags="0x2"/>'
509        ]
510
511    def test_toXML_transform_scale(self):
512        comp = GlyphComponent()
513        comp.glyphName = "a"
514        comp.flags = ARGS_ARE_XY_VALUES | WE_HAVE_A_SCALE
515        comp.x, comp.y = 1, 2
516
517        comp.transform = [[0.2999878, 0], [0, 0.2999878]]
518        assert getXML(comp.toXML) == [
519            '<component glyphName="a" x="1" y="2" scale="0.3" flags="0xa"/>'
520        ]
521
522    def test_toXML_transform_xy_scale(self):
523        comp = GlyphComponent()
524        comp.glyphName = "a"
525        comp.flags = ARGS_ARE_XY_VALUES | WE_HAVE_AN_X_AND_Y_SCALE
526        comp.x, comp.y = 1, 2
527
528        comp.transform = [[0.5999756, 0], [0, 0.2999878]]
529        assert getXML(comp.toXML) == [
530            '<component glyphName="a" x="1" y="2" scalex="0.6" '
531            'scaley="0.3" flags="0x42"/>'
532        ]
533
534    def test_toXML_transform_2x2_scale(self):
535        comp = GlyphComponent()
536        comp.glyphName = "a"
537        comp.flags = ARGS_ARE_XY_VALUES | WE_HAVE_A_TWO_BY_TWO
538        comp.x, comp.y = 1, 2
539
540        comp.transform = [[0.5999756, -0.2000122], [0.2000122, 0.2999878]]
541        assert getXML(comp.toXML) == [
542            '<component glyphName="a" x="1" y="2" scalex="0.6" scale01="-0.2" '
543            'scale10="0.2" scaley="0.3" flags="0x82"/>'
544        ]
545
546    def test_fromXML_no_transform(self):
547        comp = GlyphComponent()
548        for name, attrs, content in parseXML(
549            ['<component glyphName="a" x="1" y="2" flags="0x2"/>']
550        ):
551            comp.fromXML(name, attrs, content, ttFont=None)
552
553        assert comp.glyphName == "a"
554        assert comp.flags & ARGS_ARE_XY_VALUES != 0
555        assert (comp.x, comp.y) == (1, 2)
556        assert not hasattr(comp, "transform")
557
558    def test_fromXML_transform_scale(self):
559        comp = GlyphComponent()
560        for name, attrs, content in parseXML(
561            ['<component glyphName="a" x="1" y="2" scale="0.3" flags="0xa"/>']
562        ):
563            comp.fromXML(name, attrs, content, ttFont=None)
564
565        assert comp.glyphName == "a"
566        assert comp.flags & ARGS_ARE_XY_VALUES != 0
567        assert comp.flags & WE_HAVE_A_SCALE != 0
568        assert (comp.x, comp.y) == (1, 2)
569        assert hasattr(comp, "transform")
570        for value, expected in zip(
571            itertools.chain(*comp.transform), [0.2999878, 0, 0, 0.2999878]
572        ):
573            assert value == pytest.approx(expected)
574
575    def test_fromXML_transform_xy_scale(self):
576        comp = GlyphComponent()
577        for name, attrs, content in parseXML(
578            [
579                '<component glyphName="a" x="1" y="2" scalex="0.6" '
580                'scaley="0.3" flags="0x42"/>'
581            ]
582        ):
583            comp.fromXML(name, attrs, content, ttFont=None)
584
585        assert comp.glyphName == "a"
586        assert comp.flags & ARGS_ARE_XY_VALUES != 0
587        assert comp.flags & WE_HAVE_AN_X_AND_Y_SCALE != 0
588        assert (comp.x, comp.y) == (1, 2)
589        assert hasattr(comp, "transform")
590        for value, expected in zip(
591            itertools.chain(*comp.transform), [0.5999756, 0, 0, 0.2999878]
592        ):
593            assert value == pytest.approx(expected)
594
595    def test_fromXML_transform_2x2_scale(self):
596        comp = GlyphComponent()
597        for name, attrs, content in parseXML(
598            [
599                '<component glyphName="a" x="1" y="2" scalex="0.6" scale01="-0.2" '
600                'scale10="0.2" scaley="0.3" flags="0x82"/>'
601            ]
602        ):
603            comp.fromXML(name, attrs, content, ttFont=None)
604
605        assert comp.glyphName == "a"
606        assert comp.flags & ARGS_ARE_XY_VALUES != 0
607        assert comp.flags & WE_HAVE_A_TWO_BY_TWO != 0
608        assert (comp.x, comp.y) == (1, 2)
609        assert hasattr(comp, "transform")
610        for value, expected in zip(
611            itertools.chain(*comp.transform),
612            [0.5999756, -0.2000122, 0.2000122, 0.2999878]
613        ):
614            assert value == pytest.approx(expected)
615
616    def test_toXML_reference_points(self):
617        comp = GlyphComponent()
618        comp.glyphName = "a"
619        comp.flags = 0
620        comp.firstPt = 1
621        comp.secondPt = 2
622
623        assert getXML(comp.toXML) == [
624            '<component glyphName="a" firstPt="1" secondPt="2" flags="0x0"/>'
625        ]
626
627    def test_fromXML_reference_points(self):
628        comp = GlyphComponent()
629        for name, attrs, content in parseXML(
630            ['<component glyphName="a" firstPt="1" secondPt="2" flags="0x0"/>']
631        ):
632            comp.fromXML(name, attrs, content, ttFont=None)
633
634        assert comp.glyphName == "a"
635        assert comp.flags == 0
636        assert (comp.firstPt, comp.secondPt) == (1, 2)
637        assert not hasattr(comp, "transform")
638
639
640if __name__ == "__main__":
641    import sys
642    sys.exit(unittest.main())
643