-
Notifications
You must be signed in to change notification settings - Fork 43
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
DecomposeTransformedComponentsFilter: MM compatibility issues #621
Comments
The order between components and contours is basically undefined in UFO spec: unified-font-object/ufo-spec#169 The way This should not matter because they are distinct glyphs in the end, we match each glyph to merge from each masters by their name, so as long as "exclam" and "exclamdown" have each respectively the same structure across masters, who cares that the contours are ordered differently betweem them. But.. it turns out, in at least two of the three masters in the testcase (Bold and ExtraLight, but not Regular), the "exclam" composite glyph has a "period" component with a non-identity 2x2 transform (in Regular, it only has an offset), so in those two masters (but not in the third) it gets caught by the DecomposeTransformedComponents filter and decomposed. If you are still reading, congratulations. |
(sorry, of course I meant post-order, not pre-order, edited post) |
If we change the order in which we traverse the component tree and do a pre-order traversal, i.e. visiting each node before visiting its children, then this particular test case gets fixed, however I am still not sure this is the right fix in general. diff --git a/Lib/ufo2ft/util.py b/Lib/ufo2ft/util.py
index 0a1511b..fad38e8 100644
--- a/Lib/ufo2ft/util.py
+++ b/Lib/ufo2ft/util.py
@@ -150,6 +150,20 @@ def deepCopyContours(
None, decompose all components of a glyph unconditionally and completely. If
passed, only completely decompose components whose baseGlyph is in the list.
"""
+ # Check if there are any contours to copy before instantiating pens.
+ if composite != parent and len(composite):
+ if transformation == Identity:
+ pen = parent.getPen()
+ else:
+ pen = TransformPen(parent.getPen(), transformation)
+ # if the transformation has a negative determinant, it will
+ # reverse the contour direction of the component
+ xx, xy, yx, yy = transformation[:4]
+ if xx * yy - xy * yx < 0:
+ pen = ReverseContourPen(pen)
+
+ for contour in composite:
+ contour.draw(pen)
for nestedComponent in composite.components:
# Because this function works recursively, test at each turn if we are going to
@@ -180,21 +194,6 @@ def deepCopyContours(
specificComponents=specificComponentsEffective,
)
- # Check if there are any contours to copy before instantiating pens.
- if composite != parent and len(composite):
- if transformation == Identity:
- pen = parent.getPen()
- else:
- pen = TransformPen(parent.getPen(), transformation)
- # if the transformation has a negative determinant, it will
- # reverse the contour direction of the component
- xx, xy, yx, yy = transformation[:4]
- if xx * yy - xy * yx < 0:
- pen = ReverseContourPen(pen)
-
- for contour in composite:
- contour.draw(pen)
-
def makeUnicodeToGlyphNameMapping(font, glyphOrder=None):
"""Make a unicode: glyph name mapping for this glyph set (dict or Font). |
no actually, they are just sorted alphabetically, see ufo2ft/Lib/ufo2ft/filters/base.py Lines 190 to 191 in 42257bc
Of course, the root of the problem is we are modifying the glyphSet we are iterating over, hence the order in which we iterate matters for operations like decomposing in which one glyph influence another; if we made a copy before applying the filter, and then collected the modified glyphs and only substituted them in the glyphSet after we have filtered them all, then we would not incur in this issue, as the reference glyphSet used for decomposing would be the original, unmodified one, before running the decomposing filter; thus, the glyph order in which perform decomposition would not matter. However, making a copy of each glyph upfront, before knowing if it would be modified or not, would be quite inefficient. diff --git a/Lib/ufo2ft/filters/base.py b/Lib/ufo2ft/filters/base.py
index 0286414..2bcd47c 100644
--- a/Lib/ufo2ft/filters/base.py
+++ b/Lib/ufo2ft/filters/base.py
@@ -3,7 +3,7 @@ from types import SimpleNamespace
from fontTools.misc.loggingTools import Timer
-from ufo2ft.util import _GlyphSet, _LazyFontName
+from ufo2ft.util import _GlyphSet, _LazyFontName, _copyGlyph
logger = logging.getLogger(__name__)
@@ -185,6 +185,7 @@ class BaseFilter:
filter_ = self.filter
include = self.include
modified = context.modified
+ copies = {}
with Timer() as t:
# we sort the glyph names to make loop deterministic
@@ -192,11 +193,17 @@ class BaseFilter:
if glyphName in modified:
continue
glyph = glyphSet[glyphName]
- if include(glyph) and filter_(glyph):
- modified.add(glyphName)
+ if include(glyph):
+ copy = _copyGlyph(glyph)
+ if filter_(copy):
+ modified.add(glyphName)
+ copies[glyphName] = copy
num = len(modified)
if num > 0:
+ for n in modified:
+ glyphSet[n] = copies[n]
+
logger.debug(
"Took %.3fs to run %s on %d glyph%s",
t, |
Wasn't this (or some other form of it) something @simoncozens recently fixed? |
no it's slightly different. #609 deals with ensuring that if a glyph was decomposed in some masters as result of DecomposeTransformedComponents filter, it is also decomposed in the rest of the masters as well. Here "exclamdown" is decomposed in all masters already at first pass, only that it gets different contour order because of <can't summarize it further> |
another way to fix this would be to change the order in which glyphs are processed by decompose filter by processing the composites with greater depth first (exclamdown before exclam) |
I see. May be we should sort the glyphs based on their use as components. I think the anchor propagation or some other code does this kind of sorting. |
... that way, we avoid the interference of decomposing a composite of lower depth before another of greater depth. If we assume there are no cycles (which would already be a font bug), composites with same depth can't reference one another. |
data copied from test font at changa.zip in #621 Thanks Marc Foley!
I've just come across a family which has an exclamdown which is constructed using a rotated exclam:
The exclam component for this glyph is constructed from both paths and components.
When the DecomposeTransformedComponentsFilter is run as a prefilter, the exclam is decomposed as one would expect.
However, when the filter decomposes the exclamdown, the path order differs.
Shotgun approaches to solve this issue are to run the filter as a post filter. This works because the exclam which has mixed paths and components will be decomposed first, https://github.com/googlefonts/ufo2ft/blob/main/Lib/ufo2ft/preProcessor.py#L337-L342. The other options is to move the https://github.com/googlefonts/ufo2ft/blob/main/Lib/ufo2ft/preProcessor.py#L337-L342 so it happens before the pre filters.
Here's a minimal testcase:
changa.zip
The text was updated successfully, but these errors were encountered: