Spaces:
Sleeping
Sleeping
File size: 7,300 Bytes
7d134e4 |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 |
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()
|