xref: /aosp_15_r20/external/fonttools/Lib/fontTools/pens/reverseContourPen.py (revision e1fe3e4ad2793916b15cccdc4a7da52a7e1dd0e9)
1*e1fe3e4aSElliott Hughesfrom fontTools.misc.arrayTools import pairwise
2*e1fe3e4aSElliott Hughesfrom fontTools.pens.filterPen import ContourFilterPen
3*e1fe3e4aSElliott Hughes
4*e1fe3e4aSElliott Hughes
5*e1fe3e4aSElliott Hughes__all__ = ["reversedContour", "ReverseContourPen"]
6*e1fe3e4aSElliott Hughes
7*e1fe3e4aSElliott Hughes
8*e1fe3e4aSElliott Hughesclass ReverseContourPen(ContourFilterPen):
9*e1fe3e4aSElliott Hughes    """Filter pen that passes outline data to another pen, but reversing
10*e1fe3e4aSElliott Hughes    the winding direction of all contours. Components are simply passed
11*e1fe3e4aSElliott Hughes    through unchanged.
12*e1fe3e4aSElliott Hughes
13*e1fe3e4aSElliott Hughes    Closed contours are reversed in such a way that the first point remains
14*e1fe3e4aSElliott Hughes    the first point.
15*e1fe3e4aSElliott Hughes    """
16*e1fe3e4aSElliott Hughes
17*e1fe3e4aSElliott Hughes    def __init__(self, outPen, outputImpliedClosingLine=False):
18*e1fe3e4aSElliott Hughes        super().__init__(outPen)
19*e1fe3e4aSElliott Hughes        self.outputImpliedClosingLine = outputImpliedClosingLine
20*e1fe3e4aSElliott Hughes
21*e1fe3e4aSElliott Hughes    def filterContour(self, contour):
22*e1fe3e4aSElliott Hughes        return reversedContour(contour, self.outputImpliedClosingLine)
23*e1fe3e4aSElliott Hughes
24*e1fe3e4aSElliott Hughes
25*e1fe3e4aSElliott Hughesdef reversedContour(contour, outputImpliedClosingLine=False):
26*e1fe3e4aSElliott Hughes    """Generator that takes a list of pen's (operator, operands) tuples,
27*e1fe3e4aSElliott Hughes    and yields them with the winding direction reversed.
28*e1fe3e4aSElliott Hughes    """
29*e1fe3e4aSElliott Hughes    if not contour:
30*e1fe3e4aSElliott Hughes        return  # nothing to do, stop iteration
31*e1fe3e4aSElliott Hughes
32*e1fe3e4aSElliott Hughes    # valid contours must have at least a starting and ending command,
33*e1fe3e4aSElliott Hughes    # can't have one without the other
34*e1fe3e4aSElliott Hughes    assert len(contour) > 1, "invalid contour"
35*e1fe3e4aSElliott Hughes
36*e1fe3e4aSElliott Hughes    # the type of the last command determines if the contour is closed
37*e1fe3e4aSElliott Hughes    contourType = contour.pop()[0]
38*e1fe3e4aSElliott Hughes    assert contourType in ("endPath", "closePath")
39*e1fe3e4aSElliott Hughes    closed = contourType == "closePath"
40*e1fe3e4aSElliott Hughes
41*e1fe3e4aSElliott Hughes    firstType, firstPts = contour.pop(0)
42*e1fe3e4aSElliott Hughes    assert firstType in ("moveTo", "qCurveTo"), (
43*e1fe3e4aSElliott Hughes        "invalid initial segment type: %r" % firstType
44*e1fe3e4aSElliott Hughes    )
45*e1fe3e4aSElliott Hughes    firstOnCurve = firstPts[-1]
46*e1fe3e4aSElliott Hughes    if firstType == "qCurveTo":
47*e1fe3e4aSElliott Hughes        # special case for TrueType paths contaning only off-curve points
48*e1fe3e4aSElliott Hughes        assert firstOnCurve is None, "off-curve only paths must end with 'None'"
49*e1fe3e4aSElliott Hughes        assert not contour, "only one qCurveTo allowed per off-curve path"
50*e1fe3e4aSElliott Hughes        firstPts = (firstPts[0],) + tuple(reversed(firstPts[1:-1])) + (None,)
51*e1fe3e4aSElliott Hughes
52*e1fe3e4aSElliott Hughes    if not contour:
53*e1fe3e4aSElliott Hughes        # contour contains only one segment, nothing to reverse
54*e1fe3e4aSElliott Hughes        if firstType == "moveTo":
55*e1fe3e4aSElliott Hughes            closed = False  # single-point paths can't be closed
56*e1fe3e4aSElliott Hughes        else:
57*e1fe3e4aSElliott Hughes            closed = True  # off-curve paths are closed by definition
58*e1fe3e4aSElliott Hughes        yield firstType, firstPts
59*e1fe3e4aSElliott Hughes    else:
60*e1fe3e4aSElliott Hughes        lastType, lastPts = contour[-1]
61*e1fe3e4aSElliott Hughes        lastOnCurve = lastPts[-1]
62*e1fe3e4aSElliott Hughes        if closed:
63*e1fe3e4aSElliott Hughes            # for closed paths, we keep the starting point
64*e1fe3e4aSElliott Hughes            yield firstType, firstPts
65*e1fe3e4aSElliott Hughes            if firstOnCurve != lastOnCurve:
66*e1fe3e4aSElliott Hughes                # emit an implied line between the last and first points
67*e1fe3e4aSElliott Hughes                yield "lineTo", (lastOnCurve,)
68*e1fe3e4aSElliott Hughes                contour[-1] = (lastType, tuple(lastPts[:-1]) + (firstOnCurve,))
69*e1fe3e4aSElliott Hughes
70*e1fe3e4aSElliott Hughes            if len(contour) > 1:
71*e1fe3e4aSElliott Hughes                secondType, secondPts = contour[0]
72*e1fe3e4aSElliott Hughes            else:
73*e1fe3e4aSElliott Hughes                # contour has only two points, the second and last are the same
74*e1fe3e4aSElliott Hughes                secondType, secondPts = lastType, lastPts
75*e1fe3e4aSElliott Hughes
76*e1fe3e4aSElliott Hughes            if not outputImpliedClosingLine:
77*e1fe3e4aSElliott Hughes                # if a lineTo follows the initial moveTo, after reversing it
78*e1fe3e4aSElliott Hughes                # will be implied by the closePath, so we don't emit one;
79*e1fe3e4aSElliott Hughes                # unless the lineTo and moveTo overlap, in which case we keep the
80*e1fe3e4aSElliott Hughes                # duplicate points
81*e1fe3e4aSElliott Hughes                if secondType == "lineTo" and firstPts != secondPts:
82*e1fe3e4aSElliott Hughes                    del contour[0]
83*e1fe3e4aSElliott Hughes                    if contour:
84*e1fe3e4aSElliott Hughes                        contour[-1] = (lastType, tuple(lastPts[:-1]) + secondPts)
85*e1fe3e4aSElliott Hughes        else:
86*e1fe3e4aSElliott Hughes            # for open paths, the last point will become the first
87*e1fe3e4aSElliott Hughes            yield firstType, (lastOnCurve,)
88*e1fe3e4aSElliott Hughes            contour[-1] = (lastType, tuple(lastPts[:-1]) + (firstOnCurve,))
89*e1fe3e4aSElliott Hughes
90*e1fe3e4aSElliott Hughes        # we iterate over all segment pairs in reverse order, and yield
91*e1fe3e4aSElliott Hughes        # each one with the off-curve points reversed (if any), and
92*e1fe3e4aSElliott Hughes        # with the on-curve point of the following segment
93*e1fe3e4aSElliott Hughes        for (curType, curPts), (_, nextPts) in pairwise(contour, reverse=True):
94*e1fe3e4aSElliott Hughes            yield curType, tuple(reversed(curPts[:-1])) + (nextPts[-1],)
95*e1fe3e4aSElliott Hughes
96*e1fe3e4aSElliott Hughes    yield "closePath" if closed else "endPath", ()
97