Variable Fonts
Variable fonts contain one or more axes of variation (weight, width, slant, …) that can be set to any value within a defined range. The shaper applies these variations during shaping, adjusting glyph advances, positions, and substitutions accordingly.
Checking for Variable Font Support
shaper, _ := ot.NewShaper(font)
if shaper.HasVariations() {
fmt.Println("This is a variable font")
}You can also check via the Face:
face, _ := ot.NewFace(font)
face.HasVariations()Querying Axes
List all variation axes with their ranges:
face, _ := ot.NewFace(font)
for _, axis := range face.VariationAxes() {
fmt.Printf(" %s: %.0f .. %.0f (default %.0f)\n",
axis.Tag.String(),
axis.MinValue, axis.MaxValue, axis.DefaultValue)
}The AxisInfo struct:
| Field | Type | Description |
|---|---|---|
Index |
int |
Axis index |
Tag |
Tag |
Axis tag (wght, wdth, slnt, …) |
MinValue |
float32 |
Minimum value |
DefaultValue |
float32 |
Default value |
MaxValue |
float32 |
Maximum value |
Find a specific axis:
if axis, ok := face.FindVariationAxis(ot.MakeTag('w', 'g', 'h', 't')); ok {
fmt.Printf("Weight: %g – %g\n", axis.MinValue, axis.MaxValue)
}Setting Variations
Multiple Axes at Once
SetVariations sets all axes simultaneously. Axes not included in the list are reset to their default values.
shaper.SetVariations([]ot.Variation{
{Tag: ot.MakeTag('w', 'g', 'h', 't'), Value: 700}, // Bold
{Tag: ot.MakeTag('w', 'd', 't', 'h'), Value: 75}, // Condensed
})
shaper.Shape(buf, nil) // Shapes with weight=700, width=75
The Variation type:
type Variation struct {
Tag Tag // Axis tag (e.g., 'wght')
Value float32 // Axis value (clamped to min/max)
}Values are automatically clamped to the axis range.
Single Axis
SetVariation changes one axis without resetting the others:
// Set weight to 700, keep all other axes at their current values
shaper.SetVariation(ot.MakeTag('w', 'g', 'h', 't'), 700)Named Instances
Variable fonts often define named instances like “Bold”, “Light Italic”, etc.:
face, _ := ot.NewFace(font)
for _, inst := range face.NamedInstances() {
fmt.Printf("Instance %d: coords=%v\n", inst.Index, inst.Coords)
}
// Apply a named instance
shaper.SetNamedInstance(2) // Sets all axes to that instance's coordinates
The NamedInstance struct:
| Field | Type | Description |
|---|---|---|
Index |
int |
Instance index |
SubfamilyNameID |
uint16 |
Name table entry for subfamily |
PostScriptNameID |
uint16 |
Name table entry for PostScript name |
Coords |
[]float32 |
Axis coordinates for this instance |
Reading Current Coordinates
// User-space coordinates (as set by SetVariations / SetVariation)
designCoords := shaper.DesignCoords()
// Normalized coordinates in [-1, 1] range (after avar mapping)
normCoords := shaper.NormalizedCoords()Common Axis Tags
| Tag | Name | Typical Range | Description |
|---|---|---|---|
wght |
Weight | 100 – 900 | Thin (100) to Black (900) |
wdth |
Width | 50 – 200 | Condensed (75) to Expanded (125) |
slnt |
Slant | -20 – 0 | Upright (0) to Oblique (-12) |
ital |
Italic | 0 – 1 | Upright (0) or Italic (1) |
opsz |
Optical Size | 6 – 144 | Font size in points |
Example: Weight Animation
shaper, _ := ot.NewShaper(font)
buf := ot.NewBuffer()
// Shape at different weights
for weight := float32(100); weight <= 900; weight += 100 {
shaper.SetVariation(ot.MakeTag('w', 'g', 'h', 't'), weight)
buf.Clear()
buf.AddString("Variable")
buf.GuessSegmentProperties()
shaper.Shape(buf, nil)
// Advances change with weight
totalWidth := int16(0)
for _, p := range buf.Pos {
totalWidth += p.XAdvance
}
fmt.Printf("Weight %3.0f → total advance: %d\n", weight, totalWidth)
}How Variations Affect Shaping
Variable font variations are applied at multiple levels during shaping:
- Horizontal advances — HVAR table or gvar phantom points adjust glyph widths
- Vertical advances — gvar phantom points adjust glyph heights
- GPOS positioning — Device tables in GPOS use variation deltas
- Glyph extents — gvar deltas modify bounding boxes (affects vertical origin calculations)
- Vertical origins — gvar phantom points adjust top side bearing
The shaper handles all of this automatically. You just set the axis values and shape.