Blame Lib/fontTools/varLib/featureVars.py

rpm-build 6a2e4c
"""Module to build FeatureVariation tables:
rpm-build 6a2e4c
https://docs.microsoft.com/en-us/typography/opentype/spec/chapter2#featurevariations-table
rpm-build 6a2e4c
rpm-build 6a2e4c
NOTE: The API is experimental and subject to change.
rpm-build 6a2e4c
"""
rpm-build 6a2e4c
from __future__ import print_function, absolute_import, division
rpm-build 6a2e4c
rpm-build 6a2e4c
from fontTools.ttLib import newTable
rpm-build 6a2e4c
from fontTools.ttLib.tables import otTables as ot
rpm-build 6a2e4c
from fontTools.otlLib.builder import buildLookup, buildSingleSubstSubtable
rpm-build 6a2e4c
import itertools
rpm-build 6a2e4c
rpm-build 6a2e4c
rpm-build 6a2e4c
def addFeatureVariations(font, conditionalSubstitutions):
rpm-build 6a2e4c
    """Add conditional substitutions to a Variable Font.
rpm-build 6a2e4c
rpm-build 6a2e4c
    The `conditionalSubstitutions` argument is a list of (Region, Substitutions)
rpm-build 6a2e4c
    tuples.
rpm-build 6a2e4c
rpm-build 6a2e4c
    A Region is a list of Spaces. A Space is a dict mapping axisTags to
rpm-build 6a2e4c
    (minValue, maxValue) tuples. Irrelevant axes may be omitted.
rpm-build 6a2e4c
    A Space represents a 'rectangular' subset of an N-dimensional design space.
rpm-build 6a2e4c
    A Region represents a more complex subset of an N-dimensional design space,
rpm-build 6a2e4c
    ie. the union of all the Spaces in the Region.
rpm-build 6a2e4c
    For efficiency, Spaces within a Region should ideally not overlap, but
rpm-build 6a2e4c
    functionality is not compromised if they do.
rpm-build 6a2e4c
rpm-build 6a2e4c
    The minimum and maximum values are expressed in normalized coordinates.
rpm-build 6a2e4c
rpm-build 6a2e4c
    A Substitution is a dict mapping source glyph names to substitute glyph names.
rpm-build 6a2e4c
    """
rpm-build 6a2e4c
rpm-build 6a2e4c
    # Example:
rpm-build 6a2e4c
    #
rpm-build 6a2e4c
    #     >>> f = TTFont(srcPath)
rpm-build 6a2e4c
    #     >>> condSubst = [
rpm-build 6a2e4c
    #     ...     # A list of (Region, Substitution) tuples.
rpm-build 6a2e4c
    #     ...     ([{"wght": (0.5, 1.0)}], {"dollar": "dollar.rvrn"}),
rpm-build 6a2e4c
    #     ...     ([{"wdth": (0.5, 1.0)}], {"cent": "cent.rvrn"}),
rpm-build 6a2e4c
    #     ... ]
rpm-build 6a2e4c
    #     >>> addFeatureVariations(f, condSubst)
rpm-build 6a2e4c
    #     >>> f.save(dstPath)
rpm-build 6a2e4c
rpm-build 6a2e4c
    # Since the FeatureVariations table will only ever match one rule at a time,
rpm-build 6a2e4c
    # we will make new rules for all possible combinations of our input, so we
rpm-build 6a2e4c
    # can indirectly support overlapping rules.
rpm-build 6a2e4c
    explodedConditionalSubstitutions = []
rpm-build 6a2e4c
    for combination in iterAllCombinations(len(conditionalSubstitutions)):
rpm-build 6a2e4c
        regions = []
rpm-build 6a2e4c
        lookups = []
rpm-build 6a2e4c
        for index in combination:
rpm-build 6a2e4c
            regions.append(conditionalSubstitutions[index][0])
rpm-build 6a2e4c
            lookups.append(conditionalSubstitutions[index][1])
rpm-build 6a2e4c
        if not regions:
rpm-build 6a2e4c
            continue
