1from fontTools.pens.basePen import AbstractPen 2from fontTools.pens.pointPen import AbstractPointPen 3from fontTools.pens.recordingPen import RecordingPen 4 5 6class _PassThruComponentsMixin(object): 7 8 def addComponent(self, glyphName, transformation, **kwargs): 9 self._outPen.addComponent(glyphName, transformation, **kwargs) 10 11 12class FilterPen(_PassThruComponentsMixin, AbstractPen): 13 14 """ Base class for pens that apply some transformation to the coordinates 15 they receive and pass them to another pen. 16 17 You can override any of its methods. The default implementation does 18 nothing, but passes the commands unmodified to the other pen. 19 20 >>> from fontTools.pens.recordingPen import RecordingPen 21 >>> rec = RecordingPen() 22 >>> pen = FilterPen(rec) 23 >>> v = iter(rec.value) 24 25 >>> pen.moveTo((0, 0)) 26 >>> next(v) 27 ('moveTo', ((0, 0),)) 28 29 >>> pen.lineTo((1, 1)) 30 >>> next(v) 31 ('lineTo', ((1, 1),)) 32 33 >>> pen.curveTo((2, 2), (3, 3), (4, 4)) 34 >>> next(v) 35 ('curveTo', ((2, 2), (3, 3), (4, 4))) 36 37 >>> pen.qCurveTo((5, 5), (6, 6), (7, 7), (8, 8)) 38 >>> next(v) 39 ('qCurveTo', ((5, 5), (6, 6), (7, 7), (8, 8))) 40 41 >>> pen.closePath() 42 >>> next(v) 43 ('closePath', ()) 44 45 >>> pen.moveTo((9, 9)) 46 >>> next(v) 47 ('moveTo', ((9, 9),)) 48 49 >>> pen.endPath() 50 >>> next(v) 51 ('endPath', ()) 52 53 >>> pen.addComponent('foo', (1, 0, 0, 1, 0, 0)) 54 >>> next(v) 55 ('addComponent', ('foo', (1, 0, 0, 1, 0, 0))) 56 """ 57 58 def __init__(self, outPen): 59 self._outPen = outPen 60 61 def moveTo(self, pt): 62 self._outPen.moveTo(pt) 63 64 def lineTo(self, pt): 65 self._outPen.lineTo(pt) 66 67 def curveTo(self, *points): 68 self._outPen.curveTo(*points) 69 70 def qCurveTo(self, *points): 71 self._outPen.qCurveTo(*points) 72 73 def closePath(self): 74 self._outPen.closePath() 75 76 def endPath(self): 77 self._outPen.endPath() 78 79 80class ContourFilterPen(_PassThruComponentsMixin, RecordingPen): 81 """A "buffered" filter pen that accumulates contour data, passes 82 it through a ``filterContour`` method when the contour is closed or ended, 83 and finally draws the result with the output pen. 84 85 Components are passed through unchanged. 86 """ 87 88 def __init__(self, outPen): 89 super(ContourFilterPen, self).__init__() 90 self._outPen = outPen 91 92 def closePath(self): 93 super(ContourFilterPen, self).closePath() 94 self._flushContour() 95 96 def endPath(self): 97 super(ContourFilterPen, self).endPath() 98 self._flushContour() 99 100 def _flushContour(self): 101 result = self.filterContour(self.value) 102 if result is not None: 103 self.value = result 104 self.replay(self._outPen) 105 self.value = [] 106 107 def filterContour(self, contour): 108 """Subclasses must override this to perform the filtering. 109 110 The contour is a list of pen (operator, operands) tuples. 111 Operators are strings corresponding to the AbstractPen methods: 112 "moveTo", "lineTo", "curveTo", "qCurveTo", "closePath" and 113 "endPath". The operands are the positional arguments that are 114 passed to each method. 115 116 If the method doesn't return a value (i.e. returns None), it's 117 assumed that the argument was modified in-place. 118 Otherwise, the return value is drawn with the output pen. 119 """ 120 return # or return contour 121 122 123class FilterPointPen(_PassThruComponentsMixin, AbstractPointPen): 124 """ Baseclass for point pens that apply some transformation to the 125 coordinates they receive and pass them to another point pen. 126 127 You can override any of its methods. The default implementation does 128 nothing, but passes the commands unmodified to the other pen. 129 130 >>> from fontTools.pens.recordingPen import RecordingPointPen 131 >>> rec = RecordingPointPen() 132 >>> pen = FilterPointPen(rec) 133 >>> v = iter(rec.value) 134 >>> pen.beginPath(identifier="abc") 135 >>> next(v) 136 ('beginPath', (), {'identifier': 'abc'}) 137 >>> pen.addPoint((1, 2), "line", False) 138 >>> next(v) 139 ('addPoint', ((1, 2), 'line', False, None), {}) 140 >>> pen.addComponent("a", (2, 0, 0, 2, 10, -10), identifier="0001") 141 >>> next(v) 142 ('addComponent', ('a', (2, 0, 0, 2, 10, -10)), {'identifier': '0001'}) 143 >>> pen.endPath() 144 >>> next(v) 145 ('endPath', (), {}) 146 """ 147 148 def __init__(self, outPointPen): 149 self._outPen = outPointPen 150 151 def beginPath(self, **kwargs): 152 self._outPen.beginPath(**kwargs) 153 154 def endPath(self): 155 self._outPen.endPath() 156 157 def addPoint(self, pt, segmentType=None, smooth=False, name=None, **kwargs): 158 self._outPen.addPoint(pt, segmentType, smooth, name, **kwargs) 159