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