rpm-build 6a2e4c
        intersection = regions[0]
rpm-build 6a2e4c
        for region in regions[1:]:
rpm-build 6a2e4c
            intersection = intersectRegions(intersection, region)
rpm-build 6a2e4c
        for space in intersection:
rpm-build 6a2e4c
            # Remove default values, so we don't generate redundant ConditionSets
rpm-build 6a2e4c
            space = cleanupSpace(space)
rpm-build 6a2e4c
            if space:
rpm-build 6a2e4c
                explodedConditionalSubstitutions.append((space, lookups))
rpm-build 6a2e4c
rpm-build 6a2e4c
    addFeatureVariationsRaw(font, explodedConditionalSubstitutions)
rpm-build 6a2e4c
rpm-build 6a2e4c
rpm-build 6a2e4c
def iterAllCombinations(numRules):
rpm-build 6a2e4c
    """Given a number of rules, yield all the combinations of indices, sorted
rpm-build 6a2e4c
    by decreasing length, so we get the most specialized rules first.
rpm-build 6a2e4c
rpm-build 6a2e4c
        >>> list(iterAllCombinations(0))
rpm-build 6a2e4c
        []
rpm-build 6a2e4c
        >>> list(iterAllCombinations(1))
rpm-build 6a2e4c
        [(0,)]
rpm-build 6a2e4c
        >>> list(iterAllCombinations(2))
rpm-build 6a2e4c
        [(0, 1), (0,), (1,)]
rpm-build 6a2e4c
        >>> list(iterAllCombinations(3))
rpm-build 6a2e4c
        [(0, 1, 2), (0, 1), (0, 2), (1, 2), (0,), (1,), (2,)]
rpm-build 6a2e4c
    """
rpm-build 6a2e4c
    indices = range(numRules)
rpm-build 6a2e4c
    for length in range(numRules, 0, -1):
rpm-build 6a2e4c
        for combinations in itertools.combinations(indices, length):
rpm-build 6a2e4c
            yield combinations
rpm-build 6a2e4c
rpm-build 6a2e4c
rpm-build 6a2e4c
#
rpm-build 6a2e4c
# Region and Space support
rpm-build 6a2e4c
#
rpm-build 6a2e4c
# Terminology:
rpm-build 6a2e4c
#
rpm-build 6a2e4c
# A 'Space' is a dict representing a "rectangular" bit of N-dimensional space.
rpm-build 6a2e4c
# The keys in the dict are axis tags, the values are (minValue, maxValue) tuples.
rpm-build 6a2e4c
# Missing dimensions (keys) are substituted by the default min and max values
rpm-build 6a2e4c
# from the corresponding axes.
rpm-build 6a2e4c
#
rpm-build 6a2e4c
# A 'Region' is a list of Space dicts, representing the union of the Spaces,
rpm-build 6a2e4c
# therefore representing a more complex subset of design space.
rpm-build 6a2e4c
#
rpm-build 6a2e4c
rpm-build 6a2e4c
def intersectRegions(region1, region2):
rpm-build 6a2e4c
    """Return the region intersecting `region1` and `region2`.
rpm-build 6a2e4c
rpm-build 6a2e4c
        >>> intersectRegions([], [])
rpm-build 6a2e4c
        []
rpm-build 6a2e4c
        >>> intersectRegions([{'wdth': (0.0, 1.0)}], [])
rpm-build 6a2e4c
        []
rpm-build 6a2e4c
        >>> expected = [{'wdth': (0.0, 1.0), 'wght': (-1.0, 0.0)}]
rpm-build 6a2e4c
        >>> expected == intersectRegions([{'wdth': (0.0, 1.0)}], [{'wght': (-1.0, 0.0)}])
rpm-build 6a2e4c
        True
rpm-build 6a2e4c
        >>> expected = [{'wdth': (0.0, 1.0), 'wght': (-0.5, 0.0)}]
rpm-build 6a2e4c
        >>> expected == intersectRegions([{'wdth': (0.0, 1.0), 'wght': (-0.5, 0.5)}], [{'wght': (-1.0, 0.0)}])
rpm-build 6a2e4c
        True
rpm-build 6a2e4c
        >>> intersectRegions(
rpm-build 6a2e4c
        ...     [{'wdth': (0.0, 1.0), 'wght': (-0.5, 0.5)}],
rpm-build 6a2e4c
        ...     [{'wdth': (-1.0, 0.0), 'wght': (-1.0, 0.0)}])
rpm-build 6a2e4c
        []
rpm-build 6a2e4c
rpm-build 6a2e4c
    """
