From e5245645e9826c919857d2d07abfbf4a3e15b3b9 Mon Sep 17 00:00:00 2001 From: Khaled Hosny Date: Sat, 5 Oct 2024 12:11:23 +0300 Subject: [PATCH] propagate_anchors: fix handling of contextual anchors MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Don’t propagate contextual anchors, GlyphsApp does not propagate them. - When copying anchors, copy also userData, otherwise the context of contextual anchors of any processed glyph will be dropped. This is a regression from https://github.com/googlefonts/glyphsLib/pull/1011 --- .../transformations/propagate_anchors.py | 10 ++- Lib/glyphsLib/classes.py | 4 +- .../transformations/propagate_anchors_test.py | 63 ++++++++++++++++++- 3 files changed, 73 insertions(+), 4 deletions(-) diff --git a/Lib/glyphsLib/builder/transformations/propagate_anchors.py b/Lib/glyphsLib/builder/transformations/propagate_anchors.py index e4f2d492b..2a3325a66 100644 --- a/Lib/glyphsLib/builder/transformations/propagate_anchors.py +++ b/Lib/glyphsLib/builder/transformations/propagate_anchors.py @@ -211,6 +211,9 @@ def anchors_traversing_components( component_transform = Transform(*component.transform) xscale, yscale = get_xy_rotation(component_transform) for anchor in anchors: + # skip contextual anchors + if anchor.name.startswith("*") and "GPOS_Context" in anchor.userData: + continue new_has_underscore = anchor.name.startswith("_") if (component_idx > 0 or has_underscore) and new_has_underscore: continue @@ -299,6 +302,7 @@ def origin_adjusted_anchors(anchors: list[GSAnchor]) -> Iterable[GSAnchor]: GSAnchor( name=a.name, position=Point(a.position.x - origin.x, a.position.y - origin.y), + userData=a.userData, ) for a in anchors if a.name != "*origin" @@ -398,7 +402,11 @@ def get_component_layer_anchors( if layer_anchors is not None: # return a copy as they may be modified in place layer_anchors = [ - GSAnchor(name=a.name, position=Point(a.position.x, a.position.y)) + GSAnchor( + name=a.name, + position=Point(a.position.x, a.position.y), + userData=a.userData, + ) for a in layer_anchors ] return layer_anchors diff --git a/Lib/glyphsLib/classes.py b/Lib/glyphsLib/classes.py index ef4a2bd84..a9564ab85 100755 --- a/Lib/glyphsLib/classes.py +++ b/Lib/glyphsLib/classes.py @@ -2787,13 +2787,15 @@ def _serialize_to_plist(self, writer): _parent = None _defaultsForName = {"position": Point(0, 0)} - def __init__(self, name=None, position=None): + def __init__(self, name=None, position=None, userData=None): self.name = "" if name is None else name self._userData = None if position is None: self.position = copy.deepcopy(self._defaultsForName["position"]) else: self.position = position + if userData is not None: + self.userData = userData def __repr__(self): return '<{} "{}" x={:.1f} y={:.1f}>'.format( diff --git a/tests/builder/transformations/propagate_anchors_test.py b/tests/builder/transformations/propagate_anchors_test.py index c3a1775d2..9c313598c 100644 --- a/tests/builder/transformations/propagate_anchors_test.py +++ b/tests/builder/transformations/propagate_anchors_test.py @@ -112,8 +112,13 @@ def add_component_anchor(self, name: str) -> Self: component.anchor = name return self - def add_anchor(self, name: str, pos: tuple[float, float]) -> Self: - anchor = GSAnchor(name, Point(*pos)) + def add_anchor( + self, + name: str, + pos: tuple[float, float], + userData: dict | None = None, + ) -> Self: + anchor = GSAnchor(name, Point(*pos), userData=userData) self.current_layer.anchors.append(anchor) return self @@ -193,6 +198,7 @@ def assert_anchors(actual, expected): for a, e in zip(actual, expected): assert a.name == e[0] assert a.position == Point(*e[1]) + assert dict(a.userData) == (e[2] if len(e) > 2 else {}) def test_no_components_anchors_are_unchanged(): @@ -611,6 +617,59 @@ def test_origin_anchor(): ) +def test_contextual_anchors(): + glyphs = ( + GlyphSetBuilder() + .add_glyph( + "behDotless-ar.init", + lambda glyph: ( + glyph.add_anchor("bottom", (50, 0)) + .add_anchor( + "*bottom", + (95, 0), + userData={"GPOS_Context": "* behDotless-ar.medi"}, + ) + .add_anchor("top", (35, 229)) + ), + ) + .add_glyph( + "behDotless-ar.medi", + lambda glyph: ( + glyph.add_component("behDotless-ar.init", (0, 0)).add_anchor( + "*bottom", + (95, 0), + userData={"GPOS_Context": "* behDotless-ar.fina"}, + ) + ), + ) + .add_glyph( + "behDotless-ar.fina", + lambda glyph: glyph.add_component("behDotless-ar.init", (0, 0)), + ) + .build() + ) + propagate_all_anchors_impl(glyphs) + + new_glyph = glyphs["behDotless-ar.medi"] + assert_anchors( + new_glyph.layers[0].anchors, + [ + ("bottom", (50, 0)), + ("top", (35, 229)), + ("*bottom", (95, 0), {"GPOS_Context": "* behDotless-ar.fina"}), + ], + ) + + new_glyph = glyphs["behDotless-ar.fina"] + assert_anchors( + new_glyph.layers[0].anchors, + [ + ("bottom", (50, 0)), + ("top", (35, 229)), + ], + ) + + def test_invert_names_on_rotation(): # derived from the observed behaviour of glyphs 3.2.2 (3259) glyphs = (