The table directory starts at offset 48, straight after the
header, and has numTables
entries. It lists the tables of the font in the order their data appears in the
compressed stream. Where WOFF 1.0
spent 20 bytes on each entry, WOFF2 usually spends two to six: a common tag is one
number in a flags byte, and lengths are variable-length numbers. There are no offsets
or checksums; a table’s position follows from the lengths of the ones before it, and
the decoder works out the checksums itself.
Directory entry
| Type | Name | Present | Description |
|---|---|---|---|
| UInt8 | flags | always | Bits 0 to 5: the index of the table’s tag in the known tags list, or 63 when the tag follows. Bits 6 and 7: the transformation version, 0 to 3. |
| UInt32 | tag | only when bits 0 to 5 are 63 | The table’s four-character tag, written out. |
| UIntBase128 | origLength | always | The length of the table in the decoded font. |
| UIntBase128 | transformLength | only when a transformation other than the null one applies | The length of the transformed table in the compressed stream. |
The tag index is flags & 0x3F and the transformation version is
flags >> 6. Encoders must use the index for every tag in the list;
index 63 is for other tags.
Transformation versions
A table can be stored as it is (the null transformation) or rearranged first so that it compresses better. Which version number means “as it is” depends on the table:
| Table | Version | Meaning | transformLength |
|---|---|---|---|
glyf | 0 | transformed glyf | present |
glyf | 3 | null: stored as it is | absent |
loca | 0 | transformed loca: rebuilt from the glyf data, takes no bytes | present, and must be 0 |
loca | 3 | null: stored as it is | absent |
hmtx | 0 | null: stored as it is | absent |
hmtx | 1 | transformed hmtx | present |
| any other table | 0 | null: stored as it is | absent |
Any other combination is an unknown transformation, and the decoder must reject the
whole font. glyf and loca are either both transformed or both
not. An encoder should choose the version that gives the smaller result, unless the
gain is too small to matter.
Order
Entries are in the order of the table data in the stream, not sorted by tag. The
one rule is that loca comes after glyf, because it is
rebuilt while glyf is decoded; in a single font other tables may sit
between them, and in a collection each loca comes immediately after its
glyf. When the decoder rebuilds the font it sorts the table records by
tag, as OpenType requires.
Example
The directory of IBM Plex Mono Regular (see the header example): 14 entries in 40 bytes, from offset 48 to 87.
00000030 1A 4C 1B 86 52 1C 86 56 06 60 00 84 58 11 10 0A
00000040 81 8B 78 ED 68 0B 84 32 00 01 36 02 24 03 88 60
00000050 04 20 05 83 3A 07 89 69
| Bytes | Tag | Version | origLength | transformLength |
|---|---|---|---|---|
1A 4C | 26 GDEF | 0, null | 76 | – |
1B 86 52 | 27 GPOS | 0, null | 850 | – |
1C 86 56 | 28 GSUB | 0, null | 854 | – |
06 60 | 6 OS/2 | 0, null | 96 | – |
00 84 58 | 0 cmap | 0, null | 600 | – |
11 10 | 17 gasp | 0, null | 16 | – |
0A 81 8B 78 ED 68 | 10 glyf | 0, transformed | 17,912 | 14,056 |
0B 84 32 00 | 11 loca | 0, transformed | 562 | 0 |
01 36 | 1 head | 0, null | 54 | – |
02 24 | 2 hhea | 0, null | 36 | – |
03 88 60 | 3 hmtx | 0, null | 1,120 | – |
04 20 | 4 maxp | 0, null | 32 | – |
05 83 3A | 5 name | 0, null | 442 | – |
07 89 69 | 7 post | 0, null | 1,257 | – |
loca’s length of 562 is (280 + 1) × 2, so the font has 280 glyphs and
short loca offsets. The tables in the stream take 14,056 bytes for the
transformed glyf, nothing for loca and 5,433 for the rest:
19,489 bytes, which is what the Brotli stream must decompress to.