rpm-build 6a2e4c
    region = []
rpm-build 6a2e4c
    for space1 in region1:
rpm-build 6a2e4c
        for space2 in region2:
rpm-build 6a2e4c
            space = intersectSpaces(space1, space2)
rpm-build 6a2e4c
            if space is not None:
rpm-build 6a2e4c
                region.append(space)
rpm-build 6a2e4c
    return region
rpm-build 6a2e4c
rpm-build 6a2e4c
rpm-build 6a2e4c
def intersectSpaces(space1, space2):
rpm-build 6a2e4c
    """Return the space intersected by `space1` and `space2`, or None if there
rpm-build 6a2e4c
    is no intersection.
rpm-build 6a2e4c
rpm-build 6a2e4c
        >>> intersectSpaces({}, {})
rpm-build 6a2e4c
        {}
rpm-build 6a2e4c
        >>> intersectSpaces({'wdth': (-0.5, 0.5)}, {})
rpm-build 6a2e4c
        {'wdth': (-0.5, 0.5)}
rpm-build 6a2e4c
        >>> intersectSpaces({'wdth': (-0.5, 0.5)}, {'wdth': (0.0, 1.0)})
rpm-build 6a2e4c
        {'wdth': (0.0, 0.5)}
rpm-build 6a2e4c
        >>> expected = {'wdth': (0.0, 0.5), 'wght': (0.25, 0.5)}
rpm-build 6a2e4c
        >>> expected == intersectSpaces({'wdth': (-0.5, 0.5), 'wght': (0.0, 0.5)}, {'wdth': (0.0, 1.0), 'wght': (0.25, 0.75)})
rpm-build 6a2e4c
        True
rpm-build 6a2e4c
        >>> expected = {'wdth': (-0.5, 0.5), 'wght': (0.0, 1.0)}
rpm-build 6a2e4c
        >>> expected == intersectSpaces({'wdth': (-0.5, 0.5)}, {'wght': (0.0, 1.0)})
rpm-build 6a2e4c
        True
rpm-build 6a2e4c
        >>> intersectSpaces({'wdth': (-0.5, 0)}, {'wdth': (0.1, 0.5)})
rpm-build 6a2e4c
rpm-build 6a2e4c
    """
rpm-build 6a2e4c
    space = {}
rpm-build 6a2e4c
    space.update(space1)
rpm-build 6a2e4c
    space.update(space2)
rpm-build 6a2e4c
    for axisTag in set(space1) & set(space2):
rpm-build 6a2e4c
        min1, max1 = space1[axisTag]
rpm-build 6a2e4c
        min2, max2 = space2[axisTag]
rpm-build 6a2e4c
        minimum = max(min1, min2)
rpm-build 6a2e4c
        maximum = min(max1, max2)
rpm-build 6a2e4c
        if not minimum < maximum:
rpm-build 6a2e4c
            return None
rpm-build 6a2e4c
        space[axisTag] = minimum, maximum
rpm-build 6a2e4c
    return space
rpm-build 6a2e4c
rpm-build 6a2e4c
rpm-build 6a2e4c
def cleanupSpace(space):
rpm-build 6a2e4c
    """Return a sparse copy of `space`, without redundant (default) values.
rpm-build 6a2e4c
rpm-build 6a2e4c
        >>> cleanupSpace({})
rpm-build 6a2e4c
        {}
rpm-build 6a2e4c
        >>> cleanupSpace({'wdth': (0.0, 1.0)})
rpm-build 6a2e4c
        {'wdth': (0.0, 1.0)}
rpm-build 6a2e4c
        >>> cleanupSpace({'wdth': (-1.0, 1.0)})
rpm-build 6a2e4c
        {}
rpm-build 6a2e4c
rpm-build 6a2e4c
    """
