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

63 statements  

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

1"""Pen recording operations that can be accessed or replayed.""" 

2from fontTools.pens.basePen import AbstractPen, DecomposingPen 

3from fontTools.pens.pointPen import AbstractPointPen 

4 

5 

6__all__ = [ 

7 "replayRecording", 

8 "RecordingPen", 

9 "DecomposingRecordingPen", 

10 "RecordingPointPen", 

11] 

12 

13 

14def replayRecording(recording, pen): 

15 """Replay a recording, as produced by RecordingPen or DecomposingRecordingPen, 

16 to a pen. 

17 

18 Note that recording does not have to be produced by those pens. 

19 It can be any iterable of tuples of method name and tuple-of-arguments. 

20 Likewise, pen can be any objects receiving those method calls. 

21 """ 

22 for operator, operands in recording: 

23 getattr(pen, operator)(*operands) 

24 

25 

26class RecordingPen(AbstractPen): 

27 """Pen recording operations that can be accessed or replayed. 

28 

29 The recording can be accessed as pen.value; or replayed using 

30 pen.replay(otherPen). 

31 

32 :Example: 

33 

34 from fontTools.ttLib import TTFont 

35 from fontTools.pens.recordingPen import RecordingPen 

36 

37 glyph_name = 'dollar' 

38 font_path = 'MyFont.otf' 

39 

40 font = TTFont(font_path) 

41 glyphset = font.getGlyphSet() 

42 glyph = glyphset[glyph_name] 

43 

44 pen = RecordingPen() 

45 glyph.draw(pen) 

46 print(pen.value) 

47 """ 

48 

49 def __init__(self): 

50 self.value = [] 

51 

52 def moveTo(self, p0): 

53 self.value.append(("moveTo", (p0,))) 

54 

55 def lineTo(self, p1): 

56 self.value.append(("lineTo", (p1,))) 

57 

58 def qCurveTo(self, *points): 

59 self.value.append(("qCurveTo", points)) 

60 

61 def curveTo(self, *points): 

62 self.value.append(("curveTo", points)) 

63 

64 def closePath(self): 

65 self.value.append(("closePath", ())) 

66 

67 def endPath(self): 

68 self.value.append(("endPath", ())) 

69 

70 def addComponent(self, glyphName, transformation): 

71 self.value.append(("addComponent", (glyphName, transformation))) 

72 

73 def addVarComponent(self, glyphName, transformation, location): 

74 self.value.append(("addVarComponent", (glyphName, transformation, location))) 

75 

76 def replay(self, pen): 

77 replayRecording(self.value, pen) 

78 

79 draw = replay 

80 

81 

82class DecomposingRecordingPen(DecomposingPen, RecordingPen): 

83 """Same as RecordingPen, except that it doesn't keep components 

84 as references, but draws them decomposed as regular contours. 

85 

86 The constructor takes a single 'glyphSet' positional argument, 

87 a dictionary of glyph objects (i.e. with a 'draw' method) keyed 

88 by thir name:: 

89 

90 >>> class SimpleGlyph(object): 

91 ... def draw(self, pen): 

92 ... pen.moveTo((0, 0)) 

93 ... pen.curveTo((1, 1), (2, 2), (3, 3)) 

94 ... pen.closePath() 

95 >>> class CompositeGlyph(object): 

96 ... def draw(self, pen): 

97 ... pen.addComponent('a', (1, 0, 0, 1, -1, 1)) 

98 >>> glyphSet = {'a': SimpleGlyph(), 'b': CompositeGlyph()} 

99 >>> for name, glyph in sorted(glyphSet.items()): 

100 ... pen = DecomposingRecordingPen(glyphSet) 

101 ... glyph.draw(pen) 

102 ... print("{}: {}".format(name, pen.value)) 

103 a: [('moveTo', ((0, 0),)), ('curveTo', ((1, 1), (2, 2), (3, 3))), ('closePath', ())] 

104 b: [('moveTo', ((-1, 1),)), ('curveTo', ((0, 2), (1, 3), (2, 4))), ('closePath', ())] 

105 """ 

106 

107 # raises KeyError if base glyph is not found in glyphSet 

108 skipMissingComponents = False 

109 

110 

111class RecordingPointPen(AbstractPointPen): 

112 """PointPen recording operations that can be accessed or replayed. 

113 

114 The recording can be accessed as pen.value; or replayed using 

115 pointPen.replay(otherPointPen). 

116 

117 :Example: 

118 

119 from defcon import Font 

120 from fontTools.pens.recordingPen import RecordingPointPen 

121 

122 glyph_name = 'a' 

123 font_path = 'MyFont.ufo' 

124 

125 font = Font(font_path) 

126 glyph = font[glyph_name] 

127 

128 pen = RecordingPointPen() 

129 glyph.drawPoints(pen) 

130 print(pen.value) 

131 

132 new_glyph = font.newGlyph('b') 

133 pen.replay(new_glyph.getPointPen()) 

134 """ 

135 

136 def __init__(self): 

137 self.value = [] 

138 

139 def beginPath(self, identifier=None, **kwargs): 

140 if identifier is not None: 

141 kwargs["identifier"] = identifier 

142 self.value.append(("beginPath", (), kwargs)) 

143 

144 def endPath(self): 

145 self.value.append(("endPath", (), {})) 

146 

147 def addPoint( 

148 self, pt, segmentType=None, smooth=False, name=None, identifier=None, **kwargs 

149 ): 

150 if identifier is not None: 

151 kwargs["identifier"] = identifier 

152 self.value.append(("addPoint", (pt, segmentType, smooth, name), kwargs)) 

153 

154 def addComponent(self, baseGlyphName, transformation, identifier=None, **kwargs): 

155 if identifier is not None: 

156 kwargs["identifier"] = identifier 

157 self.value.append(("addComponent", (baseGlyphName, transformation), kwargs)) 

158 

159 def addVarComponent( 

160 self, baseGlyphName, transformation, location, identifier=None, **kwargs 

161 ): 

162 if identifier is not None: 

163 kwargs["identifier"] = identifier 

164 self.value.append( 

165 ("addVarComponent", (baseGlyphName, transformation, location), kwargs) 

166 ) 

167 

168 def replay(self, pointPen): 

169 for operator, args, kwargs in self.value: 

170 getattr(pointPen, operator)(*args, **kwargs) 

171 

172 drawPoints = replay 

173 

174 

175if __name__ == "__main__": 

176 pen = RecordingPen() 

177 pen.moveTo((0, 0)) 

178 pen.lineTo((0, 100)) 

179 pen.curveTo((50, 75), (60, 50), (50, 25)) 

180 pen.closePath() 

181 from pprint import pprint 

182 

183 pprint(pen.value)