xref: /aosp_15_r20/external/fonttools/Lib/fontTools/varLib/instancer/featureVars.py (revision e1fe3e4ad2793916b15cccdc4a7da52a7e1dd0e9)
1*e1fe3e4aSElliott Hughesfrom fontTools.ttLib.tables import otTables as ot
2*e1fe3e4aSElliott Hughesfrom copy import deepcopy
3*e1fe3e4aSElliott Hughesimport logging
4*e1fe3e4aSElliott Hughes
5*e1fe3e4aSElliott Hughes
6*e1fe3e4aSElliott Hugheslog = logging.getLogger("fontTools.varLib.instancer")
7*e1fe3e4aSElliott Hughes
8*e1fe3e4aSElliott Hughes
9*e1fe3e4aSElliott Hughesdef _featureVariationRecordIsUnique(rec, seen):
10*e1fe3e4aSElliott Hughes    conditionSet = []
11*e1fe3e4aSElliott Hughes    conditionSets = (
12*e1fe3e4aSElliott Hughes        rec.ConditionSet.ConditionTable if rec.ConditionSet is not None else []
13*e1fe3e4aSElliott Hughes    )
14*e1fe3e4aSElliott Hughes    for cond in conditionSets:
15*e1fe3e4aSElliott Hughes        if cond.Format != 1:
16*e1fe3e4aSElliott Hughes            # can't tell whether this is duplicate, assume is unique
17*e1fe3e4aSElliott Hughes            return True
18*e1fe3e4aSElliott Hughes        conditionSet.append(
19*e1fe3e4aSElliott Hughes            (cond.AxisIndex, cond.FilterRangeMinValue, cond.FilterRangeMaxValue)
20*e1fe3e4aSElliott Hughes        )
21*e1fe3e4aSElliott Hughes    # besides the set of conditions, we also include the FeatureTableSubstitution
22*e1fe3e4aSElliott Hughes    # version to identify unique FeatureVariationRecords, even though only one
23*e1fe3e4aSElliott Hughes    # version is currently defined. It's theoretically possible that multiple
24*e1fe3e4aSElliott Hughes    # records with same conditions but different substitution table version be
25*e1fe3e4aSElliott Hughes    # present in the same font for backward compatibility.
26*e1fe3e4aSElliott Hughes    recordKey = frozenset([rec.FeatureTableSubstitution.Version] + conditionSet)
27*e1fe3e4aSElliott Hughes    if recordKey in seen:
28*e1fe3e4aSElliott Hughes        return False
29*e1fe3e4aSElliott Hughes    else:
30*e1fe3e4aSElliott Hughes        seen.add(recordKey)  # side effect
31*e1fe3e4aSElliott Hughes        return True
32*e1fe3e4aSElliott Hughes
33*e1fe3e4aSElliott Hughes
34*e1fe3e4aSElliott Hughesdef _limitFeatureVariationConditionRange(condition, axisLimit):
35*e1fe3e4aSElliott Hughes    minValue = condition.FilterRangeMinValue
36*e1fe3e4aSElliott Hughes    maxValue = condition.FilterRangeMaxValue
37*e1fe3e4aSElliott Hughes
38*e1fe3e4aSElliott Hughes    if (
39*e1fe3e4aSElliott Hughes        minValue > maxValue
40*e1fe3e4aSElliott Hughes        or minValue > axisLimit.maximum
41*e1fe3e4aSElliott Hughes        or maxValue < axisLimit.minimum
42*e1fe3e4aSElliott Hughes    ):
43*e1fe3e4aSElliott Hughes        # condition invalid or out of range
44*e1fe3e4aSElliott Hughes        return
45*e1fe3e4aSElliott Hughes
46*e1fe3e4aSElliott Hughes    return tuple(
47*e1fe3e4aSElliott Hughes        axisLimit.renormalizeValue(v, extrapolate=False) for v in (minValue, maxValue)
48*e1fe3e4aSElliott Hughes    )
49*e1fe3e4aSElliott Hughes
50*e1fe3e4aSElliott Hughes
51*e1fe3e4aSElliott Hughesdef _instantiateFeatureVariationRecord(
52*e1fe3e4aSElliott Hughes    record, recIdx, axisLimits, fvarAxes, axisIndexMap
53*e1fe3e4aSElliott Hughes):
54*e1fe3e4aSElliott Hughes    applies = True
55*e1fe3e4aSElliott Hughes    shouldKeep = False
56*e1fe3e4aSElliott Hughes    newConditions = []
57*e1fe3e4aSElliott Hughes    from fontTools.varLib.instancer import NormalizedAxisTripleAndDistances
58*e1fe3e4aSElliott Hughes
59*e1fe3e4aSElliott Hughes    default_triple = NormalizedAxisTripleAndDistances(-1, 0, +1)
60*e1fe3e4aSElliott Hughes    if record.ConditionSet is None:
61*e1fe3e4aSElliott Hughes        record.ConditionSet = ot.ConditionSet()
62*e1fe3e4aSElliott Hughes        record.ConditionSet.ConditionTable = []
63*e1fe3e4aSElliott Hughes        record.ConditionSet.ConditionCount = 0
64*e1fe3e4aSElliott Hughes    for i, condition in enumerate(record.ConditionSet.ConditionTable):
65*e1fe3e4aSElliott Hughes        if condition.Format == 1:
66*e1fe3e4aSElliott Hughes            axisIdx = condition.AxisIndex
67*e1fe3e4aSElliott Hughes            axisTag = fvarAxes[axisIdx].axisTag
68*e1fe3e4aSElliott Hughes
69*e1fe3e4aSElliott Hughes            minValue = condition.FilterRangeMinValue
70*e1fe3e4aSElliott Hughes            maxValue = condition.FilterRangeMaxValue
71*e1fe3e4aSElliott Hughes            triple = axisLimits.get(axisTag, default_triple)
72*e1fe3e4aSElliott Hughes
73*e1fe3e4aSElliott Hughes            if not (minValue <= triple.default <= maxValue):
74*e1fe3e4aSElliott Hughes                applies = False
75*e1fe3e4aSElliott Hughes
76*e1fe3e4aSElliott Hughes            # if condition not met, remove entire record
77*e1fe3e4aSElliott Hughes            if triple.minimum > maxValue or triple.maximum < minValue:
78*e1fe3e4aSElliott Hughes                newConditions = None
79*e1fe3e4aSElliott Hughes                break
80*e1fe3e4aSElliott Hughes
81*e1fe3e4aSElliott Hughes            if axisTag in axisIndexMap:
82*e1fe3e4aSElliott Hughes                # remap axis index
83*e1fe3e4aSElliott Hughes                condition.AxisIndex = axisIndexMap[axisTag]
84*e1fe3e4aSElliott Hughes
85*e1fe3e4aSElliott Hughes                # remap condition limits
86*e1fe3e4aSElliott Hughes                newRange = _limitFeatureVariationConditionRange(condition, triple)
87*e1fe3e4aSElliott Hughes                if newRange:
88*e1fe3e4aSElliott Hughes                    # keep condition with updated limits
89*e1fe3e4aSElliott Hughes                    minimum, maximum = newRange
90*e1fe3e4aSElliott Hughes                    condition.FilterRangeMinValue = minimum
91*e1fe3e4aSElliott Hughes                    condition.FilterRangeMaxValue = maximum
92*e1fe3e4aSElliott Hughes                    shouldKeep = True
93*e1fe3e4aSElliott Hughes                    if minimum != -1 or maximum != +1:
94*e1fe3e4aSElliott Hughes                        newConditions.append(condition)
95*e1fe3e4aSElliott Hughes                else:
96*e1fe3e4aSElliott Hughes                    # condition out of range, remove entire record
97*e1fe3e4aSElliott Hughes                    newConditions = None
98*e1fe3e4aSElliott Hughes                    break
99*e1fe3e4aSElliott Hughes
100*e1fe3e4aSElliott Hughes        else:
101*e1fe3e4aSElliott Hughes            log.warning(
102*e1fe3e4aSElliott Hughes                "Condition table {0} of FeatureVariationRecord {1} has "
103*e1fe3e4aSElliott Hughes                "unsupported format ({2}); ignored".format(i, recIdx, condition.Format)
104*e1fe3e4aSElliott Hughes            )
105*e1fe3e4aSElliott Hughes            applies = False
106*e1fe3e4aSElliott Hughes            newConditions.append(condition)
107*e1fe3e4aSElliott Hughes
108*e1fe3e4aSElliott Hughes    if newConditions is not None and shouldKeep:
109*e1fe3e4aSElliott Hughes        record.ConditionSet.ConditionTable = newConditions
110*e1fe3e4aSElliott Hughes        if not newConditions:
111*e1fe3e4aSElliott Hughes            record.ConditionSet = None
112*e1fe3e4aSElliott Hughes        shouldKeep = True
113*e1fe3e4aSElliott Hughes    else:
114*e1fe3e4aSElliott Hughes        shouldKeep = False
115*e1fe3e4aSElliott Hughes
116*e1fe3e4aSElliott Hughes    # Does this *always* apply?
117*e1fe3e4aSElliott Hughes    universal = shouldKeep and not newConditions
118*e1fe3e4aSElliott Hughes
119*e1fe3e4aSElliott Hughes    return applies, shouldKeep, universal
120*e1fe3e4aSElliott Hughes
121*e1fe3e4aSElliott Hughes
122*e1fe3e4aSElliott Hughesdef _instantiateFeatureVariations(table, fvarAxes, axisLimits):
123*e1fe3e4aSElliott Hughes    pinnedAxes = set(axisLimits.pinnedLocation())
124*e1fe3e4aSElliott Hughes    axisOrder = [axis.axisTag for axis in fvarAxes if axis.axisTag not in pinnedAxes]
125*e1fe3e4aSElliott Hughes    axisIndexMap = {axisTag: axisOrder.index(axisTag) for axisTag in axisOrder}
126*e1fe3e4aSElliott Hughes
127*e1fe3e4aSElliott Hughes    featureVariationApplied = False
128*e1fe3e4aSElliott Hughes    uniqueRecords = set()
129*e1fe3e4aSElliott Hughes    newRecords = []
130*e1fe3e4aSElliott Hughes    defaultsSubsts = None
131*e1fe3e4aSElliott Hughes
132*e1fe3e4aSElliott Hughes    for i, record in enumerate(table.FeatureVariations.FeatureVariationRecord):
133*e1fe3e4aSElliott Hughes        applies, shouldKeep, universal = _instantiateFeatureVariationRecord(
134*e1fe3e4aSElliott Hughes            record, i, axisLimits, fvarAxes, axisIndexMap
135*e1fe3e4aSElliott Hughes        )
136*e1fe3e4aSElliott Hughes
137*e1fe3e4aSElliott Hughes        if shouldKeep and _featureVariationRecordIsUnique(record, uniqueRecords):
138*e1fe3e4aSElliott Hughes            newRecords.append(record)
139*e1fe3e4aSElliott Hughes
140*e1fe3e4aSElliott Hughes        if applies and not featureVariationApplied:
141*e1fe3e4aSElliott Hughes            assert record.FeatureTableSubstitution.Version == 0x00010000
142*e1fe3e4aSElliott Hughes            defaultsSubsts = deepcopy(record.FeatureTableSubstitution)
143*e1fe3e4aSElliott Hughes            for default, rec in zip(
144*e1fe3e4aSElliott Hughes                defaultsSubsts.SubstitutionRecord,
145*e1fe3e4aSElliott Hughes                record.FeatureTableSubstitution.SubstitutionRecord,
146*e1fe3e4aSElliott Hughes            ):
147*e1fe3e4aSElliott Hughes                default.Feature = deepcopy(
148*e1fe3e4aSElliott Hughes                    table.FeatureList.FeatureRecord[rec.FeatureIndex].Feature
149*e1fe3e4aSElliott Hughes                )
150*e1fe3e4aSElliott Hughes                table.FeatureList.FeatureRecord[rec.FeatureIndex].Feature = deepcopy(
151*e1fe3e4aSElliott Hughes                    rec.Feature
152*e1fe3e4aSElliott Hughes                )
153*e1fe3e4aSElliott Hughes            # Set variations only once
154*e1fe3e4aSElliott Hughes            featureVariationApplied = True
155*e1fe3e4aSElliott Hughes
156*e1fe3e4aSElliott Hughes        # Further records don't have a chance to apply after a universal record
157*e1fe3e4aSElliott Hughes        if universal:
158*e1fe3e4aSElliott Hughes            break
159*e1fe3e4aSElliott Hughes
160*e1fe3e4aSElliott Hughes    # Insert a catch-all record to reinstate the old features if necessary
161*e1fe3e4aSElliott Hughes    if featureVariationApplied and newRecords and not universal:
162*e1fe3e4aSElliott Hughes        defaultRecord = ot.FeatureVariationRecord()
163*e1fe3e4aSElliott Hughes        defaultRecord.ConditionSet = ot.ConditionSet()
164*e1fe3e4aSElliott Hughes        defaultRecord.ConditionSet.ConditionTable = []
165*e1fe3e4aSElliott Hughes        defaultRecord.ConditionSet.ConditionCount = 0
166*e1fe3e4aSElliott Hughes        defaultRecord.FeatureTableSubstitution = defaultsSubsts
167*e1fe3e4aSElliott Hughes
168*e1fe3e4aSElliott Hughes        newRecords.append(defaultRecord)
169*e1fe3e4aSElliott Hughes
170*e1fe3e4aSElliott Hughes    if newRecords:
171*e1fe3e4aSElliott Hughes        table.FeatureVariations.FeatureVariationRecord = newRecords
172*e1fe3e4aSElliott Hughes        table.FeatureVariations.FeatureVariationCount = len(newRecords)
173*e1fe3e4aSElliott Hughes    else:
174*e1fe3e4aSElliott Hughes        del table.FeatureVariations
175*e1fe3e4aSElliott Hughes        # downgrade table version if there are no FeatureVariations left
176*e1fe3e4aSElliott Hughes        table.Version = 0x00010000
177*e1fe3e4aSElliott Hughes
178*e1fe3e4aSElliott Hughes
179*e1fe3e4aSElliott Hughesdef instantiateFeatureVariations(varfont, axisLimits):
180*e1fe3e4aSElliott Hughes    for tableTag in ("GPOS", "GSUB"):
181*e1fe3e4aSElliott Hughes        if tableTag not in varfont or not getattr(
182*e1fe3e4aSElliott Hughes            varfont[tableTag].table, "FeatureVariations", None
183*e1fe3e4aSElliott Hughes        ):
184*e1fe3e4aSElliott Hughes            continue
185*e1fe3e4aSElliott Hughes        log.info("Instantiating FeatureVariations of %s table", tableTag)
186*e1fe3e4aSElliott Hughes        _instantiateFeatureVariations(
187*e1fe3e4aSElliott Hughes            varfont[tableTag].table, varfont["fvar"].axes, axisLimits
188*e1fe3e4aSElliott Hughes        )
189*e1fe3e4aSElliott Hughes        # remove unreferenced lookups
190*e1fe3e4aSElliott Hughes        varfont[tableTag].prune_lookups()
191