[instancer/L4] Misc fixes and fix tests

This commit is contained in:
Behdad Esfahbod 2023-06-21 14:47:52 -06:00
parent 43e5aae018
commit 204532aee3
4 changed files with 37 additions and 24 deletions

View File

@ -204,8 +204,8 @@ class AxisTriple(Sequence):
default = None default = None
if n == 2: if n == 2:
minimum, maximum = v minimum, maximum = v
elif n == 3: elif n >= 3:
minimum, default, maximum = v return cls(*v)
else: else:
raise ValueError(f"expected sequence of 2 or 3; got {n}: {v!r}") raise ValueError(f"expected sequence of 2 or 3; got {n}: {v!r}")
return cls(minimum, default, maximum) return cls(minimum, default, maximum)
@ -234,14 +234,14 @@ class AxisTriple(Sequence):
@dataclasses.dataclass(frozen=True, order=True, repr=False) @dataclasses.dataclass(frozen=True, order=True, repr=False)
class NormalizedAxisTriple(AxisTriple): class NormalizedAxisTripleAndDistances(AxisTriple):
"""A triple of (min, default, max) normalized axis values.""" """A triple of (min, default, max) normalized axis values."""
minimum: float minimum: float
default: float default: float
maximum: float maximum: float
distanceNegative: float distanceNegative: Optional[float] = 1
distancePositive: float distancePositive: Optional[float] = 1
def __post_init__(self): def __post_init__(self):
if self.default is None: if self.default is None:
@ -256,15 +256,18 @@ class NormalizedAxisTriple(AxisTriple):
v = self v = self
return self.__class__(-v[2], -v[1], -v[0], v[4], v[3]) return self.__class__(-v[2], -v[1], -v[0], v[4], v[3])
def normalizeValue(self, v): def normalizeValue(self, v, extrapolate=True):
lower, default, upper, distanceNegative, distancePositive = self lower, default, upper, distanceNegative, distancePositive = self
assert lower <= default <= upper assert lower <= default <= upper
if not extrapolate:
v = max(lower, min(upper, v))
if v == default: if v == default:
return 0 return 0
if default < 0: if default < 0:
return -self.reverse_negate().normalizeValue(-v) return -self.reverse_negate().normalizeValue(-v, extrapolate=extrapolate)
# default >= 0 and v != default # default >= 0 and v != default
@ -285,6 +288,12 @@ class NormalizedAxisTriple(AxisTriple):
else: else:
vDistance = -v * distanceNegative + distancePositive * default vDistance = -v * distanceNegative + distancePositive * default
if totalDistance == 0:
# This happens
if default == 0:
return -v / lower
return 0 # Shouldn't happen
return -vDistance / totalDistance return -vDistance / totalDistance
@ -375,7 +384,7 @@ class AxisLimits(_BaseAxisLimits):
distancePositive = triple[2] - triple[1] distancePositive = triple[2] - triple[1]
if self[axis_tag] is None: if self[axis_tag] is None:
normalizedLimits[axis_tag] = NormalizedAxisTriple( normalizedLimits[axis_tag] = NormalizedAxisTripleAndDistances(
0, 0, 0, distanceNegative, distancePositive 0, 0, 0, distanceNegative, distancePositive
) )
continue continue
@ -386,7 +395,7 @@ class AxisLimits(_BaseAxisLimits):
defaultV = triple[1] defaultV = triple[1]
avarMapping = avarSegments.get(axis_tag, None) avarMapping = avarSegments.get(axis_tag, None)
normalizedLimits[axis_tag] = NormalizedAxisTriple( normalizedLimits[axis_tag] = NormalizedAxisTripleAndDistances(
*(normalize(v, triple, avarMapping) for v in (minV, defaultV, maxV)), *(normalize(v, triple, avarMapping) for v in (minV, defaultV, maxV)),
distanceNegative, distanceNegative,
distancePositive, distancePositive,
@ -402,7 +411,7 @@ class NormalizedAxisLimits(_BaseAxisLimits):
self._data = data = {} self._data = data = {}
for k, v in dict(*args, **kwargs).items(): for k, v in dict(*args, **kwargs).items():
try: try:
triple = NormalizedAxisTriple.expand(v) triple = NormalizedAxisTripleAndDistances.expand(v)
except ValueError as e: except ValueError as e:
raise ValueError(f"Invalid axis limits for {k!r}: {v!r}") from e raise ValueError(f"Invalid axis limits for {k!r}: {v!r}") from e
data[k] = triple data[k] = triple
@ -486,7 +495,7 @@ def changeTupleVariationsAxisLimits(variations, axisLimits):
def changeTupleVariationAxisLimit(var, axisTag, axisLimit): def changeTupleVariationAxisLimit(var, axisTag, axisLimit):
assert isinstance(axisLimit, NormalizedAxisTriple) assert isinstance(axisLimit, NormalizedAxisTripleAndDistances)
# Skip when current axis is missing (i.e. doesn't participate), # Skip when current axis is missing (i.e. doesn't participate),
lower, peak, upper = var.axes.get(axisTag, (-1, 0, 1)) lower, peak, upper = var.axes.get(axisTag, (-1, 0, 1))
@ -549,7 +558,7 @@ def _instantiateGvarGlyph(
"Instancing accross VarComposite axes with variation is not supported." "Instancing accross VarComposite axes with variation is not supported."
) )
limits = axisLimits[tag] limits = axisLimits[tag]
loc = limits.normalizeValue(loc) loc = limits.normalizeValue(loc, extrapolate=False)
newLocation[tag] = loc newLocation[tag] = loc
component.location = newLocation component.location = newLocation
@ -969,7 +978,7 @@ def instantiateAvar(varfont, axisLimits):
mappedMax = floatToFixedToFloat( mappedMax = floatToFixedToFloat(
piecewiseLinearMap(axisRange.maximum, mapping), 14 piecewiseLinearMap(axisRange.maximum, mapping), 14
) )
mappedAxisLimit = NormalizedAxisTriple( mappedAxisLimit = NormalizedAxisTripleAndDistances(
mappedMin, mappedMin,
mappedDef, mappedDef,
mappedMax, mappedMax,

View File

@ -1,5 +1,4 @@
from fontTools.ttLib.tables import otTables as ot from fontTools.ttLib.tables import otTables as ot
from fontTools.varLib.models import normalizeValue
from copy import deepcopy from copy import deepcopy
import logging import logging
@ -41,7 +40,9 @@ def _limitFeatureVariationConditionRange(condition, axisLimit):
# condition invalid or out of range # condition invalid or out of range
return return
return tuple(axisLimit.normalizeValue(v) for v in (minValue, maxValue)) return tuple(
axisLimit.normalizeValue(v, extrapolate=False) for v in (minValue, maxValue)
)
def _instantiateFeatureVariationRecord( def _instantiateFeatureVariationRecord(
@ -50,9 +51,9 @@ def _instantiateFeatureVariationRecord(
applies = True applies = True
shouldKeep = False shouldKeep = False
newConditions = [] newConditions = []
from fontTools.varLib.instancer import NormalizedAxisTriple from fontTools.varLib.instancer import NormalizedAxisTripleAndDistances
default_triple = NormalizedAxisTriple(-1, 0, +1, 0, 0) default_triple = NormalizedAxisTripleAndDistances(-1, 0, +1, 0, 0)
for i, condition in enumerate(record.ConditionSet.ConditionTable): for i, condition in enumerate(record.ConditionSet.ConditionTable):
if condition.Format == 1: if condition.Format == 1:
axisIdx = condition.AxisIndex axisIdx = condition.AxisIndex

View File

@ -1950,7 +1950,7 @@ class LimitTupleVariationAxisRangesTest:
], ],
) )
def test_positive_var(self, var, axisTag, newMax, expected): def test_positive_var(self, var, axisTag, newMax, expected):
axisRange = instancer.NormalizedAxisTriple(0, 0, newMax) axisRange = instancer.NormalizedAxisTripleAndDistances(0, 0, newMax, 1, 1)
self.check_limit_single_var_axis_range(var, axisTag, axisRange, expected) self.check_limit_single_var_axis_range(var, axisTag, axisRange, expected)
@pytest.mark.parametrize( @pytest.mark.parametrize(
@ -2029,7 +2029,7 @@ class LimitTupleVariationAxisRangesTest:
], ],
) )
def test_negative_var(self, var, axisTag, newMin, expected): def test_negative_var(self, var, axisTag, newMin, expected):
axisRange = instancer.NormalizedAxisTriple(newMin, 0, 0) axisRange = instancer.NormalizedAxisTripleAndDistances(newMin, 0, 0, 1, 1)
self.check_limit_single_var_axis_range(var, axisTag, axisRange, expected) self.check_limit_single_var_axis_range(var, axisTag, axisRange, expected)
@ -2052,7 +2052,7 @@ def test_limitFeatureVariationConditionRange(oldRange, newLimit, expected):
condition = featureVars.buildConditionTable(0, *oldRange) condition = featureVars.buildConditionTable(0, *oldRange)
result = instancer.featureVars._limitFeatureVariationConditionRange( result = instancer.featureVars._limitFeatureVariationConditionRange(
condition, instancer.NormalizedAxisTriple(*newLimit) condition, instancer.NormalizedAxisTripleAndDistances(*newLimit, 1, 1)
) )
assert result == expected assert result == expected
@ -2094,9 +2094,9 @@ def test_parseLimits_invalid(limits):
@pytest.mark.parametrize( @pytest.mark.parametrize(
"limits, expected", "limits, expected",
[ [
({"wght": (100, 400)}, {"wght": (-1.0, 0, 0)}), ({"wght": (100, 400)}, {"wght": (-1.0, 0, 0, 300, 500)}),
({"wght": (100, 400, 400)}, {"wght": (-1.0, 0, 0)}), ({"wght": (100, 400, 400)}, {"wght": (-1.0, 0, 0, 300, 500)}),
({"wght": (100, 300, 400)}, {"wght": (-1.0, -0.5, 0)}), ({"wght": (100, 300, 400)}, {"wght": (-1.0, -0.5, 0, 300, 500)}),
], ],
) )
def test_normalizeAxisLimits(varfont, limits, expected): def test_normalizeAxisLimits(varfont, limits, expected):
@ -2113,7 +2113,7 @@ def test_normalizeAxisLimits_no_avar(varfont):
limits = instancer.AxisLimits(wght=(400, 400, 500)) limits = instancer.AxisLimits(wght=(400, 400, 500))
normalized = limits.normalize(varfont) normalized = limits.normalize(varfont)
assert normalized["wght"] == pytest.approx((0, 0, 0.2), 1e-4) assert normalized["wght"] == pytest.approx((0, 0, 0.2, 300, 500), 1e-4)
def test_normalizeAxisLimits_missing_from_fvar(varfont): def test_normalizeAxisLimits_missing_from_fvar(varfont):

View File

@ -1,4 +1,5 @@
from fontTools.varLib.instancer import solver from fontTools.varLib.instancer import solver
from fontTools.varLib.instancer import NormalizedAxisTripleAndDistances
import pytest import pytest
@ -264,6 +265,8 @@ class RebaseTentTest(object):
], ],
) )
def test_rebaseTent(self, tent, axisRange, expected): def test_rebaseTent(self, tent, axisRange, expected):
axisRange = NormalizedAxisTripleAndDistances(*axisRange)
sol = solver.rebaseTent(tent, axisRange) sol = solver.rebaseTent(tent, axisRange)
a = pytest.approx a = pytest.approx