Spaces:
Paused
Paused
| 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: | |
| # can't tell whether this is duplicate, assume is unique | |
| return True | |
| conditionSet.append( | |
| (cond.AxisIndex, cond.FilterRangeMinValue, cond.FilterRangeMaxValue) | |
| ) | |
| # besides the set of conditions, we also include the FeatureTableSubstitution | |
| # version to identify unique FeatureVariationRecords, even though only one | |
| # version is currently defined. It's theoretically possible that multiple | |
| # records with same conditions but different substitution table version be | |
| # present in the same font for backward compatibility. | |
| recordKey = frozenset([rec.FeatureTableSubstitution.Version] + conditionSet) | |
| if recordKey in seen: | |
| return False | |
| else: | |
| seen.add(recordKey) # side effect | |
| return True | |
| def _limitFeatureVariationConditionRange(condition, axisLimit): | |
| minValue = condition.FilterRangeMinValue | |
| maxValue = condition.FilterRangeMaxValue | |
| if ( | |
| minValue > maxValue | |
| or minValue > axisLimit.maximum | |
| or maxValue < axisLimit.minimum | |
| ): | |
| # condition invalid or out of range | |
| 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 condition not met, remove entire record | |
| if triple.minimum > maxValue or triple.maximum < minValue: | |
| newConditions = None | |
| break | |
| if axisTag in axisIndexMap: | |
| # remap axis index | |
| condition.AxisIndex = axisIndexMap[axisTag] | |
| # remap condition limits | |
| newRange = _limitFeatureVariationConditionRange(condition, triple) | |
| if newRange: | |
| # keep condition with updated limits | |
| minimum, maximum = newRange | |
| condition.FilterRangeMinValue = minimum | |
| condition.FilterRangeMaxValue = maximum | |
| shouldKeep = True | |
| if minimum != -1 or maximum != +1: | |
| newConditions.append(condition) | |
| else: | |
| # condition out of range, remove entire record | |
| 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 | |
| # Does this *always* apply? | |
| 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 | |
| ) | |
| # Set variations only once | |
| featureVariationApplied = True | |
| # Further records don't have a chance to apply after a universal record | |
| if universal: | |
| break | |
| # Insert a catch-all record to reinstate the old features if necessary | |
| 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 | |
| # downgrade table version if there are no FeatureVariations left | |
| 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 | |
| ) | |
| # remove unreferenced lookups | |
| varfont[tableTag].prune_lookups() | |