How to audit the kerning pair table of any font
- Step 1Drop your font onto the tool — Accepts `.ttf`, `.otf`, `.woff`, `.woff2`. WOFF2 is decompressed in-browser (wawoff2/WASM) and WOFF is inflated to SFNT before parsing — you get the same result regardless of wrapper. The Kerning Pair Auditor is a **Pro-tier** tool; the free font file ceiling is 5 MB, Pro raises it to 50 MB.
- Step 2Set the Top-N slider — The single option is `pairLimit` — a range slider from 10 to 500 in steps of 10, default 50. Lower it (10–30) to eyeball just the loudest adjustments; raise it to 500 for a full export. The slider only changes how many rows you see; it never changes which table is read.
- Step 3Run and read the metrics strip — Above the JSON you get three numbers: **Total pairs** (every non-zero pair in the kern table), **Showing** (min of Total and your Top-N), and **Largest |Δ|** (the single biggest absolute adjustment). If Total pairs is `0`, the font has no `kern` table — its kerning is in GPOS, which this tool doesn't enumerate.
- Step 4Inspect the JSON pairs array — Each entry is `{ left, right, value, sample }`. `left`/`right` are **glyph indices** (not codepoints). `value` is signed FUnits — negative pulls glyphs together (the common case). `sample` is the two visible characters, e.g. `"AV"`, or `[glyphname]` for a glyph with no Unicode mapping (small caps, ligature components).
- Step 5Convert FUnits to em fractions using upm — Divide each `value` by the `upm` field to get the adjustment as a fraction of the em. A `-90` value at `upm: 1000` is -0.09em; the same -90 at `upm: 2048` is only -0.044em. Always normalise by `upm` before comparing kerning strength across two fonts.
- Step 6Download or copy the JSON — The result panel offers Download (`<stem>.kerning.json`) and Copy to clipboard. The on-page preview truncates at 4,000 characters with a `… (truncated — download for full output)` marker, so for a 500-pair dump, download rather than copy from the preview.
The one option and the JSON output shape
The Kerning Pair Auditor exposes exactly one control. Everything else about the output is fixed by the handler. Field names below are the literal keys in the JSON.
| Field / control | Type | What it is |
|---|---|---|
pairLimit (slider) | range 10–500, step 10, default 50 | Top-N cap. Output is sorted by |value| descending, then sliced to this many rows. The only UI control on the page. |
total_kerning_pairs | number | Count of every non-zero pair found in the kern table. Zero-value pairs are dropped before this count. |
showing_top_n | number | min(total_kerning_pairs, pairLimit) — how many rows the pairs array actually contains. |
upm | number | Units per em (font.unitsPerEm). Divide any value by this to get an em fraction. Usually 1000 (CFF/OTF) or 2048 (TrueType). |
pairs[].left / pairs[].right | number | Glyph indices, not Unicode codepoints. To map back to a character you need the font's cmap (the sample field does this for you). |
pairs[].value | signed number | Kerning adjustment in FUnits. Negative = pull together (normal). Positive = push apart (rare; some slab serifs / monospaced). |
pairs[].sample | string | Two visible characters from each glyph's unicode, or [glyphname] if a glyph has no codepoint, or ? if glyph lookup throws. |
Which kerning sources this tool actually reads
opentype.js 1.3.5 populates font.kerningPairs from the kern table only. The auditor reads that map. This table is the single most important accuracy fact about the tool.
| Kerning source | Enumerated here? | Why |
|---|---|---|
kern table, format 0 (Windows) | Yes | Simple (left, right, value) pair list. Parsed directly into font.kerningPairs. This is the only fully-supported source. |
kern table, format 1 (Mac), first subtable | Yes (first subtable only) | opentype.js warns Only the first kern subtable is supported and reads version-0 coverage. Later subtables are ignored. |
kern table, format 2 (class-based, inside kern) | No | opentype.js's kern parser handles the simple pair list only; the format-2 class array isn't expanded. |
| GPOS pair-pos (LookupType 2) | No | GPOS is parsed into font.tables.gpos but the auditor reads font.kerningPairs, which is never fed from GPOS. Use opentype-features-inspector to confirm a kern feature exists in GPOS. |
| GPOS class-based kerning (LookupType 2, format 2) | No | Even the GPOS reader in opentype.js doesn't expand class-based pairs to individual pairs; the auditor wouldn't see them regardless. |
Pairs with value === 0 | Dropped | Filtered out before counting — a zero adjustment is not real kerning. |
Cookbook
Real audit runs against the kinds of fonts you actually drop on this tool. Output is the literal JSON shape the handler emits. If you find the audit shows zero pairs, the cause is almost always GPOS-only kerning — read the kern vs GPOS explainer.
A classic kern-table font: top adjustments first
ExampleAn older TrueType face (upm 2048) with a real kern table. The auditor returns pairs sorted by absolute value, so the loudest cap pairs lead. Note left/right are glyph indices; sample does the cmap lookup for you.
Slider: pairLimit = 10
Output (abridged):
{
"total_kerning_pairs": 842,
"showing_top_n": 10,
"upm": 2048,
"pairs": [
{ "left": 36, "right": 57, "value": -205, "sample": "AV" },
{ "left": 57, "right": 36, "value": -205, "sample": "VA" },
{ "left": 55, "right": 68, "value": -184, "sample": "To" },
{ "left": 58, "right": 68, "value": -164, "sample": "Wo" }
]
}
Metrics strip: Total pairs 842 | Showing 10 | Largest |Δ| -205GPOS-only font audits as zero pairs
ExampleMost fonts shipped after ~2012 (and nearly all variable fonts) put kerning exclusively in GPOS and drop the legacy kern table. The auditor reads only the kern table, so the result is empty even though the font kerns perfectly in Chrome.
Output:
{
"total_kerning_pairs": 0,
"showing_top_n": 0,
"upm": 1000,
"pairs": []
}
Metrics strip: Total pairs 0 | Showing 0 | Largest |Δ| 0
This is NOT a broken font. Confirm the kern feature lives in GPOS
with the OpenType Features Inspector — if it lists a 'kern'
feature, the font kerns; it just isn't in the legacy table.Converting raw FUnits to an em fraction
ExampleTwo fonts with the same -90 value kern differently because their em sizes differ. Always divide by upm before comparing strength across fonts.
Font A (CFF): value -90, upm 1000 → -90 / 1000 = -0.090 em Font B (TTF): value -90, upm 2048 → -90 / 2048 = -0.044 em Font A's 'AV' is roughly twice as tight as Font B's, even though the raw FUnit value is identical.
A glyph with no codepoint shows its name
ExampleSmall-cap glyphs, ligature components, and alternates often have no Unicode mapping. The sample field falls back to the opentype.js glyph name in square brackets instead of a character.
{
"left": 412, "right": 57, "value": -72,
"sample": "[A.sc]V"
}
The left glyph (index 412) is a small-cap A with no cmap entry,
so it renders as [A.sc] rather than a letter. The kern still
counts; only the human-readable sample changes form.Comparing two competing typefaces at a glance
ExampleRun both fonts at pairLimit 500 and compare total_kerning_pairs and the top adjustments. A font with a few hundred kern pairs vs one with a couple thousand tells you which foundry invested in spacing — but only counts what's in the kern table.
Typeface A: total_kerning_pairs 1,910 largest |Δ| -240 Typeface B: total_kerning_pairs 118 largest |Δ| -60 A is far more thoroughly kerned IN THE kern TABLE. Caveat: if B's real kerning is in GPOS, this comparison is unfair — verify both fonts store kerning the same way first.
Edge cases and what actually happens
Every row below was probed against the live API. Some documented requirements (alphabetical axis order, numerical tuple order) are not actually enforced in practice — useful to know if you've been blaming the wrong thing for a 400.
Font kerns in the browser but audits as zero pairs
By designThe auditor reads font.kerningPairs, which opentype.js fills from the kern table only. A font that stores kerning exclusively in GPOS (the modern norm) returns total_kerning_pairs: 0 here while kerning correctly in every browser. This is not a tool bug and not a font defect — the two systems just look in different tables. Use opentype-features-inspector to confirm a kern feature exists in GPOS.
Class-based kern (format 2) in the legacy table
Not enumeratedSome older fonts pack kerning as a format-2 class array inside the kern table. opentype.js's kern parser expands only the simple pair list (format 0 / Mac version-0); the class array is skipped. The audit will show fewer pairs than the font defines, or zero. There's no option to force class expansion — class-aware extraction needs desktop fontTools.
Multiple kern subtables (Mac format 1)
First subtable onlyWhen a Mac-style kern table contains more than one subtable, opentype.js emits the console warning Only the first kern subtable is supported and reads just the first. Pairs defined in later subtables are invisible to the audit. Rare in modern fonts, common in 1990s Mac TrueType.
Pairs with a value of exactly 0
DroppedA kern table can list pairs with value 0 (placeholders or stripped values). The handler filters value === 0 before counting, so total_kerning_pairs reflects real adjustments only. If you need the raw table-entry count including zeros, this tool won't give it — it reports effective kerning, not table size.
Unsupported kern table version
Erroropentype.js only understands kern table version 0 (Windows) and version 1 (Mac). Any other version throws Unsupported kern table version. In practice you'll never see this from a real-world font, but a corrupted or hand-edited table can trigger it — the tool surfaces the error rather than guessing.
left/right are glyph indices, not characters
ExpectedThe left and right fields are glyph indices into the font, not Unicode codepoints. The same letter can have different indices in different fonts, so you cannot compare indices across files. Compare by the sample string instead. The sample is derived per-glyph from glyph.unicode; glyphs without a codepoint show [glyphname].
Positive kerning values
SupportedNegative values (pull together) dominate, but positive values are legal and the auditor surfaces them — they push glyphs apart. They appear mostly in slab serifs and a few monospaced faces. Because sorting is by absolute value, a +40 pair can outrank a -30 pair in the list.
File over the tier ceiling
RejectedThe Kerning Pair Auditor is a Pro-tier tool. The free font file limit is 5 MB and Pro is 50 MB; oversized files are rejected before parsing. Fonts large enough to hit 50 MB are almost always full CJK families — and those kern via GPOS class lookups, which this tool doesn't enumerate anyway.
Variable font dropped in
Parses, usually zero pairsVariable fonts parse fine (the auditor reads the default instance's tables), but they near-universally store kerning in GPOS, so the audit typically returns zero kern-table pairs. To inspect a variable font's actual kerning you'd freeze it first with variable-font-freezer — though freezing preserves GPOS, not a legacy kern table, so the audit may still be empty.
Frequently asked questions
Does this tool read GPOS kerning?
No. Despite what older tool copy may say, the handler reads font.kerningPairs, which opentype.js 1.3.5 populates from the legacy kern table only. GPOS is parsed into font.tables.gpos but is never expanded into the pair list. A GPOS-only font (the modern default) audits as zero pairs here. To confirm whether a font kerns via GPOS, use opentype-features-inspector and look for a kern feature.
Why does my font show zero kerning pairs?
Almost always because its kerning lives in GPOS, not the legacy kern table — true for most fonts made after roughly 2012 and nearly all variable fonts. Zero pairs here does not mean the font doesn't kern; it means there's no kern table for this tool to read. Other causes: class-based kerning in a format-2 kern table (not expanded), or all pairs being value 0 (filtered out).
What does the Top-N slider actually do?
It sets pairLimit — how many rows the output pairs array contains. The handler first sorts every non-zero pair by absolute value (loudest first), then slices to your N (10–500, default 50). It never changes which table is read or how total_kerning_pairs is computed; total_kerning_pairs always reflects the full set, while showing_top_n reflects the slice.
Are the left/right numbers character codes?
No — they're glyph indices into the font, not Unicode codepoints. The same letter has different indices in different fonts, so don't compare indices across files. Use the sample field for the human-readable pair; it maps each glyph back to its character via the font's cmap, falling back to [glyphname] for glyphs with no codepoint.
What's a typical kerning value range?
Most pairs sit between roughly -120 and +20 FUnits. The classic loud pairs — AV, VA, To, Wa — land around -50 to -120. Punctuation and lowercase pairs are gentler (-10 to -40). Positive values are rare. Always normalise by the upm field before comparing: -90 means -0.09em at upm 1000 but only -0.044em at upm 2048.
Why is a pair with a positive value ranked above a negative one?
Sorting is by absolute value, so a +40 pair outranks a -30 pair. The intent is to surface the largest visual adjustments regardless of direction. Negative dominates in practice (pulling glyphs together), but slab serifs and some monospaced faces use positive kerning to push specific pairs apart.
What input formats are accepted?
TTF, OTF, WOFF, and WOFF2. WOFF2 is decompressed in-browser via a WASM build of wawoff2; WOFF is inflated to SFNT first. All four end up parsed by opentype.js the same way, so the kern-table reading is identical regardless of the wrapper format.
Is the font uploaded anywhere?
No. Parsing and kern-table extraction run entirely in your browser via opentype.js (and a WASM decompressor for WOFF2). The font bytes never reach a server. The only server-side write for signed-in users is a usage counter that records that a tool ran — no font content.
How big a font can I audit?
The Kerning Pair Auditor is a Pro-tier tool. The free font file ceiling is 5 MB; Pro raises it to 50 MB (developer tier far higher). Kern-table parsing is cheap, so the practical limit is the file-size tier, not processing time. Note that fonts large enough to approach 50 MB are usually CJK families that kern via GPOS — which this tool doesn't enumerate.
Can I add or edit kerning pairs with this tool?
No — it's read-only. opentype.js can parse a kern table but this tool never writes one, and editing GPOS safely in the browser isn't feasible. To author kerning, use a desktop editor like Glyphs or FontForge. This auditor is for QA and comparison, not authoring.
How do I download the full list?
The result panel has a Download button that saves <stem>.kerning.json, plus a Copy to clipboard button. The on-page preview is truncated at 4,000 characters with a … (truncated) marker, so for a 500-pair export, use Download rather than copying from the preview — Download contains the complete array.
Which sibling tool should I use to see the kerning a GPOS-only font actually applies?
There isn't a browser tool that expands GPOS class kerning into a pair list — that needs desktop fontTools (ttx on the GPOS table). For browser QA, opentype-features-inspector confirms the kern feature exists, glyph-inspector shows individual glyph outlines and metrics, and font-metrics-analyzer covers vertical metrics. Combine them for a full spacing review.
Privacy first
Every JAD Font tool runs entirely in your browser using opentype.js and the wawoff2 WASM Brotli encoder. Your fonts never leave your device — verified by zero outbound network requests during processing.