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