0
# Drawing and Pens
1
2
Standardized drawing interface for glyph construction, manipulation, and rendering with specialized pen implementations for different use cases. The pen protocol provides a consistent API for drawing vector graphics that can be rendered to various outputs.
3
4
## Capabilities
5
6
### Base Pen Classes
7
8
Core pen protocol and base implementations that define the standard drawing interface.
9
10
```python { .api }
11
class AbstractPen:
12
"""Abstract base class defining the pen protocol."""
13
14
def moveTo(self, pt):
15
"""
16
Move to point without drawing.
17
18
Parameters:
19
- pt: Tuple[float, float], (x, y) coordinates
20
"""
21
22
def lineTo(self, pt):
23
"""
24
Draw line from current point to specified point.
25
26
Parameters:
27
- pt: Tuple[float, float], (x, y) coordinates
28
"""
29
30
def curveTo(self, *points):
31
"""
32
Draw cubic Bezier curve.
33
34
Parameters:
35
- points: Tuple[float, float], sequence of control points and end point
36
Last point is curve endpoint, others are control points
37
"""
38
39
def qCurveTo(self, *points):
40
"""
41
Draw quadratic Bezier curve(s).
42
43
Parameters:
44
- points: Tuple[float, float], sequence of control points and optional end point
45
If no end point given, curves to next oncurve point
46
"""
47
48
def closePath(self):
49
"""Close current path with line back to start point."""
50
51
def endPath(self):
52
"""End current path without closing."""
53
54
def addComponent(self, glyphName, transformation):
55
"""
56
Add component reference to another glyph.
57
58
Parameters:
59
- glyphName: str, name of referenced glyph
60
- transformation: Transform, transformation matrix
61
"""
62
63
class BasePen(AbstractPen):
64
def __init__(self, glyphSet=None):
65
"""
66
Base pen implementation with validation.
67
68
Parameters:
69
- glyphSet: GlyphSet, glyph set for component validation
70
"""
71
72
def moveTo(self, pt):
73
"""Move to point with validation."""
74
75
def lineTo(self, pt):
76
"""Draw line with validation."""
77
78
def curveTo(self, *points):
79
"""Draw cubic curve with validation."""
80
81
def qCurveTo(self, *points):
82
"""Draw quadratic curve with validation."""
83
84
class NullPen(AbstractPen):
85
"""No-operation pen for measurement and testing."""
86
87
def moveTo(self, pt): pass
88
def lineTo(self, pt): pass
89
def curveTo(self, *points): pass
90
def qCurveTo(self, *points): pass
91
def closePath(self): pass
92
def endPath(self): pass
93
```
94
95
#### Basic Pen Usage
96
97
```python
98
from fontTools.pens.basePen import BasePen
99
100
class DebugPen(BasePen):
101
"""Custom pen that prints drawing operations."""
102
103
def __init__(self):
104
super().__init__()
105
self.operations = []
106
107
def moveTo(self, pt):
108
self.operations.append(f"moveTo{pt}")
109
print(f"Move to {pt}")
110
111
def lineTo(self, pt):
112
self.operations.append(f"lineTo{pt}")
113
print(f"Line to {pt}")
114
115
def curveTo(self, *points):
116
self.operations.append(f"curveTo{points}")
117
print(f"Curve to {points}")
118
119
def closePath(self):
120
self.operations.append("closePath")
121
print("Close path")
122
123
# Use the pen
124
pen = DebugPen()
125
pen.moveTo((100, 100))
126
pen.lineTo((200, 100))
127
pen.lineTo((200, 200))
128
pen.lineTo((100, 200))
129
pen.closePath()
130
```
131
132
### TrueType Glyph Creation
133
134
Pens for creating TrueType glyph data with proper curve conversion and hinting.
135
136
```python { .api }
137
class TTGlyphPen(BasePen):
138
def __init__(self, glyphSet):
139
"""
140
Pen for creating TrueType glyph objects.
141
142
Parameters:
143
- glyphSet: GlyphSet, glyph set for component resolution
144
"""
145
146
def glyph(self):
147
"""
148
Get the constructed glyph object.
149
150
Returns:
151
Glyph: TrueType glyph with quadratic curves
152
"""
153
154
class T2CharStringPen(BasePen):
155
def __init__(self, width, glyphSet):
156
"""
157
Pen for creating CFF CharString objects.
158
159
Parameters:
160
- width: int, glyph advance width
161
- glyphSet: GlyphSet, glyph set for component resolution
162
"""
163
164
def getCharString(self):
165
"""
166
Get the constructed CharString.
167
168
Returns:
169
CharString: CFF CharString with cubic curves
170
"""
171
```
172
173
#### Creating Glyphs with Pens
174
175
```python
176
from fontTools.pens.ttGlyphPen import TTGlyphPen
177
from fontTools.pens.t2CharStringPen import T2CharStringPen
178
179
# Create TrueType glyph (letter A)
180
tt_pen = TTGlyphPen(None)
181
182
# Draw letter A outline
183
tt_pen.moveTo((200, 0)) # Bottom left
184
tt_pen.lineTo((100, 700)) # Top left
185
tt_pen.lineTo((300, 700)) # Top right
186
tt_pen.lineTo((400, 0)) # Bottom right
187
tt_pen.closePath()
188
189
# Add crossbar
190
tt_pen.moveTo((175, 350))
191
tt_pen.lineTo((325, 350))
192
tt_pen.lineTo((325, 400))
193
tt_pen.lineTo((175, 400))
194
tt_pen.closePath()
195
196
# Get the glyph
197
glyph_a = tt_pen.glyph()
198
199
# Create CFF CharString (letter O)
200
cff_pen = T2CharStringPen(500, None)
201
202
# Draw letter O outline (with curves)
203
cff_pen.moveTo((250, 0))
204
cff_pen.curveTo((100, 0), (50, 100), (50, 350)) # Left curve
205
cff_pen.curveTo((50, 600), (100, 700), (250, 700)) # Top curve
206
cff_pen.curveTo((400, 700), (450, 600), (450, 350)) # Right curve
207
cff_pen.curveTo((450, 100), (400, 0), (250, 0)) # Bottom curve
208
cff_pen.closePath()
209
210
# Inner counter
211
cff_pen.moveTo((250, 100))
212
cff_pen.curveTo((350, 100), (350, 150), (350, 350))
213
cff_pen.curveTo((350, 550), (350, 600), (250, 600))
214
cff_pen.curveTo((150, 600), (150, 550), (150, 350))
215
cff_pen.curveTo((150, 150), (150, 100), (250, 100))
216
cff_pen.closePath()
217
218
char_string_o = cff_pen.getCharString()
219
```
220
221
### Analysis and Measurement Pens
222
223
Pens for analyzing glyph properties without rendering.
224
225
```python { .api }
226
class BoundsPen(BasePen):
227
def __init__(self, glyphSet):
228
"""
229
Calculate glyph bounding box.
230
231
Parameters:
232
- glyphSet: GlyphSet, glyph set for component resolution
233
"""
234
235
@property
236
def bounds(self):
237
"""
238
Get calculated bounds.
239
240
Returns:
241
Tuple[float, float, float, float]: (xMin, yMin, xMax, yMax) or None
242
"""
243
244
class AreaPen(BasePen):
245
def __init__(self, glyphSet=None):
246
"""
247
Calculate glyph area.
248
249
Parameters:
250
- glyphSet: GlyphSet, glyph set for component resolution
251
"""
252
253
@property
254
def area(self):
255
"""
256
Get calculated area.
257
258
Returns:
259
float: Glyph area (positive for clockwise, negative for counter-clockwise)
260
"""
261
262
class StatisticsPen(BasePen):
263
def __init__(self, glyphSet=None):
264
"""
265
Gather glyph statistics.
266
267
Parameters:
268
- glyphSet: GlyphSet, glyph set for component resolution
269
"""
270
271
@property
272
def area(self):
273
"""Get glyph area."""
274
275
@property
276
def length(self):
277
"""Get total path length."""
278
279
@property
280
def moments(self):
281
"""Get statistical moments."""
282
```
283
284
#### Using Analysis Pens
285
286
```python
287
from fontTools.pens.boundsPen import BoundsPen
288
from fontTools.pens.areaPen import AreaPen
289
from fontTools.pens.statisticsPen import StatisticsPen
290
from fontTools.ttLib import TTFont
291
292
# Load font and get glyph
293
font = TTFont("font.ttf")
294
glyph_set = font.getGlyphSet()
295
glyph = glyph_set['A']
296
297
# Calculate bounds
298
bounds_pen = BoundsPen(glyph_set)
299
glyph.draw(bounds_pen)
300
bounds = bounds_pen.bounds
301
print(f"Glyph bounds: {bounds}") # (xMin, yMin, xMax, yMax)
302
303
# Calculate area
304
area_pen = AreaPen(glyph_set)
305
glyph.draw(area_pen)
306
area = area_pen.area
307
print(f"Glyph area: {area}")
308
309
# Gather statistics
310
stats_pen = StatisticsPen(glyph_set)
311
glyph.draw(stats_pen)
312
print(f"Area: {stats_pen.area}")
313
print(f"Length: {stats_pen.length}")
314
print(f"Moments: {stats_pen.moments}")
315
```
316
317
### Transformation Pens
318
319
Pens for applying geometric transformations to glyph data.
320
321
```python { .api }
322
class TransformPen(BasePen):
323
def __init__(self, otherPen, transformation):
324
"""
325
Apply transformation to pen operations.
326
327
Parameters:
328
- otherPen: AbstractPen, target pen to receive transformed operations
329
- transformation: Transform, transformation matrix to apply
330
"""
331
332
class ReversedContourPen(BasePen):
333
def __init__(self, otherPen):
334
"""
335
Reverse contour direction.
336
337
Parameters:
338
- otherPen: AbstractPen, target pen to receive reversed operations
339
"""
340
```
341
342
#### Transforming Glyphs
343
344
```python
345
from fontTools.pens.transformPen import TransformPen
346
from fontTools.pens.ttGlyphPen import TTGlyphPen
347
from fontTools.misc.transform import Transform
348
349
# Create base glyph
350
base_pen = TTGlyphPen(None)
351
base_pen.moveTo((100, 100))
352
base_pen.lineTo((200, 100))
353
base_pen.lineTo((150, 200))
354
base_pen.closePath()
355
356
# Create transformed version (scaled and rotated)
357
target_pen = TTGlyphPen(None)
358
transform = Transform()
359
transform = transform.scale(1.5, 1.5) # Scale 150%
360
transform = transform.rotate(math.radians(45)) # Rotate 45 degrees
361
362
transform_pen = TransformPen(target_pen, transform)
363
364
# Draw original shape through transform pen
365
transform_pen.moveTo((100, 100))
366
transform_pen.lineTo((200, 100))
367
transform_pen.lineTo((150, 200))
368
transform_pen.closePath()
369
370
transformed_glyph = target_pen.glyph()
371
```
372
373
### Recording and Replay Pens
374
375
Pens for capturing and replaying drawing operations.
376
377
```python { .api }
378
class RecordingPen(BasePen):
379
def __init__(self):
380
"""Pen that records all drawing operations."""
381
382
@property
383
def value(self):
384
"""
385
Get recorded operations.
386
387
Returns:
388
List[Tuple]: List of (operation, args) tuples
389
"""
390
391
def replay(self, pen):
392
"""
393
Replay recorded operations to another pen.
394
395
Parameters:
396
- pen: AbstractPen, target pen for replay
397
"""
398
399
class DecomposingRecordingPen(RecordingPen):
400
def __init__(self, glyphSet):
401
"""
402
Recording pen that decomposes components.
403
404
Parameters:
405
- glyphSet: GlyphSet, glyph set for component decomposition
406
"""
407
```
408
409
#### Recording and Replaying Operations
410
411
```python
412
from fontTools.pens.recordingPen import RecordingPen
413
414
# Record drawing operations
415
recording_pen = RecordingPen()
416
recording_pen.moveTo((0, 0))
417
recording_pen.lineTo((100, 0))
418
recording_pen.lineTo((100, 100))
419
recording_pen.lineTo((0, 100))
420
recording_pen.closePath()
421
422
# Get recorded operations
423
operations = recording_pen.value
424
print("Recorded operations:")
425
for op, args in operations:
426
print(f" {op}{args}")
427
428
# Replay to different pen
429
target_pen = TTGlyphPen(None)
430
recording_pen.replay(target_pen)
431
replayed_glyph = target_pen.glyph()
432
```
433
434
### Output Format Pens
435
436
Pens for generating various output formats from glyph data.
437
438
```python { .api }
439
class SVGPathPen(BasePen):
440
def __init__(self, glyphSet=None):
441
"""
442
Generate SVG path data.
443
444
Parameters:
445
- glyphSet: GlyphSet, glyph set for component resolution
446
"""
447
448
def getCommands(self):
449
"""
450
Get SVG path commands.
451
452
Returns:
453
List[str]: SVG path command strings
454
"""
455
456
def d(self):
457
"""
458
Get SVG path 'd' attribute value.
459
460
Returns:
461
str: Complete SVG path data
462
"""
463
```
464
465
#### Generating SVG Paths
466
467
```python
468
from fontTools.pens.svgPathPen import SVGPathPen
469
470
# Create SVG path from glyph
471
svg_pen = SVGPathPen()
472
473
# Draw a simple shape
474
svg_pen.moveTo((100, 100))
475
svg_pen.curveTo((150, 50), (250, 50), (300, 100))
476
svg_pen.curveTo((350, 150), (350, 250), (300, 300))
477
svg_pen.curveTo((250, 350), (150, 350), (100, 300))
478
svg_pen.curveTo((50, 250), (50, 150), (100, 100))
479
svg_pen.closePath()
480
481
# Get SVG path data
482
path_data = svg_pen.d()
483
print(f"SVG path: {path_data}")
484
485
# Use in SVG
486
svg_content = f'''
487
<svg viewBox="0 0 400 400" xmlns="http://www.w3.org/2000/svg">
488
<path d="{path_data}" fill="black"/>
489
</svg>
490
'''
491
```
492
493
### Error Handling
494
495
```python { .api }
496
class PenError(Exception):
497
"""Base pen exception."""
498
499
class OpenContourError(PenError):
500
"""Raised when path operations are invalid due to open contour."""
501
```
502
503
### Custom Pen Development
504
505
```python
506
class CustomPen(BasePen):
507
"""Example custom pen implementation."""
508
509
def __init__(self):
510
super().__init__()
511
self.paths = []
512
self.current_path = []
513
514
def moveTo(self, pt):
515
if self.current_path:
516
self.paths.append(self.current_path)
517
self.current_path = [('moveTo', pt)]
518
519
def lineTo(self, pt):
520
self.current_path.append(('lineTo', pt))
521
522
def curveTo(self, *points):
523
self.current_path.append(('curveTo', points))
524
525
def closePath(self):
526
self.current_path.append(('closePath',))
527
self.paths.append(self.current_path)
528
self.current_path = []
529
530
def endPath(self):
531
if self.current_path:
532
self.paths.append(self.current_path)
533
self.current_path = []
534
535
# Usage patterns for drawing glyphs
536
def draw_rectangle(pen, x, y, width, height):
537
"""Helper function to draw rectangle."""
538
pen.moveTo((x, y))
539
pen.lineTo((x + width, y))
540
pen.lineTo((x + width, y + height))
541
pen.lineTo((x, y + height))
542
pen.closePath()
543
544
def draw_circle(pen, cx, cy, radius, segments=32):
545
"""Helper function to draw circle approximation."""
546
import math
547
548
# Calculate points for circle
549
points = []
550
for i in range(segments):
551
angle = 2 * math.pi * i / segments
552
x = cx + radius * math.cos(angle)
553
y = cy + radius * math.sin(angle)
554
points.append((x, y))
555
556
pen.moveTo(points[0])
557
for point in points[1:]:
558
pen.lineTo(point)
559
pen.closePath()
560
```