From bddedc1bc1429e5a0dbc622d3d8cd3af6141154d Mon Sep 17 00:00:00 2001 From: Cosimo Lupo Date: Fri, 24 Jan 2025 18:05:18 +0000 Subject: [PATCH] restrict the rounding to where strictly needed we don't need to round all simple glyphs' coordinates (which may be costly), only when these are used as components with an interesting transform --- Lib/fontTools/ttLib/tables/_g_l_y_f.py | 36 +++++++++++++++++++------- 1 file changed, 27 insertions(+), 9 deletions(-) diff --git a/Lib/fontTools/ttLib/tables/_g_l_y_f.py b/Lib/fontTools/ttLib/tables/_g_l_y_f.py index 095bd3177..eaa9920f0 100644 --- a/Lib/fontTools/ttLib/tables/_g_l_y_f.py +++ b/Lib/fontTools/ttLib/tables/_g_l_y_f.py @@ -1206,9 +1206,7 @@ class Glyph(object): Return True if bounds were calculated, False otherwise. """ for compo in self.components: - if hasattr(compo, "firstPt") or hasattr(compo, "transform"): - return False - if not float(compo.x).is_integer() or not float(compo.y).is_integer(): + if not compo._hasOnlyIntegerTranslate(): return False # All components are untransformed and have an integer x/y translate @@ -1241,7 +1239,7 @@ class Glyph(object): else: return self.numberOfContours == -1 - def getCoordinates(self, glyfTable, round=noRound): + def getCoordinates(self, glyfTable, *, round=noRound): """Return the coordinates, end points and flags This method returns three values: A :py:class:`GlyphCoordinates` object, @@ -1258,11 +1256,7 @@ class Glyph(object): """ if self.numberOfContours > 0: - coordinates = self.coordinates - if round is not noRound: - coordinates = GlyphCoordinates(coordinates) - coordinates.toInt(round=round) - return coordinates, self.endPtsOfContours, self.flags + return self.coordinates, self.endPtsOfContours, self.flags elif self.isComposite(): # it's a composite allCoords = GlyphCoordinates() @@ -1280,6 +1274,18 @@ class Glyph(object): % compo.glyphName ) coordinates = GlyphCoordinates(coordinates) + # if asked to round e.g. while computing bboxes, it's important we + # do it immediately before a component transform is applied to a + # simple glyph's coordinates in case these might still contain floats; + # however, if the referenced component glyph is another composite, we + # must not round here but only at the end, after all the nested + # transforms have been applied, or else rounding errors will compound. + if ( + round is not noRound + and g.numberOfContours > 0 + and not compo._hasOnlyIntegerTranslate() + ): + coordinates.toInt(round=round) if hasattr(compo, "firstPt"): # component uses two reference points: we apply the transform _before_ # computing the offset between the points @@ -1936,6 +1942,18 @@ class GlyphComponent(object): result = self.__eq__(other) return result if result is NotImplemented else not result + def _hasOnlyIntegerTranslate(self): + """Return True if it's a 'simple' component. + + That is, it has no anchor points and no transform other than integer translate. + """ + return ( + not hasattr(self, "firstPt") + and not hasattr(self, "transform") + and float(self.x).is_integer() + and float(self.y).is_integer() + ) + class GlyphCoordinates(object): """A list of glyph coordinates.