You cannot select more than 25 topics
			Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
		
		
		
		
		
			
		
			
				
	
	
		
			191 lines
		
	
	
		
			6.9 KiB
		
	
	
	
		
			Python
		
	
			
		
		
	
	
			191 lines
		
	
	
		
			6.9 KiB
		
	
	
	
		
			Python
		
	
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()
 |