Skip to content

Commit

Permalink
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Port fontc' propagate_anchors.rs to .py, apply as preflight transform…
Browse files Browse the repository at this point in the history
… on GSFont

Fixes #368

Translate fontc' propagate_anchors.rs from Rust to Python and apply to the GSFont in the 'preflight' step before it's passed on to UFOBuilder
The Rust code was based off the original Glyphs.app's Objective-C code (Georg shared a snippet with us privately) and is as such more 'correct' then the old, reverse-engineered implementation.
The old glyphsLib.builder.anchor_propagation module is deprecated but kept around in case users may instantiate the UFOBuilder class directly with propagate_anchors=True.
Also, the existing `propagate_anchors` parameters of `to_designspace`, `to_ufos` and other high-level methods continues to have an equivalent effect but now controls the **new** propagate anchors logic. Clients such as fontmake do not need to do anything besides upgrading glyphsLib to use this.
anthrotype committed Jul 30, 2024

Verified

This commit was signed with the committer’s verified signature.
florianduros Florian Duros
1 parent cedaacd commit 9cabd52
Showing 8 changed files with 527 additions and 21 deletions.
58 changes: 49 additions & 9 deletions Lib/glyphsLib/builder/__init__.py
Original file line number Diff line number Diff line change
@@ -18,7 +18,7 @@
from glyphsLib import classes

from .builders import UFOBuilder, GlyphsBuilder
from .transformations import TRANSFORMATIONS
from .transformations import TRANSFORMATIONS, TRANSFORMATION_CUSTOM_PARAMS

logger = logging.getLogger(__name__)

