How to inspect every glyph in any font online
- Step 1Drop your font onto the tool — Use the upload zone above — it accepts `.ttf`, `.otf`, `.woff`, `.woff2`. The file is sniffed by its first 4 magic bytes, not its extension, so a mislabelled file is still routed correctly. WOFF2 is Brotli-decompressed and WOFF is zlib-decompressed to an sfnt before parsing.
- Step 2Press Process — There are no options to set — the inspector has a single action. It parses the sfnt with opentype.js, reads `font.glyphs.length`, and iterates from index 0 to `min(total, 5000)`.
- Step 3Read the header block — The JSON starts with `total_glyphs`, `sampled`, `units_per_em`, `ascender`, `descender`, and a `coordinate_system` note. If `sampled` is less than `total_glyphs`, the font has more than 5,000 glyphs and only the first 5,000 records were serialised.
- Step 4Scan the glyphs array — Each entry is a `GlyphRecord`. Look at `index` (font-internal, 0 = .notdef), `name` (or `null`), `unicode` (or `null`), `advance` (font units), and the bbox. The `svgPath` is the outline; outline-less glyphs like space return `svgPath: ""`.
- Step 5Render a glyph if you need a preview — Wrap any record's `svgPath` in `<svg viewBox="..."><path d="..."/></svg>` using that record's `viewBox`. Because the path is scaled to the em box, you get an upright glyph at a stable size without computing transforms yourself.
- Step 6Copy or download the JSON — Use Copy to clipboard for a quick paste, or Download to save `<stem>.glyphs.json`. The metrics chips above the output report Total glyphs, Sampled, and UPM so you can sanity-check the run at a glance.
Every field in a glyph record
The exact shape emitted per glyph, taken from the GlyphRecord type the processor builds. Every value is either the stated type or null — fields are never omitted.
| Field | Type | What it is |
|---|---|---|
index | number | Font-internal glyph index, 0..N-1. Index 0 is always .notdef (the tofu rectangle). Indices are not portable between fonts. |
name | string | null | Glyph name from the post table (TrueType) or CFF charset (OTF). null when the font ships no names — many web fonts strip post to save bytes. |
unicode | string | null | The glyph's primary codepoint as U+XXXX (e.g. U+0041). null for unencoded glyphs reached only via OpenType features. |
advance | number | null | Advance width in font units from hmtx. null if opentype reports none. Drives layout spacing, not the visible ink width. |
xMin / xMax | number | null | Horizontal outline extent in font units, from getBoundingBox() (falls back to the glyph table's recorded extents). |
yMin / yMax | number | null | Vertical outline extent in font units. Negative yMin means the glyph dips below the baseline (descenders, commas). |
svgPath | string | SVG path d string in y-down screen coords, baseline at y = units_per_em. Empty string "" for glyphs with no contours (space, control chars). |
viewBox | string | 0 0 <width> <units_per_em> where width is the advance (or the em when advance is 0/absent). Pair with svgPath for an upright <svg>. |
Top-level header in the JSON output
Fields that appear once, before the glyphs array. These let you interpret every per-glyph metric and detect truncation.
| Field | Example | Meaning |
|---|---|---|
total_glyphs | 2,847 | Every glyph in the font (font.glyphs.length), including unencoded ones and .notdef. |
sampled | 2,847 | How many records were serialised: min(total_glyphs, 5000). If lower than total_glyphs, the safety cap truncated the list. |
units_per_em | 1000 or 2048 | The em square. Use this as both the SVG fontSize and the viewBox height when rendering paths. 1000 is typical for CFF/OTF, 2048 for many TrueType fonts. |
ascender / descender | 800 / -200 | Font-unit vertical metrics from opentype (may be null). Useful for laying glyphs out against a baseline grid. |
coordinate_system | y-down; fontSize=units_per_em; baseline at y=units_per_em | A literal reminder that paths are y-down, rendered at fontSize = units_per_em, baseline at y = units_per_em. |
Accepted inputs and how each is decoded
Format is detected from the first 4 magic bytes, not the filename. Anything else is rejected before opentype parsing.
| Input | Magic | Handling |
|---|---|---|
| TTF | 00 01 00 00 / true | Passed straight to opentype.js as-is (sfnt with TrueType outlines). |
| OTF | OTTO | Passed straight through (sfnt with CFF/PostScript outlines). Glyph names come from the CFF charset. |
| WOFF | wOFF | zlib-decompressed table-by-table back to a flat sfnt, then parsed. |
| WOFF2 | wOF2 | Brotli-decompressed (wawoff2) to an sfnt, then parsed. |
| TTC (collection) | ttcf | Detected but rejected — throws Unsupported font format: ttc. Split the collection to a single face first. |
| Anything else | unknown magic | Rejected with Unsupported font format (or the upload guard's not-a-supported-font message). |
Cookbook
Real records from real fonts, abbreviated. UPM here is 1000 unless noted; your numbers scale with the font's units_per_em.
A normal encoded letter
ExampleCapital A in a 1000-UPM font. It has a name (post table present), a primary Unicode, a positive advance, and a full outline path. The bbox sits at and above the baseline.
Glyph record for "A":
{
"index": 36,
"name": "A",
"unicode": "U+0041",
"advance": 684,
"xMin": 14, "xMax": 670,
"yMin": 0, "yMax": 700,
"svgPath": "M14 700 L342 0 ...Z",
"viewBox": "0 0 684 1000"
}The .notdef glyph at index 0
ExampleEvery font must ship a glyph at index 0 — the tofu box rendered when a codepoint has no glyph. It has no Unicode mapping of its own but usually has an outline (the box).
First record in every output:
{
"index": 0,
"name": ".notdef",
"unicode": null,
"advance": 500,
"xMin": 50, "xMax": 450,
"yMin": 0, "yMax": 700,
"svgPath": "M50 700 H450 V0 H50 Z ...",
"viewBox": "0 0 500 1000"
}The space glyph — advance but no ink
ExampleSpace carries an advance width (it pushes the cursor) but has no contours, so svgPath is the empty string. This is by design, not a bug — don't filter it out of layout calculations.
Glyph record for U+0020:
{
"index": 3,
"name": "space",
"unicode": "U+0020",
"advance": 250,
"xMin": 0, "xMax": 0,
"yMin": 0, "yMax": 0,
"svgPath": "",
"viewBox": "0 0 250 1000"
}An unencoded ligature glyph
ExampleThe "fi" ligature exists in the font and has an outline, but no codepoint maps to it directly — it is substituted by the GSUB liga feature. So unicode is null. You only reach it through shaping, never by typing a character.
Glyph record for the fi ligature:
{
"index": 612,
"name": "f_i",
"unicode": null,
"advance": 556,
"xMin": 30, "xMax": 520,
"yMin": 0, "yMax": 720,
"svgPath": "M30 720 ...Z",
"viewBox": "0 0 556 1000"
}Rendering a record back to an SVG
ExampleBecause the path is pre-scaled to the em box and y-down, you wrap it with the record's own viewBox and it renders upright. No transform maths required.
Take any record and do: <svg viewBox="0 0 684 1000" width="48" height="70"> <path d="M14 700 L342 0 ...Z" /> </svg> The glyph appears upright, baseline near the bottom (at y=1000), filling the em box.
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 has more than 5,000 glyphs (CJK, large icon sets)
Truncated at 5,000The processor caps the serialised list at GLYPH_INSPECT_SAFETY_CAP = 5000 to keep the JSON payload and render workload bounded. This is NOT a tier limit — it applies on every tier. The cap is reported honestly: total_glyphs is the real count, sampled is 5,000. A full CJK font (often 20,000+ glyphs) shows only its first 5,000 records by index. To enumerate beyond that, run the same opentype walk yourself in a Node script.
Font ships no glyph names (post table stripped)
By designGoogle Fonts and many optimised web fonts drop the post glyph-name table to save 5–20 KB. opentype then can't assign names, so name is null for those glyphs. The record still has its index and unicode, which are all you need to identify the glyph. Pair with the font-metadata-extractor to confirm whether post is present in tables_present.
A glyph maps to more than one Unicode codepoint
Primary onlySome glyphs are reached by several codepoints (e.g. a single quote glyph used for both U+0027 and U+2019). The record reports only the glyph's primary unicode — it does not list every codepoint that maps to it. To see the full codepoint→glyph map, use the character-coverage-map, which scores coverage against 346 real Unicode blocks.
Outline-less glyphs (space, NBSP, control chars)
Empty path (expected)Glyphs with no contours yield svgPath: "" — never null, never a crash. They still carry an advance and a viewBox, because they participate in layout. If you're building a thumbnail grid, render these as blank cells rather than skipping them, so your indices stay aligned with the font.
TrueType Collection (.ttc) uploaded
Unsupported formatThe detector recognises the ttcf magic, but the parser only handles single-face TTF/OTF/WOFF/WOFF2 and throws Unsupported font format: ttc. Collections pack multiple faces in one file; pick a single face out of the collection (with fonttools or a font app) before inspecting.
Variable font dropped in
Default masteropentype reads the glyf/CFF outlines at the font's default-axis position, so the inspector shows the default master's geometry. It does not interpolate gvar deltas, so you won't see a Bold or Condensed instance unless that's the default. To bake a specific instance first, run the variable-font-freezer and inspect the result.
Free tier file-size cap hit
413-style blockInputs are checked against the tier's per-job size limit before parsing: 5 MB on free, 50 MB on pro, 1 GB on developer. An oversized file is rejected with exceeds the <tier> tier per-job limit and never parsed. Most single-style TTF/OTF fonts are well under 5 MB; large CJK fonts or multi-master TTFs can exceed it.
Corrupt or non-font file
Parse errorIf the first 4 bytes don't match a known font magic, you get an Unsupported font format error before opentype runs. If the magic is valid but the table directory is damaged, opentype's parse throws and the tool surfaces the message in the error panel. Nothing partial is written.
Bounding box vs glyph-table extents disagree
Outline winsThe record prefers the precise outline bbox from getBoundingBox(); only if that returns non-finite or inverted values does it fall back to the glyph table's recorded xMin/xMax/yMin/yMax. For most fonts these agree, but auto-generated or subset fonts sometimes have stale table extents — trust the reported bbox, which is derived from the actual contours.
Frequently asked questions
Does the inspector show a gallery of glyph thumbnails?
No. The tool emits structured JSON, one record per glyph, and the result panel shows that JSON text (truncated to a preview, with Copy and Download). Each record includes a real svgPath plus viewBox, so you can render any glyph to an upright <svg> yourself — but there is no built-in canvas gallery, no PNG thumbnails, and no search box. If you want a rendered visual sheet, feed the paths into your own <svg> grid.
How many glyphs can it handle?
Every glyph up to a hard safety cap of 5,000 records, applied on all tiers (free, pro, developer alike). The cap exists to keep the JSON payload bounded for pathologically large fonts; it is not a paywall. The output always reports the true total_glyphs alongside the sampled count, so you know exactly when truncation happened. Fonts above 5,000 glyphs (large CJK, big icon sets) show only their first 5,000 by index.
What limits the file size, then?
File size is tier-gated: 5 MB on free, 50 MB on pro, 1 GB on developer. The check runs before parsing, so an oversized file is rejected cleanly. Most Latin/Greek/Cyrillic fonts are a few hundred KB; you'll only brush the free limit with large CJK fonts or uncompressed multi-master TrueType.
Why do some glyphs have no name?
Glyph names live in the post table (TrueType) or the CFF charset (OTF). Optimised web fonts — including most of Google Fonts — strip post to save 5–20 KB, so opentype can't assign names and the record's name is null. The index and unicode still identify the glyph. Use the metadata extractor to confirm whether post is present in the font's table list.
Why is `unicode` null on some glyphs?
Those are unencoded glyphs — they have an index and an outline but no codepoint in the cmap maps to them directly. Ligatures (f_i), small caps (a.smcp), stylistic alternates, and .notdef are typical examples. They're reached through OpenType layout features (GSUB), not by typing a character, so there's nothing meaningful to put in unicode.
What coordinate system is the SVG path in?
y-down screen space, rendered at fontSize = units_per_em with the baseline placed at y = units_per_em. That means an upright glyph occupies roughly 0..units_per_em vertically and pairs directly with the record's viewBox (0 0 <width> <units_per_em>). You don't flip the Y axis or apply a scale — it's render-ready.
Why does the space glyph have an empty path?
Space (and NBSP, tab, control characters) have an advance width but no contours, so there's nothing to draw. The record reports svgPath: "" deliberately — never null — while keeping advance and viewBox so the glyph still contributes to layout. Render these as blank cells if you're building a grid.
Can I get the advance width and side bearings?
You get advance (from hmtx) directly. The left side bearing isn't a separate field, but you can derive it as xMin for glyphs whose outline starts after the origin; the right side bearing is advance - xMax. The record gives you advance, xMin, and xMax, which is everything needed to compute both bearings.
Does it work on OTF (CFF) fonts as well as TrueType?
Yes. OTF files (magic OTTO, PostScript/CFF outlines) parse fine, and glyph names come from the CFF charset rather than a post table. The svgPath is produced the same way via opentype's getPath, so CFF and TrueType outlines both render correctly.
What's the .notdef glyph at index 0?
It's the mandatory fallback glyph every OpenType font must ship at index 0 — the box ("tofu") the engine draws when a codepoint has no real glyph. It has unicode: null because no codepoint maps to it, usually has a box outline, and is always the first record in the output.
Will my font be uploaded anywhere?
No. Parsing happens in your browser via opentype.js (and wawoff2/pako for WOFF2/WOFF decode). The result panel even shows a 0 bytes uploaded badge. This matters for unreleased brand fonts and licensed type — the binary never touches a server.
How do I see coverage across whole scripts instead of one glyph at a time?
Use the character-coverage-map, which scores the font against 346 real Unicode blocks and shows where coverage is complete, partial, or absent. The inspector is for per-glyph detail; the coverage map is for the bird's-eye view. The glyph-count-analyzer gives you the totals.
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.