diff --git a/Lib/fontTools/varLib/instancer/__init__.py b/Lib/fontTools/varLib/instancer/__init__.py index 8e9e21ee1..a8663ec42 100644 --- a/Lib/fontTools/varLib/instancer/__init__.py +++ b/Lib/fontTools/varLib/instancer/__init__.py @@ -234,9 +234,29 @@ class AxisTriple(Sequence): @dataclasses.dataclass(frozen=True, order=True, repr=False) -class NormalizedAxisTripleAndDistances(AxisTriple): +class NormalizedAxisTriple(AxisTriple): """A triple of (min, default, max) normalized axis values.""" + minimum: float + default: float + maximum: float + + def __post_init__(self): + if self.default is None: + object.__setattr__(self, "default", max(self.minimum, min(self.maximum, 0))) + if not (-1.0 <= self.minimum <= self.default <= self.maximum <= 1.0): + raise ValueError( + "Normalized axis values not in -1..+1 range; got " + f"minimum={self.minimum:g}, default={self.default:g}, maximum={self.maximum:g})" + ) + + +@dataclasses.dataclass(frozen=True, order=True, repr=False) +class NormalizedAxisTripleAndDistances(AxisTriple): + """A triple of (min, default, max) normalized axis values, + with distances between min and default, and default and max, + in the *pre-normalized* space.""" + minimum: float default: float maximum: float @@ -256,7 +276,11 @@ class NormalizedAxisTripleAndDistances(AxisTriple): v = self return self.__class__(-v[2], -v[1], -v[0], v[4], v[3]) - def normalizeValue(self, v, extrapolate=True): + def renormalizeValue(self, v, extrapolate=True): + """Renormalizes a normalized value v to the range of this axis, + considering the pre-normalized distances as well as the new + axis limits.""" + lower, default, upper, distanceNegative, distancePositive = self assert lower <= default <= upper @@ -267,7 +291,7 @@ class NormalizedAxisTripleAndDistances(AxisTriple): return 0 if default < 0: - return -self.reverse_negate().normalizeValue(-v, extrapolate=extrapolate) + return -self.reverse_negate().renormalizeValue(-v, extrapolate=extrapolate) # default >= 0 and v != default @@ -288,12 +312,6 @@ class NormalizedAxisTripleAndDistances(AxisTriple): else: vDistance = -v * distanceNegative + distancePositive * default - if totalDistance == 0: - # This happens - if default == 0: - return -v / lower - return 0 # Shouldn't happen - return -vDistance / totalDistance @@ -558,7 +576,7 @@ def _instantiateGvarGlyph( "Instancing accross VarComposite axes with variation is not supported." ) limits = axisLimits[tag] - loc = limits.normalizeValue(loc, extrapolate=False) + loc = limits.renormalizeValue(loc, extrapolate=False) newLocation[tag] = loc component.location = newLocation @@ -989,10 +1007,10 @@ def instantiateAvar(varfont, axisLimits): for fromCoord, toCoord in mapping.items(): if fromCoord < axisRange.minimum or fromCoord > axisRange.maximum: continue - fromCoord = axisRange.normalizeValue(fromCoord) + fromCoord = axisRange.renormalizeValue(fromCoord) assert mappedMin <= toCoord <= mappedMax - toCoord = mappedAxisLimit.normalizeValue(toCoord) + toCoord = mappedAxisLimit.renormalizeValue(toCoord) fromCoord = floatToFixedToFloat(fromCoord, 14) toCoord = floatToFixedToFloat(toCoord, 14) diff --git a/Lib/fontTools/varLib/instancer/featureVars.py b/Lib/fontTools/varLib/instancer/featureVars.py index 3433577c1..350c90a41 100644 --- a/Lib/fontTools/varLib/instancer/featureVars.py +++ b/Lib/fontTools/varLib/instancer/featureVars.py @@ -41,7 +41,7 @@ def _limitFeatureVariationConditionRange(condition, axisLimit): return return tuple( - axisLimit.normalizeValue(v, extrapolate=False) for v in (minValue, maxValue) + axisLimit.renormalizeValue(v, extrapolate=False) for v in (minValue, maxValue) ) @@ -53,7 +53,7 @@ def _instantiateFeatureVariationRecord( newConditions = [] from fontTools.varLib.instancer import NormalizedAxisTripleAndDistances - default_triple = NormalizedAxisTripleAndDistances(-1, 0, +1, 0, 0) + default_triple = NormalizedAxisTripleAndDistances(-1, 0, +1) for i, condition in enumerate(record.ConditionSet.ConditionTable): if condition.Format == 1: axisIdx = condition.AxisIndex diff --git a/Lib/fontTools/varLib/instancer/solver.py b/Lib/fontTools/varLib/instancer/solver.py index 4b63b3cfe..c991fcdcf 100644 --- a/Lib/fontTools/varLib/instancer/solver.py +++ b/Lib/fontTools/varLib/instancer/solver.py @@ -12,7 +12,7 @@ def _reverse_negate(v): def _solve(tent, axisLimit, negative=False): - axisMin, axisDef, axisMax, distanceNegative, distancePositive = axisLimit + axisMin, axisDef, axisMax, _distanceNegative, _distancePositive = axisLimit lower, peak, upper = tent # Mirror the problem such that axisDef <= peak @@ -285,7 +285,7 @@ def rebaseTent(tent, axisLimit): If tent value is None, that is a special deltaset that should be always-enabled (called "gain").""" - axisMin, axisDef, axisMax, distanceNegative, distancePositive = axisLimit + axisMin, axisDef, axisMax, _distanceNegative, _distancePositive = axisLimit assert -1 <= axisMin <= axisDef <= axisMax <= +1 lower, peak, upper = tent @@ -295,7 +295,7 @@ def rebaseTent(tent, axisLimit): sols = _solve(tent, axisLimit) - n = lambda v: axisLimit.normalizeValue(v) + n = lambda v: axisLimit.renormalizeValue(v) sols = [ (scalar, (n(v[0]), n(v[1]), n(v[2])) if v is not None else None) for scalar, v in sols diff --git a/Tests/varLib/instancer/instancer_test.py b/Tests/varLib/instancer/instancer_test.py index f722ceb67..e8bedcf81 100644 --- a/Tests/varLib/instancer/instancer_test.py +++ b/Tests/varLib/instancer/instancer_test.py @@ -1950,7 +1950,7 @@ class LimitTupleVariationAxisRangesTest: ], ) def test_positive_var(self, var, axisTag, newMax, expected): - axisRange = instancer.NormalizedAxisTripleAndDistances(0, 0, newMax, 1, 1) + axisRange = instancer.NormalizedAxisTripleAndDistances(0, 0, newMax) self.check_limit_single_var_axis_range(var, axisTag, axisRange, expected) @pytest.mark.parametrize( @@ -2094,6 +2094,7 @@ def test_parseLimits_invalid(limits): @pytest.mark.parametrize( "limits, expected", [ + # 300, 500 come from the font having 100,400,900 fvar axis limits. ({"wght": (100, 400)}, {"wght": (-1.0, 0, 0, 300, 500)}), ({"wght": (100, 400, 400)}, {"wght": (-1.0, 0, 0, 300, 500)}), ({"wght": (100, 300, 400)}, {"wght": (-1.0, -0.5, 0, 300, 500)}),