• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1from fontTools.varLib.models import (
2    normalizeLocation,
3    supportScalar,
4    VariationModel,
5    VariationModelError,
6)
7import pytest
8
9
10def test_normalizeLocation():
11    axes = {"wght": (100, 400, 900)}
12    assert normalizeLocation({"wght": 400}, axes) == {"wght": 0.0}
13    assert normalizeLocation({"wght": 100}, axes) == {"wght": -1.0}
14    assert normalizeLocation({"wght": 900}, axes) == {"wght": 1.0}
15    assert normalizeLocation({"wght": 650}, axes) == {"wght": 0.5}
16    assert normalizeLocation({"wght": 1000}, axes) == {"wght": 1.0}
17    assert normalizeLocation({"wght": 0}, axes) == {"wght": -1.0}
18
19    axes = {"wght": (0, 0, 1000)}
20    assert normalizeLocation({"wght": 0}, axes) == {"wght": 0.0}
21    assert normalizeLocation({"wght": -1}, axes) == {"wght": 0.0}
22    assert normalizeLocation({"wght": 1000}, axes) == {"wght": 1.0}
23    assert normalizeLocation({"wght": 500}, axes) == {"wght": 0.5}
24    assert normalizeLocation({"wght": 1001}, axes) == {"wght": 1.0}
25
26    axes = {"wght": (0, 1000, 1000)}
27    assert normalizeLocation({"wght": 0}, axes) == {"wght": -1.0}
28    assert normalizeLocation({"wght": -1}, axes) == {"wght": -1.0}
29    assert normalizeLocation({"wght": 500}, axes) == {"wght": -0.5}
30    assert normalizeLocation({"wght": 1000}, axes) == {"wght": 0.0}
31    assert normalizeLocation({"wght": 1001}, axes) == {"wght": 0.0}
32
33
34def test_supportScalar():
35    assert supportScalar({}, {}) == 1.0
36    assert supportScalar({"wght": 0.2}, {}) == 1.0
37    assert supportScalar({"wght": 0.2}, {"wght": (0, 2, 3)}) == 0.1
38    assert supportScalar({"wght": 2.5}, {"wght": (0, 2, 4)}) == 0.75
39    assert supportScalar({"wght": 4}, {"wght": (0, 2, 2)}) == 0.0
40    assert supportScalar({"wght": 4}, {"wght": (0, 2, 2)}, extrapolate=True) == 2.0
41    assert supportScalar({"wght": 4}, {"wght": (0, 2, 3)}, extrapolate=True) == 2.0
42    assert supportScalar({"wght": 2}, {"wght": (0, 0.75, 1)}, extrapolate=True) == -4.0
43
44
45@pytest.mark.parametrize(
46    "numLocations, numSamples",
47    [
48        pytest.param(127, 509, marks=pytest.mark.slow),
49        (31, 251),
50    ],
51)
52def test_modeling_error(numLocations, numSamples):
53    # https://github.com/fonttools/fonttools/issues/2213
54    locations = [{"axis": float(i) / numLocations} for i in range(numLocations)]
55    masterValues = [100.0 if i else 0.0 for i in range(numLocations)]
56
57    model = VariationModel(locations)
58
59    for i in range(numSamples):
60        loc = {"axis": float(i) / numSamples}
61        scalars = model.getScalars(loc)
62
63        deltas_float = model.getDeltas(masterValues)
64        deltas_round = model.getDeltas(masterValues, round=round)
65
66        expected = model.interpolateFromDeltasAndScalars(deltas_float, scalars)
67        actual = model.interpolateFromDeltasAndScalars(deltas_round, scalars)
68
69        err = abs(actual - expected)
70        assert err <= 0.5, (i, err)
71
72        # This is how NOT to round deltas.
73        # deltas_late_round = [round(d) for d in deltas_float]
74        # bad = model.interpolateFromDeltasAndScalars(deltas_late_round, scalars)
75        # err_bad = abs(bad - expected)
76        # if err != err_bad:
77        #    print("{:d}	{:.2}	{:.2}".format(i, err, err_bad))
78
79
80class VariationModelTest(object):
81    @pytest.mark.parametrize(
82        "locations, axisOrder, sortedLocs, supports, deltaWeights",
83        [
84            (
85                [
86                    {"wght": 0.55, "wdth": 0.0},
87                    {"wght": -0.55, "wdth": 0.0},
88                    {"wght": -1.0, "wdth": 0.0},
89                    {"wght": 0.0, "wdth": 1.0},
90                    {"wght": 0.66, "wdth": 1.0},
91                    {"wght": 0.66, "wdth": 0.66},
92                    {"wght": 0.0, "wdth": 0.0},
93                    {"wght": 1.0, "wdth": 1.0},
94                    {"wght": 1.0, "wdth": 0.0},
95                ],
96                ["wght"],
97                [
98                    {},
99                    {"wght": -0.55},
100                    {"wght": -1.0},
101                    {"wght": 0.55},
102                    {"wght": 1.0},
103                    {"wdth": 1.0},
104                    {"wdth": 1.0, "wght": 1.0},
105                    {"wdth": 1.0, "wght": 0.66},
106                    {"wdth": 0.66, "wght": 0.66},
107                ],
108                [
109                    {},
110                    {"wght": (-1.0, -0.55, 0)},
111                    {"wght": (-1.0, -1.0, -0.55)},
112                    {"wght": (0, 0.55, 1.0)},
113                    {"wght": (0.55, 1.0, 1.0)},
114                    {"wdth": (0, 1.0, 1.0)},
115                    {"wdth": (0, 1.0, 1.0), "wght": (0, 1.0, 1.0)},
116                    {"wdth": (0, 1.0, 1.0), "wght": (0, 0.66, 1.0)},
117                    {"wdth": (0, 0.66, 1.0), "wght": (0, 0.66, 1.0)},
118                ],
119                [
120                    {},
121                    {0: 1.0},
122                    {0: 1.0},
123                    {0: 1.0},
124                    {0: 1.0},
125                    {0: 1.0},
126                    {0: 1.0, 4: 1.0, 5: 1.0},
127                    {
128                        0: 1.0,
129                        3: 0.7555555555555555,
130                        4: 0.24444444444444444,
131                        5: 1.0,
132                        6: 0.66,
133                    },
134                    {
135                        0: 1.0,
136                        3: 0.7555555555555555,
137                        4: 0.24444444444444444,
138                        5: 0.66,
139                        6: 0.43560000000000004,
140                        7: 0.66,
141                    },
142                ],
143            ),
144            (
145                [
146                    {},
147                    {"bar": 0.5},
148                    {"bar": 1.0},
149                    {"foo": 1.0},
150                    {"bar": 0.5, "foo": 1.0},
151                    {"bar": 1.0, "foo": 1.0},
152                ],
153                None,
154                [
155                    {},
156                    {"bar": 0.5},
157                    {"bar": 1.0},
158                    {"foo": 1.0},
159                    {"bar": 0.5, "foo": 1.0},
160                    {"bar": 1.0, "foo": 1.0},
161                ],
162                [
163                    {},
164                    {"bar": (0, 0.5, 1.0)},
165                    {"bar": (0.5, 1.0, 1.0)},
166                    {"foo": (0, 1.0, 1.0)},
167                    {"bar": (0, 0.5, 1.0), "foo": (0, 1.0, 1.0)},
168                    {"bar": (0.5, 1.0, 1.0), "foo": (0, 1.0, 1.0)},
169                ],
170                [
171                    {},
172                    {0: 1.0},
173                    {0: 1.0},
174                    {0: 1.0},
175                    {0: 1.0, 1: 1.0, 3: 1.0},
176                    {0: 1.0, 2: 1.0, 3: 1.0},
177                ],
178            ),
179            (
180                [
181                    {},
182                    {"foo": 0.25},
183                    {"foo": 0.5},
184                    {"foo": 0.75},
185                    {"foo": 1.0},
186                    {"bar": 0.25},
187                    {"bar": 0.75},
188                    {"bar": 1.0},
189                ],
190                None,
191                [
192                    {},
193                    {"bar": 0.25},
194                    {"bar": 0.75},
195                    {"bar": 1.0},
196                    {"foo": 0.25},
197                    {"foo": 0.5},
198                    {"foo": 0.75},
199                    {"foo": 1.0},
200                ],
201                [
202                    {},
203                    {"bar": (0.0, 0.25, 1.0)},
204                    {"bar": (0.25, 0.75, 1.0)},
205                    {"bar": (0.75, 1.0, 1.0)},
206                    {"foo": (0.0, 0.25, 1.0)},
207                    {"foo": (0.25, 0.5, 1.0)},
208                    {"foo": (0.5, 0.75, 1.0)},
209                    {"foo": (0.75, 1.0, 1.0)},
210                ],
211                [
212                    {},
213                    {0: 1.0},
214                    {0: 1.0, 1: 0.3333333333333333},
215                    {0: 1.0},
216                    {0: 1.0},
217                    {0: 1.0, 4: 0.6666666666666666},
218                    {0: 1.0, 4: 0.3333333333333333, 5: 0.5},
219                    {0: 1.0},
220                ],
221            ),
222            (
223                [
224                    {},
225                    {"foo": 0.25},
226                    {"foo": 0.5},
227                    {"foo": 0.75},
228                    {"foo": 1.0},
229                    {"bar": 0.25},
230                    {"bar": 0.75},
231                    {"bar": 1.0},
232                ],
233                None,
234                [
235                    {},
236                    {"bar": 0.25},
237                    {"bar": 0.75},
238                    {"bar": 1.0},
239                    {"foo": 0.25},
240                    {"foo": 0.5},
241                    {"foo": 0.75},
242                    {"foo": 1.0},
243                ],
244                [
245                    {},
246                    {"bar": (0, 0.25, 1.0)},
247                    {"bar": (0.25, 0.75, 1.0)},
248                    {"bar": (0.75, 1.0, 1.0)},
249                    {"foo": (0, 0.25, 1.0)},
250                    {"foo": (0.25, 0.5, 1.0)},
251                    {"foo": (0.5, 0.75, 1.0)},
252                    {"foo": (0.75, 1.0, 1.0)},
253                ],
254                [
255                    {},
256                    {0: 1.0},
257                    {0: 1.0, 1: 0.3333333333333333},
258                    {0: 1.0},
259                    {0: 1.0},
260                    {0: 1.0, 4: 0.6666666666666666},
261                    {0: 1.0, 4: 0.3333333333333333, 5: 0.5},
262                    {0: 1.0},
263                ],
264            ),
265        ],
266    )
267    def test_init(self, locations, axisOrder, sortedLocs, supports, deltaWeights):
268        model = VariationModel(locations, axisOrder=axisOrder)
269
270        assert model.locations == sortedLocs
271        assert model.supports == supports
272        assert model.deltaWeights == deltaWeights
273
274    def test_init_duplicate_locations(self):
275        with pytest.raises(VariationModelError, match="Locations must be unique."):
276            VariationModel(
277                [
278                    {"foo": 0.0, "bar": 0.0},
279                    {"foo": 1.0, "bar": 1.0},
280                    {"bar": 1.0, "foo": 1.0},
281                ]
282            )
283
284    @pytest.mark.parametrize(
285        "locations, axisOrder, masterValues, instanceLocation, expectedValue",
286        [
287            (
288                [
289                    {},
290                    {"axis_A": 1.0},
291                    {"axis_B": 1.0},
292                    {"axis_A": 1.0, "axis_B": 1.0},
293                    {"axis_A": 0.5, "axis_B": 1.0},
294                    {"axis_A": 1.0, "axis_B": 0.5},
295                ],
296                ["axis_A", "axis_B"],
297                [
298                    0,
299                    10,
300                    20,
301                    70,
302                    50,
303                    60,
304                ],
305                {
306                    "axis_A": 0.5,
307                    "axis_B": 0.5,
308                },
309                37.5,
310            ),
311        ],
312    )
313    def test_interpolation(
314        self,
315        locations,
316        axisOrder,
317        masterValues,
318        instanceLocation,
319        expectedValue,
320    ):
321        model = VariationModel(locations, axisOrder=axisOrder)
322        interpolatedValue = model.interpolateFromMasters(instanceLocation, masterValues)
323
324        assert interpolatedValue == expectedValue
325