0
# Font Processing
1
2
Font optimization, subsetting, and merging tools for reducing file sizes, removing unused features, and combining multiple fonts into collections. These tools are essential for web font optimization and font distribution workflows.
3
4
## Capabilities
5
6
### Font Subsetting
7
8
Remove unused glyphs, features, and tables to create optimized fonts with smaller file sizes.
9
10
```python { .api }
11
class Subsetter:
12
def __init__(self, options=None):
13
"""
14
Initialize font subsetter.
15
16
Parameters:
17
- options: Options, subsetting configuration (default: default options)
18
"""
19
20
def subset(self, font):
21
"""
22
Apply subsetting to TTFont instance.
23
24
Parameters:
25
- font: TTFont, font to subset (modified in-place)
26
"""
27
28
class Options:
29
def __init__(self, **kwargs):
30
"""
31
Subsetting configuration options.
32
33
Parameters:
34
- text: str, characters to keep (default: None)
35
- unicodes: List[int], Unicode codepoints to keep
36
- glyphs: List[str], glyph names to keep
37
- layout_features: List[str], OpenType features to keep
38
- name_IDs: List[int], name table entries to keep
39
- name_languages: List[int], name table languages to keep
40
- drop_tables: List[str], tables to remove
41
- passthrough_tables: bool, keep unhandled tables
42
- hinting: bool, keep hinting information
43
- legacy_kern: bool, keep legacy kern table
44
- symbol_cmap: bool, keep symbol character maps
45
- ignore_missing_glyphs: bool, continue if glyphs missing
46
- ignore_missing_unicodes: bool, continue if Unicode missing
47
"""
48
```
49
50
#### Basic Subsetting Example
51
52
```python
53
from fontTools.subset import Subsetter, Options
54
from fontTools.ttLib import TTFont
55
56
# Load font
57
font = TTFont("input.ttf")
58
59
# Create subsetter with options
60
options = Options(
61
text="Hello World!", # Keep only these characters
62
layout_features=['kern', 'liga'], # Keep kerning and ligatures
63
hinting=True, # Preserve hinting
64
ignore_missing_glyphs=True # Don't fail on missing glyphs
65
)
66
67
subsetter = Subsetter(options)
68
subsetter.subset(font)
69
70
# Save subsetted font
71
font.save("output_subset.ttf")
72
font.close()
73
```
74
75
#### Advanced Subsetting
76
77
```python
78
# Subset by Unicode ranges
79
options = Options(
80
unicodes=list(range(0x20, 0x7F)) + list(range(0xA0, 0xFF)), # Basic Latin + Latin-1
81
layout_features=['kern', 'liga', 'clig', 'frac'],
82
drop_tables=['DSIG', 'GSUB', 'GPOS'], # Remove specific tables
83
name_IDs=[1, 2, 3, 4, 5, 6], # Keep essential name entries
84
hinting=False # Remove hinting for smaller size
85
)
86
87
subsetter = Subsetter(options)
88
subsetter.subset(font)
89
```
90
91
#### Web Font Optimization
92
93
```python
94
def optimize_for_web(input_path, output_path, text_content):
95
"""Optimize font for web usage."""
96
font = TTFont(input_path)
97
98
# Web-optimized subsetting
99
options = Options(
100
text=text_content,
101
layout_features=['kern', 'liga'], # Common web features
102
drop_tables=['DSIG', 'prep', 'fpgm', 'cvt '], # Remove hinting tables
103
hinting=False, # Remove hinting
104
desubroutinize=True, # Flatten CFF subroutines
105
passthrough_tables=False # Drop unknown tables
106
)
107
108
subsetter = Subsetter(options)
109
subsetter.subset(font)
110
111
font.save(output_path)
112
font.close()
113
114
print(f"Optimized font saved to {output_path}")
115
```
116
117
### Font Merging
118
119
Combine multiple fonts into a single font or font collection.
120
121
```python { .api }
122
class Merger:
123
def __init__(self, options=None):
124
"""
125
Initialize font merger.
126
127
Parameters:
128
- options: Options, merging configuration
129
"""
130
131
def merge(self, fontfiles):
132
"""
133
Merge multiple fonts into single font.
134
135
Parameters:
136
- fontfiles: List[str], paths to font files to merge
137
138
Returns:
139
TTFont: Merged font
140
"""
141
142
class Options:
143
def __init__(self, **kwargs):
144
"""
145
Font merging options.
146
147
Parameters:
148
- verbose: bool, enable verbose output
149
- timing: bool, report timing information
150
- ignore_missing_glyphs: bool, continue on missing glyphs
151
- drop_tables: List[str], tables to exclude from merge
152
"""
153
```
154
155
#### Basic Font Merging
156
157
```python
158
from fontTools.merge import Merger, Options
159
from fontTools.ttLib import TTFont
160
161
# Merge multiple fonts
162
merger = Merger()
163
merged_font = merger.merge(["font1.ttf", "font2.ttf", "font3.ttf"])
164
165
# Save merged result
166
merged_font.save("merged_font.ttf")
167
merged_font.close()
168
```
169
170
#### Advanced Merging with Options
171
172
```python
173
# Configure merging options
174
options = Options(
175
verbose=True,
176
ignore_missing_glyphs=True,
177
drop_tables=['DSIG'] # Drop digital signatures
178
)
179
180
merger = Merger(options)
181
182
# Merge fonts with specific character ranges
183
latin_font = TTFont("latin.ttf")
184
cyrillic_font = TTFont("cyrillic.ttf")
185
arabic_font = TTFont("arabic.ttf")
186
187
merged_font = merger.merge([latin_font, cyrillic_font, arabic_font])
188
merged_font.save("multilingual.ttf")
189
```
190
191
### TTX XML Conversion
192
193
Convert fonts to/from TTX XML format for human-readable font analysis and modification.
194
195
```python { .api }
196
def main(args=None):
197
"""
198
Convert OpenType fonts to XML and back.
199
200
Parameters:
201
- args: List[str], command line arguments
202
Common options:
203
- -d: dump font to XML
204
- -m: merge XML back to font
205
- -t: specify tables to dump/compile
206
- -x: exclude specific tables
207
- -s: split tables into separate files
208
- -i: don't disassemble TT instructions
209
- -z: use WOFF2 compression
210
"""
211
```
212
213
#### TTX Usage Examples
214
215
```python
216
import subprocess
217
218
# Convert font to XML
219
subprocess.run(["fonttools", "ttx", "font.ttf"]) # Creates font.ttx
220
221
# Convert specific tables only
222
subprocess.run(["fonttools", "ttx", "-t", "cmap", "-t", "name", "font.ttf"])
223
224
# Convert XML back to font
225
subprocess.run(["fonttools", "ttx", "font.ttx"]) # Creates font.ttf
226
227
# Split each table into separate XML file
228
subprocess.run(["fonttools", "ttx", "-s", "font.ttf"])
229
```
230
231
### Command-Line Interface
232
233
FontTools provides command-line tools for batch processing:
234
235
```python { .api }
236
def main(args=None):
237
"""
238
Font subsetting command-line interface.
239
240
Parameters:
241
- args: List[str], command line arguments
242
"""
243
```
244
245
#### Command-Line Usage
246
247
```bash
248
# Subset font to specific text
249
fonttools subset font.ttf --text="Hello World"
250
251
# Subset with Unicode ranges
252
fonttools subset font.ttf --unicodes="U+0020-007F,U+00A0-00FF"
253
254
# Web font optimization
255
fonttools subset font.ttf --text-file=content.txt --layout-features="kern,liga" --no-hinting
256
257
# Merge fonts
258
fonttools merge font1.ttf font2.ttf font3.ttf
259
260
# TTX conversion
261
fonttools ttx font.ttf # to XML
262
fonttools ttx font.ttx # to binary
263
```
264
265
### Error Handling
266
267
```python { .api }
268
class SubsettingError(Exception):
269
"""Base subsetting exception."""
270
271
class MissingGlyphsSubsettingError(SubsettingError):
272
"""Required glyphs are missing from font."""
273
274
class MissingUnicodesSubsettingError(SubsettingError):
275
"""Required Unicode codepoints are missing from font."""
276
```
277
278
### Batch Processing Utilities
279
280
```python
281
def process_font_directory(input_dir, output_dir, text_content):
282
"""Process all fonts in a directory."""
283
import os
284
from pathlib import Path
285
286
input_path = Path(input_dir)
287
output_path = Path(output_dir)
288
output_path.mkdir(exist_ok=True)
289
290
for font_file in input_path.glob("*.ttf"):
291
try:
292
font = TTFont(font_file)
293
294
# Apply subsetting
295
options = Options(text=text_content, hinting=False)
296
subsetter = Subsetter(options)
297
subsetter.subset(font)
298
299
# Save with suffix
300
output_file = output_path / f"{font_file.stem}_subset.ttf"
301
font.save(output_file)
302
font.close()
303
304
print(f"Processed: {font_file.name} -> {output_file.name}")
305
306
except Exception as e:
307
print(f"Error processing {font_file.name}: {e}")
308
309
# Usage
310
process_font_directory("input_fonts/", "output_fonts/", "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789")
311
```
312
313
### Performance Optimization
314
315
```python
316
def optimize_subsetting_performance(font_paths, common_options):
317
"""Optimize subsetting for multiple fonts with shared options."""
318
# Reuse subsetter instance for better performance
319
subsetter = Subsetter(common_options)
320
321
for font_path in font_paths:
322
font = TTFont(font_path)
323
324
# Create a copy for subsetting (preserves original)
325
font_copy = TTFont()
326
font_copy.importXML(font.saveXML())
327
328
subsetter.subset(font_copy)
329
330
output_path = font_path.replace('.ttf', '_subset.ttf')
331
font_copy.save(output_path)
332
333
font.close()
334
font_copy.close()
335
```
336
337
### Integration Patterns
338
339
```python
340
def create_web_font_variants(font_path, text_samples):
341
"""Create multiple optimized variants for different use cases."""
342
base_font = TTFont(font_path)
343
variants = {}
344
345
# Full feature set (headlines)
346
headline_options = Options(
347
text=text_samples['headlines'],
348
layout_features=['kern', 'liga', 'clig', 'dlig'],
349
hinting=True
350
)
351
variants['headline'] = create_subset(base_font, headline_options)
352
353
# Basic features (body text)
354
body_options = Options(
355
text=text_samples['body'],
356
layout_features=['kern'],
357
hinting=False # Smaller size for body text
358
)
359
variants['body'] = create_subset(base_font, body_options)
360
361
# Minimal (fallback)
362
fallback_options = Options(
363
text=text_samples['essential'],
364
layout_features=[],
365
hinting=False
366
)
367
variants['fallback'] = create_subset(base_font, fallback_options)
368
369
return variants
370
371
def create_subset(base_font, options):
372
"""Helper to create font subset."""
373
# Create deep copy to avoid modifying original
374
font = TTFont()
375
font.importXML(base_font.saveXML())
376
377
subsetter = Subsetter(options)
378
subsetter.subset(font)
379
380
return font
381
```