commit
fb56e7b7c9
@ -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)
|
||||||
@ -251,6 +251,70 @@ class NormalizedAxisTriple(AxisTriple):
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@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
|
||||||
|
distanceNegative: Optional[float] = 1
|
||||||
|
distancePositive: Optional[float] = 1
|
||||||
|
|
||||||
|
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})"
|
||||||
|
)
|
||||||
|
|
||||||
|
def reverse_negate(self):
|
||||||
|
v = self
|
||||||
|
return self.__class__(-v[2], -v[1], -v[0], v[4], v[3])
|
||||||
|
|
||||||
|
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
|
||||||
|
|
||||||
|
if not extrapolate:
|
||||||
|
v = max(lower, min(upper, v))
|
||||||
|
|
||||||
|
if v == default:
|
||||||
|
return 0
|
||||||
|
|
||||||
|
if default < 0:
|
||||||
|
return -self.reverse_negate().renormalizeValue(-v, extrapolate=extrapolate)
|
||||||
|
|
||||||
|
# default >= 0 and v != default
|
||||||
|
|
||||||
|
if v > default:
|
||||||
|
return (v - default) / (upper - default)
|
||||||
|
|
||||||
|
# v < default
|
||||||
|
|
||||||
|
if lower >= 0:
|
||||||
|
return (v - default) / (default - lower)
|
||||||
|
|
||||||
|
# lower < 0 and v < default
|
||||||
|
|
||||||
|
totalDistance = distanceNegative * -lower + distancePositive * default
|
||||||
|
|
||||||
|
if v >= 0:
|
||||||
|
vDistance = (default - v) * distancePositive
|
||||||
|
else:
|
||||||
|
vDistance = -v * distanceNegative + distancePositive * default
|
||||||
|
|
||||||
|
return -vDistance / totalDistance
|
||||||
|
|
||||||
|
|
||||||
class _BaseAxisLimits(Mapping[str, AxisTriple]):
|
class _BaseAxisLimits(Mapping[str, AxisTriple]):
|
||||||
def __getitem__(self, key: str) -> AxisTriple:
|
def __getitem__(self, key: str) -> AxisTriple:
|
||||||
return self._data[key]
|
return self._data[key]
|
||||||
@ -334,8 +398,13 @@ class AxisLimits(_BaseAxisLimits):
|
|||||||
normalizedLimits = {}
|
normalizedLimits = {}
|
||||||
|
|
||||||
for axis_tag, triple in axes.items():
|
for axis_tag, triple in axes.items():
|
||||||
|
distanceNegative = triple[1] - triple[0]
|
||||||
|
distancePositive = triple[2] - triple[1]
|
||||||
|
|
||||||
if self[axis_tag] is None:
|
if self[axis_tag] is None:
|
||||||
normalizedLimits[axis_tag] = NormalizedAxisTriple(0, 0, 0)
|
normalizedLimits[axis_tag] = NormalizedAxisTripleAndDistances(
|
||||||
|
0, 0, 0, distanceNegative, distancePositive
|
||||||
|
)
|
||||||
continue
|
continue
|
||||||
|
|
||||||
minV, defaultV, maxV = self[axis_tag]
|
minV, defaultV, maxV = self[axis_tag]
|
||||||
@ -344,8 +413,10 @@ 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,
|
||||||
|
distancePositive,
|
||||||
)
|
)
|
||||||
|
|
||||||
return NormalizedAxisLimits(normalizedLimits)
|
return NormalizedAxisLimits(normalizedLimits)
|
||||||
@ -358,7 +429,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
|
||||||
@ -442,7 +513,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))
|
||||||
@ -505,7 +576,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 = normalizeValue(loc, limits)
|
loc = limits.renormalizeValue(loc, extrapolate=False)
|
||||||
newLocation[tag] = loc
|
newLocation[tag] = loc
|
||||||
component.location = newLocation
|
component.location = newLocation
|
||||||
|
|
||||||
@ -925,15 +996,21 @@ def instantiateAvar(varfont, axisLimits):
|
|||||||
mappedMax = floatToFixedToFloat(
|
mappedMax = floatToFixedToFloat(
|
||||||
piecewiseLinearMap(axisRange.maximum, mapping), 14
|
piecewiseLinearMap(axisRange.maximum, mapping), 14
|
||||||
)
|
)
|
||||||
|
mappedAxisLimit = NormalizedAxisTripleAndDistances(
|
||||||
|
mappedMin,
|
||||||
|
mappedDef,
|
||||||
|
mappedMax,
|
||||||
|
axisRange.distanceNegative,
|
||||||
|
axisRange.distancePositive,
|
||||||
|
)
|
||||||
newMapping = {}
|
newMapping = {}
|
||||||
for fromCoord, toCoord in mapping.items():
|
for fromCoord, toCoord in mapping.items():
|
||||||
|
|
||||||
if fromCoord < axisRange.minimum or fromCoord > axisRange.maximum:
|
if fromCoord < axisRange.minimum or fromCoord > axisRange.maximum:
|
||||||
continue
|
continue
|
||||||
fromCoord = normalizeValue(fromCoord, axisRange)
|
fromCoord = axisRange.renormalizeValue(fromCoord)
|
||||||
|
|
||||||
assert mappedMin <= toCoord <= mappedMax
|
assert mappedMin <= toCoord <= mappedMax
|
||||||
toCoord = normalizeValue(toCoord, (mappedMin, mappedDef, mappedMax))
|
toCoord = mappedAxisLimit.renormalizeValue(toCoord)
|
||||||
|
|
||||||
fromCoord = floatToFixedToFloat(fromCoord, 14)
|
fromCoord = floatToFixedToFloat(fromCoord, 14)
|
||||||
toCoord = floatToFixedToFloat(toCoord, 14)
|
toCoord = floatToFixedToFloat(toCoord, 14)
|
||||||
|
@ -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(normalizeValue(v, axisLimit) for v in (minValue, maxValue))
|
return tuple(
|
||||||
|
axisLimit.renormalizeValue(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)
|
default_triple = NormalizedAxisTripleAndDistances(-1, 0, +1)
|
||||||
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
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
from fontTools.varLib.models import supportScalar, normalizeValue
|
from fontTools.varLib.models import supportScalar
|
||||||
from fontTools.misc.fixedTools import MAX_F2DOT14
|
from fontTools.misc.fixedTools import MAX_F2DOT14
|
||||||
from functools import lru_cache
|
from functools import lru_cache
|
||||||
|
|
||||||
@ -12,7 +12,7 @@ def _reverse_negate(v):
|
|||||||
|
|
||||||
|
|
||||||
def _solve(tent, axisLimit, negative=False):
|
def _solve(tent, axisLimit, negative=False):
|
||||||
axisMin, axisDef, axisMax = axisLimit
|
axisMin, axisDef, axisMax, _distanceNegative, _distancePositive = axisLimit
|
||||||
lower, peak, upper = tent
|
lower, peak, upper = tent
|
||||||
|
|
||||||
# Mirror the problem such that axisDef <= peak
|
# Mirror the problem such that axisDef <= peak
|
||||||
@ -20,7 +20,9 @@ def _solve(tent, axisLimit, negative=False):
|
|||||||
return [
|
return [
|
||||||
(scalar, _reverse_negate(t) if t is not None else None)
|
(scalar, _reverse_negate(t) if t is not None else None)
|
||||||
for scalar, t in _solve(
|
for scalar, t in _solve(
|
||||||
_reverse_negate(tent), _reverse_negate(axisLimit), not negative
|
_reverse_negate(tent),
|
||||||
|
axisLimit.reverse_negate(),
|
||||||
|
not negative,
|
||||||
)
|
)
|
||||||
]
|
]
|
||||||
# axisDef <= peak
|
# axisDef <= peak
|
||||||
@ -98,9 +100,8 @@ def _solve(tent, axisLimit, negative=False):
|
|||||||
# |
|
# |
|
||||||
# crossing
|
# crossing
|
||||||
if gain > outGain:
|
if gain > outGain:
|
||||||
|
|
||||||
# Crossing point on the axis.
|
# Crossing point on the axis.
|
||||||
crossing = peak + ((1 - gain) * (upper - peak) / (1 - outGain))
|
crossing = peak + (1 - gain) * (upper - peak)
|
||||||
|
|
||||||
loc = (axisDef, peak, crossing)
|
loc = (axisDef, peak, crossing)
|
||||||
scalar = 1
|
scalar = 1
|
||||||
@ -116,7 +117,7 @@ def _solve(tent, axisLimit, negative=False):
|
|||||||
# the drawing above.
|
# the drawing above.
|
||||||
if upper >= axisMax:
|
if upper >= axisMax:
|
||||||
loc = (crossing, axisMax, axisMax)
|
loc = (crossing, axisMax, axisMax)
|
||||||
scalar = supportScalar({"tag": axisMax}, {"tag": tent})
|
scalar = outGain
|
||||||
|
|
||||||
out.append((scalar - gain, loc))
|
out.append((scalar - gain, loc))
|
||||||
|
|
||||||
@ -147,84 +148,73 @@ def _solve(tent, axisLimit, negative=False):
|
|||||||
|
|
||||||
# Eternity justify.
|
# Eternity justify.
|
||||||
loc2 = (upper, axisMax, axisMax)
|
loc2 = (upper, axisMax, axisMax)
|
||||||
scalar2 = supportScalar({"tag": axisMax}, {"tag": tent})
|
scalar2 = 0
|
||||||
|
|
||||||
out.append((scalar1 - gain, loc1))
|
out.append((scalar1 - gain, loc1))
|
||||||
out.append((scalar2 - gain, loc2))
|
out.append((scalar2 - gain, loc2))
|
||||||
|
|
||||||
# Case 3: Outermost limit still fits within F2Dot14 bounds;
|
else:
|
||||||
# we keep deltas as is and only scale the axes bounds. Deltas beyond -1.0
|
|
||||||
# or +1.0 will never be applied as implementations must clamp to that range.
|
|
||||||
#
|
|
||||||
# A second tent is needed for cases when gain is positive, though we add it
|
|
||||||
# unconditionally and it will be dropped because scalar ends up 0.
|
|
||||||
#
|
|
||||||
# TODO: See if we can just move upper closer to adjust the slope, instead of
|
|
||||||
# second tent.
|
|
||||||
#
|
|
||||||
# | peak |
|
|
||||||
# 1.........|............o...|..................
|
|
||||||
# | /x\ |
|
|
||||||
# | /xxx\ |
|
|
||||||
# | /xxxxx\|
|
|
||||||
# | /xxxxxxx+
|
|
||||||
# | /xxxxxxxx|\
|
|
||||||
# 0---|-----|------oxxxxxxxxx|xo---------------1
|
|
||||||
# axisMin | lower | upper
|
|
||||||
# | |
|
|
||||||
# axisDef axisMax
|
|
||||||
#
|
|
||||||
elif axisDef + (axisMax - axisDef) * 2 >= upper:
|
|
||||||
|
|
||||||
if not negative and axisDef + (axisMax - axisDef) * MAX_F2DOT14 < upper:
|
|
||||||
# we clamp +2.0 to the max F2Dot14 (~1.99994) for convenience
|
|
||||||
upper = axisDef + (axisMax - axisDef) * MAX_F2DOT14
|
|
||||||
assert peak < upper
|
|
||||||
|
|
||||||
# Special-case if peak is at axisMax.
|
# Special-case if peak is at axisMax.
|
||||||
if axisMax == peak:
|
if axisMax == peak:
|
||||||
upper = peak
|
upper = peak
|
||||||
|
|
||||||
loc1 = (max(axisDef, lower), peak, upper)
|
# Case 3:
|
||||||
scalar1 = 1
|
# We keep delta as is and only scale the axis upper to achieve
|
||||||
|
# the desired new tent if feasible.
|
||||||
|
#
|
||||||
|
# peak
|
||||||
|
# 1.....................o....................
|
||||||
|
# / \_|
|
||||||
|
# ..................../....+_.........outGain
|
||||||
|
# / | \
|
||||||
|
# gain..............+......|..+_.............
|
||||||
|
# /| | | \
|
||||||
|
# 0---|-----------o | | | o----------1
|
||||||
|
# axisMin lower| | | upper
|
||||||
|
# | | newUpper
|
||||||
|
# axisDef axisMax
|
||||||
|
#
|
||||||
|
newUpper = peak + (1 - gain) * (upper - peak)
|
||||||
|
assert axisMax <= newUpper # Because outGain >= gain
|
||||||
|
if newUpper <= axisDef + (axisMax - axisDef) * 2:
|
||||||
|
upper = newUpper
|
||||||
|
if not negative and axisDef + (axisMax - axisDef) * MAX_F2DOT14 < upper:
|
||||||
|
# we clamp +2.0 to the max F2Dot14 (~1.99994) for convenience
|
||||||
|
upper = axisDef + (axisMax - axisDef) * MAX_F2DOT14
|
||||||
|
assert peak < upper
|
||||||
|
|
||||||
loc2 = (peak, upper, upper)
|
loc = (max(axisDef, lower), peak, upper)
|
||||||
scalar2 = 0
|
scalar = 1
|
||||||
|
|
||||||
|
out.append((scalar - gain, loc))
|
||||||
|
|
||||||
|
# Case 4: New limit doesn't fit; we need to chop into two tents,
|
||||||
|
# because the shape of a triangle with part of one side cut off
|
||||||
|
# cannot be represented as a triangle itself.
|
||||||
|
#
|
||||||
|
# | peak |
|
||||||
|
# 1.........|......o.|....................
|
||||||
|
# ..........|...../x\|.............outGain
|
||||||
|
# | |xxy|\_
|
||||||
|
# | /xxxy| \_
|
||||||
|
# | |xxxxy| \_
|
||||||
|
# | /xxxxy| \_
|
||||||
|
# 0---|-----|-oxxxxxx| o----------1
|
||||||
|
# axisMin | lower | upper
|
||||||
|
# | |
|
||||||
|
# axisDef axisMax
|
||||||
|
#
|
||||||
|
else:
|
||||||
|
loc1 = (max(axisDef, lower), peak, axisMax)
|
||||||
|
scalar1 = 1
|
||||||
|
|
||||||
|
loc2 = (peak, axisMax, axisMax)
|
||||||
|
scalar2 = outGain
|
||||||
|
|
||||||
# Don't add a dirac delta!
|
|
||||||
if axisDef < upper:
|
|
||||||
out.append((scalar1 - gain, loc1))
|
out.append((scalar1 - gain, loc1))
|
||||||
if peak < upper:
|
# Don't add a dirac delta!
|
||||||
out.append((scalar2 - gain, loc2))
|
if peak < axisMax:
|
||||||
|
out.append((scalar2 - gain, loc2))
|
||||||
# Case 4: New limit doesn't fit; we need to chop into two tents,
|
|
||||||
# because the shape of a triangle with part of one side cut off
|
|
||||||
# cannot be represented as a triangle itself.
|
|
||||||
#
|
|
||||||
# | peak |
|
|
||||||
# 1.........|......o.|...................
|
|
||||||
# | /x\|
|
|
||||||
# | |xxy|\_
|
|
||||||
# | /xxxy| \_
|
|
||||||
# | |xxxxy| \_
|
|
||||||
# | /xxxxy| \_
|
|
||||||
# 0---|-----|-oxxxxxx| o----------1
|
|
||||||
# axisMin | lower | upper
|
|
||||||
# | |
|
|
||||||
# axisDef axisMax
|
|
||||||
#
|
|
||||||
else:
|
|
||||||
|
|
||||||
loc1 = (max(axisDef, lower), peak, axisMax)
|
|
||||||
scalar1 = 1
|
|
||||||
|
|
||||||
loc2 = (peak, axisMax, axisMax)
|
|
||||||
scalar2 = supportScalar({"tag": axisMax}, {"tag": tent})
|
|
||||||
|
|
||||||
out.append((scalar1 - gain, loc1))
|
|
||||||
# Don't add a dirac delta!
|
|
||||||
if peak < axisMax:
|
|
||||||
out.append((scalar2 - gain, loc2))
|
|
||||||
|
|
||||||
# Now, the negative side
|
# Now, the negative side
|
||||||
|
|
||||||
@ -295,7 +285,7 @@ def rebaseTent(tent, axisLimit):
|
|||||||
If tent value is None, that is a special deltaset that should
|
If tent value is None, that is a special deltaset that should
|
||||||
be always-enabled (called "gain")."""
|
be always-enabled (called "gain")."""
|
||||||
|
|
||||||
axisMin, axisDef, axisMax = axisLimit
|
axisMin, axisDef, axisMax, _distanceNegative, _distancePositive = axisLimit
|
||||||
assert -1 <= axisMin <= axisDef <= axisMax <= +1
|
assert -1 <= axisMin <= axisDef <= axisMax <= +1
|
||||||
|
|
||||||
lower, peak, upper = tent
|
lower, peak, upper = tent
|
||||||
@ -305,7 +295,7 @@ def rebaseTent(tent, axisLimit):
|
|||||||
|
|
||||||
sols = _solve(tent, axisLimit)
|
sols = _solve(tent, axisLimit)
|
||||||
|
|
||||||
n = lambda v: normalizeValue(v, axisLimit, extrapolate=True)
|
n = lambda v: axisLimit.renormalizeValue(v)
|
||||||
sols = [
|
sols = [
|
||||||
(scalar, (n(v[0]), n(v[1]), n(v[2])) if v is not None else None)
|
(scalar, (n(v[0]), n(v[1]), n(v[2])) if v is not None else None)
|
||||||
for scalar, v in sols
|
for scalar, v in sols
|
||||||
|
@ -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)
|
||||||
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,10 @@ def test_parseLimits_invalid(limits):
|
|||||||
@pytest.mark.parametrize(
|
@pytest.mark.parametrize(
|
||||||
"limits, expected",
|
"limits, expected",
|
||||||
[
|
[
|
||||||
({"wght": (100, 400)}, {"wght": (-1.0, 0, 0)}),
|
# 300, 500 come from the font having 100,400,900 fvar axis limits.
|
||||||
({"wght": (100, 400, 400)}, {"wght": (-1.0, 0, 0)}),
|
({"wght": (100, 400)}, {"wght": (-1.0, 0, 0, 300, 500)}),
|
||||||
({"wght": (100, 300, 400)}, {"wght": (-1.0, -0.5, 0)}),
|
({"wght": (100, 400, 400)}, {"wght": (-1.0, 0, 0, 300, 500)}),
|
||||||
|
({"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 +2114,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
|
||||||
|
|
||||||
|
|
||||||
@ -91,6 +92,25 @@ class RebaseTentTest(object):
|
|||||||
(-1, (-1, -1, 0)),
|
(-1, (-1, -1, 0)),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
|
pytest.param(
|
||||||
|
(0.0, 0.5, 1),
|
||||||
|
(0, 0.5, 0.75),
|
||||||
|
[
|
||||||
|
(1, None),
|
||||||
|
(-0.5, (0, 1, 1)),
|
||||||
|
(-1, (-1, -1, 0)),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
pytest.param(
|
||||||
|
(0.0, 0.5, 1),
|
||||||
|
(0, 0.25, 0.8),
|
||||||
|
[
|
||||||
|
(0.5, None),
|
||||||
|
(0.5, (0, 0.45454545, 0.9090909090)),
|
||||||
|
(-0.1, (0.9090909090, 1.0, 1.0)),
|
||||||
|
(-0.5, (-1, -1, 0)),
|
||||||
|
],
|
||||||
|
),
|
||||||
# Case 3a/1neg
|
# Case 3a/1neg
|
||||||
pytest.param(
|
pytest.param(
|
||||||
(0.0, 0.5, 2),
|
(0.0, 0.5, 2),
|
||||||
@ -117,8 +137,7 @@ class RebaseTentTest(object):
|
|||||||
(0.25, 0.25, 0.75),
|
(0.25, 0.25, 0.75),
|
||||||
[
|
[
|
||||||
(0.5, None),
|
(0.5, None),
|
||||||
(0.5, (0, 0.5, 1.5)),
|
(0.5, (0, 0.5, 1.0)),
|
||||||
(-0.5, (0.5, 1.5, 1.5)),
|
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
# Case 1neg
|
# Case 1neg
|
||||||
@ -242,9 +261,26 @@ class RebaseTentTest(object):
|
|||||||
(-1, (-1, -1, -0.0001220703)),
|
(-1, (-1, -1, -0.0001220703)),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
|
# https://github.com/fonttools/fonttools/issues/3177
|
||||||
|
pytest.param(
|
||||||
|
(0, 1, 1),
|
||||||
|
(-1, -0.5, +1, 1, 1),
|
||||||
|
[
|
||||||
|
(1.0, (1 / 3, 1.0, 1.0)),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
pytest.param(
|
||||||
|
(0, 1, 1),
|
||||||
|
(-1, -0.5, +1, 2, 1),
|
||||||
|
[
|
||||||
|
(1.0, (0.5, 1.0, 1.0)),
|
||||||
|
],
|
||||||
|
),
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
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