@leo-studio/font-patcher
v0.2.0
Published
Patches Nerd Fonts with hand-drawn TUI primitives, git-graph glyphs, and Leo/AI-provider icons for leo's TUIs
Maintainers
Readme
@leo-studio/font-patcher
OpenType font patcher for leo. Layers a small set of hand-drawn
drawing primitives on top of a source font (typically a Nerd Font),
producing a patched copy under ~/.leo/chroma/ that's safe to install
alongside the original. The output filename is content-hashed
(icon-font-{hash}.otf) to bypass the macOS font cache, which keys on
PostScript name; old hashed copies and the legacy unhashed
icon-font.otf are pruned after each successful patch. Use
getPatchedFontOutputPath() (or leo font status) to discover
the current output path.
The primitive set is designed for composing big digits / letters /
status-bar capsules in a terminal — see
docs/terminal-verification.md for
the visual verification checklist.
Quick start
# Patch a Nerd Font with the default primitive set.
leo font setup --font "JetBrainsMono Nerd Font"
# Output:
# ~/.leo/chroma/icon-font-{hash}.otf
# Family name: "JetBrainsMono Nerd Font Leo Icons"
# Install into your user font directory (resolve the current path via
# `leo font status` or `getPatchedFontOutputPath()`).
cp "$(leo font status --json | jq -r '.patchedFont.path')" ~/Library/Fonts/ # macOSThen configure your terminal to use the Leo Icons family name.
Drawing primitives
All primitives live in a single block adjacent to Powerline:
U+E0D8..U+E0EF (24 glyphs), with U+E0F0..U+E0FF reserved. The
patcher's collision guard skips any codepoint the source font already
populates unless the primitive opts into forceOverwrite. leo's
join-variant drawing slots
(E0E8..E0EF) intentionally force-overwrite because current Nerd Font
sources already contain placeholder/foreign glyphs there.
Geometry contract: every primitive's path is integer-snapped to font
units; advance width equals the source font's monospace cell width
(derived from the M / 0 advance, not blindly unitsPerEm).
Contours wind counter-clockwise around the filled region in font Y-up
coordinates.
Source-font requirement. The bowl-waist primitives (
E0E0,E0E1) pick a disc radius ofcellHeight / 2so the two arcs are tangent at the side-edge midpoint. This requirescellHeight ≤ 2 * cellWidth(true for every standard monospace font tested — JetBrains Mono, Hack, SF Mono, Cascadia, Iosevka). On an extreme font violating that bound the bowl-waist arc would extend outside the cell horizontally and clip.
Reused standard codepoints (redrawn full-cell)
| Codepoint | Glyph | Name |
| --------- | ----- | ---- |
| U+25E2 | ◢ | Corner triangle — lower-right (full-cell) |
| U+25E3 | ◣ | Corner triangle — lower-left (full-cell) |
| U+25E4 | ◤ | Corner triangle — upper-left (full-cell) |
| U+25E5 | ◥ | Corner triangle — upper-right (full-cell) |
These are forceOverwrite: true. The standard glyphs in most fonts
(including Nerd Fonts) draw the triangles at reduced size; leo
redraws them corner-to-corner so two adjacent triangles compose into
seamless peaks / valleys.
Reused Powerline codepoints (kept as-is, with overhang)
| Codepoint | Name |
| --------- | ---- |
| U+2588 | Full block █ (standard Unicode, no Powerline) |
| U+E0B4 | Powerline right half-circle solid |
| U+E0B6 | Powerline left half-circle solid |
Policy: Powerline's intentional ~1/64 em overhang on E0B4/E0B6
is preserved so horizontal capsules E0B6 2588 E0B4 join without
sub-pixel seams. The trade-off is that the cursor may appear offset
by one column on the cap glyphs — accepted for the seamless join.
New custom block: U+E0D8..U+E0FF — Drawing Extensions
| Codepoint | Name | Description |
| --------- | ---- | ----------- |
| U+E0D8 | Round corner filled, UL rounded | Cell filled except for a quarter-disc cut from the upper-left corner (radius = cellWidth/2) |
| U+E0D9 | Round corner filled, UR rounded | Mirror of E0D8 |
| U+E0DA | Round corner filled, LL rounded | |
| U+E0DB | Round corner filled, LR rounded | |
| U+E0DC | Diagonal stripe, / left half | Pairs with E0DD for a thick / (footprint = 3/4 cellWidth per edge) |
| U+E0DD | Diagonal stripe, / right half | |
| U+E0DE | Diagonal stripe, \ left half | Pairs with E0DF for a thick \ |
| U+E0DF | Diagonal stripe, \ right half | |
| U+E0E0 | Bowl waist, left | Cell mostly filled, cuts from the right corners meeting at (cellWidth, yMid) |
| U+E0E1 | Bowl waist, right | Mirror of E0E0; pairs with E0E0 for an 8-waist |
| U+E0E2 | Diagonal round cap, UL end | Quarter-disc fill in the named corner (radius cellWidth/2) |
| U+E0E3 | Diagonal round cap, UR end | |
| U+E0E4 | Diagonal round cap, LL end | |
| U+E0E5 | Diagonal round cap, LR end | |
| U+E0E6 | Vertical capsule cap, top | Lower-half rectangle + semicircle bulging up to the ascender |
| U+E0E7 | Vertical capsule cap, bottom | Mirror of E0E6 |
| U+E0E8 | Diagonal stripe, / left half, top join | Base E0DC plus a thin full-width cap for a full block above; force-overwrites source glyphs |
| U+E0E9 | Diagonal stripe, / left half, bottom join | Base E0DC plus a thin full-width cap for a full block below |
| U+E0EA | Diagonal stripe, / right half, top join | Base E0DD plus a thin full-width cap for a full block above |
| U+E0EB | Diagonal stripe, / right half, bottom join | Base E0DD plus a thin full-width cap for a full block below |
| U+E0EC | Diagonal stripe, \ left half, top join | Base E0DE plus a thin full-width cap for a full block above |
| U+E0ED | Diagonal stripe, \ left half, bottom join | Base E0DE plus a thin full-width cap for a full block below |
| U+E0EE | Diagonal stripe, \ right half, top join | Base E0DF plus a thin full-width cap for a full block above |
| U+E0EF | Diagonal stripe, \ right half, bottom join | Base E0DF plus a thin full-width cap for a full block below |
| U+E0F0..E0FF | reserved | |
Identicon motif block: U+E164..E175
A second custom block holds the identicon motifs consumed by the
generateIdenticon helper in @leo-studio/identicon (see Identicons). Each motif
fills a square block = two adjacent cells (a monospace cell is ~1:2, so
two side-by-side cells are square) and is a glyph pair — a left half
and a right half — printed in the motif's foreground colour over the
block background. Two motifs reuse standard glyphs (empty → U+0020,
square → U+2588); the nine shape motifs below need real outlines
because a terminal's stock ◖/◗ render as two separated half discs.
| Codepoints | Motif | Description |
| ---------- | ----- | ----------- |
| U+E164 E165 | disc | Full circle (two cell-filling semicircles meeting at the seam) |
| U+E166 E167 | ring | Annulus / donut |
| U+E168 E169 | discHole | Filled block with a centred circular hole |
| U+E16A E16B | smallDisc | Small centred circle |
| U+E16C E16D | diamond | Rhombus |
| U+E16E E16F | diamondHole | Filled block with a centred diamond hole |
| U+E170 E171 | smallDiamond | Small centred rhombus |
| U+E172 E173 | triUp | Triangle, apex at top-centre |
| U+E174 E175 | triDown | Triangle, apex at bottom-centre |
Unlike the overprint experiments, every motif half draws entirely inside its own cell (no out-of-cell ink), so the motifs render in any terminal that loads the font.
Git-graph glyphs: U+E1B0..U+E1C2
Single-tone routing + commit cells for rendering a branching commit
graph in the worktree-tui LogTab. ViewBox is 24×48 (full-cell width
× full-cell height) so adjacent rows compose into a continuous vertical
wire. Strokes use currentColor so SGR foreground tint covers the
whole glyph — COLR v0 colored twins are intentionally absent (broken
in Ghostty; see docs/plans/git-graph-glyphs-tui.plan.md).
| Codepoint | Name | Description |
| --------- | ---- | ----------- |
| U+E1B0 | git-line-straight | Vertical wire, top→bottom |
| U+E1B1 | git-line-branch-tl | Vertical wire + arc exiting upper-left edge |
| U+E1B2 | git-line-branch-tr | Vertical wire + arc exiting upper-right edge |
| U+E1B3 | git-line-merge-bl | Vertical wire + arc entering from lower-left edge |
| U+E1B4 | git-line-merge-br | Vertical wire + arc entering from lower-right edge |
| U+E1B5 | git-commit | Vertical wire with commit node at centre |
| U+E1B6 | git-commit-branch-tl | Commit + arc exiting upper-left edge |
| U+E1B7 | git-commit-branch-tr | Commit + arc exiting upper-right edge |
| U+E1B8 | git-commit-merge-bl | Commit + arc entering from lower-left edge |
| U+E1B9 | git-commit-merge-br | Commit + arc entering from lower-right edge |
| U+E1BA | git-commit-tip | Commit with only a bottom strand (branch tip / HEAD) |
| U+E1BB | git-commit-root | Commit with only a top strand (initial commit) |
| U+E1BC | git-commit-merge-blr | Commit + bl + br arcs (3-parent octopus through one cell) |
| U+E1BD | git-line-h-straight | Horizontal wire at the cell vertical midline |
| U+E1BE | git-line-corner-ne | Corner connecting top edge + right edge |
| U+E1BF | git-line-corner-nw | Corner connecting top edge + left edge |
| U+E1C0 | git-line-corner-se | Corner connecting bottom edge + right edge |
| U+E1C1 | git-line-corner-sw | Corner connecting bottom edge + left edge |
| U+E1C2 | git-line-cross | Vertical wire + horizontal wire intersecting |
The PoC at
__poc__/git-graph-glyphs/index.html
previews the rescaled set + composed-scene examples; the source 24×24
two-tone design lives in /leo/git-glyphs/. Visual-regression
references for the patched font are in
test/fixtures/rendered/git-*.svg (driven by the [git-*] fixtures in
test/fixtures/digits.txt).
Composition cheat-sheet
| What | Cell sequence (rows × cols) |
| ---- | --------------------------- |
| Horizontal capsule (full width) | E0B6 2588 ... 2588 E0B4 |
| Vertical capsule (full height) | E0E6 over 2588 over … over E0E7 |
| Closed bowl, top (0/8/9) | E0D8 E0D9 |
| Closed bowl, bottom | E0DA E0DB |
| 8-waist | E0E0 E0E1 |
| Thick / slash | E0DC E0DD |
| Thick \ slash | E0DE E0DF |
| Slash/full-block vertical joins | Use E0E8..E0EF variants on the slash cell where it touches a 2588 row |
| Round-capped diagonal terminus | E0E2..E0E5 placed at stroke ends |
| Continuous valley (◤◥ shape) | 25E4 25E5 |
| Continuous peak (◣◢ shape) | 25E3 25E2 |
| Linear commit chain | E1BA (tip) over E1B5 × N (commits) over E1BB (root) |
| Fork + merge (2 adjacent lanes) | E1B9 _ \| E1B0 E1BA \| E1B7 _ \| E1BB _ |
| 3-parent octopus merge | _ E1BC _ over E1BB E1BB E1BB |
| Cross routing | _ E1B0 _ over E1BD E1C2 E1BD over _ E1B0 _ |
A worked example lives at
test/fixtures/digits.txt; the rendered
references in test/fixtures/rendered/ show what each composition
looks like at the synthetic-font metrics.
API
import {
createPatchedFont,
patchFontWithIcons,
PRIMITIVE_GLYPHS,
type PrimitiveGlyph,
type PrimitiveDrawContext,
buildDrawContext,
renderEntryToSvg,
parseDigitFixture,
} from '@leo-studio/font-patcher';createPatchedFont(fontName?, progressCallback?, { sourceFontKind? })— high-level orchestrator. Patches the named source font (default'SF Mono') with the full registered primitive set, installs to user fonts. Terminal detection (which font iTerm2/Ghostty uses) is the caller's job; this package never reads terminal config.patchFontWithIcons(sourcePath, outputPath, iconSvgs, { sourceFontKind? })— lower-level: takes an explicit source font path and emits a new patched font. Returns{ success, glyphsAdded, sourceWasNerdFont, blockedCodepoints }.PRIMITIVE_GLYPHS— module-level registry of primitives by short identifier. Pre-populated with the full set; consumers can register additional primitives by mutating this object before callingpatchFontWithIcons.PrimitiveGlyph—{ codepoint, description, draw, forceOverwrite? }.draw(path, ctx)mutates anopentype.Path;ctxis aPrimitiveDrawContextwithcellWidth,ascender,descender,overhang.renderEntryToSvg(entry, font, options?)— render a fixture entry to SVG markup against the given font; used by the visual regression test.parseDigitFixture(path)— parser for thedigits.txtfixture format.
Identicons
The identicon generator lives in @leo-studio/identicon (leo monorepo).
@leo-studio/font-patcher owns the motif codepoint table
(IDENTICON_MOTIFS, SHAPE_MOTIF_NAMES, IDENTICON_MOTIF_BASE) that the
generator consumes, the motif glyph outlines (the U+E164..E175
primitives that ship via leo font setup), and the svgToPng raster
helper. Use @leo-studio/identicon for generation,
toSvg, and terminal rendering; use svgToPng from this package only
when you need PNG bytes.
import { generateIdenticon, makeIdentity, toSvg } from '@leo-studio/identicon';
import { svgToPng } from '@leo-studio/font-patcher';
const identity = makeIdentity(originUrl, branch);
const model = generateIdenticon(identity);
const svg = toSvg(model);
const png = svgToPng(svg); // optional — needs font-patcher; synchronousTests & verification
pnpm testruns the unit suite (100+ tests covering primitive geometry, NF collision guard, dispatch, fixture parser, render options, and SVG visual regression).pnpm exec tsx scripts/render-references.tsregenerates the reference SVGs intest/fixtures/rendered/after an intentional geometry change. Commit the diff.- See
docs/terminal-verification.mdfor the manual terminal-matrix checklist.
