or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

Files

docs

core-font-operations.mddrawing-pens.mdfont-building.mdfont-processing.mdindex.mdutilities-tools.mdvariable-fonts.md

drawing-pens.mddocs/

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

```