123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190 |
- 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()
|