A variable font (OpenType 1.8, 2016) holds a continuous range of designs, such as every
weight from thin to black, in one file. Its outlines and metrics are a default design
plus deltas that apply in regions of a design space made of axes. This page
covers the tables that define that space: fvar (required), avar
(optional) and STAT (required in variable fonts, recommended in others).
The deltas themselves are on the variation
data page. All numbers are big-endian.
fvar
| Offset | Type | Name | Description |
|---|---|---|---|
0 | uint16 | majorVersion | 1. |
2 | uint16 | minorVersion | 0. |
4 | Offset16 | axesArrayOffset | Where the axis records start, from the start of the table (16 in practice). |
6 | uint16 | reserved | Always 2 (once a count of size/count pairs). |
8 | uint16 | axisCount | Number of axes. 0 means the font must be treated as not variable. |
10 | uint16 | axisSize | Size of an axis record: 20. |
12 | uint16 | instanceCount | Number of named instances. |
14 | uint16 | instanceSize | Size of an instance record: 4 × axisCount + 4, or + 6 when it includes postScriptNameID. |
The axis records follow at axesArrayOffset, then the instance records
straight after them. Readers must step through both with the sizes given, since later
minor versions may make records longer.
VariationAxisRecord
| Offset | Type | Name | Description |
|---|---|---|---|
0 | Tag | axisTag | The axis tag. |
4 | Fixed | minValue | Lowest value the font supports, in the axis’s user scale. |
8 | Fixed | defaultValue | The value of the default design, the outlines stored in glyf or CFF2. |
12 | Fixed | maxValue | Highest value. |
16 | uint16 | flags | Bit 0 (0x0001) HIDDEN_AXIS: do not show this axis in ordinary user interfaces. Other bits reserved. |
18 | uint16 | axisNameID | Name ID of the axis’s display name, 256 to 32767. |
InstanceRecord
| Offset | Type | Name | Description |
|---|---|---|---|
0 | uint16 | subfamilyNameID | Name ID of the instance’s style name, used like name ID 17. 2 or 17 only for the default instance; otherwise 256 to 32767. |
2 | uint16 | flags | Reserved, 0. |
4 | Fixed × axisCount | coordinates | The instance’s position, in user scale, in axis order. |
| + | uint16 | postScriptNameID | Optional (all records have it or none): name ID of the instance’s PostScript name; 6 only for the default instance; 0xFFFF for none. |
- The default instance always exists, even with no record for it.
- Records should not repeat coordinates or name IDs; duplicates after the first can be ignored.
- Software picks an instance by name or by arbitrary axis values; a missing axis takes its default, and values outside the font’s range are clamped to it.
Normalisation and regions
All variation data works on normalised coordinates, −1 to +1, not the user
values in fvar. For each axis the default maps to 0, the minimum to −1 and
the maximum to +1, linearly in between:
if (v < default) n = −(default − v) / (default − min)
else if (v > default) n = (v − default) / (max − default)
else n = 0
Then, if there is an avar table, its segment map for the axis bends that
line (below). Normalised values are stored as F2DOT14.
Each set of deltas applies to a region: per axis a start, peak and end coordinate. For an instance at normalised position n the per-axis factor is: 1 if the region’s peak on that axis is 0 (the axis does not matter); 0 if n is outside start to end; 1 at the peak; otherwise a straight-line ramp from 0 at start (or end) up to 1 at the peak. The region’s scalar is the product of its axes’ factors, and every delta is multiplied by its region’s scalar and added to the default value. Regions in the item variation store must have start ≤ peak ≤ end, all on one side of 0 unless the peak is 0.
avar
| Structure | Type | Name | Description |
|---|---|---|---|
| Header | uint16 | majorVersion | 1. |
uint16 | minorVersion | 0. | |
uint16 | reserved | 0. | |
uint16 | axisCount | Must equal fvar’s. | |
| SegmentMaps × axisCount | axisSegmentMaps | One per axis, in fvar order. | |
| SegmentMaps | uint16 | positionMapCount | Number of pairs for this axis; 0 leaves the axis unchanged. |
| AxisValueMap × count | axisValueMaps | Each F2DOT14 fromCoordinate (default-normalised) and F2DOT14 toCoordinate (what it becomes). |
- A map that is used must include −1→−1, 0→0 and 1→1; if any is missing the axis is not changed at all.
fromCoordinatevalues must strictly increase andtoCoordinatevalues must not decrease; records breaking this may be ignored.- Between neighbouring pairs the value is interpolated linearly, so the map is a piecewise-linear curve from −1 to 1.
OpenType 1.9.1 defines only version 1.0. A version 2.0, which adds an item variation store so that axes can affect one another, has been proposed and is supported by some software, but is not part of this specification and is not documented here.
STAT
The style attributes table describes each font’s place in its family: the axes the family varies along (variable or not), and labels such as “Bold” or “Condensed” for values on them. Software uses it to build style names and menus, and to fit large families into older models (regular/bold/italic, or weight-width-slope).
| Offset | Type | Name | Description |
|---|---|---|---|
0 | uint16 | majorVersion | 1. |
2 | uint16 | minorVersion | 2 (current), 1, or 0 (deprecated, lacks the last field). |
4 | uint16 | designAxisSize | Size of an axis record (8). |
6 | uint16 | designAxisCount | Number of axis records; at least fvar.axisCount in a variable font. |
8 | Offset32 | designAxesOffset | From the start of STAT; 0 if there are none. |
12 | uint16 | axisValueCount | Number of axis value tables. |
14 | Offset32 | offsetToAxisValueOffsets | To an array of Offset16, one per axis value table, each counted from the start of that array. |
18 | uint16 | elidedFallbackNameID | Versions 1.1 and 1.2: the name to use when every label is elidable (often 2 or 17, “Regular”). |
Version 1.2 adds axis value format 4; otherwise 1.1 and 1.2 are the same.
AxisRecord
| Type | Name | Description |
|---|---|---|
Tag | axisTag | The axis; the same tag rules as fvar, no duplicates. |
uint16 | axisNameID | Display name; must match fvar’s for variable axes. |
uint16 | axisOrdering | Order in which labels are combined in names and styles sorted; should agree across the family. |
Records may be in any order, independent of fvar.
Axis value tables
Every format starts with uint16 format; an unknown format is
skipped. Values are Fixed in the axis’s user scale, and name IDs are
256 to 32767 unless an existing name (such as 2 or 17) fits.
| Format | Fields after format | Use |
|---|---|---|
| 1 | uint16 axisIndex, uint16 flags, uint16 valueNameID, Fixed value | A label for one value on one axis. |
| 2 | uint16 axisIndex, uint16 flags, uint16 valueNameID, Fixed nominalValue, Fixed rangeMinValue, Fixed rangeMaxValue | A label for a range (inclusive), with a nominal value for named instances. 0x80000000 and 0x7FFFFFFF mean minus and plus infinity. |
| 3 | uint16 axisIndex, uint16 flags, uint16 valueNameID, Fixed value, Fixed linkedValue | As format 1, plus the style-linked counterpart, such as 400 → 700 for the Bold button. |
| 4 | uint16 axisCount, uint16 flags, uint16 valueNameID, then axisCount × (uint16 axisIndex, Fixed value) | One label for a combination of values on several axes (a name like “Florid” that cannot be split); version 1.2. |
| Flag | Name | Meaning |
|---|---|---|
0x0001 | OLDER_SIBLING_FONT_ATTRIBUTE | Describes other, older fonts of the family, made before this axis was added, not this font. |
0x0002 | ELIDABLE_AXIS_VALUE_NAME | The “normal” value, whose label may be left out of names (such as “Regular” weight). |
0xFFFC | reserved | 0. |
Where tables overlap: touching format 2 ranges go to the higher range unless the lower one’s nominal value sits on the boundary; a format 1 or 3 value on a range’s boundary wins over the range; the more specific format 4 match wins over a single-axis table, and among equally specific format 4 tables the first wins.
Example
SF Mono (SFNSMono.ttf, macOS) is a variable TrueType font. Its
140-byte fvar starts:
0001D0E0 00 01 00 00 00 10 00 02 00 02 00 14 00 06 00 0E
0001D0F0 59 41 58 53 01 26 AC 50 01 26 AC 50 03 84 00 00
0001D100 00 01 01 02 77 67 68 74 01 26 AC 50 01 26 AC 50
0001D110 03 84 00 00 00 00 01 03 00 02 00 00 01 26 AC 50
0001D120 01 26 AC 50 01 04 01 05 00 00 01 44 55 88 01 90
0001D130 00 00 01 06 …
| Bytes | Meaning |
|---|---|
00 01 00 00 00 10 00 02 | Version 1.0; axes at 16; reserved 2. |
00 02 00 14 00 06 00 0E | 2 axes of 20 bytes; 6 instances of 14 bytes (2 × 4 + 6, so with PostScript names). |
59 41 58 53 … | Axis 0: YAXS, a font maker’s own (upper-case) tag; minimum and default 294.673 (0x0126AC50), maximum 900; flags 1, hidden; name ID 258. |
77 67 68 74 … | Axis 1: wght, 294.673 to 900, default 294.673; name ID 259. |
00 02 00 00 01 26 AC 50 01 26 AC 50 01 04 | Instance 0: style name ID 2 (the default instance), at the default coordinates, PostScript name ID 260. |
01 05 00 00 01 44 55 88 01 90 00 00 01 06 | Instance 1: name ID 261 at YAXS 324.33, wght 400; PostScript name ID 262. |
The default is the lightest weight, so every wght region is on the
positive side. Its STAT (version 1.2) has two axis records,
YAXS (ordering 0) and wght (ordering 1), six axis value tables
and elidedFallbackNameID 2; the first table is format 4, labelling the
combination of both axes’ values with name ID 273.