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