[varLib] Support extrapolation

Fixes https://github.com/fonttools/fonttools/issues/1252
This commit is contained in:
Behdad Esfahbod 2022-08-23 09:55:36 -06:00
parent fce82adba2
commit bad70c68ef
2 changed files with 34 additions and 5 deletions

View File

@ -116,7 +116,7 @@ def normalizeLocation(location, axes):
return out return out
def supportScalar(location, support, ot=True): def supportScalar(location, support, ot=True, extrapolate=False):
"""Returns the scalar multiplier at location, for a master """Returns the scalar multiplier at location, for a master
with support. If ot is True, then a peak value of zero with support. If ot is True, then a peak value of zero
for support of an axis means "axis does not participate". That for support of an axis means "axis does not participate". That
@ -138,6 +138,10 @@ def supportScalar(location, support, ot=True):
0.75 0.75
>>> supportScalar({'wght':2.5, 'wdth':.5}, {'wght':(0,2,4), 'wdth':(-1,0,+1)}) >>> supportScalar({'wght':2.5, 'wdth':.5}, {'wght':(0,2,4), 'wdth':(-1,0,+1)})
0.75 0.75
>>> supportScalar({'wght':4}, {'wght':(0,2,3)}, extrapolate=True)
2.0
>>> supportScalar({'wght':4}, {'wght':(0,2,2)}, extrapolate=True)
2.0
""" """
scalar = 1.0 scalar = 1.0
for axis, (lower, peak, upper) in support.items(): for axis, (lower, peak, upper) in support.items():
@ -155,9 +159,27 @@ def supportScalar(location, support, ot=True):
v = location[axis] v = location[axis]
if v == peak: if v == peak:
continue continue
if extrapolate:
if v < -1 and lower <= -1:
if peak <= -1 and peak < upper:
scalar *= (v - upper) / (peak - upper)
continue
elif -1 < peak:
scalar *= (v - lower) / (peak - lower)
continue
elif +1 < v and +1 <= upper:
if +1 <= peak and lower < peak:
scalar *= (v - lower) / (peak - lower)
continue
elif peak < +1:
scalar *= (v - upper) / (peak - upper)
continue
if v <= lower or upper <= v: if v <= lower or upper <= v:
scalar = 0.0 scalar = 0.0
break break
if v < peak: if v < peak:
scalar *= (v - lower) / (peak - lower) scalar *= (v - lower) / (peak - lower)
else: # v > peak else: # v > peak
@ -169,7 +191,7 @@ class VariationModel(object):
""" """
Locations must be in normalized space. Ie. base master Locations must be in normalized space. Ie. base master
is at origin (0):: is at origin (0).
>>> from pprint import pprint >>> from pprint import pprint
>>> locations = [ \ >>> locations = [ \
@ -206,12 +228,14 @@ class VariationModel(object):
{0: 1.0, 3: 0.75, 4: 0.25, 5: 0.6666666666666667}] {0: 1.0, 3: 0.75, 4: 0.25, 5: 0.6666666666666667}]
""" """
def __init__(self, locations, axisOrder=None): def __init__(self, locations, axisOrder=None, extrapolate=False):
if len(set(tuple(sorted(l.items())) for l in locations)) != len(locations): if len(set(tuple(sorted(l.items())) for l in locations)) != len(locations):
raise VariationModelError("Locations must be unique.") raise VariationModelError("Locations must be unique.")
self.origLocations = locations self.origLocations = locations
self.axisOrder = axisOrder if axisOrder is not None else [] self.axisOrder = axisOrder if axisOrder is not None else []
self.extrapolate = extrapolate
locations = [{k: v for k, v in loc.items() if v != 0.0} for loc in locations] locations = [{k: v for k, v in loc.items() if v != 0.0} for loc in locations]
keyFunc = self.getMasterLocationsSortKeyFunc( keyFunc = self.getMasterLocationsSortKeyFunc(
@ -435,7 +459,8 @@ class VariationModel(object):
return model.getDeltas(items, round=round), model.supports return model.getDeltas(items, round=round), model.supports
def getScalars(self, loc): def getScalars(self, loc):
return [supportScalar(loc, support) for support in self.supports] return [supportScalar(loc, support, extrapolate=self.extrapolate)
for support in self.supports]
@staticmethod @staticmethod
def interpolateFromDeltasAndScalars(deltas, scalars): def interpolateFromDeltasAndScalars(deltas, scalars):

View File

@ -36,6 +36,10 @@ def test_supportScalar():
assert supportScalar({"wght": 0.2}, {}) == 1.0 assert supportScalar({"wght": 0.2}, {}) == 1.0
assert supportScalar({"wght": 0.2}, {"wght": (0, 2, 3)}) == 0.1 assert supportScalar({"wght": 0.2}, {"wght": (0, 2, 3)}) == 0.1
assert supportScalar({"wght": 2.5}, {"wght": (0, 2, 4)}) == 0.75 assert supportScalar({"wght": 2.5}, {"wght": (0, 2, 4)}) == 0.75
assert supportScalar({"wght": 4}, {"wght": (0, 2, 2)}) == 0.0
assert supportScalar({"wght": 4}, {"wght": (0, 2, 2)}, extrapolate=True) == 2.0
assert supportScalar({"wght": 4}, {"wght": (0, 2, 3)}, extrapolate=True) == 2.0
assert supportScalar({"wght": 2}, {"wght": (0, .75, 1)}, extrapolate=True) == -4.0
@pytest.mark.parametrize( @pytest.mark.parametrize(