Deterministic, self-hosted web-font subsetting service
970
![]()
Repository: github.com/kserksi/klyph
Klyph is a deterministic, self-hosted web-font subsetting service. It normalizes a requested character set, generates an immutable WOFF2 subset with FastAPI and FontTools, and serves it through a versioned API and a browser SDK. It has no required domain or hosting-provider dependency.
The source fonts come from the official Google Fonts GitHub repository and are pinned to exact commits:
python scripts/download_fonts.py
The script records source metadata and SHA-256 digests in fonts/sources.json. Both font families include their SIL Open Font License 1.1 texts.
python -m venv .venv
.venv\Scripts\pip install -e ".[test]"
python scripts/download_fonts.py
.venv\Scripts\uvicorn app.main:app --reload
Run the test suite with:
.venv\Scripts\python.exe -m pytest
Download the fonts before building the image:
python scripts/download_fonts.py
docker build -t klyph .
docker run --rm -p 8000:8000 `
-e FONT_PUBLIC_BASE_URL=https://fonts.example.com `
-e FONT_ALLOWED_ORIGINS=https://www.example.com `
-v font-cache:/app/cache `
klyph
The image is based on Python 3.14.6 slim, runs as a non-root user, and starts a single HTTP process. /healthz is the liveness endpoint. /readyz also verifies that required font files exist and the cache directory is writable. Runtime dependencies are pinned in requirements.lock.
POST /v2/subsets
Content-Type: application/json
{"font":"zen-kaku-regular","characters":"障害情報"}
The response contains an immutable WOFF2 URL versioned by the font version and normalized character hash.
Possible error responses:
400: invalid font or character input403: browser origin is not allowed413: request body exceeds the configured limit503: generation queue is full, a lock timed out, or generation timed out507: cache capacity or minimum free-space threshold was reached<script defer src="https://fonts.example.com/sdk/v2.js"></script>
<script>
document.addEventListener('DOMContentLoaded', function () {
WebFont.load({
font: 'zen-kaku-regular',
family: 'Zen Kaku Gothic New',
selectors: ['.post-content', '.site-header']
});
});
</script>
Use WebFont.observe() for debounced incremental loading when monitored content changes.
/: service overview, live readiness, font specimens, and internal API summary/terms: terms of service/privacy: character data, logging, external service, and cache handling policy/licenses: font, artwork, and software licenses and credits/components: production open-source components and pinned versionsThe pages share /assets/site.css and /assets/site.js. They use no cookies, local storage, or third-party analytics. FastAPI's interactive documentation and OpenAPI schema are disabled.
Search metadata is provided through robots.txt, sitemap.xml, canonical links, hreflang, Open Graph, Twitter Cards, and Schema.org JSON-LD. Machine endpoints return X-Robots-Tag: noindex, nofollow.
Regenerate the local brand assets on Windows after changing the visual identity:
powershell -NoProfile -ExecutionPolicy Bypass -File scripts/generate_brand_assets.ps1
| Environment variable | Default | Description |
|---|---|---|
FONT_PUBLIC_BASE_URL | http://localhost:8000 | Fixed public root used in API responses and rendered metadata |
FONT_ALLOWED_ORIGINS | local port 8000 | Comma-separated origins allowed to call the generation endpoint |
FONT_MAX_REQUEST_BYTES | 65536 | Maximum request-body size in bytes |
FONT_MAX_CHARACTERS | 8000 | Maximum number of normalized unique characters |
FONT_GENERATION_TIMEOUT | 20 | Total lock-wait and generation timeout in seconds |
FONT_GENERATION_WORKERS | 2 | Maximum concurrent font-generation processes |
FONT_MAX_PENDING_GENERATIONS | 32 | Maximum pending jobs for distinct character sets |
FONT_MAX_CACHE_BYTES | 10737418240 | Maximum immutable font cache size (10 GiB) |
FONT_MIN_FREE_BYTES | 268435456 | Free space reserved on the cache volume (256 MiB) |
FONT_CACHE_MAX_AGE_DAYS | 30 | Delete a font after this many days without access |
FONT_CACHE_CLEANUP_INTERVAL | 86400 | Cache cleanup interval in seconds (24 hours) |
FONT_SHUTDOWN_TIMEOUT | 10 | Graceful shutdown timeout in seconds |
FONT_LOG_LEVEL | INFO | Structured application log level |
Klyph writes single-line JSON logs to standard output. Logs include request IDs, font IDs, unique character counts, subset hashes, cache hits, output sizes, and durations. Raw character content is never logged.
/v2/subsets with method rules, per-client rate limits, and a global circuit breaker./v2/fonts/* through a long-lived CDN cache.503, 507, generation failures, and generation latency.Klyph's source code is licensed under the Apache License 2.0. The bundled fonts are distributed separately under SIL Open Font License 1.1. The Hero artwork is by Lilac (Pixiv artwork #146748240); all rights to the image remain with the artist, and it is not covered by Apache-2.0. See fonts/OFL-kaku.txt, fonts/OFL-maru.txt, and the /licenses page for attribution details.
Content type
Image
Digest
sha256:58fb73c1e…
Size
64.4 MB
Last updated
2 months ago
docker pull jaykserks/klyph