made by FontLab https://www.fontlab.com/
Ultra-fast font search/discovery toolkit in Rust with a matching Python API. typg tracks fontgrep/fontgrepc semantics (live scan + JSON cache subcommands) while reusing fontations + typf assets to stay lean.
typg-core: search engine built onread-fonts/skrifa(fontations) with cached-filter hooks.typg-cli: clap-based CLI that mirrors fontgrep’sfindflags and output modes (plain/columns/JSON/NDJSON).typg-python: PyO3 bindings plus a Fire/Typer CLI shim (typgpy) so Python users get the same surface.
- Live scans work: axes/features/scripts/tables/name/regex/codepoints/text filters plus STDIN/system font discovery.
- Cache path now ships:
typg cache add/list/find/cleanwrites a JSON cache file;--jobscontrols ingest/search threads. - OS/2 weight, width, and family-class filters now ship across Rust/Python/HTTP surfaces.
- Creator and license regex search (
-c/-l) across name table fields (copyright, trademark, manufacturer, designer, description, URLs, license). - Docs/spec cover planned parity (
docs/spec.md); architecture notes live inARCHITECTURE.md.
Every font file is a binary container of named tables — think of them as embedded databases. typg opens each file, reads the tables it cares about, builds a metadata record, and evaluates your query against it. No full-text indexing, no image rendering — just table reads and tag comparisons.
What's in the tables:
| Table | Contains | typg uses it for |
|---|---|---|
name |
Human-readable strings: family name, designer, copyright | --name, --creator, --license filters |
OS/2 |
Weight class (100=Thin → 900=Black), width class (1–9), family class (serif vs sans-serif) | --weight, --width, --family-class filters |
cmap |
Mapping from Unicode codepoints to glyphs — the font's promise that it can draw a character | --codepoints, --text filters |
fvar |
Variable font axes: wght (weight), wdth (width), opsz (optical size), ital, slnt |
--axes, --variable filters |
GSUB |
Glyph substitution rules — features like liga (ligatures), smcp (small capitals), onum (oldstyle numerals) |
--features filter |
GPOS |
Glyph positioning rules — features like kern (kerning), mark (diacritics placement) |
--features filter |
GSUB/GPOS script lists |
Writing systems with dedicated shaping rules: latn (Latin), cyrl (Cyrillic), arab (Arabic), deva (Devanagari) |
--scripts filter |
A query in plain English: "Find me every font in ~/Fonts that supports
Cyrillic (-s cyrl), has small capitals (-f smcp), and is heavier than
Regular (-w 500-900)":
typg find -s cyrl -f smcp -w 500-900 ~/Fontstypg opens each font file, checks the GSUB/GPOS script list for cyrl,
checks the feature list for smcp, reads usWeightClass from OS/2, and
returns only the fonts satisfying all three. With rayon, it does this across
all CPU cores simultaneously.
Scripts vs. codepoints: A font can draw Cyrillic characters (they're in
its cmap) without having shaping rules for Cyrillic (no cyrl in its
script list). -s cyrl finds fonts with proper typographic shaping support
— kerning, contextual alternates, the works. --text "Привет" finds fonts
that can at least render those characters.
Variable fonts: A single variable font file replaces an entire family.
Instead of separate Bold/Regular/Light files, the fvar table defines
continuous axes. --variable matches only these multi-axis files;
--axes wght,wdth matches those with both weight and width axes.
# Rust CLI (quick install)
./install.sh
# Rust CLI (with LMDB index support)
./install.sh --hpindex
# Rust CLI (manual)
cargo install --path cli
# Python bindings/CLI (uv-based)
cd py/typg-python
uv venv --python 3.12
source .venv/bin/activate
uv pip install maturin
maturin develop --locked- Live scan a directory for small caps + Latin support:
typg find -f smcp -s latn ~/Fonts - Accept STDIN paths:
fd .ttf ~/Fonts | typg find --stdin-paths --ndjson - Include system font roots:
typg find --system-fonts --columns - Control worker count when scanning:
typg find --jobs 4 --variable ~/Fonts(defaults to CPU count) - Filter OS/2 classifications:
typg find --weight 300-500 --width 5 --family-class sans ~/Fonts - Search by creator/maker (regex across copyright, trademark, manufacturer, designer, description, URLs, license fields):
typg find --creator "FontLab" ~/Fonts - Search by license (regex across copyright, license description, license URL):
typg find --license "OFL|Apache" ~/Fonts - JSON output: add
--json(array) or--ndjson(one match per line). Columns/plain auto-colorize unless--color never. - Paths-only output for piping into typf/fontlift/testypf:
typg find --paths ~/Fonts(also works withcache list/find). - Path overrides for system fonts: set
TYPOG_SYSTEM_FONT_DIRS="/opt/fonts:/tmp/fonts". - Build and query a cache (JSON file):
typg cache add --cache-path ~/.cache/typg/cache.json ~/Fontsthentypg cache find --cache-path ~/.cache/typg/cache.json --scripts latn --json; usetypg cache cleanto drop missing fonts andtypg cache list --jsonto inspect entries. Cache path defaults to~/.cache/typg/cache.json(orLOCALAPPDATAon Windows) and respectsTYPOG_CACHE_PATH. - Cache info:
typg cache infoshows cache/index statistics (path, type, font count, size). Supports--jsonand--index. - Count-only queries:
typg cache find --scripts latn --countoutputs just the number of matching fonts (useful for scripting). - Quiet mode:
typg -q cache add ~/Fontssuppresses informational stderr messages. - High-performance index (optional
hpindexfeature): For 100k+ font collections, use LMDB-backed index instead of JSON cache. Build withcargo build --features hpindex, then use--indexflag:- Ingest:
typg cache add --index ~/Fonts(indexes to~/.cache/typg/index/by default). - Query:
typg cache find --index --scripts latn --features smcp(O(K) tag intersection via Roaring Bitmaps). - List:
typg cache list --index(lists all indexed fonts). - Clean:
typg cache clean --index(removes entries for missing files). - Custom location:
typg cache add --index --index-path /path/to/index ~/Fonts. RespectsTYPOG_INDEX_PATHenv var.
- Ingest:
- Remote querying:
typg serve --bind 127.0.0.1:8765exposes/healthand/search(POST JSON with paths/filters, setpaths_only:trueto get a newline-ready list). With hpindex feature,/searchalso acceptsuse_index:trueand optionalindex_pathto query the LMDB index instead of live scanning.
from typg import find, find_paths
matches = find(paths=["~/Fonts"], scripts=["latn"], features=["smcp"], variable=True)
for m in matches:
print(m["path"], m["names"][0])
paths_only = find_paths(paths=["~/Fonts"], scripts=["latn"])
print("first path:", paths_only[0])
weighted = find(paths=["~/Fonts"], weight="400-700", width="5")
print("weighted matches:", len(weighted))
family = find(paths=["~/Fonts"], family_class="sans")
print("sans-serif matches:", len(family))
by_creator = find(paths=["~/Fonts"], creator=["FontLab"])
print("FontLab fonts:", len(by_creator))
by_license = find(paths=["~/Fonts"], license=["OFL|Apache"])
print("open-license fonts:", len(by_license))
# Indexed search (requires hpindex feature in build)
try:
from typg import find_indexed, list_indexed, count_indexed
matches = find_indexed(index_path="~/.cache/typg/index", scripts=["latn"])
count = count_indexed(index_path="~/.cache/typg/index")
all_fonts = list_indexed(index_path="~/.cache/typg/index")
except ImportError:
print("hpindex feature not enabled in build")CLI parity from Python: typgpy find --paths ~/Fonts --scripts latn --features smcp --variable --paths_only True.
use std::path::PathBuf;
use typg_core::query::Query;
use typg_core::search::{search, SearchOptions};
use typg_core::tags::tag4;
let paths = vec![PathBuf::from("~/Fonts")];
let query = Query::new().with_features(vec![tag4("smcp").unwrap()]);
let matches = search(&paths, &query, &SearchOptions::default())?;typg findmirrorsfontgrep findflags already shipped (axes/features/scripts/tables/name/regex/codepoints/text/creator/license, STDIN, system fonts, JSON/NDJSON, columns/plain).- Cache subcommands mirror fontgrepc (
add/list/find/clean) using a JSON cache file; keep using fontgrepc if you need SQLite today. - Weight/class/width shorthands are still planned; use explicit tag filters for now.
- Output layout matches fontgrepc NDJSON; column widths are stable for downstream tooling.
- macOS local builds:
./build.sh [release|debug]emits thetypgRust CLI and thetypgPython wheel/typgpyCLI (version comes from git tags via hatch-vcs). - Manual publishing:
./publish.sh [publish|rust-only|python-only|sync|check]syncs Cargo crate versions to the current semver git tag, then pushes crates to crates.io and wheels to PyPI when credentials are present. - GitHub Actions:
release.ymltriggers onvN.N.Ntags to build manylinux/macOS/Windows wheels, publish to PyPI, publish crates (typg-core,typg-cli,typg-python) to crates.io, and attach wheels to the GitHub release.
- Flags mirror fontgrep; see
docs/spec.mdfor any divergence. - Cache subcommands are available:
typg cache add/list/find/cleanmanage a JSON cache file; live scans remain available viatypg find. - NDJSON output matches fontgrepc conventions so log pipelines stay compatible.
- Keep functions short and prefer deleting over adding.
- Match fontgrep/fontgrepc semantics unless a deviation is documented in
docs/spec.md. - Add tests (property for parsers, snapshot for CLI) before marking tasks done.
- Plan:
TASKS.md - Tasks:
TODO.md - Spec:
docs/spec.md - Architecture:
ARCHITECTURE.md - Work log:
WORK.md