A TrueType or OpenType
font is a set of tables, each named by a four-character tag such as head,
cmap or glyf. WOFF keeps those tables byte for byte, but
compresses each one on its own and replaces the font’s own table records with a directory
of its own. The directory starts at offset 44, straight after the
header, with one 20-byte entry per table:
numTables × 20 bytes in all.
Directory entry
Offsets are from the start of the entry; sizes are in bytes. All values are big-endian.
| Offset | Size | Type | Name | Description |
|---|---|---|---|---|
0 | 4 | UInt32 | tag | The table’s four-character tag, as in the font, for example glyf or cvt (shorter tags end in spaces). |
4 | 4 | UInt32 | offset | Where the table’s data starts, from the start of the WOFF file. Always a multiple of 4. |
8 | 4 | UInt32 | compLength | The length of the data as stored, without padding. |
12 | 4 | UInt32 | origLength | The length of the table once uncompressed, without padding. When it equals compLength the table is stored uncompressed. |
16 | 4 | UInt32 | origChecksum | The table’s checksum, taken from the record in the original font. See checksums. |
Order and padding
Two orders are involved, and they need not be the same:
- Directory entries are sorted by tag in ascending order, compared
as unsigned 32-bit numbers. So upper-case tags come before lower-case ones:
OS/2beforecmap. A producer must write them this way. - Table data is stored in the order the tables had in the original
font, which the offsets show. In the example, the original
font put
glyffirst, soglyfcomes first in the WOFF file too, although it is the sixth entry in the directory.
Every table starts on a four-byte boundary. After each table come up to three zero
bytes to reach the next boundary; compLength and origLength
do not count them. The first table follows the directory directly, and the rest
follow one another with nothing between them but that padding. Bytes anywhere else
that belong to no table or block make the file invalid, as does any table that
overlaps another or runs past the end of the file.
Compression
Each table is compressed on its own with zlib’s compress2() and
uncompressed with uncompress(), or any code that produces the same
format: zlib’s, defined in RFC 1950, which is a two-byte header, a raw deflate stream
and an Adler-32 checksum. Compressed tables in real files start 78 DA,
the zlib header for the best compression level. The specification’s media type
registration (its appendix B) says WOFF “uses gzip compression”, which disagrees with
the rest of the document; the data is zlib, not gzip.
- If compressing a table would not make it smaller, it must be stored as it is, with
compLengthequal toorigLength. - A producer may also store any other table uncompressed.
- A
compLengthgreater thanorigLengthmakes the file invalid. - So does a table that fails to uncompress, or that uncompresses to anything other
than exactly
origLengthbytes.
An invalid file must not be loaded at all; a user agent does not skip the bad table and use the rest.
Checksums
WOFF does not define a checksum of its own. origChecksum is the value the
original font gave the table, which the OpenType specification defines as the sum of
the table’s bytes read as big-endian UInt32 words, with the last word padded with zero
bytes, ignoring overflow. For the head table the
checksumAdjustment field (bytes 8 to 11 of head) is counted
as zero. checksumAdjustment itself is 0xB1B0AFBA minus the
same sum taken over the whole font file.
A producer must check every table’s checksum and the head checksum of
the font it is given, and refuse a font where they are wrong. The specification asks
that a decoded font match the original bit for bit; a tool that cannot achieve that
because the input font is faulty should refuse it or warn.
Rebuilding the font
The original font’s header and table records are not stored. A decoder makes them
again, in a buffer of totalSfntSize bytes:
- The 12-byte offset table:
flavorfrom the WOFF header assfntVersion, thennumTables, thensearchRange,entrySelectorandrangeShift, which it must compute as OpenType defines them:searchRangeis the largest power of 2 not abovenumTables, times 16;entrySelectoris the base-2 logarithm of that power of 2;rangeShiftisnumTables× 16 −searchRange. - One 16-byte table record per table, in ascending tag order:
tag,checksum(theorigChecksum),offsetin the rebuilt font, andlength(theorigLength). - The uncompressed tables, each padded with zeros to a multiple of 4.
The specification does not say where in the rebuilt font each table must go, only
that the records are in tag order. A decoder may reorder the tables, or leave some out.
If it does, the font checksum in head must be worked out again, and a
DSIG digital signature table, which would no longer match, should be
removed.
For 14 tables the largest power of 2 not above 14 is 8, so the rebuilt offset table of the example is:
00 01 00 00 sfntVersion TrueType, from flavor
00 0E numTables 14
00 80 searchRange 8 × 16 = 128
00 03 entrySelector log2(8) = 3
00 60 rangeShift 14 × 16 − 128 = 96
Example
The directory of KaTeX’s Size2 Regular font (see the header example), 14 entries from offset 44 to offset 323. The first entry’s bytes:
0000002C 4F 53 2F 32 00 00 0D BC 00 00 00 4E 00 00 00 60 OS/2.......N...`
0000003C 45 61 95 F5 Ea..
The whole directory, in the order it is stored:
| Tag | Offset | compLength | origLength | origChecksum | Stored |
|---|---|---|---|---|---|
OS/2 | 3,516 | 78 | 96 | 456195F5 | compressed |
cmap | 3,596 | 143 | 196 | 9E88C148 | compressed |
cvt | 5,588 | 10 | 14 | 00000000 | compressed |
fpgm | 3,740 | 1,724 | 3,596 | 622EF97A | compressed |
gasp | 6,180 | 8 | 8 | 00000010 | as it is |
glyf | 324 | 2,872 | 5,390 | 8522CEC5 | compressed |
head | 3,312 | 54 | 54 | 62F242D7 | as it is |
hhea | 3,484 | 31 | 36 | 097C017E | compressed |
hmtx | 3,368 | 114 | 158 | 76ED01CD | compressed |
loca | 3,228 | 82 | 82 | 59455413 | as it is |
maxp | 3,196 | 29 | 32 | 00BB0ECB | compressed |
name | 5,600 | 416 | 1,152 | 5FD37EB2 | compressed |
post | 6,016 | 161 | 292 | 70A69C6C | compressed |
prep | 5,464 | 122 | 156 | 7EB63BB6 | compressed |
Sorted by offset, the data runs glyf, maxp,
loca, head, hmtx, hhea,
OS/2, cmap, fpgm, prep,
cvt , name, post, gasp, the
order of the original font. glyf starts at 324, right after the
directory, and ends at 3,196, where maxp starts. maxp ends at
3,225, and three zero bytes pad it to 3,228, where loca starts.
gasp ends at 6,188, the end of the file.
head, loca and gasp are stored as they are,
because zlib could not make them smaller. The cvt table is 14 zero
bytes, which is why its checksum is 0, and its 10 stored bytes are a complete zlib
stream:
78 DA zlib header: deflate, 32 KiB window, best compression
63 40 01 00 deflate data: 14 zero bytes
00 0E 00 01 Adler-32 of the uncompressed data