rpm-build 6a2e4c
    return {tag: limit for tag, limit in space.items() if limit != (-1.0, 1.0)}
rpm-build 6a2e4c
rpm-build 6a2e4c
rpm-build 6a2e4c
#
rpm-build 6a2e4c
# Low level implementation
rpm-build 6a2e4c
#
rpm-build 6a2e4c
rpm-build 6a2e4c
def addFeatureVariationsRaw(font, conditionalSubstitutions):
rpm-build 6a2e4c
    """Low level implementation of addFeatureVariations that directly
rpm-build 6a2e4c
    models the possibilities of the FeatureVariations table."""
rpm-build 6a2e4c
rpm-build 6a2e4c
    #
rpm-build 6a2e4c
    # assert there is no 'rvrn' feature
rpm-build 6a2e4c
    # make dummy 'rvrn' feature with no lookups
rpm-build 6a2e4c
    # sort features, get 'rvrn' feature index
rpm-build 6a2e4c
    # add 'rvrn' feature to all scripts
rpm-build 6a2e4c
    # make lookups
rpm-build 6a2e4c
    # add feature variations
rpm-build 6a2e4c
    #
rpm-build 6a2e4c
rpm-build 6a2e4c
    if "GSUB" not in font:
rpm-build 6a2e4c
        font["GSUB"] = buildGSUB()
rpm-build 6a2e4c
rpm-build 6a2e4c
    gsub = font["GSUB"].table
rpm-build 6a2e4c
rpm-build 6a2e4c
    if gsub.Version < 0x00010001:
rpm-build 6a2e4c
        gsub.Version = 0x00010001  # allow gsub.FeatureVariations
rpm-build 6a2e4c
rpm-build 6a2e4c
    gsub.FeatureVariations = None  # delete any existing FeatureVariations
rpm-build 6a2e4c
rpm-build 6a2e4c
    for feature in gsub.FeatureList.FeatureRecord:
rpm-build 6a2e4c
        assert feature.FeatureTag != 'rvrn'
rpm-build 6a2e4c
rpm-build 6a2e4c
    rvrnFeature = buildFeatureRecord('rvrn', [])
rpm-build 6a2e4c
    gsub.FeatureList.FeatureRecord.append(rvrnFeature)
rpm-build 6a2e4c
rpm-build 6a2e4c
    sortFeatureList(gsub)
rpm-build 6a2e4c
    rvrnFeatureIndex = gsub.FeatureList.FeatureRecord.index(rvrnFeature)
rpm-build 6a2e4c
rpm-build 6a2e4c
    for scriptRecord in gsub.ScriptList.ScriptRecord:
rpm-build 6a2e4c
        for langSys in [scriptRecord.Script.DefaultLangSys] + scriptRecord.Script.LangSysRecord:
rpm-build 6a2e4c
            langSys.FeatureIndex.append(rvrnFeatureIndex)
rpm-build 6a2e4c
rpm-build 6a2e4c
    # setup lookups
rpm-build 6a2e4c
rpm-build 6a2e4c
    # turn substitution dicts into tuples of tuples, so they are hashable
rpm-build 6a2e4c
    conditionalSubstitutions, allSubstitutions = makeSubstitutionsHashable(conditionalSubstitutions)
rpm-build 6a2e4c
rpm-build 6a2e4c
    lookupMap = buildSubstitutionLookups(gsub, allSubstitutions)
rpm-build 6a2e4c
rpm-build 6a2e4c
    axisIndices = {axis.axisTag: axisIndex for axisIndex, axis in enumerate(font["fvar"].axes)}
rpm-build 6a2e4c
rpm-build 6a2e4c
    featureVariationRecords = []
rpm-build 6a2e4c
    for conditionSet, substitutions in conditionalSubstitutions:
rpm-build 6a2e4c
        conditionTable = []
