Coverage for /usr/lib/python3/dist-packages/fontTools/pens/filterPen.py: 44%

55 statements  

« prev     ^ index     » next       coverage.py v7.9.1, created at 2025-06-14 15:55 +0200

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 

13 """Base class for pens that apply some transformation to the coordinates 

14 they receive and pass them to another pen. 

15 

16 You can override any of its methods. The default implementation does 

17 nothing, but passes the commands unmodified to the other pen. 

18 

19 >>> from fontTools.pens.recordingPen import RecordingPen 

20 >>> rec = RecordingPen() 

21 >>> pen = FilterPen(rec) 

22 >>> v = iter(rec.value) 

23 

24 >>> pen.moveTo((0, 0)) 

25 >>> next(v) 

26 ('moveTo', ((0, 0),)) 

27 

28 >>> pen.lineTo((1, 1)) 

29 >>> next(v) 

30 ('lineTo', ((1, 1),)) 

31 

32 >>> pen.curveTo((2, 2), (3, 3), (4, 4)) 

33 >>> next(v) 

34 ('curveTo', ((2, 2), (3, 3), (4, 4))) 

35 

36 >>> pen.qCurveTo((5, 5), (6, 6), (7, 7), (8, 8)) 

37 >>> next(v) 

38 ('qCurveTo', ((5, 5), (6, 6), (7, 7), (8, 8))) 

39 

40 >>> pen.closePath() 

41 >>> next(v) 

42 ('closePath', ()) 

43 

44 >>> pen.moveTo((9, 9)) 

45 >>> next(v) 

46 ('moveTo', ((9, 9),)) 

47 

48 >>> pen.endPath() 

49 >>> next(v) 

50 ('endPath', ()) 

51 

52 >>> pen.addComponent('foo', (1, 0, 0, 1, 0, 0)) 

53 >>> next(v) 

54 ('addComponent', ('foo', (1, 0, 0, 1, 0, 0))) 

55 """ 

56 

57 def __init__(self, outPen): 

58 self._outPen = outPen 

59 self.current_pt = None 

60 

61 def moveTo(self, pt): 

62 self._outPen.moveTo(pt) 

63 self.current_pt = pt 

64 

65 def lineTo(self, pt): 

66 self._outPen.lineTo(pt) 

67 self.current_pt = pt 

68 

69 def curveTo(self, *points): 

70 self._outPen.curveTo(*points) 

71 self.current_pt = points[-1] 

72 

73 def qCurveTo(self, *points): 

74 self._outPen.qCurveTo(*points) 

75 self.current_pt = points[-1] 

76 

77 def closePath(self): 

78 self._outPen.closePath() 

79 self.current_pt = None 

80 

81 def endPath(self): 

82 self._outPen.endPath() 

83 self.current_pt = None 

84 

85 

86class ContourFilterPen(_PassThruComponentsMixin, RecordingPen): 

87 """A "buffered" filter pen that accumulates contour data, passes 

88 it through a ``filterContour`` method when the contour is closed or ended, 

89 and finally draws the result with the output pen. 

90 

91 Components are passed through unchanged. 

92 """ 

93 

94 def __init__(self, outPen): 

95 super(ContourFilterPen, self).__init__() 

96 self._outPen = outPen 

97 

98 def closePath(self): 

99 super(ContourFilterPen, self).closePath() 

100 self._flushContour() 

101 

102 def endPath(self): 

103 super(ContourFilterPen, self).endPath() 

104 self._flushContour() 

105 

106 def _flushContour(self): 

107 result = self.filterContour(self.value) 

108 if result is not None: 

109 self.value = result 

110 self.replay(self._outPen) 

111 self.value = [] 

112 

113 def filterContour(self, contour): 

114 """Subclasses must override this to perform the filtering. 

115 

116 The contour is a list of pen (operator, operands) tuples. 

117 Operators are strings corresponding to the AbstractPen methods: 

118 "moveTo", "lineTo", "curveTo", "qCurveTo", "closePath" and 

119 "endPath". The operands are the positional arguments that are 

120 passed to each method. 

121 

122 If the method doesn't return a value (i.e. returns None), it's 

123 assumed that the argument was modified in-place. 

124 Otherwise, the return value is drawn with the output pen. 

125 """ 

126 return # or return contour 

127 

128 

129class FilterPointPen(_PassThruComponentsMixin, AbstractPointPen): 

130 """Baseclass for point pens that apply some transformation to the 

131 coordinates they receive and pass them to another point pen. 

132 

133 You can override any of its methods. The default implementation does 

134 nothing, but passes the commands unmodified to the other pen. 

135 

136 >>> from fontTools.pens.recordingPen import RecordingPointPen 

137 >>> rec = RecordingPointPen() 

138 >>> pen = FilterPointPen(rec) 

139 >>> v = iter(rec.value) 

140 >>> pen.beginPath(identifier="abc") 

141 >>> next(v) 

142 ('beginPath', (), {'identifier': 'abc'}) 

143 >>> pen.addPoint((1, 2), "line", False) 

144 >>> next(v) 

145 ('addPoint', ((1, 2), 'line', False, None), {}) 

146 >>> pen.addComponent("a", (2, 0, 0, 2, 10, -10), identifier="0001") 

147 >>> next(v) 

148 ('addComponent', ('a', (2, 0, 0, 2, 10, -10)), {'identifier': '0001'}) 

149 >>> pen.endPath() 

150 >>> next(v) 

151 ('endPath', (), {}) 

152 """ 

153 

154 def __init__(self, outPointPen): 

155 self._outPen = outPointPen 

156 

157 def beginPath(self, **kwargs): 

158 self._outPen.beginPath(**kwargs) 

159 

160 def endPath(self): 

161 self._outPen.endPath() 

162 

163 def addPoint(self, pt, segmentType=None, smooth=False, name=None, **kwargs): 

164 self._outPen.addPoint(pt, segmentType, smooth, name, **kwargs)