Source code for fontTools.pens.reverseContourPen

from fontTools.misc.arrayTools import pairwise
from fontTools.pens.filterPen import ContourFilterPen

__all__ = ["reversedContour", "ReverseContourPen"]

[docs] class ReverseContourPen(ContourFilterPen): """Filter pen that passes outline data to another pen, but reversing the winding direction of all contours. Components are simply passed through unchanged. Closed contours are reversed in such a way that the first point remains the first point. """ def __init__(self, outPen, outputImpliedClosingLine=False): super().__init__(outPen) self.outputImpliedClosingLine = outputImpliedClosingLine
[docs] def filterContour(self, contour): return reversedContour(contour, self.outputImpliedClosingLine)
[docs] def reversedContour(contour, outputImpliedClosingLine=False): """Generator that takes a list of pen's (operator, operands) tuples, and yields them with the winding direction reversed. """ if not contour: return # nothing to do, stop iteration # valid contours must have at least a starting and ending command, # can't have one without the other assert len(contour) > 1, "invalid contour" # the type of the last command determines if the contour is closed contourType = contour.pop()[0] assert contourType in ("endPath", "closePath") closed = contourType == "closePath" firstType, firstPts = contour.pop(0) assert firstType in ("moveTo", "qCurveTo"), ( "invalid initial segment type: %r" % firstType ) firstOnCurve = firstPts[-1] if firstType == "qCurveTo": # special case for TrueType paths contaning only off-curve points assert firstOnCurve is None, "off-curve only paths must end with 'None'" assert not contour, "only one qCurveTo allowed per off-curve path" firstPts = (firstPts[0],) + tuple(reversed(firstPts[1:-1])) + (None,) if not contour: # contour contains only one segment, nothing to reverse if firstType == "moveTo": closed = False # single-point paths can't be closed else: closed = True # off-curve paths are closed by definition yield firstType, firstPts else: lastType, lastPts = contour[-1] lastOnCurve = lastPts[-1] if closed: # for closed paths, we keep the starting point yield firstType, firstPts if firstOnCurve != lastOnCurve: # emit an implied line between the last and first points yield "lineTo", (lastOnCurve,) contour[-1] = (lastType, tuple(lastPts[:-1]) + (firstOnCurve,)) if len(contour) > 1: secondType, secondPts = contour[0] else: # contour has only two points, the second and last are the same secondType, secondPts = lastType, lastPts if not outputImpliedClosingLine: # if a lineTo follows the initial moveTo, after reversing it # will be implied by the closePath, so we don't emit one; # unless the lineTo and moveTo overlap, in which case we keep the # duplicate points if secondType == "lineTo" and firstPts != secondPts: del contour[0] if contour: contour[-1] = (lastType, tuple(lastPts[:-1]) + secondPts) else: # for open paths, the last point will become the first yield firstType, (lastOnCurve,) contour[-1] = (lastType, tuple(lastPts[:-1]) + (firstOnCurve,)) # we iterate over all segment pairs in reverse order, and yield # each one with the off-curve points reversed (if any), and # with the on-curve point of the following segment for (curType, curPts), (_, nextPts) in pairwise(contour, reverse=True): yield curType, tuple(reversed(curPts[:-1])) + (nextPts[-1],) yield "closePath" if closed else "endPath", ()