1import re 2import shutil 3from pathlib import Path 4 5import pytest 6from fontTools.designspaceLib import ( 7 AxisDescriptor, 8 AxisLabelDescriptor, 9 DesignSpaceDocument, 10 DiscreteAxisDescriptor, 11 InstanceDescriptor, 12 LocationLabelDescriptor, 13 RangeAxisSubsetDescriptor, 14 SourceDescriptor, 15 ValueAxisSubsetDescriptor, 16 VariableFontDescriptor, 17 posix, 18) 19 20from .fixtures import datadir 21 22 23def assert_descriptors_equal(actual, expected): 24 assert len(actual) == len(expected) 25 for a, e in zip(actual, expected): 26 assert a.asdict() == e.asdict() 27 28 29def test_read_v5_document_simple(datadir): 30 doc = DesignSpaceDocument.fromfile(datadir / "test_v5.designspace") 31 32 assert_descriptors_equal( 33 doc.axes, 34 [ 35 AxisDescriptor( 36 tag="wght", 37 name="Weight", 38 minimum=200, 39 maximum=1000, 40 default=200, 41 labelNames={"en": "Wéíght", "fa-IR": "قطر"}, 42 map=[ 43 (200, 0), 44 (300, 100), 45 (400, 368), 46 (600, 600), 47 (700, 824), 48 (900, 1000), 49 ], 50 axisOrdering=None, 51 axisLabels=[ 52 AxisLabelDescriptor( 53 name="Extra Light", 54 userMinimum=200, 55 userValue=200, 56 userMaximum=250, 57 labelNames={"de": "Extraleicht", "fr": "Extra léger"}, 58 ), 59 AxisLabelDescriptor( 60 name="Light", userMinimum=250, userValue=300, userMaximum=350 61 ), 62 AxisLabelDescriptor( 63 name="Regular", 64 userMinimum=350, 65 userValue=400, 66 userMaximum=450, 67 elidable=True, 68 ), 69 AxisLabelDescriptor( 70 name="Semi Bold", 71 userMinimum=450, 72 userValue=600, 73 userMaximum=650, 74 ), 75 AxisLabelDescriptor( 76 name="Bold", userMinimum=650, userValue=700, userMaximum=850 77 ), 78 AxisLabelDescriptor( 79 name="Black", userMinimum=850, userValue=900, userMaximum=900 80 ), 81 AxisLabelDescriptor( 82 name="Regular", 83 userValue=400, 84 linkedUserValue=700, 85 elidable=True, 86 ), 87 AxisLabelDescriptor( 88 name="Bold", userValue=700, linkedUserValue=400 89 ), 90 ], 91 ), 92 AxisDescriptor( 93 tag="wdth", 94 name="Width", 95 minimum=50, 96 maximum=150, 97 default=100, 98 hidden=True, 99 labelNames={"fr": "Chasse"}, 100 map=[(50, 10), (100, 20), (125, 66), (150, 990)], 101 axisOrdering=1, 102 axisLabels=[ 103 AxisLabelDescriptor(name="Condensed", userValue=50), 104 AxisLabelDescriptor( 105 name="Normal", elidable=True, olderSibling=True, userValue=100 106 ), 107 AxisLabelDescriptor(name="Wide", userValue=125), 108 AxisLabelDescriptor( 109 name="Extra Wide", userValue=150, userMinimum=150 110 ), 111 ], 112 ), 113 DiscreteAxisDescriptor( 114 tag="ital", 115 name="Italic", 116 values=[0, 1], 117 default=0, 118 axisOrdering=None, 119 axisLabels=[ 120 AxisLabelDescriptor( 121 name="Roman", userValue=0, elidable=True, linkedUserValue=1 122 ), 123 AxisLabelDescriptor(name="Italic", userValue=1), 124 ], 125 ), 126 ], 127 ) 128 129 assert_descriptors_equal( 130 doc.locationLabels, 131 [ 132 LocationLabelDescriptor( 133 name="Some Style", 134 labelNames={"fr": "Un Style"}, 135 userLocation={"Weight": 300, "Width": 50, "Italic": 0}, 136 ), 137 LocationLabelDescriptor( 138 name="Other", userLocation={"Weight": 700, "Width": 100, "Italic": 1} 139 ), 140 ], 141 ) 142 143 assert_descriptors_equal( 144 doc.sources, 145 [ 146 SourceDescriptor( 147 filename="masters/masterTest1.ufo", 148 path=posix(str((datadir / "masters/masterTest1.ufo").resolve())), 149 name="master.ufo1", 150 layerName=None, 151 location={"Italic": 0.0, "Weight": 0.0, "Width": 20.0}, 152 copyLib=True, 153 copyInfo=True, 154 copyGroups=False, 155 copyFeatures=True, 156 muteKerning=False, 157 muteInfo=False, 158 mutedGlyphNames=["A", "Z"], 159 familyName="MasterFamilyName", 160 styleName="MasterStyleNameOne", 161 localisedFamilyName={"fr": "Montserrat", "ja": "モンセラート"}, 162 ), 163 SourceDescriptor( 164 filename="masters/masterTest2.ufo", 165 path=posix(str((datadir / "masters/masterTest2.ufo").resolve())), 166 name="master.ufo2", 167 layerName=None, 168 location={"Italic": 0.0, "Weight": 1000.0, "Width": 20.0}, 169 copyLib=False, 170 copyInfo=False, 171 copyGroups=False, 172 copyFeatures=False, 173 muteKerning=True, 174 muteInfo=False, 175 mutedGlyphNames=[], 176 familyName="MasterFamilyName", 177 styleName="MasterStyleNameTwo", 178 localisedFamilyName={}, 179 ), 180 SourceDescriptor( 181 filename="masters/masterTest2.ufo", 182 path=posix(str((datadir / "masters/masterTest2.ufo").resolve())), 183 name="master.ufo2", 184 layerName="supports", 185 location={"Italic": 0.0, "Weight": 1000.0, "Width": 20.0}, 186 copyLib=False, 187 copyInfo=False, 188 copyGroups=False, 189 copyFeatures=False, 190 muteKerning=False, 191 muteInfo=False, 192 mutedGlyphNames=[], 193 familyName="MasterFamilyName", 194 styleName="Supports", 195 localisedFamilyName={}, 196 ), 197 SourceDescriptor( 198 filename="masters/masterTest2.ufo", 199 path=posix(str((datadir / "masters/masterTest2.ufo").resolve())), 200 name="master.ufo3", 201 layerName=None, 202 location={"Italic": 1.0, "Weight": 0.0, "Width": 100.0}, 203 copyLib=False, 204 copyGroups=False, 205 copyFeatures=False, 206 muteKerning=False, 207 muteInfo=False, 208 mutedGlyphNames=[], 209 familyName="MasterFamilyName", 210 styleName="FauxItalic", 211 localisedFamilyName={}, 212 ), 213 ], 214 ) 215 216 assert_descriptors_equal( 217 doc.variableFonts, 218 [ 219 VariableFontDescriptor( 220 name="Test_WghtWdth", 221 filename="Test_WghtWdth_different_from_name.ttf", 222 axisSubsets=[ 223 RangeAxisSubsetDescriptor(name="Weight"), 224 RangeAxisSubsetDescriptor(name="Width"), 225 ], 226 lib={"com.vtt.source": "sources/vtt/Test_WghtWdth.vtt"}, 227 ), 228 VariableFontDescriptor( 229 name="Test_Wght", 230 axisSubsets=[RangeAxisSubsetDescriptor(name="Weight")], 231 lib={"com.vtt.source": "sources/vtt/Test_Wght.vtt"}, 232 ), 233 VariableFontDescriptor( 234 name="TestCd_Wght", 235 axisSubsets=[ 236 RangeAxisSubsetDescriptor(name="Weight"), 237 ValueAxisSubsetDescriptor(name="Width", userValue=0), 238 ], 239 ), 240 VariableFontDescriptor( 241 name="TestWd_Wght", 242 axisSubsets=[ 243 RangeAxisSubsetDescriptor(name="Weight"), 244 ValueAxisSubsetDescriptor(name="Width", userValue=1000), 245 ], 246 ), 247 VariableFontDescriptor( 248 name="TestItalic_Wght", 249 axisSubsets=[ 250 RangeAxisSubsetDescriptor(name="Weight"), 251 ValueAxisSubsetDescriptor(name="Italic", userValue=1), 252 ], 253 ), 254 VariableFontDescriptor( 255 name="TestRB_Wght", 256 axisSubsets=[ 257 RangeAxisSubsetDescriptor( 258 name="Weight", userMinimum=400, userDefault=400, userMaximum=700 259 ), 260 ValueAxisSubsetDescriptor(name="Italic", userValue=0), 261 ], 262 ), 263 ], 264 ) 265 266 assert_descriptors_equal( 267 doc.instances, 268 [ 269 InstanceDescriptor( 270 filename="instances/instanceTest1.ufo", 271 path=posix(str((datadir / "instances/instanceTest1.ufo").resolve())), 272 name="instance.ufo1", 273 designLocation={"Weight": 500.0, "Width": 20.0}, 274 familyName="InstanceFamilyName", 275 styleName="InstanceStyleName", 276 postScriptFontName="InstancePostscriptName", 277 styleMapFamilyName="InstanceStyleMapFamilyName", 278 styleMapStyleName="InstanceStyleMapStyleName", 279 localisedFamilyName={"fr": "Montserrat", "ja": "モンセラート"}, 280 localisedStyleName={"fr": "Demigras", "ja": "半ば"}, 281 localisedStyleMapFamilyName={ 282 "de": "Montserrat Halbfett", 283 "ja": "モンセラート SemiBold", 284 }, 285 localisedStyleMapStyleName={"de": "Standard"}, 286 glyphs={"arrow": {"mute": True, "unicodes": [291, 292, 293]}}, 287 lib={ 288 "com.coolDesignspaceApp.binaryData": b"<binary gunk>", 289 "com.coolDesignspaceApp.specimenText": "Hamburgerwhatever", 290 }, 291 ), 292 InstanceDescriptor( 293 filename="instances/instanceTest2.ufo", 294 path=posix(str((datadir / "instances/instanceTest2.ufo").resolve())), 295 name="instance.ufo2", 296 designLocation={"Weight": 500.0, "Width": (400.0, 300.0)}, 297 familyName="InstanceFamilyName", 298 styleName="InstanceStyleName", 299 postScriptFontName="InstancePostscriptName", 300 styleMapFamilyName="InstanceStyleMapFamilyName", 301 styleMapStyleName="InstanceStyleMapStyleName", 302 glyphs={ 303 "arrow": { 304 "unicodes": [101, 201, 301], 305 "note": "A note about this glyph", 306 "instanceLocation": {"Weight": 120.0, "Width": 100.0}, 307 "masters": [ 308 { 309 "font": "master.ufo1", 310 "location": {"Weight": 20.0, "Width": 20.0}, 311 "glyphName": "BB", 312 }, 313 { 314 "font": "master.ufo2", 315 "location": {"Weight": 900.0, "Width": 900.0}, 316 "glyphName": "CC", 317 }, 318 ], 319 }, 320 "arrow2": {}, 321 }, 322 ), 323 InstanceDescriptor( 324 locationLabel="Some Style", 325 ), 326 InstanceDescriptor( 327 designLocation={"Weight": 600.0, "Width": (401.0, 420.0)}, 328 ), 329 InstanceDescriptor( 330 designLocation={"Weight": 10.0, "Italic": 0.0}, 331 userLocation={"Width": 100.0}, 332 ), 333 InstanceDescriptor( 334 userLocation={"Weight": 300.0, "Width": 130.0, "Italic": 1.0}, 335 ), 336 ], 337 ) 338 339 340def test_read_v5_document_decovar(datadir): 341 doc = DesignSpaceDocument.fromfile(datadir / "test_v5_decovar.designspace") 342 343 assert not doc.variableFonts 344 345 assert_descriptors_equal( 346 doc.axes, 347 [ 348 AxisDescriptor( 349 default=0, maximum=1000, minimum=0, name="Inline", tag="BLDA" 350 ), 351 AxisDescriptor( 352 default=0, maximum=1000, minimum=0, name="Shearded", tag="TRMD" 353 ), 354 AxisDescriptor( 355 default=0, maximum=1000, minimum=0, name="Rounded Slab", tag="TRMC" 356 ), 357 AxisDescriptor( 358 default=0, maximum=1000, minimum=0, name="Stripes", tag="SKLD" 359 ), 360 AxisDescriptor( 361 default=0, maximum=1000, minimum=0, name="Worm Terminal", tag="TRML" 362 ), 363 AxisDescriptor( 364 default=0, maximum=1000, minimum=0, name="Inline Skeleton", tag="SKLA" 365 ), 366 AxisDescriptor( 367 default=0, 368 maximum=1000, 369 minimum=0, 370 name="Open Inline Terminal", 371 tag="TRMF", 372 ), 373 AxisDescriptor( 374 default=0, maximum=1000, minimum=0, name="Inline Terminal", tag="TRMK" 375 ), 376 AxisDescriptor(default=0, maximum=1000, minimum=0, name="Worm", tag="BLDB"), 377 AxisDescriptor( 378 default=0, maximum=1000, minimum=0, name="Weight", tag="WMX2" 379 ), 380 AxisDescriptor( 381 default=0, maximum=1000, minimum=0, name="Flared", tag="TRMB" 382 ), 383 AxisDescriptor( 384 default=0, maximum=1000, minimum=0, name="Rounded", tag="TRMA" 385 ), 386 AxisDescriptor( 387 default=0, maximum=1000, minimum=0, name="Worm Skeleton", tag="SKLB" 388 ), 389 AxisDescriptor(default=0, maximum=1000, minimum=0, name="Slab", tag="TRMG"), 390 AxisDescriptor( 391 default=0, maximum=1000, minimum=0, name="Bifurcated", tag="TRME" 392 ), 393 ], 394 ) 395 396 assert_descriptors_equal( 397 doc.locationLabels, 398 [ 399 LocationLabelDescriptor(name="Default", elidable=True, userLocation={}), 400 LocationLabelDescriptor( 401 name="Open", userLocation={"Inline": 1000}, labelNames={"de": "Offen"} 402 ), 403 LocationLabelDescriptor(name="Worm", userLocation={"Worm": 1000}), 404 LocationLabelDescriptor( 405 name="Checkered", userLocation={"Inline Skeleton": 1000} 406 ), 407 LocationLabelDescriptor( 408 name="Checkered Reverse", userLocation={"Inline Terminal": 1000} 409 ), 410 LocationLabelDescriptor(name="Striped", userLocation={"Stripes": 500}), 411 LocationLabelDescriptor(name="Rounded", userLocation={"Rounded": 1000}), 412 LocationLabelDescriptor(name="Flared", userLocation={"Flared": 1000}), 413 LocationLabelDescriptor( 414 name="Flared Open", 415 userLocation={"Inline Skeleton": 1000, "Flared": 1000}, 416 ), 417 LocationLabelDescriptor( 418 name="Rounded Slab", userLocation={"Rounded Slab": 1000} 419 ), 420 LocationLabelDescriptor(name="Sheared", userLocation={"Shearded": 1000}), 421 LocationLabelDescriptor( 422 name="Bifurcated", userLocation={"Bifurcated": 1000} 423 ), 424 LocationLabelDescriptor( 425 name="Inline", 426 userLocation={"Inline Skeleton": 500, "Open Inline Terminal": 500}, 427 ), 428 LocationLabelDescriptor(name="Slab", userLocation={"Slab": 1000}), 429 LocationLabelDescriptor(name="Contrast", userLocation={"Weight": 1000}), 430 LocationLabelDescriptor( 431 name="Fancy", 432 userLocation={"Inline Skeleton": 1000, "Flared": 1000, "Weight": 1000}, 433 ), 434 LocationLabelDescriptor( 435 name="Mayhem", 436 userLocation={ 437 "Inline Skeleton": 1000, 438 "Worm Skeleton": 1000, 439 "Rounded": 500, 440 "Flared": 500, 441 "Rounded Slab": 750, 442 "Bifurcated": 500, 443 "Open Inline Terminal": 250, 444 "Slab": 750, 445 "Inline Terminal": 250, 446 "Worm Terminal": 250, 447 "Weight": 750, 448 "Worm": 1000, 449 }, 450 ), 451 ], 452 ) 453 454 assert [i.locationLabel for i in doc.instances] == [ 455 "Default", 456 "Open", 457 "Worm", 458 "Checkered", 459 "Checkered Reverse", 460 "Striped", 461 "Rounded", 462 "Flared", 463 "Flared Open", 464 "Rounded Slab", 465 "Sheared", 466 "Bifurcated", 467 "Inline", 468 "Slab", 469 "Contrast", 470 "Fancy", 471 "Mayhem", 472 ] 473 474 475def test_read_v5_document_discrete(datadir): 476 doc = DesignSpaceDocument.fromfile(datadir / "test_v5_discrete.designspace") 477 478 assert not doc.locationLabels 479 assert not doc.variableFonts 480 481 assert_descriptors_equal( 482 doc.axes, 483 [ 484 DiscreteAxisDescriptor( 485 default=400, 486 values=[400, 700, 900], 487 name="Weight", 488 tag="wght", 489 axisLabels=[ 490 AxisLabelDescriptor( 491 name="Regular", 492 userValue=400, 493 elidable=True, 494 linkedUserValue=700, 495 ), 496 AxisLabelDescriptor(name="Bold", userValue=700), 497 AxisLabelDescriptor(name="Black", userValue=900), 498 ], 499 ), 500 DiscreteAxisDescriptor( 501 default=100, 502 values=[75, 100], 503 name="Width", 504 tag="wdth", 505 axisLabels=[ 506 AxisLabelDescriptor(name="Narrow", userValue=75), 507 AxisLabelDescriptor(name="Normal", userValue=100, elidable=True), 508 ], 509 ), 510 DiscreteAxisDescriptor( 511 default=0, 512 values=[0, 1], 513 name="Italic", 514 tag="ital", 515 axisLabels=[ 516 AxisLabelDescriptor( 517 name="Roman", userValue=0, elidable=True, linkedUserValue=1 518 ), 519 AxisLabelDescriptor(name="Italic", userValue=1), 520 ], 521 ), 522 ], 523 ) 524 525 526def test_read_v5_document_aktiv(datadir): 527 doc = DesignSpaceDocument.fromfile(datadir / "test_v5_aktiv.designspace") 528 529 assert not doc.locationLabels 530 531 assert_descriptors_equal( 532 doc.axes, 533 [ 534 AxisDescriptor( 535 tag="wght", 536 name="Weight", 537 minimum=100, 538 default=400, 539 maximum=900, 540 map=[ 541 (100, 22), 542 (200, 38), 543 (300, 57), 544 (400, 84), 545 (500, 98), 546 (600, 115), 547 (700, 133), 548 (800, 158), 549 (900, 185), 550 ], 551 axisOrdering=1, 552 axisLabels=[ 553 AxisLabelDescriptor(name="Hair", userValue=100), 554 AxisLabelDescriptor(userValue=200, name="Thin"), 555 AxisLabelDescriptor(userValue=300, name="Light"), 556 AxisLabelDescriptor( 557 userValue=400, 558 name="Regular", 559 elidable=True, 560 linkedUserValue=700, 561 ), 562 AxisLabelDescriptor(userValue=500, name="Medium"), 563 AxisLabelDescriptor(userValue=600, name="SemiBold"), 564 AxisLabelDescriptor(userValue=700, name="Bold"), 565 AxisLabelDescriptor(userValue=800, name="XBold"), 566 AxisLabelDescriptor(userValue=900, name="Black"), 567 ], 568 ), 569 AxisDescriptor( 570 tag="wdth", 571 name="Width", 572 minimum=75, 573 default=100, 574 maximum=125, 575 axisOrdering=0, 576 axisLabels=[ 577 AxisLabelDescriptor(name="Cd", userValue=75), 578 AxisLabelDescriptor(name="Normal", elidable=True, userValue=100), 579 AxisLabelDescriptor(name="Ex", userValue=125), 580 ], 581 ), 582 AxisDescriptor( 583 tag="ital", 584 name="Italic", 585 minimum=0, 586 default=0, 587 maximum=1, 588 axisOrdering=2, 589 axisLabels=[ 590 AxisLabelDescriptor( 591 name="Upright", userValue=0, elidable=True, linkedUserValue=1 592 ), 593 AxisLabelDescriptor(name="Italic", userValue=1), 594 ], 595 ), 596 ], 597 ) 598 599 assert_descriptors_equal( 600 doc.variableFonts, 601 [ 602 VariableFontDescriptor( 603 name="AktivGroteskVF_WghtWdthItal", 604 axisSubsets=[ 605 RangeAxisSubsetDescriptor(name="Weight"), 606 RangeAxisSubsetDescriptor(name="Width"), 607 RangeAxisSubsetDescriptor(name="Italic"), 608 ], 609 ), 610 VariableFontDescriptor( 611 name="AktivGroteskVF_WghtWdth", 612 axisSubsets=[ 613 RangeAxisSubsetDescriptor(name="Weight"), 614 RangeAxisSubsetDescriptor(name="Width"), 615 ], 616 ), 617 VariableFontDescriptor( 618 name="AktivGroteskVF_Wght", 619 axisSubsets=[ 620 RangeAxisSubsetDescriptor(name="Weight"), 621 ], 622 ), 623 VariableFontDescriptor( 624 name="AktivGroteskVF_Italics_WghtWdth", 625 axisSubsets=[ 626 RangeAxisSubsetDescriptor(name="Weight"), 627 RangeAxisSubsetDescriptor(name="Width"), 628 ValueAxisSubsetDescriptor(name="Italic", userValue=1), 629 ], 630 ), 631 VariableFontDescriptor( 632 name="AktivGroteskVF_Italics_Wght", 633 axisSubsets=[ 634 RangeAxisSubsetDescriptor(name="Weight"), 635 ValueAxisSubsetDescriptor(name="Italic", userValue=1), 636 ], 637 ), 638 ], 639 ) 640 641 642@pytest.fixture 643def map_doc(): 644 """Generate a document with a few axes to test the mapping functions""" 645 doc = DesignSpaceDocument() 646 doc.addAxis( 647 AxisDescriptor( 648 tag="wght", 649 name="Weight", 650 minimum=100, 651 maximum=900, 652 default=100, 653 map=[(100, 10), (900, 90)], 654 ) 655 ) 656 doc.addAxis( 657 AxisDescriptor( 658 tag="wdth", 659 name="Width", 660 minimum=75, 661 maximum=200, 662 default=100, 663 map=[(75, 7500), (100, 10000), (200, 20000)], 664 ) 665 ) 666 doc.addAxis( 667 AxisDescriptor(tag="CUST", name="Custom", minimum=1, maximum=2, default=1.5) 668 ) 669 doc.addLocationLabel( 670 LocationLabelDescriptor( 671 name="Wonky", userLocation={"Weight": 800, "Custom": 1.2} 672 ) 673 ) 674 return doc 675 676 677def test_doc_location_map_forward(map_doc: DesignSpaceDocument): 678 assert map_doc.map_forward({"Weight": 400, "Width": 150, "Custom": 2}) == { 679 "Weight": 40, 680 "Width": 15000, 681 "Custom": 2, 682 }, "The mappings should be used to compute the design locations" 683 assert map_doc.map_forward({"Weight": 400}) == { 684 "Weight": 40, 685 "Width": 10000, 686 "Custom": 1.5, 687 }, "Missing user locations should be assumed equal to the axis's default" 688 689 690def test_doc_location_map_backward(map_doc: DesignSpaceDocument): 691 assert map_doc.map_backward({"Weight": 40, "Width": 15000, "Custom": 2}) == { 692 "Weight": 400, 693 "Width": 150, 694 "Custom": 2, 695 }, "The mappings should be used to compute the user locations" 696 assert map_doc.map_backward({"Weight": 40}) == { 697 "Weight": 400, 698 "Width": 100, 699 "Custom": 1.5, 700 }, "Missing design locations should be assumed equal to the axis's default" 701 assert map_doc.map_backward( 702 {"Weight": (40, 50), "Width": (15000, 100000), "Custom": (2, 1.5)} 703 ) == { 704 "Weight": 400, 705 "Width": 150, 706 "Custom": 2, 707 }, "Only the xvalue of anisotropic locations is used" 708 709 710def test_instance_location_from_label(map_doc): 711 inst = InstanceDescriptor(locationLabel="Wonky") 712 assert inst.getFullUserLocation(map_doc) == { 713 "Weight": 800, 714 "Width": 100, 715 "Custom": 1.2, 716 }, "an instance with a locationLabel uses the user location from that label, empty values on the label use axis defaults" 717 assert inst.getFullDesignLocation(map_doc) == { 718 "Weight": 80, 719 "Width": 10000, 720 "Custom": 1.2, 721 }, "an instance with a locationLabel computes the design location from that label, empty values on the label use axis defaults" 722 723 inst = InstanceDescriptor(locationLabel="Wonky", userLocation={"Width": 200}) 724 assert inst.getFullUserLocation(map_doc) == { 725 "Weight": 800, 726 "Width": 100, 727 "Custom": 1.2, 728 }, "an instance with a locationLabel uses the user location from that label, other location values are ignored" 729 assert inst.getFullDesignLocation(map_doc) == { 730 "Weight": 80, 731 "Width": 10000, 732 "Custom": 1.2, 733 }, "an instance with a locationLabel computes the design location from that label, other location values are ignored" 734 735 736def test_instance_location_no_data(map_doc): 737 inst = InstanceDescriptor() 738 assert inst.getFullUserLocation(map_doc) == { 739 "Weight": 100, 740 "Width": 100, 741 "Custom": 1.5, 742 }, "an instance without any location data has the default user location" 743 assert inst.getFullDesignLocation(map_doc) == { 744 "Weight": 10, 745 "Width": 10000, 746 "Custom": 1.5, 747 }, "an instance without any location data has the default design location" 748 749 750def test_instance_location_design_first(map_doc): 751 inst = InstanceDescriptor( 752 designLocation={"Weight": (60, 61), "Width": 11000, "Custom": 1.2}, 753 userLocation={"Weight": 700, "Width": 180, "Custom": 1.4}, 754 ) 755 assert inst.getFullUserLocation(map_doc) == { 756 "Weight": 600, 757 "Width": 110, 758 "Custom": 1.2, 759 }, "when both design and user location data are provided, design wins" 760 assert inst.getFullDesignLocation(map_doc) == { 761 "Weight": (60, 61), 762 "Width": 11000, 763 "Custom": 1.2, 764 }, "when both design and user location data are provided, design wins (incl. anisotropy)" 765 766 767def test_instance_location_mix(map_doc): 768 inst = InstanceDescriptor( 769 designLocation={"Weight": (60, 61)}, 770 userLocation={"Width": 180}, 771 ) 772 assert inst.getFullUserLocation(map_doc) == { 773 "Weight": 600, 774 "Width": 180, 775 "Custom": 1.5, 776 }, "instance location is a mix of design and user locations" 777 assert inst.getFullDesignLocation(map_doc) == { 778 "Weight": (60, 61), 779 "Width": 18000, 780 "Custom": 1.5, 781 }, "instance location is a mix of design and user location" 782 783 784@pytest.mark.parametrize( 785 "filename", 786 [ 787 "test_v4_original.designspace", 788 "test_v5_original.designspace", 789 "test_v5_aktiv.designspace", 790 "test_v5_decovar.designspace", 791 "test_v5_discrete.designspace", 792 "test_v5_sourceserif.designspace", 793 "test_v5.designspace", 794 ], 795) 796def test_roundtrip(tmpdir, datadir, filename): 797 test_file = datadir / filename 798 output_path = tmpdir / filename 799 # Move the file to the tmpdir so that the filenames stay the same 800 # (they're relative to the file's path) 801 shutil.copy(test_file, output_path) 802 doc = DesignSpaceDocument.fromfile(output_path) 803 doc.write(output_path) 804 # The input XML has comments and empty lines for documentation purposes 805 xml = test_file.read_text(encoding="utf-8") 806 xml = re.sub( 807 r"<!-- ROUNDTRIP_TEST_REMOVE_ME_BEGIN -->(.|\n)*?<!-- ROUNDTRIP_TEST_REMOVE_ME_END -->", 808 "", 809 xml, 810 ) 811 xml = re.sub(r"<!--(.|\n)*?-->", "", xml) 812 xml = re.sub(r"\s*\n+", "\n", xml) 813 assert output_path.read_text(encoding="utf-8") == xml 814 815 816def test_using_v5_features_upgrades_format(tmpdir, datadir): 817 test_file = datadir / "test_v4_original.designspace" 818 output_4_path = tmpdir / "test_v4.designspace" 819 output_5_path = tmpdir / "test_v5.designspace" 820 shutil.copy(test_file, output_4_path) 821 doc = DesignSpaceDocument.fromfile(output_4_path) 822 doc.write(output_4_path) 823 assert 'format="4.1"' in output_4_path.read_text(encoding="utf-8") 824 doc.addVariableFont(VariableFontDescriptor(name="TestVF")) 825 doc.write(output_5_path) 826 assert 'format="5.0"' in output_5_path.read_text(encoding="utf-8") 827 828 829def test_addAxisDescriptor_discrete(): 830 ds = DesignSpaceDocument() 831 832 axis = ds.addAxisDescriptor( 833 name="Italic", 834 tag="ital", 835 values=[0, 1], 836 default=0, 837 hidden=True, 838 map=[(0, -12), (1, 0)], 839 axisOrdering=3, 840 axisLabels=[ 841 AxisLabelDescriptor( 842 name="Roman", 843 userValue=0, 844 elidable=True, 845 olderSibling=True, 846 linkedUserValue=1, 847 labelNames={"fr": "Romain"}, 848 ) 849 ], 850 ) 851 852 assert ds.axes[0] is axis 853 assert_descriptors_equal( 854 [axis], 855 [ 856 DiscreteAxisDescriptor( 857 tag="ital", 858 name="Italic", 859 values=[0, 1], 860 default=0, 861 hidden=True, 862 map=[(0, -12), (1, 0)], 863 axisOrdering=3, 864 axisLabels=[ 865 AxisLabelDescriptor( 866 name="Roman", 867 userValue=0, 868 elidable=True, 869 olderSibling=True, 870 linkedUserValue=1, 871 labelNames={"fr": "Romain"}, 872 ) 873 ], 874 ) 875 ], 876 ) 877 878 879def test_addLocationLabelDescriptor(): 880 ds = DesignSpaceDocument() 881 882 label = ds.addLocationLabelDescriptor( 883 name="Somewhere", 884 userLocation={}, 885 elidable=True, 886 olderSibling=True, 887 labelNames={"fr": "Quelque part"}, 888 ) 889 890 assert ds.locationLabels[0] is label 891 assert_descriptors_equal( 892 [label], 893 [ 894 LocationLabelDescriptor( 895 name="Somewhere", 896 userLocation={}, 897 elidable=True, 898 olderSibling=True, 899 labelNames={"fr": "Quelque part"}, 900 ) 901 ], 902 ) 903 904 905def test_addVariableFontDescriptor(): 906 ds = DesignSpaceDocument() 907 908 vf = ds.addVariableFontDescriptor(name="TestVF", filename="TestVF.ttf") 909 910 assert ds.variableFonts[0] is vf 911 assert_descriptors_equal( 912 [vf], [VariableFontDescriptor(name="TestVF", filename="TestVF.ttf")] 913 ) 914