[featureVars] allow to assign same subst lookup to several feature tags

this can be useful to maximise layout engine compatibility or simply to have same feature variations substitutions be accessible via mutliple feature tags for whatever reason.

Fixes https://github.com/fonttools/fonttools/issues/2050
Fixes https://github.com/fonttools/fonttools/issues/3004
This commit is contained in:
Cosimo Lupo 2023-11-30 18:06:19 +00:00
parent ee3d7c8f80
commit a2f6f2ffed
No known key found for this signature in database
GPG Key ID: DF65A8A5A119C9A8
2 changed files with 69 additions and 31 deletions

View File

@ -52,7 +52,8 @@ from .errors import VarLibError, VarLibValidationError
log = logging.getLogger("fontTools.varLib")
# This is a lib key for the designspace document. The value should be
# an OpenType feature tag, to be used as the FeatureVariations feature.
# a comma-separated list of OpenType feature tag(s), to be used as the
# FeatureVariations feature.
# If present, the DesignSpace <rules processing="..."> flag is ignored.
FEAVAR_FEATURETAG_LIB_KEY = "com.github.fonttools.varLib.featureVarsFeatureTag"
@ -781,7 +782,9 @@ def _merge_OTL(font, model, master_fonts, axisTags):
font["GPOS"].table.remap_device_varidxes(varidx_map)
def _add_GSUB_feature_variations(font, axes, internal_axis_supports, rules, featureTag):
def _add_GSUB_feature_variations(
font, axes, internal_axis_supports, rules, featureTags
):
def normalize(name, value):
return models.normalizeLocation({name: value}, internal_axis_supports)[name]
@ -812,7 +815,7 @@ def _add_GSUB_feature_variations(font, axes, internal_axis_supports, rules, feat
conditional_subs.append((region, subs))
addFeatureVariations(font, conditional_subs, featureTag)
addFeatureVariations(font, conditional_subs, featureTags)
_DesignSpaceData = namedtuple(
@ -1204,11 +1207,9 @@ def build(
if "cvar" not in exclude and "glyf" in vf:
_merge_TTHinting(vf, model, master_fonts)
if "GSUB" not in exclude and ds.rules:
featureTag = ds.lib.get(
FEAVAR_FEATURETAG_LIB_KEY, "rclt" if ds.rulesProcessingLast else "rvrn"
)
featureTags = _feature_variations_tags(ds)
_add_GSUB_feature_variations(
vf, ds.axes, ds.internal_axis_supports, ds.rules, featureTag
vf, ds.axes, ds.internal_axis_supports, ds.rules, featureTags
)
if "CFF2" not in exclude and ("CFF " in vf or "CFF2" in vf):
_add_CFF2(vf, model, master_fonts)
@ -1299,6 +1300,14 @@ class MasterFinder(object):
return os.path.normpath(path)
def _feature_variations_tags(ds):
raw_tags = ds.lib.get(
FEAVAR_FEATURETAG_LIB_KEY,
"rclt" if ds.rulesProcessingLast else "rvrn",
)
return sorted({t.strip() for t in raw_tags.split(",")})
def main(args=None):
"""Build variable fonts from a designspace file and masters"""
from argparse import ArgumentParser

View File

@ -43,9 +43,18 @@ def addFeatureVariations(font, conditionalSubstitutions, featureTag="rvrn"):
# ... ]
# >>> addFeatureVariations(f, condSubst)
# >>> f.save(dstPath)
The `featureTag` parameter takes either a str or a iterable of str (the single str
is kept for backwards compatibility), and defines which feature(s) will be
associated with the feature variations.
Note, if this is "rvrn", then the substitution lookup will be inserted at the
beginning of the lookup list so that it is processed before others, otherwise
for any other feature tags it will be appended last.
"""
processLast = featureTag != "rvrn"
# process first when "rvrn" is the only listed tag
featureTags = [featureTag] if isinstance(featureTag, str) else sorted(featureTag)
processLast = "rvrn" not in featureTags or len(featureTags) > 1
_checkSubstitutionGlyphsExist(
glyphNames=set(font.getGlyphOrder()),
@ -75,7 +84,7 @@ def addFeatureVariations(font, conditionalSubstitutions, featureTag="rvrn"):
(conditionSet, [lookupMap[s] for s in substitutions])
)
addFeatureVariationsRaw(font, font["GSUB"].table, conditionsAndLookups, featureTag)
addFeatureVariationsRaw(font, font["GSUB"].table, conditionsAndLookups, featureTags)
def _checkSubstitutionGlyphsExist(glyphNames, substitutions):
@ -324,13 +333,16 @@ def addFeatureVariationsRaw(font, table, conditionalSubstitutions, featureTag="r
"""Low level implementation of addFeatureVariations that directly
models the possibilities of the FeatureVariations table."""
processLast = featureTag != "rvrn"
featureTags = [featureTag] if isinstance(featureTag, str) else sorted(featureTag)
processLast = "rvrn" not in featureTags or len(featureTags) > 1
#
# if there is no <featureTag> feature:
# if a <featureTag> feature is not present:
# make empty <featureTag> feature
# sort features, get <featureTag> feature index
# add <featureTag> feature to all scripts
# if a <featureTag> feature is present:
# reuse <featureTag> feature index
# make lookups
# add feature variations
#
@ -339,31 +351,48 @@ def addFeatureVariationsRaw(font, table, conditionalSubstitutions, featureTag="r
table.FeatureVariations = None # delete any existing FeatureVariations
varFeatureIndices = []
for index, feature in enumerate(table.FeatureList.FeatureRecord):
if feature.FeatureTag == featureTag:
varFeatureIndices.append(index)
varFeatureIndices = set()
if not varFeatureIndices:
varFeature = buildFeatureRecord(featureTag, [])
table.FeatureList.FeatureRecord.append(varFeature)
existingTags = {
feature.FeatureTag
for feature in table.FeatureList.FeatureRecord
if feature.FeatureTag in featureTags
}
newTags = set(featureTags) - existingTags
if newTags:
varFeatures = []
for featureTag in sorted(newTags):
varFeature = buildFeatureRecord(featureTag, [])
table.FeatureList.FeatureRecord.append(varFeature)
varFeatures.append(varFeature)
table.FeatureList.FeatureCount = len(table.FeatureList.FeatureRecord)
sortFeatureList(table)
varFeatureIndex = table.FeatureList.FeatureRecord.index(varFeature)
for scriptRecord in table.ScriptList.ScriptRecord:
if scriptRecord.Script.DefaultLangSys is None:
raise VarLibError(
"Feature variations require that the script "
f"'{scriptRecord.ScriptTag}' defines a default language system."
)
langSystems = [lsr.LangSys for lsr in scriptRecord.Script.LangSysRecord]
for langSys in [scriptRecord.Script.DefaultLangSys] + langSystems:
langSys.FeatureIndex.append(varFeatureIndex)
langSys.FeatureCount = len(langSys.FeatureIndex)
for varFeature in varFeatures:
varFeatureIndex = table.FeatureList.FeatureRecord.index(varFeature)
varFeatureIndices = [varFeatureIndex]
for scriptRecord in table.ScriptList.ScriptRecord:
if scriptRecord.Script.DefaultLangSys is None:
raise VarLibError(
"Feature variations require that the script "
f"'{scriptRecord.ScriptTag}' defines a default language system."
)
langSystems = [lsr.LangSys for lsr in scriptRecord.Script.LangSysRecord]
for langSys in [scriptRecord.Script.DefaultLangSys] + langSystems:
langSys.FeatureIndex.append(varFeatureIndex)
langSys.FeatureCount = len(langSys.FeatureIndex)
varFeatureIndices.add(varFeatureIndex)
if existingTags:
# indices may have changed if we inserted new features and sorted feature list
# so we must do this after the above
varFeatureIndices.update(
index
for index, feature in enumerate(table.FeatureList.FeatureRecord)
if feature.FeatureTag in existingTags
)
axisIndices = {
axis.axisTag: axisIndex for axisIndex, axis in enumerate(font["fvar"].axes)
@ -380,7 +409,7 @@ def addFeatureVariationsRaw(font, table, conditionalSubstitutions, featureTag="r
ct = buildConditionTable(axisIndices[axisTag], minValue, maxValue)
conditionTable.append(ct)
records = []
for varFeatureIndex in varFeatureIndices:
for varFeatureIndex in sorted(varFeatureIndices):
existingLookupIndices = table.FeatureList.FeatureRecord[
varFeatureIndex
].Feature.LookupListIndex