Skip to content

Commit

Permalink
Merge pull request #3362 from fonttools/add-gsub-feavars-api
Browse files Browse the repository at this point in the history
[featureVars] expose public method to addGSUBFeatureVariations to an existing VF
  • Loading branch information
anthrotype committed Dec 2, 2023
2 parents 0b05cec + 8202173 commit 4c527e6
Show file tree
Hide file tree
Showing 2 changed files with 69 additions and 8 deletions.
43 changes: 36 additions & 7 deletions Lib/fontTools/varLib/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -863,7 +863,7 @@ def _add_COLR(font, model, master_fonts, axisTags, colr_layer_reuse=True):
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,
# never a file path, as that's already handled by caller
if hasattr(designspace, "sources"): # Assume a DesignspaceDocument
Expand Down Expand Up @@ -911,10 +911,11 @@ def load_designspace(designspace):
axis.labelNames["en"] = tostr(axis_name)

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
if axisMappings:
if axisMappings and log_enabled:
log.info("Mappings:\n%s", pformat(axisMappings))

# Check all master and instance locations are valid and fill in defaults
Expand Down Expand Up @@ -944,20 +945,23 @@ def load_designspace(designspace):
# Normalize master locations

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
internal_axis_supports = {}
for axis in axes.values():
triple = (axis.minimum, axis.default, axis.maximum)
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 = [
models.normalizeLocation(m, internal_axis_supports)
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
base_idx = None
Expand All @@ -972,7 +976,8 @@ def load_designspace(designspace):
raise VarLibValidationError(
"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(
axes,
Expand Down Expand Up @@ -1308,6 +1313,30 @@ def _feature_variations_tags(ds):
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):
"""Build variable fonts from a designspace file and masters"""
from argparse import ArgumentParser
Expand Down
34 changes: 33 additions & 1 deletion Tests/varLib/varLib_test.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,13 @@
from fontTools.colorLib.builder import buildCOLR
from fontTools.ttLib import TTFont, newTable
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
import fontTools.varLib.errors as varLibErrors
from fontTools.varLib.models import VariationModel
Expand Down Expand Up @@ -1009,6 +1015,32 @@ def test_varlib_build_variable_cff2_with_empty_sparse_glyph(self):
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():
ds = DesignSpaceDocument()
Expand Down

0 comments on commit 4c527e6

Please sign in to comment.