[instancer/L4] Fix normalizeValue for L4 solver
Imagine a font with current min/default/max of 100,700,1000. And new
setting of 100,400,1000. The current normalizeLocation will calculate
the new location for 700 to be +.33, whereas it should calculate +.5!
This is because 400 translates to -.5, so 700 will be normalized to
-1,-.5,+1 and get +.33...
We need a special normalizeLocation that is aware of the "distance"
between min/default/max, ie. the non-normalized values. Then it will be
clear that the distance from 400 to 700 is equal to 700 to 1000, and as
such 700 should be normalized to .5, not .33... I'm still trying to
figure out the case where avar is present.
Store this distance in NormalizeAxisLimit and reach it out in the
solver.
Fixes https://github.com/fonttools/fonttools/issues/3177
2023-06-21 12:49:19 -06:00
|
|
|
from fontTools.varLib.models import supportScalar
|
2022-08-08 05:35:05 -06:00
|
|
|
from fontTools.misc.fixedTools import MAX_F2DOT14
|
2022-08-09 15:08:29 -06:00
|
|
|
from functools import lru_cache
|
2022-08-22 15:59:18 -06:00
|
|
|
|
2022-08-22 16:00:37 -06:00
|
|
|
__all__ = ["rebaseTent"]
|
2022-08-08 12:05:10 -06:00
|
|
|
|
2022-08-09 20:32:25 -06:00
|
|
|
EPSILON = 1 / (1 << 14)
|
|
|
|
|
2022-08-22 16:00:37 -06:00
|
|
|
|
2022-08-08 12:57:49 -06:00
|
|
|
def _reverse_negate(v):
|
2022-08-06 20:33:16 -06:00
|
|
|
return (-v[2], -v[1], -v[0])
|
|
|
|
|
2022-08-22 16:00:37 -06:00
|
|
|
|
2022-08-09 14:59:12 -06:00
|
|
|
def _solve(tent, axisLimit, negative=False):
|
2023-07-10 12:42:17 -06:00
|
|
|
axisMin, axisDef, axisMax, _distanceNegative, _distancePositive = axisLimit
|
2022-08-08 07:55:42 -06:00
|
|
|
lower, peak, upper = tent
|
2022-08-08 05:35:05 -06:00
|
|
|
|
2022-08-08 12:58:28 -06:00
|
|
|
# Mirror the problem such that axisDef <= peak
|
2022-08-08 11:27:17 -06:00
|
|
|
if axisDef > peak:
|
2022-08-22 16:00:37 -06:00
|
|
|
return [
|
|
|
|
(scalar, _reverse_negate(t) if t is not None else None)
|
|
|
|
for scalar, t in _solve(
|
[instancer/L4] Fix normalizeValue for L4 solver
Imagine a font with current min/default/max of 100,700,1000. And new
setting of 100,400,1000. The current normalizeLocation will calculate
the new location for 700 to be +.33, whereas it should calculate +.5!
This is because 400 translates to -.5, so 700 will be normalized to
-1,-.5,+1 and get +.33...
We need a special normalizeLocation that is aware of the "distance"
between min/default/max, ie. the non-normalized values. Then it will be
clear that the distance from 400 to 700 is equal to 700 to 1000, and as
such 700 should be normalized to .5, not .33... I'm still trying to
figure out the case where avar is present.
Store this distance in NormalizeAxisLimit and reach it out in the
solver.
Fixes https://github.com/fonttools/fonttools/issues/3177
2023-06-21 12:49:19 -06:00
|
|
|
_reverse_negate(tent),
|
2023-06-21 13:52:49 -06:00
|
|
|
axisLimit.reverse_negate(),
|
[instancer/L4] Fix normalizeValue for L4 solver
Imagine a font with current min/default/max of 100,700,1000. And new
setting of 100,400,1000. The current normalizeLocation will calculate
the new location for 700 to be +.33, whereas it should calculate +.5!
This is because 400 translates to -.5, so 700 will be normalized to
-1,-.5,+1 and get +.33...
We need a special normalizeLocation that is aware of the "distance"
between min/default/max, ie. the non-normalized values. Then it will be
clear that the distance from 400 to 700 is equal to 700 to 1000, and as
such 700 should be normalized to .5, not .33... I'm still trying to
figure out the case where avar is present.
Store this distance in NormalizeAxisLimit and reach it out in the
solver.
Fixes https://github.com/fonttools/fonttools/issues/3177
2023-06-21 12:49:19 -06:00
|
|
|
not negative,
|
2022-08-22 16:00:37 -06:00
|
|
|
)
|
|
|
|
]
|
2022-08-08 11:27:17 -06:00
|
|
|
# axisDef <= peak
|
2022-08-08 07:55:42 -06:00
|
|
|
|
2022-08-08 12:47:46 -06:00
|
|
|
# case 1: The whole deltaset falls outside the new limit; we can drop it
|
|
|
|
#
|
|
|
|
# peak
|
|
|
|
# 1.........................................o..........
|
|
|
|
# / \
|
|
|
|
# / \
|
|
|
|
# / \
|
|
|
|
# / \
|
|
|
|
# 0---|-----------|----------|-------- o o----1
|
|
|
|
# axisMin axisDef axisMax lower upper
|
|
|
|
#
|
2022-08-08 11:27:17 -06:00
|
|
|
if axisMax <= lower and axisMax < peak:
|
2022-08-22 16:00:37 -06:00
|
|
|
return [] # No overlap
|
2022-08-08 07:55:42 -06:00
|
|
|
|
2022-08-08 12:47:46 -06:00
|
|
|
# case 2: Only the peak and outermost bound fall outside the new limit;
|
2022-08-08 11:27:17 -06:00
|
|
|
# we keep the deltaset, update peak and outermost bound and and scale deltas
|
2022-08-08 11:49:49 -06:00
|
|
|
# by the scalar value for the restricted axis at the new limit, and solve
|
|
|
|
# recursively.
|
2022-08-08 12:47:46 -06:00
|
|
|
#
|
|
|
|
# |peak
|
|
|
|
# 1...............................|.o..........
|
|
|
|
# |/ \
|
|
|
|
# / \
|
|
|
|
# /| \
|
|
|
|
# / | \
|
2022-08-11 11:34:21 -06:00
|
|
|
# 0--------------------------- o | o----1
|
|
|
|
# lower | upper
|
2022-08-08 12:47:46 -06:00
|
|
|
# |
|
|
|
|
# axisMax
|
|
|
|
#
|
|
|
|
# Convert to:
|
|
|
|
#
|
|
|
|
# 1............................................
|
|
|
|
# |
|
|
|
|
# o peak
|
|
|
|
# /|
|
|
|
|
# /x|
|
2022-08-11 11:34:21 -06:00
|
|
|
# 0--------------------------- o o upper ----1
|
|
|
|
# lower |
|
2022-08-08 12:47:46 -06:00
|
|
|
# |
|
|
|
|
# axisMax
|
2022-08-08 11:27:17 -06:00
|
|
|
if axisMax < peak:
|
2022-08-22 16:00:37 -06:00
|
|
|
mult = supportScalar({"tag": axisMax}, {"tag": tent})
|
2022-08-08 11:27:17 -06:00
|
|
|
tent = (lower, axisMax, axisMax)
|
2022-08-22 16:00:37 -06:00
|
|
|
return [(scalar * mult, t) for scalar, t in _solve(tent, axisLimit)]
|
2022-08-08 07:55:42 -06:00
|
|
|
|
|
|
|
# lower <= axisDef <= peak <= axisMax
|
2022-08-08 05:57:43 -06:00
|
|
|
|
2022-08-22 16:00:37 -06:00
|
|
|
gain = supportScalar({"tag": axisDef}, {"tag": tent})
|
2022-08-08 10:46:55 -06:00
|
|
|
out = [(gain, None)]
|
2022-08-08 05:57:43 -06:00
|
|
|
|
|
|
|
# First, the positive side
|
|
|
|
|
2022-08-08 11:49:49 -06:00
|
|
|
# outGain is the scalar of axisMax at the tent.
|
2022-08-22 16:00:37 -06:00
|
|
|
outGain = supportScalar({"tag": axisMax}, {"tag": tent})
|
2022-08-08 08:33:32 -06:00
|
|
|
|
2022-08-08 12:47:46 -06:00
|
|
|
# Case 3a: Gain is more than outGain. The tent down-slope crosses
|
2022-08-08 11:49:49 -06:00
|
|
|
# the axis into negative. We have to split it into multiples.
|
2022-08-08 12:47:46 -06:00
|
|
|
#
|
|
|
|
# | peak |
|
|
|
|
# 1...................|.o.....|..............
|
|
|
|
# |/x\_ |
|
|
|
|
# gain................+....+_.|..............
|
|
|
|
# /| |y\|
|
|
|
|
# ................../.|....|..+_......outGain
|
|
|
|
# / | | | \
|
|
|
|
# 0---|-----------o | | | o----------1
|
|
|
|
# axisMin lower | | | upper
|
|
|
|
# | | |
|
|
|
|
# axisDef | axisMax
|
|
|
|
# |
|
|
|
|
# crossing
|
2023-10-16 13:46:39 -06:00
|
|
|
if gain >= outGain:
|
|
|
|
# Note that this is the branch taken if both gain and outGain are 0.
|
|
|
|
|
2022-08-08 11:49:49 -06:00
|
|
|
# Crossing point on the axis.
|
2023-06-21 09:47:08 -06:00
|
|
|
crossing = peak + (1 - gain) * (upper - peak)
|
2022-08-08 08:33:32 -06:00
|
|
|
|
2023-10-16 13:46:39 -06:00
|
|
|
loc = (max(lower, axisDef), peak, crossing)
|
2022-08-08 09:35:10 -06:00
|
|
|
scalar = 1
|
2022-08-08 08:33:32 -06:00
|
|
|
|
2022-08-08 11:49:49 -06:00
|
|
|
# The part before the crossing point.
|
2022-08-08 09:35:10 -06:00
|
|
|
out.append((scalar - gain, loc))
|
|
|
|
|
2022-08-08 11:49:49 -06:00
|
|
|
# The part after the crossing point may use one or two tents,
|
|
|
|
# depending on whether upper is before axisMax or not, in one
|
|
|
|
# case we need to keep it down to eternity.
|
|
|
|
|
2022-08-08 12:47:46 -06:00
|
|
|
# Case 3a1, similar to case 1neg; just one tent needed, as in
|
|
|
|
# the drawing above.
|
2022-08-08 09:35:10 -06:00
|
|
|
if upper >= axisMax:
|
|
|
|
loc = (crossing, axisMax, axisMax)
|
2023-06-21 09:53:03 -06:00
|
|
|
scalar = outGain
|
2022-08-08 09:35:10 -06:00
|
|
|
|
|
|
|
out.append((scalar - gain, loc))
|
2022-08-08 11:32:07 -06:00
|
|
|
|
2022-08-08 12:47:46 -06:00
|
|
|
# Case 3a2: Similar to case 2neg; two tents needed, to keep
|
2022-08-08 11:49:49 -06:00
|
|
|
# down to eternity.
|
2022-08-08 12:47:46 -06:00
|
|
|
#
|
|
|
|
# | peak |
|
|
|
|
# 1...................|.o................|...
|
|
|
|
# |/ \_ |
|
|
|
|
# gain................+....+_............|...
|
|
|
|
# /| | \xxxxxxxxxxy|
|
|
|
|
# / | | \_xxxxxyyyy|
|
|
|
|
# / | | \xxyyyyyy|
|
|
|
|
# 0---|-----------o | | o-------|--1
|
|
|
|
# axisMin lower | | upper |
|
|
|
|
# | | |
|
|
|
|
# axisDef | axisMax
|
|
|
|
# |
|
|
|
|
# crossing
|
2022-08-08 09:35:10 -06:00
|
|
|
else:
|
2022-08-09 20:32:25 -06:00
|
|
|
# A tent's peak cannot fall on axis default. Nudge it.
|
|
|
|
if upper == axisDef:
|
|
|
|
upper += EPSILON
|
|
|
|
|
2022-08-08 11:49:49 -06:00
|
|
|
# Downslope.
|
2022-08-08 09:35:10 -06:00
|
|
|
loc1 = (crossing, upper, axisMax)
|
|
|
|
scalar1 = 0
|
|
|
|
|
2022-08-08 11:49:49 -06:00
|
|
|
# Eternity justify.
|
2022-08-08 09:35:10 -06:00
|
|
|
loc2 = (upper, axisMax, axisMax)
|
2023-06-21 09:57:57 -06:00
|
|
|
scalar2 = 0
|
2022-08-08 08:33:32 -06:00
|
|
|
|
2022-08-08 09:35:10 -06:00
|
|
|
out.append((scalar1 - gain, loc1))
|
2022-08-08 08:33:32 -06:00
|
|
|
out.append((scalar2 - gain, loc2))
|
|
|
|
|
2023-06-21 17:45:19 -06:00
|
|
|
else:
|
2022-08-08 11:55:10 -06:00
|
|
|
# Special-case if peak is at axisMax.
|
|
|
|
if axisMax == peak:
|
|
|
|
upper = peak
|
|
|
|
|
2023-06-21 18:52:26 -06:00
|
|
|
# Case 3:
|
|
|
|
# We keep delta as is and only scale the axis upper to achieve
|
2023-06-21 17:45:19 -06:00
|
|
|
# the desired new tent if feasible.
|
|
|
|
#
|
2023-06-21 18:52:26 -06:00
|
|
|
# peak
|
|
|
|
# 1.....................o....................
|
|
|
|
# / \_|
|
|
|
|
# ..................../....+_.........outGain
|
|
|
|
# / | \
|
|
|
|
# gain..............+......|..+_.............
|
|
|
|
# /| | | \
|
|
|
|
# 0---|-----------o | | | o----------1
|
|
|
|
# axisMin lower| | | upper
|
|
|
|
# | | newUpper
|
|
|
|
# axisDef axisMax
|
2023-06-21 17:45:19 -06:00
|
|
|
#
|
|
|
|
newUpper = peak + (1 - gain) * (upper - peak)
|
2023-10-16 13:46:39 -06:00
|
|
|
assert axisMax <= newUpper # Because outGain > gain
|
2023-06-21 18:52:26 -06:00
|
|
|
if newUpper <= axisDef + (axisMax - axisDef) * 2:
|
2023-06-21 17:45:19 -06:00
|
|
|
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
|
2022-08-09 21:01:53 -06:00
|
|
|
|
2023-06-21 17:45:19 -06:00
|
|
|
loc = (max(axisDef, lower), peak, upper)
|
|
|
|
scalar = 1
|
2022-08-08 11:25:41 -06:00
|
|
|
|
2023-06-21 17:45:19 -06:00
|
|
|
out.append((scalar - gain, loc))
|
2022-08-08 05:57:43 -06:00
|
|
|
|
2023-06-21 17:45:19 -06:00
|
|
|
# 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
|
|
|
|
|
|
|
|
out.append((scalar1 - gain, loc1))
|
|
|
|
# Don't add a dirac delta!
|
|
|
|
if peak < axisMax:
|
|
|
|
out.append((scalar2 - gain, loc2))
|
2022-08-08 05:57:43 -06:00
|
|
|
|
|
|
|
# Now, the negative side
|
|
|
|
|
2022-08-08 12:47:46 -06:00
|
|
|
# Case 1neg: Lower extends beyond axisMin: we chop. Simple.
|
|
|
|
#
|
|
|
|
# | |peak
|
|
|
|
# 1..................|...|.o.................
|
|
|
|
# | |/ \
|
|
|
|
# gain...............|...+...\...............
|
|
|
|
# |x_/| \
|
|
|
|
# |/ | \
|
|
|
|
# _/| | \
|
|
|
|
# 0---------------o | | o----------1
|
|
|
|
# lower | | upper
|
|
|
|
# | |
|
|
|
|
# axisMin axisDef
|
|
|
|
#
|
2022-08-08 05:57:43 -06:00
|
|
|
if lower <= axisMin:
|
|
|
|
loc = (axisMin, axisMin, axisDef)
|
2022-08-22 16:00:37 -06:00
|
|
|
scalar = supportScalar({"tag": axisMin}, {"tag": tent})
|
2022-08-08 05:57:43 -06:00
|
|
|
|
|
|
|
out.append((scalar - gain, loc))
|
|
|
|
|
2022-08-08 12:47:46 -06:00
|
|
|
# Case 2neg: Lower is betwen axisMin and axisDef: we add two
|
|
|
|
# tents to keep it down all the way to eternity.
|
|
|
|
#
|
|
|
|
# | |peak
|
|
|
|
# 1...|...............|.o.................
|
|
|
|
# | |/ \
|
|
|
|
# gain|...............+...\...............
|
|
|
|
# |yxxxxxxxxxxxxx/| \
|
|
|
|
# |yyyyyyxxxxxxx/ | \
|
|
|
|
# |yyyyyyyyyyyx/ | \
|
|
|
|
# 0---|-----------o | o----------1
|
|
|
|
# axisMin lower | upper
|
|
|
|
# |
|
|
|
|
# axisDef
|
|
|
|
#
|
2022-08-08 07:55:42 -06:00
|
|
|
else:
|
2022-08-09 20:32:25 -06:00
|
|
|
# A tent's peak cannot fall on axis default. Nudge it.
|
|
|
|
if lower == axisDef:
|
|
|
|
lower -= EPSILON
|
|
|
|
|
2022-08-08 11:49:49 -06:00
|
|
|
# Downslope.
|
2022-08-08 05:57:43 -06:00
|
|
|
loc1 = (axisMin, lower, axisDef)
|
|
|
|
scalar1 = 0
|
|
|
|
|
2022-08-08 11:49:49 -06:00
|
|
|
# Eternity justify.
|
2022-08-08 05:57:43 -06:00
|
|
|
loc2 = (axisMin, axisMin, lower)
|
|
|
|
scalar2 = 0
|
|
|
|
|
|
|
|
out.append((scalar1 - gain, loc1))
|
|
|
|
out.append((scalar2 - gain, loc2))
|
|
|
|
|
2022-08-09 20:32:25 -06:00
|
|
|
return out
|
2022-08-08 05:57:43 -06:00
|
|
|
|
|
|
|
|
2022-08-09 15:17:36 -06:00
|
|
|
@lru_cache(128)
|
2022-08-06 16:17:43 -06:00
|
|
|
def rebaseTent(tent, axisLimit):
|
2022-08-08 12:05:10 -06:00
|
|
|
"""Given a tuple (lower,peak,upper) "tent" and new axis limits
|
|
|
|
(axisMin,axisDefault,axisMax), solves how to represent the tent
|
2022-08-08 12:07:04 -06:00
|
|
|
under the new axis configuration. All values are in normalized
|
|
|
|
-1,0,+1 coordinate system. Tent values can be outside this range.
|
2022-08-08 12:05:10 -06:00
|
|
|
|
|
|
|
Return value is a list of tuples. Each tuple is of the form
|
|
|
|
(scalar,tent), where scalar is a multipler to multiply any
|
|
|
|
delta-sets by, and tent is a new tent for that output delta-set.
|
|
|
|
If tent value is None, that is a special deltaset that should
|
|
|
|
be always-enabled (called "gain")."""
|
2022-08-06 15:20:41 -06:00
|
|
|
|
2023-07-10 12:42:17 -06:00
|
|
|
axisMin, axisDef, axisMax, _distanceNegative, _distancePositive = axisLimit
|
2022-08-06 15:20:41 -06:00
|
|
|
assert -1 <= axisMin <= axisDef <= axisMax <= +1
|
|
|
|
|
2022-08-06 16:17:43 -06:00
|
|
|
lower, peak, upper = tent
|
|
|
|
assert -2 <= lower <= peak <= upper <= +2
|
|
|
|
|
2022-08-06 16:22:39 -06:00
|
|
|
assert peak != 0
|
|
|
|
|
2022-08-08 11:27:17 -06:00
|
|
|
sols = _solve(tent, axisLimit)
|
|
|
|
|
2023-07-10 12:42:17 -06:00
|
|
|
n = lambda v: axisLimit.renormalizeValue(v)
|
2022-08-22 16:00:37 -06:00
|
|
|
sols = [
|
|
|
|
(scalar, (n(v[0]), n(v[1]), n(v[2])) if v is not None else None)
|
|
|
|
for scalar, v in sols
|
|
|
|
if scalar
|
|
|
|
]
|
2022-08-08 11:27:17 -06:00
|
|
|
|
2022-08-08 05:35:05 -06:00
|
|
|
return sols
|