rpm-build 6a2e4c
        for axisTag, (minValue, maxValue) in sorted(conditionSet.items()):
rpm-build 6a2e4c
            assert minValue < maxValue
rpm-build 6a2e4c
            ct = buildConditionTable(axisIndices[axisTag], minValue, maxValue)
rpm-build 6a2e4c
            conditionTable.append(ct)
rpm-build 6a2e4c
rpm-build 6a2e4c
        lookupIndices = [lookupMap[subst] for subst in substitutions]
rpm-build 6a2e4c
        record = buildFeatureTableSubstitutionRecord(rvrnFeatureIndex, lookupIndices)
rpm-build 6a2e4c
        featureVariationRecords.append(buildFeatureVariationRecord(conditionTable, [record]))
rpm-build 6a2e4c
rpm-build 6a2e4c
    gsub.FeatureVariations = buildFeatureVariations(featureVariationRecords)
rpm-build 6a2e4c
rpm-build 6a2e4c
rpm-build 6a2e4c
#
rpm-build 6a2e4c
# Building GSUB/FeatureVariations internals
rpm-build 6a2e4c
#
rpm-build 6a2e4c
rpm-build 6a2e4c
def buildGSUB():
rpm-build 6a2e4c
    """Build a GSUB table from scratch."""
rpm-build 6a2e4c
    fontTable = newTable("GSUB")
rpm-build 6a2e4c
    gsub = fontTable.table = ot.GSUB()
rpm-build 6a2e4c
    gsub.Version = 0x00010001  # allow gsub.FeatureVariations
rpm-build 6a2e4c
rpm-build 6a2e4c
    gsub.ScriptList = ot.ScriptList()
rpm-build 6a2e4c
    gsub.ScriptList.ScriptRecord = []
rpm-build 6a2e4c
    gsub.FeatureList = ot.FeatureList()
rpm-build 6a2e4c
    gsub.FeatureList.FeatureRecord = []
rpm-build 6a2e4c
    gsub.LookupList = ot.LookupList()
rpm-build 6a2e4c
    gsub.LookupList.Lookup = []
rpm-build 6a2e4c
rpm-build 6a2e4c
    srec = ot.ScriptRecord()
rpm-build 6a2e4c
    srec.ScriptTag = 'DFLT'
rpm-build 6a2e4c
    srec.Script = ot.Script()
rpm-build 6a2e4c
    srec.Script.DefaultLangSys = None
rpm-build 6a2e4c
    srec.Script.LangSysRecord = []
rpm-build 6a2e4c
rpm-build 6a2e4c
    langrec = ot.LangSysRecord()
rpm-build 6a2e4c
    langrec.LangSys = ot.LangSys()
rpm-build 6a2e4c
    langrec.LangSys.ReqFeatureIndex = 0xFFFF
rpm-build 6a2e4c
    langrec.LangSys.FeatureIndex = [0]
rpm-build 6a2e4c
    srec.Script.DefaultLangSys = langrec.LangSys
rpm-build 6a2e4c
rpm-build 6a2e4c
    gsub.ScriptList.ScriptRecord.append(srec)
rpm-build 6a2e4c
    gsub.FeatureVariations = None
rpm-build 6a2e4c
rpm-build 6a2e4c
    return fontTable
rpm-build 6a2e4c
rpm-build 6a2e4c
rpm-build 6a2e4c
def makeSubstitutionsHashable(conditionalSubstitutions):
rpm-build 6a2e4c
    """Turn all the substitution dictionaries in sorted tuples of tuples so
rpm-build 6a2e4c
    they are hashable, to detect duplicates so we don't write out redundant
rpm-build 6a2e4c
    data."""
rpm-build 6a2e4c
    allSubstitutions = set()
rpm-build 6a2e4c
    condSubst = []
rpm-build 6a2e4c
    for conditionSet, substitutionMaps in conditionalSubstitutions:
rpm-build 6a2e4c
        substitutions = []
rpm-build 6a2e4c
        for substitutionMap in substitutionMaps:
rpm-build 6a2e4c
            subst = tuple(sorted(substitutionMap.items()))
