2022-08-08 05:35:05 -06:00
|
|
|
from fontTools.varLib.models import supportScalar, normalizeValue
|
|
|
|
from fontTools.misc.fixedTools import MAX_F2DOT14
|
2022-08-08 07:23:12 -06:00
|
|
|
from functools import cache
|
2022-08-22 15:59:18 -06:00
|
|
|
|
2022-08-06 20:33:16 -06:00
|
|
|
def _revnegate(v):
|
|
|
|
return (-v[2], -v[1], -v[0])
|
|
|
|
|
2022-08-08 07:55:42 -06:00
|
|
|
def _solveWithoutGain(tent, axisLimit):
|
|
|
|
axisMin, axisDef, axisMax = axisLimit
|
|
|
|
lower, peak, upper = tent
|
2022-08-08 05:35:05 -06:00
|
|
|
|
2022-08-08 07:55:42 -06:00
|
|
|
# axisMin <= axisDef <= lower < peak <= axisMax
|
2022-08-08 05:35:05 -06:00
|
|
|
|
2022-08-08 07:55:42 -06:00
|
|
|
# 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:
|
2022-08-06 20:33:16 -06:00
|
|
|
|
2022-08-08 07:55:42 -06:00
|
|
|
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
|
2022-08-06 20:33:16 -06:00
|
|
|
|
2022-08-08 07:55:42 -06:00
|
|
|
return [(1, (lower, peak, upper))]
|
2022-08-06 20:33:16 -06:00
|
|
|
|
2022-08-08 07:55:42 -06:00
|
|
|
# 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:
|
2022-08-06 20:33:16 -06:00
|
|
|
|
2022-08-08 07:55:42 -06:00
|
|
|
loc1 = (lower, peak, axisMax)
|
|
|
|
scalar1 = 1
|
2022-08-08 05:57:43 -06:00
|
|
|
|
2022-08-08 07:55:42 -06:00
|
|
|
loc2 = (peak, axisMax, axisMax)
|
|
|
|
scalar2 = supportScalar({'tag': axisMax}, {'tag': tent})
|
|
|
|
|
|
|
|
if (peak < axisMax):
|
|
|
|
return [(scalar1, loc1), (scalar2, loc2)]
|
|
|
|
else:
|
|
|
|
return [(scalar1, loc1)]
|
|
|
|
|
|
|
|
|
|
|
|
def _solveWithGain(tent, axisLimit):
|
|
|
|
axisMin, axisDef, axisMax = axisLimit
|
|
|
|
lower, peak, upper = tent
|
|
|
|
|
|
|
|
# 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 08:33:32 -06:00
|
|
|
# case 3a: gain is more than outGain.
|
|
|
|
outGain = supportScalar({'tag': axisMax}, {'tag': tent})
|
|
|
|
|
|
|
|
if gain > outGain:
|
|
|
|
|
|
|
|
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 09:35:10 -06:00
|
|
|
out.append((scalar - gain, loc))
|
|
|
|
|
|
|
|
if upper >= axisMax:
|
|
|
|
loc = (crossing, axisMax, axisMax)
|
|
|
|
scalar = supportScalar({'tag': axisMax}, {'tag': tent})
|
|
|
|
|
|
|
|
out.append((scalar - gain, loc))
|
|
|
|
else:
|
|
|
|
loc1 = (crossing, upper, axisMax)
|
|
|
|
scalar1 = 0
|
|
|
|
|
|
|
|
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 05:57:43 -06:00
|
|
|
# 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.
|
2022-08-08 08:33:32 -06:00
|
|
|
elif axisDef + (axisMax - axisDef) * 2 >= upper:
|
2022-08-08 05:57:43 -06:00
|
|
|
|
|
|
|
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
|
|
|
|
|
2022-08-08 08:05:37 -06:00
|
|
|
if upper > axisDef:
|
2022-08-08 08:33:32 -06:00
|
|
|
out.append((1 - gain, (axisDef, peak, upper)))
|
2022-08-08 05:57:43 -06:00
|
|
|
|
|
|
|
# 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 = (axisDef, 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 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
|
|
|
|
|
|
|
|
# case 1neg: lower extends beyond axisMin: we chop.
|
|
|
|
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))
|
|
|
|
|
|
|
|
# case 2neg: lower is betwen axisMin and axisDef: we add two deltasets to
|
|
|
|
# keep it "up" all the way to end.
|
2022-08-08 07:55:42 -06:00
|
|
|
else:
|
2022-08-08 05:57:43 -06:00
|
|
|
loc1 = (axisMin, lower, axisDef)
|
|
|
|
scalar1 = 0
|
|
|
|
|
|
|
|
loc2 = (axisMin, axisMin, lower)
|
|
|
|
scalar2 = 0
|
|
|
|
|
|
|
|
out.append((scalar1 - gain, loc1))
|
|
|
|
out.append((scalar2 - gain, loc2))
|
|
|
|
|
|
|
|
return out
|
|
|
|
|
|
|
|
|
2022-08-08 07:55:42 -06:00
|
|
|
def _solveGeneral(tent, axisLimit):
|
|
|
|
axisMin, axisDef, axisMax = axisLimit
|
|
|
|
lower, peak, upper = tent
|
|
|
|
|
|
|
|
# Mirror the problem such that axisDef is always <= peak
|
|
|
|
if axisDef > peak:
|
2022-08-08 10:46:55 -06:00
|
|
|
return [(scalar, _revnegate(t) if t is not None else None)
|
2022-08-08 07:55:42 -06:00
|
|
|
for scalar,t
|
|
|
|
in _solveGeneral(_revnegate(tent),
|
|
|
|
_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': 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 _solveWithoutGain(tent, axisLimit)
|
|
|
|
else:
|
|
|
|
return _solveWithGain(tent, axisLimit)
|
|
|
|
|
|
|
|
raise NotImplementedError
|
|
|
|
|
|
|
|
|
2022-08-08 07:23:12 -06:00
|
|
|
@cache
|
2022-08-06 16:17:43 -06:00
|
|
|
def rebaseTent(tent, axisLimit):
|
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 07:55:42 -06:00
|
|
|
sols = _solveGeneral(tent, axisLimit)
|
2022-08-08 05:35:05 -06:00
|
|
|
n = lambda v: normalizeValue(v, axisLimit, extrapolate=True)
|
2022-08-08 10:46:55 -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 != 0]
|
2022-08-08 05:35:05 -06:00
|
|
|
return sols
|