xref: /aosp_15_r20/external/fonttools/Tests/varLib/instancer/instancer_test.py (revision e1fe3e4ad2793916b15cccdc4a7da52a7e1dd0e9)
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