A font file is not just a collection of letter shapes. It is a sophisticated database containing thousands of glyphs, precise spacing information, rules for substituting and positioning characters, hinting instructions for screen rendering, and metadata describing the typeface. A single font file might contain over 3,000 glyphs covering multiple languages, each with carefully designed curves and meticulously specified spacing relationships.
Modern font files are the product of decades of evolution, accumulating features as typography moved from metal type to phototypesetting to digital displays. They carry forward lessons learned from centuries of print typography while adapting to challenges that metal type never faced: rendering at arbitrary sizes on pixel grids of varying densities, supporting scripts that flow in different directions, handling dynamic layout in responsive web designs.
Understanding what goes into a font file illuminates both the complexity of digital typography and the ingenuity of the engineers and type designers who built these systems. It also explains why fonts are so expensive to create - each glyph represents hours of skilled design work, and the supporting data requires deep expertise in both typography and software engineering.
The table structure
OpenType fonts organize their data into tables - discrete chunks of data identified by four-letter tags. Some tables are required: 'head' contains global font information, 'hhea' defines horizontal metrics, 'maxp' specifies memory requirements, 'cmap' maps character codes to glyph indices. Others are optional: 'kern' for legacy kerning, 'GSUB' for glyph substitution, 'GPOS' for advanced positioning.
A typical font might contain 15-25 tables, though complex fonts with extensive language support can have many more. The file begins with an offset table that indexes all tables, allowing software to quickly locate specific data without parsing the entire file. This table-based architecture allows the format to evolve - new tables can be added without breaking compatibility with older software that simply ignores unknown tables.
OpenType Font Tables
The distinction between TrueType and PostScript flavored OpenType fonts lies primarily in the outline format. TrueType fonts store outlines in the 'glyf' table using quadratic Bézier curves. PostScript (CFF) fonts use the 'CFF ' or 'CFF2' table with cubic Béziers. Both can coexist in the same ecosystem, with applications handling either transparently.
Glyph outlines: the shapes themselves
The heart of a font is its glyph outlines - the Bézier curves that define each character's shape. TrueType outlines use quadratic Bézier curves, which are simpler to process but require more control points to describe smooth shapes. PostScript/CFF outlines use cubic curves, offering more design flexibility but requiring more computation to render.
Each glyph outline is a series of contours - closed paths that define the character's shape. A lowercase 'o' has two contours: the outer boundary and the inner counter (the hole). A lowercase 'i' might have two contours as well: the main body and the dot. Complex glyphs like an ampersand might have multiple overlapping contours that must be filled correctly using winding rules.
Typographic Anatomy
Outlines are defined in a coordinate system called 'font units' or 'em units.' The em square - typically 1000 or 2048 units on a side - represents the notional size of the font. When you set type at 12 points, the em square is scaled to 12 points, and all glyph coordinates scale proportionally. This resolution-independence is what allows fonts to render crisply at any size.
// Conceptual structure of glyph outline data
interface Glyph {
name: string // e.g., 'A', 'ampersand', 'uni0041'
unicode?: number[] // Unicode code points this glyph represents
advanceWidth: number // How far to move after drawing this glyph
leftSideBearing: number // Space before the glyph's leftmost point
contours: Contour[] // The actual outline data
}
interface Contour {
points: Point[] // Control points defining the contour
closed: boolean // Almost always true for font outlines
}
interface Point {
x: number // In font units (0-2048 typically)
y: number
onCurve: boolean // True for endpoints, false for control handles
}Metrics: positioning characters
Beyond shapes, fonts must specify how characters relate to each other spatially. Each glyph has an advance width - how far to move rightward before placing the next character. It has left and right side bearings - the whitespace padding on either side of the actual drawn shape. These metrics are as important as the outlines themselves; poorly spaced letters are immediately noticeable even if the shapes are beautiful.
Vertical metrics are more complex. The 'hhea' table specifies the ascender height (how high letters like 'b' and 'd' reach), descender depth (how low 'p' and 'g' extend), and line gap (extra space between lines). Different operating systems and applications interpret these values differently, leading to the frustrating inconsistencies you may have noticed when the same font renders with different line heights in different contexts.
// Simplified text layout using font metrics
function layoutText(text: string, font: Font, fontSize: number): LayoutResult {
const scale = fontSize / font.unitsPerEm
let x = 0
const glyphs: PlacedGlyph[] = []
for (let i = 0; i < text.length; i++) {
const glyph = font.getGlyph(text[i])
const prevGlyph = i > 0 ? font.getGlyph(text[i-1]) : null
// Apply kerning adjustment if a pair is defined
const kern = prevGlyph ? font.getKerning(prevGlyph, glyph) * scale : 0
x += kern
glyphs.push({
glyph,
x: x + glyph.leftSideBearing * scale,
y: 0
})
x += glyph.advanceWidth * scale
}
return { glyphs, totalWidth: x }
}Kerning: the fine adjustments
Standard metrics work well for most letter pairs, but some combinations need special treatment. The word 'WAVE' would have awkward gaps without kerning - the slanted sides of W, A, and V create visual holes that must be closed. The pair 'To' needs the 'o' tucked under the 'T's crossbar. Without kerning, text looks amateurish even with well-designed letters.
Legacy kerning is stored in the 'kern' table as a list of glyph pairs and adjustment values. A font might define thousands of kerning pairs. Modern fonts use the 'GPOS' (Glyph Positioning) table, which offers more sophisticated positioning including contextual kerning, mark positioning for diacritics, and adjustments based on script or language.
Kerning values can be positive (increasing space) or negative (decreasing space). The values are in font units and scale with the font size. A kerning value of -50 in a 1000 UPM font reduces spacing by 5% of the em square. Type designers spend considerable time tuning kerning tables, often reviewing hundreds of critical pairs and testing them in various combinations and sizes.
OpenType features: beyond basic text
Modern fonts can do far more than display characters. OpenType features enable sophisticated typography: ligatures that combine 'fi' and 'fl' into single elegant glyphs, small caps for acronyms, old-style numerals for body text, tabular figures for financial tables, stylistic alternates for decorative contexts. These features transform plain text into refined typography.
The GSUB (Glyph Substitution) table defines rules for replacing glyphs based on context. A 'liga' (standard ligatures) feature might specify that the sequence 'f' followed by 'i' should be replaced with a single 'fi' ligature glyph. A 'smcp' (small caps) feature maps lowercase letters to small capital forms. A 'ss01' (stylistic set 1) feature might offer an alternate 'g' with a single-story design.
The GPOS (Glyph Positioning) table handles positioning adjustments beyond simple kerning. It can position diacritical marks correctly over their base characters (critical for Arabic, Vietnamese, and many other scripts), create subscript and superscript positioning, and handle the complex mark stacking required by Indic scripts.
/* Enabling various OpenType features in CSS */
.refined-text {
font-feature-settings:
'kern' 1, /* Kerning - usually on by default */
'liga' 1, /* Standard ligatures */
'calt' 1, /* Contextual alternates */
'onum' 1, /* Old-style numerals */
'pnum' 1; /* Proportional numerals */
}
.tabular-data {
font-feature-settings:
'tnum' 1, /* Tabular figures - same width for alignment */
'lnum' 1; /* Lining figures - consistent height */
}Hinting: optimizing for screens
Vector outlines can be scaled to any size, but screens have finite resolution. At small sizes, the mathematical outline might fall between pixel boundaries, creating ambiguity about which pixels to light up. Poor choices create uneven stroke weights, wobbly baselines, and illegible text. Hinting instructions tell the rasterizer how to fit the outline to the pixel grid.
TrueType hinting uses a complete programming language - essentially assembly code executed by the rasterizer. Instructions can move points to align with pixel boundaries, ensure stems have consistent width, and maintain relationships between features. Professional hinting of a single glyph can take hours; a complete font might represent months of work.
PostScript/CFF fonts use a simpler 'hints' mechanism - declarations about stem widths and positions rather than imperative instructions. This is less powerful but much easier to create. Modern operating systems use sophisticated auto-hinting and anti-aliasing that reduce the need for manual hinting, though hand-hinted fonts still render better at small sizes on low-DPI screens.
Variable fonts: infinite variations
Traditional font families ship separate files for each weight and style: Regular, Bold, Italic, Bold Italic, Light, Medium, Semibold - a family might include 16+ files. Variable fonts contain all variations in a single file, with the ability to smoothly interpolate between them. Want weight 450 instead of 400 or 500? A variable font can produce it instantly.
Variable fonts define 'axes of variation' - weight, width, slant, and custom axes defined by the type designer. Each glyph contains master outlines at extreme positions on these axes. The rasterizer interpolates between masters to produce any intermediate design. This enables responsive typography that adapts to reading conditions, animated type that smoothly transitions between weights, and reduced bandwidth since one file replaces many.
The technical implementation adds several new tables: 'fvar' defines the variation axes and named instances, 'gvar' contains glyph variations for TrueType outlines, 'HVAR' and 'VVAR' handle metric variations, and 'MVAR' varies global metrics. The mathematics of interpolation extends to delta values applied to control points, producing smoothly varying outlines across the design space.
/* Using variable fonts in CSS */
@font-face {
font-family: 'MyVariableFont';
src: url('myfont.woff2') format('woff2-variations');
font-weight: 100 900; /* Declare weight range */
font-stretch: 75% 125%; /* Declare width range */
}
.headline {
font-family: 'MyVariableFont';
font-weight: 750; /* Any value in the range */
font-variation-settings: 'wdth' 110, 'GRAD' 88;
}A font file is a small marvel of engineering - a compact database encoding the accumulated knowledge of type designers, engineers, and rendering specialists. The next time you read text on a screen, remember that each letter represents thousands of carefully placed control points, meticulously tuned spacing values, and sophisticated rendering instructions working together to make the text legible and beautiful.