@@ -64,11 +64,13 @@ def to_ufos(
"""
if preserve_original:
font = copy.deepcopy(font)
font = preflight_glyphs(
font, glyph_data=glyph_data, do_propagate_all_anchors=propagate_anchors
)
builder = UFOBuilder(
preflight_glyphs(font),
font,
ufo_module=ufo_module,
family_name=family_name,
propagate_anchors=propagate_anchors,
minimize_glyphs_diffs=minimize_glyphs_diffs,
generate_GDEF=generate_GDEF,
store_editor_state=store_editor_state,
@@ -129,13 +131,14 @@ def to_designspace(
"""
if preserve_original:
font = copy.deepcopy(font)

font = preflight_glyphs(
font, glyph_data=glyph_data, do_propagate_all_anchors=propagate_anchors
)
builder = UFOBuilder(
preflight_glyphs(font),
font,
ufo_module=ufo_module,
family_name=family_name,
instance_dir=instance_dir,
propagate_anchors=propagate_anchors,
use_designspace=True,
minimize_glyphs_diffs=minimize_glyphs_diffs,
generate_GDEF=generate_GDEF,
@@ -148,12 +151,49 @@ def to_designspace(
return builder.designspace


def preflight_glyphs(font):
def preflight_glyphs(font, *, glyph_data=None, **todos):
"""Run a set of transformations over a GSFont object to make
it easier to convert to UFO; resolve all the "smart stuff"."""
it easier to convert to UFO; resolve all the "smart stuff".
Currently, the transformations are:
- `propagate_all_anchors`: copy anchors from components to their parent
More transformations may be added in the future.
Some transformations may have custom parameters that can be set in the
font. For example, the `propagate_all_anchors` transformation can be
disabled by setting the custom parameter "Propagate Anchors" to False
(see `TRANSFORMATION_CUSTOM_PARAMS`).
Args:
font: a GSFont object
glyph_data: an optional GlyphData object associating various properties to
glyph names (e.g. category) that overrides the default one
**todos: a set of boolean flags to enable/disable specific transformations,
named `do_<transformation_name>`, e.g. `do_propagate_all_anchors=False`
will disable the propagation of anchors.
Returns:
the modified GSFont object
"""

for transform in TRANSFORMATIONS:
transform(font)
do_transform = todos.pop("do_" + transform.__name__, None)
if do_transform is True:
pass
elif do_transform is False:
continue
elif do_transform is None:
if transform in TRANSFORMATION_CUSTOM_PARAMS:
param = TRANSFORMATION_CUSTOM_PARAMS[transform]
if not font.customParameters.get(param.name, param.default):
continue
else:
raise ValueError(f"Invalid value for do_{transform.__name__}")
logger.info(f"Running '{transform.__name__}' transformation")
transform(font, glyph_data=glyph_data)
if todos:
logger.warning(f"preflight_glyphs has unused `todos` arguments: {todos}")
return font


8 changes: 8 additions & 0 deletions Lib/glyphsLib/builder/anchor_propagation.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,11 @@
"""This module is DEPRECATED and will be removed in a future release.
For anchor propagation on GSFont objects, you can use the
`glyphsLib.builder.transformations.propagate_anchors` module.
For anchor propagation on UFO font objects, you can try the
`ufo2ft.filters.propagateAnchors` filter.
"""

from fontTools.misc.transform import Transform
import fontTools.pens.boundsPen

26 changes: 17 additions & 9 deletions Lib/glyphsLib/builder/builders.py
Original file line number Diff line number Diff line change
@@ -28,7 +28,7 @@
FONT_CUSTOM_PARAM_PREFIX,
)
from .axes import WEIGHT_AXIS_DEF, WIDTH_AXIS_DEF, find_base_style, class_to_value
from glyphsLib.util import LoggerMixin
from glyphsLib.util import LoggerMixin, _DeprecatedArgument


class UFOBuilder(LoggerMixin):
@@ -41,7 +41,7 @@ def __init__(
designspace_module=designspaceLib,
family_name=None,
instance_dir=None,
propagate_anchors=None,
propagate_anchors=_DeprecatedArgument, # DEPRECATED
use_designspace=False,
minimize_glyphs_diffs=False,
generate_GDEF=True,
@@ -66,9 +66,8 @@ def __init__(
only instances with this name will be returned.
instance_dir -- if provided, instance UFOs will be located in this
directory, according to their Designspace filenames.
propagate_anchors -- set to True or False to explicitly control anchor
propagation, the default is to check for
"Propagate Anchors" custom parameter.
propagate_anchors -- DEPRECATED. Use preflight_glyphs to propagate anchors on
the GSFont before building UFOs.
use_designspace -- set to True to make optimal use of the designspace:
data that is common to all ufos will go there.
minimize_glyphs_diffs -- set to True to store extra info in UFOs
@@ -106,9 +105,18 @@ def __init__(
self.expand_includes = expand_includes
self.minimal = minimal

if propagate_anchors is None:
propagate_anchors = font.customParameters["Propagate Anchors"]
propagate_anchors = bool(propagate_anchors is None or propagate_anchors)
if propagate_anchors is not _DeprecatedArgument:
from warnings import warn

warn(
"The 'propagate_anchors' argument is deprecated and will be removed "
"in a future version. "
"Use glyphsLib.builder.preflight_glyphs to propagate anchors on the "
"GSFont before building UFOs.",
DeprecationWarning,
)
else:
propagate_anchors = False
self.propagate_anchors = propagate_anchors

# The set of (SourceDescriptor + UFO)s that will be built,
@@ -215,7 +223,7 @@ def masters(self):
for master_id, source in self._sources.items():
ufo = source.font
master = self.font.masters[master_id]
if self.propagate_anchors:
if self.propagate_anchors: # deprecated, will be removed one day
self.to_ufo_propagate_font_anchors(ufo) # .anchor_propagation
if not self.minimal:
for layer in list(ufo.layers):
21 changes: 20 additions & 1 deletion Lib/glyphsLib/builder/transformations/__init__.py
Original file line number Diff line number Diff line change
@@ -1 +1,20 @@
TRANSFORMATIONS = []
from types import MappingProxyType
from typing import NamedTuple

from .propagate_anchors import propagate_all_anchors

TRANSFORMATIONS = [
propagate_all_anchors,
]


class _CustomParameter(NamedTuple):
name: str
default: bool


TRANSFORMATION_CUSTOM_PARAMS = MappingProxyType(
{
propagate_all_anchors: _CustomParameter("Propagate Anchors", True),
}
)
426 changes: 426 additions & 0 deletions Lib/glyphsLib/builder/transformations/propagate_anchors.py

Large diffs are not rendered by default.

4 changes: 4 additions & 0 deletions Lib/glyphsLib/util.py
Original file line number Diff line number Diff line change
@@ -176,3 +176,7 @@ def __next__(self):

def peek(self, n=0):
return self.list[self.index + n]


# sentinel object to indicate a deprecated argument
_DeprecatedArgument = object()
3 changes: 2 additions & 1 deletion tests/builder/designspace_gen_test.py
Original file line number Diff line number Diff line change
@@ -26,6 +26,7 @@
import glyphsLib
from glyphsLib import to_designspace, to_glyphs
from glyphsLib.util import open_ufo
from glyphsLib.types import Point


def test_designspace_generation_regular_same_family_name(tmpdir, ufo_module):
@@ -481,7 +482,7 @@ def test_designspace_generation_bracket_GDEF(datadir, ufo_module):
for layer in font.glyphs["x"].layers:
anchor = glyphsLib.classes.GSAnchor()
anchor.name = "top"
anchor.position = (0, 0)
anchor.position = Point(0, 0)
layer.anchors.append(anchor)

designspace = to_designspace(font, ufo_module=ufo_module, generate_GDEF=True)
2 changes: 1 addition & 1 deletion tests/classes_test.py
Original file line number Diff line number Diff line change
@@ -115,7 +115,7 @@ def add_anchor(font, glyphname, anchorname, x, y):
layer.anchors = getattr(layer, "anchors", [])
anchor = GSAnchor()
anchor.name = anchorname
anchor.position = (x, y)
anchor.position = Point(x, y)
layer.anchors.append(anchor)


0 comments on commit 9cabd52

Please sign in to comment.