[instancer.solver] Implement no-gain in the general solution

This can replace the DefaultUnmoved
This commit is contained in:
Behdad Esfahbod 2022-08-08 05:35:05 -06:00
parent e94f564149
commit bf55b28c50
2 changed files with 48 additions and 6 deletions

View File

@ -75,7 +75,6 @@ from fontTools.misc.fixedTools import (
floatToFixedToFloat,
strToFixedToFloat,
otRound,
MAX_F2DOT14,
)
from fontTools.varLib.models import supportScalar, normalizeValue, piecewiseLinearMap
from fontTools.ttLib import TTFont

View File

@ -1,4 +1,5 @@
from fontTools.varLib.models import supportScalar
from fontTools.varLib.models import supportScalar, normalizeValue
from fontTools.misc.fixedTools import MAX_F2DOT14
def _negate(*values):
yield from (-1 * v for v in values)
@ -105,14 +106,43 @@ def _solveDefaultUnmoved(tent, axisLimit):
def _solveNoGain(tent, axisLimit):
axisMin, axisDef, axisMax = axisLimit
lower, peak, upper = tent
# axisMin <= axisDef <= lower < peak <= axisMax
# case 3: outermost limit still fits within F2Dot14 bounds;
# 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.
if axisDef + (axisMax - axisDef) * 2 >= upper:
if axisDef + (axisMax - axisDef) * MAX_F2DOT14 < upper:
# we clamp +2.0 to the max F2Dot14 (~1.99994) for convenience
upper = axisDef + (axisMax - axisDef) * MAX_F2DOT14
return [(1, (lower, peak, upper))]
# case 4: new limit doesn't fit; we need to chop the deltaset into two 'tents',
# because the shape of a triangle with part of one side cut off cannot be
# represented as a triangle itself. It can be represented as sum of two triangles.
# NOTE: This increases the file size!
else:
loc1 = (lower, peak, axisMax)
scalar1 = 1
loc2 = (peak, axisMax, axisMax)
scalar2 = supportScalar({tag: axisMax}, {tag: tent})
return [(scalar1, loc1), (scalar2, loc2)]
def _solveGeneral(tent, axisLimit):
axisMin, axisDef, axisMax = axisLimit
lower, peak, upper = tent
# Mirror the problem such that axisDef is always <= peak
if axisDef > peak:
return [(scalar, _revnegate(t))
for scalar,t
@ -120,16 +150,24 @@ def _solveGeneral(tent, axisLimit):
_revnegate(axisLimit))]
# axisDef <= peak
# case 1: the whole deltaset falls outside the new limit; we can drop it
if axisMax <= lower and axisMax < peak:
return [] # No overlap
# case 2: only the peak and outermost bound fall outside the new limit;
# we keep the deltaset, update peak and outermost bound and and scale deltas
# by the scalar value for the restricted axis at the new limit.
if axisMax < peak:
mult = supportScalar({'tag': peak}, {'tag': axisLimit})
mult = supportScalar({'tag': axisMax}, {'tag': tent})
tent = (lower, axisMax, axisMax)
return [(scalar*mult, t) for scalar,t in _solveGeneral(tent, axisLimit)]
# axisDef <= peak <= axisMax
if axisDef <= lower and axisDef < peak:
# No gain to carry
return _solveNoGain(tent, axisLimit)
raise NotImplementedError
@ -148,7 +186,12 @@ def rebaseTent(tent, axisLimit):
return _solvePinned(tent, axisLimit)
# If default isn't moving, get that out of the way as well
if axisDef == 0:
return _solveDefaultUnmoved(tent, axisLimit)
if False and axisDef == 0:
sols = _solveDefaultUnmoved(tent, axisLimit)
print(sols)
return sols
return _solveGeneral(tent, axisLimit)
sols = _solveGeneral(tent, axisLimit)
n = lambda v: normalizeValue(v, axisLimit, extrapolate=True)
sols = [(scalar, (n(v[0]), n(v[1]), n(v[2]))) for scalar,v in sols if scalar != 0]
return sols