0
# Variable Fonts
1
2
Build and manipulate variable fonts using design space documents, supporting multi-axis font variations and instance generation. Variable fonts allow a single font file to contain multiple styles and weights along continuous axes.
3
4
## Capabilities
5
6
### Variable Font Building
7
8
Core functions for building variable fonts from master sources and design space documents.
9
10
```python { .api }
11
def build(designspace, master_finder=lambda s: s, exclude=[], optimize=True, **kwargs):
12
"""
13
Build variable font from designspace document.
14
15
Parameters:
16
- designspace: str or DesignSpaceDocument, path to .designspace file or document object
17
- master_finder: callable, function to locate master font files
18
- exclude: List[str], OpenType tables to exclude from variation
19
- optimize: bool, enable variation optimization
20
- kwargs: additional build options
21
22
Returns:
23
TTFont: Variable font with variation tables
24
"""
25
26
def build_many(designspace, master_finder=lambda s: s, exclude=[], **kwargs):
27
"""
28
Build multiple variable fonts from designspace v5 document.
29
30
Parameters:
31
- designspace: str or DesignSpaceDocument, designspace file or document
32
- master_finder: callable, function to locate master font files
33
- exclude: List[str], tables to exclude
34
- kwargs: additional build options
35
36
Returns:
37
List[TTFont]: List of built variable fonts
38
"""
39
40
def load_designspace(designspace):
41
"""
42
Parse and validate designspace document.
43
44
Parameters:
45
- designspace: str, path to designspace file
46
47
Returns:
48
DesignSpaceDocument: Parsed and validated document
49
"""
50
51
def load_masters(designspace, master_finder):
52
"""
53
Load master fonts referenced in designspace.
54
55
Parameters:
56
- designspace: DesignSpaceDocument, design space document
57
- master_finder: callable, function to resolve master font paths
58
59
Returns:
60
Dict[str, TTFont]: Master fonts keyed by source identifier
61
"""
62
```
63
64
#### Basic Variable Font Building
65
66
```python
67
from fontTools.varLib import build
68
from fontTools.designspaceLib import DesignSpaceDocument
69
70
# Build from designspace file
71
variable_font = build("MyFont.designspace")
72
variable_font.save("MyFont-VF.ttf")
73
74
# Build with custom master finder
75
def find_masters(source_path):
76
"""Custom logic to locate master fonts."""
77
return f"masters/{source_path}"
78
79
variable_font = build("MyFont.designspace", master_finder=find_masters)
80
```
81
82
#### Advanced Building with Options
83
84
```python
85
# Build with optimization and exclusions
86
variable_font = build(
87
"MyFont.designspace",
88
exclude=['DSIG', 'hdmx'], # Exclude problematic tables
89
optimize=True, # Enable delta optimization
90
overlap_tolerance=0.5, # Overlap detection tolerance
91
cff_version=2 # Use CFF2 for PostScript outlines
92
)
93
94
# Build multiple fonts from v5 designspace
95
fonts = build_many("MyFamily.designspace")
96
for i, font in enumerate(fonts):
97
font.save(f"MyFamily-VF-{i}.ttf")
98
```
99
100
### Design Space Documents
101
102
Classes for creating and manipulating design space documents that define variable font parameters.
103
104
```python { .api }
105
class DesignSpaceDocument:
106
def __init__(self):
107
"""Initialize empty design space document."""
108
109
def read(self, path):
110
"""
111
Read designspace from file.
112
113
Parameters:
114
- path: str, path to .designspace file
115
"""
116
117
def write(self, path):
118
"""
119
Write designspace to file.
120
121
Parameters:
122
- path: str, output file path
123
"""
124
125
def addAxisDescriptor(self, descriptor):
126
"""
127
Add variation axis definition.
128
129
Parameters:
130
- descriptor: AxisDescriptor, axis definition
131
"""
132
133
def addSourceDescriptor(self, descriptor):
134
"""
135
Add master font source.
136
137
Parameters:
138
- descriptor: SourceDescriptor, source definition
139
"""
140
141
def addInstanceDescriptor(self, descriptor):
142
"""
143
Add named instance definition.
144
145
Parameters:
146
- descriptor: InstanceDescriptor, instance definition
147
"""
148
149
def addRuleDescriptor(self, descriptor):
150
"""
151
Add conditional substitution rule.
152
153
Parameters:
154
- descriptor: RuleDescriptor, rule definition
155
"""
156
```
157
158
#### Creating Design Space Documents
159
160
```python
161
from fontTools.designspaceLib import (
162
DesignSpaceDocument, AxisDescriptor, SourceDescriptor, InstanceDescriptor
163
)
164
165
# Create new design space
166
doc = DesignSpaceDocument()
167
168
# Define weight axis
169
weight_axis = AxisDescriptor()
170
weight_axis.name = "Weight"
171
weight_axis.tag = "wght"
172
weight_axis.minimum = 100
173
weight_axis.default = 400
174
weight_axis.maximum = 900
175
doc.addAxisDescriptor(weight_axis)
176
177
# Define width axis
178
width_axis = AxisDescriptor()
179
width_axis.name = "Width"
180
width_axis.tag = "wdth"
181
width_axis.minimum = 75
182
width_axis.default = 100
183
width_axis.maximum = 125
184
doc.addAxisDescriptor(width_axis)
185
186
# Add master sources
187
light_source = SourceDescriptor()
188
light_source.filename = "MyFont-Light.ufo"
189
light_source.location = {"Weight": 100, "Width": 100}
190
doc.addSourceDescriptor(light_source)
191
192
regular_source = SourceDescriptor()
193
regular_source.filename = "MyFont-Regular.ufo"
194
regular_source.location = {"Weight": 400, "Width": 100}
195
regular_source.copyInfo = True # Copy font info from this master
196
doc.addSourceDescriptor(regular_source)
197
198
bold_source = SourceDescriptor()
199
bold_source.filename = "MyFont-Bold.ufo"
200
bold_source.location = {"Weight": 900, "Width": 100}
201
doc.addSourceDescriptor(bold_source)
202
203
# Add named instances
204
regular_instance = InstanceDescriptor()
205
regular_instance.name = "Regular"
206
regular_instance.location = {"Weight": 400, "Width": 100}
207
doc.addInstanceDescriptor(regular_instance)
208
209
bold_instance = InstanceDescriptor()
210
bold_instance.name = "Bold"
211
bold_instance.location = {"Weight": 700, "Width": 100}
212
doc.addInstanceDescriptor(bold_instance)
213
214
# Save design space
215
doc.write("MyFont.designspace")
216
```
217
218
### Descriptor Classes
219
220
Classes that define the components of a design space document.
221
222
```python { .api }
223
class AxisDescriptor:
224
def __init__(self):
225
"""Initialize axis descriptor."""
226
227
name: str # Human-readable axis name
228
tag: str # 4-character axis tag
229
minimum: float # Minimum axis value
230
default: float # Default axis value
231
maximum: float # Maximum axis value
232
hidden: bool # Hide axis from user interfaces
233
labelNames: Dict # Localized axis names
234
235
class SourceDescriptor:
236
def __init__(self):
237
"""Initialize source descriptor."""
238
239
filename: str # Path to master font file
240
location: Dict # Axis coordinates for this master
241
copyInfo: bool # Copy font info from this master
242
copyGroups: bool # Copy glyph groups
243
copyLib: bool # Copy font lib
244
copyFeatures: bool # Copy OpenType features
245
muteKerning: bool # Disable kerning from this master
246
mutedGlyphNames: List # Glyphs to exclude from this master
247
248
class InstanceDescriptor:
249
def __init__(self):
250
"""Initialize instance descriptor."""
251
252
name: str # Instance name
253
location: Dict # Axis coordinates
254
styleName: str # Style name for naming table
255
familyName: str # Family name override
256
postScriptFontName: str # PostScript name
257
styleMapFamilyName: str # Style map family name
258
styleMapStyleName: str # Style map style name
259
260
class RuleDescriptor:
261
def __init__(self):
262
"""Initialize rule descriptor."""
263
264
name: str # Rule name
265
conditionSets: List[Dict] # Conditions when rule applies
266
subs: List[Tuple[str, str]] # Glyph substitutions (from, to)
267
```
268
269
### Variable Font Utilities
270
271
Utility functions for variable font manipulation and optimization.
272
273
```python { .api }
274
def set_default_weight_width_slant(font, axes_config=None):
275
"""
276
Set default axis values in STAT table.
277
278
Parameters:
279
- font: TTFont, variable font
280
- axes_config: Dict, axis configuration overrides
281
"""
282
283
def drop_implied_oncurve_points(font):
284
"""
285
Optimize TrueType curves by dropping implied on-curve points.
286
287
Parameters:
288
- font: TTFont, font to optimize
289
"""
290
291
def addGSUBFeatureVariations(font, conditional_substitutions):
292
"""
293
Add GSUB feature variations for conditional substitutions.
294
295
Parameters:
296
- font: TTFont, variable font
297
- conditional_substitutions: Dict, feature variation definitions
298
"""
299
```
300
301
#### Variable Font Optimization
302
303
```python
304
from fontTools.varLib import set_default_weight_width_slant, drop_implied_oncurve_points
305
306
# Optimize variable font
307
variable_font = build("MyFont.designspace")
308
309
# Set default axes values for better browser support
310
set_default_weight_width_slant(variable_font, {
311
'wght': 400, # Regular weight as default
312
'wdth': 100 # Normal width as default
313
})
314
315
# Optimize TrueType curves
316
drop_implied_oncurve_points(variable_font)
317
318
variable_font.save("MyFont-VF-Optimized.ttf")
319
```
320
321
### Master Font Finding
322
323
Utilities for locating and validating master font files.
324
325
```python { .api }
326
class MasterFinder:
327
def __init__(self, template):
328
"""
329
Template-based master font finder.
330
331
Parameters:
332
- template: str, path template with placeholders
333
"""
334
335
def __call__(self, source_path):
336
"""
337
Find master font file.
338
339
Parameters:
340
- source_path: str, source file path from designspace
341
342
Returns:
343
str: Resolved master font path
344
"""
345
```
346
347
#### Custom Master Finding
348
349
```python
350
from fontTools.varLib import MasterFinder
351
352
# Template-based finder
353
finder = MasterFinder("masters/{stem}.ufo")
354
355
# Custom finder function
356
def custom_finder(source_path):
357
"""Custom logic for finding masters."""
358
import os
359
360
# Look in multiple directories
361
search_paths = ["masters/", "sources/", "fonts/"]
362
363
for search_dir in search_paths:
364
full_path = os.path.join(search_dir, source_path)
365
if os.path.exists(full_path):
366
return full_path
367
368
raise FileNotFoundError(f"Master not found: {source_path}")
369
370
# Use with build
371
variable_font = build("MyFont.designspace", master_finder=custom_finder)
372
```
373
374
### Feature Variations
375
376
Support for conditional OpenType feature substitutions in variable fonts.
377
378
```python
379
# Add feature variations for different weights
380
feature_variations = {
381
'liga': [
382
{
383
'conditions': [{'wght': (400, 900)}], # Bold weights
384
'substitutions': [('f_i', 'f_i.bold')] # Use bold ligature
385
}
386
]
387
}
388
389
addGSUBFeatureVariations(variable_font, feature_variations)
390
```
391
392
### Error Handling
393
394
Variable font building may raise various exceptions:
395
396
- **FileNotFoundError**: Master font files not found
397
- **DesignSpaceDocumentError**: Invalid designspace document
398
- **VarLibError**: Variable font building errors
399
- **InterpolationError**: Incompatible masters for interpolation
400
401
### Integration Examples
402
403
```python
404
def build_font_family_variable(designspace_path, output_dir):
405
"""Build variable font family with proper naming."""
406
import os
407
from pathlib import Path
408
409
# Load and validate designspace
410
doc = load_designspace(designspace_path)
411
412
# Build variable font
413
vf = build(doc, optimize=True)
414
415
# Set up proper naming
416
name_table = vf['name']
417
family_name = doc.default.familyName or "Variable Font"
418
419
# Update name table entries
420
name_table.setName(family_name, 1, 3, 1, 0x409) # Family name
421
name_table.setName("Variable", 2, 3, 1, 0x409) # Subfamily name
422
423
# Save with proper filename
424
output_path = Path(output_dir) / f"{family_name.replace(' ', '')}-VF.ttf"
425
vf.save(output_path)
426
427
return output_path
428
429
def validate_masters_compatibility(designspace_path):
430
"""Validate that masters are compatible for interpolation."""
431
doc = load_designspace(designspace_path)
432
masters = load_masters(doc, lambda s: s)
433
434
# Check glyph compatibility
435
reference_glyphs = None
436
for source, font in masters.items():
437
glyph_set = set(font.getGlyphOrder())
438
439
if reference_glyphs is None:
440
reference_glyphs = glyph_set
441
elif glyph_set != reference_glyphs:
442
missing = reference_glyphs - glyph_set
443
extra = glyph_set - reference_glyphs
444
print(f"Master {source} compatibility issues:")
445
if missing:
446
print(f" Missing glyphs: {missing}")
447
if extra:
448
print(f" Extra glyphs: {extra}")
449
450
return len(masters)
451
```