xref: /aosp_15_r20/external/fonttools/Lib/fontTools/varLib/instancer/solver.py (revision e1fe3e4ad2793916b15cccdc4a7da52a7e1dd0e9)
1*e1fe3e4aSElliott Hughesfrom fontTools.varLib.models import supportScalar
2*e1fe3e4aSElliott Hughesfrom fontTools.misc.fixedTools import MAX_F2DOT14
3*e1fe3e4aSElliott Hughesfrom functools import lru_cache
4*e1fe3e4aSElliott Hughes
5*e1fe3e4aSElliott Hughes__all__ = ["rebaseTent"]
6*e1fe3e4aSElliott Hughes
7*e1fe3e4aSElliott HughesEPSILON = 1 / (1 << 14)
8*e1fe3e4aSElliott Hughes
9*e1fe3e4aSElliott Hughes
10*e1fe3e4aSElliott Hughesdef _reverse_negate(v):
11*e1fe3e4aSElliott Hughes    return (-v[2], -v[1], -v[0])
12*e1fe3e4aSElliott Hughes
13*e1fe3e4aSElliott Hughes
14*e1fe3e4aSElliott Hughesdef _solve(tent, axisLimit, negative=False):
15*e1fe3e4aSElliott Hughes    axisMin, axisDef, axisMax, _distanceNegative, _distancePositive = axisLimit
16*e1fe3e4aSElliott Hughes    lower, peak, upper = tent
17*e1fe3e4aSElliott Hughes
18*e1fe3e4aSElliott Hughes    # Mirror the problem such that axisDef <= peak
19*e1fe3e4aSElliott Hughes    if axisDef > peak:
20*e1fe3e4aSElliott Hughes        return [
21*e1fe3e4aSElliott Hughes            (scalar, _reverse_negate(t) if t is not None else None)
22*e1fe3e4aSElliott Hughes            for scalar, t in _solve(
23*e1fe3e4aSElliott Hughes                _reverse_negate(tent),
24*e1fe3e4aSElliott Hughes                axisLimit.reverse_negate(),
25*e1fe3e4aSElliott Hughes                not negative,
26*e1fe3e4aSElliott Hughes            )
27*e1fe3e4aSElliott Hughes        ]
28*e1fe3e4aSElliott Hughes    # axisDef <= peak
29*e1fe3e4aSElliott Hughes
30*e1fe3e4aSElliott Hughes    # case 1: The whole deltaset falls outside the new limit; we can drop it
31*e1fe3e4aSElliott Hughes    #
32*e1fe3e4aSElliott Hughes    #                                          peak
33*e1fe3e4aSElliott Hughes    #  1.........................................o..........
34*e1fe3e4aSElliott Hughes    #                                           / \
35*e1fe3e4aSElliott Hughes    #                                          /   \
36*e1fe3e4aSElliott Hughes    #                                         /     \
37*e1fe3e4aSElliott Hughes    #                                        /       \
38*e1fe3e4aSElliott Hughes    #  0---|-----------|----------|-------- o         o----1
39*e1fe3e4aSElliott Hughes    #    axisMin     axisDef    axisMax   lower     upper
40*e1fe3e4aSElliott Hughes    #
41*e1fe3e4aSElliott Hughes    if axisMax <= lower and axisMax < peak:
42*e1fe3e4aSElliott Hughes        return []  # No overlap
43*e1fe3e4aSElliott Hughes
44*e1fe3e4aSElliott Hughes    # case 2: Only the peak and outermost bound fall outside the new limit;
45*e1fe3e4aSElliott Hughes    # we keep the deltaset, update peak and outermost bound and and scale deltas
46*e1fe3e4aSElliott Hughes    # by the scalar value for the restricted axis at the new limit, and solve
47*e1fe3e4aSElliott Hughes    # recursively.
48*e1fe3e4aSElliott Hughes    #
49*e1fe3e4aSElliott Hughes    #                                  |peak
50*e1fe3e4aSElliott Hughes    #  1...............................|.o..........
51*e1fe3e4aSElliott Hughes    #                                  |/ \
52*e1fe3e4aSElliott Hughes    #                                  /   \
53*e1fe3e4aSElliott Hughes    #                                 /|    \
54*e1fe3e4aSElliott Hughes    #                                / |     \
55*e1fe3e4aSElliott Hughes    #  0--------------------------- o  |      o----1
56*e1fe3e4aSElliott Hughes    #                           lower  |      upper
57*e1fe3e4aSElliott Hughes    #                                  |
58*e1fe3e4aSElliott Hughes    #                                axisMax
59*e1fe3e4aSElliott Hughes    #
60*e1fe3e4aSElliott Hughes    # Convert to:
61*e1fe3e4aSElliott Hughes    #
62*e1fe3e4aSElliott Hughes    #  1............................................
63*e1fe3e4aSElliott Hughes    #                                  |
64*e1fe3e4aSElliott Hughes    #                                  o peak
65*e1fe3e4aSElliott Hughes    #                                 /|
66*e1fe3e4aSElliott Hughes    #                                /x|
67*e1fe3e4aSElliott Hughes    #  0--------------------------- o  o upper ----1
68*e1fe3e4aSElliott Hughes    #                           lower  |
69*e1fe3e4aSElliott Hughes    #                                  |
70*e1fe3e4aSElliott Hughes    #                                axisMax
71*e1fe3e4aSElliott Hughes    if axisMax < peak:
72*e1fe3e4aSElliott Hughes        mult = supportScalar({"tag": axisMax}, {"tag": tent})
73*e1fe3e4aSElliott Hughes        tent = (lower, axisMax, axisMax)
74*e1fe3e4aSElliott Hughes        return [(scalar * mult, t) for scalar, t in _solve(tent, axisLimit)]
75*e1fe3e4aSElliott Hughes
76*e1fe3e4aSElliott Hughes    # lower <= axisDef <= peak <= axisMax
77*e1fe3e4aSElliott Hughes
78*e1fe3e4aSElliott Hughes    gain = supportScalar({"tag": axisDef}, {"tag": tent})
79*e1fe3e4aSElliott Hughes    out = [(gain, None)]
80*e1fe3e4aSElliott Hughes
81*e1fe3e4aSElliott Hughes    # First, the positive side
82*e1fe3e4aSElliott Hughes
83*e1fe3e4aSElliott Hughes    # outGain is the scalar of axisMax at the tent.
84*e1fe3e4aSElliott Hughes    outGain = supportScalar({"tag": axisMax}, {"tag": tent})
85*e1fe3e4aSElliott Hughes
86*e1fe3e4aSElliott Hughes    # Case 3a: Gain is more than outGain. The tent down-slope crosses
87*e1fe3e4aSElliott Hughes    # the axis into negative. We have to split it into multiples.
88*e1fe3e4aSElliott Hughes    #
89*e1fe3e4aSElliott Hughes    #                      | peak  |
90*e1fe3e4aSElliott Hughes    #  1...................|.o.....|..............
91*e1fe3e4aSElliott Hughes    #                      |/x\_   |
92*e1fe3e4aSElliott Hughes    #  gain................+....+_.|..............
93*e1fe3e4aSElliott Hughes    #                     /|    |y\|
94*e1fe3e4aSElliott Hughes    #  ................../.|....|..+_......outGain
95*e1fe3e4aSElliott Hughes    #                   /  |    |  | \
96*e1fe3e4aSElliott Hughes    #  0---|-----------o   |    |  |  o----------1
97*e1fe3e4aSElliott Hughes    #    axisMin    lower  |    |  |   upper
98*e1fe3e4aSElliott Hughes    #                      |    |  |
99*e1fe3e4aSElliott Hughes    #                axisDef    |  axisMax
100*e1fe3e4aSElliott Hughes    #                           |
101*e1fe3e4aSElliott Hughes    #                      crossing
102*e1fe3e4aSElliott Hughes    if gain >= outGain:
103*e1fe3e4aSElliott Hughes        # Note that this is the branch taken if both gain and outGain are 0.
104*e1fe3e4aSElliott Hughes
105*e1fe3e4aSElliott Hughes        # Crossing point on the axis.
106*e1fe3e4aSElliott Hughes        crossing = peak + (1 - gain) * (upper - peak)
107*e1fe3e4aSElliott Hughes
108*e1fe3e4aSElliott Hughes        loc = (max(lower, axisDef), peak, crossing)
109*e1fe3e4aSElliott Hughes        scalar = 1
110*e1fe3e4aSElliott Hughes
111*e1fe3e4aSElliott Hughes        # The part before the crossing point.
112*e1fe3e4aSElliott Hughes        out.append((scalar - gain, loc))
113*e1fe3e4aSElliott Hughes
114*e1fe3e4aSElliott Hughes        # The part after the crossing point may use one or two tents,
115*e1fe3e4aSElliott Hughes        # depending on whether upper is before axisMax or not, in one
116*e1fe3e4aSElliott Hughes        # case we need to keep it down to eternity.
117*e1fe3e4aSElliott Hughes
118*e1fe3e4aSElliott Hughes        # Case 3a1, similar to case 1neg; just one tent needed, as in
119*e1fe3e4aSElliott Hughes        # the drawing above.
120*e1fe3e4aSElliott Hughes        if upper >= axisMax:
121*e1fe3e4aSElliott Hughes            loc = (crossing, axisMax, axisMax)
122*e1fe3e4aSElliott Hughes            scalar = outGain
123*e1fe3e4aSElliott Hughes
124*e1fe3e4aSElliott Hughes            out.append((scalar - gain, loc))
125*e1fe3e4aSElliott Hughes
126*e1fe3e4aSElliott Hughes        # Case 3a2: Similar to case 2neg; two tents needed, to keep
127*e1fe3e4aSElliott Hughes        # down to eternity.
128*e1fe3e4aSElliott Hughes        #
129*e1fe3e4aSElliott Hughes        #                      | peak             |
130*e1fe3e4aSElliott Hughes        #  1...................|.o................|...
131*e1fe3e4aSElliott Hughes        #                      |/ \_              |
132*e1fe3e4aSElliott Hughes        #  gain................+....+_............|...
133*e1fe3e4aSElliott Hughes        #                     /|    | \xxxxxxxxxxy|
134*e1fe3e4aSElliott Hughes        #                    / |    |  \_xxxxxyyyy|
135*e1fe3e4aSElliott Hughes        #                   /  |    |    \xxyyyyyy|
136*e1fe3e4aSElliott Hughes        #  0---|-----------o   |    |     o-------|--1
137*e1fe3e4aSElliott Hughes        #    axisMin    lower  |    |      upper  |
138*e1fe3e4aSElliott Hughes        #                      |    |             |
139*e1fe3e4aSElliott Hughes        #                axisDef    |             axisMax
140*e1fe3e4aSElliott Hughes        #                           |
141*e1fe3e4aSElliott Hughes        #                      crossing
142*e1fe3e4aSElliott Hughes        else:
143*e1fe3e4aSElliott Hughes            # A tent's peak cannot fall on axis default. Nudge it.
144*e1fe3e4aSElliott Hughes            if upper == axisDef:
145*e1fe3e4aSElliott Hughes                upper += EPSILON
146*e1fe3e4aSElliott Hughes
147*e1fe3e4aSElliott Hughes            # Downslope.
148*e1fe3e4aSElliott Hughes            loc1 = (crossing, upper, axisMax)
149*e1fe3e4aSElliott Hughes            scalar1 = 0
150*e1fe3e4aSElliott Hughes
151*e1fe3e4aSElliott Hughes            # Eternity justify.
152*e1fe3e4aSElliott Hughes            loc2 = (upper, axisMax, axisMax)
153*e1fe3e4aSElliott Hughes            scalar2 = 0
154*e1fe3e4aSElliott Hughes
155*e1fe3e4aSElliott Hughes            out.append((scalar1 - gain, loc1))
156*e1fe3e4aSElliott Hughes            out.append((scalar2 - gain, loc2))
157*e1fe3e4aSElliott Hughes
158*e1fe3e4aSElliott Hughes    else:
159*e1fe3e4aSElliott Hughes        # Special-case if peak is at axisMax.
160*e1fe3e4aSElliott Hughes        if axisMax == peak:
161*e1fe3e4aSElliott Hughes            upper = peak
162*e1fe3e4aSElliott Hughes
163*e1fe3e4aSElliott Hughes        # Case 3:
164*e1fe3e4aSElliott Hughes        # We keep delta as is and only scale the axis upper to achieve
165*e1fe3e4aSElliott Hughes        # the desired new tent if feasible.
166*e1fe3e4aSElliott Hughes        #
167*e1fe3e4aSElliott Hughes        #                        peak
168*e1fe3e4aSElliott Hughes        #  1.....................o....................
169*e1fe3e4aSElliott Hughes        #                       / \_|
170*e1fe3e4aSElliott Hughes        #  ..................../....+_.........outGain
171*e1fe3e4aSElliott Hughes        #                     /     | \
172*e1fe3e4aSElliott Hughes        #  gain..............+......|..+_.............
173*e1fe3e4aSElliott Hughes        #                   /|      |  | \
174*e1fe3e4aSElliott Hughes        #  0---|-----------o |      |  |  o----------1
175*e1fe3e4aSElliott Hughes        #    axisMin    lower|      |  |   upper
176*e1fe3e4aSElliott Hughes        #                    |      |  newUpper
177*e1fe3e4aSElliott Hughes        #              axisDef      axisMax
178*e1fe3e4aSElliott Hughes        #
179*e1fe3e4aSElliott Hughes        newUpper = peak + (1 - gain) * (upper - peak)
180*e1fe3e4aSElliott Hughes        assert axisMax <= newUpper  # Because outGain > gain
181*e1fe3e4aSElliott Hughes        # Disabled because ots doesn't like us:
182*e1fe3e4aSElliott Hughes        # https://github.com/fonttools/fonttools/issues/3350
183*e1fe3e4aSElliott Hughes        if False and newUpper <= axisDef + (axisMax - axisDef) * 2:
184*e1fe3e4aSElliott Hughes            upper = newUpper
185*e1fe3e4aSElliott Hughes            if not negative and axisDef + (axisMax - axisDef) * MAX_F2DOT14 < upper:
186*e1fe3e4aSElliott Hughes                # we clamp +2.0 to the max F2Dot14 (~1.99994) for convenience
187*e1fe3e4aSElliott Hughes                upper = axisDef + (axisMax - axisDef) * MAX_F2DOT14
188*e1fe3e4aSElliott Hughes                assert peak < upper
189*e1fe3e4aSElliott Hughes
190*e1fe3e4aSElliott Hughes            loc = (max(axisDef, lower), peak, upper)
191*e1fe3e4aSElliott Hughes            scalar = 1
192*e1fe3e4aSElliott Hughes
193*e1fe3e4aSElliott Hughes            out.append((scalar - gain, loc))
194*e1fe3e4aSElliott Hughes
195*e1fe3e4aSElliott Hughes        # Case 4: New limit doesn't fit; we need to chop into two tents,
196*e1fe3e4aSElliott Hughes        # because the shape of a triangle with part of one side cut off
197*e1fe3e4aSElliott Hughes        # cannot be represented as a triangle itself.
198*e1fe3e4aSElliott Hughes        #
199*e1fe3e4aSElliott Hughes        #            |   peak |
200*e1fe3e4aSElliott Hughes        #  1.........|......o.|....................
201*e1fe3e4aSElliott Hughes        #  ..........|...../x\|.............outGain
202*e1fe3e4aSElliott Hughes        #            |    |xxy|\_
203*e1fe3e4aSElliott Hughes        #            |   /xxxy|  \_
204*e1fe3e4aSElliott Hughes        #            |  |xxxxy|    \_
205*e1fe3e4aSElliott Hughes        #            |  /xxxxy|      \_
206*e1fe3e4aSElliott Hughes        #  0---|-----|-oxxxxxx|        o----------1
207*e1fe3e4aSElliott Hughes        #    axisMin | lower  |        upper
208*e1fe3e4aSElliott Hughes        #            |        |
209*e1fe3e4aSElliott Hughes        #          axisDef  axisMax
210*e1fe3e4aSElliott Hughes        #
211*e1fe3e4aSElliott Hughes        else:
212*e1fe3e4aSElliott Hughes            loc1 = (max(axisDef, lower), peak, axisMax)
213*e1fe3e4aSElliott Hughes            scalar1 = 1
214*e1fe3e4aSElliott Hughes
215*e1fe3e4aSElliott Hughes            loc2 = (peak, axisMax, axisMax)
216*e1fe3e4aSElliott Hughes            scalar2 = outGain
217*e1fe3e4aSElliott Hughes
218*e1fe3e4aSElliott Hughes            out.append((scalar1 - gain, loc1))
219*e1fe3e4aSElliott Hughes            # Don't add a dirac delta!
220*e1fe3e4aSElliott Hughes            if peak < axisMax:
221*e1fe3e4aSElliott Hughes                out.append((scalar2 - gain, loc2))
222*e1fe3e4aSElliott Hughes
223*e1fe3e4aSElliott Hughes    # Now, the negative side
224*e1fe3e4aSElliott Hughes
225*e1fe3e4aSElliott Hughes    # Case 1neg: Lower extends beyond axisMin: we chop. Simple.
226*e1fe3e4aSElliott Hughes    #
227*e1fe3e4aSElliott Hughes    #                     |   |peak
228*e1fe3e4aSElliott Hughes    #  1..................|...|.o.................
229*e1fe3e4aSElliott Hughes    #                     |   |/ \
230*e1fe3e4aSElliott Hughes    #  gain...............|...+...\...............
231*e1fe3e4aSElliott Hughes    #                     |x_/|    \
232*e1fe3e4aSElliott Hughes    #                     |/  |     \
233*e1fe3e4aSElliott Hughes    #                   _/|   |      \
234*e1fe3e4aSElliott Hughes    #  0---------------o  |   |       o----------1
235*e1fe3e4aSElliott Hughes    #              lower  |   |       upper
236*e1fe3e4aSElliott Hughes    #                     |   |
237*e1fe3e4aSElliott Hughes    #               axisMin   axisDef
238*e1fe3e4aSElliott Hughes    #
239*e1fe3e4aSElliott Hughes    if lower <= axisMin:
240*e1fe3e4aSElliott Hughes        loc = (axisMin, axisMin, axisDef)
241*e1fe3e4aSElliott Hughes        scalar = supportScalar({"tag": axisMin}, {"tag": tent})
242*e1fe3e4aSElliott Hughes
243*e1fe3e4aSElliott Hughes        out.append((scalar - gain, loc))
244*e1fe3e4aSElliott Hughes
245*e1fe3e4aSElliott Hughes    # Case 2neg: Lower is betwen axisMin and axisDef: we add two
246*e1fe3e4aSElliott Hughes    # tents to keep it down all the way to eternity.
247*e1fe3e4aSElliott Hughes    #
248*e1fe3e4aSElliott Hughes    #      |               |peak
249*e1fe3e4aSElliott Hughes    #  1...|...............|.o.................
250*e1fe3e4aSElliott Hughes    #      |               |/ \
251*e1fe3e4aSElliott Hughes    #  gain|...............+...\...............
252*e1fe3e4aSElliott Hughes    #      |yxxxxxxxxxxxxx/|    \
253*e1fe3e4aSElliott Hughes    #      |yyyyyyxxxxxxx/ |     \
254*e1fe3e4aSElliott Hughes    #      |yyyyyyyyyyyx/  |      \
255*e1fe3e4aSElliott Hughes    #  0---|-----------o   |       o----------1
256*e1fe3e4aSElliott Hughes    #    axisMin    lower  |       upper
257*e1fe3e4aSElliott Hughes    #                      |
258*e1fe3e4aSElliott Hughes    #                    axisDef
259*e1fe3e4aSElliott Hughes    #
260*e1fe3e4aSElliott Hughes    else:
261*e1fe3e4aSElliott Hughes        # A tent's peak cannot fall on axis default. Nudge it.
262*e1fe3e4aSElliott Hughes        if lower == axisDef:
263*e1fe3e4aSElliott Hughes            lower -= EPSILON
264*e1fe3e4aSElliott Hughes
265*e1fe3e4aSElliott Hughes        # Downslope.
266*e1fe3e4aSElliott Hughes        loc1 = (axisMin, lower, axisDef)
267*e1fe3e4aSElliott Hughes        scalar1 = 0
268*e1fe3e4aSElliott Hughes
269*e1fe3e4aSElliott Hughes        # Eternity justify.
270*e1fe3e4aSElliott Hughes        loc2 = (axisMin, axisMin, lower)
271*e1fe3e4aSElliott Hughes        scalar2 = 0
272*e1fe3e4aSElliott Hughes
273*e1fe3e4aSElliott Hughes        out.append((scalar1 - gain, loc1))
274*e1fe3e4aSElliott Hughes        out.append((scalar2 - gain, loc2))
275*e1fe3e4aSElliott Hughes
276*e1fe3e4aSElliott Hughes    return out
277*e1fe3e4aSElliott Hughes
278*e1fe3e4aSElliott Hughes
279*e1fe3e4aSElliott Hughes@lru_cache(128)
280*e1fe3e4aSElliott Hughesdef rebaseTent(tent, axisLimit):
281*e1fe3e4aSElliott Hughes    """Given a tuple (lower,peak,upper) "tent" and new axis limits
282*e1fe3e4aSElliott Hughes    (axisMin,axisDefault,axisMax), solves how to represent the tent
283*e1fe3e4aSElliott Hughes    under the new axis configuration.  All values are in normalized
284*e1fe3e4aSElliott Hughes    -1,0,+1 coordinate system. Tent values can be outside this range.
285*e1fe3e4aSElliott Hughes
286*e1fe3e4aSElliott Hughes    Return value is a list of tuples. Each tuple is of the form
287*e1fe3e4aSElliott Hughes    (scalar,tent), where scalar is a multipler to multiply any
288*e1fe3e4aSElliott Hughes    delta-sets by, and tent is a new tent for that output delta-set.
289*e1fe3e4aSElliott Hughes    If tent value is None, that is a special deltaset that should
290*e1fe3e4aSElliott Hughes    be always-enabled (called "gain")."""
291*e1fe3e4aSElliott Hughes
292*e1fe3e4aSElliott Hughes    axisMin, axisDef, axisMax, _distanceNegative, _distancePositive = axisLimit
293*e1fe3e4aSElliott Hughes    assert -1 <= axisMin <= axisDef <= axisMax <= +1
294*e1fe3e4aSElliott Hughes
295*e1fe3e4aSElliott Hughes    lower, peak, upper = tent
296*e1fe3e4aSElliott Hughes    assert -2 <= lower <= peak <= upper <= +2
297*e1fe3e4aSElliott Hughes
298*e1fe3e4aSElliott Hughes    assert peak != 0
299*e1fe3e4aSElliott Hughes
300*e1fe3e4aSElliott Hughes    sols = _solve(tent, axisLimit)
301*e1fe3e4aSElliott Hughes
302*e1fe3e4aSElliott Hughes    n = lambda v: axisLimit.renormalizeValue(v)
303*e1fe3e4aSElliott Hughes    sols = [
304*e1fe3e4aSElliott Hughes        (scalar, (n(v[0]), n(v[1]), n(v[2])) if v is not None else None)
305*e1fe3e4aSElliott Hughes        for scalar, v in sols
306*e1fe3e4aSElliott Hughes        if scalar
307*e1fe3e4aSElliott Hughes    ]
308*e1fe3e4aSElliott Hughes
309*e1fe3e4aSElliott Hughes    return sols
310