[featureVars] expose method to addGSUBFeatureVariations to existing VF

Fixes https://github.com/fonttools/fonttools/issues/3357
This commit is contained in:
Cosimo Lupo 2023-12-01 18:49:29 +00:00
parent 0b05cec0a3
commit 82021732ae
No known key found for this signature in database
GPG Key ID: DF65A8A5A119C9A8
2 changed files with 69 additions and 8 deletions

View File

@ -863,7 +863,7 @@ def _add_COLR(font, model, master_fonts, axisTags, colr_layer_reuse=True):
colr.VarIndexMap = builder.buildDeltaSetIndexMap(varIdxes) colr.VarIndexMap = builder.buildDeltaSetIndexMap(varIdxes)
def load_designspace(designspace): def load_designspace(designspace, log_enabled=True):
# TODO: remove this and always assume 'designspace' is a DesignSpaceDocument, # TODO: remove this and always assume 'designspace' is a DesignSpaceDocument,
# never a file path, as that's already handled by caller # never a file path, as that's already handled by caller
if hasattr(designspace, "sources"): # Assume a DesignspaceDocument if hasattr(designspace, "sources"): # Assume a DesignspaceDocument
@ -911,10 +911,11 @@ def load_designspace(designspace):
axis.labelNames["en"] = tostr(axis_name) axis.labelNames["en"] = tostr(axis_name)
axes[axis_name] = axis axes[axis_name] = axis
log.info("Axes:\n%s", pformat([axis.asdict() for axis in axes.values()])) if log_enabled:
log.info("Axes:\n%s", pformat([axis.asdict() for axis in axes.values()]))
axisMappings = ds.axisMappings axisMappings = ds.axisMappings
if axisMappings: if axisMappings and log_enabled:
log.info("Mappings:\n%s", pformat(axisMappings)) log.info("Mappings:\n%s", pformat(axisMappings))
# Check all master and instance locations are valid and fill in defaults # Check all master and instance locations are valid and fill in defaults
@ -944,20 +945,23 @@ def load_designspace(designspace):
# Normalize master locations # Normalize master locations
internal_master_locs = [o.getFullDesignLocation(ds) for o in masters] internal_master_locs = [o.getFullDesignLocation(ds) for o in masters]
log.info("Internal master locations:\n%s", pformat(internal_master_locs)) if log_enabled:
log.info("Internal master locations:\n%s", pformat(internal_master_locs))
# TODO This mapping should ideally be moved closer to logic in _add_fvar/avar # TODO This mapping should ideally be moved closer to logic in _add_fvar/avar
internal_axis_supports = {} internal_axis_supports = {}
for axis in axes.values(): for axis in axes.values():
triple = (axis.minimum, axis.default, axis.maximum) triple = (axis.minimum, axis.default, axis.maximum)
internal_axis_supports[axis.name] = [axis.map_forward(v) for v in triple] internal_axis_supports[axis.name] = [axis.map_forward(v) for v in triple]
log.info("Internal axis supports:\n%s", pformat(internal_axis_supports)) if log_enabled:
log.info("Internal axis supports:\n%s", pformat(internal_axis_supports))
normalized_master_locs = [ normalized_master_locs = [
models.normalizeLocation(m, internal_axis_supports) models.normalizeLocation(m, internal_axis_supports)
for m in internal_master_locs for m in internal_master_locs
] ]
log.info("Normalized master locations:\n%s", pformat(normalized_master_locs)) if log_enabled:
log.info("Normalized master locations:\n%s", pformat(normalized_master_locs))
# Find base master # Find base master
base_idx = None base_idx = None
@ -972,7 +976,8 @@ def load_designspace(designspace):
raise VarLibValidationError( raise VarLibValidationError(
"Base master not found; no master at default location?" "Base master not found; no master at default location?"
) )
log.info("Index of base master: %s", base_idx) if log_enabled:
log.info("Index of base master: %s", base_idx)
return _DesignSpaceData( return _DesignSpaceData(
axes, axes,
@ -1308,6 +1313,30 @@ def _feature_variations_tags(ds):
return sorted({t.strip() for t in raw_tags.split(",")}) return sorted({t.strip() for t in raw_tags.split(",")})
def addGSUBFeatureVariations(vf, designspace, featureTags=(), *, log_enabled=False):
"""Add GSUB FeatureVariations table to variable font, based on DesignSpace rules.
Args:
vf: A TTFont object representing the variable font.
designspace: A DesignSpaceDocument object.
featureTags: Optional feature tag(s) to use for the FeatureVariations records.
If unset, the key 'com.github.fonttools.varLib.featureVarsFeatureTag' is
looked up in the DS <lib> and used; otherwise the default is 'rclt' if
the <rules processing="last"> attribute is set, else 'rvrn'.
See <https://fonttools.readthedocs.io/en/latest/designspaceLib/xml.html#rules-element>
log_enabled: If True, log info about DS axes and sources. Default is False, as
the same info may have already been logged as part of varLib.build.
"""
ds = load_designspace(designspace, log_enabled=log_enabled)
if not ds.rules:
return
if not featureTags:
featureTags = _feature_variations_tags(ds)
_add_GSUB_feature_variations(
vf, ds.axes, ds.internal_axis_supports, ds.rules, featureTags
)
def main(args=None): def main(args=None):
"""Build variable fonts from a designspace file and masters""" """Build variable fonts from a designspace file and masters"""
from argparse import ArgumentParser from argparse import ArgumentParser

View File

@ -1,7 +1,13 @@
from fontTools.colorLib.builder import buildCOLR from fontTools.colorLib.builder import buildCOLR
from fontTools.ttLib import TTFont, newTable from fontTools.ttLib import TTFont, newTable
from fontTools.ttLib.tables import otTables as ot from fontTools.ttLib.tables import otTables as ot
from fontTools.varLib import build, build_many, load_designspace, _add_COLR from fontTools.varLib import (
build,
build_many,
load_designspace,
_add_COLR,
addGSUBFeatureVariations,
)
from fontTools.varLib.errors import VarLibValidationError from fontTools.varLib.errors import VarLibValidationError
import fontTools.varLib.errors as varLibErrors import fontTools.varLib.errors as varLibErrors
from fontTools.varLib.models import VariationModel from fontTools.varLib.models import VariationModel
@ -1009,6 +1015,32 @@ Expected to see .ScriptCount==1, instead saw 0""",
save_before_dump=True, save_before_dump=True,
) )
def test_varlib_addGSUBFeatureVariations(self):
ttx_dir = self.get_test_input("master_ttx_interpolatable_ttf")
ds = DesignSpaceDocument.fromfile(
self.get_test_input("FeatureVars.designspace")
)
for source in ds.sources:
ttx_dump = TTFont()
ttx_dump.importXML(
os.path.join(
ttx_dir, os.path.basename(source.filename).replace(".ufo", ".ttx")
)
)
source.font = ttx_dump
varfont, _, _ = build(ds, exclude=["GSUB"])
assert "GSUB" not in varfont
addGSUBFeatureVariations(varfont, ds)
assert "GSUB" in varfont
tables = ["fvar", "GSUB"]
expected_ttx_path = self.get_test_output("FeatureVars.ttx")
self.expect_ttx(varfont, expected_ttx_path, tables)
self.check_ttx_dump(varfont, expected_ttx_path, tables, ".ttf")
def test_load_masters_layerName_without_required_font(): def test_load_masters_layerName_without_required_font():
ds = DesignSpaceDocument() ds = DesignSpaceDocument()