1*e1fe3e4aSElliott Hughesfrom fontTools.misc.fixedTools import floatToFixedToFloat 2*e1fe3e4aSElliott Hughesfrom fontTools.misc.roundTools import noRound 3*e1fe3e4aSElliott Hughesfrom fontTools.misc.testTools import stripVariableItemsFromTTX 4*e1fe3e4aSElliott Hughesfrom fontTools.misc.textTools import Tag 5*e1fe3e4aSElliott Hughesfrom fontTools import ttLib 6*e1fe3e4aSElliott Hughesfrom fontTools import designspaceLib 7*e1fe3e4aSElliott Hughesfrom fontTools.feaLib.builder import addOpenTypeFeaturesFromString 8*e1fe3e4aSElliott Hughesfrom fontTools.ttLib.tables import _f_v_a_r, _g_l_y_f 9*e1fe3e4aSElliott Hughesfrom fontTools.ttLib.tables import otTables 10*e1fe3e4aSElliott Hughesfrom fontTools.ttLib.tables.TupleVariation import TupleVariation 11*e1fe3e4aSElliott Hughesfrom fontTools import varLib 12*e1fe3e4aSElliott Hughesfrom fontTools.varLib import instancer 13*e1fe3e4aSElliott Hughesfrom fontTools.varLib.mvar import MVAR_ENTRIES 14*e1fe3e4aSElliott Hughesfrom fontTools.varLib import builder 15*e1fe3e4aSElliott Hughesfrom fontTools.varLib import featureVars 16*e1fe3e4aSElliott Hughesfrom fontTools.varLib import models 17*e1fe3e4aSElliott Hughesimport collections 18*e1fe3e4aSElliott Hughesfrom copy import deepcopy 19*e1fe3e4aSElliott Hughesfrom io import BytesIO, StringIO 20*e1fe3e4aSElliott Hughesimport logging 21*e1fe3e4aSElliott Hughesimport os 22*e1fe3e4aSElliott Hughesimport re 23*e1fe3e4aSElliott Hughesfrom types import SimpleNamespace 24*e1fe3e4aSElliott Hughesimport pytest 25*e1fe3e4aSElliott Hughes 26*e1fe3e4aSElliott Hughes 27*e1fe3e4aSElliott Hughes# see Tests/varLib/instancer/conftest.py for "varfont" fixture definition 28*e1fe3e4aSElliott Hughes 29*e1fe3e4aSElliott HughesTESTDATA = os.path.join(os.path.dirname(__file__), "data") 30*e1fe3e4aSElliott Hughes 31*e1fe3e4aSElliott Hughes 32*e1fe3e4aSElliott Hughes@pytest.fixture(params=[True, False], ids=["optimize", "no-optimize"]) 33*e1fe3e4aSElliott Hughesdef optimize(request): 34*e1fe3e4aSElliott Hughes return request.param 35*e1fe3e4aSElliott Hughes 36*e1fe3e4aSElliott Hughes 37*e1fe3e4aSElliott Hughes@pytest.fixture 38*e1fe3e4aSElliott Hughesdef fvarAxes(): 39*e1fe3e4aSElliott Hughes wght = _f_v_a_r.Axis() 40*e1fe3e4aSElliott Hughes wght.axisTag = Tag("wght") 41*e1fe3e4aSElliott Hughes wght.minValue = 100 42*e1fe3e4aSElliott Hughes wght.defaultValue = 400 43*e1fe3e4aSElliott Hughes wght.maxValue = 900 44*e1fe3e4aSElliott Hughes wdth = _f_v_a_r.Axis() 45*e1fe3e4aSElliott Hughes wdth.axisTag = Tag("wdth") 46*e1fe3e4aSElliott Hughes wdth.minValue = 70 47*e1fe3e4aSElliott Hughes wdth.defaultValue = 100 48*e1fe3e4aSElliott Hughes wdth.maxValue = 100 49*e1fe3e4aSElliott Hughes return [wght, wdth] 50*e1fe3e4aSElliott Hughes 51*e1fe3e4aSElliott Hughes 52*e1fe3e4aSElliott Hughesdef _get_coordinates(varfont, glyphname): 53*e1fe3e4aSElliott Hughes # converts GlyphCoordinates to a list of (x, y) tuples, so that pytest's 54*e1fe3e4aSElliott Hughes # assert will give us a nicer diff 55*e1fe3e4aSElliott Hughes return list( 56*e1fe3e4aSElliott Hughes varfont["glyf"]._getCoordinatesAndControls( 57*e1fe3e4aSElliott Hughes glyphname, 58*e1fe3e4aSElliott Hughes varfont["hmtx"].metrics, 59*e1fe3e4aSElliott Hughes varfont["vmtx"].metrics, 60*e1fe3e4aSElliott Hughes # the tests expect float coordinates 61*e1fe3e4aSElliott Hughes round=noRound, 62*e1fe3e4aSElliott Hughes )[0] 63*e1fe3e4aSElliott Hughes ) 64*e1fe3e4aSElliott Hughes 65*e1fe3e4aSElliott Hughes 66*e1fe3e4aSElliott Hughesclass InstantiateGvarTest(object): 67*e1fe3e4aSElliott Hughes @pytest.mark.parametrize("glyph_name", ["hyphen"]) 68*e1fe3e4aSElliott Hughes @pytest.mark.parametrize( 69*e1fe3e4aSElliott Hughes "location, expected", 70*e1fe3e4aSElliott Hughes [ 71*e1fe3e4aSElliott Hughes pytest.param( 72*e1fe3e4aSElliott Hughes {"wdth": -1.0}, 73*e1fe3e4aSElliott Hughes { 74*e1fe3e4aSElliott Hughes "hyphen": [ 75*e1fe3e4aSElliott Hughes (27, 229), 76*e1fe3e4aSElliott Hughes (27, 310), 77*e1fe3e4aSElliott Hughes (247, 310), 78*e1fe3e4aSElliott Hughes (247, 229), 79*e1fe3e4aSElliott Hughes (0, 0), 80*e1fe3e4aSElliott Hughes (274, 0), 81*e1fe3e4aSElliott Hughes (0, 536), 82*e1fe3e4aSElliott Hughes (0, 0), 83*e1fe3e4aSElliott Hughes ] 84*e1fe3e4aSElliott Hughes }, 85*e1fe3e4aSElliott Hughes id="wdth=-1.0", 86*e1fe3e4aSElliott Hughes ), 87*e1fe3e4aSElliott Hughes pytest.param( 88*e1fe3e4aSElliott Hughes {"wdth": -0.5}, 89*e1fe3e4aSElliott Hughes { 90*e1fe3e4aSElliott Hughes "hyphen": [ 91*e1fe3e4aSElliott Hughes (33.5, 229), 92*e1fe3e4aSElliott Hughes (33.5, 308.5), 93*e1fe3e4aSElliott Hughes (264.5, 308.5), 94*e1fe3e4aSElliott Hughes (264.5, 229), 95*e1fe3e4aSElliott Hughes (0, 0), 96*e1fe3e4aSElliott Hughes (298, 0), 97*e1fe3e4aSElliott Hughes (0, 536), 98*e1fe3e4aSElliott Hughes (0, 0), 99*e1fe3e4aSElliott Hughes ] 100*e1fe3e4aSElliott Hughes }, 101*e1fe3e4aSElliott Hughes id="wdth=-0.5", 102*e1fe3e4aSElliott Hughes ), 103*e1fe3e4aSElliott Hughes # an axis pinned at the default normalized location (0.0) means 104*e1fe3e4aSElliott Hughes # the default glyf outline stays the same 105*e1fe3e4aSElliott Hughes pytest.param( 106*e1fe3e4aSElliott Hughes {"wdth": 0.0}, 107*e1fe3e4aSElliott Hughes { 108*e1fe3e4aSElliott Hughes "hyphen": [ 109*e1fe3e4aSElliott Hughes (40, 229), 110*e1fe3e4aSElliott Hughes (40, 307), 111*e1fe3e4aSElliott Hughes (282, 307), 112*e1fe3e4aSElliott Hughes (282, 229), 113*e1fe3e4aSElliott Hughes (0, 0), 114*e1fe3e4aSElliott Hughes (322, 0), 115*e1fe3e4aSElliott Hughes (0, 536), 116*e1fe3e4aSElliott Hughes (0, 0), 117*e1fe3e4aSElliott Hughes ] 118*e1fe3e4aSElliott Hughes }, 119*e1fe3e4aSElliott Hughes id="wdth=0.0", 120*e1fe3e4aSElliott Hughes ), 121*e1fe3e4aSElliott Hughes ], 122*e1fe3e4aSElliott Hughes ) 123*e1fe3e4aSElliott Hughes def test_pin_and_drop_axis(self, varfont, glyph_name, location, expected, optimize): 124*e1fe3e4aSElliott Hughes location = instancer.NormalizedAxisLimits(location) 125*e1fe3e4aSElliott Hughes 126*e1fe3e4aSElliott Hughes instancer.instantiateGvar(varfont, location, optimize=optimize) 127*e1fe3e4aSElliott Hughes 128*e1fe3e4aSElliott Hughes assert _get_coordinates(varfont, glyph_name) == expected[glyph_name] 129*e1fe3e4aSElliott Hughes 130*e1fe3e4aSElliott Hughes # check that the pinned axis has been dropped from gvar 131*e1fe3e4aSElliott Hughes assert not any( 132*e1fe3e4aSElliott Hughes "wdth" in t.axes 133*e1fe3e4aSElliott Hughes for tuples in varfont["gvar"].variations.values() 134*e1fe3e4aSElliott Hughes for t in tuples 135*e1fe3e4aSElliott Hughes ) 136*e1fe3e4aSElliott Hughes 137*e1fe3e4aSElliott Hughes def test_full_instance(self, varfont, optimize): 138*e1fe3e4aSElliott Hughes location = instancer.NormalizedAxisLimits(wght=0.0, wdth=-0.5) 139*e1fe3e4aSElliott Hughes 140*e1fe3e4aSElliott Hughes instancer.instantiateGvar(varfont, location, optimize=optimize) 141*e1fe3e4aSElliott Hughes 142*e1fe3e4aSElliott Hughes assert _get_coordinates(varfont, "hyphen") == [ 143*e1fe3e4aSElliott Hughes (33.5, 229), 144*e1fe3e4aSElliott Hughes (33.5, 308.5), 145*e1fe3e4aSElliott Hughes (264.5, 308.5), 146*e1fe3e4aSElliott Hughes (264.5, 229), 147*e1fe3e4aSElliott Hughes (0, 0), 148*e1fe3e4aSElliott Hughes (298, 0), 149*e1fe3e4aSElliott Hughes (0, 536), 150*e1fe3e4aSElliott Hughes (0, 0), 151*e1fe3e4aSElliott Hughes ] 152*e1fe3e4aSElliott Hughes 153*e1fe3e4aSElliott Hughes assert "gvar" not in varfont 154*e1fe3e4aSElliott Hughes 155*e1fe3e4aSElliott Hughes def test_composite_glyph_not_in_gvar(self, varfont): 156*e1fe3e4aSElliott Hughes """The 'minus' glyph is a composite glyph, which references 'hyphen' as a 157*e1fe3e4aSElliott Hughes component, but has no tuple variations in gvar table, so the component offset 158*e1fe3e4aSElliott Hughes and the phantom points do not change; however the sidebearings and bounding box 159*e1fe3e4aSElliott Hughes do change as a result of the parent glyph 'hyphen' changing. 160*e1fe3e4aSElliott Hughes """ 161*e1fe3e4aSElliott Hughes hmtx = varfont["hmtx"] 162*e1fe3e4aSElliott Hughes vmtx = varfont["vmtx"] 163*e1fe3e4aSElliott Hughes 164*e1fe3e4aSElliott Hughes hyphenCoords = _get_coordinates(varfont, "hyphen") 165*e1fe3e4aSElliott Hughes assert hyphenCoords == [ 166*e1fe3e4aSElliott Hughes (40, 229), 167*e1fe3e4aSElliott Hughes (40, 307), 168*e1fe3e4aSElliott Hughes (282, 307), 169*e1fe3e4aSElliott Hughes (282, 229), 170*e1fe3e4aSElliott Hughes (0, 0), 171*e1fe3e4aSElliott Hughes (322, 0), 172*e1fe3e4aSElliott Hughes (0, 536), 173*e1fe3e4aSElliott Hughes (0, 0), 174*e1fe3e4aSElliott Hughes ] 175*e1fe3e4aSElliott Hughes assert hmtx["hyphen"] == (322, 40) 176*e1fe3e4aSElliott Hughes assert vmtx["hyphen"] == (536, 229) 177*e1fe3e4aSElliott Hughes 178*e1fe3e4aSElliott Hughes minusCoords = _get_coordinates(varfont, "minus") 179*e1fe3e4aSElliott Hughes assert minusCoords == [(0, 0), (0, 0), (422, 0), (0, 536), (0, 0)] 180*e1fe3e4aSElliott Hughes assert hmtx["minus"] == (422, 40) 181*e1fe3e4aSElliott Hughes assert vmtx["minus"] == (536, 229) 182*e1fe3e4aSElliott Hughes 183*e1fe3e4aSElliott Hughes location = instancer.NormalizedAxisLimits(wght=-1.0, wdth=-1.0) 184*e1fe3e4aSElliott Hughes 185*e1fe3e4aSElliott Hughes instancer.instantiateGvar(varfont, location) 186*e1fe3e4aSElliott Hughes 187*e1fe3e4aSElliott Hughes # check 'hyphen' coordinates changed 188*e1fe3e4aSElliott Hughes assert _get_coordinates(varfont, "hyphen") == [ 189*e1fe3e4aSElliott Hughes (26, 259), 190*e1fe3e4aSElliott Hughes (26, 286), 191*e1fe3e4aSElliott Hughes (237, 286), 192*e1fe3e4aSElliott Hughes (237, 259), 193*e1fe3e4aSElliott Hughes (0, 0), 194*e1fe3e4aSElliott Hughes (263, 0), 195*e1fe3e4aSElliott Hughes (0, 536), 196*e1fe3e4aSElliott Hughes (0, 0), 197*e1fe3e4aSElliott Hughes ] 198*e1fe3e4aSElliott Hughes # check 'minus' coordinates (i.e. component offset and phantom points) 199*e1fe3e4aSElliott Hughes # did _not_ change 200*e1fe3e4aSElliott Hughes assert _get_coordinates(varfont, "minus") == minusCoords 201*e1fe3e4aSElliott Hughes 202*e1fe3e4aSElliott Hughes assert hmtx["hyphen"] == (263, 26) 203*e1fe3e4aSElliott Hughes assert vmtx["hyphen"] == (536, 250) 204*e1fe3e4aSElliott Hughes 205*e1fe3e4aSElliott Hughes assert hmtx["minus"] == (422, 26) # 'minus' left sidebearing changed 206*e1fe3e4aSElliott Hughes assert vmtx["minus"] == (536, 250) # 'minus' top sidebearing too 207*e1fe3e4aSElliott Hughes 208*e1fe3e4aSElliott Hughes 209*e1fe3e4aSElliott Hughesclass InstantiateCvarTest(object): 210*e1fe3e4aSElliott Hughes @pytest.mark.parametrize( 211*e1fe3e4aSElliott Hughes "location, expected", 212*e1fe3e4aSElliott Hughes [ 213*e1fe3e4aSElliott Hughes pytest.param({"wght": -1.0}, [500, -400, 150, 250], id="wght=-1.0"), 214*e1fe3e4aSElliott Hughes pytest.param({"wdth": -1.0}, [500, -400, 180, 200], id="wdth=-1.0"), 215*e1fe3e4aSElliott Hughes pytest.param({"wght": -0.5}, [500, -400, 165, 250], id="wght=-0.5"), 216*e1fe3e4aSElliott Hughes pytest.param({"wdth": -0.3}, [500, -400, 180, 235], id="wdth=-0.3"), 217*e1fe3e4aSElliott Hughes ], 218*e1fe3e4aSElliott Hughes ) 219*e1fe3e4aSElliott Hughes def test_pin_and_drop_axis(self, varfont, location, expected): 220*e1fe3e4aSElliott Hughes location = instancer.NormalizedAxisLimits(location) 221*e1fe3e4aSElliott Hughes 222*e1fe3e4aSElliott Hughes instancer.instantiateCvar(varfont, location) 223*e1fe3e4aSElliott Hughes 224*e1fe3e4aSElliott Hughes assert list(varfont["cvt "].values) == expected 225*e1fe3e4aSElliott Hughes 226*e1fe3e4aSElliott Hughes # check that the pinned axis has been dropped from cvar 227*e1fe3e4aSElliott Hughes pinned_axes = location.keys() 228*e1fe3e4aSElliott Hughes assert not any( 229*e1fe3e4aSElliott Hughes axis in t.axes for t in varfont["cvar"].variations for axis in pinned_axes 230*e1fe3e4aSElliott Hughes ) 231*e1fe3e4aSElliott Hughes 232*e1fe3e4aSElliott Hughes def test_full_instance(self, varfont): 233*e1fe3e4aSElliott Hughes location = instancer.NormalizedAxisLimits(wght=-0.5, wdth=-0.5) 234*e1fe3e4aSElliott Hughes 235*e1fe3e4aSElliott Hughes instancer.instantiateCvar(varfont, location) 236*e1fe3e4aSElliott Hughes 237*e1fe3e4aSElliott Hughes assert list(varfont["cvt "].values) == [500, -400, 165, 225] 238*e1fe3e4aSElliott Hughes 239*e1fe3e4aSElliott Hughes assert "cvar" not in varfont 240*e1fe3e4aSElliott Hughes 241*e1fe3e4aSElliott Hughes 242*e1fe3e4aSElliott Hughesclass InstantiateMVARTest(object): 243*e1fe3e4aSElliott Hughes @pytest.mark.parametrize( 244*e1fe3e4aSElliott Hughes "location, expected", 245*e1fe3e4aSElliott Hughes [ 246*e1fe3e4aSElliott Hughes pytest.param( 247*e1fe3e4aSElliott Hughes {"wght": 1.0}, 248*e1fe3e4aSElliott Hughes {"strs": 100, "undo": -200, "unds": 150, "xhgt": 530}, 249*e1fe3e4aSElliott Hughes id="wght=1.0", 250*e1fe3e4aSElliott Hughes ), 251*e1fe3e4aSElliott Hughes pytest.param( 252*e1fe3e4aSElliott Hughes {"wght": 0.5}, 253*e1fe3e4aSElliott Hughes {"strs": 75, "undo": -150, "unds": 100, "xhgt": 515}, 254*e1fe3e4aSElliott Hughes id="wght=0.5", 255*e1fe3e4aSElliott Hughes ), 256*e1fe3e4aSElliott Hughes pytest.param( 257*e1fe3e4aSElliott Hughes {"wght": 0.0}, 258*e1fe3e4aSElliott Hughes {"strs": 50, "undo": -100, "unds": 50, "xhgt": 500}, 259*e1fe3e4aSElliott Hughes id="wght=0.0", 260*e1fe3e4aSElliott Hughes ), 261*e1fe3e4aSElliott Hughes pytest.param( 262*e1fe3e4aSElliott Hughes {"wdth": -1.0}, 263*e1fe3e4aSElliott Hughes {"strs": 20, "undo": -100, "unds": 50, "xhgt": 500}, 264*e1fe3e4aSElliott Hughes id="wdth=-1.0", 265*e1fe3e4aSElliott Hughes ), 266*e1fe3e4aSElliott Hughes pytest.param( 267*e1fe3e4aSElliott Hughes {"wdth": -0.5}, 268*e1fe3e4aSElliott Hughes {"strs": 35, "undo": -100, "unds": 50, "xhgt": 500}, 269*e1fe3e4aSElliott Hughes id="wdth=-0.5", 270*e1fe3e4aSElliott Hughes ), 271*e1fe3e4aSElliott Hughes pytest.param( 272*e1fe3e4aSElliott Hughes {"wdth": 0.0}, 273*e1fe3e4aSElliott Hughes {"strs": 50, "undo": -100, "unds": 50, "xhgt": 500}, 274*e1fe3e4aSElliott Hughes id="wdth=0.0", 275*e1fe3e4aSElliott Hughes ), 276*e1fe3e4aSElliott Hughes ], 277*e1fe3e4aSElliott Hughes ) 278*e1fe3e4aSElliott Hughes def test_pin_and_drop_axis(self, varfont, location, expected): 279*e1fe3e4aSElliott Hughes mvar = varfont["MVAR"].table 280*e1fe3e4aSElliott Hughes # initially we have two VarData: the first contains deltas associated with 3 281*e1fe3e4aSElliott Hughes # regions: 1 with only wght, 1 with only wdth, and 1 with both wght and wdth 282*e1fe3e4aSElliott Hughes assert len(mvar.VarStore.VarData) == 2 283*e1fe3e4aSElliott Hughes assert mvar.VarStore.VarRegionList.RegionCount == 3 284*e1fe3e4aSElliott Hughes assert mvar.VarStore.VarData[0].VarRegionCount == 3 285*e1fe3e4aSElliott Hughes assert all(len(item) == 3 for item in mvar.VarStore.VarData[0].Item) 286*e1fe3e4aSElliott Hughes # The second VarData has deltas associated only with 1 region (wght only). 287*e1fe3e4aSElliott Hughes assert mvar.VarStore.VarData[1].VarRegionCount == 1 288*e1fe3e4aSElliott Hughes assert all(len(item) == 1 for item in mvar.VarStore.VarData[1].Item) 289*e1fe3e4aSElliott Hughes 290*e1fe3e4aSElliott Hughes location = instancer.NormalizedAxisLimits(location) 291*e1fe3e4aSElliott Hughes 292*e1fe3e4aSElliott Hughes instancer.instantiateMVAR(varfont, location) 293*e1fe3e4aSElliott Hughes 294*e1fe3e4aSElliott Hughes for mvar_tag, expected_value in expected.items(): 295*e1fe3e4aSElliott Hughes table_tag, item_name = MVAR_ENTRIES[mvar_tag] 296*e1fe3e4aSElliott Hughes assert getattr(varfont[table_tag], item_name) == expected_value 297*e1fe3e4aSElliott Hughes 298*e1fe3e4aSElliott Hughes # check that regions and accompanying deltas have been dropped 299*e1fe3e4aSElliott Hughes num_regions_left = len(mvar.VarStore.VarRegionList.Region) 300*e1fe3e4aSElliott Hughes assert num_regions_left < 3 301*e1fe3e4aSElliott Hughes assert mvar.VarStore.VarRegionList.RegionCount == num_regions_left 302*e1fe3e4aSElliott Hughes assert mvar.VarStore.VarData[0].VarRegionCount == num_regions_left 303*e1fe3e4aSElliott Hughes # VarData subtables have been merged 304*e1fe3e4aSElliott Hughes assert len(mvar.VarStore.VarData) == 1 305*e1fe3e4aSElliott Hughes 306*e1fe3e4aSElliott Hughes @pytest.mark.parametrize( 307*e1fe3e4aSElliott Hughes "location, expected, sync_vmetrics", 308*e1fe3e4aSElliott Hughes [ 309*e1fe3e4aSElliott Hughes pytest.param( 310*e1fe3e4aSElliott Hughes {"wght": 1.0, "wdth": 0.0}, 311*e1fe3e4aSElliott Hughes {"strs": 100, "undo": -200, "unds": 150, "hasc": 1100}, 312*e1fe3e4aSElliott Hughes True, 313*e1fe3e4aSElliott Hughes id="wght=1.0,wdth=0.0", 314*e1fe3e4aSElliott Hughes ), 315*e1fe3e4aSElliott Hughes pytest.param( 316*e1fe3e4aSElliott Hughes {"wght": 0.0, "wdth": -1.0}, 317*e1fe3e4aSElliott Hughes {"strs": 20, "undo": -100, "unds": 50, "hasc": 1000}, 318*e1fe3e4aSElliott Hughes True, 319*e1fe3e4aSElliott Hughes id="wght=0.0,wdth=-1.0", 320*e1fe3e4aSElliott Hughes ), 321*e1fe3e4aSElliott Hughes pytest.param( 322*e1fe3e4aSElliott Hughes {"wght": 0.5, "wdth": -0.5}, 323*e1fe3e4aSElliott Hughes {"strs": 55, "undo": -145, "unds": 95, "hasc": 1050}, 324*e1fe3e4aSElliott Hughes True, 325*e1fe3e4aSElliott Hughes id="wght=0.5,wdth=-0.5", 326*e1fe3e4aSElliott Hughes ), 327*e1fe3e4aSElliott Hughes pytest.param( 328*e1fe3e4aSElliott Hughes {"wght": 1.0, "wdth": -1.0}, 329*e1fe3e4aSElliott Hughes {"strs": 50, "undo": -180, "unds": 130, "hasc": 1100}, 330*e1fe3e4aSElliott Hughes True, 331*e1fe3e4aSElliott Hughes id="wght=0.5,wdth=-0.5", 332*e1fe3e4aSElliott Hughes ), 333*e1fe3e4aSElliott Hughes pytest.param( 334*e1fe3e4aSElliott Hughes {"wght": 1.0, "wdth": 0.0}, 335*e1fe3e4aSElliott Hughes {"strs": 100, "undo": -200, "unds": 150, "hasc": 1100}, 336*e1fe3e4aSElliott Hughes False, 337*e1fe3e4aSElliott Hughes id="wght=1.0,wdth=0.0,no_sync_vmetrics", 338*e1fe3e4aSElliott Hughes ), 339*e1fe3e4aSElliott Hughes ], 340*e1fe3e4aSElliott Hughes ) 341*e1fe3e4aSElliott Hughes def test_full_instance(self, varfont, location, sync_vmetrics, expected): 342*e1fe3e4aSElliott Hughes location = instancer.NormalizedAxisLimits(location) 343*e1fe3e4aSElliott Hughes 344*e1fe3e4aSElliott Hughes # check vertical metrics are in sync before... 345*e1fe3e4aSElliott Hughes if sync_vmetrics: 346*e1fe3e4aSElliott Hughes assert varfont["OS/2"].sTypoAscender == varfont["hhea"].ascender 347*e1fe3e4aSElliott Hughes assert varfont["OS/2"].sTypoDescender == varfont["hhea"].descender 348*e1fe3e4aSElliott Hughes assert varfont["OS/2"].sTypoLineGap == varfont["hhea"].lineGap 349*e1fe3e4aSElliott Hughes else: 350*e1fe3e4aSElliott Hughes # force them not to be in sync 351*e1fe3e4aSElliott Hughes varfont["OS/2"].sTypoDescender -= 100 352*e1fe3e4aSElliott Hughes varfont["OS/2"].sTypoLineGap += 200 353*e1fe3e4aSElliott Hughes 354*e1fe3e4aSElliott Hughes instancer.instantiateMVAR(varfont, location) 355*e1fe3e4aSElliott Hughes 356*e1fe3e4aSElliott Hughes for mvar_tag, expected_value in expected.items(): 357*e1fe3e4aSElliott Hughes table_tag, item_name = MVAR_ENTRIES[mvar_tag] 358*e1fe3e4aSElliott Hughes assert getattr(varfont[table_tag], item_name) == expected_value 359*e1fe3e4aSElliott Hughes 360*e1fe3e4aSElliott Hughes # ... as well as after instancing, but only if they were already 361*e1fe3e4aSElliott Hughes # https://github.com/fonttools/fonttools/issues/3297 362*e1fe3e4aSElliott Hughes if sync_vmetrics: 363*e1fe3e4aSElliott Hughes assert varfont["OS/2"].sTypoAscender == varfont["hhea"].ascender 364*e1fe3e4aSElliott Hughes assert varfont["OS/2"].sTypoDescender == varfont["hhea"].descender 365*e1fe3e4aSElliott Hughes assert varfont["OS/2"].sTypoLineGap == varfont["hhea"].lineGap 366*e1fe3e4aSElliott Hughes else: 367*e1fe3e4aSElliott Hughes assert varfont["OS/2"].sTypoDescender != varfont["hhea"].descender 368*e1fe3e4aSElliott Hughes assert varfont["OS/2"].sTypoLineGap != varfont["hhea"].lineGap 369*e1fe3e4aSElliott Hughes 370*e1fe3e4aSElliott Hughes assert "MVAR" not in varfont 371*e1fe3e4aSElliott Hughes 372*e1fe3e4aSElliott Hughes 373*e1fe3e4aSElliott Hughesclass InstantiateHVARTest(object): 374*e1fe3e4aSElliott Hughes # the 'expectedDeltas' below refer to the VarData item deltas for the "hyphen" 375*e1fe3e4aSElliott Hughes # glyph in the PartialInstancerTest-VF.ttx test font, that are left after 376*e1fe3e4aSElliott Hughes # partial instancing 377*e1fe3e4aSElliott Hughes @pytest.mark.parametrize( 378*e1fe3e4aSElliott Hughes "location, expectedRegions, expectedDeltas", 379*e1fe3e4aSElliott Hughes [ 380*e1fe3e4aSElliott Hughes ({"wght": -1.0}, [{"wdth": (-1.0, -1.0, 0)}], [-59]), 381*e1fe3e4aSElliott Hughes ({"wght": 0}, [{"wdth": (-1.0, -1.0, 0)}], [-48]), 382*e1fe3e4aSElliott Hughes ({"wght": 1.0}, [{"wdth": (-1.0, -1.0, 0)}], [7]), 383*e1fe3e4aSElliott Hughes ( 384*e1fe3e4aSElliott Hughes {"wdth": -1.0}, 385*e1fe3e4aSElliott Hughes [ 386*e1fe3e4aSElliott Hughes {"wght": (-1.0, -1.0, 0.0)}, 387*e1fe3e4aSElliott Hughes {"wght": (0.0, 0.6099854, 1.0)}, 388*e1fe3e4aSElliott Hughes {"wght": (0.6099854, 1.0, 1.0)}, 389*e1fe3e4aSElliott Hughes ], 390*e1fe3e4aSElliott Hughes [-11, 31, 51], 391*e1fe3e4aSElliott Hughes ), 392*e1fe3e4aSElliott Hughes ({"wdth": 0}, [{"wght": (0.6099854, 1.0, 1.0)}], [-4]), 393*e1fe3e4aSElliott Hughes ], 394*e1fe3e4aSElliott Hughes ) 395*e1fe3e4aSElliott Hughes def test_partial_instance(self, varfont, location, expectedRegions, expectedDeltas): 396*e1fe3e4aSElliott Hughes location = instancer.NormalizedAxisLimits(location) 397*e1fe3e4aSElliott Hughes 398*e1fe3e4aSElliott Hughes instancer.instantiateHVAR(varfont, location) 399*e1fe3e4aSElliott Hughes 400*e1fe3e4aSElliott Hughes assert "HVAR" in varfont 401*e1fe3e4aSElliott Hughes hvar = varfont["HVAR"].table 402*e1fe3e4aSElliott Hughes varStore = hvar.VarStore 403*e1fe3e4aSElliott Hughes 404*e1fe3e4aSElliott Hughes regions = varStore.VarRegionList.Region 405*e1fe3e4aSElliott Hughes fvarAxes = [a for a in varfont["fvar"].axes if a.axisTag not in location] 406*e1fe3e4aSElliott Hughes regionDicts = [reg.get_support(fvarAxes) for reg in regions] 407*e1fe3e4aSElliott Hughes assert len(regionDicts) == len(expectedRegions) 408*e1fe3e4aSElliott Hughes for region, expectedRegion in zip(regionDicts, expectedRegions): 409*e1fe3e4aSElliott Hughes assert region.keys() == expectedRegion.keys() 410*e1fe3e4aSElliott Hughes for axisTag, support in region.items(): 411*e1fe3e4aSElliott Hughes assert support == pytest.approx(expectedRegion[axisTag]) 412*e1fe3e4aSElliott Hughes 413*e1fe3e4aSElliott Hughes assert len(varStore.VarData) == 1 414*e1fe3e4aSElliott Hughes assert varStore.VarData[0].ItemCount == 2 415*e1fe3e4aSElliott Hughes 416*e1fe3e4aSElliott Hughes assert hvar.AdvWidthMap is not None 417*e1fe3e4aSElliott Hughes advWithMap = hvar.AdvWidthMap.mapping 418*e1fe3e4aSElliott Hughes 419*e1fe3e4aSElliott Hughes assert advWithMap[".notdef"] == advWithMap["space"] 420*e1fe3e4aSElliott Hughes varIdx = advWithMap[".notdef"] 421*e1fe3e4aSElliott Hughes # these glyphs have no metrics variations in the test font 422*e1fe3e4aSElliott Hughes assert varStore.VarData[varIdx >> 16].Item[varIdx & 0xFFFF] == ( 423*e1fe3e4aSElliott Hughes [0] * varStore.VarData[0].VarRegionCount 424*e1fe3e4aSElliott Hughes ) 425*e1fe3e4aSElliott Hughes 426*e1fe3e4aSElliott Hughes varIdx = advWithMap["hyphen"] 427*e1fe3e4aSElliott Hughes assert varStore.VarData[varIdx >> 16].Item[varIdx & 0xFFFF] == expectedDeltas 428*e1fe3e4aSElliott Hughes 429*e1fe3e4aSElliott Hughes def test_full_instance(self, varfont): 430*e1fe3e4aSElliott Hughes location = instancer.NormalizedAxisLimits(wght=0, wdth=0) 431*e1fe3e4aSElliott Hughes 432*e1fe3e4aSElliott Hughes instancer.instantiateHVAR(varfont, location) 433*e1fe3e4aSElliott Hughes 434*e1fe3e4aSElliott Hughes assert "HVAR" not in varfont 435*e1fe3e4aSElliott Hughes 436*e1fe3e4aSElliott Hughes def test_partial_instance_keep_empty_table(self, varfont): 437*e1fe3e4aSElliott Hughes # Append an additional dummy axis to fvar, for which the current HVAR table 438*e1fe3e4aSElliott Hughes # in our test 'varfont' contains no variation data. 439*e1fe3e4aSElliott Hughes # Instancing the other two wght and wdth axes should leave HVAR table empty, 440*e1fe3e4aSElliott Hughes # to signal there are variations to the glyph's advance widths. 441*e1fe3e4aSElliott Hughes fvar = varfont["fvar"] 442*e1fe3e4aSElliott Hughes axis = _f_v_a_r.Axis() 443*e1fe3e4aSElliott Hughes axis.axisTag = "TEST" 444*e1fe3e4aSElliott Hughes fvar.axes.append(axis) 445*e1fe3e4aSElliott Hughes 446*e1fe3e4aSElliott Hughes location = instancer.NormalizedAxisLimits(wght=0, wdth=0) 447*e1fe3e4aSElliott Hughes 448*e1fe3e4aSElliott Hughes instancer.instantiateHVAR(varfont, location) 449*e1fe3e4aSElliott Hughes 450*e1fe3e4aSElliott Hughes assert "HVAR" in varfont 451*e1fe3e4aSElliott Hughes 452*e1fe3e4aSElliott Hughes varStore = varfont["HVAR"].table.VarStore 453*e1fe3e4aSElliott Hughes 454*e1fe3e4aSElliott Hughes assert varStore.VarRegionList.RegionCount == 0 455*e1fe3e4aSElliott Hughes assert not varStore.VarRegionList.Region 456*e1fe3e4aSElliott Hughes assert varStore.VarRegionList.RegionAxisCount == 1 457*e1fe3e4aSElliott Hughes 458*e1fe3e4aSElliott Hughes 459*e1fe3e4aSElliott Hughesclass InstantiateItemVariationStoreTest(object): 460*e1fe3e4aSElliott Hughes def test_VarRegion_get_support(self): 461*e1fe3e4aSElliott Hughes axisOrder = ["wght", "wdth", "opsz"] 462*e1fe3e4aSElliott Hughes regionAxes = {"wdth": (-1.0, -1.0, 0.0), "wght": (0.0, 1.0, 1.0)} 463*e1fe3e4aSElliott Hughes region = builder.buildVarRegion(regionAxes, axisOrder) 464*e1fe3e4aSElliott Hughes 465*e1fe3e4aSElliott Hughes assert len(region.VarRegionAxis) == 3 466*e1fe3e4aSElliott Hughes assert region.VarRegionAxis[2].PeakCoord == 0 467*e1fe3e4aSElliott Hughes 468*e1fe3e4aSElliott Hughes fvarAxes = [SimpleNamespace(axisTag=axisTag) for axisTag in axisOrder] 469*e1fe3e4aSElliott Hughes 470*e1fe3e4aSElliott Hughes assert region.get_support(fvarAxes) == regionAxes 471*e1fe3e4aSElliott Hughes 472*e1fe3e4aSElliott Hughes @pytest.fixture 473*e1fe3e4aSElliott Hughes def varStore(self): 474*e1fe3e4aSElliott Hughes return builder.buildVarStore( 475*e1fe3e4aSElliott Hughes builder.buildVarRegionList( 476*e1fe3e4aSElliott Hughes [ 477*e1fe3e4aSElliott Hughes {"wght": (-1.0, -1.0, 0)}, 478*e1fe3e4aSElliott Hughes {"wght": (0, 0.5, 1.0)}, 479*e1fe3e4aSElliott Hughes {"wght": (0.5, 1.0, 1.0)}, 480*e1fe3e4aSElliott Hughes {"wdth": (-1.0, -1.0, 0)}, 481*e1fe3e4aSElliott Hughes {"wght": (-1.0, -1.0, 0), "wdth": (-1.0, -1.0, 0)}, 482*e1fe3e4aSElliott Hughes {"wght": (0, 0.5, 1.0), "wdth": (-1.0, -1.0, 0)}, 483*e1fe3e4aSElliott Hughes {"wght": (0.5, 1.0, 1.0), "wdth": (-1.0, -1.0, 0)}, 484*e1fe3e4aSElliott Hughes ], 485*e1fe3e4aSElliott Hughes ["wght", "wdth"], 486*e1fe3e4aSElliott Hughes ), 487*e1fe3e4aSElliott Hughes [ 488*e1fe3e4aSElliott Hughes builder.buildVarData([0, 1, 2], [[100, 100, 100], [100, 100, 100]]), 489*e1fe3e4aSElliott Hughes builder.buildVarData( 490*e1fe3e4aSElliott Hughes [3, 4, 5, 6], [[100, 100, 100, 100], [100, 100, 100, 100]] 491*e1fe3e4aSElliott Hughes ), 492*e1fe3e4aSElliott Hughes ], 493*e1fe3e4aSElliott Hughes ) 494*e1fe3e4aSElliott Hughes 495*e1fe3e4aSElliott Hughes @pytest.mark.parametrize( 496*e1fe3e4aSElliott Hughes "location, expected_deltas, num_regions", 497*e1fe3e4aSElliott Hughes [ 498*e1fe3e4aSElliott Hughes ({"wght": 0}, [[0, 0], [0, 0]], 1), 499*e1fe3e4aSElliott Hughes ({"wght": 0.25}, [[50, 50], [0, 0]], 1), 500*e1fe3e4aSElliott Hughes ({"wdth": 0}, [[0, 0], [0, 0]], 3), 501*e1fe3e4aSElliott Hughes ({"wdth": -0.75}, [[0, 0], [75, 75]], 3), 502*e1fe3e4aSElliott Hughes ({"wght": 0, "wdth": 0}, [[0, 0], [0, 0]], 0), 503*e1fe3e4aSElliott Hughes ({"wght": 0.25, "wdth": 0}, [[50, 50], [0, 0]], 0), 504*e1fe3e4aSElliott Hughes ({"wght": 0, "wdth": -0.75}, [[0, 0], [75, 75]], 0), 505*e1fe3e4aSElliott Hughes ], 506*e1fe3e4aSElliott Hughes ) 507*e1fe3e4aSElliott Hughes def test_instantiate_default_deltas( 508*e1fe3e4aSElliott Hughes self, varStore, fvarAxes, location, expected_deltas, num_regions 509*e1fe3e4aSElliott Hughes ): 510*e1fe3e4aSElliott Hughes location = instancer.NormalizedAxisLimits(location) 511*e1fe3e4aSElliott Hughes 512*e1fe3e4aSElliott Hughes defaultDeltas = instancer.instantiateItemVariationStore( 513*e1fe3e4aSElliott Hughes varStore, fvarAxes, location 514*e1fe3e4aSElliott Hughes ) 515*e1fe3e4aSElliott Hughes 516*e1fe3e4aSElliott Hughes defaultDeltaArray = [] 517*e1fe3e4aSElliott Hughes for varidx, delta in sorted(defaultDeltas.items()): 518*e1fe3e4aSElliott Hughes if varidx == varStore.NO_VARIATION_INDEX: 519*e1fe3e4aSElliott Hughes continue 520*e1fe3e4aSElliott Hughes major, minor = varidx >> 16, varidx & 0xFFFF 521*e1fe3e4aSElliott Hughes if major == len(defaultDeltaArray): 522*e1fe3e4aSElliott Hughes defaultDeltaArray.append([]) 523*e1fe3e4aSElliott Hughes assert len(defaultDeltaArray[major]) == minor 524*e1fe3e4aSElliott Hughes defaultDeltaArray[major].append(delta) 525*e1fe3e4aSElliott Hughes 526*e1fe3e4aSElliott Hughes assert defaultDeltaArray == expected_deltas 527*e1fe3e4aSElliott Hughes assert varStore.VarRegionList.RegionCount == num_regions 528*e1fe3e4aSElliott Hughes 529*e1fe3e4aSElliott Hughes 530*e1fe3e4aSElliott Hughesclass TupleVarStoreAdapterTest(object): 531*e1fe3e4aSElliott Hughes def test_instantiate(self): 532*e1fe3e4aSElliott Hughes regions = [ 533*e1fe3e4aSElliott Hughes {"wght": (-1.0, -1.0, 0)}, 534*e1fe3e4aSElliott Hughes {"wght": (0.0, 1.0, 1.0)}, 535*e1fe3e4aSElliott Hughes {"wdth": (-1.0, -1.0, 0)}, 536*e1fe3e4aSElliott Hughes {"wght": (-1.0, -1.0, 0), "wdth": (-1.0, -1.0, 0)}, 537*e1fe3e4aSElliott Hughes {"wght": (0, 1.0, 1.0), "wdth": (-1.0, -1.0, 0)}, 538*e1fe3e4aSElliott Hughes ] 539*e1fe3e4aSElliott Hughes axisOrder = ["wght", "wdth"] 540*e1fe3e4aSElliott Hughes tupleVarData = [ 541*e1fe3e4aSElliott Hughes [ 542*e1fe3e4aSElliott Hughes TupleVariation({"wght": (-1.0, -1.0, 0)}, [10, 70]), 543*e1fe3e4aSElliott Hughes TupleVariation({"wght": (0.0, 1.0, 1.0)}, [30, 90]), 544*e1fe3e4aSElliott Hughes TupleVariation( 545*e1fe3e4aSElliott Hughes {"wght": (-1.0, -1.0, 0), "wdth": (-1.0, -1.0, 0)}, [-40, -100] 546*e1fe3e4aSElliott Hughes ), 547*e1fe3e4aSElliott Hughes TupleVariation( 548*e1fe3e4aSElliott Hughes {"wght": (0, 1.0, 1.0), "wdth": (-1.0, -1.0, 0)}, [-60, -120] 549*e1fe3e4aSElliott Hughes ), 550*e1fe3e4aSElliott Hughes ], 551*e1fe3e4aSElliott Hughes [ 552*e1fe3e4aSElliott Hughes TupleVariation({"wdth": (-1.0, -1.0, 0)}, [5, 45]), 553*e1fe3e4aSElliott Hughes TupleVariation( 554*e1fe3e4aSElliott Hughes {"wght": (-1.0, -1.0, 0), "wdth": (-1.0, -1.0, 0)}, [-15, -55] 555*e1fe3e4aSElliott Hughes ), 556*e1fe3e4aSElliott Hughes TupleVariation( 557*e1fe3e4aSElliott Hughes {"wght": (0, 1.0, 1.0), "wdth": (-1.0, -1.0, 0)}, [-35, -75] 558*e1fe3e4aSElliott Hughes ), 559*e1fe3e4aSElliott Hughes ], 560*e1fe3e4aSElliott Hughes ] 561*e1fe3e4aSElliott Hughes adapter = instancer._TupleVarStoreAdapter( 562*e1fe3e4aSElliott Hughes regions, axisOrder, tupleVarData, itemCounts=[2, 2] 563*e1fe3e4aSElliott Hughes ) 564*e1fe3e4aSElliott Hughes location = instancer.NormalizedAxisLimits(wght=0.5) 565*e1fe3e4aSElliott Hughes 566*e1fe3e4aSElliott Hughes defaultDeltaArray = adapter.instantiate(location) 567*e1fe3e4aSElliott Hughes 568*e1fe3e4aSElliott Hughes assert defaultDeltaArray == [[15, 45], [0, 0]] 569*e1fe3e4aSElliott Hughes assert adapter.regions == [{"wdth": (-1.0, -1.0, 0)}] 570*e1fe3e4aSElliott Hughes assert adapter.tupleVarData == [ 571*e1fe3e4aSElliott Hughes [TupleVariation({"wdth": (-1.0, -1.0, 0)}, [-30, -60])], 572*e1fe3e4aSElliott Hughes [TupleVariation({"wdth": (-1.0, -1.0, 0)}, [-12, 8])], 573*e1fe3e4aSElliott Hughes ] 574*e1fe3e4aSElliott Hughes 575*e1fe3e4aSElliott Hughes def test_rebuildRegions(self): 576*e1fe3e4aSElliott Hughes regions = [ 577*e1fe3e4aSElliott Hughes {"wght": (-1.0, -1.0, 0)}, 578*e1fe3e4aSElliott Hughes {"wght": (0.0, 1.0, 1.0)}, 579*e1fe3e4aSElliott Hughes {"wdth": (-1.0, -1.0, 0)}, 580*e1fe3e4aSElliott Hughes {"wght": (-1.0, -1.0, 0), "wdth": (-1.0, -1.0, 0)}, 581*e1fe3e4aSElliott Hughes {"wght": (0, 1.0, 1.0), "wdth": (-1.0, -1.0, 0)}, 582*e1fe3e4aSElliott Hughes ] 583*e1fe3e4aSElliott Hughes axisOrder = ["wght", "wdth"] 584*e1fe3e4aSElliott Hughes variations = [] 585*e1fe3e4aSElliott Hughes for region in regions: 586*e1fe3e4aSElliott Hughes variations.append(TupleVariation(region, [100])) 587*e1fe3e4aSElliott Hughes tupleVarData = [variations[:3], variations[3:]] 588*e1fe3e4aSElliott Hughes adapter = instancer._TupleVarStoreAdapter( 589*e1fe3e4aSElliott Hughes regions, axisOrder, tupleVarData, itemCounts=[1, 1] 590*e1fe3e4aSElliott Hughes ) 591*e1fe3e4aSElliott Hughes 592*e1fe3e4aSElliott Hughes adapter.rebuildRegions() 593*e1fe3e4aSElliott Hughes 594*e1fe3e4aSElliott Hughes assert adapter.regions == regions 595*e1fe3e4aSElliott Hughes 596*e1fe3e4aSElliott Hughes del tupleVarData[0][2] 597*e1fe3e4aSElliott Hughes tupleVarData[1][0].axes = {"wght": (-1.0, -0.5, 0)} 598*e1fe3e4aSElliott Hughes tupleVarData[1][1].axes = {"wght": (0, 0.5, 1.0)} 599*e1fe3e4aSElliott Hughes 600*e1fe3e4aSElliott Hughes adapter.rebuildRegions() 601*e1fe3e4aSElliott Hughes 602*e1fe3e4aSElliott Hughes assert adapter.regions == [ 603*e1fe3e4aSElliott Hughes {"wght": (-1.0, -1.0, 0)}, 604*e1fe3e4aSElliott Hughes {"wght": (0.0, 1.0, 1.0)}, 605*e1fe3e4aSElliott Hughes {"wght": (-1.0, -0.5, 0)}, 606*e1fe3e4aSElliott Hughes {"wght": (0, 0.5, 1.0)}, 607*e1fe3e4aSElliott Hughes ] 608*e1fe3e4aSElliott Hughes 609*e1fe3e4aSElliott Hughes def test_roundtrip(self, fvarAxes): 610*e1fe3e4aSElliott Hughes regions = [ 611*e1fe3e4aSElliott Hughes {"wght": (-1.0, -1.0, 0)}, 612*e1fe3e4aSElliott Hughes {"wght": (0, 0.5, 1.0)}, 613*e1fe3e4aSElliott Hughes {"wght": (0.5, 1.0, 1.0)}, 614*e1fe3e4aSElliott Hughes {"wdth": (-1.0, -1.0, 0)}, 615*e1fe3e4aSElliott Hughes {"wght": (-1.0, -1.0, 0), "wdth": (-1.0, -1.0, 0)}, 616*e1fe3e4aSElliott Hughes {"wght": (0, 0.5, 1.0), "wdth": (-1.0, -1.0, 0)}, 617*e1fe3e4aSElliott Hughes {"wght": (0.5, 1.0, 1.0), "wdth": (-1.0, -1.0, 0)}, 618*e1fe3e4aSElliott Hughes ] 619*e1fe3e4aSElliott Hughes axisOrder = [axis.axisTag for axis in fvarAxes] 620*e1fe3e4aSElliott Hughes 621*e1fe3e4aSElliott Hughes itemVarStore = builder.buildVarStore( 622*e1fe3e4aSElliott Hughes builder.buildVarRegionList(regions, axisOrder), 623*e1fe3e4aSElliott Hughes [ 624*e1fe3e4aSElliott Hughes builder.buildVarData( 625*e1fe3e4aSElliott Hughes [0, 1, 2, 4, 5, 6], 626*e1fe3e4aSElliott Hughes [[10, -20, 30, -40, 50, -60], [70, -80, 90, -100, 110, -120]], 627*e1fe3e4aSElliott Hughes ), 628*e1fe3e4aSElliott Hughes builder.buildVarData( 629*e1fe3e4aSElliott Hughes [3, 4, 5, 6], [[5, -15, 25, -35], [45, -55, 65, -75]] 630*e1fe3e4aSElliott Hughes ), 631*e1fe3e4aSElliott Hughes ], 632*e1fe3e4aSElliott Hughes ) 633*e1fe3e4aSElliott Hughes 634*e1fe3e4aSElliott Hughes adapter = instancer._TupleVarStoreAdapter.fromItemVarStore( 635*e1fe3e4aSElliott Hughes itemVarStore, fvarAxes 636*e1fe3e4aSElliott Hughes ) 637*e1fe3e4aSElliott Hughes 638*e1fe3e4aSElliott Hughes assert adapter.tupleVarData == [ 639*e1fe3e4aSElliott Hughes [ 640*e1fe3e4aSElliott Hughes TupleVariation({"wght": (-1.0, -1.0, 0)}, [10, 70]), 641*e1fe3e4aSElliott Hughes TupleVariation({"wght": (0, 0.5, 1.0)}, [-20, -80]), 642*e1fe3e4aSElliott Hughes TupleVariation({"wght": (0.5, 1.0, 1.0)}, [30, 90]), 643*e1fe3e4aSElliott Hughes TupleVariation( 644*e1fe3e4aSElliott Hughes {"wght": (-1.0, -1.0, 0), "wdth": (-1.0, -1.0, 0)}, [-40, -100] 645*e1fe3e4aSElliott Hughes ), 646*e1fe3e4aSElliott Hughes TupleVariation( 647*e1fe3e4aSElliott Hughes {"wght": (0, 0.5, 1.0), "wdth": (-1.0, -1.0, 0)}, [50, 110] 648*e1fe3e4aSElliott Hughes ), 649*e1fe3e4aSElliott Hughes TupleVariation( 650*e1fe3e4aSElliott Hughes {"wght": (0.5, 1.0, 1.0), "wdth": (-1.0, -1.0, 0)}, [-60, -120] 651*e1fe3e4aSElliott Hughes ), 652*e1fe3e4aSElliott Hughes ], 653*e1fe3e4aSElliott Hughes [ 654*e1fe3e4aSElliott Hughes TupleVariation({"wdth": (-1.0, -1.0, 0)}, [5, 45]), 655*e1fe3e4aSElliott Hughes TupleVariation( 656*e1fe3e4aSElliott Hughes {"wght": (-1.0, -1.0, 0), "wdth": (-1.0, -1.0, 0)}, [-15, -55] 657*e1fe3e4aSElliott Hughes ), 658*e1fe3e4aSElliott Hughes TupleVariation( 659*e1fe3e4aSElliott Hughes {"wght": (0, 0.5, 1.0), "wdth": (-1.0, -1.0, 0)}, [25, 65] 660*e1fe3e4aSElliott Hughes ), 661*e1fe3e4aSElliott Hughes TupleVariation( 662*e1fe3e4aSElliott Hughes {"wght": (0.5, 1.0, 1.0), "wdth": (-1.0, -1.0, 0)}, [-35, -75] 663*e1fe3e4aSElliott Hughes ), 664*e1fe3e4aSElliott Hughes ], 665*e1fe3e4aSElliott Hughes ] 666*e1fe3e4aSElliott Hughes assert adapter.itemCounts == [data.ItemCount for data in itemVarStore.VarData] 667*e1fe3e4aSElliott Hughes assert adapter.regions == regions 668*e1fe3e4aSElliott Hughes assert adapter.axisOrder == axisOrder 669*e1fe3e4aSElliott Hughes 670*e1fe3e4aSElliott Hughes itemVarStore2 = adapter.asItemVarStore() 671*e1fe3e4aSElliott Hughes 672*e1fe3e4aSElliott Hughes assert [ 673*e1fe3e4aSElliott Hughes reg.get_support(fvarAxes) for reg in itemVarStore2.VarRegionList.Region 674*e1fe3e4aSElliott Hughes ] == regions 675*e1fe3e4aSElliott Hughes 676*e1fe3e4aSElliott Hughes assert itemVarStore2.VarDataCount == 2 677*e1fe3e4aSElliott Hughes assert itemVarStore2.VarData[0].VarRegionIndex == [0, 1, 2, 4, 5, 6] 678*e1fe3e4aSElliott Hughes assert itemVarStore2.VarData[0].Item == [ 679*e1fe3e4aSElliott Hughes [10, -20, 30, -40, 50, -60], 680*e1fe3e4aSElliott Hughes [70, -80, 90, -100, 110, -120], 681*e1fe3e4aSElliott Hughes ] 682*e1fe3e4aSElliott Hughes assert itemVarStore2.VarData[1].VarRegionIndex == [3, 4, 5, 6] 683*e1fe3e4aSElliott Hughes assert itemVarStore2.VarData[1].Item == [[5, -15, 25, -35], [45, -55, 65, -75]] 684*e1fe3e4aSElliott Hughes 685*e1fe3e4aSElliott Hughes 686*e1fe3e4aSElliott Hughesdef makeTTFont(glyphOrder, features): 687*e1fe3e4aSElliott Hughes font = ttLib.TTFont() 688*e1fe3e4aSElliott Hughes font.setGlyphOrder(glyphOrder) 689*e1fe3e4aSElliott Hughes addOpenTypeFeaturesFromString(font, features) 690*e1fe3e4aSElliott Hughes font["name"] = ttLib.newTable("name") 691*e1fe3e4aSElliott Hughes return font 692*e1fe3e4aSElliott Hughes 693*e1fe3e4aSElliott Hughes 694*e1fe3e4aSElliott Hughesdef _makeDSAxesDict(axes): 695*e1fe3e4aSElliott Hughes dsAxes = collections.OrderedDict() 696*e1fe3e4aSElliott Hughes for axisTag, axisValues in axes: 697*e1fe3e4aSElliott Hughes axis = designspaceLib.AxisDescriptor() 698*e1fe3e4aSElliott Hughes axis.name = axis.tag = axis.labelNames["en"] = axisTag 699*e1fe3e4aSElliott Hughes axis.minimum, axis.default, axis.maximum = axisValues 700*e1fe3e4aSElliott Hughes dsAxes[axis.tag] = axis 701*e1fe3e4aSElliott Hughes return dsAxes 702*e1fe3e4aSElliott Hughes 703*e1fe3e4aSElliott Hughes 704*e1fe3e4aSElliott Hughesdef makeVariableFont(masters, baseIndex, axes, masterLocations): 705*e1fe3e4aSElliott Hughes vf = deepcopy(masters[baseIndex]) 706*e1fe3e4aSElliott Hughes dsAxes = _makeDSAxesDict(axes) 707*e1fe3e4aSElliott Hughes fvar = varLib._add_fvar(vf, dsAxes, instances=()) 708*e1fe3e4aSElliott Hughes axisTags = [axis.axisTag for axis in fvar.axes] 709*e1fe3e4aSElliott Hughes normalizedLocs = [models.normalizeLocation(m, dict(axes)) for m in masterLocations] 710*e1fe3e4aSElliott Hughes model = models.VariationModel(normalizedLocs, axisOrder=axisTags) 711*e1fe3e4aSElliott Hughes varLib._merge_OTL(vf, model, masters, axisTags) 712*e1fe3e4aSElliott Hughes return vf 713*e1fe3e4aSElliott Hughes 714*e1fe3e4aSElliott Hughes 715*e1fe3e4aSElliott Hughesdef makeParametrizedVF(glyphOrder, features, values, increments): 716*e1fe3e4aSElliott Hughes # Create a test VF with given glyphs and parametrized OTL features. 717*e1fe3e4aSElliott Hughes # The VF is built from 9 masters (3 x 3 along wght and wdth), with 718*e1fe3e4aSElliott Hughes # locations hard-coded and base master at wght=400 and wdth=100. 719*e1fe3e4aSElliott Hughes # 'values' is a list of initial values that are interpolated in the 720*e1fe3e4aSElliott Hughes # 'features' string, and incremented for each subsequent master by the 721*e1fe3e4aSElliott Hughes # given 'increments' (list of 2-tuple) along the two axes. 722*e1fe3e4aSElliott Hughes assert values and len(values) == len(increments) 723*e1fe3e4aSElliott Hughes assert all(len(i) == 2 for i in increments) 724*e1fe3e4aSElliott Hughes masterLocations = [ 725*e1fe3e4aSElliott Hughes {"wght": 100, "wdth": 50}, 726*e1fe3e4aSElliott Hughes {"wght": 100, "wdth": 100}, 727*e1fe3e4aSElliott Hughes {"wght": 100, "wdth": 150}, 728*e1fe3e4aSElliott Hughes {"wght": 400, "wdth": 50}, 729*e1fe3e4aSElliott Hughes {"wght": 400, "wdth": 100}, # base master 730*e1fe3e4aSElliott Hughes {"wght": 400, "wdth": 150}, 731*e1fe3e4aSElliott Hughes {"wght": 700, "wdth": 50}, 732*e1fe3e4aSElliott Hughes {"wght": 700, "wdth": 100}, 733*e1fe3e4aSElliott Hughes {"wght": 700, "wdth": 150}, 734*e1fe3e4aSElliott Hughes ] 735*e1fe3e4aSElliott Hughes n = len(values) 736*e1fe3e4aSElliott Hughes values = list(values) 737*e1fe3e4aSElliott Hughes masters = [] 738*e1fe3e4aSElliott Hughes for _ in range(3): 739*e1fe3e4aSElliott Hughes for _ in range(3): 740*e1fe3e4aSElliott Hughes master = makeTTFont(glyphOrder, features=features % tuple(values)) 741*e1fe3e4aSElliott Hughes masters.append(master) 742*e1fe3e4aSElliott Hughes for i in range(n): 743*e1fe3e4aSElliott Hughes values[i] += increments[i][1] 744*e1fe3e4aSElliott Hughes for i in range(n): 745*e1fe3e4aSElliott Hughes values[i] += increments[i][0] 746*e1fe3e4aSElliott Hughes baseIndex = 4 747*e1fe3e4aSElliott Hughes axes = [("wght", (100, 400, 700)), ("wdth", (50, 100, 150))] 748*e1fe3e4aSElliott Hughes vf = makeVariableFont(masters, baseIndex, axes, masterLocations) 749*e1fe3e4aSElliott Hughes return vf 750*e1fe3e4aSElliott Hughes 751*e1fe3e4aSElliott Hughes 752*e1fe3e4aSElliott Hughes@pytest.fixture 753*e1fe3e4aSElliott Hughesdef varfontGDEF(): 754*e1fe3e4aSElliott Hughes glyphOrder = [".notdef", "f", "i", "f_i"] 755*e1fe3e4aSElliott Hughes features = ( 756*e1fe3e4aSElliott Hughes "feature liga { sub f i by f_i;} liga;" 757*e1fe3e4aSElliott Hughes "table GDEF { LigatureCaretByPos f_i %d; } GDEF;" 758*e1fe3e4aSElliott Hughes ) 759*e1fe3e4aSElliott Hughes values = [100] 760*e1fe3e4aSElliott Hughes increments = [(+30, +10)] 761*e1fe3e4aSElliott Hughes return makeParametrizedVF(glyphOrder, features, values, increments) 762*e1fe3e4aSElliott Hughes 763*e1fe3e4aSElliott Hughes 764*e1fe3e4aSElliott Hughes@pytest.fixture 765*e1fe3e4aSElliott Hughesdef varfontGPOS(): 766*e1fe3e4aSElliott Hughes glyphOrder = [".notdef", "V", "A"] 767*e1fe3e4aSElliott Hughes features = "feature kern { pos V A %d; } kern;" 768*e1fe3e4aSElliott Hughes values = [-80] 769*e1fe3e4aSElliott Hughes increments = [(-10, -5)] 770*e1fe3e4aSElliott Hughes return makeParametrizedVF(glyphOrder, features, values, increments) 771*e1fe3e4aSElliott Hughes 772*e1fe3e4aSElliott Hughes 773*e1fe3e4aSElliott Hughes@pytest.fixture 774*e1fe3e4aSElliott Hughesdef varfontGPOS2(): 775*e1fe3e4aSElliott Hughes glyphOrder = [".notdef", "V", "A", "acutecomb"] 776*e1fe3e4aSElliott Hughes features = ( 777*e1fe3e4aSElliott Hughes "markClass [acutecomb] <anchor 150 -10> @TOP_MARKS;" 778*e1fe3e4aSElliott Hughes "feature mark {" 779*e1fe3e4aSElliott Hughes " pos base A <anchor %d 450> mark @TOP_MARKS;" 780*e1fe3e4aSElliott Hughes "} mark;" 781*e1fe3e4aSElliott Hughes "feature kern {" 782*e1fe3e4aSElliott Hughes " pos V A %d;" 783*e1fe3e4aSElliott Hughes "} kern;" 784*e1fe3e4aSElliott Hughes ) 785*e1fe3e4aSElliott Hughes values = [200, -80] 786*e1fe3e4aSElliott Hughes increments = [(+30, +10), (-10, -5)] 787*e1fe3e4aSElliott Hughes return makeParametrizedVF(glyphOrder, features, values, increments) 788*e1fe3e4aSElliott Hughes 789*e1fe3e4aSElliott Hughes 790*e1fe3e4aSElliott Hughesclass InstantiateOTLTest(object): 791*e1fe3e4aSElliott Hughes @pytest.mark.parametrize( 792*e1fe3e4aSElliott Hughes "location, expected", 793*e1fe3e4aSElliott Hughes [ 794*e1fe3e4aSElliott Hughes ({"wght": -1.0}, 110), # -60 795*e1fe3e4aSElliott Hughes ({"wght": 0}, 170), 796*e1fe3e4aSElliott Hughes ({"wght": 0.5}, 200), # +30 797*e1fe3e4aSElliott Hughes ({"wght": 1.0}, 230), # +60 798*e1fe3e4aSElliott Hughes ({"wdth": -1.0}, 160), # -10 799*e1fe3e4aSElliott Hughes ({"wdth": -0.3}, 167), # -3 800*e1fe3e4aSElliott Hughes ({"wdth": 0}, 170), 801*e1fe3e4aSElliott Hughes ({"wdth": 1.0}, 180), # +10 802*e1fe3e4aSElliott Hughes ], 803*e1fe3e4aSElliott Hughes ) 804*e1fe3e4aSElliott Hughes def test_pin_and_drop_axis_GDEF(self, varfontGDEF, location, expected): 805*e1fe3e4aSElliott Hughes vf = varfontGDEF 806*e1fe3e4aSElliott Hughes assert "GDEF" in vf 807*e1fe3e4aSElliott Hughes 808*e1fe3e4aSElliott Hughes location = instancer.NormalizedAxisLimits(location) 809*e1fe3e4aSElliott Hughes 810*e1fe3e4aSElliott Hughes instancer.instantiateOTL(vf, location) 811*e1fe3e4aSElliott Hughes 812*e1fe3e4aSElliott Hughes assert "GDEF" in vf 813*e1fe3e4aSElliott Hughes gdef = vf["GDEF"].table 814*e1fe3e4aSElliott Hughes assert gdef.Version == 0x00010003 815*e1fe3e4aSElliott Hughes assert gdef.VarStore 816*e1fe3e4aSElliott Hughes assert gdef.LigCaretList 817*e1fe3e4aSElliott Hughes caretValue = gdef.LigCaretList.LigGlyph[0].CaretValue[0] 818*e1fe3e4aSElliott Hughes assert caretValue.Format == 3 819*e1fe3e4aSElliott Hughes assert hasattr(caretValue, "DeviceTable") 820*e1fe3e4aSElliott Hughes assert caretValue.DeviceTable.DeltaFormat == 0x8000 821*e1fe3e4aSElliott Hughes assert caretValue.Coordinate == expected 822*e1fe3e4aSElliott Hughes 823*e1fe3e4aSElliott Hughes @pytest.mark.parametrize( 824*e1fe3e4aSElliott Hughes "location, expected", 825*e1fe3e4aSElliott Hughes [ 826*e1fe3e4aSElliott Hughes ({"wght": -1.0, "wdth": -1.0}, 100), # -60 - 10 827*e1fe3e4aSElliott Hughes ({"wght": -1.0, "wdth": 0.0}, 110), # -60 828*e1fe3e4aSElliott Hughes ({"wght": -1.0, "wdth": 1.0}, 120), # -60 + 10 829*e1fe3e4aSElliott Hughes ({"wght": 0.0, "wdth": -1.0}, 160), # -10 830*e1fe3e4aSElliott Hughes ({"wght": 0.0, "wdth": 0.0}, 170), 831*e1fe3e4aSElliott Hughes ({"wght": 0.0, "wdth": 1.0}, 180), # +10 832*e1fe3e4aSElliott Hughes ({"wght": 1.0, "wdth": -1.0}, 220), # +60 - 10 833*e1fe3e4aSElliott Hughes ({"wght": 1.0, "wdth": 0.0}, 230), # +60 834*e1fe3e4aSElliott Hughes ({"wght": 1.0, "wdth": 1.0}, 240), # +60 + 10 835*e1fe3e4aSElliott Hughes ], 836*e1fe3e4aSElliott Hughes ) 837*e1fe3e4aSElliott Hughes def test_full_instance_GDEF(self, varfontGDEF, location, expected): 838*e1fe3e4aSElliott Hughes vf = varfontGDEF 839*e1fe3e4aSElliott Hughes assert "GDEF" in vf 840*e1fe3e4aSElliott Hughes 841*e1fe3e4aSElliott Hughes location = instancer.NormalizedAxisLimits(location) 842*e1fe3e4aSElliott Hughes 843*e1fe3e4aSElliott Hughes instancer.instantiateOTL(vf, location) 844*e1fe3e4aSElliott Hughes 845*e1fe3e4aSElliott Hughes assert "GDEF" in vf 846*e1fe3e4aSElliott Hughes gdef = vf["GDEF"].table 847*e1fe3e4aSElliott Hughes assert gdef.Version == 0x00010000 848*e1fe3e4aSElliott Hughes assert not hasattr(gdef, "VarStore") 849*e1fe3e4aSElliott Hughes assert gdef.LigCaretList 850*e1fe3e4aSElliott Hughes caretValue = gdef.LigCaretList.LigGlyph[0].CaretValue[0] 851*e1fe3e4aSElliott Hughes assert caretValue.Format == 1 852*e1fe3e4aSElliott Hughes assert not hasattr(caretValue, "DeviceTable") 853*e1fe3e4aSElliott Hughes assert caretValue.Coordinate == expected 854*e1fe3e4aSElliott Hughes 855*e1fe3e4aSElliott Hughes @pytest.mark.parametrize( 856*e1fe3e4aSElliott Hughes "location, expected", 857*e1fe3e4aSElliott Hughes [ 858*e1fe3e4aSElliott Hughes ({"wght": -1.0}, -85), # +25 859*e1fe3e4aSElliott Hughes ({"wght": 0}, -110), 860*e1fe3e4aSElliott Hughes ({"wght": 1.0}, -135), # -25 861*e1fe3e4aSElliott Hughes ({"wdth": -1.0}, -105), # +5 862*e1fe3e4aSElliott Hughes ({"wdth": 0}, -110), 863*e1fe3e4aSElliott Hughes ({"wdth": 1.0}, -115), # -5 864*e1fe3e4aSElliott Hughes ], 865*e1fe3e4aSElliott Hughes ) 866*e1fe3e4aSElliott Hughes def test_pin_and_drop_axis_GPOS_kern(self, varfontGPOS, location, expected): 867*e1fe3e4aSElliott Hughes vf = varfontGPOS 868*e1fe3e4aSElliott Hughes assert "GDEF" in vf 869*e1fe3e4aSElliott Hughes assert "GPOS" in vf 870*e1fe3e4aSElliott Hughes 871*e1fe3e4aSElliott Hughes location = instancer.NormalizedAxisLimits(location) 872*e1fe3e4aSElliott Hughes 873*e1fe3e4aSElliott Hughes instancer.instantiateOTL(vf, location) 874*e1fe3e4aSElliott Hughes 875*e1fe3e4aSElliott Hughes gdef = vf["GDEF"].table 876*e1fe3e4aSElliott Hughes gpos = vf["GPOS"].table 877*e1fe3e4aSElliott Hughes assert gdef.Version == 0x00010003 878*e1fe3e4aSElliott Hughes assert gdef.VarStore 879*e1fe3e4aSElliott Hughes 880*e1fe3e4aSElliott Hughes assert gpos.LookupList.Lookup[0].LookupType == 2 # PairPos 881*e1fe3e4aSElliott Hughes pairPos = gpos.LookupList.Lookup[0].SubTable[0] 882*e1fe3e4aSElliott Hughes valueRec1 = pairPos.PairSet[0].PairValueRecord[0].Value1 883*e1fe3e4aSElliott Hughes assert valueRec1.XAdvDevice 884*e1fe3e4aSElliott Hughes assert valueRec1.XAdvDevice.DeltaFormat == 0x8000 885*e1fe3e4aSElliott Hughes assert valueRec1.XAdvance == expected 886*e1fe3e4aSElliott Hughes 887*e1fe3e4aSElliott Hughes @pytest.mark.parametrize( 888*e1fe3e4aSElliott Hughes "location, expected", 889*e1fe3e4aSElliott Hughes [ 890*e1fe3e4aSElliott Hughes ({"wght": -1.0, "wdth": -1.0}, -80), # +25 + 5 891*e1fe3e4aSElliott Hughes ({"wght": -1.0, "wdth": 0.0}, -85), # +25 892*e1fe3e4aSElliott Hughes ({"wght": -1.0, "wdth": 1.0}, -90), # +25 - 5 893*e1fe3e4aSElliott Hughes ({"wght": 0.0, "wdth": -1.0}, -105), # +5 894*e1fe3e4aSElliott Hughes ({"wght": 0.0, "wdth": 0.0}, -110), 895*e1fe3e4aSElliott Hughes ({"wght": 0.0, "wdth": 1.0}, -115), # -5 896*e1fe3e4aSElliott Hughes ({"wght": 1.0, "wdth": -1.0}, -130), # -25 + 5 897*e1fe3e4aSElliott Hughes ({"wght": 1.0, "wdth": 0.0}, -135), # -25 898*e1fe3e4aSElliott Hughes ({"wght": 1.0, "wdth": 1.0}, -140), # -25 - 5 899*e1fe3e4aSElliott Hughes ], 900*e1fe3e4aSElliott Hughes ) 901*e1fe3e4aSElliott Hughes def test_full_instance_GPOS_kern(self, varfontGPOS, location, expected): 902*e1fe3e4aSElliott Hughes vf = varfontGPOS 903*e1fe3e4aSElliott Hughes assert "GDEF" in vf 904*e1fe3e4aSElliott Hughes assert "GPOS" in vf 905*e1fe3e4aSElliott Hughes 906*e1fe3e4aSElliott Hughes location = instancer.NormalizedAxisLimits(location) 907*e1fe3e4aSElliott Hughes 908*e1fe3e4aSElliott Hughes instancer.instantiateOTL(vf, location) 909*e1fe3e4aSElliott Hughes 910*e1fe3e4aSElliott Hughes assert "GDEF" not in vf 911*e1fe3e4aSElliott Hughes gpos = vf["GPOS"].table 912*e1fe3e4aSElliott Hughes 913*e1fe3e4aSElliott Hughes assert gpos.LookupList.Lookup[0].LookupType == 2 # PairPos 914*e1fe3e4aSElliott Hughes pairPos = gpos.LookupList.Lookup[0].SubTable[0] 915*e1fe3e4aSElliott Hughes valueRec1 = pairPos.PairSet[0].PairValueRecord[0].Value1 916*e1fe3e4aSElliott Hughes assert not hasattr(valueRec1, "XAdvDevice") 917*e1fe3e4aSElliott Hughes assert valueRec1.XAdvance == expected 918*e1fe3e4aSElliott Hughes 919*e1fe3e4aSElliott Hughes @pytest.mark.parametrize( 920*e1fe3e4aSElliott Hughes "location, expected", 921*e1fe3e4aSElliott Hughes [ 922*e1fe3e4aSElliott Hughes ({"wght": -1.0}, (210, -85)), # -60, +25 923*e1fe3e4aSElliott Hughes ({"wght": 0}, (270, -110)), 924*e1fe3e4aSElliott Hughes ({"wght": 0.5}, (300, -122)), # +30, -12 925*e1fe3e4aSElliott Hughes ({"wght": 1.0}, (330, -135)), # +60, -25 926*e1fe3e4aSElliott Hughes ({"wdth": -1.0}, (260, -105)), # -10, +5 927*e1fe3e4aSElliott Hughes ({"wdth": -0.3}, (267, -108)), # -3, +2 928*e1fe3e4aSElliott Hughes ({"wdth": 0}, (270, -110)), 929*e1fe3e4aSElliott Hughes ({"wdth": 1.0}, (280, -115)), # +10, -5 930*e1fe3e4aSElliott Hughes ], 931*e1fe3e4aSElliott Hughes ) 932*e1fe3e4aSElliott Hughes def test_pin_and_drop_axis_GPOS_mark_and_kern( 933*e1fe3e4aSElliott Hughes self, varfontGPOS2, location, expected 934*e1fe3e4aSElliott Hughes ): 935*e1fe3e4aSElliott Hughes vf = varfontGPOS2 936*e1fe3e4aSElliott Hughes assert "GDEF" in vf 937*e1fe3e4aSElliott Hughes assert "GPOS" in vf 938*e1fe3e4aSElliott Hughes 939*e1fe3e4aSElliott Hughes location = instancer.NormalizedAxisLimits(location) 940*e1fe3e4aSElliott Hughes 941*e1fe3e4aSElliott Hughes instancer.instantiateOTL(vf, location) 942*e1fe3e4aSElliott Hughes 943*e1fe3e4aSElliott Hughes v1, v2 = expected 944*e1fe3e4aSElliott Hughes gdef = vf["GDEF"].table 945*e1fe3e4aSElliott Hughes gpos = vf["GPOS"].table 946*e1fe3e4aSElliott Hughes assert gdef.Version == 0x00010003 947*e1fe3e4aSElliott Hughes assert gdef.VarStore 948*e1fe3e4aSElliott Hughes assert gdef.GlyphClassDef 949*e1fe3e4aSElliott Hughes 950*e1fe3e4aSElliott Hughes assert gpos.LookupList.Lookup[0].LookupType == 4 # MarkBasePos 951*e1fe3e4aSElliott Hughes markBasePos = gpos.LookupList.Lookup[0].SubTable[0] 952*e1fe3e4aSElliott Hughes baseAnchor = markBasePos.BaseArray.BaseRecord[0].BaseAnchor[0] 953*e1fe3e4aSElliott Hughes assert baseAnchor.Format == 3 954*e1fe3e4aSElliott Hughes assert baseAnchor.XDeviceTable 955*e1fe3e4aSElliott Hughes assert baseAnchor.XDeviceTable.DeltaFormat == 0x8000 956*e1fe3e4aSElliott Hughes assert not baseAnchor.YDeviceTable 957*e1fe3e4aSElliott Hughes assert baseAnchor.XCoordinate == v1 958*e1fe3e4aSElliott Hughes assert baseAnchor.YCoordinate == 450 959*e1fe3e4aSElliott Hughes 960*e1fe3e4aSElliott Hughes assert gpos.LookupList.Lookup[1].LookupType == 2 # PairPos 961*e1fe3e4aSElliott Hughes pairPos = gpos.LookupList.Lookup[1].SubTable[0] 962*e1fe3e4aSElliott Hughes valueRec1 = pairPos.PairSet[0].PairValueRecord[0].Value1 963*e1fe3e4aSElliott Hughes assert valueRec1.XAdvDevice 964*e1fe3e4aSElliott Hughes assert valueRec1.XAdvDevice.DeltaFormat == 0x8000 965*e1fe3e4aSElliott Hughes assert valueRec1.XAdvance == v2 966*e1fe3e4aSElliott Hughes 967*e1fe3e4aSElliott Hughes @pytest.mark.parametrize( 968*e1fe3e4aSElliott Hughes "location, expected", 969*e1fe3e4aSElliott Hughes [ 970*e1fe3e4aSElliott Hughes ({"wght": -1.0, "wdth": -1.0}, (200, -80)), # -60 - 10, +25 + 5 971*e1fe3e4aSElliott Hughes ({"wght": -1.0, "wdth": 0.0}, (210, -85)), # -60, +25 972*e1fe3e4aSElliott Hughes ({"wght": -1.0, "wdth": 1.0}, (220, -90)), # -60 + 10, +25 - 5 973*e1fe3e4aSElliott Hughes ({"wght": 0.0, "wdth": -1.0}, (260, -105)), # -10, +5 974*e1fe3e4aSElliott Hughes ({"wght": 0.0, "wdth": 0.0}, (270, -110)), 975*e1fe3e4aSElliott Hughes ({"wght": 0.0, "wdth": 1.0}, (280, -115)), # +10, -5 976*e1fe3e4aSElliott Hughes ({"wght": 1.0, "wdth": -1.0}, (320, -130)), # +60 - 10, -25 + 5 977*e1fe3e4aSElliott Hughes ({"wght": 1.0, "wdth": 0.0}, (330, -135)), # +60, -25 978*e1fe3e4aSElliott Hughes ({"wght": 1.0, "wdth": 1.0}, (340, -140)), # +60 + 10, -25 - 5 979*e1fe3e4aSElliott Hughes ], 980*e1fe3e4aSElliott Hughes ) 981*e1fe3e4aSElliott Hughes def test_full_instance_GPOS_mark_and_kern(self, varfontGPOS2, location, expected): 982*e1fe3e4aSElliott Hughes vf = varfontGPOS2 983*e1fe3e4aSElliott Hughes assert "GDEF" in vf 984*e1fe3e4aSElliott Hughes assert "GPOS" in vf 985*e1fe3e4aSElliott Hughes 986*e1fe3e4aSElliott Hughes location = instancer.NormalizedAxisLimits(location) 987*e1fe3e4aSElliott Hughes 988*e1fe3e4aSElliott Hughes instancer.instantiateOTL(vf, location) 989*e1fe3e4aSElliott Hughes 990*e1fe3e4aSElliott Hughes v1, v2 = expected 991*e1fe3e4aSElliott Hughes gdef = vf["GDEF"].table 992*e1fe3e4aSElliott Hughes gpos = vf["GPOS"].table 993*e1fe3e4aSElliott Hughes assert gdef.Version == 0x00010000 994*e1fe3e4aSElliott Hughes assert not hasattr(gdef, "VarStore") 995*e1fe3e4aSElliott Hughes assert gdef.GlyphClassDef 996*e1fe3e4aSElliott Hughes 997*e1fe3e4aSElliott Hughes assert gpos.LookupList.Lookup[0].LookupType == 4 # MarkBasePos 998*e1fe3e4aSElliott Hughes markBasePos = gpos.LookupList.Lookup[0].SubTable[0] 999*e1fe3e4aSElliott Hughes baseAnchor = markBasePos.BaseArray.BaseRecord[0].BaseAnchor[0] 1000*e1fe3e4aSElliott Hughes assert baseAnchor.Format == 1 1001*e1fe3e4aSElliott Hughes assert not hasattr(baseAnchor, "XDeviceTable") 1002*e1fe3e4aSElliott Hughes assert not hasattr(baseAnchor, "YDeviceTable") 1003*e1fe3e4aSElliott Hughes assert baseAnchor.XCoordinate == v1 1004*e1fe3e4aSElliott Hughes assert baseAnchor.YCoordinate == 450 1005*e1fe3e4aSElliott Hughes 1006*e1fe3e4aSElliott Hughes assert gpos.LookupList.Lookup[1].LookupType == 2 # PairPos 1007*e1fe3e4aSElliott Hughes pairPos = gpos.LookupList.Lookup[1].SubTable[0] 1008*e1fe3e4aSElliott Hughes valueRec1 = pairPos.PairSet[0].PairValueRecord[0].Value1 1009*e1fe3e4aSElliott Hughes assert not hasattr(valueRec1, "XAdvDevice") 1010*e1fe3e4aSElliott Hughes assert valueRec1.XAdvance == v2 1011*e1fe3e4aSElliott Hughes 1012*e1fe3e4aSElliott Hughes def test_GPOS_ValueRecord_XAdvDevice_wtihout_XAdvance(self): 1013*e1fe3e4aSElliott Hughes # Test VF contains a PairPos adjustment in which the default instance 1014*e1fe3e4aSElliott Hughes # has no XAdvance but there are deltas in XAdvDevice (VariationIndex). 1015*e1fe3e4aSElliott Hughes vf = ttLib.TTFont() 1016*e1fe3e4aSElliott Hughes vf.importXML(os.path.join(TESTDATA, "PartialInstancerTest4-VF.ttx")) 1017*e1fe3e4aSElliott Hughes pairPos = vf["GPOS"].table.LookupList.Lookup[0].SubTable[0] 1018*e1fe3e4aSElliott Hughes assert pairPos.ValueFormat1 == 0x40 1019*e1fe3e4aSElliott Hughes valueRec1 = pairPos.PairSet[0].PairValueRecord[0].Value1 1020*e1fe3e4aSElliott Hughes assert not hasattr(valueRec1, "XAdvance") 1021*e1fe3e4aSElliott Hughes assert valueRec1.XAdvDevice.DeltaFormat == 0x8000 1022*e1fe3e4aSElliott Hughes outer = valueRec1.XAdvDevice.StartSize 1023*e1fe3e4aSElliott Hughes inner = valueRec1.XAdvDevice.EndSize 1024*e1fe3e4aSElliott Hughes assert vf["GDEF"].table.VarStore.VarData[outer].Item[inner] == [-50] 1025*e1fe3e4aSElliott Hughes 1026*e1fe3e4aSElliott Hughes # check that MutatorMerger for ValueRecord doesn't raise AttributeError 1027*e1fe3e4aSElliott Hughes # when XAdvDevice is present but there's no corresponding XAdvance. 1028*e1fe3e4aSElliott Hughes instancer.instantiateOTL(vf, instancer.NormalizedAxisLimits(wght=0.5)) 1029*e1fe3e4aSElliott Hughes 1030*e1fe3e4aSElliott Hughes pairPos = vf["GPOS"].table.LookupList.Lookup[0].SubTable[0] 1031*e1fe3e4aSElliott Hughes assert pairPos.ValueFormat1 == 0x4 1032*e1fe3e4aSElliott Hughes valueRec1 = pairPos.PairSet[0].PairValueRecord[0].Value1 1033*e1fe3e4aSElliott Hughes assert not hasattr(valueRec1, "XAdvDevice") 1034*e1fe3e4aSElliott Hughes assert valueRec1.XAdvance == -25 1035*e1fe3e4aSElliott Hughes 1036*e1fe3e4aSElliott Hughes 1037*e1fe3e4aSElliott Hughesclass InstantiateAvarTest(object): 1038*e1fe3e4aSElliott Hughes @pytest.mark.parametrize("location", [{"wght": 0.0}, {"wdth": 0.0}]) 1039*e1fe3e4aSElliott Hughes def test_pin_and_drop_axis(self, varfont, location): 1040*e1fe3e4aSElliott Hughes location = instancer.AxisLimits(location) 1041*e1fe3e4aSElliott Hughes 1042*e1fe3e4aSElliott Hughes instancer.instantiateAvar(varfont, location) 1043*e1fe3e4aSElliott Hughes 1044*e1fe3e4aSElliott Hughes assert set(varfont["avar"].segments).isdisjoint(location) 1045*e1fe3e4aSElliott Hughes 1046*e1fe3e4aSElliott Hughes def test_full_instance(self, varfont): 1047*e1fe3e4aSElliott Hughes location = instancer.AxisLimits(wght=0.0, wdth=0.0) 1048*e1fe3e4aSElliott Hughes 1049*e1fe3e4aSElliott Hughes instancer.instantiateAvar(varfont, location) 1050*e1fe3e4aSElliott Hughes 1051*e1fe3e4aSElliott Hughes assert "avar" not in varfont 1052*e1fe3e4aSElliott Hughes 1053*e1fe3e4aSElliott Hughes @staticmethod 1054*e1fe3e4aSElliott Hughes def quantizeF2Dot14Floats(mapping): 1055*e1fe3e4aSElliott Hughes return { 1056*e1fe3e4aSElliott Hughes floatToFixedToFloat(k, 14): floatToFixedToFloat(v, 14) 1057*e1fe3e4aSElliott Hughes for k, v in mapping.items() 1058*e1fe3e4aSElliott Hughes } 1059*e1fe3e4aSElliott Hughes 1060*e1fe3e4aSElliott Hughes # the following values come from NotoSans-VF.ttf 1061*e1fe3e4aSElliott Hughes DFLT_WGHT_MAPPING = { 1062*e1fe3e4aSElliott Hughes -1.0: -1.0, 1063*e1fe3e4aSElliott Hughes -0.6667: -0.7969, 1064*e1fe3e4aSElliott Hughes -0.3333: -0.5, 1065*e1fe3e4aSElliott Hughes 0: 0, 1066*e1fe3e4aSElliott Hughes 0.2: 0.18, 1067*e1fe3e4aSElliott Hughes 0.4: 0.38, 1068*e1fe3e4aSElliott Hughes 0.6: 0.61, 1069*e1fe3e4aSElliott Hughes 0.8: 0.79, 1070*e1fe3e4aSElliott Hughes 1.0: 1.0, 1071*e1fe3e4aSElliott Hughes } 1072*e1fe3e4aSElliott Hughes 1073*e1fe3e4aSElliott Hughes DFLT_WDTH_MAPPING = {-1.0: -1.0, -0.6667: -0.7, -0.3333: -0.36664, 0: 0, 1.0: 1.0} 1074*e1fe3e4aSElliott Hughes 1075*e1fe3e4aSElliott Hughes @pytest.fixture 1076*e1fe3e4aSElliott Hughes def varfont(self): 1077*e1fe3e4aSElliott Hughes fvarAxes = ("wght", (100, 400, 900)), ("wdth", (62.5, 100, 100)) 1078*e1fe3e4aSElliott Hughes avarSegments = { 1079*e1fe3e4aSElliott Hughes "wght": self.quantizeF2Dot14Floats(self.DFLT_WGHT_MAPPING), 1080*e1fe3e4aSElliott Hughes "wdth": self.quantizeF2Dot14Floats(self.DFLT_WDTH_MAPPING), 1081*e1fe3e4aSElliott Hughes } 1082*e1fe3e4aSElliott Hughes varfont = ttLib.TTFont() 1083*e1fe3e4aSElliott Hughes varfont["name"] = ttLib.newTable("name") 1084*e1fe3e4aSElliott Hughes varLib._add_fvar(varfont, _makeDSAxesDict(fvarAxes), instances=()) 1085*e1fe3e4aSElliott Hughes avar = varfont["avar"] = ttLib.newTable("avar") 1086*e1fe3e4aSElliott Hughes avar.segments = avarSegments 1087*e1fe3e4aSElliott Hughes return varfont 1088*e1fe3e4aSElliott Hughes 1089*e1fe3e4aSElliott Hughes @pytest.mark.parametrize( 1090*e1fe3e4aSElliott Hughes "axisLimits, expectedSegments", 1091*e1fe3e4aSElliott Hughes [ 1092*e1fe3e4aSElliott Hughes pytest.param( 1093*e1fe3e4aSElliott Hughes {"wght": (100, 900)}, 1094*e1fe3e4aSElliott Hughes {"wght": DFLT_WGHT_MAPPING, "wdth": DFLT_WDTH_MAPPING}, 1095*e1fe3e4aSElliott Hughes id="wght=100:900", 1096*e1fe3e4aSElliott Hughes ), 1097*e1fe3e4aSElliott Hughes pytest.param( 1098*e1fe3e4aSElliott Hughes {"wght": (400, 900)}, 1099*e1fe3e4aSElliott Hughes { 1100*e1fe3e4aSElliott Hughes "wght": { 1101*e1fe3e4aSElliott Hughes -1.0: -1.0, 1102*e1fe3e4aSElliott Hughes 0: 0, 1103*e1fe3e4aSElliott Hughes 0.2: 0.18, 1104*e1fe3e4aSElliott Hughes 0.4: 0.38, 1105*e1fe3e4aSElliott Hughes 0.6: 0.61, 1106*e1fe3e4aSElliott Hughes 0.8: 0.79, 1107*e1fe3e4aSElliott Hughes 1.0: 1.0, 1108*e1fe3e4aSElliott Hughes }, 1109*e1fe3e4aSElliott Hughes "wdth": DFLT_WDTH_MAPPING, 1110*e1fe3e4aSElliott Hughes }, 1111*e1fe3e4aSElliott Hughes id="wght=400:900", 1112*e1fe3e4aSElliott Hughes ), 1113*e1fe3e4aSElliott Hughes pytest.param( 1114*e1fe3e4aSElliott Hughes {"wght": (100, 400)}, 1115*e1fe3e4aSElliott Hughes { 1116*e1fe3e4aSElliott Hughes "wght": { 1117*e1fe3e4aSElliott Hughes -1.0: -1.0, 1118*e1fe3e4aSElliott Hughes -0.6667: -0.7969, 1119*e1fe3e4aSElliott Hughes -0.3333: -0.5, 1120*e1fe3e4aSElliott Hughes 0: 0, 1121*e1fe3e4aSElliott Hughes 1.0: 1.0, 1122*e1fe3e4aSElliott Hughes }, 1123*e1fe3e4aSElliott Hughes "wdth": DFLT_WDTH_MAPPING, 1124*e1fe3e4aSElliott Hughes }, 1125*e1fe3e4aSElliott Hughes id="wght=100:400", 1126*e1fe3e4aSElliott Hughes ), 1127*e1fe3e4aSElliott Hughes pytest.param( 1128*e1fe3e4aSElliott Hughes {"wght": (400, 800)}, 1129*e1fe3e4aSElliott Hughes { 1130*e1fe3e4aSElliott Hughes "wght": { 1131*e1fe3e4aSElliott Hughes -1.0: -1.0, 1132*e1fe3e4aSElliott Hughes 0: 0, 1133*e1fe3e4aSElliott Hughes 0.25: 0.22784, 1134*e1fe3e4aSElliott Hughes 0.50006: 0.48103, 1135*e1fe3e4aSElliott Hughes 0.75: 0.77214, 1136*e1fe3e4aSElliott Hughes 1.0: 1.0, 1137*e1fe3e4aSElliott Hughes }, 1138*e1fe3e4aSElliott Hughes "wdth": DFLT_WDTH_MAPPING, 1139*e1fe3e4aSElliott Hughes }, 1140*e1fe3e4aSElliott Hughes id="wght=400:800", 1141*e1fe3e4aSElliott Hughes ), 1142*e1fe3e4aSElliott Hughes pytest.param( 1143*e1fe3e4aSElliott Hughes {"wght": (400, 700)}, 1144*e1fe3e4aSElliott Hughes { 1145*e1fe3e4aSElliott Hughes "wght": { 1146*e1fe3e4aSElliott Hughes -1.0: -1.0, 1147*e1fe3e4aSElliott Hughes 0: 0, 1148*e1fe3e4aSElliott Hughes 0.3334: 0.2951, 1149*e1fe3e4aSElliott Hughes 0.66675: 0.623, 1150*e1fe3e4aSElliott Hughes 1.0: 1.0, 1151*e1fe3e4aSElliott Hughes }, 1152*e1fe3e4aSElliott Hughes "wdth": DFLT_WDTH_MAPPING, 1153*e1fe3e4aSElliott Hughes }, 1154*e1fe3e4aSElliott Hughes id="wght=400:700", 1155*e1fe3e4aSElliott Hughes ), 1156*e1fe3e4aSElliott Hughes pytest.param( 1157*e1fe3e4aSElliott Hughes {"wght": (400, 600)}, 1158*e1fe3e4aSElliott Hughes { 1159*e1fe3e4aSElliott Hughes "wght": {-1.0: -1.0, 0: 0, 0.5: 0.47363, 1.0: 1.0}, 1160*e1fe3e4aSElliott Hughes "wdth": DFLT_WDTH_MAPPING, 1161*e1fe3e4aSElliott Hughes }, 1162*e1fe3e4aSElliott Hughes id="wght=400:600", 1163*e1fe3e4aSElliott Hughes ), 1164*e1fe3e4aSElliott Hughes pytest.param( 1165*e1fe3e4aSElliott Hughes {"wdth": (62.5, 100)}, 1166*e1fe3e4aSElliott Hughes { 1167*e1fe3e4aSElliott Hughes "wght": DFLT_WGHT_MAPPING, 1168*e1fe3e4aSElliott Hughes "wdth": { 1169*e1fe3e4aSElliott Hughes -1.0: -1.0, 1170*e1fe3e4aSElliott Hughes -0.6667: -0.7, 1171*e1fe3e4aSElliott Hughes -0.3333: -0.36664, 1172*e1fe3e4aSElliott Hughes 0: 0, 1173*e1fe3e4aSElliott Hughes 1.0: 1.0, 1174*e1fe3e4aSElliott Hughes }, 1175*e1fe3e4aSElliott Hughes }, 1176*e1fe3e4aSElliott Hughes id="wdth=62.5:100", 1177*e1fe3e4aSElliott Hughes ), 1178*e1fe3e4aSElliott Hughes pytest.param( 1179*e1fe3e4aSElliott Hughes {"wdth": (70, 100)}, 1180*e1fe3e4aSElliott Hughes { 1181*e1fe3e4aSElliott Hughes "wght": DFLT_WGHT_MAPPING, 1182*e1fe3e4aSElliott Hughes "wdth": { 1183*e1fe3e4aSElliott Hughes -1.0: -1.0, 1184*e1fe3e4aSElliott Hughes -0.8334: -0.85364, 1185*e1fe3e4aSElliott Hughes -0.4166: -0.44714, 1186*e1fe3e4aSElliott Hughes 0: 0, 1187*e1fe3e4aSElliott Hughes 1.0: 1.0, 1188*e1fe3e4aSElliott Hughes }, 1189*e1fe3e4aSElliott Hughes }, 1190*e1fe3e4aSElliott Hughes id="wdth=70:100", 1191*e1fe3e4aSElliott Hughes ), 1192*e1fe3e4aSElliott Hughes pytest.param( 1193*e1fe3e4aSElliott Hughes {"wdth": (75, 100)}, 1194*e1fe3e4aSElliott Hughes { 1195*e1fe3e4aSElliott Hughes "wght": DFLT_WGHT_MAPPING, 1196*e1fe3e4aSElliott Hughes "wdth": {-1.0: -1.0, -0.49994: -0.52374, 0: 0, 1.0: 1.0}, 1197*e1fe3e4aSElliott Hughes }, 1198*e1fe3e4aSElliott Hughes id="wdth=75:100", 1199*e1fe3e4aSElliott Hughes ), 1200*e1fe3e4aSElliott Hughes pytest.param( 1201*e1fe3e4aSElliott Hughes {"wdth": (77, 100)}, 1202*e1fe3e4aSElliott Hughes { 1203*e1fe3e4aSElliott Hughes "wght": DFLT_WGHT_MAPPING, 1204*e1fe3e4aSElliott Hughes "wdth": {-1.0: -1.0, -0.54346: -0.56696, 0: 0, 1.0: 1.0}, 1205*e1fe3e4aSElliott Hughes }, 1206*e1fe3e4aSElliott Hughes id="wdth=77:100", 1207*e1fe3e4aSElliott Hughes ), 1208*e1fe3e4aSElliott Hughes pytest.param( 1209*e1fe3e4aSElliott Hughes {"wdth": (87.5, 100)}, 1210*e1fe3e4aSElliott Hughes {"wght": DFLT_WGHT_MAPPING, "wdth": {-1.0: -1.0, 0: 0, 1.0: 1.0}}, 1211*e1fe3e4aSElliott Hughes id="wdth=87.5:100", 1212*e1fe3e4aSElliott Hughes ), 1213*e1fe3e4aSElliott Hughes ], 1214*e1fe3e4aSElliott Hughes ) 1215*e1fe3e4aSElliott Hughes def test_limit_axes(self, varfont, axisLimits, expectedSegments): 1216*e1fe3e4aSElliott Hughes axisLimits = instancer.AxisLimits(axisLimits) 1217*e1fe3e4aSElliott Hughes 1218*e1fe3e4aSElliott Hughes instancer.instantiateAvar(varfont, axisLimits) 1219*e1fe3e4aSElliott Hughes 1220*e1fe3e4aSElliott Hughes newSegments = varfont["avar"].segments 1221*e1fe3e4aSElliott Hughes expectedSegments = { 1222*e1fe3e4aSElliott Hughes axisTag: self.quantizeF2Dot14Floats(mapping) 1223*e1fe3e4aSElliott Hughes for axisTag, mapping in expectedSegments.items() 1224*e1fe3e4aSElliott Hughes } 1225*e1fe3e4aSElliott Hughes assert newSegments == expectedSegments 1226*e1fe3e4aSElliott Hughes 1227*e1fe3e4aSElliott Hughes @pytest.mark.parametrize( 1228*e1fe3e4aSElliott Hughes "invalidSegmentMap", 1229*e1fe3e4aSElliott Hughes [ 1230*e1fe3e4aSElliott Hughes pytest.param({0.5: 0.5}, id="missing-required-maps-1"), 1231*e1fe3e4aSElliott Hughes pytest.param({-1.0: -1.0, 1.0: 1.0}, id="missing-required-maps-2"), 1232*e1fe3e4aSElliott Hughes pytest.param( 1233*e1fe3e4aSElliott Hughes {-1.0: -1.0, 0: 0, 0.5: 0.5, 0.6: 0.4, 1.0: 1.0}, 1234*e1fe3e4aSElliott Hughes id="retrograde-value-maps", 1235*e1fe3e4aSElliott Hughes ), 1236*e1fe3e4aSElliott Hughes ], 1237*e1fe3e4aSElliott Hughes ) 1238*e1fe3e4aSElliott Hughes def test_drop_invalid_segment_map(self, varfont, invalidSegmentMap, caplog): 1239*e1fe3e4aSElliott Hughes varfont["avar"].segments["wght"] = invalidSegmentMap 1240*e1fe3e4aSElliott Hughes 1241*e1fe3e4aSElliott Hughes axisLimits = instancer.AxisLimits(wght=(100, 400)) 1242*e1fe3e4aSElliott Hughes 1243*e1fe3e4aSElliott Hughes with caplog.at_level(logging.WARNING, logger="fontTools.varLib.instancer"): 1244*e1fe3e4aSElliott Hughes instancer.instantiateAvar(varfont, axisLimits) 1245*e1fe3e4aSElliott Hughes 1246*e1fe3e4aSElliott Hughes assert "Invalid avar" in caplog.text 1247*e1fe3e4aSElliott Hughes assert "wght" not in varfont["avar"].segments 1248*e1fe3e4aSElliott Hughes 1249*e1fe3e4aSElliott Hughes def test_isValidAvarSegmentMap(self): 1250*e1fe3e4aSElliott Hughes assert instancer._isValidAvarSegmentMap("FOOO", {}) 1251*e1fe3e4aSElliott Hughes assert instancer._isValidAvarSegmentMap("FOOO", {-1.0: -1.0, 0: 0, 1.0: 1.0}) 1252*e1fe3e4aSElliott Hughes assert instancer._isValidAvarSegmentMap( 1253*e1fe3e4aSElliott Hughes "FOOO", {-1.0: -1.0, 0: 0, 0.5: 0.5, 1.0: 1.0} 1254*e1fe3e4aSElliott Hughes ) 1255*e1fe3e4aSElliott Hughes assert instancer._isValidAvarSegmentMap( 1256*e1fe3e4aSElliott Hughes "FOOO", {-1.0: -1.0, 0: 0, 0.5: 0.5, 0.7: 0.5, 1.0: 1.0} 1257*e1fe3e4aSElliott Hughes ) 1258*e1fe3e4aSElliott Hughes 1259*e1fe3e4aSElliott Hughes 1260*e1fe3e4aSElliott Hughesclass InstantiateFvarTest(object): 1261*e1fe3e4aSElliott Hughes @pytest.mark.parametrize( 1262*e1fe3e4aSElliott Hughes "location, instancesLeft", 1263*e1fe3e4aSElliott Hughes [ 1264*e1fe3e4aSElliott Hughes ( 1265*e1fe3e4aSElliott Hughes {"wght": 400.0}, 1266*e1fe3e4aSElliott Hughes ["Regular", "SemiCondensed", "Condensed", "ExtraCondensed"], 1267*e1fe3e4aSElliott Hughes ), 1268*e1fe3e4aSElliott Hughes ( 1269*e1fe3e4aSElliott Hughes {"wght": 100.0}, 1270*e1fe3e4aSElliott Hughes ["Thin", "SemiCondensed Thin", "Condensed Thin", "ExtraCondensed Thin"], 1271*e1fe3e4aSElliott Hughes ), 1272*e1fe3e4aSElliott Hughes ( 1273*e1fe3e4aSElliott Hughes {"wdth": 100.0}, 1274*e1fe3e4aSElliott Hughes [ 1275*e1fe3e4aSElliott Hughes "Thin", 1276*e1fe3e4aSElliott Hughes "ExtraLight", 1277*e1fe3e4aSElliott Hughes "Light", 1278*e1fe3e4aSElliott Hughes "Regular", 1279*e1fe3e4aSElliott Hughes "Medium", 1280*e1fe3e4aSElliott Hughes "SemiBold", 1281*e1fe3e4aSElliott Hughes "Bold", 1282*e1fe3e4aSElliott Hughes "ExtraBold", 1283*e1fe3e4aSElliott Hughes "Black", 1284*e1fe3e4aSElliott Hughes ], 1285*e1fe3e4aSElliott Hughes ), 1286*e1fe3e4aSElliott Hughes # no named instance at pinned location 1287*e1fe3e4aSElliott Hughes ({"wdth": 90.0}, []), 1288*e1fe3e4aSElliott Hughes ], 1289*e1fe3e4aSElliott Hughes ) 1290*e1fe3e4aSElliott Hughes def test_pin_and_drop_axis(self, varfont, location, instancesLeft): 1291*e1fe3e4aSElliott Hughes location = instancer.AxisLimits(location) 1292*e1fe3e4aSElliott Hughes 1293*e1fe3e4aSElliott Hughes instancer.instantiateFvar(varfont, location) 1294*e1fe3e4aSElliott Hughes 1295*e1fe3e4aSElliott Hughes fvar = varfont["fvar"] 1296*e1fe3e4aSElliott Hughes assert {a.axisTag for a in fvar.axes}.isdisjoint(location) 1297*e1fe3e4aSElliott Hughes 1298*e1fe3e4aSElliott Hughes for instance in fvar.instances: 1299*e1fe3e4aSElliott Hughes assert set(instance.coordinates).isdisjoint(location) 1300*e1fe3e4aSElliott Hughes 1301*e1fe3e4aSElliott Hughes name = varfont["name"] 1302*e1fe3e4aSElliott Hughes assert [ 1303*e1fe3e4aSElliott Hughes name.getDebugName(instance.subfamilyNameID) for instance in fvar.instances 1304*e1fe3e4aSElliott Hughes ] == instancesLeft 1305*e1fe3e4aSElliott Hughes 1306*e1fe3e4aSElliott Hughes def test_full_instance(self, varfont): 1307*e1fe3e4aSElliott Hughes location = instancer.AxisLimits({"wght": 0.0, "wdth": 0.0}) 1308*e1fe3e4aSElliott Hughes 1309*e1fe3e4aSElliott Hughes instancer.instantiateFvar(varfont, location) 1310*e1fe3e4aSElliott Hughes 1311*e1fe3e4aSElliott Hughes assert "fvar" not in varfont 1312*e1fe3e4aSElliott Hughes 1313*e1fe3e4aSElliott Hughes @pytest.mark.parametrize( 1314*e1fe3e4aSElliott Hughes "location, expected", 1315*e1fe3e4aSElliott Hughes [ 1316*e1fe3e4aSElliott Hughes ({"wght": (30, 40, 700)}, (100, 100, 700)), 1317*e1fe3e4aSElliott Hughes ({"wght": (30, 40, None)}, (100, 100, 900)), 1318*e1fe3e4aSElliott Hughes ({"wght": (30, None, 700)}, (100, 400, 700)), 1319*e1fe3e4aSElliott Hughes ({"wght": (None, 200, 700)}, (100, 200, 700)), 1320*e1fe3e4aSElliott Hughes ({"wght": (40, None, None)}, (100, 400, 900)), 1321*e1fe3e4aSElliott Hughes ({"wght": (None, 40, None)}, (100, 100, 900)), 1322*e1fe3e4aSElliott Hughes ({"wght": (None, None, 700)}, (100, 400, 700)), 1323*e1fe3e4aSElliott Hughes ({"wght": (None, None, None)}, (100, 400, 900)), 1324*e1fe3e4aSElliott Hughes ], 1325*e1fe3e4aSElliott Hughes ) 1326*e1fe3e4aSElliott Hughes def test_axis_limits(self, varfont, location, expected): 1327*e1fe3e4aSElliott Hughes location = instancer.AxisLimits(location) 1328*e1fe3e4aSElliott Hughes 1329*e1fe3e4aSElliott Hughes varfont = instancer.instantiateVariableFont(varfont, location) 1330*e1fe3e4aSElliott Hughes 1331*e1fe3e4aSElliott Hughes fvar = varfont["fvar"] 1332*e1fe3e4aSElliott Hughes axes = {a.axisTag: a for a in fvar.axes} 1333*e1fe3e4aSElliott Hughes assert axes["wght"].minValue == expected[0] 1334*e1fe3e4aSElliott Hughes assert axes["wght"].defaultValue == expected[1] 1335*e1fe3e4aSElliott Hughes assert axes["wght"].maxValue == expected[2] 1336*e1fe3e4aSElliott Hughes 1337*e1fe3e4aSElliott Hughes 1338*e1fe3e4aSElliott Hughesclass InstantiateSTATTest(object): 1339*e1fe3e4aSElliott Hughes @pytest.mark.parametrize( 1340*e1fe3e4aSElliott Hughes "location, expected", 1341*e1fe3e4aSElliott Hughes [ 1342*e1fe3e4aSElliott Hughes ({"wght": 400}, ["Regular", "Condensed", "Upright", "Normal"]), 1343*e1fe3e4aSElliott Hughes ( 1344*e1fe3e4aSElliott Hughes {"wdth": 100}, 1345*e1fe3e4aSElliott Hughes ["Thin", "Regular", "Medium", "Black", "Upright", "Normal"], 1346*e1fe3e4aSElliott Hughes ), 1347*e1fe3e4aSElliott Hughes ], 1348*e1fe3e4aSElliott Hughes ) 1349*e1fe3e4aSElliott Hughes def test_pin_and_drop_axis(self, varfont, location, expected): 1350*e1fe3e4aSElliott Hughes location = instancer.AxisLimits(location) 1351*e1fe3e4aSElliott Hughes 1352*e1fe3e4aSElliott Hughes instancer.instantiateSTAT(varfont, location) 1353*e1fe3e4aSElliott Hughes 1354*e1fe3e4aSElliott Hughes stat = varfont["STAT"].table 1355*e1fe3e4aSElliott Hughes designAxes = {a.AxisTag for a in stat.DesignAxisRecord.Axis} 1356*e1fe3e4aSElliott Hughes 1357*e1fe3e4aSElliott Hughes assert designAxes == {"wght", "wdth", "ital"} 1358*e1fe3e4aSElliott Hughes 1359*e1fe3e4aSElliott Hughes name = varfont["name"] 1360*e1fe3e4aSElliott Hughes valueNames = [] 1361*e1fe3e4aSElliott Hughes for axisValueTable in stat.AxisValueArray.AxisValue: 1362*e1fe3e4aSElliott Hughes valueName = name.getDebugName(axisValueTable.ValueNameID) 1363*e1fe3e4aSElliott Hughes valueNames.append(valueName) 1364*e1fe3e4aSElliott Hughes 1365*e1fe3e4aSElliott Hughes assert valueNames == expected 1366*e1fe3e4aSElliott Hughes 1367*e1fe3e4aSElliott Hughes def test_skip_table_no_axis_value_array(self, varfont): 1368*e1fe3e4aSElliott Hughes varfont["STAT"].table.AxisValueArray = None 1369*e1fe3e4aSElliott Hughes 1370*e1fe3e4aSElliott Hughes instancer.instantiateSTAT(varfont, instancer.AxisLimits(wght=100)) 1371*e1fe3e4aSElliott Hughes 1372*e1fe3e4aSElliott Hughes assert len(varfont["STAT"].table.DesignAxisRecord.Axis) == 3 1373*e1fe3e4aSElliott Hughes assert varfont["STAT"].table.AxisValueArray is None 1374*e1fe3e4aSElliott Hughes 1375*e1fe3e4aSElliott Hughes def test_skip_table_axis_value_array_empty(self, varfont): 1376*e1fe3e4aSElliott Hughes varfont["STAT"].table.AxisValueArray.AxisValue = [] 1377*e1fe3e4aSElliott Hughes 1378*e1fe3e4aSElliott Hughes instancer.instantiateSTAT(varfont, {"wght": 100}) 1379*e1fe3e4aSElliott Hughes 1380*e1fe3e4aSElliott Hughes assert len(varfont["STAT"].table.DesignAxisRecord.Axis) == 3 1381*e1fe3e4aSElliott Hughes assert not varfont["STAT"].table.AxisValueArray.AxisValue 1382*e1fe3e4aSElliott Hughes 1383*e1fe3e4aSElliott Hughes def test_skip_table_no_design_axes(self, varfont): 1384*e1fe3e4aSElliott Hughes stat = otTables.STAT() 1385*e1fe3e4aSElliott Hughes stat.Version = 0x00010001 1386*e1fe3e4aSElliott Hughes stat.populateDefaults() 1387*e1fe3e4aSElliott Hughes assert not stat.DesignAxisRecord 1388*e1fe3e4aSElliott Hughes assert not stat.AxisValueArray 1389*e1fe3e4aSElliott Hughes varfont["STAT"].table = stat 1390*e1fe3e4aSElliott Hughes 1391*e1fe3e4aSElliott Hughes instancer.instantiateSTAT(varfont, {"wght": 100}) 1392*e1fe3e4aSElliott Hughes 1393*e1fe3e4aSElliott Hughes assert not varfont["STAT"].table.DesignAxisRecord 1394*e1fe3e4aSElliott Hughes 1395*e1fe3e4aSElliott Hughes @staticmethod 1396*e1fe3e4aSElliott Hughes def get_STAT_axis_values(stat): 1397*e1fe3e4aSElliott Hughes axes = stat.DesignAxisRecord.Axis 1398*e1fe3e4aSElliott Hughes result = [] 1399*e1fe3e4aSElliott Hughes for axisValue in stat.AxisValueArray.AxisValue: 1400*e1fe3e4aSElliott Hughes if axisValue.Format == 1: 1401*e1fe3e4aSElliott Hughes result.append((axes[axisValue.AxisIndex].AxisTag, axisValue.Value)) 1402*e1fe3e4aSElliott Hughes elif axisValue.Format == 3: 1403*e1fe3e4aSElliott Hughes result.append( 1404*e1fe3e4aSElliott Hughes ( 1405*e1fe3e4aSElliott Hughes axes[axisValue.AxisIndex].AxisTag, 1406*e1fe3e4aSElliott Hughes (axisValue.Value, axisValue.LinkedValue), 1407*e1fe3e4aSElliott Hughes ) 1408*e1fe3e4aSElliott Hughes ) 1409*e1fe3e4aSElliott Hughes elif axisValue.Format == 2: 1410*e1fe3e4aSElliott Hughes result.append( 1411*e1fe3e4aSElliott Hughes ( 1412*e1fe3e4aSElliott Hughes axes[axisValue.AxisIndex].AxisTag, 1413*e1fe3e4aSElliott Hughes ( 1414*e1fe3e4aSElliott Hughes axisValue.RangeMinValue, 1415*e1fe3e4aSElliott Hughes axisValue.NominalValue, 1416*e1fe3e4aSElliott Hughes axisValue.RangeMaxValue, 1417*e1fe3e4aSElliott Hughes ), 1418*e1fe3e4aSElliott Hughes ) 1419*e1fe3e4aSElliott Hughes ) 1420*e1fe3e4aSElliott Hughes elif axisValue.Format == 4: 1421*e1fe3e4aSElliott Hughes result.append( 1422*e1fe3e4aSElliott Hughes tuple( 1423*e1fe3e4aSElliott Hughes (axes[rec.AxisIndex].AxisTag, rec.Value) 1424*e1fe3e4aSElliott Hughes for rec in axisValue.AxisValueRecord 1425*e1fe3e4aSElliott Hughes ) 1426*e1fe3e4aSElliott Hughes ) 1427*e1fe3e4aSElliott Hughes else: 1428*e1fe3e4aSElliott Hughes raise AssertionError(axisValue.Format) 1429*e1fe3e4aSElliott Hughes return result 1430*e1fe3e4aSElliott Hughes 1431*e1fe3e4aSElliott Hughes def test_limit_axes(self, varfont2): 1432*e1fe3e4aSElliott Hughes axisLimits = instancer.AxisLimits({"wght": (400, 500), "wdth": (75, 100)}) 1433*e1fe3e4aSElliott Hughes 1434*e1fe3e4aSElliott Hughes instancer.instantiateSTAT(varfont2, axisLimits) 1435*e1fe3e4aSElliott Hughes 1436*e1fe3e4aSElliott Hughes assert len(varfont2["STAT"].table.AxisValueArray.AxisValue) == 5 1437*e1fe3e4aSElliott Hughes assert self.get_STAT_axis_values(varfont2["STAT"].table) == [ 1438*e1fe3e4aSElliott Hughes ("wght", (400.0, 700.0)), 1439*e1fe3e4aSElliott Hughes ("wght", 500.0), 1440*e1fe3e4aSElliott Hughes ("wdth", (93.75, 100.0, 100.0)), 1441*e1fe3e4aSElliott Hughes ("wdth", (81.25, 87.5, 93.75)), 1442*e1fe3e4aSElliott Hughes ("wdth", (68.75, 75.0, 81.25)), 1443*e1fe3e4aSElliott Hughes ] 1444*e1fe3e4aSElliott Hughes 1445*e1fe3e4aSElliott Hughes def test_limit_axis_value_format_4(self, varfont2): 1446*e1fe3e4aSElliott Hughes stat = varfont2["STAT"].table 1447*e1fe3e4aSElliott Hughes 1448*e1fe3e4aSElliott Hughes axisValue = otTables.AxisValue() 1449*e1fe3e4aSElliott Hughes axisValue.Format = 4 1450*e1fe3e4aSElliott Hughes axisValue.AxisValueRecord = [] 1451*e1fe3e4aSElliott Hughes for tag, value in (("wght", 575), ("wdth", 90)): 1452*e1fe3e4aSElliott Hughes rec = otTables.AxisValueRecord() 1453*e1fe3e4aSElliott Hughes rec.AxisIndex = next( 1454*e1fe3e4aSElliott Hughes i for i, a in enumerate(stat.DesignAxisRecord.Axis) if a.AxisTag == tag 1455*e1fe3e4aSElliott Hughes ) 1456*e1fe3e4aSElliott Hughes rec.Value = value 1457*e1fe3e4aSElliott Hughes axisValue.AxisValueRecord.append(rec) 1458*e1fe3e4aSElliott Hughes stat.AxisValueArray.AxisValue.append(axisValue) 1459*e1fe3e4aSElliott Hughes 1460*e1fe3e4aSElliott Hughes instancer.instantiateSTAT(varfont2, instancer.AxisLimits(wght=(100, 600))) 1461*e1fe3e4aSElliott Hughes 1462*e1fe3e4aSElliott Hughes assert axisValue in varfont2["STAT"].table.AxisValueArray.AxisValue 1463*e1fe3e4aSElliott Hughes 1464*e1fe3e4aSElliott Hughes instancer.instantiateSTAT(varfont2, instancer.AxisLimits(wdth=(62.5, 87.5))) 1465*e1fe3e4aSElliott Hughes 1466*e1fe3e4aSElliott Hughes assert axisValue not in varfont2["STAT"].table.AxisValueArray.AxisValue 1467*e1fe3e4aSElliott Hughes 1468*e1fe3e4aSElliott Hughes def test_unknown_axis_value_format(self, varfont2, caplog): 1469*e1fe3e4aSElliott Hughes stat = varfont2["STAT"].table 1470*e1fe3e4aSElliott Hughes axisValue = otTables.AxisValue() 1471*e1fe3e4aSElliott Hughes axisValue.Format = 5 1472*e1fe3e4aSElliott Hughes stat.AxisValueArray.AxisValue.append(axisValue) 1473*e1fe3e4aSElliott Hughes 1474*e1fe3e4aSElliott Hughes with caplog.at_level(logging.WARNING, logger="fontTools.varLib.instancer"): 1475*e1fe3e4aSElliott Hughes instancer.instantiateSTAT(varfont2, instancer.AxisLimits(wght=400)) 1476*e1fe3e4aSElliott Hughes 1477*e1fe3e4aSElliott Hughes assert "Unknown AxisValue table format (5)" in caplog.text 1478*e1fe3e4aSElliott Hughes assert axisValue in varfont2["STAT"].table.AxisValueArray.AxisValue 1479*e1fe3e4aSElliott Hughes 1480*e1fe3e4aSElliott Hughes 1481*e1fe3e4aSElliott Hughesdef test_setMacOverlapFlags(): 1482*e1fe3e4aSElliott Hughes flagOverlapCompound = _g_l_y_f.OVERLAP_COMPOUND 1483*e1fe3e4aSElliott Hughes flagOverlapSimple = _g_l_y_f.flagOverlapSimple 1484*e1fe3e4aSElliott Hughes 1485*e1fe3e4aSElliott Hughes glyf = ttLib.newTable("glyf") 1486*e1fe3e4aSElliott Hughes glyf.glyphOrder = ["a", "b", "c"] 1487*e1fe3e4aSElliott Hughes a = _g_l_y_f.Glyph() 1488*e1fe3e4aSElliott Hughes a.numberOfContours = 1 1489*e1fe3e4aSElliott Hughes a.flags = [0] 1490*e1fe3e4aSElliott Hughes b = _g_l_y_f.Glyph() 1491*e1fe3e4aSElliott Hughes b.numberOfContours = -1 1492*e1fe3e4aSElliott Hughes comp = _g_l_y_f.GlyphComponent() 1493*e1fe3e4aSElliott Hughes comp.flags = 0 1494*e1fe3e4aSElliott Hughes b.components = [comp] 1495*e1fe3e4aSElliott Hughes c = _g_l_y_f.Glyph() 1496*e1fe3e4aSElliott Hughes c.numberOfContours = 0 1497*e1fe3e4aSElliott Hughes glyf.glyphs = {"a": a, "b": b, "c": c} 1498*e1fe3e4aSElliott Hughes 1499*e1fe3e4aSElliott Hughes instancer.setMacOverlapFlags(glyf) 1500*e1fe3e4aSElliott Hughes 1501*e1fe3e4aSElliott Hughes assert a.flags[0] & flagOverlapSimple != 0 1502*e1fe3e4aSElliott Hughes assert b.components[0].flags & flagOverlapCompound != 0 1503*e1fe3e4aSElliott Hughes 1504*e1fe3e4aSElliott Hughes 1505*e1fe3e4aSElliott Hughes@pytest.fixture 1506*e1fe3e4aSElliott Hughesdef varfont2(): 1507*e1fe3e4aSElliott Hughes f = ttLib.TTFont(recalcTimestamp=False) 1508*e1fe3e4aSElliott Hughes f.importXML(os.path.join(TESTDATA, "PartialInstancerTest2-VF.ttx")) 1509*e1fe3e4aSElliott Hughes return f 1510*e1fe3e4aSElliott Hughes 1511*e1fe3e4aSElliott Hughes 1512*e1fe3e4aSElliott Hughes@pytest.fixture 1513*e1fe3e4aSElliott Hughesdef varfont3(): 1514*e1fe3e4aSElliott Hughes f = ttLib.TTFont(recalcTimestamp=False) 1515*e1fe3e4aSElliott Hughes f.importXML(os.path.join(TESTDATA, "PartialInstancerTest3-VF.ttx")) 1516*e1fe3e4aSElliott Hughes return f 1517*e1fe3e4aSElliott Hughes 1518*e1fe3e4aSElliott Hughes 1519*e1fe3e4aSElliott Hughesdef _dump_ttx(ttFont): 1520*e1fe3e4aSElliott Hughes # compile to temporary bytes stream, reload and dump to XML 1521*e1fe3e4aSElliott Hughes tmp = BytesIO() 1522*e1fe3e4aSElliott Hughes ttFont.save(tmp) 1523*e1fe3e4aSElliott Hughes tmp.seek(0) 1524*e1fe3e4aSElliott Hughes ttFont2 = ttLib.TTFont(tmp, recalcBBoxes=False, recalcTimestamp=False) 1525*e1fe3e4aSElliott Hughes s = StringIO() 1526*e1fe3e4aSElliott Hughes ttFont2.saveXML(s) 1527*e1fe3e4aSElliott Hughes return stripVariableItemsFromTTX(s.getvalue()) 1528*e1fe3e4aSElliott Hughes 1529*e1fe3e4aSElliott Hughes 1530*e1fe3e4aSElliott Hughesdef _get_expected_instance_ttx( 1531*e1fe3e4aSElliott Hughes name, *locations, overlap=instancer.OverlapMode.KEEP_AND_SET_FLAGS 1532*e1fe3e4aSElliott Hughes): 1533*e1fe3e4aSElliott Hughes filename = f"{name}-VF-instance-{','.join(str(loc) for loc in locations)}" 1534*e1fe3e4aSElliott Hughes if overlap == instancer.OverlapMode.KEEP_AND_DONT_SET_FLAGS: 1535*e1fe3e4aSElliott Hughes filename += "-no-overlap-flags" 1536*e1fe3e4aSElliott Hughes elif overlap == instancer.OverlapMode.REMOVE: 1537*e1fe3e4aSElliott Hughes filename += "-no-overlaps" 1538*e1fe3e4aSElliott Hughes with open( 1539*e1fe3e4aSElliott Hughes os.path.join(TESTDATA, "test_results", f"{filename}.ttx"), 1540*e1fe3e4aSElliott Hughes "r", 1541*e1fe3e4aSElliott Hughes encoding="utf-8", 1542*e1fe3e4aSElliott Hughes ) as fp: 1543*e1fe3e4aSElliott Hughes return stripVariableItemsFromTTX(fp.read()) 1544*e1fe3e4aSElliott Hughes 1545*e1fe3e4aSElliott Hughes 1546*e1fe3e4aSElliott Hughesclass InstantiateVariableFontTest(object): 1547*e1fe3e4aSElliott Hughes @pytest.mark.parametrize( 1548*e1fe3e4aSElliott Hughes "wght, wdth", 1549*e1fe3e4aSElliott Hughes [(100, 100), (400, 100), (900, 100), (100, 62.5), (400, 62.5), (900, 62.5)], 1550*e1fe3e4aSElliott Hughes ) 1551*e1fe3e4aSElliott Hughes def test_multiple_instancing(self, varfont2, wght, wdth): 1552*e1fe3e4aSElliott Hughes partial = instancer.instantiateVariableFont(varfont2, {"wght": wght}) 1553*e1fe3e4aSElliott Hughes instance = instancer.instantiateVariableFont(partial, {"wdth": wdth}) 1554*e1fe3e4aSElliott Hughes 1555*e1fe3e4aSElliott Hughes expected = _get_expected_instance_ttx("PartialInstancerTest2", wght, wdth) 1556*e1fe3e4aSElliott Hughes 1557*e1fe3e4aSElliott Hughes assert _dump_ttx(instance) == expected 1558*e1fe3e4aSElliott Hughes 1559*e1fe3e4aSElliott Hughes def test_default_instance(self, varfont2): 1560*e1fe3e4aSElliott Hughes instance = instancer.instantiateVariableFont( 1561*e1fe3e4aSElliott Hughes varfont2, {"wght": None, "wdth": None} 1562*e1fe3e4aSElliott Hughes ) 1563*e1fe3e4aSElliott Hughes 1564*e1fe3e4aSElliott Hughes expected = _get_expected_instance_ttx("PartialInstancerTest2", 400, 100) 1565*e1fe3e4aSElliott Hughes 1566*e1fe3e4aSElliott Hughes assert _dump_ttx(instance) == expected 1567*e1fe3e4aSElliott Hughes 1568*e1fe3e4aSElliott Hughes def test_move_weight_width_axis_default(self, varfont2): 1569*e1fe3e4aSElliott Hughes # https://github.com/fonttools/fonttools/issues/2885 1570*e1fe3e4aSElliott Hughes assert varfont2["OS/2"].usWeightClass == 400 1571*e1fe3e4aSElliott Hughes assert varfont2["OS/2"].usWidthClass == 5 1572*e1fe3e4aSElliott Hughes 1573*e1fe3e4aSElliott Hughes varfont = instancer.instantiateVariableFont( 1574*e1fe3e4aSElliott Hughes varfont2, {"wght": (100, 500, 900), "wdth": 87.5} 1575*e1fe3e4aSElliott Hughes ) 1576*e1fe3e4aSElliott Hughes 1577*e1fe3e4aSElliott Hughes assert varfont["OS/2"].usWeightClass == 500 1578*e1fe3e4aSElliott Hughes assert varfont["OS/2"].usWidthClass == 4 1579*e1fe3e4aSElliott Hughes 1580*e1fe3e4aSElliott Hughes @pytest.mark.parametrize( 1581*e1fe3e4aSElliott Hughes "overlap, wght", 1582*e1fe3e4aSElliott Hughes [ 1583*e1fe3e4aSElliott Hughes (instancer.OverlapMode.KEEP_AND_DONT_SET_FLAGS, 400), 1584*e1fe3e4aSElliott Hughes (instancer.OverlapMode.REMOVE, 400), 1585*e1fe3e4aSElliott Hughes (instancer.OverlapMode.REMOVE, 700), 1586*e1fe3e4aSElliott Hughes ], 1587*e1fe3e4aSElliott Hughes ) 1588*e1fe3e4aSElliott Hughes def test_overlap(self, varfont3, wght, overlap): 1589*e1fe3e4aSElliott Hughes pytest.importorskip("pathops") 1590*e1fe3e4aSElliott Hughes 1591*e1fe3e4aSElliott Hughes location = {"wght": wght} 1592*e1fe3e4aSElliott Hughes 1593*e1fe3e4aSElliott Hughes instance = instancer.instantiateVariableFont( 1594*e1fe3e4aSElliott Hughes varfont3, location, overlap=overlap 1595*e1fe3e4aSElliott Hughes ) 1596*e1fe3e4aSElliott Hughes 1597*e1fe3e4aSElliott Hughes expected = _get_expected_instance_ttx( 1598*e1fe3e4aSElliott Hughes "PartialInstancerTest3", wght, overlap=overlap 1599*e1fe3e4aSElliott Hughes ) 1600*e1fe3e4aSElliott Hughes 1601*e1fe3e4aSElliott Hughes assert _dump_ttx(instance) == expected 1602*e1fe3e4aSElliott Hughes 1603*e1fe3e4aSElliott Hughes def test_singlepos(self): 1604*e1fe3e4aSElliott Hughes varfont = ttLib.TTFont(recalcTimestamp=False) 1605*e1fe3e4aSElliott Hughes varfont.importXML(os.path.join(TESTDATA, "SinglePos.ttx")) 1606*e1fe3e4aSElliott Hughes 1607*e1fe3e4aSElliott Hughes location = {"wght": 280, "opsz": 18} 1608*e1fe3e4aSElliott Hughes 1609*e1fe3e4aSElliott Hughes instance = instancer.instantiateVariableFont( 1610*e1fe3e4aSElliott Hughes varfont, 1611*e1fe3e4aSElliott Hughes location, 1612*e1fe3e4aSElliott Hughes ) 1613*e1fe3e4aSElliott Hughes 1614*e1fe3e4aSElliott Hughes expected = _get_expected_instance_ttx("SinglePos", *location.values()) 1615*e1fe3e4aSElliott Hughes 1616*e1fe3e4aSElliott Hughes assert _dump_ttx(instance) == expected 1617*e1fe3e4aSElliott Hughes 1618*e1fe3e4aSElliott Hughes def test_varComposite(self): 1619*e1fe3e4aSElliott Hughes input_path = os.path.join( 1620*e1fe3e4aSElliott Hughes TESTDATA, "..", "..", "..", "ttLib", "data", "varc-ac00-ac01.ttf" 1621*e1fe3e4aSElliott Hughes ) 1622*e1fe3e4aSElliott Hughes varfont = ttLib.TTFont(input_path) 1623*e1fe3e4aSElliott Hughes 1624*e1fe3e4aSElliott Hughes location = {"wght": 600} 1625*e1fe3e4aSElliott Hughes 1626*e1fe3e4aSElliott Hughes instance = instancer.instantiateVariableFont( 1627*e1fe3e4aSElliott Hughes varfont, 1628*e1fe3e4aSElliott Hughes location, 1629*e1fe3e4aSElliott Hughes ) 1630*e1fe3e4aSElliott Hughes 1631*e1fe3e4aSElliott Hughes location = {"0000": 0.5} 1632*e1fe3e4aSElliott Hughes 1633*e1fe3e4aSElliott Hughes instance = instancer.instantiateVariableFont( 1634*e1fe3e4aSElliott Hughes varfont, 1635*e1fe3e4aSElliott Hughes location, 1636*e1fe3e4aSElliott Hughes ) 1637*e1fe3e4aSElliott Hughes 1638*e1fe3e4aSElliott Hughes 1639*e1fe3e4aSElliott Hughesdef _conditionSetAsDict(conditionSet, axisOrder): 1640*e1fe3e4aSElliott Hughes result = {} 1641*e1fe3e4aSElliott Hughes conditionSets = conditionSet.ConditionTable if conditionSet is not None else [] 1642*e1fe3e4aSElliott Hughes for cond in conditionSets: 1643*e1fe3e4aSElliott Hughes assert cond.Format == 1 1644*e1fe3e4aSElliott Hughes axisTag = axisOrder[cond.AxisIndex] 1645*e1fe3e4aSElliott Hughes result[axisTag] = (cond.FilterRangeMinValue, cond.FilterRangeMaxValue) 1646*e1fe3e4aSElliott Hughes return result 1647*e1fe3e4aSElliott Hughes 1648*e1fe3e4aSElliott Hughes 1649*e1fe3e4aSElliott Hughesdef _getSubstitutions(gsub, lookupIndices): 1650*e1fe3e4aSElliott Hughes subs = {} 1651*e1fe3e4aSElliott Hughes for index, lookup in enumerate(gsub.LookupList.Lookup): 1652*e1fe3e4aSElliott Hughes if index in lookupIndices: 1653*e1fe3e4aSElliott Hughes for subtable in lookup.SubTable: 1654*e1fe3e4aSElliott Hughes subs.update(subtable.mapping) 1655*e1fe3e4aSElliott Hughes return subs 1656*e1fe3e4aSElliott Hughes 1657*e1fe3e4aSElliott Hughes 1658*e1fe3e4aSElliott Hughesdef makeFeatureVarsFont(conditionalSubstitutions): 1659*e1fe3e4aSElliott Hughes axes = set() 1660*e1fe3e4aSElliott Hughes glyphs = set() 1661*e1fe3e4aSElliott Hughes for region, substitutions in conditionalSubstitutions: 1662*e1fe3e4aSElliott Hughes for box in region: 1663*e1fe3e4aSElliott Hughes axes.update(box.keys()) 1664*e1fe3e4aSElliott Hughes glyphs.update(*substitutions.items()) 1665*e1fe3e4aSElliott Hughes 1666*e1fe3e4aSElliott Hughes varfont = ttLib.TTFont() 1667*e1fe3e4aSElliott Hughes varfont.setGlyphOrder(sorted(glyphs)) 1668*e1fe3e4aSElliott Hughes 1669*e1fe3e4aSElliott Hughes fvar = varfont["fvar"] = ttLib.newTable("fvar") 1670*e1fe3e4aSElliott Hughes fvar.axes = [] 1671*e1fe3e4aSElliott Hughes for axisTag in sorted(axes): 1672*e1fe3e4aSElliott Hughes axis = _f_v_a_r.Axis() 1673*e1fe3e4aSElliott Hughes axis.axisTag = Tag(axisTag) 1674*e1fe3e4aSElliott Hughes fvar.axes.append(axis) 1675*e1fe3e4aSElliott Hughes 1676*e1fe3e4aSElliott Hughes featureVars.addFeatureVariations(varfont, conditionalSubstitutions) 1677*e1fe3e4aSElliott Hughes 1678*e1fe3e4aSElliott Hughes return varfont 1679*e1fe3e4aSElliott Hughes 1680*e1fe3e4aSElliott Hughes 1681*e1fe3e4aSElliott Hughesclass InstantiateFeatureVariationsTest(object): 1682*e1fe3e4aSElliott Hughes @pytest.mark.parametrize( 1683*e1fe3e4aSElliott Hughes "location, appliedSubs, expectedRecords", 1684*e1fe3e4aSElliott Hughes [ 1685*e1fe3e4aSElliott Hughes ({"wght": 0}, {}, [({"cntr": (0.75, 1.0)}, {"uni0041": "uni0061"})]), 1686*e1fe3e4aSElliott Hughes ( 1687*e1fe3e4aSElliott Hughes {"wght": -1.0}, 1688*e1fe3e4aSElliott Hughes {"uni0061": "uni0041"}, 1689*e1fe3e4aSElliott Hughes [ 1690*e1fe3e4aSElliott Hughes ({"cntr": (0, 0.25)}, {"uni0061": "uni0041"}), 1691*e1fe3e4aSElliott Hughes ({"cntr": (0.75, 1.0)}, {"uni0041": "uni0061"}), 1692*e1fe3e4aSElliott Hughes ({}, {}), 1693*e1fe3e4aSElliott Hughes ], 1694*e1fe3e4aSElliott Hughes ), 1695*e1fe3e4aSElliott Hughes ( 1696*e1fe3e4aSElliott Hughes {"wght": 1.0}, 1697*e1fe3e4aSElliott Hughes {"uni0024": "uni0024.nostroke"}, 1698*e1fe3e4aSElliott Hughes [ 1699*e1fe3e4aSElliott Hughes ( 1700*e1fe3e4aSElliott Hughes {"cntr": (0.75, 1.0)}, 1701*e1fe3e4aSElliott Hughes {"uni0024": "uni0024.nostroke", "uni0041": "uni0061"}, 1702*e1fe3e4aSElliott Hughes ), 1703*e1fe3e4aSElliott Hughes ({}, {}), 1704*e1fe3e4aSElliott Hughes ], 1705*e1fe3e4aSElliott Hughes ), 1706*e1fe3e4aSElliott Hughes ( 1707*e1fe3e4aSElliott Hughes {"cntr": 0}, 1708*e1fe3e4aSElliott Hughes {}, 1709*e1fe3e4aSElliott Hughes [ 1710*e1fe3e4aSElliott Hughes ({"wght": (-1.0, -0.45654)}, {"uni0061": "uni0041"}), 1711*e1fe3e4aSElliott Hughes ({"wght": (0.20886, 1.0)}, {"uni0024": "uni0024.nostroke"}), 1712*e1fe3e4aSElliott Hughes ], 1713*e1fe3e4aSElliott Hughes ), 1714*e1fe3e4aSElliott Hughes ( 1715*e1fe3e4aSElliott Hughes {"cntr": 1.0}, 1716*e1fe3e4aSElliott Hughes {"uni0041": "uni0061"}, 1717*e1fe3e4aSElliott Hughes [ 1718*e1fe3e4aSElliott Hughes ( 1719*e1fe3e4aSElliott Hughes {"wght": (0.20886, 1.0)}, 1720*e1fe3e4aSElliott Hughes {"uni0024": "uni0024.nostroke", "uni0041": "uni0061"}, 1721*e1fe3e4aSElliott Hughes ), 1722*e1fe3e4aSElliott Hughes ({}, {}), 1723*e1fe3e4aSElliott Hughes ], 1724*e1fe3e4aSElliott Hughes ), 1725*e1fe3e4aSElliott Hughes ( 1726*e1fe3e4aSElliott Hughes {"cntr": (-0.5, 0, 1.0)}, 1727*e1fe3e4aSElliott Hughes {}, 1728*e1fe3e4aSElliott Hughes [ 1729*e1fe3e4aSElliott Hughes ( 1730*e1fe3e4aSElliott Hughes {"wght": (0.20886, 1.0), "cntr": (0.75, 1)}, 1731*e1fe3e4aSElliott Hughes {"uni0024": "uni0024.nostroke", "uni0041": "uni0061"}, 1732*e1fe3e4aSElliott Hughes ), 1733*e1fe3e4aSElliott Hughes ( 1734*e1fe3e4aSElliott Hughes {"wght": (-1.0, -0.45654), "cntr": (0, 0.25)}, 1735*e1fe3e4aSElliott Hughes {"uni0061": "uni0041"}, 1736*e1fe3e4aSElliott Hughes ), 1737*e1fe3e4aSElliott Hughes ( 1738*e1fe3e4aSElliott Hughes {"cntr": (0.75, 1.0)}, 1739*e1fe3e4aSElliott Hughes {"uni0041": "uni0061"}, 1740*e1fe3e4aSElliott Hughes ), 1741*e1fe3e4aSElliott Hughes ( 1742*e1fe3e4aSElliott Hughes {"wght": (0.20886, 1.0)}, 1743*e1fe3e4aSElliott Hughes {"uni0024": "uni0024.nostroke"}, 1744*e1fe3e4aSElliott Hughes ), 1745*e1fe3e4aSElliott Hughes ], 1746*e1fe3e4aSElliott Hughes ), 1747*e1fe3e4aSElliott Hughes ( 1748*e1fe3e4aSElliott Hughes {"cntr": (0.8, 0.9, 1.0)}, 1749*e1fe3e4aSElliott Hughes {"uni0041": "uni0061"}, 1750*e1fe3e4aSElliott Hughes [ 1751*e1fe3e4aSElliott Hughes ( 1752*e1fe3e4aSElliott Hughes {"wght": (0.20886, 1.0)}, 1753*e1fe3e4aSElliott Hughes {"uni0024": "uni0024.nostroke", "uni0041": "uni0061"}, 1754*e1fe3e4aSElliott Hughes ), 1755*e1fe3e4aSElliott Hughes ( 1756*e1fe3e4aSElliott Hughes {}, 1757*e1fe3e4aSElliott Hughes {"uni0041": "uni0061"}, 1758*e1fe3e4aSElliott Hughes ), 1759*e1fe3e4aSElliott Hughes ], 1760*e1fe3e4aSElliott Hughes ), 1761*e1fe3e4aSElliott Hughes ( 1762*e1fe3e4aSElliott Hughes {"cntr": (0.7, 0.9, 1.0)}, 1763*e1fe3e4aSElliott Hughes {"uni0041": "uni0061"}, 1764*e1fe3e4aSElliott Hughes [ 1765*e1fe3e4aSElliott Hughes ( 1766*e1fe3e4aSElliott Hughes {"cntr": (-0.7499999999999999, 1.0), "wght": (0.20886, 1.0)}, 1767*e1fe3e4aSElliott Hughes {"uni0024": "uni0024.nostroke", "uni0041": "uni0061"}, 1768*e1fe3e4aSElliott Hughes ), 1769*e1fe3e4aSElliott Hughes ( 1770*e1fe3e4aSElliott Hughes {"cntr": (-0.7499999999999999, 1.0)}, 1771*e1fe3e4aSElliott Hughes {"uni0041": "uni0061"}, 1772*e1fe3e4aSElliott Hughes ), 1773*e1fe3e4aSElliott Hughes ( 1774*e1fe3e4aSElliott Hughes {"wght": (0.20886, 1.0)}, 1775*e1fe3e4aSElliott Hughes {"uni0024": "uni0024.nostroke"}, 1776*e1fe3e4aSElliott Hughes ), 1777*e1fe3e4aSElliott Hughes ( 1778*e1fe3e4aSElliott Hughes {}, 1779*e1fe3e4aSElliott Hughes {}, 1780*e1fe3e4aSElliott Hughes ), 1781*e1fe3e4aSElliott Hughes ], 1782*e1fe3e4aSElliott Hughes ), 1783*e1fe3e4aSElliott Hughes ], 1784*e1fe3e4aSElliott Hughes ) 1785*e1fe3e4aSElliott Hughes def test_partial_instance(self, location, appliedSubs, expectedRecords): 1786*e1fe3e4aSElliott Hughes font = makeFeatureVarsFont( 1787*e1fe3e4aSElliott Hughes [ 1788*e1fe3e4aSElliott Hughes ([{"wght": (0.20886, 1.0)}], {"uni0024": "uni0024.nostroke"}), 1789*e1fe3e4aSElliott Hughes ([{"cntr": (0.75, 1.0)}], {"uni0041": "uni0061"}), 1790*e1fe3e4aSElliott Hughes ( 1791*e1fe3e4aSElliott Hughes [{"wght": (-1.0, -0.45654), "cntr": (0, 0.25)}], 1792*e1fe3e4aSElliott Hughes {"uni0061": "uni0041"}, 1793*e1fe3e4aSElliott Hughes ), 1794*e1fe3e4aSElliott Hughes ] 1795*e1fe3e4aSElliott Hughes ) 1796*e1fe3e4aSElliott Hughes 1797*e1fe3e4aSElliott Hughes limits = instancer.NormalizedAxisLimits(location) 1798*e1fe3e4aSElliott Hughes instancer.instantiateFeatureVariations(font, limits) 1799*e1fe3e4aSElliott Hughes 1800*e1fe3e4aSElliott Hughes gsub = font["GSUB"].table 1801*e1fe3e4aSElliott Hughes featureVariations = gsub.FeatureVariations 1802*e1fe3e4aSElliott Hughes 1803*e1fe3e4aSElliott Hughes assert featureVariations.FeatureVariationCount == len(expectedRecords) 1804*e1fe3e4aSElliott Hughes 1805*e1fe3e4aSElliott Hughes axisOrder = [ 1806*e1fe3e4aSElliott Hughes a.axisTag 1807*e1fe3e4aSElliott Hughes for a in font["fvar"].axes 1808*e1fe3e4aSElliott Hughes if a.axisTag not in location or isinstance(location[a.axisTag], tuple) 1809*e1fe3e4aSElliott Hughes ] 1810*e1fe3e4aSElliott Hughes for i, (expectedConditionSet, expectedSubs) in enumerate(expectedRecords): 1811*e1fe3e4aSElliott Hughes rec = featureVariations.FeatureVariationRecord[i] 1812*e1fe3e4aSElliott Hughes conditionSet = _conditionSetAsDict(rec.ConditionSet, axisOrder) 1813*e1fe3e4aSElliott Hughes 1814*e1fe3e4aSElliott Hughes assert conditionSet == expectedConditionSet, i 1815*e1fe3e4aSElliott Hughes 1816*e1fe3e4aSElliott Hughes subsRecord = rec.FeatureTableSubstitution.SubstitutionRecord[0] 1817*e1fe3e4aSElliott Hughes lookupIndices = subsRecord.Feature.LookupListIndex 1818*e1fe3e4aSElliott Hughes substitutions = _getSubstitutions(gsub, lookupIndices) 1819*e1fe3e4aSElliott Hughes 1820*e1fe3e4aSElliott Hughes assert substitutions == expectedSubs, i 1821*e1fe3e4aSElliott Hughes 1822*e1fe3e4aSElliott Hughes appliedLookupIndices = gsub.FeatureList.FeatureRecord[0].Feature.LookupListIndex 1823*e1fe3e4aSElliott Hughes 1824*e1fe3e4aSElliott Hughes assert _getSubstitutions(gsub, appliedLookupIndices) == appliedSubs 1825*e1fe3e4aSElliott Hughes 1826*e1fe3e4aSElliott Hughes @pytest.mark.parametrize( 1827*e1fe3e4aSElliott Hughes "location, appliedSubs", 1828*e1fe3e4aSElliott Hughes [ 1829*e1fe3e4aSElliott Hughes ({"wght": 0, "cntr": 0}, None), 1830*e1fe3e4aSElliott Hughes ({"wght": -1.0, "cntr": 0}, {"uni0061": "uni0041"}), 1831*e1fe3e4aSElliott Hughes ({"wght": 1.0, "cntr": 0}, {"uni0024": "uni0024.nostroke"}), 1832*e1fe3e4aSElliott Hughes ({"wght": 0.0, "cntr": 1.0}, {"uni0041": "uni0061"}), 1833*e1fe3e4aSElliott Hughes ( 1834*e1fe3e4aSElliott Hughes {"wght": 1.0, "cntr": 1.0}, 1835*e1fe3e4aSElliott Hughes {"uni0041": "uni0061", "uni0024": "uni0024.nostroke"}, 1836*e1fe3e4aSElliott Hughes ), 1837*e1fe3e4aSElliott Hughes ({"wght": -1.0, "cntr": 0.3}, None), 1838*e1fe3e4aSElliott Hughes ], 1839*e1fe3e4aSElliott Hughes ) 1840*e1fe3e4aSElliott Hughes def test_full_instance(self, location, appliedSubs): 1841*e1fe3e4aSElliott Hughes font = makeFeatureVarsFont( 1842*e1fe3e4aSElliott Hughes [ 1843*e1fe3e4aSElliott Hughes ([{"wght": (0.20886, 1.0)}], {"uni0024": "uni0024.nostroke"}), 1844*e1fe3e4aSElliott Hughes ([{"cntr": (0.75, 1.0)}], {"uni0041": "uni0061"}), 1845*e1fe3e4aSElliott Hughes ( 1846*e1fe3e4aSElliott Hughes [{"wght": (-1.0, -0.45654), "cntr": (0, 0.25)}], 1847*e1fe3e4aSElliott Hughes {"uni0061": "uni0041"}, 1848*e1fe3e4aSElliott Hughes ), 1849*e1fe3e4aSElliott Hughes ] 1850*e1fe3e4aSElliott Hughes ) 1851*e1fe3e4aSElliott Hughes gsub = font["GSUB"].table 1852*e1fe3e4aSElliott Hughes assert gsub.FeatureVariations 1853*e1fe3e4aSElliott Hughes assert gsub.Version == 0x00010001 1854*e1fe3e4aSElliott Hughes 1855*e1fe3e4aSElliott Hughes location = instancer.NormalizedAxisLimits(location) 1856*e1fe3e4aSElliott Hughes 1857*e1fe3e4aSElliott Hughes instancer.instantiateFeatureVariations(font, location) 1858*e1fe3e4aSElliott Hughes 1859*e1fe3e4aSElliott Hughes assert not hasattr(gsub, "FeatureVariations") 1860*e1fe3e4aSElliott Hughes assert gsub.Version == 0x00010000 1861*e1fe3e4aSElliott Hughes 1862*e1fe3e4aSElliott Hughes if appliedSubs: 1863*e1fe3e4aSElliott Hughes lookupIndices = gsub.FeatureList.FeatureRecord[0].Feature.LookupListIndex 1864*e1fe3e4aSElliott Hughes assert _getSubstitutions(gsub, lookupIndices) == appliedSubs 1865*e1fe3e4aSElliott Hughes else: 1866*e1fe3e4aSElliott Hughes assert not gsub.FeatureList.FeatureRecord 1867*e1fe3e4aSElliott Hughes 1868*e1fe3e4aSElliott Hughes def test_null_conditionset(self): 1869*e1fe3e4aSElliott Hughes # A null ConditionSet offset should be treated like an empty ConditionTable, i.e. 1870*e1fe3e4aSElliott Hughes # all contexts are matched; see https://github.com/fonttools/fonttools/issues/3211 1871*e1fe3e4aSElliott Hughes font = makeFeatureVarsFont( 1872*e1fe3e4aSElliott Hughes [([{"wght": (-1.0, 1.0)}], {"uni0024": "uni0024.nostroke"})] 1873*e1fe3e4aSElliott Hughes ) 1874*e1fe3e4aSElliott Hughes gsub = font["GSUB"].table 1875*e1fe3e4aSElliott Hughes gsub.FeatureVariations.FeatureVariationRecord[0].ConditionSet = None 1876*e1fe3e4aSElliott Hughes 1877*e1fe3e4aSElliott Hughes location = instancer.NormalizedAxisLimits({"wght": 0.5}) 1878*e1fe3e4aSElliott Hughes instancer.instantiateFeatureVariations(font, location) 1879*e1fe3e4aSElliott Hughes 1880*e1fe3e4aSElliott Hughes assert not hasattr(gsub, "FeatureVariations") 1881*e1fe3e4aSElliott Hughes assert gsub.Version == 0x00010000 1882*e1fe3e4aSElliott Hughes 1883*e1fe3e4aSElliott Hughes lookupIndices = gsub.FeatureList.FeatureRecord[0].Feature.LookupListIndex 1884*e1fe3e4aSElliott Hughes assert _getSubstitutions(gsub, lookupIndices) == {"uni0024": "uni0024.nostroke"} 1885*e1fe3e4aSElliott Hughes 1886*e1fe3e4aSElliott Hughes def test_unsupported_condition_format(self, caplog): 1887*e1fe3e4aSElliott Hughes font = makeFeatureVarsFont( 1888*e1fe3e4aSElliott Hughes [ 1889*e1fe3e4aSElliott Hughes ( 1890*e1fe3e4aSElliott Hughes [{"wdth": (-1.0, -0.5), "wght": (0.5, 1.0)}], 1891*e1fe3e4aSElliott Hughes {"dollar": "dollar.nostroke"}, 1892*e1fe3e4aSElliott Hughes ) 1893*e1fe3e4aSElliott Hughes ] 1894*e1fe3e4aSElliott Hughes ) 1895*e1fe3e4aSElliott Hughes featureVariations = font["GSUB"].table.FeatureVariations 1896*e1fe3e4aSElliott Hughes rec1 = featureVariations.FeatureVariationRecord[0] 1897*e1fe3e4aSElliott Hughes assert len(rec1.ConditionSet.ConditionTable) == 2 1898*e1fe3e4aSElliott Hughes rec1.ConditionSet.ConditionTable[0].Format = 2 1899*e1fe3e4aSElliott Hughes 1900*e1fe3e4aSElliott Hughes with caplog.at_level(logging.WARNING, logger="fontTools.varLib.instancer"): 1901*e1fe3e4aSElliott Hughes instancer.instantiateFeatureVariations( 1902*e1fe3e4aSElliott Hughes font, instancer.NormalizedAxisLimits(wdth=0) 1903*e1fe3e4aSElliott Hughes ) 1904*e1fe3e4aSElliott Hughes 1905*e1fe3e4aSElliott Hughes assert ( 1906*e1fe3e4aSElliott Hughes "Condition table 0 of FeatureVariationRecord 0 " 1907*e1fe3e4aSElliott Hughes "has unsupported format (2); ignored" 1908*e1fe3e4aSElliott Hughes ) in caplog.text 1909*e1fe3e4aSElliott Hughes 1910*e1fe3e4aSElliott Hughes # check that record with unsupported condition format (but whose other 1911*e1fe3e4aSElliott Hughes # conditions do not reference pinned axes) is kept as is 1912*e1fe3e4aSElliott Hughes featureVariations = font["GSUB"].table.FeatureVariations 1913*e1fe3e4aSElliott Hughes assert featureVariations.FeatureVariationRecord[0] is rec1 1914*e1fe3e4aSElliott Hughes assert len(rec1.ConditionSet.ConditionTable) == 2 1915*e1fe3e4aSElliott Hughes assert rec1.ConditionSet.ConditionTable[0].Format == 2 1916*e1fe3e4aSElliott Hughes 1917*e1fe3e4aSElliott Hughes def test_GSUB_FeatureVariations_is_None(self, varfont2): 1918*e1fe3e4aSElliott Hughes varfont2["GSUB"].table.Version = 0x00010001 1919*e1fe3e4aSElliott Hughes varfont2["GSUB"].table.FeatureVariations = None 1920*e1fe3e4aSElliott Hughes tmp = BytesIO() 1921*e1fe3e4aSElliott Hughes varfont2.save(tmp) 1922*e1fe3e4aSElliott Hughes varfont = ttLib.TTFont(tmp) 1923*e1fe3e4aSElliott Hughes 1924*e1fe3e4aSElliott Hughes # DO NOT raise an exception when the optional 'FeatureVariations' attribute is 1925*e1fe3e4aSElliott Hughes # present but is set to None (e.g. with GSUB 1.1); skip and do nothing. 1926*e1fe3e4aSElliott Hughes assert varfont["GSUB"].table.FeatureVariations is None 1927*e1fe3e4aSElliott Hughes instancer.instantiateFeatureVariations(varfont, {"wght": 400, "wdth": 100}) 1928*e1fe3e4aSElliott Hughes assert varfont["GSUB"].table.FeatureVariations is None 1929*e1fe3e4aSElliott Hughes 1930*e1fe3e4aSElliott Hughes 1931*e1fe3e4aSElliott Hughesclass LimitTupleVariationAxisRangesTest: 1932*e1fe3e4aSElliott Hughes def check_limit_single_var_axis_range(self, var, axisTag, axisRange, expected): 1933*e1fe3e4aSElliott Hughes result = instancer.changeTupleVariationAxisLimit(var, axisTag, axisRange) 1934*e1fe3e4aSElliott Hughes print(result) 1935*e1fe3e4aSElliott Hughes 1936*e1fe3e4aSElliott Hughes assert len(result) == len(expected) 1937*e1fe3e4aSElliott Hughes for v1, v2 in zip(result, expected): 1938*e1fe3e4aSElliott Hughes assert v1.coordinates == pytest.approx(v2.coordinates) 1939*e1fe3e4aSElliott Hughes assert v1.axes.keys() == v2.axes.keys() 1940*e1fe3e4aSElliott Hughes for k in v1.axes: 1941*e1fe3e4aSElliott Hughes p, q = v1.axes[k], v2.axes[k] 1942*e1fe3e4aSElliott Hughes assert p == pytest.approx(q) 1943*e1fe3e4aSElliott Hughes 1944*e1fe3e4aSElliott Hughes @pytest.mark.parametrize( 1945*e1fe3e4aSElliott Hughes "var, axisTag, newMax, expected", 1946*e1fe3e4aSElliott Hughes [ 1947*e1fe3e4aSElliott Hughes ( 1948*e1fe3e4aSElliott Hughes TupleVariation({"wght": (0.0, 1.0, 1.0)}, [100, 100]), 1949*e1fe3e4aSElliott Hughes "wdth", 1950*e1fe3e4aSElliott Hughes 0.5, 1951*e1fe3e4aSElliott Hughes [TupleVariation({"wght": (0.0, 1.0, 1.0)}, [100, 100])], 1952*e1fe3e4aSElliott Hughes ), 1953*e1fe3e4aSElliott Hughes ( 1954*e1fe3e4aSElliott Hughes TupleVariation({"wght": (0.0, 1.0, 1.0)}, [100, 100]), 1955*e1fe3e4aSElliott Hughes "wght", 1956*e1fe3e4aSElliott Hughes 0.5, 1957*e1fe3e4aSElliott Hughes [TupleVariation({"wght": (0.0, 1.0, 1.0)}, [50, 50])], 1958*e1fe3e4aSElliott Hughes ), 1959*e1fe3e4aSElliott Hughes ( 1960*e1fe3e4aSElliott Hughes TupleVariation({"wght": (0.0, 1.0, 1.0)}, [100, 100]), 1961*e1fe3e4aSElliott Hughes "wght", 1962*e1fe3e4aSElliott Hughes 0.8, 1963*e1fe3e4aSElliott Hughes [TupleVariation({"wght": (0.0, 1.0, 1.0)}, [80, 80])], 1964*e1fe3e4aSElliott Hughes ), 1965*e1fe3e4aSElliott Hughes ( 1966*e1fe3e4aSElliott Hughes TupleVariation({"wght": (0.0, 1.0, 1.0)}, [100, 100]), 1967*e1fe3e4aSElliott Hughes "wght", 1968*e1fe3e4aSElliott Hughes 1.0, 1969*e1fe3e4aSElliott Hughes [TupleVariation({"wght": (0.0, 1.0, 1.0)}, [100, 100])], 1970*e1fe3e4aSElliott Hughes ), 1971*e1fe3e4aSElliott Hughes (TupleVariation({"wght": (0.0, 1.0, 1.0)}, [100, 100]), "wght", 0.0, []), 1972*e1fe3e4aSElliott Hughes (TupleVariation({"wght": (0.5, 1.0, 1.0)}, [100, 100]), "wght", 0.4, []), 1973*e1fe3e4aSElliott Hughes ( 1974*e1fe3e4aSElliott Hughes TupleVariation({"wght": (0.0, 0.5, 1.0)}, [100, 100]), 1975*e1fe3e4aSElliott Hughes "wght", 1976*e1fe3e4aSElliott Hughes 0.5, 1977*e1fe3e4aSElliott Hughes [TupleVariation({"wght": (0.0, 1.0, 1.0)}, [100, 100])], 1978*e1fe3e4aSElliott Hughes ), 1979*e1fe3e4aSElliott Hughes ( 1980*e1fe3e4aSElliott Hughes TupleVariation({"wght": (0.0, 0.5, 1.0)}, [100, 100]), 1981*e1fe3e4aSElliott Hughes "wght", 1982*e1fe3e4aSElliott Hughes 0.4, 1983*e1fe3e4aSElliott Hughes [TupleVariation({"wght": (0.0, 1.0, 1.0)}, [80, 80])], 1984*e1fe3e4aSElliott Hughes ), 1985*e1fe3e4aSElliott Hughes ( 1986*e1fe3e4aSElliott Hughes TupleVariation({"wght": (0.0, 0.5, 1.0)}, [100, 100]), 1987*e1fe3e4aSElliott Hughes "wght", 1988*e1fe3e4aSElliott Hughes 0.6, 1989*e1fe3e4aSElliott Hughes [ 1990*e1fe3e4aSElliott Hughes TupleVariation({"wght": (0.0, 0.833334, 1.0)}, [100, 100]), 1991*e1fe3e4aSElliott Hughes TupleVariation({"wght": (0.833334, 1.0, 1.0)}, [80, 80]), 1992*e1fe3e4aSElliott Hughes ], 1993*e1fe3e4aSElliott Hughes ), 1994*e1fe3e4aSElliott Hughes ( 1995*e1fe3e4aSElliott Hughes TupleVariation({"wght": (0.0, 0.2, 1.0)}, [100, 100]), 1996*e1fe3e4aSElliott Hughes "wght", 1997*e1fe3e4aSElliott Hughes 0.4, 1998*e1fe3e4aSElliott Hughes [ 1999*e1fe3e4aSElliott Hughes TupleVariation({"wght": (0.0, 0.5, 1.0)}, [100, 100]), 2000*e1fe3e4aSElliott Hughes TupleVariation({"wght": (0.5, 1.0, 1.0)}, [75, 75]), 2001*e1fe3e4aSElliott Hughes ], 2002*e1fe3e4aSElliott Hughes ), 2003*e1fe3e4aSElliott Hughes ( 2004*e1fe3e4aSElliott Hughes TupleVariation({"wght": (0.0, 0.2, 1.0)}, [100, 100]), 2005*e1fe3e4aSElliott Hughes "wght", 2006*e1fe3e4aSElliott Hughes 0.5, 2007*e1fe3e4aSElliott Hughes [ 2008*e1fe3e4aSElliott Hughes TupleVariation({"wght": (0.0, 0.4, 1)}, [100, 100]), 2009*e1fe3e4aSElliott Hughes TupleVariation({"wght": (0.4, 1, 1)}, [62.5, 62.5]), 2010*e1fe3e4aSElliott Hughes ], 2011*e1fe3e4aSElliott Hughes ), 2012*e1fe3e4aSElliott Hughes ( 2013*e1fe3e4aSElliott Hughes TupleVariation({"wght": (0.5, 0.5, 1.0)}, [100, 100]), 2014*e1fe3e4aSElliott Hughes "wght", 2015*e1fe3e4aSElliott Hughes 0.5, 2016*e1fe3e4aSElliott Hughes [TupleVariation({"wght": (1.0, 1.0, 1.0)}, [100, 100])], 2017*e1fe3e4aSElliott Hughes ), 2018*e1fe3e4aSElliott Hughes ], 2019*e1fe3e4aSElliott Hughes ) 2020*e1fe3e4aSElliott Hughes def test_positive_var(self, var, axisTag, newMax, expected): 2021*e1fe3e4aSElliott Hughes axisRange = instancer.NormalizedAxisTripleAndDistances(0, 0, newMax) 2022*e1fe3e4aSElliott Hughes self.check_limit_single_var_axis_range(var, axisTag, axisRange, expected) 2023*e1fe3e4aSElliott Hughes 2024*e1fe3e4aSElliott Hughes @pytest.mark.parametrize( 2025*e1fe3e4aSElliott Hughes "var, axisTag, newMin, expected", 2026*e1fe3e4aSElliott Hughes [ 2027*e1fe3e4aSElliott Hughes ( 2028*e1fe3e4aSElliott Hughes TupleVariation({"wght": (-1.0, -1.0, 0.0)}, [100, 100]), 2029*e1fe3e4aSElliott Hughes "wdth", 2030*e1fe3e4aSElliott Hughes -0.5, 2031*e1fe3e4aSElliott Hughes [TupleVariation({"wght": (-1.0, -1.0, 0.0)}, [100, 100])], 2032*e1fe3e4aSElliott Hughes ), 2033*e1fe3e4aSElliott Hughes ( 2034*e1fe3e4aSElliott Hughes TupleVariation({"wght": (-1.0, -1.0, 0.0)}, [100, 100]), 2035*e1fe3e4aSElliott Hughes "wght", 2036*e1fe3e4aSElliott Hughes -0.5, 2037*e1fe3e4aSElliott Hughes [TupleVariation({"wght": (-1.0, -1.0, 0.0)}, [50, 50])], 2038*e1fe3e4aSElliott Hughes ), 2039*e1fe3e4aSElliott Hughes ( 2040*e1fe3e4aSElliott Hughes TupleVariation({"wght": (-1.0, -1.0, 0.0)}, [100, 100]), 2041*e1fe3e4aSElliott Hughes "wght", 2042*e1fe3e4aSElliott Hughes -0.8, 2043*e1fe3e4aSElliott Hughes [TupleVariation({"wght": (-1.0, -1.0, 0.0)}, [80, 80])], 2044*e1fe3e4aSElliott Hughes ), 2045*e1fe3e4aSElliott Hughes ( 2046*e1fe3e4aSElliott Hughes TupleVariation({"wght": (-1.0, -1.0, 0.0)}, [100, 100]), 2047*e1fe3e4aSElliott Hughes "wght", 2048*e1fe3e4aSElliott Hughes -1.0, 2049*e1fe3e4aSElliott Hughes [TupleVariation({"wght": (-1.0, -1.0, 0.0)}, [100, 100])], 2050*e1fe3e4aSElliott Hughes ), 2051*e1fe3e4aSElliott Hughes (TupleVariation({"wght": (-1.0, -1.0, 0.0)}, [100, 100]), "wght", 0.0, []), 2052*e1fe3e4aSElliott Hughes ( 2053*e1fe3e4aSElliott Hughes TupleVariation({"wght": (-1.0, -1.0, -0.5)}, [100, 100]), 2054*e1fe3e4aSElliott Hughes "wght", 2055*e1fe3e4aSElliott Hughes -0.4, 2056*e1fe3e4aSElliott Hughes [], 2057*e1fe3e4aSElliott Hughes ), 2058*e1fe3e4aSElliott Hughes ( 2059*e1fe3e4aSElliott Hughes TupleVariation({"wght": (-1.0, -0.5, 0.0)}, [100, 100]), 2060*e1fe3e4aSElliott Hughes "wght", 2061*e1fe3e4aSElliott Hughes -0.5, 2062*e1fe3e4aSElliott Hughes [TupleVariation({"wght": (-1.0, -1.0, 0.0)}, [100, 100])], 2063*e1fe3e4aSElliott Hughes ), 2064*e1fe3e4aSElliott Hughes ( 2065*e1fe3e4aSElliott Hughes TupleVariation({"wght": (-1.0, -0.5, 0.0)}, [100, 100]), 2066*e1fe3e4aSElliott Hughes "wght", 2067*e1fe3e4aSElliott Hughes -0.4, 2068*e1fe3e4aSElliott Hughes [TupleVariation({"wght": (-1.0, -1.0, 0.0)}, [80, 80])], 2069*e1fe3e4aSElliott Hughes ), 2070*e1fe3e4aSElliott Hughes ( 2071*e1fe3e4aSElliott Hughes TupleVariation({"wght": (-1.0, -0.5, 0.0)}, [100, 100]), 2072*e1fe3e4aSElliott Hughes "wght", 2073*e1fe3e4aSElliott Hughes -0.6, 2074*e1fe3e4aSElliott Hughes [ 2075*e1fe3e4aSElliott Hughes TupleVariation({"wght": (-1.0, -0.833334, 0.0)}, [100, 100]), 2076*e1fe3e4aSElliott Hughes TupleVariation({"wght": (-1.0, -1.0, -0.833334)}, [80, 80]), 2077*e1fe3e4aSElliott Hughes ], 2078*e1fe3e4aSElliott Hughes ), 2079*e1fe3e4aSElliott Hughes ( 2080*e1fe3e4aSElliott Hughes TupleVariation({"wght": (-1.0, -0.2, 0.0)}, [100, 100]), 2081*e1fe3e4aSElliott Hughes "wght", 2082*e1fe3e4aSElliott Hughes -0.4, 2083*e1fe3e4aSElliott Hughes [ 2084*e1fe3e4aSElliott Hughes TupleVariation({"wght": (-1.0, -0.5, -0.0)}, [100, 100]), 2085*e1fe3e4aSElliott Hughes TupleVariation({"wght": (-1.0, -1.0, -0.5)}, [75, 75]), 2086*e1fe3e4aSElliott Hughes ], 2087*e1fe3e4aSElliott Hughes ), 2088*e1fe3e4aSElliott Hughes ( 2089*e1fe3e4aSElliott Hughes TupleVariation({"wght": (-1.0, -0.2, 0.0)}, [100, 100]), 2090*e1fe3e4aSElliott Hughes "wght", 2091*e1fe3e4aSElliott Hughes -0.5, 2092*e1fe3e4aSElliott Hughes [ 2093*e1fe3e4aSElliott Hughes TupleVariation({"wght": (-1.0, -0.4, 0.0)}, [100, 100]), 2094*e1fe3e4aSElliott Hughes TupleVariation({"wght": (-1.0, -1.0, -0.4)}, [62.5, 62.5]), 2095*e1fe3e4aSElliott Hughes ], 2096*e1fe3e4aSElliott Hughes ), 2097*e1fe3e4aSElliott Hughes ( 2098*e1fe3e4aSElliott Hughes TupleVariation({"wght": (-1.0, -0.5, -0.5)}, [100, 100]), 2099*e1fe3e4aSElliott Hughes "wght", 2100*e1fe3e4aSElliott Hughes -0.5, 2101*e1fe3e4aSElliott Hughes [TupleVariation({"wght": (-1.0, -1.0, -1.0)}, [100, 100])], 2102*e1fe3e4aSElliott Hughes ), 2103*e1fe3e4aSElliott Hughes ], 2104*e1fe3e4aSElliott Hughes ) 2105*e1fe3e4aSElliott Hughes def test_negative_var(self, var, axisTag, newMin, expected): 2106*e1fe3e4aSElliott Hughes axisRange = instancer.NormalizedAxisTripleAndDistances(newMin, 0, 0, 1, 1) 2107*e1fe3e4aSElliott Hughes self.check_limit_single_var_axis_range(var, axisTag, axisRange, expected) 2108*e1fe3e4aSElliott Hughes 2109*e1fe3e4aSElliott Hughes 2110*e1fe3e4aSElliott Hughes@pytest.mark.parametrize( 2111*e1fe3e4aSElliott Hughes "oldRange, newLimit, expected", 2112*e1fe3e4aSElliott Hughes [ 2113*e1fe3e4aSElliott Hughes ((1.0, -1.0), (-1.0, 0, 1.0), None), # invalid oldRange min > max 2114*e1fe3e4aSElliott Hughes ((0.6, 1.0), (0, 0, 0.5), None), 2115*e1fe3e4aSElliott Hughes ((-1.0, -0.6), (-0.5, 0, 0), None), 2116*e1fe3e4aSElliott Hughes ((0.4, 1.0), (0, 0, 0.5), (0.8, 1.0)), 2117*e1fe3e4aSElliott Hughes ((-1.0, -0.4), (-0.5, 0, 0), (-1.0, -0.8)), 2118*e1fe3e4aSElliott Hughes ((0.4, 1.0), (0, 0, 0.4), (1.0, 1.0)), 2119*e1fe3e4aSElliott Hughes ((-1.0, -0.4), (-0.4, 0, 0), (-1.0, -1.0)), 2120*e1fe3e4aSElliott Hughes ((-0.5, 0.5), (-0.4, 0, 0.4), (-1.0, 1.0)), 2121*e1fe3e4aSElliott Hughes ((0, 1.0), (-1.0, 0, 0), (0, 0)), # or None? 2122*e1fe3e4aSElliott Hughes ((-1.0, 0), (0, 0, 1.0), (0, 0)), # or None? 2123*e1fe3e4aSElliott Hughes ], 2124*e1fe3e4aSElliott Hughes) 2125*e1fe3e4aSElliott Hughesdef test_limitFeatureVariationConditionRange(oldRange, newLimit, expected): 2126*e1fe3e4aSElliott Hughes condition = featureVars.buildConditionTable(0, *oldRange) 2127*e1fe3e4aSElliott Hughes 2128*e1fe3e4aSElliott Hughes result = instancer.featureVars._limitFeatureVariationConditionRange( 2129*e1fe3e4aSElliott Hughes condition, instancer.NormalizedAxisTripleAndDistances(*newLimit, 1, 1) 2130*e1fe3e4aSElliott Hughes ) 2131*e1fe3e4aSElliott Hughes 2132*e1fe3e4aSElliott Hughes assert result == expected 2133*e1fe3e4aSElliott Hughes 2134*e1fe3e4aSElliott Hughes 2135*e1fe3e4aSElliott Hughes@pytest.mark.parametrize( 2136*e1fe3e4aSElliott Hughes "limits, expected", 2137*e1fe3e4aSElliott Hughes [ 2138*e1fe3e4aSElliott Hughes (["wght=400", "wdth=100"], {"wght": 400, "wdth": 100}), 2139*e1fe3e4aSElliott Hughes (["wght=400:900"], {"wght": (400, 900)}), 2140*e1fe3e4aSElliott Hughes (["wght=400:700:900"], {"wght": (400, 700, 900)}), 2141*e1fe3e4aSElliott Hughes (["slnt=11.4"], {"slnt": 11.399994}), 2142*e1fe3e4aSElliott Hughes (["ABCD=drop"], {"ABCD": None}), 2143*e1fe3e4aSElliott Hughes (["wght=:500:"], {"wght": (None, 500, None)}), 2144*e1fe3e4aSElliott Hughes (["wght=::700"], {"wght": (None, None, 700)}), 2145*e1fe3e4aSElliott Hughes (["wght=200::"], {"wght": (200, None, None)}), 2146*e1fe3e4aSElliott Hughes (["wght=200:300:"], {"wght": (200, 300, None)}), 2147*e1fe3e4aSElliott Hughes (["wght=:300:500"], {"wght": (None, 300, 500)}), 2148*e1fe3e4aSElliott Hughes (["wght=300::700"], {"wght": (300, None, 700)}), 2149*e1fe3e4aSElliott Hughes (["wght=300:700"], {"wght": (300, None, 700)}), 2150*e1fe3e4aSElliott Hughes (["wght=:700"], {"wght": (None, None, 700)}), 2151*e1fe3e4aSElliott Hughes (["wght=200:"], {"wght": (200, None, None)}), 2152*e1fe3e4aSElliott Hughes ], 2153*e1fe3e4aSElliott Hughes) 2154*e1fe3e4aSElliott Hughesdef test_parseLimits(limits, expected): 2155*e1fe3e4aSElliott Hughes limits = instancer.parseLimits(limits) 2156*e1fe3e4aSElliott Hughes expected = instancer.AxisLimits(expected) 2157*e1fe3e4aSElliott Hughes 2158*e1fe3e4aSElliott Hughes assert limits.keys() == expected.keys() 2159*e1fe3e4aSElliott Hughes for axis, triple in limits.items(): 2160*e1fe3e4aSElliott Hughes expected_triple = expected[axis] 2161*e1fe3e4aSElliott Hughes if expected_triple is None: 2162*e1fe3e4aSElliott Hughes assert triple is None 2163*e1fe3e4aSElliott Hughes else: 2164*e1fe3e4aSElliott Hughes assert isinstance(triple, instancer.AxisTriple) 2165*e1fe3e4aSElliott Hughes assert isinstance(expected_triple, instancer.AxisTriple) 2166*e1fe3e4aSElliott Hughes assert triple == pytest.approx(expected_triple) 2167*e1fe3e4aSElliott Hughes 2168*e1fe3e4aSElliott Hughes 2169*e1fe3e4aSElliott Hughes@pytest.mark.parametrize( 2170*e1fe3e4aSElliott Hughes "limits", [["abcde=123", "=0", "wght=:", "wght=1:", "wght=abcd", "wght=x:y"]] 2171*e1fe3e4aSElliott Hughes) 2172*e1fe3e4aSElliott Hughesdef test_parseLimits_invalid(limits): 2173*e1fe3e4aSElliott Hughes with pytest.raises(ValueError, match="invalid location format"): 2174*e1fe3e4aSElliott Hughes instancer.parseLimits(limits) 2175*e1fe3e4aSElliott Hughes 2176*e1fe3e4aSElliott Hughes 2177*e1fe3e4aSElliott Hughes@pytest.mark.parametrize( 2178*e1fe3e4aSElliott Hughes "limits, expected", 2179*e1fe3e4aSElliott Hughes [ 2180*e1fe3e4aSElliott Hughes # 300, 500 come from the font having 100,400,900 fvar axis limits. 2181*e1fe3e4aSElliott Hughes ({"wght": (100, 400)}, {"wght": (-1.0, 0, 0, 300, 500)}), 2182*e1fe3e4aSElliott Hughes ({"wght": (100, 400, 400)}, {"wght": (-1.0, 0, 0, 300, 500)}), 2183*e1fe3e4aSElliott Hughes ({"wght": (100, 300, 400)}, {"wght": (-1.0, -0.5, 0, 300, 500)}), 2184*e1fe3e4aSElliott Hughes ], 2185*e1fe3e4aSElliott Hughes) 2186*e1fe3e4aSElliott Hughesdef test_normalizeAxisLimits(varfont, limits, expected): 2187*e1fe3e4aSElliott Hughes limits = instancer.AxisLimits(limits) 2188*e1fe3e4aSElliott Hughes 2189*e1fe3e4aSElliott Hughes normalized = limits.normalize(varfont) 2190*e1fe3e4aSElliott Hughes 2191*e1fe3e4aSElliott Hughes assert normalized == instancer.NormalizedAxisLimits(expected) 2192*e1fe3e4aSElliott Hughes 2193*e1fe3e4aSElliott Hughes 2194*e1fe3e4aSElliott Hughesdef test_normalizeAxisLimits_no_avar(varfont): 2195*e1fe3e4aSElliott Hughes del varfont["avar"] 2196*e1fe3e4aSElliott Hughes 2197*e1fe3e4aSElliott Hughes limits = instancer.AxisLimits(wght=(400, 400, 500)) 2198*e1fe3e4aSElliott Hughes normalized = limits.normalize(varfont) 2199*e1fe3e4aSElliott Hughes 2200*e1fe3e4aSElliott Hughes assert normalized["wght"] == pytest.approx((0, 0, 0.2, 300, 500), 1e-4) 2201*e1fe3e4aSElliott Hughes 2202*e1fe3e4aSElliott Hughes 2203*e1fe3e4aSElliott Hughesdef test_normalizeAxisLimits_missing_from_fvar(varfont): 2204*e1fe3e4aSElliott Hughes with pytest.raises(ValueError, match="not present in fvar"): 2205*e1fe3e4aSElliott Hughes instancer.AxisLimits({"ZZZZ": 1000}).normalize(varfont) 2206*e1fe3e4aSElliott Hughes 2207*e1fe3e4aSElliott Hughes 2208*e1fe3e4aSElliott Hughesdef test_sanityCheckVariableTables(varfont): 2209*e1fe3e4aSElliott Hughes font = ttLib.TTFont() 2210*e1fe3e4aSElliott Hughes with pytest.raises(ValueError, match="Missing required table fvar"): 2211*e1fe3e4aSElliott Hughes instancer.sanityCheckVariableTables(font) 2212*e1fe3e4aSElliott Hughes 2213*e1fe3e4aSElliott Hughes del varfont["glyf"] 2214*e1fe3e4aSElliott Hughes 2215*e1fe3e4aSElliott Hughes with pytest.raises(ValueError, match="Can't have gvar without glyf"): 2216*e1fe3e4aSElliott Hughes instancer.sanityCheckVariableTables(varfont) 2217*e1fe3e4aSElliott Hughes 2218*e1fe3e4aSElliott Hughes 2219*e1fe3e4aSElliott Hughesdef test_main(varfont, tmpdir): 2220*e1fe3e4aSElliott Hughes fontfile = str(tmpdir / "PartialInstancerTest-VF.ttf") 2221*e1fe3e4aSElliott Hughes varfont.save(fontfile) 2222*e1fe3e4aSElliott Hughes args = [fontfile, "wght=400"] 2223*e1fe3e4aSElliott Hughes 2224*e1fe3e4aSElliott Hughes # exits without errors 2225*e1fe3e4aSElliott Hughes assert instancer.main(args) is None 2226*e1fe3e4aSElliott Hughes 2227*e1fe3e4aSElliott Hughes 2228*e1fe3e4aSElliott Hughesdef test_main_exit_nonexistent_file(capsys): 2229*e1fe3e4aSElliott Hughes with pytest.raises(SystemExit): 2230*e1fe3e4aSElliott Hughes instancer.main([""]) 2231*e1fe3e4aSElliott Hughes captured = capsys.readouterr() 2232*e1fe3e4aSElliott Hughes 2233*e1fe3e4aSElliott Hughes assert "No such file ''" in captured.err 2234*e1fe3e4aSElliott Hughes 2235*e1fe3e4aSElliott Hughes 2236*e1fe3e4aSElliott Hughesdef test_main_exit_invalid_location(varfont, tmpdir, capsys): 2237*e1fe3e4aSElliott Hughes fontfile = str(tmpdir / "PartialInstancerTest-VF.ttf") 2238*e1fe3e4aSElliott Hughes varfont.save(fontfile) 2239*e1fe3e4aSElliott Hughes 2240*e1fe3e4aSElliott Hughes with pytest.raises(SystemExit): 2241*e1fe3e4aSElliott Hughes instancer.main([fontfile, "wght:100"]) 2242*e1fe3e4aSElliott Hughes captured = capsys.readouterr() 2243*e1fe3e4aSElliott Hughes 2244*e1fe3e4aSElliott Hughes assert "invalid location format" in captured.err 2245*e1fe3e4aSElliott Hughes 2246*e1fe3e4aSElliott Hughes 2247*e1fe3e4aSElliott Hughesdef test_main_exit_multiple_limits(varfont, tmpdir, capsys): 2248*e1fe3e4aSElliott Hughes fontfile = str(tmpdir / "PartialInstancerTest-VF.ttf") 2249*e1fe3e4aSElliott Hughes varfont.save(fontfile) 2250*e1fe3e4aSElliott Hughes 2251*e1fe3e4aSElliott Hughes with pytest.raises(SystemExit): 2252*e1fe3e4aSElliott Hughes instancer.main([fontfile, "wght=400", "wght=90"]) 2253*e1fe3e4aSElliott Hughes captured = capsys.readouterr() 2254*e1fe3e4aSElliott Hughes 2255*e1fe3e4aSElliott Hughes assert "Specified multiple limits for the same axis" in captured.err 2256*e1fe3e4aSElliott Hughes 2257*e1fe3e4aSElliott Hughes 2258*e1fe3e4aSElliott Hughesdef test_set_ribbi_bits(): 2259*e1fe3e4aSElliott Hughes varfont = ttLib.TTFont() 2260*e1fe3e4aSElliott Hughes varfont.importXML(os.path.join(TESTDATA, "STATInstancerTest.ttx")) 2261*e1fe3e4aSElliott Hughes 2262*e1fe3e4aSElliott Hughes for location in [instance.coordinates for instance in varfont["fvar"].instances]: 2263*e1fe3e4aSElliott Hughes instance = instancer.instantiateVariableFont( 2264*e1fe3e4aSElliott Hughes varfont, location, updateFontNames=True 2265*e1fe3e4aSElliott Hughes ) 2266*e1fe3e4aSElliott Hughes name_id_2 = instance["name"].getDebugName(2) 2267*e1fe3e4aSElliott Hughes mac_style = instance["head"].macStyle 2268*e1fe3e4aSElliott Hughes fs_selection = instance["OS/2"].fsSelection & 0b1100001 # Just bits 0, 5, 6 2269*e1fe3e4aSElliott Hughes 2270*e1fe3e4aSElliott Hughes if location["ital"] == 0: 2271*e1fe3e4aSElliott Hughes if location["wght"] == 700: 2272*e1fe3e4aSElliott Hughes assert name_id_2 == "Bold", location 2273*e1fe3e4aSElliott Hughes assert mac_style == 0b01, location 2274*e1fe3e4aSElliott Hughes assert fs_selection == 0b0100000, location 2275*e1fe3e4aSElliott Hughes else: 2276*e1fe3e4aSElliott Hughes assert name_id_2 == "Regular", location 2277*e1fe3e4aSElliott Hughes assert mac_style == 0b00, location 2278*e1fe3e4aSElliott Hughes assert fs_selection == 0b1000000, location 2279*e1fe3e4aSElliott Hughes else: 2280*e1fe3e4aSElliott Hughes if location["wght"] == 700: 2281*e1fe3e4aSElliott Hughes assert name_id_2 == "Bold Italic", location 2282*e1fe3e4aSElliott Hughes assert mac_style == 0b11, location 2283*e1fe3e4aSElliott Hughes assert fs_selection == 0b0100001, location 2284*e1fe3e4aSElliott Hughes else: 2285*e1fe3e4aSElliott Hughes assert name_id_2 == "Italic", location 2286*e1fe3e4aSElliott Hughes assert mac_style == 0b10, location 2287*e1fe3e4aSElliott Hughes assert fs_selection == 0b0000001, location 2288