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