1# -*- coding: utf-8 -*- 2from __future__ import print_function, absolute_import, division, unicode_literals 3from fontTools.ttLib import TTFont 4from fontTools.misc.py23 import basestring, unichr, byteord 5from ufo2ft.outlineCompiler import OutlineTTFCompiler, OutlineOTFCompiler 6from ufo2ft.fontInfoData import intListToNum 7from fontTools.ttLib.tables._g_l_y_f import USE_MY_METRICS 8from ufo2ft.constants import USE_PRODUCTION_NAMES, GLYPHS_DONT_USE_PRODUCTION_NAMES 9from ufo2ft import compileTTF 10import os 11import logging 12import pytest 13 14 15def getpath(filename): 16 dirname = os.path.dirname(__file__) 17 return os.path.join(dirname, "data", filename) 18 19 20@pytest.fixture 21def testufo(FontClass): 22 font = FontClass(getpath("TestFont.ufo")) 23 del font.lib["public.postscriptNames"] 24 return font 25 26 27@pytest.fixture 28def use_my_metrics_ufo(FontClass): 29 return FontClass(getpath("UseMyMetrics.ufo")) 30 31 32@pytest.fixture 33def emptyufo(FontClass): 34 font = FontClass() 35 font.info.unitsPerEm = 1000 36 font.info.familyName = "Test Font" 37 font.info.styleName = "Regular" 38 font.info.ascender = 750 39 font.info.descender = -250 40 font.info.xHeight = 500 41 font.info.capHeight = 750 42 return font 43 44 45class OutlineTTFCompilerTest(object): 46 def test_setupTable_gasp(self, testufo): 47 compiler = OutlineTTFCompiler(testufo) 48 compiler.otf = TTFont() 49 compiler.setupTable_gasp() 50 assert "gasp" in compiler.otf 51 assert compiler.otf["gasp"].gaspRange == {7: 10, 65535: 15} 52 53 def test_compile_with_gasp(self, testufo): 54 compiler = OutlineTTFCompiler(testufo) 55 compiler.compile() 56 assert "gasp" in compiler.otf 57 assert compiler.otf["gasp"].gaspRange == {7: 10, 65535: 15} 58 59 def test_compile_without_gasp(self, testufo): 60 testufo.info.openTypeGaspRangeRecords = None 61 compiler = OutlineTTFCompiler(testufo) 62 compiler.compile() 63 assert "gasp" not in compiler.otf 64 65 def test_compile_empty_gasp(self, testufo): 66 # ignore empty gasp 67 testufo.info.openTypeGaspRangeRecords = [] 68 compiler = OutlineTTFCompiler(testufo) 69 compiler.compile() 70 assert "gasp" not in compiler.otf 71 72 def test_makeGlyphsBoundingBoxes(self, testufo): 73 # the call to 'makeGlyphsBoundingBoxes' happen in the __init__ method 74 compiler = OutlineTTFCompiler(testufo) 75 assert compiler.glyphBoundingBoxes[".notdef"] == (50, 0, 450, 750) 76 # no outline data 77 assert compiler.glyphBoundingBoxes["space"] is None 78 # float coordinates are rounded, so is the bbox 79 assert compiler.glyphBoundingBoxes["d"] == (90, 77, 211, 197) 80 81 def test_autoUseMyMetrics(self, use_my_metrics_ufo): 82 compiler = OutlineTTFCompiler(use_my_metrics_ufo) 83 ttf = compiler.compile() 84 # the first component in the 'Iacute' composite glyph ('acute') 85 # does _not_ have the USE_MY_METRICS flag 86 assert not (ttf["glyf"]["Iacute"].components[0].flags & USE_MY_METRICS) 87 # the second component in the 'Iacute' composite glyph ('I') 88 # has the USE_MY_METRICS flag set 89 assert ttf["glyf"]["Iacute"].components[1].flags & USE_MY_METRICS 90 # none of the 'I' components of the 'romanthree' glyph has 91 # the USE_MY_METRICS flag set, because the composite glyph has a 92 # different width 93 for component in ttf["glyf"]["romanthree"].components: 94 assert not (component.flags & USE_MY_METRICS) 95 96 def test_autoUseMyMetrics_None(self, use_my_metrics_ufo): 97 compiler = OutlineTTFCompiler(use_my_metrics_ufo) 98 # setting 'autoUseMyMetrics' attribute to None disables the feature 99 compiler.autoUseMyMetrics = None 100 ttf = compiler.compile() 101 assert not (ttf["glyf"]["Iacute"].components[1].flags & USE_MY_METRICS) 102 103 def test_importTTX(self, testufo): 104 compiler = OutlineTTFCompiler(testufo) 105 otf = compiler.otf = TTFont() 106 compiler.importTTX() 107 assert "CUST" in otf 108 assert otf["CUST"].data == b"\x00\x01\xbe\xef" 109 assert otf.sfntVersion == "\x00\x01\x00\x00" 110 111 def test_no_contour_glyphs(self, testufo): 112 for glyph in testufo: 113 glyph.clearContours() 114 compiler = OutlineTTFCompiler(testufo) 115 compiler.compile() 116 assert compiler.otf["hhea"].advanceWidthMax == 600 117 assert compiler.otf["hhea"].minLeftSideBearing == 0 118 assert compiler.otf["hhea"].minRightSideBearing == 0 119 assert compiler.otf["hhea"].xMaxExtent == 0 120 121 def test_os2_no_widths(self, testufo): 122 for glyph in testufo: 123 glyph.width = 0 124 compiler = OutlineTTFCompiler(testufo) 125 compiler.compile() 126 assert compiler.otf["OS/2"].xAvgCharWidth == 0 127 128 def test_missing_component(self, emptyufo): 129 ufo = emptyufo 130 a = ufo.newGlyph("a") 131 pen = a.getPen() 132 pen.moveTo((0, 0)) 133 pen.lineTo((100, 0)) 134 pen.lineTo((100, 100)) 135 pen.lineTo((0, 100)) 136 pen.closePath() 137 138 # a mixed contour/component glyph, which is decomposed by the 139 # TTGlyphPen; one of the components does not exist thus should 140 # be dropped 141 b = ufo.newGlyph("b") 142 pen = b.getPen() 143 pen.moveTo((0, 200)) 144 pen.lineTo((100, 200)) 145 pen.lineTo((50, 300)) 146 pen.closePath() 147 pen.addComponent("a", (1, 0, 0, 1, 0, 0)) 148 pen.addComponent("c", (1, 0, 0, 1, 0, 0)) # missing 149 150 d = ufo.newGlyph("d") 151 pen = d.getPen() 152 pen.addComponent("c", (1, 0, 0, 1, 0, 0)) # missing 153 154 e = ufo.newGlyph("e") 155 pen = e.getPen() 156 pen.addComponent("a", (1, 0, 0, 1, 0, 0)) 157 pen.addComponent("c", (1, 0, 0, 1, 0, 0)) # missing 158 159 compiler = OutlineTTFCompiler(ufo) 160 ttFont = compiler.compile() 161 glyf = ttFont["glyf"] 162 163 assert glyf["a"].numberOfContours == 1 164 assert glyf["b"].numberOfContours == 2 165 assert glyf["d"].numberOfContours == 0 166 assert glyf["e"].numberOfContours == -1 # composite glyph 167 assert len(glyf["e"].components) == 1 168 169 170class OutlineOTFCompilerTest(object): 171 def test_setupTable_CFF_all_blues_defined(self, testufo): 172 testufo.info.postscriptBlueFuzz = 2 173 testufo.info.postscriptBlueShift = 8 174 testufo.info.postscriptBlueScale = 0.049736 175 testufo.info.postscriptForceBold = False 176 testufo.info.postscriptBlueValues = [-12, 0, 486, 498, 712, 724] 177 testufo.info.postscriptOtherBlues = [-217, -205] 178 testufo.info.postscriptFamilyBlues = [-12, 0, 486, 498, 712, 724] 179 testufo.info.postscriptFamilyOtherBlues = [-217, -205] 180 181 compiler = OutlineOTFCompiler(testufo) 182 compiler.otf = TTFont(sfntVersion="OTTO") 183 184 compiler.setupTable_CFF() 185 186 cff = compiler.otf["CFF "].cff 187 private = cff[list(cff.keys())[0]].Private 188 189 assert private.BlueFuzz == 2 190 assert private.BlueShift == 8 191 assert private.BlueScale == 0.049736 192 assert private.ForceBold == 0 193 assert private.BlueValues == [-12, 0, 486, 498, 712, 724] 194 assert private.OtherBlues == [-217, -205] 195 assert private.FamilyBlues == [-12, 0, 486, 498, 712, 724] 196 assert private.FamilyOtherBlues == [-217, -205] 197 198 def test_setupTable_CFF_no_blues_defined(self, testufo): 199 # no blue values defined 200 testufo.info.postscriptBlueValues = [] 201 testufo.info.postscriptOtherBlues = [] 202 testufo.info.postscriptFamilyBlues = [] 203 testufo.info.postscriptFamilyOtherBlues = [] 204 # the following attributes have no effect 205 testufo.info.postscriptBlueFuzz = 2 206 testufo.info.postscriptBlueShift = 8 207 testufo.info.postscriptBlueScale = 0.049736 208 testufo.info.postscriptForceBold = False 209 210 compiler = OutlineOTFCompiler(testufo) 211 compiler.otf = TTFont(sfntVersion="OTTO") 212 213 compiler.setupTable_CFF() 214 215 cff = compiler.otf["CFF "].cff 216 private = cff[list(cff.keys())[0]].Private 217 218 # expect default values as defined in fontTools' cffLib.py 219 assert private.BlueFuzz == 1 220 assert private.BlueShift == 7 221 assert private.BlueScale == 0.039625 222 assert private.ForceBold == 0 223 # CFF PrivateDict has no blues attributes 224 assert not hasattr(private, "BlueValues") 225 assert not hasattr(private, "OtherBlues") 226 assert not hasattr(private, "FamilyBlues") 227 assert not hasattr(private, "FamilyOtherBlues") 228 229 def test_setupTable_CFF_some_blues_defined(self, testufo): 230 testufo.info.postscriptBlueFuzz = 2 231 testufo.info.postscriptForceBold = True 232 testufo.info.postscriptBlueValues = [] 233 testufo.info.postscriptOtherBlues = [-217, -205] 234 testufo.info.postscriptFamilyBlues = [] 235 testufo.info.postscriptFamilyOtherBlues = [] 236 237 compiler = OutlineOTFCompiler(testufo) 238 compiler.otf = TTFont(sfntVersion="OTTO") 239 240 compiler.setupTable_CFF() 241 242 cff = compiler.otf["CFF "].cff 243 private = cff[list(cff.keys())[0]].Private 244 245 assert private.BlueFuzz == 2 246 assert private.BlueShift == 7 # default 247 assert private.BlueScale == 0.039625 # default 248 assert private.ForceBold is True 249 assert not hasattr(private, "BlueValues") 250 assert private.OtherBlues == [-217, -205] 251 assert not hasattr(private, "FamilyBlues") 252 assert not hasattr(private, "FamilyOtherBlues") 253 254 @staticmethod 255 def get_charstring_program(ttFont, glyphName): 256 cff = ttFont["CFF "].cff 257 charstrings = cff[list(cff.keys())[0]].CharStrings 258 c, _ = charstrings.getItemAndSelector(glyphName) 259 c.decompile() 260 return c.program 261 262 def assertProgramEqual(self, expected, actual): 263 assert len(expected) == len(actual) 264 for exp_token, act_token in zip(expected, actual): 265 if isinstance(exp_token, basestring): 266 assert exp_token == act_token 267 else: 268 assert not isinstance(act_token, basestring) 269 assert exp_token == pytest.approx(act_token) 270 271 def test_setupTable_CFF_round_all(self, testufo): 272 # by default all floats are rounded to integer 273 compiler = OutlineOTFCompiler(testufo) 274 otf = compiler.otf = TTFont(sfntVersion="OTTO") 275 276 compiler.setupTable_CFF() 277 # glyph 'd' in TestFont.ufo contains float coordinates 278 program = self.get_charstring_program(otf, "d") 279 280 self.assertProgramEqual( 281 program, 282 [ 283 -26, 284 151, 285 197, 286 "rmoveto", 287 -34, 288 -27, 289 -27, 290 -33, 291 -33, 292 27, 293 -27, 294 34, 295 33, 296 27, 297 27, 298 33, 299 33, 300 -27, 301 27, 302 -33, 303 "hvcurveto", 304 "endchar", 305 ], 306 ) 307 308 def test_setupTable_CFF_round_none(self, testufo): 309 # roundTolerance=0 means 'don't round, keep all floats' 310 compiler = OutlineOTFCompiler(testufo, roundTolerance=0) 311 otf = compiler.otf = TTFont(sfntVersion="OTTO") 312 313 compiler.setupTable_CFF() 314 program = self.get_charstring_program(otf, "d") 315 316 self.assertProgramEqual( 317 program, 318 [ 319 -26, 320 150.66, 321 197.32, 322 "rmoveto", 323 -33.66, 324 -26.67, 325 -26.99, 326 -33.33, 327 -33.33, 328 26.67, 329 -26.66, 330 33.66, 331 33.33, 332 26.66, 333 26.66, 334 33.33, 335 33.33, 336 -26.66, 337 26.99, 338 -33.33, 339 "hvcurveto", 340 "endchar", 341 ], 342 ) 343 344 def test_setupTable_CFF_round_some(self, testufo): 345 # only floats 'close enough' are rounded to integer 346 compiler = OutlineOTFCompiler(testufo, roundTolerance=0.34) 347 otf = compiler.otf = TTFont(sfntVersion="OTTO") 348 349 compiler.setupTable_CFF() 350 program = self.get_charstring_program(otf, "d") 351 352 self.assertProgramEqual( 353 program, 354 [ 355 -26, 356 150.66, 357 197, 358 "rmoveto", 359 -33.66, 360 -27, 361 -27, 362 -33, 363 -33, 364 27, 365 -27, 366 33.66, 367 33.34, 368 26.65, 369 27, 370 33, 371 33, 372 -26.65, 373 27, 374 -33.34, 375 "hvcurveto", 376 "endchar", 377 ], 378 ) 379 380 def test_setupTable_CFF_optimize(self, testufo): 381 compiler = OutlineOTFCompiler(testufo, optimizeCFF=True) 382 otf = compiler.otf = TTFont(sfntVersion="OTTO") 383 384 compiler.setupTable_CFF() 385 program = self.get_charstring_program(otf, "a") 386 387 self.assertProgramEqual( 388 program, 389 [ 390 -12, 391 66, 392 'hmoveto', 393 256, 394 'hlineto', 395 -128, 396 510, 397 'rlineto', 398 'endchar' 399 ] 400 ) 401 402 def test_setupTable_CFF_no_optimize(self, testufo): 403 compiler = OutlineOTFCompiler(testufo, optimizeCFF=False) 404 otf = compiler.otf = TTFont(sfntVersion="OTTO") 405 406 compiler.setupTable_CFF() 407 program = self.get_charstring_program(otf, "a") 408 409 self.assertProgramEqual( 410 program, 411 [ 412 -12, 413 66, 414 0, 415 'rmoveto', 416 256, 417 0, 418 'rlineto', 419 -128, 420 510, 421 'rlineto', 422 'endchar' 423 ], 424 ) 425 426 def test_makeGlyphsBoundingBoxes(self, testufo): 427 # the call to 'makeGlyphsBoundingBoxes' happen in the __init__ method 428 compiler = OutlineOTFCompiler(testufo) 429 # with default roundTolerance, all coordinates and hence the bounding 430 # box values are rounded with otRound() 431 assert compiler.glyphBoundingBoxes["d"] == (90, 77, 211, 197) 432 433 def test_makeGlyphsBoundingBoxes_floats(self, testufo): 434 # specifying a custom roundTolerance affects which coordinates are 435 # rounded; in this case, the top-most Y coordinate stays a float 436 # (197.32), hence the bbox.yMax (198) is rounded using math.ceiling() 437 compiler = OutlineOTFCompiler(testufo, roundTolerance=0.1) 438 assert compiler.glyphBoundingBoxes["d"] == (90, 77, 211, 198) 439 440 def test_importTTX(self, testufo): 441 compiler = OutlineOTFCompiler(testufo) 442 otf = compiler.otf = TTFont(sfntVersion="OTTO") 443 compiler.importTTX() 444 assert "CUST" in otf 445 assert otf["CUST"].data == b"\x00\x01\xbe\xef" 446 assert otf.sfntVersion == "OTTO" 447 448 def test_no_contour_glyphs(self, testufo): 449 for glyph in testufo: 450 glyph.clearContours() 451 compiler = OutlineOTFCompiler(testufo) 452 compiler.compile() 453 assert compiler.otf["hhea"].advanceWidthMax == 600 454 assert compiler.otf["hhea"].minLeftSideBearing == 0 455 assert compiler.otf["hhea"].minRightSideBearing == 0 456 assert compiler.otf["hhea"].xMaxExtent == 0 457 458 def test_optimized_default_and_nominal_widths(self, FontClass): 459 ufo = FontClass() 460 ufo.info.unitsPerEm = 1000 461 for glyphName, width in ( 462 (".notdef", 500), 463 ("space", 250), 464 ("a", 388), 465 ("b", 410), 466 ("c", 374), 467 ("d", 374), 468 ("e", 388), 469 ("f", 410), 470 ("g", 388), 471 ("h", 410), 472 ("i", 600), 473 ("j", 600), 474 ("k", 600), 475 ("l", 600), 476 ): 477 glyph = ufo.newGlyph(glyphName) 478 glyph.width = width 479 480 compiler = OutlineOTFCompiler(ufo) 481 compiler.otf = TTFont(sfntVersion="OTTO") 482 483 compiler.setupTable_hmtx() 484 compiler.setupTable_CFF() 485 486 cff = compiler.otf["CFF "].cff 487 topDict = cff[list(cff.keys())[0]] 488 private = topDict.Private 489 490 assert private.defaultWidthX == 600 491 assert private.nominalWidthX == 303 492 493 charStrings = topDict.CharStrings 494 # the following have width == defaultWidthX, so it's omitted 495 for g in ("i", "j", "k", "l"): 496 assert charStrings.getItemAndSelector(g)[0].program == ["endchar"] 497 # 'space' has width 250, so the width encoded in its charstring is: 498 # 250 - nominalWidthX 499 assert charStrings.getItemAndSelector("space")[0].program == [-53, "endchar"] 500 501 502class GlyphOrderTest(object): 503 def test_compile_original_glyph_order(self, testufo): 504 DEFAULT_ORDER = [ 505 ".notdef", 506 "space", 507 "a", 508 "b", 509 "c", 510 "d", 511 "e", 512 "f", 513 "g", 514 "h", 515 "i", 516 "j", 517 "k", 518 "l", 519 ] 520 compiler = OutlineTTFCompiler(testufo) 521 compiler.compile() 522 assert compiler.otf.getGlyphOrder() == DEFAULT_ORDER 523 524 def test_compile_tweaked_glyph_order(self, testufo): 525 NEW_ORDER = [ 526 ".notdef", 527 "space", 528 "b", 529 "a", 530 "c", 531 "d", 532 "e", 533 "f", 534 "g", 535 "h", 536 "i", 537 "j", 538 "k", 539 "l", 540 ] 541 testufo.lib["public.glyphOrder"] = NEW_ORDER 542 compiler = OutlineTTFCompiler(testufo) 543 compiler.compile() 544 assert compiler.otf.getGlyphOrder() == NEW_ORDER 545 546 def test_compile_strange_glyph_order(self, testufo): 547 """Move space and .notdef to end of glyph ids 548 ufo2ft always puts .notdef first. 549 """ 550 NEW_ORDER = ["b", "a", "c", "d", "space", ".notdef"] 551 EXPECTED_ORDER = [ 552 ".notdef", 553 "b", 554 "a", 555 "c", 556 "d", 557 "space", 558 "e", 559 "f", 560 "g", 561 "h", 562 "i", 563 "j", 564 "k", 565 "l", 566 ] 567 testufo.lib["public.glyphOrder"] = NEW_ORDER 568 compiler = OutlineTTFCompiler(testufo) 569 compiler.compile() 570 assert compiler.otf.getGlyphOrder() == EXPECTED_ORDER 571 572 573class NamesTest(object): 574 @pytest.mark.parametrize( 575 "prod_names_key, prod_names_value", 576 [(USE_PRODUCTION_NAMES, False), (GLYPHS_DONT_USE_PRODUCTION_NAMES, True)], 577 ids=["useProductionNames", "Don't use Production Names"], 578 ) 579 def test_compile_without_production_names( 580 self, testufo, prod_names_key, prod_names_value 581 ): 582 expected = [ 583 ".notdef", 584 "space", 585 "a", 586 "b", 587 "c", 588 "d", 589 "e", 590 "f", 591 "g", 592 "h", 593 "i", 594 "j", 595 "k", 596 "l", 597 ] 598 599 result = compileTTF(testufo, useProductionNames=False) 600 assert result.getGlyphOrder() == expected 601 602 testufo.lib[prod_names_key] = prod_names_value 603 result = compileTTF(testufo) 604 assert result.getGlyphOrder() == expected 605 606 def test_compile_with_production_names(self, testufo): 607 original = [ 608 ".notdef", 609 "space", 610 "a", 611 "b", 612 "c", 613 "d", 614 "e", 615 "f", 616 "g", 617 "h", 618 "i", 619 "j", 620 "k", 621 "l", 622 ] 623 modified = [ 624 ".notdef", 625 "uni0020", 626 "uni0061", 627 "uni0062", 628 "uni0063", 629 "uni0064", 630 "uni0065", 631 "uni0066", 632 "uni0067", 633 "uni0068", 634 "uni0069", 635 "uni006A", 636 "uni006B", 637 "uni006C", 638 ] 639 640 result = compileTTF(testufo) 641 assert result.getGlyphOrder() == original 642 643 result = compileTTF(testufo, useProductionNames=True) 644 assert result.getGlyphOrder() == modified 645 646 testufo.lib[USE_PRODUCTION_NAMES] = True 647 result = compileTTF(testufo) 648 assert result.getGlyphOrder() == modified 649 650 def test_postprocess_production_names_no_notdef(self, testufo): 651 import ufo2ft 652 653 del testufo[".notdef"] 654 assert ".notdef" not in testufo 655 result = compileTTF(testufo, useProductionNames=False) 656 assert ".notdef" in result.getGlyphOrder() 657 658 pp = ufo2ft.postProcessor.PostProcessor(result, testufo, glyphSet=None) 659 try: 660 f = pp.process(useProductionNames=True) 661 except Exception as e: 662 pytest.xfail("Unexpected exception: " + str(e)) 663 assert ".notdef" in f.getGlyphOrder() 664 665 CUSTOM_POSTSCRIPT_NAMES = { 666 ".notdef": ".notdef", 667 "space": "foo", 668 "a": "bar", 669 "b": "baz", 670 "c": "meh", 671 "d": "doh", 672 "e": "bim", 673 "f": "bum", 674 "g": "bam", 675 "h": "bib", 676 "i": "bob", 677 "j": "bub", 678 "k": "kkk", 679 "l": "lll", 680 } 681 682 @pytest.mark.parametrize("use_production_names", [None, True]) 683 def test_compile_with_custom_postscript_names(self, testufo, use_production_names): 684 testufo.lib["public.postscriptNames"] = self.CUSTOM_POSTSCRIPT_NAMES 685 result = compileTTF(testufo, useProductionNames=use_production_names) 686 assert sorted(result.getGlyphOrder()) == sorted( 687 self.CUSTOM_POSTSCRIPT_NAMES.values() 688 ) 689 690 @pytest.mark.parametrize("use_production_names", [None, True]) 691 def test_compile_with_custom_postscript_names_notdef_preserved( 692 self, testufo, use_production_names 693 ): 694 custom_names = dict(self.CUSTOM_POSTSCRIPT_NAMES) 695 del custom_names[".notdef"] 696 testufo.lib["public.postscriptNames"] = custom_names 697 result = compileTTF(testufo, useProductionNames=use_production_names) 698 assert result.getGlyphOrder() == [ 699 ".notdef", 700 "foo", 701 "bar", 702 "baz", 703 "meh", 704 "doh", 705 "bim", 706 "bum", 707 "bam", 708 "bib", 709 "bob", 710 "bub", 711 "kkk", 712 "lll", 713 ] 714 715 def test_warn_name_exceeds_max_length(self, testufo, caplog): 716 long_name = 64 * "a" 717 testufo.newGlyph(long_name) 718 719 with caplog.at_level(logging.WARNING, logger="ufo2ft.postProcessor"): 720 result = compileTTF(testufo, useProductionNames=True) 721 722 assert "length exceeds 63 characters" in caplog.text 723 assert long_name in result.getGlyphOrder() 724 725 def test_duplicate_glyph_names(self, testufo): 726 order = ["ab", "ab.1", "a-b", "a/b", "ba"] 727 testufo.lib["public.glyphOrder"] = order 728 testufo.lib["public.postscriptNames"] = {"ba": "ab"} 729 for name in order: 730 if name not in testufo: 731 testufo.newGlyph(name) 732 733 result = compileTTF(testufo, useProductionNames=True).getGlyphOrder() 734 735 assert result[1] == "ab" 736 assert result[2] == "ab.1" 737 assert result[3] == "ab.2" 738 assert result[4] == "ab.3" 739 assert result[5] == "ab.4" 740 741 def test_too_long_production_name(self, testufo): 742 name = "_".join(("a",) * 16) 743 testufo.newGlyph(name) 744 745 result = compileTTF(testufo, useProductionNames=True).getGlyphOrder() 746 747 # the production name uniXXXX would exceed the max length so the 748 # original name is used 749 assert name in result 750 751 752ASCII = [unichr(c) for c in range(0x20, 0x7E)] 753 754 755@pytest.mark.parametrize( 756 "unicodes, expected", 757 [ 758 [ASCII + ["Þ"], {0}], # Latin 1 759 [ASCII + ["Ľ"], {1}], # Latin 2: Eastern Europe 760 [ASCII + ["Ľ", "┤"], {1, 58}], # Latin 2 761 [["Б"], {2}], # Cyrillic 762 [["Б", "Ѕ", "┤"], {2, 57}], # IBM Cyrillic 763 [["Б", "╜", "┤"], {2, 49}], # MS-DOS Russian 764 [["Ά"], {3}], # Greek 765 [["Ά", "½", "┤"], {3, 48}], # IBM Greek 766 [["Ά", "√", "┤"], {3, 60}], # Greek, former 437 G 767 [ASCII + ["İ"], {4}], # Turkish 768 [ASCII + ["İ", "┤"], {4, 56}], # IBM turkish 769 [["א"], {5}], # Hebrew 770 [["א", "√", "┤"], {5, 53}], # Hebrew 771 [["ر"], {6}], # Arabic 772 [["ر", "√"], {6, 51}], # Arabic 773 [["ر", "√", "┤"], {6, 51, 61}], # Arabic; ASMO 708 774 [ASCII + ["ŗ"], {7}], # Windows Baltic 775 [ASCII + ["ŗ", "┤"], {7, 59}], # MS-DOS Baltic 776 [ASCII + ["₫"], {8}], # Vietnamese 777 [["ๅ"], {16}], # Thai 778 [["エ"], {17}], # JIS/Japan 779 [["ㄅ"], {18}], # Chinese: Simplified chars 780 [["ㄱ"], {19}], # Korean wansung 781 [["央"], {20}], # Chinese: Traditional chars 782 [["곴"], {21}], # Korean Johab 783 [ASCII + ["♥"], {30}], # OEM Character Set 784 [ASCII + ["þ", "┤"], {54}], # MS-DOS Icelandic 785 [ASCII + ["╚"], {62, 63}], # WE/Latin 1 786 [ASCII + ["┤", "√", "Å"], {50}], # MS-DOS Nordic 787 [ASCII + ["┤", "√", "é"], {52}], # MS-DOS Canadian French 788 [ASCII + ["┤", "√", "õ"], {55}], # MS-DOS Portuguese 789 [ASCII + ["‰", "∑"], {29}], # Macintosh Character Set (US Roman) 790 [[" ", "0", "1", "2", "අ"], {0}], # always fallback to Latin 1 791 ], 792) 793def test_calcCodePageRanges(emptyufo, unicodes, expected): 794 font = emptyufo 795 for i, c in enumerate(unicodes): 796 font.newGlyph("glyph%d" % i).unicode = byteord(c) 797 798 compiler = OutlineOTFCompiler(font) 799 compiler.compile() 800 801 assert compiler.otf["OS/2"].ulCodePageRange1 == intListToNum( 802 expected, start=0, length=32 803 ) 804 assert compiler.otf["OS/2"].ulCodePageRange2 == intListToNum( 805 expected, start=32, length=32 806 ) 807 808 809if __name__ == "__main__": 810 import sys 811 812 sys.exit(pytest.main(sys.argv)) 813