castle-web-fonts
v1.0.0
Published
The Castle Classic engine's licensed typefaces, as web fonts, for Castle decks.
Downloads
1,080
Readme
castle-web-fonts
The nine typefaces the Castle Classic engine gives its text tool, as web fonts
a Castle deck can use. Same faces, same names: a deck asking for Piazzolla
here gets what a Classic card asking for Piazzolla gets.
import { installTektur } from 'castle-web-fonts';
installTektur(); // now: font-family: 'Tektur'Install what you name, then use the family name in CSS. Two or three faces is normal — one for body and UI, one display face for titles.
The faces
| face | character | good for | woff2 |
|---|---|---|---:|
| DMSans | clean geometric sans, low contrast | body, UI, HUD | 27 KB |
| Piazzolla | sturdy text serif, readable at length | body text, narrative, prose-heavy decks | 81 KB |
| CourierPrime | monospace typewriter serif | code, terminals, typewriter effects | 28 KB |
| Tektur | condensed squarish techno | sci-fi, arcade, HUD numerals | 31 KB |
| Bore | very heavy wide grotesque | impact headlines, scores, big titles | 12 KB |
| Synco | light wide geometric, unusual joins | retro-modernist titles | 12 KB |
| Glacier | slanted condensed hand-cut italic | informal, energetic display | 13 KB |
| HelicoCentrica | blackletter-flavored roman, pointed serifs | medieval, fantasy, ornate | 78 KB |
| YatraOne | high-contrast brushy display (also Devanagari) | bold headlines, South Asian text | 89 KB |
Each has one weight and one style, as in Classic. Ask for bold or italic and the browser synthesizes it.
install<Face>(): Promise<string>
Registers the face and resolves with its CSS family name. Idempotent — repeat calls return the first call's promise, so calling it from several modules is fine.
The bytes are a data: URI inside the module, so there is no network fetch: no
CORS, no round trip, nothing to go wrong offline, and the face is available on
the first frame. That matters because a published deck is a single HTML file
served from an origin that is never castle.xyz — castle-deck:// in the iOS
app, *.decks.castle.games on Android.
Awaiting it matters before drawing to a canvas. Canvas 2D does not trigger a
font load, so ctx.font = '20px Piazzolla' before the face is ready silently
falls back to the default and never repaints when the font arrives:
import { installPiazzolla } from 'castle-web-fonts';
const family = await installPiazzolla();
ctx.font = `20px ${family}`;In CSS it matters less — the browser reflows when the face lands.
FACES, FACE_NAMES, faceInfo(name)
The catalog: each face's name, label, character, goodFor, credit,
copyright, url, and license. Metadata only — no font bytes, so a font
picker can list every face without pulling any of them into the bundle.
One named export per face, never a lookup
A deck pays for exactly the faces it names, because each face is a named export backed by its own module and there is no other way to reach the bytes. Adding a tenth face, or a fiftieth, costs every existing deck zero.
That property is fragile and worth stating plainly:
// Fine. Inlines Tektur, 41 KB.
import { installTektur } from 'castle-web-fonts';
// NOT fine. Inlines all nine, 495 KB, in every deck that does this.
const FONTS = { Tektur: installTektur, Bore: installBore, /* ... */ };
FONTS[themeName]();No bundler can prove which key a lookup reads, so it keeps every face. Declaring
"sideEffects": false does not rescue it. npm run check builds a real
single-file bundle and counts the inlined faces, so a regression fails there
rather than in someone's deck.
If a font name has to come from data — a theme file, a saved setting — resolve
that name to an install call at build time, not at runtime. The physics-2d kit
does this by generating a one-line module when the deck's font is picked.
Licensing
Eight faces are OFL 1.1, which permits woff2 conversion and redistribution. Bore is CC BY-ND 4.0, where conversion is a permitted technical modification under §2(a)(4) — but subsetting Bore is not, since dropping glyphs makes a derivative work. It is 12 KB whole.
Per-face notices, verbatim from each font's own name table, are in licenses/FONTS.md.
Development
woff2/*.woff2 are converted from the TrueType files in a ghost checkout
(core/assets/), which is where the C++ engine bakes them in from — those are
the source of truth for which faces exist:
npm run convert -- --ghost ../../ghost # ttf -> woff2/
npm run build # woff2/ -> dist/
npm run check # lint + build + bundle-size invariantconvert is deliberately not part of the build: a ghost checkout is not
something a castle-web-fonts build may assume.
