Scrape the fonts any website uses — and save them as clean TTF / OTF.
A CLI, a local web app, and a Chrome extension that share one font engine: discover
every face on a page (even dynamic, variable, and tester-injected ones), then
decompress WOFF2/WOFF to clean sfnt with the name table repaired and variable
axes preserved.
Foundry sites, type testers, SPAs — fonts hide in CSS, in JavaScript, behind extensionless URLs, and inside Web Workers. Font Grabber finds them all by running the same discovery engine in three places, then converts them locally. Nothing is uploaded anywhere.
- Showcase
- Three ways to grab fonts
- Features
- How it works
- Discovery modes
- Repository layout
- Requirements & build
- License
|
Local web app — scan a URL, preview every face as a live specimen, filter, then download a ZIP or save to a folder.
|
Chrome extension — grab fonts from the page you're on, converted in‑browser.
|
A single Rust binary serves an editorial, type‑foundry UI on 127.0.0.1 — live
specimen previews, editable sample text, filtering, and ZIP or save‑to‑folder
delivery. No Node, no build step.
scripts\font-grabber-web.cmd # build (release) + open the browser
# or directly:
cd core && cargo run -- serve # http://127.0.0.1:8787A Manifest V3 extension (extension/) that detects fonts on the
current tab, previews them, and downloads clean TTF/OTF entirely in the
browser — the WOFF2 → sfnt conversion is the same Rust code compiled to
WebAssembly. It even captures variable fonts from type testers (e.g. Displaay).
scripts\build-extension.cmd # build wasm/ → extension/convert.wasmThen chrome://extensions → Developer mode → Load unpacked → pick
extension/. Details in extension/README.md.
cd core
cargo run -- grab https://example.com # discover → select → convert → save
cargo run -- grab https://example.com --all -o ./out # grab everything, no prompts
cargo run -- grab https://displaay.net/typeface/perfektta --all # full variable family
cargo run -- scan https://example.com --json # discovery only (machine‑readable)$ font-grabber grab https://displaay.net/typeface/perfektta --all
✓ Found 22 faces (browser)
✓ Saved 22 fonts → ./fonts/displaay.net
Perfektta-Variable-400.ttf (variable · wght 100–900, slnt)
Perfektta-Bold-700.ttf Perfektta-Bold-Italic-700.ttf …
- Finds what others miss —
@font-facein CSS, fonts loaded at runtime, extensionless API endpoints, Web‑Worker fetches, and JS‑injectedFontFaces. - Whole families, not fragments — reads a foundry's own catalog to enumerate every weight + italic and the master variable font, up front.
- Faithful conversion —
WOFF2(Brotli) andWOFF(zlib) → clean sfnt, name table repaired, variable axes (fvar/gvar) preserved. Byte‑identical across the CLI, web app, and extension. - Live previews — every candidate rendered in its real typeface with editable sample text and adjustable size.
- Flexible output — ZIP, a chosen folder, or individual files.
- 100% local — no servers, no uploads, no telemetry.
One discovery engine (extension/discover.js +
capture.js) runs everywhere. The Rust apps drive it through an embedded headless
Chrome (via CDP — no separate WebDriver to install); the extension runs it in the
page directly. Candidates then flow through the shared Rust converter.
flowchart LR
CLI["CLI"] --> HC["Embedded headless Chrome"]
WEB["Local web app"] --> HC
EXT["Chrome extension"] --> PAGE["Live page context"]
HC --> DISC["discover.js + capture.js<br/>(CSS, network, catalog, FontFace hooks)"]
PAGE --> DISC
DISC --> CAND["Font candidates"]
CAND --> CONV["Converter — Rust / WASM<br/>WOFF2 / WOFF to TTF / OTF + name repair"]
CONV --> OUT[("ZIP, folder, files")]
The converter is written once in Rust (core/src/convert) and
compiled to wasm32-unknown-unknown for the browser (wasm/) with a tiny
hand‑rolled ABI — no wasm-bindgen.
| Mode | Browser? | Catches | Use it for |
|---|---|---|---|
auto (default) |
static + headless Chrome, merged | everything; falls back to static if Chrome is absent | most sites |
static |
none | @font-face in HTML/linked CSS |
speed; CI; simple sites |
render |
headless Chrome only | dynamic / extensionless / catalog fonts | type testers, SPAs |
.
├── core/ Rust crate — CLI + local web server (the engine)
│ └── src/{app,cli,convert,discovery,domain,fetch,output,support,web}
├── wasm/ Rust → WebAssembly converter (used by the extension)
├── extension/ Chrome MV3 extension (popup UI + shared discover.js + WASM)
├── scripts/ Windows build / launch helpers (.cmd)
└── docs/ screenshots
- Rust (
rustup) — builds the CLI and web app. - Chrome / Chromium installed — used by
auto/renderdiscovery (auto‑detected;staticmode needs no browser). wasm32-unknown-unknowntarget — only to (re)build the extension's converter:scripts\build-extension.cmdhandles it.
# CLI + web app
cd core && cargo build --release
# Extension converter (WASM)
scripts\build-extension.cmdMore on the engine internals: core/README.md.
ISC © Piwqust

