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