A font inspector that runs entirely in your browser. Drop in an OTF, TTF, WOFF, or WOFF2 file and see its metadata, glyph coverage, language support, and OpenType layout features — or load two fonts side by side to compare them. Everything runs client-side in WebAssembly (fontkit + harfbuzzjs); your font files never leave your device.
Live: type.soralabs.io.vn · Extension: on-page font picker, on the Microsoft Edge Add-ons store — see apps/extension
- Inspector — full metadata and a glyph grid groupable by Unicode category; language-support checking that verifies real HarfBuzz shaping/mark-positioning, not just character coverage; a variable-font tester with live axis sliders, named-instance presets, and one-click
font-variation-settingsCSS; OpenType layout-feature toggling on real text plus COLR/CPAL color palette inspection; a raw-table browser that decodes every OpenType table field by field; and CSS/subsetting tools plus full PDF report export. - Compare — two fonts side by side: matched-size text and waterfall reading, independent variable-axis sliders per font, a full glyph/OpenType-feature diff, a glyph-outline overlay with baseline/x-height/cap-height guides, and pairing diagnostics that explain x-height/weight/width/slant/serif-style differences in plain language.
- Browser extension — identify the actually-rendered font on any web page via an on-page picker (canvas pixel-diffing, not just reading the CSS stack), scan a whole page's fonts across frames, and jump straight into the full inspector. See
apps/extension/README.md.
- Saved/shareable comparisons — save a Compare session and share it via a short link, so a comparison (e.g. "Font A vs Font B at 14px") can be sent to someone else without both sides re-uploading the same font files. This needs a backend (storage for the font files + session metadata, and a short-link resolver) since the app is currently 100% client-side.
| Purpose | Library / tool |
|---|---|
| Framework | Next.js, React |
| UI primitives | Astryx (shell/data UI), Sora UI (animation-only primitives) |
| Styling | Tailwind CSS |
| Font parsing | fontkit |
| Text shaping | harfbuzzjs — official WASM build of HarfBuzz |
| Language support data | Hyperglot (CLDR-based) |
| Browser extension | WXT |
| Lint / format | Ultracite (Biome) |
| Git hooks | Lefthook |
Language support detection ports Hyperglot's checking approach (the same database FontDrop's own "Language Report" cites) to JS, using harfbuzzjs instead of Python's uharfbuzz — both bind the same HarfBuzz C library, so shaping results are equivalent, not reimplemented.
Hyperglot is Apache License 2.0. packages/font-engine/src/data/languages.json is a generated derivative of its per-language YAML database (see packages/font-engine/scripts/build-language-db.ts), not hand-authored — the checking logic below is a from-scratch reimplementation, but the data is theirs, compiled to JSON.
Three tiers, checked in order (see packages/font-engine/src/font-language-detection.ts):
- Coverage — every character's codepoint(s) present in the font (
fontkit'scharacterSet). - Decomposed fallback — a missing precomposed character (e.g.
ế) can still count if the font has the base letter + combining marks separately (String.prototype.normalize('NFD')). - Shaping — for anything relying on tier 2, or already an unencoded base+mark sequence in the source data, confirm HarfBuzz's GPOS actually positions the marks (
packages/font-engine/src/font-shaping.ts), not just that the glyphs exist.
Not ported: Hyperglot's Brahmi conjunct/half-form checks and Arabic joining-form checks (script-specific tests beyond generic mark attachment). Results should not be presented as 1:1 Hyperglot parity — only as FontDrop-equivalent-and-better (real GPOS shaping instead of cmap-only) for scripts that rely on generic base+mark attachment (Latin, Cyrillic, Greek, Vietnamese diacritics, etc.).
The database itself (packages/font-engine/src/data/languages.json) is generated, not hand-maintained:
bun run build:languagesThis downloads Hyperglot's data fresh from GitHub, extracts it to a temp dir, and rebuilds the JSON — run it whenever Hyperglot's upstream data should be refreshed.
UI copy is authored once, centrally, and consumed by both apps:
packages/i18n-contentholds the source strings —src/locales/{en,vi}/*.json, one file per feature area (common,about,privacy,compare,inspector,extension). Ships as raw TS/JSON (no build step), same pattern asfont-engine.apps/webconsumes it directly vianext-intl(getMessages()from the package). Routing:/= English (default, unprefixed),/vi= Vietnamese (localePrefix: "as-needed").apps/extensionconsumes theextensionnamespace via@wxt-dev/i18n, but WXT reads locale files from disk at build time (apps/extension/locales/{en,vi}.json), not at runtime import — so those two files are generated, not hand-edited, and are committed to the repo (same convention aspackages/font-engine/src/data/languages.json).
To edit extension copy: change packages/i18n-content/src/locales/{en,vi}/extension.json, then regenerate:
bun run i18n:sync-extensionWeb copy (common/about/privacy/compare/inspector) needs no regeneration step — edit the JSON and it's picked up on next dev/build.
The extension itself has no in-app locale switcher; Chrome/Edge selects en or vi automatically based on the browser's own UI language setting, falling back to en (the extension's default_locale) if the browser is set to anything else.
apps/web needs Upstash Redis credentials to rate-limit POST /api/export-pdf (see apps/web/.env.example):
UPSTASH_REDIS_REST_URL=
UPSTASH_REDIS_REST_TOKEN=Copy apps/web/.env.example to apps/web/.env and fill in values from your Upstash Redis database's REST API tab. Without these, rate-limit checks will fail (the endpoint fails closed).
apps/extension publishes to Microsoft Edge Add-ons via .github/workflows/publish-extension.yml, triggered by pushing a tag — pushing to main does not publish.
- Bump
versioninapps/extension/package.json. - Tag the release commit and push the tag:
git tag extension-vX.Y.Z git push origin extension-vX.Y.Z
- The workflow builds and zips the extension with
bun run zip(apps/extension/.output/*-chrome.zip) and publishes it via thewdzeng/edge-addonaction.
This only updates an existing Edge Add-ons listing — the first submission must be done once, manually, through Partner Center to obtain a Product ID. The workflow needs three repo secrets: EDGE_PRODUCT_ID, EDGE_CLIENT_ID, EDGE_API_KEY.
The extension is live on the Microsoft Edge Add-ons store. No Chrome Web Store or Firefox Add-ons listing yet — in the meantime, use bun run build / bun run build:firefox in apps/extension and load .output/* unpacked — see apps/extension/README.md.
- OpenType feature defaults and native CSS mappings (
packages/font-engine/src/opentype-feature-classification.ts,packages/font-engine/src/opentype-feature-variants.ts) — the fixed/on/off feature-state table and thefont-variant-*equivalents table are ported from Wakamai Fondue'slayout-features.js(Google LLC, Apache License 2.0), trimmed to tag-only lookups. - Generated stylesheet architecture (
packages/font-engine/src/font-stylesheet.ts) — the "combine feature/instance classes freely via CSS custom properties" approach follows the same design Wakamai Fondue uses, reimplemented independently against this project's own extracted font data rather than vendoring theirs. - Language database (
packages/font-engine/src/data/languages.json) — generated from Hyperglot's per-language YAML data (Apache License 2.0) viapackages/font-engine/scripts/build-language-db.ts; see Language detection above for what's ported vs. reimplemented.
bun install
bun devOpen http://localhost:3000.
| Command | Description |
|---|---|
bun dev |
Start dev server |
bun run build |
Production build |
bun run start |
Run production build |
bun run check |
Lint and format check |
bun run fix |
Auto-fix lint and format |
bun run build:languages |
Rebuild packages/font-engine/src/data/languages.json from Hyperglot's upstream data |
bun run i18n:sync-extension |
Regenerate apps/extension/locales/{en,vi}.json from packages/i18n-content after editing extension copy |