|
from fontTools.ttLib.tables import otTables as ot |
|
from copy import deepcopy |
|
import logging |
|
|
|
|
|
log = logging.getLogger("fontTools.varLib.instancer") |
|
|
|
|
|
def _featureVariationRecordIsUnique(rec, seen): |
|
conditionSet = [] |
|
conditionSets = ( |
|
rec.ConditionSet.ConditionTable if rec.ConditionSet is not None else [] |
|
) |
|
for cond in conditionSets: |
|
if cond.Format != 1: |
|
|
|
return True |
|
conditionSet.append( |
|
(cond.AxisIndex, cond.FilterRangeMinValue, cond.FilterRangeMaxValue) |
|
) |
|
|
|
|
|
|
|
|
|
|
|
recordKey = frozenset([rec.FeatureTableSubstitution.Version] + conditionSet) |
|
if recordKey in seen: |
|
return False |
|
else: |
|
seen.add(recordKey) |
|
return True |
|
|
|
|
|
def _limitFeatureVariationConditionRange(condition, axisLimit): |
|
minValue = condition.FilterRangeMinValue |
|
maxValue = condition.FilterRangeMaxValue |
|
|
|
if ( |
|
minValue > maxValue |
|
or minValue > axisLimit.maximum |
|
or maxValue < axisLimit.minimum |
|
): |
|
|
|
return |
|
|
|
return tuple( |
|
axisLimit.renormalizeValue(v, extrapolate=False) for v in (minValue, maxValue) |
|
) |
|
|
|
|
|
def _instantiateFeatureVariationRecord( |
|
record, recIdx, axisLimits, fvarAxes, axisIndexMap |
|
): |
|
applies = True |
|
shouldKeep = False |
|
newConditions = [] |
|
from fontTools.varLib.instancer import NormalizedAxisTripleAndDistances |
|
|
|
default_triple = NormalizedAxisTripleAndDistances(-1, 0, +1) |
|
if record.ConditionSet is None: |
|
record.ConditionSet = ot.ConditionSet() |
|
record.ConditionSet.ConditionTable = [] |
|
record.ConditionSet.ConditionCount = 0 |
|
for i, condition in enumerate(record.ConditionSet.ConditionTable): |
|
if condition.Format == 1: |
|
axisIdx = condition.AxisIndex |
|
axisTag = fvarAxes[axisIdx].axisTag |
|
|
|
minValue = condition.FilterRangeMinValue |
|
maxValue = condition.FilterRangeMaxValue |
|
triple = axisLimits.get(axisTag, default_triple) |
|
|
|
if not (minValue <= triple.default <= maxValue): |
|
applies = False |
|
|
|
|
|
if triple.minimum > maxValue or triple.maximum < minValue: |
|
newConditions = None |
|
break |
|
|
|
if axisTag in axisIndexMap: |
|
|
|
condition.AxisIndex = axisIndexMap[axisTag] |
|
|
|
|
|
newRange = _limitFeatureVariationConditionRange(condition, triple) |
|
if newRange: |
|
|
|
minimum, maximum = newRange |
|
condition.FilterRangeMinValue = minimum |
|
condition.FilterRangeMaxValue = maximum |
|
shouldKeep = True |
|
if minimum != -1 or maximum != +1: |
|
newConditions.append(condition) |
|
else: |
|
|
|
newConditions = None |
|
break |
|
|
|
else: |
|
log.warning( |
|
"Condition table {0} of FeatureVariationRecord {1} has " |
|
"unsupported format ({2}); ignored".format(i, recIdx, condition.Format) |
|
) |
|
applies = False |
|
newConditions.append(condition) |
|
|
|
if newConditions is not None and shouldKeep: |
|
record.ConditionSet.ConditionTable = newConditions |
|
if not newConditions: |
|
record.ConditionSet = None |
|
shouldKeep = True |
|
else: |
|
shouldKeep = False |
|
|
|
|
|
universal = shouldKeep and not newConditions |
|
|
|
return applies, shouldKeep, universal |
|
|
|
|
|
def _instantiateFeatureVariations(table, fvarAxes, axisLimits): |
|
pinnedAxes = set(axisLimits.pinnedLocation()) |
|
axisOrder = [axis.axisTag for axis in fvarAxes if axis.axisTag not in pinnedAxes] |
|
axisIndexMap = {axisTag: axisOrder.index(axisTag) for axisTag in axisOrder} |
|
|
|
featureVariationApplied = False |
|
uniqueRecords = set() |
|
newRecords = [] |
|
defaultsSubsts = None |
|
|
|
for i, record in enumerate(table.FeatureVariations.FeatureVariationRecord): |
|
applies, shouldKeep, universal = _instantiateFeatureVariationRecord( |
|
record, i, axisLimits, fvarAxes, axisIndexMap |
|
) |
|
|
|
if shouldKeep and _featureVariationRecordIsUnique(record, uniqueRecords): |
|
newRecords.append(record) |
|
|
|
if applies and not featureVariationApplied: |
|
assert record.FeatureTableSubstitution.Version == 0x00010000 |
|
defaultsSubsts = deepcopy(record.FeatureTableSubstitution) |
|
for default, rec in zip( |
|
defaultsSubsts.SubstitutionRecord, |
|
record.FeatureTableSubstitution.SubstitutionRecord, |
|
): |
|
default.Feature = deepcopy( |
|
table.FeatureList.FeatureRecord[rec.FeatureIndex].Feature |
|
) |
|
table.FeatureList.FeatureRecord[rec.FeatureIndex].Feature = deepcopy( |
|
rec.Feature |
|
) |
|
|
|
featureVariationApplied = True |
|
|
|
|
|
if universal: |
|
break |
|
|
|
|
|
if featureVariationApplied and newRecords and not universal: |
|
defaultRecord = ot.FeatureVariationRecord() |
|
defaultRecord.ConditionSet = ot.ConditionSet() |
|
defaultRecord.ConditionSet.ConditionTable = [] |
|
defaultRecord.ConditionSet.ConditionCount = 0 |
|
defaultRecord.FeatureTableSubstitution = defaultsSubsts |
|
|
|
newRecords.append(defaultRecord) |
|
|
|
if newRecords: |
|
table.FeatureVariations.FeatureVariationRecord = newRecords |
|
table.FeatureVariations.FeatureVariationCount = len(newRecords) |
|
else: |
|
del table.FeatureVariations |
|
|
|
table.Version = 0x00010000 |
|
|
|
|
|
def instantiateFeatureVariations(varfont, axisLimits): |
|
for tableTag in ("GPOS", "GSUB"): |
|
if tableTag not in varfont or not getattr( |
|
varfont[tableTag].table, "FeatureVariations", None |
|
): |
|
continue |
|
log.info("Instantiating FeatureVariations of %s table", tableTag) |
|
_instantiateFeatureVariations( |
|
varfont[tableTag].table, varfont["fvar"].axes, axisLimits |
|
) |
|
|
|
varfont[tableTag].prune_lookups() |
|
|