0
# Font Building
1
2
Build fonts from scratch using a builder pattern, supporting both TrueType and CFF/PostScript font creation with comprehensive table setup methods. FontBuilder provides a structured approach to creating fonts programmatically with proper table dependencies and validation.
3
4
## Capabilities
5
6
### FontBuilder Class
7
8
The primary class for constructing fonts from scratch using a builder pattern with sequential setup methods.
9
10
```python { .api }
11
class FontBuilder:
12
def __init__(self, unitsPerEm=None, font=None, isTTF=True):
13
"""
14
Initialize font builder.
15
16
Parameters:
17
- unitsPerEm: int, units per em (default: 1000)
18
- font: TTFont, existing font to modify (default: new font)
19
- isTTF: bool, True for TrueType, False for CFF/PostScript
20
"""
21
22
def setupGlyphOrder(self, glyphOrder):
23
"""
24
Set glyph names list (must be called first).
25
26
Parameters:
27
- glyphOrder: List[str], ordered list of glyph names
28
"""
29
30
def setupCharacterMap(self, cmap):
31
"""
32
Set character-to-glyph mapping.
33
34
Parameters:
35
- cmap: Dict[int, str], Unicode codepoint to glyph name mapping
36
"""
37
38
def setupGlyf(self, glyphs):
39
"""
40
Set TrueType glyph data (for TrueType fonts).
41
42
Parameters:
43
- glyphs: Dict[str, Glyph], glyph name to glyph object mapping
44
"""
45
46
def setupCFF(self, psName, charStrings, charStringsType=2):
47
"""
48
Set CFF/PostScript data (for OpenType fonts).
49
50
Parameters:
51
- psName: str, PostScript font name
52
- charStrings: Dict[str, CharString], glyph name to CharString mapping
53
- charStringsType: int, CharString format version (1 or 2)
54
"""
55
56
def setupHorizontalMetrics(self, metrics):
57
"""
58
Set glyph advance widths and left side bearings.
59
60
Parameters:
61
- metrics: Dict[str, Tuple[int, int]], glyph name to (advance, lsb) mapping
62
"""
63
64
def setupHorizontalHeader(self, ascent=None, descent=None):
65
"""
66
Set horizontal metrics header.
67
68
Parameters:
69
- ascent: int, font ascent (default: calculated from metrics)
70
- descent: int, font descent (default: calculated from metrics)
71
"""
72
73
def setupNameTable(self, nameStrings, mac=True):
74
"""
75
Set font naming information.
76
77
Parameters:
78
- nameStrings: Dict[str, str], name ID to string mapping
79
- mac: bool, include Mac platform names
80
"""
81
82
def setupOS2(self, sTypoAscender=None, sTypoDescender=None, usWinAscent=None, usWinDescent=None):
83
"""
84
Set OS/2 table with font metrics and properties.
85
86
Parameters:
87
- sTypoAscender: int, typographic ascender
88
- sTypoDescender: int, typographic descender
89
- usWinAscent: int, Windows ascent
90
- usWinDescent: int, Windows descent
91
"""
92
93
def setupPost(self, keepGlyphNames=True, extraNames=[]):
94
"""
95
Set PostScript table.
96
97
Parameters:
98
- keepGlyphNames: bool, include glyph names in font
99
- extraNames: List[str], additional glyph names beyond standard set
100
"""
101
```
102
103
#### Basic Font Building Example
104
105
```python
106
from fontTools.fontBuilder import FontBuilder
107
from fontTools.pens.ttGlyphPen import TTGlyphPen
108
109
# Initialize builder for TrueType font
110
fb = FontBuilder(unitsPerEm=1000, isTTF=True)
111
112
# 1. Set glyph order (must be first)
113
glyph_order = [".notdef", "A", "B", "space"]
114
fb.setupGlyphOrder(glyph_order)
115
116
# 2. Set character mapping
117
cmap = {
118
ord('A'): 'A',
119
ord('B'): 'B',
120
ord(' '): 'space'
121
}
122
fb.setupCharacterMap(cmap)
123
124
# 3. Create glyphs using pen
125
glyphs = {}
126
127
# Create .notdef glyph
128
pen = TTGlyphPen(None)
129
pen.moveTo((100, 0))
130
pen.lineTo((100, 750))
131
pen.lineTo((400, 750))
132
pen.lineTo((400, 0))
133
pen.closePath()
134
glyphs[".notdef"] = pen.glyph()
135
136
# Create letter A
137
pen = TTGlyphPen(None)
138
pen.moveTo((200, 0))
139
pen.lineTo((100, 700))
140
pen.lineTo((150, 700))
141
pen.lineTo((300, 700))
142
pen.lineTo((350, 700))
143
pen.lineTo((250, 0))
144
pen.closePath()
145
# Add crossbar
146
pen.moveTo((175, 350))
147
pen.lineTo((275, 350))
148
pen.lineTo((275, 400))
149
pen.lineTo((175, 400))
150
pen.closePath()
151
glyphs["A"] = pen.glyph()
152
153
# Simple rectangle for B
154
pen = TTGlyphPen(None)
155
pen.moveTo((100, 0))
156
pen.lineTo((100, 700))
157
pen.lineTo((300, 700))
158
pen.lineTo((300, 0))
159
pen.closePath()
160
glyphs["B"] = pen.glyph()
161
162
# Empty glyph for space
163
pen = TTGlyphPen(None)
164
glyphs["space"] = pen.glyph()
165
166
fb.setupGlyf(glyphs)
167
168
# 4. Set metrics (advance width, left side bearing)
169
metrics = {
170
".notdef": (500, 100),
171
"A": (450, 100),
172
"B": (400, 100),
173
"space": (250, 0)
174
}
175
fb.setupHorizontalMetrics(metrics)
176
177
# 5. Set font header
178
fb.setupHorizontalHeader(ascent=800, descent=-200)
179
180
# 6. Set font names
181
names = {
182
"familyName": "TestFont",
183
"styleName": "Regular",
184
"uniqueFontIdentifier": "TestFont-Regular",
185
"fullName": "TestFont Regular",
186
"psName": "TestFont-Regular",
187
"version": "1.0"
188
}
189
fb.setupNameTable(names)
190
191
# 7. Set OS/2 table
192
fb.setupOS2(sTypoAscender=800, sTypoDescender=-200, usWinAscent=800, usWinDescent=200)
193
194
# 8. Set PostScript table
195
fb.setupPost()
196
197
# Get the built font
198
font = fb.font
199
font.save("test_font.ttf")
200
```
201
202
### Variable Font Setup
203
204
Additional methods for creating variable fonts with multiple axes and instances.
205
206
```python { .api }
207
def setupFvar(self, axes, instances):
208
"""
209
Set up font variations (variable font axes and instances).
210
211
Parameters:
212
- axes: List[Dict], axis definitions with name, tag, min, default, max values
213
- instances: List[Dict], named instances with coordinates and names
214
"""
215
216
def setupAvar(self, axes):
217
"""
218
Set up axis variation mapping (non-linear axis mapping).
219
220
Parameters:
221
- axes: Dict[str, List[Tuple[float, float]]], axis tag to mapping pairs
222
"""
223
224
def setupGvar(self, variations):
225
"""
226
Set up glyph variations (delta coordinates for each axis).
227
228
Parameters:
229
- variations: Dict[str, Dict], glyph name to variation deltas mapping
230
"""
231
```
232
233
#### Variable Font Example
234
235
```python
236
# Build variable font with weight axis
237
fb = FontBuilder(unitsPerEm=1000, isTTF=True)
238
239
# ... basic setup steps as above ...
240
241
# Define weight axis
242
axes = [{
243
'name': 'Weight',
244
'tag': 'wght',
245
'minValue': 100,
246
'defaultValue': 400,
247
'maxValue': 900
248
}]
249
250
# Define instances
251
instances = [{
252
'name': 'Light',
253
'coordinates': {'wght': 200}
254
}, {
255
'name': 'Regular',
256
'coordinates': {'wght': 400}
257
}, {
258
'name': 'Bold',
259
'coordinates': {'wght': 700}
260
}]
261
262
fb.setupFvar(axes, instances)
263
264
# Set up glyph variations (simplified example)
265
variations = {
266
'A': {
267
'wght': [
268
# Delta coordinates for different weight values
269
{'coordinates': [10, 0, 15, 0, 20, 0], 'support': [0, 1, 1]}
270
]
271
}
272
}
273
fb.setupGvar(variations)
274
```
275
276
### Color Font Setup
277
278
Methods for creating color fonts using COLR/CPAL tables.
279
280
```python { .api }
281
def setupCOLR(self, colorLayers):
282
"""
283
Set up color layer definitions (COLR table).
284
285
Parameters:
286
- colorLayers: Dict[str, List[Tuple[str, int]]], base glyph to layers mapping
287
"""
288
289
def setupCPAL(self, palettes):
290
"""
291
Set up color palettes (CPAL table).
292
293
Parameters:
294
- palettes: List[List[Tuple[float, float, float, float]]], color palettes with RGBA values
295
"""
296
```
297
298
#### Color Font Example
299
300
```python
301
# Create color font with emoji-style glyphs
302
fb = FontBuilder(unitsPerEm=1000, isTTF=True)
303
304
# ... basic setup ...
305
306
# Define color layers
307
color_layers = {
308
'heart': [
309
('heart.base', 0), # Base shape uses palette color 0
310
('heart.highlight', 1) # Highlight uses palette color 1
311
]
312
}
313
fb.setupCOLR(color_layers)
314
315
# Define color palette
316
palettes = [[
317
(1.0, 0.0, 0.0, 1.0), # Red (RGBA)
318
(1.0, 0.5, 0.5, 1.0) # Light red
319
]]
320
fb.setupCPAL(palettes)
321
```
322
323
### Font Validation
324
325
FontBuilder automatically handles many validation requirements, but additional checks can be performed:
326
327
```python
328
# Validate required tables are present
329
font = fb.font
330
required_tables = ['head', 'hhea', 'hmtx', 'maxp', 'name', 'cmap', 'post']
331
332
for table_tag in required_tables:
333
if table_tag not in font:
334
print(f"Warning: Missing required table {table_tag}")
335
336
# Validate glyph metrics consistency
337
if 'hmtx' in font and 'hhea' in font:
338
hmtx = font['hmtx']
339
hhea = font['hhea']
340
if hhea.numberOfHMetrics != len(hmtx.metrics):
341
print("Warning: Inconsistent horizontal metrics count")
342
```
343
344
## Common Font Building Patterns
345
346
### Building from Vector Data
347
348
```python
349
from fontTools.misc.bezierTools import calcBounds
350
351
def build_glyph_from_contours(contours):
352
"""Build glyph from list of contour point lists."""
353
pen = TTGlyphPen(None)
354
355
for contour in contours:
356
if not contour:
357
continue
358
359
pen.moveTo(contour[0])
360
for point in contour[1:]:
361
pen.lineTo(point)
362
pen.closePath()
363
364
return pen.glyph()
365
366
# Calculate bounding box for metrics
367
def calc_glyph_bounds(glyph):
368
"""Calculate bounding box for glyph."""
369
if hasattr(glyph, 'coordinates'):
370
coords = glyph.coordinates
371
if coords:
372
return calcBounds(coords)
373
return (0, 0, 0, 0)
374
```
375
376
### Batch Font Generation
377
378
```python
379
def create_font_family(base_glyphs, family_name, styles):
380
"""Create multiple fonts in a family."""
381
fonts = {}
382
383
for style_name, style_params in styles.items():
384
fb = FontBuilder(unitsPerEm=1000, isTTF=True)
385
386
# Apply style-specific modifications to glyphs
387
styled_glyphs = apply_style_to_glyphs(base_glyphs, style_params)
388
389
# ... setup steps ...
390
391
names = {
392
"familyName": family_name,
393
"styleName": style_name,
394
"fullName": f"{family_name} {style_name}",
395
# ... other names
396
}
397
fb.setupNameTable(names)
398
399
fonts[style_name] = fb.font
400
401
return fonts
402
```
403
404
## Error Handling
405
406
FontBuilder methods may raise exceptions for invalid parameters or missing dependencies:
407
408
- **ValueError**: Invalid parameters or missing required data
409
- **KeyError**: Referenced glyph names not in glyph order
410
- **TypeError**: Incorrect parameter types
411
- **AttributeError**: Missing required attributes in glyph objects
412
413
Always ensure proper setup sequence and validate input data before font building operations.