rpm-build 6a2e4c
            substitutions.append(subst)
rpm-build 6a2e4c
            allSubstitutions.add(subst)
rpm-build 6a2e4c
        condSubst.append((conditionSet, substitutions))
rpm-build 6a2e4c
    return condSubst, sorted(allSubstitutions)
rpm-build 6a2e4c
rpm-build 6a2e4c
rpm-build 6a2e4c
def buildSubstitutionLookups(gsub, allSubstitutions):
rpm-build 6a2e4c
    """Build the lookups for the glyph substitutions, return a dict mapping
rpm-build 6a2e4c
    the substitution to lookup indices."""
rpm-build 6a2e4c
    firstIndex = len(gsub.LookupList.Lookup)
rpm-build 6a2e4c
    lookupMap = {}
rpm-build 6a2e4c
    for i, substitutionMap in enumerate(allSubstitutions):
rpm-build 6a2e4c
        lookupMap[substitutionMap] = i + firstIndex
rpm-build 6a2e4c
rpm-build 6a2e4c
    for subst in allSubstitutions:
rpm-build 6a2e4c
        substMap = dict(subst)
rpm-build 6a2e4c
        lookup = buildLookup([buildSingleSubstSubtable(substMap)])
rpm-build 6a2e4c
        gsub.LookupList.Lookup.append(lookup)
rpm-build 6a2e4c
        assert gsub.LookupList.Lookup[lookupMap[subst]] is lookup
rpm-build 6a2e4c
    return lookupMap
rpm-build 6a2e4c
rpm-build 6a2e4c
rpm-build 6a2e4c
def buildFeatureVariations(featureVariationRecords):
rpm-build 6a2e4c
    """Build the FeatureVariations subtable."""
rpm-build 6a2e4c
    fv = ot.FeatureVariations()
rpm-build 6a2e4c
    fv.Version = 0x00010000
rpm-build 6a2e4c
    fv.FeatureVariationRecord = featureVariationRecords
rpm-build 6a2e4c
    return fv
rpm-build 6a2e4c
rpm-build 6a2e4c
rpm-build 6a2e4c
def buildFeatureRecord(featureTag, lookupListIndices):
rpm-build 6a2e4c
    """Build a FeatureRecord."""
rpm-build 6a2e4c
    fr = ot.FeatureRecord()
rpm-build 6a2e4c
    fr.FeatureTag = featureTag
rpm-build 6a2e4c
    fr.Feature = ot.Feature()
rpm-build 6a2e4c
    fr.Feature.LookupListIndex = lookupListIndices
rpm-build 6a2e4c
    return fr
rpm-build 6a2e4c
rpm-build 6a2e4c
rpm-build 6a2e4c
def buildFeatureVariationRecord(conditionTable, substitutionRecords):
rpm-build 6a2e4c
    """Build a FeatureVariationRecord."""
rpm-build 6a2e4c
    fvr = ot.FeatureVariationRecord()
rpm-build 6a2e4c
    fvr.ConditionSet = ot.ConditionSet()
rpm-build 6a2e4c
    fvr.ConditionSet.ConditionTable = conditionTable
rpm-build 6a2e4c
    fvr.FeatureTableSubstitution = ot.FeatureTableSubstitution()
rpm-build 6a2e4c
    fvr.FeatureTableSubstitution.Version = 0x00010001
rpm-build 6a2e4c
    fvr.FeatureTableSubstitution.SubstitutionRecord = substitutionRecords
rpm-build 6a2e4c
    return fvr
rpm-build 6a2e4c
rpm-build 6a2e4c
rpm-build 6a2e4c
def buildFeatureTableSubstitutionRecord(featureIndex, lookupListIndices):
rpm-build 6a2e4c
    """Build a FeatureTableSubstitutionRecord."""
rpm-build 6a2e4c
    ftsr = ot.FeatureTableSubstitutionRecord()
rpm-build 6a2e4c
    ftsr.FeatureIndex = featureIndex
rpm-build 6a2e4c
    ftsr.Feature = ot.Feature()
