Font File Anatomy: Inside OpenType Fonts
Every table inside a font file, in one place: the sfnt table directory, the required tables, glyf and CFF outlines, the OS/2 and hhea metrics that set line height, and the KERN and GPOS tables that control letter spacing.
Key Takeaways
- • OpenType fonts are organized as collections of tables
- • 8 tables are required in every valid OpenType font
- • TrueType fonts use glyf+loca; CFF fonts use CFF/CFF2
- • Tables are located via the table directory at file start
- • OS/2 and hhea both carry vertical metrics, and they often disagree
- • Kerning lives in the legacy KERN table, the modern GPOS table, or both
In this article
Every OpenType font file is structured as a collection of tables, each storing a specific type of data. Understanding this structure helps you troubleshoot font issues, understand validation errors, and make informed decisions about format conversion.
The table-based architecture was inherited from the original TrueType specification and extended by Adobe and Microsoft in OpenType. Tables are identified by four-character tags (like cmap, glyf, GSUB), and their order within the file can vary. The table directory at the start of the file provides offset and length information for each table, allowing parsers to locate any table without reading the entire file sequentially.
Table Directory Structure
OpenType Font File Structure
├── Offset Subtable (12 bytes)
│ ├── sfntVersion: 0x00010000 (TrueType) or "OTTO" (CFF)
│ ├── numTables: number of tables
│ ├── searchRange, entrySelector, rangeShift (for binary search)
│
├── Table Record Array (16 bytes × numTables)
│ ├── [0] tag: "cmap", checksum, offset, length
│ ├── [1] tag: "head", checksum, offset, length
│ ├── [2] tag: "hhea", checksum, offset, length
│ └── ... (one record per table)
│
└── Table Data (varies)
├── cmap table data at recorded offset
├── head table data at recorded offset
└── ... (tables can be in any order)sfntVersion: Identifying Font Type
The four-byte sfntVersion field at the start of the offset subtable identifies what kind of outlines the font contains:
0x00010000TrueType outlines
Contains glyf + loca tables. The "1.0" version in fixed-point notation. Standard for TTF files.
"OTTO" (0x4F54544F)CFF outlines
Contains CFF or CFF2 table instead of glyf/loca. All OTF (OpenType with PostScript outlines) files use this signature.
"ttcf" (0x74746366)TrueType Collection
TTC files contain multiple fonts sharing table data. Each sub-font has its own offset subtable within the collection.
WOFF and WOFF2 files have their own signatures (wOFF and wOF2) in a separate WOFF header, but they store the original sfntVersion internally to identify the underlying font type after decompression. The WOFF2 format guide explains how the wrapper structure relates to the tables described here and covers browser support and delivery best practices.
The table directory enables random-access parsing of font tables without sequential file reading. Each table record stores a byte offset and length, so a parser looking for the GPOS table can jump directly to its position without reading through cmap, head, hhea, and other preceding tables. You can inspect all of these tables interactively using our font analyzer tool, which reads the table directory and reports the structure of any uploaded font file. The OS/2 and hhea tables within this structure store vertical measurement values whose cross-platform implications are explained in the metrics tables section below. The specification requires table records to be sorted alphabetically by their four-character tag, which enables binary search through the directory. A font with 20 tables requires at most 5 comparisons to locate any specific table using binary search, compared to up to 20 sequential reads without sorted ordering. Tools like fontTools use this binary search optimization when programmatically accessing specific tables.
Each table record also stores a checksum of that table's data, calculated as the sum of all 32-bit words in the table (with the final word padded to a 4-byte boundary with zeros). The head table is special: its own checksum field is zeroed before calculating the global file checksum stored in checkSumAdjustment, which equals 0xB1B0AFBA minus the sum of all table checksums. Validation tools like OTS and Font Bakery verify both per-table checksums and the global head checksum. Any font modified after generation without recalculating checksums will fail validation, which is one of the most common issues introduced by tools that patch font binaries directly rather than using proper font APIs.
Required Tables
Every valid OpenType font must contain these 8 tables:
cmapCharacter to glyph mapping
headFont header (version, dates, flags)
hheaHorizontal header (metrics)
hmtxHorizontal metrics (advances)
maxpMaximum profile (glyph count)
nameNaming table (metadata strings)
OS/2Windows metrics and embedding
postPostScript information
Table Interdependencies
OpenType tables are not independent. They form a dependency chain that parsers must follow in order:
cmap → glyf/CFF → loca: To render a character, a text engine reads the Unicode code point from cmap to get a glyph ID, then looks up that glyph ID in loca to find its offset in the glyf table, then reads the glyph outline data from glyf.
GSUB → GDEF: The GSUB substitution table references glyph class definitions in GDEF to determine which glyphs are base characters, marks, or ligatures. GSUB lookup rules can depend on GDEF classes for contextual substitutions.
hmtx → hhea: The hmtx table stores advance widths for each glyph. The numberOfHMetrics field in hhea determines how many full records are in hmtx vs how many share the last entry, so parsers must read hhea first to interpret hmtx correctly.
Outline Tables and Glyph Curves
Fonts must contain either TrueType or CFF outlines (not both). The practical differences between these two outline systems, including rendering behavior and file size implications, are covered in the TTF vs OTF comparison.
The visual appearance of every character in a font is defined by its outline, a mathematical description of curves and lines that bound the glyph shape. The two major outline formats in OpenType fonts use different mathematical representations, with implications for quality, file size, and conversion.
TrueType outlines were developed by Apple in 1987 as the mathematical basis for their operating system's font rendering. Microsoft adopted TrueType for Windows, and it became the standard for system fonts on both platforms. Adobe, meanwhile, had been using cubic Bézier curves in its PostScript and Type 1 fonts since the early 1980s. When OpenType was jointly developed by Adobe and Microsoft in 1996, it unified both outline types under a single container format: a font can use TrueType quadratic curves (the glyf table) or PostScript cubic curves (the CFF table), but not both simultaneously within one font. This historical split is the direct reason why converting between TTF and OTF involves a mathematical curve-type transformation, not just a file-format change.
The choice of curve type has practical consequences beyond file format compatibility. Quadratic curves, with their single off-curve control point per segment, require more segments to approximate complex shapes such as the bowl of an "a", the ink trap at the junction of a serif, or the terminals of a script "g". Cubic curves, with two control points per segment, can represent inflection points in a single segment and offer more expressive control over tangent directions. Type designers who work in vector illustration tools like Illustrator or Figma use cubic curves natively, making CFF-based OTF a natural output format. Those working in dedicated font applications like Glyphs.app can work in either format, with the application handling conversion as needed.
TrueType (TTF)
glyf- Glyph outline dataloca- Glyph location indexcvt- Control value table (hints)fpgm- Font program (hints)prep- Pre-program (hints)
CFF (OTF)
CFF- Compact Font Format dataCFF2- CFF version 2 (variable)VORG- Vertical origin (CJK)
The loca Table: Glyph Location Index
The loca (index to location) table is unique to TrueType fonts. CFF fonts have no equivalent because the CFF charstring data is self-delimiting. The loca table stores an array of offsets into the glyf table, one per glyph ID plus a terminator. Two formats exist:
Short Format (indexToLocFormat = 0)
Offsets stored as 16-bit values, multiplied by 2 to get actual byte offset. Maximum glyf table size: 131,072 bytes. Used for smaller fonts.
Long Format (indexToLocFormat = 1)
Offsets stored as 32-bit values directly. No size limit. Required for fonts with glyf tables larger than 128KB, typical for CJK fonts with thousands of complex glyphs.
Quadratic vs Cubic Bezier Curves
TrueType (Quadratic)
Uses quadratic Bezier curves with 3 control points: start point, one off-curve control point, and end point.
B(t) = (1-t)²P0 + 2(1-t)tP1 + t²P2
- • Stored in glyf table
- • Faster to rasterize
- • Needs more points for complex curves
- • Uses on-curve and off-curve points
CFF (Cubic)
Uses cubic Bezier curves with 4 control points: start point, two control points, and end point.
B(t) = (1-t)³P0 + 3(1-t)²tP1 + 3(1-t)t²P2 + t³P3
- • Stored in CFF/CFF2 table
- • More expressive per segment
- • Fewer points needed overall
- • Based on PostScript Type 1
On-Curve vs Off-Curve Points
TrueType outlines distinguish between on-curve points (points the outline actually passes through) and off-curve points (control points that attract the curve without touching it). When two consecutive off-curve points appear in a TrueType contour, the renderer implies an on-curve point exactly at their midpoint. This implicit midpoint rule allows compact storage of smooth curves. A circular arc can be approximated with just four explicit off-curve points and four implied on-curve midpoints.
TrueType Point Flag Byte: Bit 0 = 1: On-curve point (outline passes here) Bit 0 = 0: Off-curve quadratic control point Implicit on-curve rule: Sequence [OFF, OFF] in contour data → Renderer inserts ON* at midpoint of OFF1 and OFF2 → Actual path: [OFF1] → [ON*] → [OFF2] Circle approximation (8 total points): 4 explicit OFF points at compass positions (N, E, S, W) 4 implied ON points at 45° positions (NE, SE, SW, NW) Result: smooth quadratic approximation of a circle
CFF outlines use only explicit control points. The implicit midpoint rule does not apply. Every on-curve and off-curve point must be declared explicitly in the charstring. This means a TrueType font may appear to have fewer listed points than its CFF counterpart, even when representing identical shapes.
Winding Rules and Contour Direction
TrueType and CFF use opposite conventions for contour direction to define filled regions:
TrueType (Non-Zero Winding)
Outer contours run clockwise (↻). Inner contours (holes, as in "O") run counter-clockwise (↺). The rasterizer counts directional crossings: non-zero = filled region.
CFF (Even-Odd / PostScript)
Outer contours run counter-clockwise (↺). Inner contours run clockwise (↻). Exactly opposite to TrueType. Converting outlines requires reversing all contour directions.
Incorrect contour direction is one of the most common validation errors. A letter like "O" with the wrong inner contour direction fills solid instead of showing the center hole. Font Bakery's check/correct_contour_direction catches this; FontForge's Element → Correct Direction fixes it automatically.
Rasterizers implement winding rules at the pixel level using scan-line ray casting. For each pixel center, the renderer fires a horizontal ray leftward and counts how many contour segments it crosses, weighted by direction. Each clockwise crossing contributes +1; each counter-clockwise crossing contributes −1. The non-zero winding rule fills a region if the final count is non-zero. This is why TrueType outer contours run clockwise and inner contours run counter-clockwise: pixels within the inner contour see both crossings and sum to zero, correctly rendering as unfilled holes. Reversing the inner contour makes it contribute +1, totaling +2, and the "hole" fills solid, producing the visual defect of a letter like "O" or "D" rendered as a black rectangle.
Outline Conversion and Quality Thresholds
| Conversion | Quality | File Size |
|---|---|---|
| Quadratic → Cubic | Exact (lossless) | Slightly smaller |
| Cubic → Quadratic | Approximation | 20-50% larger |
Cubic to Quadratic Conversion
A single cubic curve may require 2-4 quadratic segments to approximate accurately. This increases point count and file size. Quality loss is usually imperceptible except at extreme magnification, but high-quality fonts may notice subtle differences in curve smoothness.
When converting cubic CFF curves to quadratic TrueType, tools like fontTools use an error tolerance to determine how many quadratic segments to generate per cubic. The default tolerance is approximately 0.001 font units per em, well below the threshold of visual perception at any normal rendering size.
| Curve Shape | Quadratic Segments Needed | Approx. Point Increase |
|---|---|---|
| Gentle arc (C, c, open bowl) | 2 segments | ~1.5–2× |
| S-curve (s, z diagonals) | 3–4 segments | ~2.5–3× |
| Tight cusp or inflection | 4–6 segments | ~3–4× |
| Straight line segment | 1 segment | No increase |
A typical Latin font converted from OTF to TTF sees overall glyph point counts increase by 20–35%. At rendering sizes up to 400px, no visual difference is perceptible. Conversion artifacts (slight polygon edges at curve peaks) are only visible at extreme magnification (2,000%+) in design software and have no impact on screen or print rendering.
Composite Glyphs
Both TrueType and CFF support composite glyphs, which are characters built from references to other glyphs. For example, "é" can reference the base "e" glyph plus an acute accent glyph with positioning data.
Composite glyph example: é (U+00E9) ├── Component 1: Glyph "e" (ID: 72) │ └── Transform: none (identity) ├── Component 2: Glyph "acutecomb" (ID: 302) │ └── Transform: translate(0, 180) └── Result: Shared outlines, smaller file
Composite glyphs are fully supported in both TrueType and CFF outline formats, but their interaction with hinting differs between the two systems. TrueType's bytecode hinting operates on the final composed outline, while CFF hints are stored per-component and applied independently. When components are scaled, rotated, or flipped in composite definitions, hinting instructions designed for the original orientation may produce suboptimal results.
Composite Glyph Trade-offs
Advantages
- • Shared outline data reduces file size
- • Changes to base glyph propagate to composites
- • Reduces design inconsistencies across accented sets
- • pyftsubset preserves composites when components are included
Trade-offs
- • Hinting quality degrades with transforms applied
- • Some subsetting tools decompose composites to simple glyphs
- • Font Bakery flags composites with shifted component origins
- • CFF2 variable fonts require decomposing composites for variations
Practical outline quality matters beyond validation compliance. Note that modifying a font's outlines requires appropriate licensing rights; check the font modification rights guide before altering any commercial font. Fonts using CFF outlines are distributed as OTF (OpenType with PostScript outlines) files, whose format characteristics and use cases differ meaningfully from TrueType-based TTF files. Outlines with unnecessary points, very short segments, or near-duplicate coordinates that differ by only one or two units can produce visual artifacts at specific rendering sizes. These micro-irregularities are invisible at large sizes but can produce subtle bumps or inconsistent anti-aliasing at 12–14px where individual pixels have significant visual weight. Professional font production includes outline cleanup steps: removing redundant on-curve points on straight segments, resolving near-coincident points, and simplifying overly complex curves while staying within the tolerance thresholds that make conversion quality imperceptible.
Metrics Tables: OS/2 and hhea
Font metrics define the vertical measurements that control line height, baseline positioning, and text alignment. These values are stored primarily in two tables: OS/2 and hhea. These tables often contain different values, and different operating systems may choose different sources, causing cross-platform rendering inconsistencies.
All metric values in OpenType fonts are expressed in font units, which are fractions of the UPM (units per em). The UPM is defined in the head table and is typically 1000 units for CFF fonts and 2048 units for TrueType fonts. A sTypoAscender of 800 in a 1000-UPM font means the ascender reaches 80% of the way up from the baseline to the top of the em square. This unit system makes fonts resolution-independent: the same values produce correct spacing whether the font renders at 12px or 120px.
OS/2 Table Metrics
| Field | Description | Typical Value |
|---|---|---|
| sTypoAscender | Typographic ascender (recommended) | 800-1000 |
| sTypoDescender | Typographic descender (negative) | -200 to -300 |
| sTypoLineGap | Extra space between lines | 0-200 |
| usWinAscent | Windows clipping ascent | 900-1200 |
| usWinDescent | Windows clipping descent (positive) | 200-400 |
| sxHeight | Height of lowercase x | 450-550 |
| sCapHeight | Height of capital letters | 650-750 |
| hhea.ascender | Mac-style ascent, used by macOS for line height | = sTypoAscender |
| hhea.descender | Mac-style descent (negative) | = sTypoDescender |
USE_TYPO_METRICS Bit
The OS/2 table contains a critical flag called fsSelection bit 7, known as USE_TYPO_METRICS (value 0x0080). When this bit is set, Windows renders line height using the typographic metrics (sTypoAscender + sTypoDescender + sTypoLineGap) instead of the Windows-specific values (usWinAscent + usWinDescent). Setting this bit is the recommended approach for new fonts because it produces consistent line heights across Windows and macOS.
USE_TYPO_METRICS not set
Windows uses usWinAscent/usWinDescent for line height. macOS uses hhea ascender/descender. These often differ, causing layout inconsistency.
USE_TYPO_METRICS set (recommended)
Both Windows and macOS use sTypo* values. Line height becomes predictable across platforms. All modern Google Fonts set this bit.
Cross-Platform Rendering Differences
Without USE_TYPO_METRICS, the same font can render with different line heights on different operating systems. Here is a real-world example using a hypothetical font with 1000 UPM:
| Metric Source | Windows (legacy) | macOS / USE_TYPO | Line Height at 16px |
|---|---|---|---|
| usWinAscent=1050, usWinDescent=350 | 1400 units | N/A | 22.4px |
| sTypoAscender=800, sTypoDescender=−200, sTypoLineGap=0 | N/A | 1000 units | 16px (1.0 line-height) |
Without USE_TYPO_METRICS, this font renders 40% taller on Windows than macOS, a common cause of layout bugs in multi-platform web projects.
The UPM value, whether 1000 or 2048, is a historical artifact with practical consequences. CFF fonts inherited 1000 from the PostScript Type 1 specification, where fonts were designed on a 1000-unit grid that could be scaled arbitrarily. TrueType fonts adopted 2048 because higher coordinate resolution gives more granularity for hinting instructions: TrueType's bytecode hinting operates in integer font units, so a 2048-UPM font allows finer positioning of hint control values than a 1000-UPM equivalent. Changing UPM requires scaling every metric value, every glyph coordinate, and every table reference proportionally. It is not a simple header field change. Conversions that silently change UPM can introduce systematic rounding errors across the entire glyph set.
Diagnosing cross-platform line height inconsistencies is one of the most common font metrics challenges for web developers. Incorrect metric values also affect font render blocking and layout stability, since a font that changes line height during loading causes content reflow. The reliable test is to apply line-height: 1 on an element and compare its computed height in pixels across Chrome on Windows and Chrome on macOS. A font without USE_TYPO_METRICS set will produce different computed heights because Windows and macOS query different metric sources when calculating line boxes. After setting USE_TYPO_METRICS and verifying that OS/2 sTypo values and hhea values agree, the computed height should be identical across platforms. The fonttools CLI command fonttools inspect -t OS/2 font.ttf shows all relevant fields, and the --layout-features flag on pyftsubset preserves these values during subsetting.
hhea Table Metrics
The hhea (horizontal header) table contains Mac-style vertical metrics:
ascenderDistance from baseline to top of tallest glyph
descenderDistance from baseline to bottom of lowest glyph (negative)
lineGapExtra spacing between lines
Cross-Platform Issue
macOS uses hhea metrics for line height. Windows traditionally uses usWinAscent/usWinDescent. If these values differ significantly, your text will have different line spacing on each platform. Modern browsers increasingly use sTypo* values when the USE_TYPO_METRICS bit is set in OS/2.
The hhea table's ascender and descender fields were originally designed for macOS's QuickDraw rendering system and carried different design philosophy than the OS/2 sTypo fields. Early font designers often set hhea values to the actual maximum glyph extent (the tallest ascender and deepest descender found anywhere in the character set) rather than the typographic design values that define the intended rhythm. This made hhea-based line height significantly larger than the designer's intent, because occasional extreme glyphs (accented capitals reaching unusually high, below-baseline swash letters) inflated the spacing for every line of text. Modern practice aligns hhea ascender and descender with sTypo equivalents and uses USE_TYPO_METRICS to ensure Windows also applies the intended values.
Excessive line height caused by inflated hhea values is often misdiagnosed as a CSS problem when it is actually a font metrics problem. A font with hhea.ascender of 1200 and hhea.descender of −400 in a 1000-UPM font produces a line box of (1200 + 400) ÷ 1000 = 1.6 line heights at any font size, significantly more spacing than the 1.2 that most UI design assumes. Setting line-height: 1 in CSS does not fix this because hhea metrics determine the natural line box size that is then scaled by the CSS line-height multiplier. Correcting the hhea values in the font itself is the only way to restore normal spacing behavior across all rendering contexts.
Recommended Metric Configuration
For new fonts targeting web and cross-platform desktop use, the industry consensus is:
sTypoAscender + sTypoDescender + sTypoLineGap = UPM value. This produces line-height: 1.0 behavior with no extra spacing.
usWinAscent = maximum glyph ascent, usWinDescent = maximum glyph descent. These define the clipping box on Windows. Values too small clip descenders.
Set USE_TYPO_METRICS (fsSelection bit 7). This tells Windows to use sTypo* values, eliminating the dual-metric inconsistency.
hhea ascender/descender = sTypoAscender/sTypoDescender. Keep hhea metrics consistent with OS/2 typographic metrics to avoid macOS inconsistencies.
CSS Metric Overrides
CSS provides descriptors to override font metrics, primarily useful for matching fallback fonts to custom fonts to prevent layout shift. This technique is directly relevant to solving FOUT and FOIT problems, where unmatched fallback metrics cause visible text reflow as the custom font loads:
@font-face {
font-family: 'CustomFont-Fallback';
src: local('Arial');
/* Match Arial metrics to your custom font */
ascent-override: 92%;
descent-override: 24%;
line-gap-override: 0%;
size-adjust: 105%;
}
body {
font-family: 'CustomFont', 'CustomFont-Fallback', sans-serif;
}To calculate the correct percentage values for ascent-override and descent-override, divide the font's metric values by the UPM, then multiply by 100. For a font with UPM 2048, sTypoAscender 1900, and sTypoDescender −480: ascent-override: 92.77% (1900÷2048×100) and descent-override: 23.44% (480÷2048×100). The size-adjust property compensates for overall glyph size differences between the custom font and the fallback. You can read the underlying values from any font with the font analyzer tool.
CLS Prevention
Metric overrides are a key technique for eliminating Cumulative Layout Shift caused by font swapping. When a fallback font has different metrics than the custom web font, text reflows as the web font loads. By defining a specially-tuned fallback with matching metrics, text occupies the same space before and after the font loads, keeping the CLS score at zero. Tools like fontaine (npm) automate calculating these values from font files.
Kerning Tables: KERN and GPOS
Kerning adjusts the space between specific letter pairs to improve visual appearance. Without kerning, combinations like "AV", "To", and "Wa" have awkward gaps that disrupt reading flow. OpenType fonts can store kerning data in two different table formats, each with distinct capabilities and trade-offs.
Understanding kerning tables matters for font conversion because this data must survive the process. Losing kerning produces noticeably inferior typography, especially in headlines and logos where letter spacing is prominent.
The KERN Table (Legacy Format)
The KERN table is the original TrueType kerning format, predating OpenType. It stores explicit pair-by-pair adjustments: glyph A + glyph B = adjustment value.
KERN Table Format 0 (Most Common)
├── Version: 0x0000
├── Number of subtables: 1+
└── Subtable
├── Version: 0x0000
├── Length: size in bytes
├── Coverage: format + flags
└── Pairs Array
├── [0] Left: 'A' (glyph ID 36)
│ Right: 'V' (glyph ID 57)
│ Value: -80 (units)
├── [1] Left: 'T' (glyph ID 55)
│ Right: 'o' (glyph ID 82)
│ Value: -60 (units)
└── [n] ...more pairsKERN Advantages
- • Simple, straightforward format
- • Wide legacy software support
- • Easy to inspect and debug
- • Binary search for fast lookup
KERN Limitations
- • No class-based kerning
- • Large file size for extensive kerning
- • Limited to horizontal adjustments
- • No contextual awareness
The KERN table's internal organization uses a sorted array of glyph ID pairs for efficient lookup. When a text renderer encounters two consecutive glyphs, it computes a 32-bit key by placing the left glyph ID in the high 16 bits and the right glyph ID in the low 16 bits, then performs a binary search through the sorted pairs array. This O(log n) lookup makes even large KERN tables with thousands of pairs fast to query at render time. The sort order is critical. A corrupted pair array where records are not strictly sorted by the combined key will produce incorrect kerning for some pairs and miss others entirely, since binary search produces undefined behavior on unsorted data. Font validation tools check this sort order as part of KERN table verification.
The scalability problem with KERN becomes acute for multilingual fonts. A Latin typeface might kern 500 specific pairs for its base character set. Adding thorough Western European accented character coverage expands the glyph set to 400+ characters, and each base glyph's kerning relationships must be duplicated for every accented variant. The letter "A" and its 18+ accented forms (À Á Â Ã Ä Å Ā Ă Ą and others) share essentially the same left-side shape. Against similarly grouped right-side characters like [V W Y T], KERN requires up to 18 × 4 = 72 explicit pair records for A-variants against these four right-side characters alone. GPOS class kerning replaces all 72 with a single class definition requiring just one stored value per class pair.
The GPOS Table (Modern OpenType)
GPOS (Glyph Positioning) is the modern OpenType approach to kerning and positioning. It supports pair kerning like KERN but adds powerful features including class-based kerning, mark positioning, and contextual adjustments.
GPOS Lookup Types for Kerning
Type 2: Pair Adjustment
Direct pair kerning similar to KERN, but with more positioning options. Can adjust X placement, Y placement, X advance, and Y advance for both glyphs in a pair.
Format 1: Specific pairsFormat 2: Class pairsClass Kerning (Format 2)
Groups similar glyphs into classes. Instead of kerning A-V, A-W, A-Y, A-T separately, you create a class containing [V W Y T] and kern A against the entire class. Dramatically reduces file size for fonts with many accented characters.
GPOS Class Kerning Structure Class Definition 1 (Left side): ├── Class 1: [A À Á Â Ã Ä Å] → Similar left edges ├── Class 2: [T Ţ Ť Ŧ] → T-shaped left └── Class 3: [V W] → V-shaped glyphs Class Definition 2 (Right side): ├── Class 1: [V W Ý] → V-shaped right ├── Class 2: [o ò ó ô õ ö] → Round lowercase └── Class 3: [a à á â ã ä] → a-shaped right Kerning Values (Class1 × Class2): ├── Class 1 + Class 1 = -80 (A-V, À-W, Á-Ý, etc.) ├── Class 1 + Class 2 = -40 (A-o, À-ò, etc.) └── Class 2 + Class 2 = -60 (T-o, Ť-ò, etc.)
KERN vs GPOS Comparison
| Feature | KERN | GPOS |
|---|---|---|
| Pair kerning | Yes | Yes |
| Class kerning | Limited (Format 2) | Yes |
| Vertical adjustment | No | Yes |
| Mark positioning | No | Yes (Type 4-6) |
| Contextual kerning | No | Yes (Type 7-8) |
| File size efficiency | Medium | High (with classes) |
| Browser support | Universal | Universal |
Industry Trend
Most modern fonts, including all Google Fonts, use GPOS exclusively. The KERN table is retained primarily for compatibility with legacy software that does not read GPOS. New fonts should use GPOS; include KERN only if legacy support is required.
Kerning in Format Conversion
Both KERN and GPOS tables are preserved when converting between TTF, OTF, WOFF, and WOFF2. These tables are copied without modification, so the conversion does not affect kerning data itself.
Potential Issues
Subsetting removes pairs
If you subset and remove glyphs, kerning pairs involving those glyphs are removed too
Table stripping
Some aggressive optimization tools may remove KERN if GPOS exists, or vice versa
CSS must enable kerning
Kerning is on by default, but can be disabled; verify with font-kerning: normal
/* Modern approach (recommended) */
.kerned-text {
font-kerning: normal; /* Enable kerning */
}
/* Legacy approach */
.kerned-text {
font-feature-settings: 'kern' 1;
}
/* Disable kerning if needed */
.no-kern {
font-kerning: none;
/* or: font-feature-settings: 'kern' 0; */
}For practical CSS patterns covering kerning alongside other OpenType features, the CSS implementation guide provides copy-ready code and browser compatibility notes. Kerning and the vertical spacing values in the metrics tables interact to determine overall text rhythm, and both can be inspected together using the font analyzer tool, which reports KERN and GPOS data alongside OS/2 and hhea values from any uploaded font file. When kerning is applied, the browser's layout engine adjusts glyph advance widths during the text shaping phase. On modern platforms, HarfBuzz handles OpenType shaping by applying GSUB substitutions first, then GPOS positioning adjustments in lookup order. A GPOS pair adjustment of −80 units in a 1000-UPM font rendered at 16px produces a spacing reduction of −80 ÷ 1000 × 16 = −1.28 pixels. This fractional pixel offset is applied through subpixel positioning on modern displays. At body text sizes the tightening is subtle; at display sizes of 48px and above, the absence of kerning becomes clearly visible in headline text, particularly for classically problematic pairs like AV, To, Wa, and Ty where the letter shapes create natural-looking gaps without correction.
A subtle interaction occurs when a font contains both KERN and GPOS kerning tables. Most modern text engines apply GPOS first for its richer class-based support, and may additionally apply KERN as a fallback for software that does not support GPOS. In HarfBuzz, KERN table kerning is only applied when no GPOS kern feature is present, preventing double-application. However, some legacy applications that exclusively read the KERN table will apply only those values, potentially applying different (often less complete) kerning than the GPOS version. Foundries maintaining both tables should verify that the KERN values represent a correct subset of the GPOS kerning to avoid inconsistency across rendering environments.
Advanced Tables
OpenType Layout
GSUB- Glyph substitutionGPOS- Glyph positioningGDEF- Glyph definitionBASE- Baseline
Variable Fonts
fvar- Font variationsgvar- Glyph variationsavar- Axis variationsSTAT- Style attributes
Other
kern- Legacy kerningCOLR- Color glyphsCPAL- Color paletteSVG- SVG glyphs
The OpenType Layout tables (GSUB, GPOS, and GDEF) form an interdependent subsystem for advanced typography. GSUB handles glyph substitutions: replacing one glyph or sequence with another for ligatures, small caps, stylistic alternates, or script-specific required forms. GPOS handles glyph positioning adjustments: kerning between adjacent glyphs, mark-to-base positioning for diacritics above base characters, cursive attachment for connected scripts, and contextual adjustments. Both GSUB and GPOS reference GDEF for glyph class data. GDEF classifies each glyph as a base character, ligature component, combining mark, or multi-component piece, and GSUB and GPOS lookup rules use these classifications to target appropriate glyphs during contextual matching. A font with GSUB or GPOS but lacking GDEF may experience feature failures in text engines that rely on GDEF class information during lookup application.
Variable fonts extend the standard table structure with a second axis-variation layer. The fvar table defines named design axes (weight, width, optical size, or proprietary axes identified by four-letter tags), each with minimum, default, and maximum numeric values. The gvar table stores delta arrays for every glyph, describing how each outline coordinate shifts across the design space. HVAR, VVAR, and MVAR extend variation coverage to horizontal metrics, vertical metrics, and named table values respectively. This architecture allows a single font file to replace an entire static family while remaining more compact than the combined family, since glyph outlines are shared and only the deltas between design instances are stored. Understanding this layer is essential for debugging variable font issues, where unexpected rendering at specific axis positions often traces to glyph delta data or avar normalization.
Font validation ensures your fonts are structurally correct and will render properly across all platforms. This is especially important after conversion, as tools can introduce errors that cause fonts to fail in browsers or render incorrectly. For a quick browser-based check without installing any tools, our font analyzer inspects table structure, reports key metrics, and flags common issues in any uploaded font file.
OpenType Sanitizer (OTS)
OTS is Google's security-focused font validator. Chrome, Firefox, and other browsers use OTS to validate fonts before rendering. If OTS rejects your font, it won't display in browsers.
# Install OTS # macOS brew install ots # Linux sudo apt-get install opentype-sanitizer # Run validation ots-sanitize myfont.woff2 # Output examples: # Success: "File sanitized successfully!" # Failure: "ERROR: Bad checksum for head table" # Failure: "ERROR: Table is too short"
Critical Importance
OTS failures mean your web font will not work in browsers. Period. Always test with OTS before deploying web fonts. This is the minimum validation requirement.
What OTS Validates
OTS performs structural validation focused on security, ensuring fonts cannot be used as attack vectors in browsers. Its checks include:
Table boundaries
Verifies no table data extends beyond declared length, preventing buffer over-reads
Checksums
Validates per-table checksums stored in the table directory against calculated values
Required tables
Confirms all 8 mandatory OpenType tables are present and correctly structured
Version fields
Ensures table versions match known valid values (rejects unknown future versions)
OTS deliberately rejects fonts with any structural anomaly, even harmless ones. A font that passes OTS will load in Chrome, Firefox, and Edge. OTS does not check typographic quality. That is Font Bakery's job.
OTS was designed as a browser security sandbox because malformed fonts could trigger memory corruption vulnerabilities in OS-level font rasterizers. Windows GDI and macOS Core Text both accumulated CVEs related to font parsing in the late 2000s, allowing attackers to execute arbitrary code through carefully crafted web fonts. OTS prevents this by reconstructing a sanitized font from scratch rather than passing raw bytes to the OS parser. Every table is independently parsed, validated against the spec, and re-serialized into a clean output. This conservative approach means even structurally unusual but technically valid fonts may be rejected if they trigger OTS's strict parsing assumptions.
OTS failures are binary: pass or fail, with no warnings or partial outcomes. When a font fails OTS, the browser silently falls back to the next font in the CSS font stack, with no visible error message for end users. The problem only reveals itself to developers who check the browser console, notice unexpected fallback font rendering, or examine network requests showing a 200 response that produces no font. This silent failure mode is why pre-deployment OTS testing is non-negotiable. When a font passes OTS but still fails to display, the issue is often a deployment or CSS problem rather than font structure; the font not loading solutions guide covers the most common causes and fixes. The command-line ots-sanitize tool replicates the browser check locally and can be integrated into build pipelines as a required gate before any font deployment.
Font Bakery
Font Bakery is an open-source Python tool that runs 200+ quality checks. It's required for Google Fonts submissions and recommended for all professional font work.
# Install Font Bakery pip install fontbakery # Run universal checks (all fonts) fontbakery check-universal myfont.ttf # Run Google Fonts profile (stricter) fontbakery check-googlefonts myfont.ttf # Run specific checks fontbakery check-outline myfont.ttf fontbakery check-opentype myfont.ttf # Output to HTML report fontbakery check-universal myfont.ttf --html report.html
Check Categories
OpenType Spec
Table structure, required tables, checksums
Metrics
Consistent values across OS/2, hhea, head
Outlines
Curve direction, overlaps, extreme points
Naming
Name table consistency, copyright format
Reading Font Bakery Output
Font Bakery reports results at four severity levels: FAIL (must fix), WARN (should fix), INFO (informational), and PASS. A clean Google Fonts submission requires zero FAILs and minimal WARNs. Understanding the output format helps prioritize fixes:
$ fontbakery check-googlefonts MyFont-Regular.ttf >> com.google.fonts/check/name/family_name_compliance [FAIL] Name ID 1 should not contain "Regular" substring. MyFont Regular → should be: MyFont >> com.google.fonts/check/metrics_winascent_and_windesced [WARN] OS/2 usWinAscent value 1200 is too large. Recommended: match your tallest glyph height (900) >> com.google.fonts/check/glyf_non_symmetric_glyphs [PASS] All glyphs have symmetric y-coordinates. >> com.google.fonts/check/ligature_carets [INFO] Font has ligatures but no caret positions defined. This is optional but recommended for cursor positioning. Results: 1 FAIL, 1 WARN, 1 INFO, 1 PASS
FAILs like the naming example are typically quick fixes in a font editor or via fontTools. WARNs about metrics require more care, as changing usWinAscent affects line spacing in Windows applications and browsers. Always test changes across platforms before resubmitting.
CI/CD Integration
Font Bakery integrates with GitHub Actions for automated validation. Add it as a pre-release check: fontbakery check-googlefonts fonts/*.ttf --ghmarkdown report.md. The --ghmarkdown flag generates a GitHub-formatted report that appears directly in your PR. For non-Google fonts, use check-universal instead.
Font Bakery organizes its checks into profiles. The universal profile covers checks applicable to any font regardless of distribution channel: OpenType specification compliance, name table correctness, outline quality, and metric consistency. License-related checks are a separate concern; understanding the difference between commercial and personal use font licenses is important before deploying fonts in any production environment. Many Font Bakery errors relate to specific table structures, so pairing validation output with knowledge from the font file anatomy guidehelps diagnose root causes faster. The Google Fonts profile adds requirements specific to the library, including copyright string formatting, required name IDs, minimum Unicode character coverage across the Basic Latin and Latin-1 Supplement blocks, and vertical metrics standards that ensure consistent line spacing in the Google Fonts serving infrastructure. Foundries distributing through other channels typically run only the universal profile to avoid false positives from Google Fonts's opinionated conventions that do not apply universally.
Font Bakery's extensible architecture supports custom check plugins written as Python modules, allowing foundries to encode house-style rules and distribution requirements as executable validation logic alongside the standard checks. Check IDs follow a hierarchical naming scheme: com.google.fonts/check/metrics_winascent_and_windescedidentifies namespace, category, and subject. Results are exported as structured JSON, enabling integration with monitoring dashboards and trend tracking across font releases. A mature font development pipeline will accumulate a custom check profile over time that catches the specific classes of errors that historically caused problems for that foundry's fonts on their target platforms.
Common Validation Errors
| Error | Cause | Fix |
|---|---|---|
| Bad table checksum | Font was modified after generation without recalculating checksums | font['head'].checkSumAdjustment = 0 then regenerate via fontTools |
| Wrong contour direction | Outer contours counter-clockwise (should be clockwise for TrueType) | FontForge: Element → Correct Direction. fontTools: reverseContour() |
| Missing required table | Conversion tool stripped tables; corrupt source file | Use ttx to dump XML, identify missing table, rebuild or use better converter |
| Glyph bounds violation | Glyph outline extends beyond declared bounding box in head table | fonttools.ttLib.recalc_bounds() or FontForge: Element → Auto Bounds |
| Duplicate glyph names | Two glyphs share the same name in the post table, confusing renderers | Rename duplicates; use post table version 2.0 with glyph name array |
| Invalid cmap subtable | Missing Platform 3, Encoding 1 subtable (Windows Unicode BMP) | Add Format 4 subtable for platform 3, encoding 1 using fontTools |
| Overlapping contours | Glyph contains self-intersecting or overlapping contours | FontForge: Element → Remove Overlap. Required for hinting tools to work correctly |
Convert with Table Preservation
Our converter maintains all font tables during format conversion.
Try Font ConverterWritten by
Sarah Mitchell
Typography expert specializing in font design, web typography, and accessibility
Verified by
Marcus Rodriguez
Full-stack developer specializing in web font implementation and performance optimization
Font File Anatomy FAQs
Common questions about font tables, metrics, kerning and outlines
