fonttools/Tests/pens/reverseContourPen_test.py

Ignoring revisions in .git-blame-ignore-revs. Click here to bypass and see the normal blame view.

518 lines
15 KiB
Python
Raw Permalink Normal View History

from fontTools.pens.recordingPen import RecordingPen
from fontTools.pens.reverseContourPen import ReverseContourPen
import pytest
TEST_DATA = [
(
[
("moveTo", ((0, 0),)),
("lineTo", ((1, 1),)),
("lineTo", ((2, 2),)),
("lineTo", ((3, 3),)), # last not on move, line is implied
("closePath", ()),
],
False, # outputImpliedClosingLine
[
("moveTo", ((0, 0),)),
("lineTo", ((3, 3),)),
("lineTo", ((2, 2),)),
("lineTo", ((1, 1),)),
("closePath", ()),
],
),
(
[
("moveTo", ((0, 0),)),
("lineTo", ((1, 1),)),
("lineTo", ((2, 2),)),
("lineTo", ((3, 3),)), # last line does not overlap move...
("closePath", ()),
],
True, # outputImpliedClosingLine
[
("moveTo", ((0, 0),)),
("lineTo", ((3, 3),)),
("lineTo", ((2, 2),)),
("lineTo", ((1, 1),)),
("lineTo", ((0, 0),)), # ... but closing line is NOT implied
("closePath", ()),
],
),
(
[
("moveTo", ((0, 0),)),
("lineTo", ((1, 1),)),
("lineTo", ((2, 2),)),
("lineTo", ((0, 0),)), # last line overlaps move, explicit line
("closePath", ()),
],
False,
[
("moveTo", ((0, 0),)),
("lineTo", ((2, 2),)),
("lineTo", ((1, 1),)),
("closePath", ()), # closing line implied
],
),
(
[
("moveTo", ((0, 0),)),
("lineTo", ((1, 1),)),
("lineTo", ((2, 2),)),
("lineTo", ((0, 0),)), # last line overlaps move...
("closePath", ()),
],
True,
[
("moveTo", ((0, 0),)),
("lineTo", ((2, 2),)),
("lineTo", ((1, 1),)),
("lineTo", ((0, 0),)), # ... but line is NOT implied
("closePath", ()),
],
),
(
[
("moveTo", ((0, 0),)),
("lineTo", ((0, 0),)), # duplicate lineTo following moveTo
("lineTo", ((1, 1),)),
("lineTo", ((2, 2),)),
("closePath", ()),
],
False,
[
("moveTo", ((0, 0),)),
("lineTo", ((2, 2),)),
("lineTo", ((1, 1),)),
("lineTo", ((0, 0),)), # extra explicit lineTo is always emitted to
("lineTo", ((0, 0),)), # disambiguate from an implicit closing line
("closePath", ()),
],
),
(
[
("moveTo", ((0, 0),)),
("lineTo", ((0, 0),)), # duplicate lineTo following moveTo
("lineTo", ((1, 1),)),
("lineTo", ((2, 2),)),
("closePath", ()),
],
True,
[
("moveTo", ((0, 0),)),
("lineTo", ((2, 2),)),
("lineTo", ((1, 1),)),
("lineTo", ((0, 0),)), # duplicate lineTo is retained also in this case,
("lineTo", ((0, 0),)), # same result as with outputImpliedClosingLine=False
("closePath", ()),
],
),
(
[
("moveTo", ((0, 0),)),
("lineTo", ((1, 1),)),
("closePath", ()),
],
False,
[
("moveTo", ((0, 0),)),
("lineTo", ((1, 1),)),
("closePath", ()),
],
),
(
[
("moveTo", ((0, 0),)),
("lineTo", ((1, 1),)),
("closePath", ()),
],
True,
[
("moveTo", ((0, 0),)),
("lineTo", ((1, 1),)),
("lineTo", ((0, 0),)),
("closePath", ()),
],
),
(
[
("moveTo", ((0, 0),)),
("curveTo", ((1, 1), (2, 2), (3, 3))),
("curveTo", ((4, 4), (5, 5), (0, 0))), # closed curveTo overlaps moveTo
("closePath", ()),
],
False,
[
("moveTo", ((0, 0),)), # no extra lineTo added here
("curveTo", ((5, 5), (4, 4), (3, 3))),
("curveTo", ((2, 2), (1, 1), (0, 0))),
("closePath", ()),
],
),
(
[
("moveTo", ((0, 0),)),
("curveTo", ((1, 1), (2, 2), (3, 3))),
("curveTo", ((4, 4), (5, 5), (0, 0))), # closed curveTo overlaps moveTo
("closePath", ()),
],
True,
[
("moveTo", ((0, 0),)), # no extra lineTo added here, same as preceding
("curveTo", ((5, 5), (4, 4), (3, 3))),
("curveTo", ((2, 2), (1, 1), (0, 0))),
("closePath", ()),
],
),
(
[
("moveTo", ((0, 0),)),
("curveTo", ((1, 1), (2, 2), (3, 3))),
("curveTo", ((4, 4), (5, 5), (6, 6))), # closed curve not overlapping move
("closePath", ()),
],
False,
[
("moveTo", ((0, 0),)),
("lineTo", ((6, 6),)), # the previously implied line
("curveTo", ((5, 5), (4, 4), (3, 3))),
("curveTo", ((2, 2), (1, 1), (0, 0))),
("closePath", ()),
],
),
(
[
("moveTo", ((0, 0),)),
("curveTo", ((1, 1), (2, 2), (3, 3))),
("curveTo", ((4, 4), (5, 5), (6, 6))), # closed curve not overlapping move
("closePath", ()),
],
True,
[
("moveTo", ((0, 0),)),
("lineTo", ((6, 6),)), # the previously implied line (same as above)
("curveTo", ((5, 5), (4, 4), (3, 3))),
("curveTo", ((2, 2), (1, 1), (0, 0))),
("closePath", ()),
],
),
(
[
("moveTo", ((0, 0),)),
("lineTo", ((1, 1),)), # this line becomes implied
("curveTo", ((2, 2), (3, 3), (4, 4))),
("curveTo", ((5, 5), (6, 6), (7, 7))),
("closePath", ()),
],
False,
[
("moveTo", ((0, 0),)),
("lineTo", ((7, 7),)),
("curveTo", ((6, 6), (5, 5), (4, 4))),
("curveTo", ((3, 3), (2, 2), (1, 1))),
("closePath", ()),
],
),
(
[
("moveTo", ((0, 0),)),
("lineTo", ((1, 1),)), # this line...
("curveTo", ((2, 2), (3, 3), (4, 4))),
("curveTo", ((5, 5), (6, 6), (7, 7))),
("closePath", ()),
],
True,
[
("moveTo", ((0, 0),)),
("lineTo", ((7, 7),)),
("curveTo", ((6, 6), (5, 5), (4, 4))),
("curveTo", ((3, 3), (2, 2), (1, 1))),
("lineTo", ((0, 0),)), # ... does NOT become implied
("closePath", ()),
],
),
(
[
("moveTo", ((0, 0),)),
("qCurveTo", ((1, 1), (2, 2))),
("qCurveTo", ((3, 3), (0, 0))), # closed qCurve overlaps move
("closePath", ()),
],
False,
[
("moveTo", ((0, 0),)), # no extra lineTo added here
("qCurveTo", ((3, 3), (2, 2))),
("qCurveTo", ((1, 1), (0, 0))),
("closePath", ()),
],
),
(
[
("moveTo", ((0, 0),)),
("qCurveTo", ((1, 1), (2, 2))),
("qCurveTo", ((3, 3), (0, 0))), # closed qCurve overlaps move
("closePath", ()),
],
True, # <--
[
("moveTo", ((0, 0),)), # no extra lineTo added here, same as above
("qCurveTo", ((3, 3), (2, 2))),
("qCurveTo", ((1, 1), (0, 0))),
("closePath", ()),
],
),
(
[
("moveTo", ((0, 0),)),
("qCurveTo", ((1, 1), (2, 2))),
("qCurveTo", ((3, 3), (4, 4))), # closed qCurve not overlapping move
("closePath", ()),
],
False,
[
("moveTo", ((0, 0),)),
("lineTo", ((4, 4),)), # the previously implied line
("qCurveTo", ((3, 3), (2, 2))),
("qCurveTo", ((1, 1), (0, 0))),
("closePath", ()),
],
),
(
[
("moveTo", ((0, 0),)),
("qCurveTo", ((1, 1), (2, 2))),
("qCurveTo", ((3, 3), (4, 4))), # closed qCurve not overlapping move
("closePath", ()),
],
True,
[
("moveTo", ((0, 0),)),
("lineTo", ((4, 4),)), # the previously implied line (same as above)
("qCurveTo", ((3, 3), (2, 2))),
("qCurveTo", ((1, 1), (0, 0))),
("closePath", ()),
],
),
(
[
("moveTo", ((0, 0),)),
("lineTo", ((1, 1),)),
("qCurveTo", ((2, 2), (3, 3))),
("closePath", ()),
],
False,
[
("moveTo", ((0, 0),)),
("lineTo", ((3, 3),)),
("qCurveTo", ((2, 2), (1, 1))),
("closePath", ()),
],
),
(
[("addComponent", ("a", (1, 0, 0, 1, 0, 0)))],
False,
[("addComponent", ("a", (1, 0, 0, 1, 0, 0)))],
),
([], False, []),
(
[
("moveTo", ((0, 0),)),
("endPath", ()),
],
False,
[
("moveTo", ((0, 0),)),
("endPath", ()),
],
),
(
[
("moveTo", ((0, 0),)),
("closePath", ()),
],
False,
[
("moveTo", ((0, 0),)),
("endPath", ()), # single-point paths is always open
],
),
(
[("moveTo", ((0, 0),)), ("lineTo", ((1, 1),)), ("endPath", ())],
False,
[("moveTo", ((1, 1),)), ("lineTo", ((0, 0),)), ("endPath", ())],
),
(
[("moveTo", ((0, 0),)), ("curveTo", ((1, 1), (2, 2), (3, 3))), ("endPath", ())],
False,
[("moveTo", ((3, 3),)), ("curveTo", ((2, 2), (1, 1), (0, 0))), ("endPath", ())],
),
(
[
("moveTo", ((0, 0),)),
("curveTo", ((1, 1), (2, 2), (3, 3))),
("lineTo", ((4, 4),)),
("endPath", ()),
],
False,
[
("moveTo", ((4, 4),)),
("lineTo", ((3, 3),)),
("curveTo", ((2, 2), (1, 1), (0, 0))),
("endPath", ()),
],
),
(
[
("moveTo", ((0, 0),)),
("lineTo", ((1, 1),)),
("curveTo", ((2, 2), (3, 3), (4, 4))),
("endPath", ()),
],
False,
[
("moveTo", ((4, 4),)),
("curveTo", ((3, 3), (2, 2), (1, 1))),
("lineTo", ((0, 0),)),
("endPath", ()),
],
),
(
[("qCurveTo", ((0, 0), (1, 1), (2, 2), None)), ("closePath", ())],
False,
[("qCurveTo", ((0, 0), (2, 2), (1, 1), None)), ("closePath", ())],
),
(
[("qCurveTo", ((0, 0), (1, 1), (2, 2), None)), ("endPath", ())],
False,
[
("qCurveTo", ((0, 0), (2, 2), (1, 1), None)),
("closePath", ()), # this is always "closed"
],
),
# Test case from:
# https://github.com/googlei18n/cu2qu/issues/51#issue-179370514
(
[
("moveTo", ((848, 348),)),
("lineTo", ((848, 348),)), # duplicate lineTo point after moveTo
("qCurveTo", ((848, 526), (649, 704), (449, 704))),
("qCurveTo", ((449, 704), (248, 704), (50, 526), (50, 348))),
("lineTo", ((50, 348),)),
("qCurveTo", ((50, 348), (50, 171), (248, -3), (449, -3))),
("qCurveTo", ((449, -3), (649, -3), (848, 171), (848, 348))),
("closePath", ()),
],
False,
[
("moveTo", ((848, 348),)),
("qCurveTo", ((848, 171), (649, -3), (449, -3), (449, -3))),
("qCurveTo", ((248, -3), (50, 171), (50, 348), (50, 348))),
("lineTo", ((50, 348),)),
("qCurveTo", ((50, 526), (248, 704), (449, 704), (449, 704))),
("qCurveTo", ((649, 704), (848, 526), (848, 348))),
("lineTo", ((848, 348),)), # the duplicate point is kept
("closePath", ()),
],
),
# Test case from https://github.com/googlefonts/fontmake/issues/572
# An additional closing lineTo is required to disambiguate a duplicate
# point at the end of a contour from the implied closing line.
(
[
("moveTo", ((0, 651),)),
("lineTo", ((0, 101),)),
("lineTo", ((0, 101),)),
("lineTo", ((0, 651),)),
("lineTo", ((0, 651),)),
("closePath", ()),
],
False,
[
("moveTo", ((0, 651),)),
("lineTo", ((0, 651),)),
("lineTo", ((0, 101),)),
("lineTo", ((0, 101),)),
("closePath", ()),
],
),
(
[
("moveTo", ((0, 651),)),
("lineTo", ((0, 101),)),
("lineTo", ((0, 101),)),
("lineTo", ((0, 651),)),
("lineTo", ((0, 651),)),
("closePath", ()),
],
True,
[
("moveTo", ((0, 651),)),
("lineTo", ((0, 651),)),
("lineTo", ((0, 101),)),
("lineTo", ((0, 101),)),
("lineTo", ((0, 651),)), # closing line not implied
("closePath", ()),
],
),
]
@pytest.mark.parametrize("contour, outputImpliedClosingLine, expected", TEST_DATA)
def test_reverse_pen(contour, outputImpliedClosingLine, expected):
recpen = RecordingPen()
revpen = ReverseContourPen(recpen, outputImpliedClosingLine)
for operator, operands in contour:
getattr(revpen, operator)(*operands)
assert recpen.value == expected
2022-12-13 11:26:36 +00:00
def test_reverse_pen_outputImpliedClosingLine():
recpen = RecordingPen()
revpen = ReverseContourPen(recpen)
revpen.moveTo((0, 0))
revpen.lineTo((10, 0))
revpen.lineTo((0, 10))
revpen.lineTo((0, 0))
revpen.closePath()
assert recpen.value == [
("moveTo", ((0, 0),)),
("lineTo", ((0, 10),)),
("lineTo", ((10, 0),)),
# ("lineTo", ((0, 0),)), # implied
("closePath", ()),
]
recpen = RecordingPen()
revpen = ReverseContourPen(recpen, outputImpliedClosingLine=True)
revpen.moveTo((0, 0))
revpen.lineTo((10, 0))
revpen.lineTo((0, 10))
revpen.lineTo((0, 0))
revpen.closePath()
assert recpen.value == [
("moveTo", ((0, 0),)),
("lineTo", ((0, 10),)),
("lineTo", ((10, 0),)),
("lineTo", ((0, 0),)), # not implied
("closePath", ()),
]
2022-12-13 11:26:36 +00:00
@pytest.mark.parametrize("contour, outputImpliedClosingLine, expected", TEST_DATA)
def test_reverse_point_pen(contour, outputImpliedClosingLine, expected):
from fontTools.pens.pointPen import (
ReverseContourPointPen,
PointToSegmentPen,
SegmentToPointPen,
)
recpen = RecordingPen()
pt2seg = PointToSegmentPen(recpen, outputImpliedClosingLine)
revpen = ReverseContourPointPen(pt2seg)
seg2pt = SegmentToPointPen(revpen)
for operator, operands in contour:
getattr(seg2pt, operator)(*operands)
assert recpen.value == expected