From 10bc7a804aa8e840da89a8a09dc12bbef79288e6 Mon Sep 17 00:00:00 2001 From: Behdad Esfahbod Date: Wed, 21 Jun 2023 17:45:19 -0600 Subject: [PATCH] [instancer/L4] Implement an optimization --- Lib/fontTools/varLib/instancer/solver.py | 155 +++++++++++++---------- Tests/varLib/instancer/solver_test.py | 3 +- 2 files changed, 92 insertions(+), 66 deletions(-) diff --git a/Lib/fontTools/varLib/instancer/solver.py b/Lib/fontTools/varLib/instancer/solver.py index 272c66645..1b0405fef 100644 --- a/Lib/fontTools/varLib/instancer/solver.py +++ b/Lib/fontTools/varLib/instancer/solver.py @@ -153,77 +153,104 @@ def _solve(tent, axisLimit, negative=False): out.append((scalar1 - gain, loc1)) out.append((scalar2 - gain, loc2)) - # 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. - # - # 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. - # - # | peak | - # 1.........|............o...|.................. - # | /x\ | - # | /xxx\ | - # | /xxxxx\| - # | /xxxxxxx+ - # | /xxxxxxxx|\ - # 0---|-----|------oxxxxxxxxx|xo---------------1 - # axisMin | lower | upper - # | | - # axisDef axisMax - # - elif axisDef + (axisMax - axisDef) * 2 >= upper: - 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 - + else: # Special-case if peak is at axisMax. if axisMax == peak: upper = peak - loc1 = (max(axisDef, lower), peak, upper) - scalar1 = 1 + # Case pre3: + # we keep deltas as is and only scale the axis upper to achieve + # the desired new tent if feasible. + # + # | peak | + # 1.........|............o...|.................. + # | /x\ | + # | /xxx\ | + # | /xxxxx\| + # | /xxxxxxx+ + # | /xxxxxxxx|\ + # 0---|-----|------oxxxxxxxxx|xo---------------1 + # axisMin | lower | upper + # | | + # axisDef axisMax + # + newUpper = peak + (1 - gain) * (upper - peak) + if axisMax <= newUpper and 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 - loc2 = (peak, upper, upper) - scalar2 = 0 + loc = (max(axisDef, lower), peak, upper) + scalar = 1 + + out.append((scalar - gain, loc)) + + # Case 3: Outermost limit still fits within F2Dot14 bounds; + # We keep axis bound as is. Deltas beyond -1.0 or +1.0 will never be + # applied as implementations must clamp to that range. + # + # 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. + # + # | peak | + # 1.........|............o...|.................. + # | /x\ | + # | /xxx\ | + # | /xxxxx\| + # | /xxxxxxx+ + # | /xxxxxxxx|\ + # 0---|-----|------oxxxxxxxxx|xo---------------1 + # axisMin | lower | upper + # | | + # axisDef axisMax + # + elif axisDef + (axisMax - axisDef) * 2 >= upper: + 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 + + loc1 = (max(axisDef, lower), peak, upper) + scalar1 = 1 + + loc2 = (peak, upper, upper) + scalar2 = 0 + + # Don't add a dirac delta! + if axisDef < upper: + out.append((scalar1 - gain, loc1)) + if peak < upper: + out.append((scalar2 - gain, loc2)) + + # 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 - # Don't add a dirac delta! - if axisDef < upper: out.append((scalar1 - gain, loc1)) - if peak < upper: - out.append((scalar2 - gain, loc2)) - - # 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)) + # Don't add a dirac delta! + if peak < axisMax: + out.append((scalar2 - gain, loc2)) # Now, the negative side diff --git a/Tests/varLib/instancer/solver_test.py b/Tests/varLib/instancer/solver_test.py index 2740a10b0..abd2ff563 100644 --- a/Tests/varLib/instancer/solver_test.py +++ b/Tests/varLib/instancer/solver_test.py @@ -137,8 +137,7 @@ class RebaseTentTest(object): (0.25, 0.25, 0.75), [ (0.5, None), - (0.5, (0, 0.5, 1.5)), - (-0.5, (0.5, 1.5, 1.5)), + (0.5, (0, 0.5, 1.0)), ], ), # Case 1neg