instancer: never drop STAT DesignAxes; only prune out-of-range AxisValues
The current method for L1 and L2 partial instacing of STAT table -- i.e. drop all pinned axes are respective axis values -- was incorrect. STAT design axis are a superset of the fvar axes, they describe the relations between members of a font family in which some aspects may be implemented as variation axes within a single VF, others as multiple discrete fonts. When we remove an axis from fvar, we still want to keep the STAT's DesignAxis, as well as the single AxisValue table along that design axis which describes the position of the new instance within the family's stylistic attributes. This means, intantiateAvar will never drop any DesignAxis, but will only drops AxisValue tables when: 1) we're pinning an axis and the desired instance coordinate doesn't exactly equal any of the existing AxisValue records; 2) we're restricting an axis range, and the (nominal) AxisValue falls outside of the desired range. We never add new AxisValue records, as that's a design decision that is outside of the scope of the partial instancer.
This commit is contained in:
parent
b8500ac97c
commit
3c6ddb0ef8
@ -981,61 +981,57 @@ def instantiateFvar(varfont, axisLimits):
|
|||||||
def instantiateSTAT(varfont, axisLimits):
|
def instantiateSTAT(varfont, axisLimits):
|
||||||
# 'axisLimits' dict must contain user-space (non-normalized) coordinates
|
# 'axisLimits' dict must contain user-space (non-normalized) coordinates
|
||||||
|
|
||||||
# XXX do something with axisRanges
|
stat = varfont["STAT"].table
|
||||||
|
if not stat.DesignAxisRecord or not (
|
||||||
|
stat.AxisValueArray and stat.AxisValueArray.AxisValue
|
||||||
|
):
|
||||||
|
return # STAT table empty, nothing to do
|
||||||
|
|
||||||
location, axisRanges = splitAxisLocationAndRanges(axisLimits, rangeType=AxisRange)
|
location, axisRanges = splitAxisLocationAndRanges(axisLimits, rangeType=AxisRange)
|
||||||
|
|
||||||
pinnedAxes = set(location.keys())
|
def isAxisValueOutsideLimits(axisTag, axisValue):
|
||||||
|
if axisTag in location and axisValue != location[axisTag]:
|
||||||
stat = varfont["STAT"].table
|
return True
|
||||||
if not stat.DesignAxisRecord:
|
elif axisTag in axisRanges:
|
||||||
return # skip empty STAT table
|
axisRange = axisRanges[axisTag]
|
||||||
|
if axisValue < axisRange.minimum or axisValue > axisRange.maximum:
|
||||||
designAxes = stat.DesignAxisRecord.Axis
|
return True
|
||||||
pinnedAxisIndices = {
|
return False
|
||||||
i for i, axis in enumerate(designAxes) if axis.AxisTag in pinnedAxes
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(pinnedAxisIndices) == len(designAxes):
|
|
||||||
log.info("Dropping STAT table")
|
|
||||||
del varfont["STAT"]
|
|
||||||
return
|
|
||||||
|
|
||||||
log.info("Instantiating STAT table")
|
log.info("Instantiating STAT table")
|
||||||
|
|
||||||
# only keep DesignAxis that were not instanced, and build a mapping from old
|
# only keep AxisValues whose axis is not pinned nor restricted, or is pinned at the
|
||||||
# to new axis indices
|
# exact (nominal) value, or is restricted but the value is within the new range
|
||||||
newDesignAxes = []
|
designAxes = stat.DesignAxisRecord.Axis
|
||||||
axisIndexMap = {}
|
newAxisValueTables = []
|
||||||
for i, axis in enumerate(designAxes):
|
for axisValueTable in stat.AxisValueArray.AxisValue:
|
||||||
if i not in pinnedAxisIndices:
|
axisValueFormat = axisValueTable.Format
|
||||||
axisIndexMap[i] = len(newDesignAxes)
|
if axisValueFormat in (1, 2, 3):
|
||||||
newDesignAxes.append(axis)
|
axisTag = designAxes[axisValueTable.AxisIndex].AxisTag
|
||||||
|
if axisValueFormat == 2:
|
||||||
if stat.AxisValueArray and stat.AxisValueArray.AxisValue:
|
axisValue = axisValueTable.NominalValue
|
||||||
# drop all AxisValue tables that reference any of the pinned axes
|
|
||||||
newAxisValueTables = []
|
|
||||||
for axisValueTable in stat.AxisValueArray.AxisValue:
|
|
||||||
if axisValueTable.Format in (1, 2, 3):
|
|
||||||
if axisValueTable.AxisIndex in pinnedAxisIndices:
|
|
||||||
continue
|
|
||||||
axisValueTable.AxisIndex = axisIndexMap[axisValueTable.AxisIndex]
|
|
||||||
newAxisValueTables.append(axisValueTable)
|
|
||||||
elif axisValueTable.Format == 4:
|
|
||||||
if any(
|
|
||||||
rec.AxisIndex in pinnedAxisIndices
|
|
||||||
for rec in axisValueTable.AxisValueRecord
|
|
||||||
):
|
|
||||||
continue
|
|
||||||
for rec in axisValueTable.AxisValueRecord:
|
|
||||||
rec.AxisIndex = axisIndexMap[rec.AxisIndex]
|
|
||||||
newAxisValueTables.append(axisValueTable)
|
|
||||||
else:
|
else:
|
||||||
raise NotImplementedError(axisValueTable.Format)
|
axisValue = axisValueTable.Value
|
||||||
stat.AxisValueArray.AxisValue = newAxisValueTables
|
if isAxisValueOutsideLimits(axisTag, axisValue):
|
||||||
stat.AxisValueCount = len(stat.AxisValueArray.AxisValue)
|
continue
|
||||||
|
elif axisValueFormat == 4:
|
||||||
|
# drop 'non-analytic' AxisValue if _any_ AxisValueRecord doesn't match
|
||||||
|
# the pinned location or is outside range
|
||||||
|
dropAxisValueTable = False
|
||||||
|
for rec in axisValueTable.AxisValueRecord:
|
||||||
|
axisTag = designAxes[rec.AxisIndex].AxisTag
|
||||||
|
axisValue = rec.Value
|
||||||
|
if isAxisValueOutsideLimits(axisTag, axisValue):
|
||||||
|
dropAxisValueTable = True
|
||||||
|
break
|
||||||
|
if dropAxisValueTable:
|
||||||
|
continue
|
||||||
|
else:
|
||||||
|
log.warn("Unknown AxisValue table format (%s); ignored", axisValueFormat)
|
||||||
|
newAxisValueTables.append(axisValueTable)
|
||||||
|
|
||||||
stat.DesignAxisRecord.Axis[:] = newDesignAxes
|
stat.AxisValueArray.AxisValue = newAxisValueTables
|
||||||
stat.DesignAxisCount = len(stat.DesignAxisRecord.Axis)
|
stat.AxisValueCount = len(stat.AxisValueArray.AxisValue)
|
||||||
|
|
||||||
|
|
||||||
def getVariationNameIDs(varfont):
|
def getVariationNameIDs(varfont):
|
||||||
|
Loading…
x
Reference in New Issue
Block a user