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