Parse a Font
Parsing TrueType (TTF) Font Files
A practical, implementation-agnostic guide to reading the TTF binary format. This page covers the mental model and parse order; the byte-level field layouts for every table live on the compact reference — TTF Byte Layout Reference.
Contents
1. Introduction
The goal is to take a .ttf file as a flat array of bytes and turn it into
structured data — the font header, the glyph outlines, the metrics, the
character map. This document covers how to do that: the file’s shape, the
order to read things in, and how to move the read cursor through the bytes.
2. Mental model
A TTF file is not a document you read front to back. It is a small archive (the format is called “sfnt” — scalable font). Its bytes are organized as:
- A 12-byte offset table (the sfnt header): what kind of font this is and how many tables it holds.
- A table directory: one fixed-size record per table, each saying “the table with this tag lives at this byte offset and is this long.”
- The table data, located only by the offsets in the directory.
The crucial consequence: you never assume where anything is. You read a table’s offset and length from the directory, then jump there.
It helps to know what the tables are for before parsing them. This is how a character becomes a drawn glyph:
character code ──(cmap)──► glyph index ──(loca)──► glyf (the outline)
└─(hmtx)──► advance width (the spacing)
head and maxp are the configuration that makes loca interpretable. This
chain is also why the parse order in section 3 is what it is.
3. Parse order
Where a table sits (physical) is independent of when you decode it (logical). Decode order is forced by data dependencies:
- Offset table — Read the first 12 bytes; capture
numTables. - Table directory — Read
numTables × 16bytes into atag → (offset, length)map. After this, switch from linear reading to seeking by offset. head— YieldsunitsPerEm,indexToLocFormat, the font bounding box.maxp— YieldsnumGlyphs.hhea— YieldsnumberOfHMetrics.hmtx— NeedsnumberOfHMetrics(hhea) andnumGlyphs(maxp).loca— NeedsindexToLocFormat(head) andnumGlyphs(maxp).glyf— Needslocato bracket each glyph.cmap— Independent; the character → glyph map.name,post,OS/2,kern— Independent / optional; parse as needed.
Validate as you go. sfntVersion is a known value; head.magicNumber == 0x5F0F3CF5; every offset + length stays within the file; the last loca entry equals the glyf table length.
4. Byte layouts
For offset-by-offset field layouts of every table — big-endian, all field types, every table — see the TTF Byte Layout Reference. It is organized as quick-lookup tables for every table in the font.