rpm-build 6a2e4c
    ftsr.Feature.LookupListIndex = lookupListIndices
rpm-build 6a2e4c
    return ftsr
rpm-build 6a2e4c
rpm-build 6a2e4c
rpm-build 6a2e4c
def buildConditionTable(axisIndex, filterRangeMinValue, filterRangeMaxValue):
rpm-build 6a2e4c
    """Build a ConditionTable."""
rpm-build 6a2e4c
    ct = ot.ConditionTable()
rpm-build 6a2e4c
    ct.Format = 1
rpm-build 6a2e4c
    ct.AxisIndex = axisIndex
rpm-build 6a2e4c
    ct.FilterRangeMinValue = filterRangeMinValue
rpm-build 6a2e4c
    ct.FilterRangeMaxValue = filterRangeMaxValue
rpm-build 6a2e4c
    return ct
rpm-build 6a2e4c
rpm-build 6a2e4c
rpm-build 6a2e4c
def sortFeatureList(table):
rpm-build 6a2e4c
    """Sort the feature list by feature tag, and remap the feature indices
rpm-build 6a2e4c
    elsewhere. This is needed after the feature list has been modified.
rpm-build 6a2e4c
    """
rpm-build 6a2e4c
    # decorate, sort, undecorate, because we need to make an index remapping table
rpm-build 6a2e4c
    tagIndexFea = [(fea.FeatureTag, index, fea) for index, fea in enumerate(table.FeatureList.FeatureRecord)]
rpm-build 6a2e4c
    tagIndexFea.sort()
rpm-build 6a2e4c
    table.FeatureList.FeatureRecord = [fea for tag, index, fea in tagIndexFea]
rpm-build 6a2e4c
    featureRemap = dict(zip([index for tag, index, fea in tagIndexFea], range(len(tagIndexFea))))
rpm-build 6a2e4c
rpm-build 6a2e4c
    # Remap the feature indices
rpm-build 6a2e4c
    remapFeatures(table, featureRemap)
rpm-build 6a2e4c
rpm-build 6a2e4c
rpm-build 6a2e4c
def remapFeatures(table, featureRemap):
rpm-build 6a2e4c
    """Go through the scripts list, and remap feature indices."""
rpm-build 6a2e4c
    for scriptIndex, script in enumerate(table.ScriptList.ScriptRecord):
rpm-build 6a2e4c
        defaultLangSys = script.Script.DefaultLangSys
rpm-build 6a2e4c
        if defaultLangSys is not None:
rpm-build 6a2e4c
            _remapLangSys(defaultLangSys, featureRemap)
rpm-build 6a2e4c
        for langSysRecordIndex, langSysRec in enumerate(script.Script.LangSysRecord):
rpm-build 6a2e4c
            langSys = langSysRec.LangSys
rpm-build 6a2e4c
            _remapLangSys(langSys, featureRemap)
rpm-build 6a2e4c
rpm-build 6a2e4c
    if hasattr(table, "FeatureVariations") and table.FeatureVariations is not None:
rpm-build 6a2e4c
        for fvr in table.FeatureVariations.FeatureVariationRecord:
rpm-build 6a2e4c
            for ftsr in fvr.FeatureTableSubstitution.SubstitutionRecord:
rpm-build 6a2e4c
                ftsr.FeatureIndex = featureRemap[ftsr.FeatureIndex]
rpm-build 6a2e4c
rpm-build 6a2e4c
rpm-build 6a2e4c
def _remapLangSys(langSys, featureRemap):
rpm-build 6a2e4c
    if langSys.ReqFeatureIndex != 0xffff:
rpm-build 6a2e4c
        langSys.ReqFeatureIndex = featureRemap[langSys.ReqFeatureIndex]
rpm-build 6a2e4c
    langSys.FeatureIndex = [featureRemap[index] for index in langSys.FeatureIndex]
rpm-build 6a2e4c
rpm-build 6a2e4c
rpm-build 6a2e4c
if __name__ == "__main__":
rpm-build 6a2e4c
    import doctest
rpm-build 6a2e4c
    doctest.testmod()