[instancer/L4] Misc fixes and fix tests
This commit is contained in:
parent
43e5aae018
commit
204532aee3
@ -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,
|
||||||
|
@ -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
|
||||||
|
@ -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):
|
||||||
|
@ -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
|
||||||
|
Loading…
x
Reference in New Issue
Block a user