2022-08-08 05:35:05 -06:00
|
|
|
from fontTools.varLib.models import supportScalar, normalizeValue
|
|
|
|
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-08 12:05:10 -06:00
|
|
|
__all__ = ['rebaseTent']
|
|
|
|
|
2022-08-09 20:32:25 -06:00
|
|
|
EPSILON = 1 / (1 << 14)
|
|
|
|
|
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-09 14:59:12 -06:00
|
|
|
def _solve(tent, axisLimit, negative=False):
|
2022-08-08 07:55:42 -06:00
|
|
|
axisMin, axisDef, axisMax = axisLimit
|
|
|
|
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-08 12:57:49 -06:00
|
|
|
return [(scalar, _reverse_negate(t) if t is not None else None)
|
2022-08-08 11:27:17 -06:00
|
|
|
for scalar,t
|
2022-08-09 14:59:12 -06:00
|
|
|
in _solve(_reverse_negate(tent), _reverse_negate(axisLimit), not negative)]
|
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:
|
|
|
|
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..........
|
|
|
|
# |/ \
|
|
|
|
# / \
|
|
|
|
# /| \
|
|
|
|
# / | \
|
|
|
|
# 0---|-----------|----------- o | o----1
|
|
|
|
# axisMin axisDef lower | upper
|
|
|
|
# |
|
|
|
|
# axisMax
|
|
|
|
#
|
|
|
|
# Convert to:
|
|
|
|
#
|
|
|
|
# 1............................................
|
|
|
|
# |
|
|
|
|
# o peak
|
|
|
|
# /|
|
|
|
|
# /x|
|
|
|
|
# 0---|-----------|----------- o o upper ----1
|
|
|
|
# axisMin axisDef lower |
|
|
|
|
# |
|
|
|
|
# axisMax
|
2022-08-08 11:27:17 -06:00
|
|
|
if axisMax < peak:
|
|
|
|
mult = supportScalar({'tag': axisMax}, {'tag': tent})
|
|
|
|
tent = (lower, axisMax, axisMax)
|
|
|
|
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-08 05:59:46 -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-08 08:33:32 -06:00
|
|
|
outGain = supportScalar({'tag': axisMax}, {'tag': tent})
|
|
|
|
|
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
|
2022-08-08 08:33:32 -06:00
|
|
|
if gain > outGain:
|
|
|
|
|
2022-08-08 11:49:49 -06:00
|
|
|
# Crossing point on the axis.
|
2022-08-08 08:33:32 -06:00
|
|
|
crossing = peak + ((1 - gain) * (upper - peak) / (1 - outGain))
|
|
|
|
|
2022-08-08 09:35:10 -06:00
|
|
|
loc = (peak, peak, crossing)
|
|
|
|
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)
|
|
|
|
scalar = supportScalar({'tag': axisMax}, {'tag': tent})
|
|
|
|
|
|
|
|
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)
|
|
|
|
scalar2 = supportScalar({'tag': axisMax}, {'tag': tent})
|
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))
|
|
|
|
|
2022-08-08 12:47:46 -06:00
|
|
|
# Case 3: Outermost limit still fits within F2Dot14 bounds;
|
2022-08-08 05:57:43 -06:00
|
|
|
# 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.
|
2022-08-08 12:47:46 -06:00
|
|
|
#
|
2022-08-09 21:01:53 -06:00
|
|
|
# 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.
|
|
|
|
#
|
2022-08-08 12:47:46 -06:00
|
|
|
# | peak |
|
|
|
|
# 1.........|............o...|..................
|
|
|
|
# | /x\ |
|
|
|
|
# | /xxx\ |
|
|
|
|
# | /xxxxx\|
|
|
|
|
# | /xxxxxxx+
|
|
|
|
# | /xxxxxxxx|\
|
|
|
|
# 0---|-----|------oxxxxxxxxx|xo---------------1
|
|
|
|
# axisMin | lower | upper
|
|
|
|
# | |
|
|
|
|
# axisDef axisMax
|
|
|
|
#
|
2022-08-08 08:33:32 -06:00
|
|
|
elif axisDef + (axisMax - axisDef) * 2 >= upper:
|
2022-08-08 05:57:43 -06:00
|
|
|
|
2022-08-09 14:59:12 -06:00
|
|
|
if not negative and axisDef + (axisMax - axisDef) * MAX_F2DOT14 < upper:
|
2022-08-08 05:57:43 -06:00
|
|
|
# we clamp +2.0 to the max F2Dot14 (~1.99994) for convenience
|
|
|
|
upper = axisDef + (axisMax - axisDef) * MAX_F2DOT14
|
2022-08-09 21:19:02 -06:00
|
|
|
assert peak < upper
|
2022-08-08 05:57:43 -06:00
|
|
|
|
2022-08-08 11:55:10 -06:00
|
|
|
# Special-case if peak is at axisMax.
|
|
|
|
if axisMax == peak:
|
|
|
|
upper = peak
|
|
|
|
|
2022-08-09 21:01:53 -06:00
|
|
|
loc1 = (max(axisDef, lower), peak, upper)
|
|
|
|
scalar1 = 1
|
|
|
|
|
|
|
|
loc2 = (peak, upper, upper)
|
|
|
|
scalar2 = 0
|
2022-08-08 11:25:41 -06:00
|
|
|
|
2022-08-08 11:49:49 -06:00
|
|
|
# Don't add a dirac delta!
|
2022-08-09 21:01:53 -06:00
|
|
|
if axisDef < upper:
|
|
|
|
out.append((scalar1 - gain, loc1))
|
|
|
|
if peak < upper:
|
|
|
|
out.append((scalar2 - gain, loc2))
|
2022-08-08 05:57:43 -06:00
|
|
|
|
2022-08-08 12:47:46 -06:00
|
|
|
# Case 4: New limit doesn't fit; we need to chop into two tents,
|
2022-08-08 11:49:49 -06:00
|
|
|
# because the shape of a triangle with part of one side cut off
|
|
|
|
# cannot be represented as a triangle itself.
|
2022-08-08 12:47:46 -06:00
|
|
|
#
|
|
|
|
# | peak |
|
|
|
|
# 1.........|......o.|...................
|
|
|
|
# | /x\|
|
|
|
|
# | |xxy|\_
|
|
|
|
# | /xxxy| \_
|
|
|
|
# | |xxxxy| \_
|
|
|
|
# | /xxxxy| \_
|
|
|
|
# 0---|-----|-oxxxxxx| o----------1
|
|
|
|
# axisMin | lower | upper
|
|
|
|
# | |
|
|
|
|
# axisDef axisMax
|
|
|
|
#
|
2022-08-08 05:57:43 -06:00
|
|
|
else:
|
|
|
|
|
2022-08-08 11:25:41 -06:00
|
|
|
loc1 = (max(axisDef, lower), peak, axisMax)
|
2022-08-08 08:33:32 -06:00
|
|
|
scalar1 = 1
|
2022-08-08 05:57:43 -06:00
|
|
|
|
|
|
|
loc2 = (peak, axisMax, axisMax)
|
2022-08-08 05:59:46 -06:00
|
|
|
scalar2 = supportScalar({'tag': axisMax}, {'tag': tent})
|
2022-08-08 05:57:43 -06:00
|
|
|
|
|
|
|
out.append((scalar1 - gain, loc1))
|
2022-08-08 11:49:49 -06:00
|
|
|
# Don't add a dirac delta!
|
2022-08-08 06:57:40 -06:00
|
|
|
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-08 05:59:46 -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
|
|
|
|
|
|
|
axisMin, axisDef, axisMax = axisLimit
|
|
|
|
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)
|
|
|
|
|
2022-08-08 05:35:05 -06:00
|
|
|
n = lambda v: normalizeValue(v, axisLimit, extrapolate=True)
|
2022-08-09 21:22:40 -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
|