Skip to content

Commit

Permalink
add option to both UFO|GlyphsBuilders to expand_includes in features
Browse files Browse the repository at this point in the history
As suggested in #797, it may be useful sometimes to resolve and expand include statements in features, to create self-contained UFOs/glyphs sources that don't reference any external files. It is disabled by default, to make sure rountripping continues to work. It is not conflated with 'minimal' option because they are distinct things, one may want to enable one without the other.
  • Loading branch information
anthrotype committed Jul 22, 2022
1 parent 9ccb90b commit 965fbf3
Show file tree
Hide file tree
Showing 5 changed files with 89 additions and 6 deletions.
4 changes: 4 additions & 0 deletions Lib/glyphsLib/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@ def load_to_ufos(
family_name=None,
propagate_anchors=None,
ufo_module=None,
expand_includes=False,
minimal=False,
glyph_data=None,
):
Expand All @@ -70,6 +71,7 @@ def load_to_ufos(
family_name=family_name,
propagate_anchors=propagate_anchors,
ufo_module=ufo_module,
expand_includes=expand_includes,
minimal=minimal,
glyph_data=glyph_data,
)
Expand All @@ -88,6 +90,7 @@ def build_masters(
generate_GDEF=True,
store_editor_state=True,
write_skipexportglyphs=False,
expand_includes=False,
ufo_module=None,
minimal=False,
glyph_data=None,
Expand Down Expand Up @@ -126,6 +129,7 @@ def build_masters(
generate_GDEF=generate_GDEF,
store_editor_state=store_editor_state,
write_skipexportglyphs=write_skipexportglyphs,
expand_includes=expand_includes,
ufo_module=ufo_module,
minimal=minimal,
glyph_data=glyph_data,
Expand Down
10 changes: 10 additions & 0 deletions Lib/glyphsLib/builder/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ def to_ufos(
generate_GDEF=True,
store_editor_state=True,
write_skipexportglyphs=False,
expand_includes=False,
minimal=False,
glyph_data=None,
):
Expand All @@ -47,6 +48,9 @@ def to_ufos(
If generate_GDEF is True, write a `table GDEF {...}` statement in the
UFO's features.fea, containing GlyphClassDef and LigatureCaretByPos.
If expand_includes is True, resolve include statements in the GSFont features
and inline them in the UFO features.fea.
If minimal is True, it is assumed that the UFOs will only be used in
font production, and unnecessary steps (e.g. converting background layers)
will be skipped.
Expand All @@ -60,6 +64,7 @@ def to_ufos(
generate_GDEF=generate_GDEF,
store_editor_state=store_editor_state,
write_skipexportglyphs=write_skipexportglyphs,
expand_includes=expand_includes,
minimal=minimal,
glyph_data=glyph_data,
)
Expand All @@ -81,6 +86,7 @@ def to_designspace(
generate_GDEF=True,
store_editor_state=True,
write_skipexportglyphs=False,
expand_includes=False,
minimal=False,
glyph_data=None,
):
Expand Down Expand Up @@ -117,6 +123,7 @@ def to_designspace(
generate_GDEF=generate_GDEF,
store_editor_state=store_editor_state,
write_skipexportglyphs=write_skipexportglyphs,
expand_includes=expand_includes,
minimal=minimal,
glyph_data=glyph_data,
)
Expand All @@ -128,6 +135,7 @@ def to_glyphs(
glyphs_module=classes,
ufo_module=None,
minimize_ufo_diffs=False,
expand_includes=False,
):
"""
Take a list of UFOs or a single DesignspaceDocument with attached UFOs
Expand All @@ -146,12 +154,14 @@ def to_glyphs(
glyphs_module=glyphs_module,
ufo_module=ufo_module,
minimize_ufo_diffs=minimize_ufo_diffs,
expand_includes=expand_includes,
)
else:
builder = GlyphsBuilder(
ufos=ufos_or_designspace,
glyphs_module=glyphs_module,
ufo_module=ufo_module,
minimize_ufo_diffs=minimize_ufo_diffs,
expand_includes=expand_includes,
)
return builder.font
10 changes: 10 additions & 0 deletions Lib/glyphsLib/builder/builders.py
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,7 @@ def __init__(
generate_GDEF=True,
store_editor_state=True,
write_skipexportglyphs=False,
expand_includes=False,
minimal=False,
glyph_data=None,
):
Expand Down Expand Up @@ -105,6 +106,10 @@ def __init__(
into the UFOs' and Designspace's lib instead
of the glyph level lib key
"com.schriftgestaltung.Glyphs.Export".
expand_includes -- If True, expand include statements in the GSFont features
and inline them in the UFO features.fea.
minimal -- If True, it is assumed that the UFOs will only be used in font
production, and unnecessary steps will be skipped.
glyph_data -- A list of GlyphData.
"""
self.font = font
Expand All @@ -122,6 +127,7 @@ def __init__(
self.store_editor_state = store_editor_state
self.bracket_layers = []
self.write_skipexportglyphs = write_skipexportglyphs
self.expand_includes = expand_includes
self.minimal = minimal

if propagate_anchors is None:
Expand Down Expand Up @@ -718,6 +724,7 @@ def __init__(
glyphs_module=classes,
ufo_module=None,
minimize_ufo_diffs=False,
expand_includes=False,
):
"""Create a builder that goes from UFOs + designspace to Glyphs.
Expand Down Expand Up @@ -746,9 +753,12 @@ def __init__(
minimize_ufo_diffs -- set to True to store extra info in .glyphs files
in order to get smaller diffs between UFOs
when going UFOs->glyphs->UFOs
expand_includes -- If True, expand include statements in the UFOs' features.fea
and inline them in the GSFont features.
"""
self.glyphs_module = glyphs_module
self.minimize_ufo_diffs = minimize_ufo_diffs
self.expand_includes = expand_includes

if designspace is not None:
if ufos:
Expand Down
53 changes: 47 additions & 6 deletions Lib/glyphsLib/builder/features.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@

from __future__ import annotations

import os
import re
from textwrap import dedent
from io import StringIO
Expand Down Expand Up @@ -47,7 +48,11 @@ def to_ufo_master_features(self, ufo, master):
ufo.features.text = original
else:
ufo.features.text = _to_ufo_features(
self.font, ufo, generate_GDEF=self.generate_GDEF, master=master
self.font,
ufo,
generate_GDEF=self.generate_GDEF,
master=master,
expand_includes=self.expand_includes,
)


Expand Down Expand Up @@ -159,6 +164,7 @@ def _to_ufo_features(
ufo: Font | None = None,
generate_GDEF: bool = False,
master: GSFontMaster | None = None,
expand_includes: bool = False,
) -> str:
"""Convert GSFont features, including prefixes and classes, to UFO.
Expand Down Expand Up @@ -246,7 +252,23 @@ def _to_ufo_features(
regenerate_opentype_categories(font, ufo)

full_text = "\n\n".join(filter(None, [class_str, prefix_str, fea_str])) + "\n"
return full_text if full_text.strip() else ""
full_text = full_text if full_text.strip() else ""

if not full_text or not expand_includes:
return full_text

# use feaLib Parser to resolve include statements relative to the GSFont
# fontpath, and inline them in the output features text.
feature_file = StringIO(full_text)
include_dir = os.path.dirname(font.filepath) if font.filepath else None
fea_parser = parser.Parser(
feature_file,
glyphNames={glyph.name for glyph in font.glyphs},
includeDir=include_dir,
followIncludes=expand_includes,
)
doc = fea_parser.parse()
return doc.asFea()


def _build_public_opentype_categories(ufo: Font) -> dict[str, str]:
Expand Down Expand Up @@ -405,11 +427,16 @@ def to_glyphs_features(self):
ufo = self.designspace.sources[0].font
if ufo.features.text is None:
return
include_dir = None
if expand_includes and ufo.path:
include_dir = os.path.dirname(os.path.normpath(ufo.path))
_to_glyphs_features(
self.font,
ufo.features.text,
glyph_names=ufo.keys(),
glyphs_module=self.glyphs_module,
include_dir=include_dir,
expand_includes=self.expand_includes,
)

# Store GDEF category data GSFont-wide to capture bracket glyphs that we
Expand All @@ -419,16 +446,30 @@ def to_glyphs_features(self):
self.font.userData[ORIGINAL_CATEGORY_KEY] = opentype_categories


def _to_glyphs_features(font, features_text, glyph_names=None, glyphs_module=None):
def _to_glyphs_features(
font,
features_text,
glyph_names=None,
glyphs_module=None,
include_dir=None,
expand_includes=False,
):
"""Import features text in GSFont, split into prefixes, features and classes.
Args:
font: GSFont
feature_text: str
glyph_names: Optional[Sequence[str]]
glyphs_module: Optional[Any]
include_dir: Optional[str]
expand_includes: bool
"""
document = FeaDocument(features_text, glyph_names)
document = FeaDocument(
features_text,
glyph_names,
include_dir=include_dir,
expand_includes=expand_includes,
)
processor = FeatureFileProcessor(document, glyphs_module)
processor.to_glyphs(font)

Expand Down Expand Up @@ -474,11 +515,11 @@ def _to_glyphs_features_basic(self):
class FeaDocument:
"""Parse the string of a fea code into statements."""

def __init__(self, text, glyph_set=None):
def __init__(self, text, glyph_set=None, include_dir=None, expand_includes=False):
feature_file = StringIO(text)
glyph_names = glyph_set if glyph_set is not None else ()
parser_ = parser.Parser(
feature_file, glyphNames=glyph_names, followIncludes=False
feature_file, glyphNames=glyph_names, followIncludes=expand_includes
)
self._doc = parser_.parse()
self.statements = self._doc.statements
Expand Down
18 changes: 18 additions & 0 deletions Lib/glyphsLib/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -151,6 +151,14 @@ def main(args=None):
"key."
),
)
group.add_argument(
"--expand-includes",
action="store_true",
help=(
"Expand include statements in the .glyphs features and inline them in "
"the exported UFO features.fea."
),
)
group = parser_glyphs2ufo.add_argument_group("Glyph data")
group.add_argument(
"--glyph-data",
Expand Down Expand Up @@ -211,6 +219,14 @@ def main(args=None):
action="store_false",
help="Enable automatic alignment of components in glyphs.",
)
group.add_argument(
"--expand-includes",
action="store_true",
help=(
"Expand include statements in the UFO features.fea and inline them in "
"the exported .glyphs features."
),
)

options = parser.parse_args(args)

Expand Down Expand Up @@ -246,6 +262,7 @@ def glyphs2ufo(options):
generate_GDEF=options.generate_GDEF,
store_editor_state=not options.no_store_editor_state,
write_skipexportglyphs=options.write_public_skip_export_glyphs,
expand_includes=options.expand_includes,
ufo_module=__import__(options.ufo_module),
minimal=options.minimal,
glyph_data=options.glyph_data or None,
Expand Down Expand Up @@ -298,6 +315,7 @@ def ufo2glyphs(options):
object_to_read,
ufo_module=ufo_module,
minimize_ufo_diffs=options.no_preserve_glyphsapp_metadata,
expand_includes=options.expand_includes,
)

# Make the Glyphs file more suitable for roundtrip:
Expand Down

0 comments on commit 965fbf3

Please sign in to comment.