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
of the same glyph impliable, and thus only the on-curve points that are impliable
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.
Args:
@ -1549,16 +1551,40 @@ def dropImpliedOnCurvePoints(*interpolatable_glyphs: Glyph) -> Set[int]:
Returns:
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:
https://developer.apple.com/fonts/TrueType-Reference-Manual/RM01/Chap1.html
"""
assert len(interpolatable_glyphs) > 0
numContours = None
flags = 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()
start = 0
flags = glyph.flags
coords = glyph.coordinates
for last in glyph.endPtsOfContours:
for i in range(start, last + 1):
@ -1583,9 +1609,11 @@ def dropImpliedOnCurvePoints(*interpolatable_glyphs: Glyph) -> Set[int]:
else:
drop.intersection_update(may_drop)
simple_glyphs.append(glyph)
if drop:
# Do the actual dropping
for glyph in interpolatable_glyphs:
for glyph in simple_glyphs:
coords = glyph.coordinates
glyph.coordinates = GlyphCoordinates(
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
glyph.endPtsOfContours = newEndPts
return drop
return drop if drop is not None else set()
class GlyphComponent(object):

View File

@ -860,13 +860,17 @@ def test_dropImpliedOnCurvePoints_all_quad_off_curves():
],
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.coordinates == GlyphCoordinates([(1, 1), (1, -1), (-1, -1), (-1, 1)])
assert glyph2.coordinates == GlyphCoordinates([(2, 2), (2, -2), (-2, -2), (-2, 2)])
assert glyph1.endPtsOfContours == glyph2.endPtsOfContours == [3]
assert glyph3.numberOfContours == 0
def test_dropImpliedOnCurvePoints_all_cubic_off_curves():
@ -890,8 +894,10 @@ def test_dropImpliedOnCurvePoints_all_cubic_off_curves():
],
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.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)]
)
assert glyph1.endPtsOfContours == glyph2.endPtsOfContours == [7]
assert glyph3.numberOfContours == 0
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])
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__":
import sys