Skip to content

Commit

Permalink
do not propagate anchors to composite mark glyphs with anchors
Browse files Browse the repository at this point in the history
This is meant to match Glyphs.app's logic, see #802

(A similar fix should be added to glyphsLib's anchor_propagation.py until the latter is superseded by the ufo2ft filter)
  • Loading branch information
anthrotype committed Jan 23, 2024
1 parent 5cc53e2 commit 9141d2c
Show file tree
Hide file tree
Showing 2 changed files with 44 additions and 9 deletions.
22 changes: 14 additions & 8 deletions Lib/ufo2ft/filters/propagateAnchors.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
from fontTools.misc.transform import Transform

from ufo2ft.filters import BaseFilter
from ufo2ft.util import OpenTypeCategories

logger = logging.getLogger(__name__)

Expand All @@ -26,14 +27,14 @@ class PropagateAnchorsFilter(BaseFilter):
def set_context(self, font, glyphSet):
ctx = super().set_context(font, glyphSet)
ctx.processed = set()
ctx.categories = OpenTypeCategories.load(font)
return ctx

def __call__(self, font, glyphSet=None):
if super().__call__(font, glyphSet):
modified = self.context.modified
if modified:
logger.info("Glyphs with propagated anchors: %i" % len(modified))
return modified
modified = super().__call__(font, glyphSet)
if modified:
logger.info("Glyphs with propagated anchors: %i" % len(modified))
return modified

def filter(self, glyph):
if not glyph.components:
Expand All @@ -44,11 +45,12 @@ def filter(self, glyph):
glyph,
self.context.processed,
self.context.modified,
self.context.categories,
)
return len(glyph.anchors) > before


def _propagate_glyph_anchors(glyphSet, composite, processed, modified):
def _propagate_glyph_anchors(glyphSet, composite, processed, modified, categories):
"""
Propagate anchors from base glyphs to a given composite
glyph, and to all composite glyphs used in between.
Expand All @@ -58,7 +60,11 @@ def _propagate_glyph_anchors(glyphSet, composite, processed, modified):
return
processed.add(composite.name)

if not composite.components:
if not composite.components or (
# "If it is a 'mark' and there are anchors, it will not look into components"
# Georg said: https://github.com/googlefonts/ufo2ft/issues/802#issuecomment-1904109457
composite.name in categories.mark and composite.anchors
):
return

base_components = []
Expand All @@ -74,7 +80,7 @@ def _propagate_glyph_anchors(glyphSet, composite, processed, modified):
"in glyph {}".format(component.baseGlyph, composite.name)
)
else:
_propagate_glyph_anchors(glyphSet, glyph, processed, modified)
_propagate_glyph_anchors(glyphSet, glyph, processed, modified, categories)
if any(a.name.startswith("_") for a in glyph.anchors):
mark_components.append(component)
else:
Expand Down
31 changes: 30 additions & 1 deletion tests/filters/propagateAnchors_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,25 @@
("addComponent", ("macroncomb", (1, 0, 0, 1, 175, 0))),
],
},
{
"name": "r",
"width": 350,
"outline": [
("moveTo", ((0, 0),)),
("lineTo", ((0, 300),)),
("lineTo", ((175, 300),)),
("closePath", ()),
],
"anchors": [(175, 300, "top"), (175, 0, "bottom")],
},
{
"name": "rcombbelow",
"width": 0,
"outline": [
("addComponent", ("r", (0.5, 0, 0, 0.5, -100, -100))),
],
"anchors": [(0, 0, "_bottom")],
},
]
}
]
Expand All @@ -120,6 +139,12 @@ def font(request, FontClass):
getattr(pen, operator)(*operands)
for x, y, name in param.get("anchors", []):
glyph.appendAnchor(dict(x=x, y=y, name=name))
# classify as 'mark' all glyphs with zero width and 'comb' in their name
font.lib["public.openTypeCategories"] = {
g["name"]: "mark"
for g in request.param["glyphs"]
if g.get("width", 0) == 0 and "comb" in g["name"]
}
return font


Expand Down Expand Up @@ -149,6 +174,10 @@ def font(request, FontClass):
],
{"a_a"},
),
# the composite glyph is a mark with anchors, hence propagation is not performed,
# i.e. 'top' and 'bottom' are *not* copied to 'rcombbelow':
# https://github.com/googlefonts/ufo2ft/issues/802
"rcombbelow": ([("_bottom", 0, 0)], set()),
}


Expand All @@ -173,7 +202,7 @@ def test_include_one_glyph_at_a_time(self, font, name):
def test_whole_font(self, font):
philter = PropagateAnchorsFilter()
modified = philter(font)
assert modified == set(EXPECTED)
assert modified == {k for k in EXPECTED if k in EXPECTED[k][1]}
for name, (expected_anchors, _) in EXPECTED.items():
assert [(a.name, a.x, a.y) for a in font[name].anchors] == expected_anchors

Expand Down

0 comments on commit 9141d2c

Please sign in to comment.