diff --git a/Lib/fontTools/varLib/instancer/__init__.py b/Lib/fontTools/varLib/instancer/__init__.py index cb154bda0..025318d44 100644 --- a/Lib/fontTools/varLib/instancer/__init__.py +++ b/Lib/fontTools/varLib/instancer/__init__.py @@ -239,7 +239,23 @@ def changeTupleVariationAxisLimit(var, axisTag, axisLimit): if axisTag not in var.axes: return [var] - return solver.changeTupleVariationAxisLimit(var, axisTag, axisLimit) + tent = var.axes[axisTag] + + solutions = solver.rebaseTent(tent, axisLimit) + + out = [] + # TODO Reuse original var + for scalar,tent in solutions: + if scalar == 0: continue + newVar = TupleVariation(var.axes, var.coordinates) + newVar.axes.pop(axisTag) + if tent[1] != 0: + newVar.axes[axisTag] == tent + if scalar != 1: + newVar.scaleDeltas(scalar) + out.append(newVar) + + return out def _instantiateGvarGlyph( glyphname, glyf, gvar, hMetrics, vMetrics, axisLimits, optimize=True diff --git a/Lib/fontTools/varLib/instancer/solver.py b/Lib/fontTools/varLib/instancer/solver.py index 1cd5d59e7..1a8453dde 100644 --- a/Lib/fontTools/varLib/instancer/solver.py +++ b/Lib/fontTools/varLib/instancer/solver.py @@ -1,32 +1,31 @@ from fontTools.varLib.models import supportScalar -def _solvePinned(var, axisTag, axisLimit): +def _solvePinned(tent, axisLimit): axisMin, axisDef, axisMax = axisLimit + assert axisMin == axisDef == axisMax - support = {axisTag: var.axes.pop(axisTag)} - scalar = supportScalar({axisTag: axisLimit.default}, support) + support = {'tag': tent} + scalar = supportScalar({'tag': axisDef}, support) if scalar == 0.0: return [] - if scalar != 1.0: - var.scaleDeltas(scalar) - return [var] + return [(scalar, (-1, 0, +1))] -def _solveDefaultUnmoved(var, axisTag, axisLimit): +def _solveDefaultUnmoved(tent, axisLimit): axisMin, axisDef, axisMax = axisLimit - lower, peak, upper = var.axes.get(axisTag) + lower, peak, upper = tent negative = lower < 0 if negative: if axisMin == -1.0: - return [var] + return [(1, tent)] elif axisMin == 0.0: return [] else: if axisMax == 1.0: - return [var] + return [(1, tent)] elif axisMax == 0.0: return [] @@ -44,8 +43,8 @@ def _solveDefaultUnmoved(var, axisTag, axisLimit): # special case when innermost bound == peak == limit if newLower == newPeak == 1.0: - var.axes[axisTag] = (-1.0, -1.0, -1.0) if negative else (1.0, 1.0, 1.0) - return [var] + loc = (-1.0, -1.0, -1.0) if negative else (1.0, 1.0, 1.0) + return [(1, loc)] # case 1: the whole deltaset falls outside the new limit; we can drop it elif newLower >= 1.0: @@ -55,14 +54,13 @@ def _solveDefaultUnmoved(var, axisTag, axisLimit): # 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. elif newPeak >= 1.0: - scalar = supportScalar({axisTag: limit}, {axisTag: (lower, peak, upper)}) - var.scaleDeltas(scalar) + scalar = supportScalar({'tag': limit}, {'tag': (lower, peak, upper)}) newPeak = 1.0 newUpper = 1.0 if negative: newLower, newPeak, newUpper = _negate(newUpper, newPeak, newLower) - var.axes[axisTag] = (newLower, newPeak, newUpper) - return [var] + loc = (newLower, newPeak, newUpper) + return [(scalar, loc)] # case 3: peak falls inside but outermost limit still fits within F2Dot14 bounds; # we keep deltas as is and only scale the axes bounds. Deltas beyond -1.0 @@ -73,8 +71,8 @@ def _solveDefaultUnmoved(var, axisTag, axisLimit): elif MAX_F2DOT14 < newUpper <= 2.0: # we clamp +2.0 to the max F2Dot14 (~1.99994) for convenience newUpper = MAX_F2DOT14 - var.axes[axisTag] = (newLower, newPeak, newUpper) - return [var] + loc = (newLower, newPeak, newUpper) + return [(1, loc)] # 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 @@ -84,39 +82,40 @@ def _solveDefaultUnmoved(var, axisTag, axisLimit): # duplicate the tent, then adjust lower/peak/upper so that the outermost limit # of the original tent is +/-2.0, whereas the new tent's starts as the old # one peaks and maxes out at +/-1.0. - newVar = TupleVariation(var.axes, var.coordinates) if negative: - var.axes[axisTag] = (-2.0, -1 * newPeak, -1 * newLower) - newVar.axes[axisTag] = (-1.0, -1.0, -1 * newPeak) + loc = (-2.0, -1 * newPeak, -1 * newLower) + newloc = (-1.0, -1.0, -1 * newPeak) else: - var.axes[axisTag] = (newLower, newPeak, MAX_F2DOT14) - newVar.axes[axisTag] = (newPeak, 1.0, 1.0) + loc = (newLower, newPeak, MAX_F2DOT14) + newloc = (newPeak, 1.0, 1.0) # the new tent's deltas are scaled by the difference between the scalar value # for the old tent at the desired limit... - scalar1 = supportScalar({axisTag: limit}, {axisTag: (lower, peak, upper)}) + scalar1 = supportScalar({tag: limit}, {tag: (lower, peak, upper)}) # ... and the scalar value for the clamped tent (with outer limit +/-2.0), # which can be simplified like this: scalar2 = 1 / (2 - newPeak) - newVar.scaleDeltas(scalar1 - scalar2) - return [var, newVar] + return [(scalar1, loc), (scalar2, newloc)] -def _solveDefaultUnmoved(var, axisTag, axisLimit): +def _solveDefaultUnmoved(tent, axisLimit): raise NotImplementedError -def changeTupleVariationAxisLimit(var, axisTag, axisLimit): +def rebaseTent(tent, axisLimit): axisMin, axisDef, axisMax = axisLimit assert -1 <= axisMin <= axisDef <= axisMax <= +1 + lower, peak, upper = tent + assert -2 <= lower <= peak <= upper <= +2 + # Get the pinned case out of the way if axisMin == axisMax: - return _solvePinned(var, axisTag, axisLimit) + return _solvePinned(tent, axisLimit) # If default isn't moving, get that out of the way as well if axisDef == 0: - return _solveDefaultUnmoved(var, axisTag, axisLimit) + return _solveDefaultUnmoved(tent, axisLimit) - return _solveGeneral(var, axisTag, axisLimit) + return _solveGeneral(tent, axisLimit)