123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307 |
- from fontTools.varLib.models import supportScalar
- from fontTools.misc.fixedTools import MAX_F2DOT14
- from functools import lru_cache
- __all__ = ["rebaseTent"]
- EPSILON = 1 / (1 << 14)
- def _reverse_negate(v):
- return (-v[2], -v[1], -v[0])
- def _solve(tent, axisLimit, negative=False):
- axisMin, axisDef, axisMax, _distanceNegative, _distancePositive = axisLimit
- lower, peak, upper = tent
- # Mirror the problem such that axisDef <= peak
- if axisDef > peak:
- return [
- (scalar, _reverse_negate(t) if t is not None else None)
- for scalar, t in _solve(
- _reverse_negate(tent),
- axisLimit.reverse_negate(),
- not negative,
- )
- ]
- # axisDef <= peak
- # 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
- #
- 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, and solve
- # recursively.
- #
- # |peak
- # 1...............................|.o..........
- # |/ \
- # / \
- # /| \
- # / | \
- # 0--------------------------- o | o----1
- # lower | upper
- # |
- # axisMax
- #
- # Convert to:
- #
- # 1............................................
- # |
- # o peak
- # /|
- # /x|
- # 0--------------------------- o o upper ----1
- # lower |
- # |
- # axisMax
- if axisMax < peak:
- mult = supportScalar({"tag": axisMax}, {"tag": tent})
- tent = (lower, axisMax, axisMax)
- return [(scalar * mult, t) for scalar, t in _solve(tent, axisLimit)]
- # lower <= axisDef <= peak <= axisMax
- gain = supportScalar({"tag": axisDef}, {"tag": tent})
- out = [(gain, None)]
- # First, the positive side
- # outGain is the scalar of axisMax at the tent.
- outGain = supportScalar({"tag": axisMax}, {"tag": tent})
- # Case 3a: Gain is more than outGain. The tent down-slope crosses
- # the axis into negative. We have to split it into multiples.
- #
- # | peak |
- # 1...................|.o.....|..............
- # |/x\_ |
- # gain................+....+_.|..............
- # /| |y\|
- # ................../.|....|..+_......outGain
- # / | | | \
- # 0---|-----------o | | | o----------1
- # axisMin lower | | | upper
- # | | |
- # axisDef | axisMax
- # |
- # crossing
- if gain >= outGain:
- # Note that this is the branch taken if both gain and outGain are 0.
- # Crossing point on the axis.
- crossing = peak + (1 - gain) * (upper - peak)
- loc = (max(lower, axisDef), peak, crossing)
- scalar = 1
- # The part before the crossing point.
- out.append((scalar - gain, loc))
- # 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.
- # Case 3a1, similar to case 1neg; just one tent needed, as in
- # the drawing above.
- if upper >= axisMax:
- loc = (crossing, axisMax, axisMax)
- scalar = outGain
- out.append((scalar - gain, loc))
- # Case 3a2: Similar to case 2neg; two tents needed, to keep
- # down to eternity.
- #
- # | peak |
- # 1...................|.o................|...
- # |/ \_ |
- # gain................+....+_............|...
- # /| | \xxxxxxxxxxy|
- # / | | \_xxxxxyyyy|
- # / | | \xxyyyyyy|
- # 0---|-----------o | | o-------|--1
- # axisMin lower | | upper |
- # | | |
- # axisDef | axisMax
- # |
- # crossing
- else:
- # A tent's peak cannot fall on axis default. Nudge it.
- if upper == axisDef:
- upper += EPSILON
- # Downslope.
- loc1 = (crossing, upper, axisMax)
- scalar1 = 0
- # Eternity justify.
- loc2 = (upper, axisMax, axisMax)
- scalar2 = 0
- out.append((scalar1 - gain, loc1))
- out.append((scalar2 - gain, loc2))
- else:
- # Special-case if peak is at axisMax.
- if axisMax == peak:
- upper = peak
- # Case 3:
- # 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
- loc = (max(axisDef, lower), peak, upper)
- 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
- out.append((scalar1 - gain, loc1))
- # Don't add a dirac delta!
- if peak < axisMax:
- out.append((scalar2 - gain, loc2))
- # Now, the negative side
- # Case 1neg: Lower extends beyond axisMin: we chop. Simple.
- #
- # | |peak
- # 1..................|...|.o.................
- # | |/ \
- # gain...............|...+...\...............
- # |x_/| \
- # |/ | \
- # _/| | \
- # 0---------------o | | o----------1
- # lower | | upper
- # | |
- # axisMin axisDef
- #
- if lower <= axisMin:
- loc = (axisMin, axisMin, axisDef)
- scalar = supportScalar({"tag": axisMin}, {"tag": tent})
- out.append((scalar - gain, loc))
- # 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
- #
- else:
- # A tent's peak cannot fall on axis default. Nudge it.
- if lower == axisDef:
- lower -= EPSILON
- # Downslope.
- loc1 = (axisMin, lower, axisDef)
- scalar1 = 0
- # Eternity justify.
- loc2 = (axisMin, axisMin, lower)
- scalar2 = 0
- out.append((scalar1 - gain, loc1))
- out.append((scalar2 - gain, loc2))
- return out
- @lru_cache(128)
- def rebaseTent(tent, axisLimit):
- """Given a tuple (lower,peak,upper) "tent" and new axis limits
- (axisMin,axisDefault,axisMax), solves how to represent the tent
- under the new axis configuration. All values are in normalized
- -1,0,+1 coordinate system. Tent values can be outside this range.
- 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")."""
- axisMin, axisDef, axisMax, _distanceNegative, _distancePositive = axisLimit
- assert -1 <= axisMin <= axisDef <= axisMax <= +1
- lower, peak, upper = tent
- assert -2 <= lower <= peak <= upper <= +2
- assert peak != 0
- sols = _solve(tent, axisLimit)
- n = lambda v: axisLimit.renormalizeValue(v)
- 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
- ]
- return sols
|