dropImpliedOnCurvePoints: raise if incompatible, skip empty/composites

This commit is contained in:
Cosimo Lupo 2023-06-02 13:51:28 +01:00
parent 84cebca6a1
commit 6a276d9f6a
No known key found for this signature in database
GPG Key ID: DF65A8A5A119C9A8
2 changed files with 86 additions and 8 deletions

View File

@ -1541,6 +1541,8 @@ def dropImpliedOnCurvePoints(*interpolatable_glyphs: Glyph) -> Set[int]:
If more than one glyphs are passed, these are assumed to be interpolatable masters If more than one glyphs are passed, these are assumed to be interpolatable masters
of the same glyph impliable, and thus only the on-curve points that are impliable of the same glyph impliable, and thus only the on-curve points that are impliable
for all of them will actually be implied. for all of them will actually be implied.
Composite glyphs or empty glyphs are skipped, only simple glyphs with 1 or more
contours are considered.
The input glyph(s) is/are modified in-place. The input glyph(s) is/are modified in-place.
Args: Args:
@ -1549,16 +1551,40 @@ def dropImpliedOnCurvePoints(*interpolatable_glyphs: Glyph) -> Set[int]:
Returns: Returns:
The set of point indices that were dropped if any. The set of point indices that were dropped if any.
Raises:
ValueError if simple glyphs are not in fact interpolatable because they have
different point flags or number of contours.
Reference: Reference:
https://developer.apple.com/fonts/TrueType-Reference-Manual/RM01/Chap1.html https://developer.apple.com/fonts/TrueType-Reference-Manual/RM01/Chap1.html
""" """
assert len(interpolatable_glyphs) > 0 numContours = None
flags = None
drop = None drop = None
for glyph in interpolatable_glyphs: simple_glyphs = []
for i, glyph in enumerate(interpolatable_glyphs):
if glyph.numberOfContours < 1:
# ignore composite or empty glyphs
continue
if numContours is None:
numContours = glyph.numberOfContours
elif glyph.numberOfContours != numContours:
raise ValueError(
f"Incompatible number of contours for glyph at master index {i}: "
f"expected {numContours}, found {glyph.numberOfContours}"
)
if flags is None:
flags = glyph.flags
elif glyph.flags != flags:
raise ValueError(
f"Incompatible flags for simple glyph at master index {i}: "
f"expected {flags}, found {glyph.flags}"
)
may_drop = set() may_drop = set()
start = 0 start = 0
flags = glyph.flags
coords = glyph.coordinates coords = glyph.coordinates
for last in glyph.endPtsOfContours: for last in glyph.endPtsOfContours:
for i in range(start, last + 1): for i in range(start, last + 1):
@ -1583,9 +1609,11 @@ def dropImpliedOnCurvePoints(*interpolatable_glyphs: Glyph) -> Set[int]:
else: else:
drop.intersection_update(may_drop) drop.intersection_update(may_drop)
simple_glyphs.append(glyph)
if drop: if drop:
# Do the actual dropping # Do the actual dropping
for glyph in interpolatable_glyphs: for glyph in simple_glyphs:
coords = glyph.coordinates coords = glyph.coordinates
glyph.coordinates = GlyphCoordinates( glyph.coordinates = GlyphCoordinates(
coords[i] for i in range(len(coords)) if i not in drop coords[i] for i in range(len(coords)) if i not in drop
@ -1608,7 +1636,7 @@ def dropImpliedOnCurvePoints(*interpolatable_glyphs: Glyph) -> Set[int]:
i += 1 i += 1
glyph.endPtsOfContours = newEndPts glyph.endPtsOfContours = newEndPts
return drop return drop if drop is not None else set()
class GlyphComponent(object): class GlyphComponent(object):

View File

@ -860,13 +860,17 @@ def test_dropImpliedOnCurvePoints_all_quad_off_curves():
], ],
Transform().scale(2.0), Transform().scale(2.0),
) )
# also add an empty glyph (will be ignored); we use this trick for 'sparse' masters
glyph3 = Glyph()
glyph3.numberOfContours = 0
assert dropImpliedOnCurvePoints(glyph1, glyph2) == {0, 2, 4, 6} assert dropImpliedOnCurvePoints(glyph1, glyph2, glyph3) == {0, 2, 4, 6}
assert glyph1.flags == glyph2.flags == array.array("B", [0, 0, 0, 0]) assert glyph1.flags == glyph2.flags == array.array("B", [0, 0, 0, 0])
assert glyph1.coordinates == GlyphCoordinates([(1, 1), (1, -1), (-1, -1), (-1, 1)]) assert glyph1.coordinates == GlyphCoordinates([(1, 1), (1, -1), (-1, -1), (-1, 1)])
assert glyph2.coordinates == GlyphCoordinates([(2, 2), (2, -2), (-2, -2), (-2, 2)]) assert glyph2.coordinates == GlyphCoordinates([(2, 2), (2, -2), (-2, -2), (-2, 2)])
assert glyph1.endPtsOfContours == glyph2.endPtsOfContours == [3] assert glyph1.endPtsOfContours == glyph2.endPtsOfContours == [3]
assert glyph3.numberOfContours == 0
def test_dropImpliedOnCurvePoints_all_cubic_off_curves(): def test_dropImpliedOnCurvePoints_all_cubic_off_curves():
@ -890,8 +894,10 @@ def test_dropImpliedOnCurvePoints_all_cubic_off_curves():
], ],
Transform().translate(10.0), Transform().translate(10.0),
) )
glyph3 = Glyph()
glyph3.numberOfContours = 0
assert dropImpliedOnCurvePoints(glyph1, glyph2) == {0, 3, 6, 9} assert dropImpliedOnCurvePoints(glyph1, glyph2, glyph3) == {0, 3, 6, 9}
assert glyph1.flags == glyph2.flags == array.array("B", [flagCubic] * 8) assert glyph1.flags == glyph2.flags == array.array("B", [flagCubic] * 8)
assert glyph1.coordinates == GlyphCoordinates( assert glyph1.coordinates == GlyphCoordinates(
@ -901,6 +907,7 @@ def test_dropImpliedOnCurvePoints_all_cubic_off_curves():
[(11, 1), (11, 1), (11, -1), (11, -1), (9, -1), (9, -1), (9, 1), (9, 1)] [(11, 1), (11, 1), (11, -1), (11, -1), (9, -1), (9, -1), (9, 1), (9, 1)]
) )
assert glyph1.endPtsOfContours == glyph2.endPtsOfContours == [7] assert glyph1.endPtsOfContours == glyph2.endPtsOfContours == [7]
assert glyph3.numberOfContours == 0
def test_dropImpliedOnCurvePoints_not_all_impliable(): def test_dropImpliedOnCurvePoints_not_all_impliable():
@ -936,6 +943,49 @@ def test_dropImpliedOnCurvePoints_not_all_impliable():
assert glyph2.flags == array.array("B", [0, flagOnCurve, 0, 0, 0]) assert glyph2.flags == array.array("B", [0, flagOnCurve, 0, 0, 0])
def test_dropImpliedOnCurvePoints_all_empty_glyphs():
glyph1 = Glyph()
glyph1.numberOfContours = 0
glyph2 = Glyph()
glyph2.numberOfContours = 0
assert dropImpliedOnCurvePoints(glyph1, glyph2) == set()
def test_dropImpliedOnCurvePoints_incompatible_number_of_contours():
glyph1 = Glyph()
glyph1.numberOfContours = 1
glyph1.endPtsOfContours = [3]
glyph1.flags = array.array("B", [1, 1, 1, 1])
glyph1.coordinates = GlyphCoordinates([(0, 0), (1, 1), (2, 2), (3, 3)])
glyph2 = Glyph()
glyph2.numberOfContours = 2
glyph2.endPtsOfContours = [1, 3]
glyph2.flags = array.array("B", [1, 1, 1, 1])
glyph2.coordinates = GlyphCoordinates([(0, 0), (1, 1), (2, 2), (3, 3)])
with pytest.raises(ValueError, match="Incompatible number of contours"):
dropImpliedOnCurvePoints(glyph1, glyph2)
def test_dropImpliedOnCurvePoints_incompatible_flags():
glyph1 = Glyph()
glyph1.numberOfContours = 1
glyph1.endPtsOfContours = [3]
glyph1.flags = array.array("B", [1, 1, 1, 1])
glyph1.coordinates = GlyphCoordinates([(0, 0), (1, 1), (2, 2), (3, 3)])
glyph2 = Glyph()
glyph2.numberOfContours = 1
glyph2.endPtsOfContours = [3]
glyph2.flags = array.array("B", [0, 0, 0, 0])
glyph2.coordinates = GlyphCoordinates([(0, 0), (1, 1), (2, 2), (3, 3)])
with pytest.raises(ValueError, match="Incompatible flags"):
dropImpliedOnCurvePoints(glyph1, glyph2)
if __name__ == "__main__": if __name__ == "__main__":
import sys import sys