SchOS/.claude/memory/schunix-text-rendering.md
Nexoniarz 3182da3965 Carry project memory in the repo, with tools/memory-sync.sh
Claude Code keeps per-project memory at <home>/.claude/projects/<key>/,
where <key> is the project's absolute path with every separator and the
drive colon turned into a dash. That makes it both rename-fragile and
machine-specific, and this project has hit each of those once:

  - D:\DEV\Schunix -> D:\DEV\SchOS changed the key, and 43 memories were
    invisible until moved across by hand.
  - Development happens from Windows and from Linux, and each looks in a
    different place for the same checkout.

Committing .claude/memory/ fixes both -- git carries it across renames
and between hosts -- and `import` puts it wherever the local machine
expects. .claude/settings.local.json stays ignored: those are per-machine
permissions.

The colon maps to a dash rather than being dropped. Deleting it gives
D-DEV-SchOS, which matches nothing and silently finds zero memories; that
was the first attempt. On Git Bash the key comes from `cygpath -w`,
because Claude Code is a Windows program and sees D:\DEV\SchOS, not
/d/DEV/SchOS.
2026-08-09 14:27:22 +02:00

4.1 KiB

name description metadata
schunix-text-rendering The text layer that replaces the 8x16 character-cell grid — glyph metrics, surfaces, coverage blending, UTF-8.
node_type type originSessionId modified
memory project 9d8a2aef-bcf0-47a0-96c6-f706efab4a95 2026-08-08T22:45:34.934Z

Built 2026-08-09. include/text.h + kernel/dev/text.c. Nexoniarz: "using ANSI fonts in 2026 is a not funny joke."

The font was never the problem — the grid was. The console indexes font_data[256][16] and steps the cursor eight pixels. A grid cannot do variable-width glyphs, cannot position text off a multiple of eight, cannot anti-alias, and cannot represent a codepoint above 255. A nicer bitmap fixes none of that.

Three decisions that make the layer worth having:

  • Glyphs carry metrics (w, h, bearing_x/y, advance). Variable-width text is that; everything else follows. text_measure() exists because you cannot centre or lay out anything without it.
  • Draws into a struct surface, not the screen. {pixels, stride, width, height}. A compositor draws off-screen and presents; rendering that writes straight to the VGA aperture is useless to it. Works on the virtio-gpu back buffer, a window buffer, or the VGA aperture, none of which know about each other.
  • struct font_ops behind an interface, and glyphs carry 8-bit coverage, not bits. The bitmap font fills it with 0 and 255 only, which costs nothing — and a TrueType rasteriser then plugs in with no caller changing, because it already has somewhere to put anti-aliased edges. blend_px() reads the destination for the same reason.

Pen position is on the baseline, as every non-terminal text API does, so glyphs of different heights sit on a common line.

Verified with echo t > /proc/sysrq + tools/textshot.ps1: text at x=61 and x=63, a checkerboard background visible around the glyphs (no cell boxes), ┌─┐ █▓▒░ ° ± → • above U+00FF, and a string measured at 160 px then centred on it. The old console renders in the same screenshot with solid cell backgrounds — a useful side-by-side.

Traps hit

  • include/font.h defines font_data rather than declaring it, so including it from a second file duplicates the symbol under -fno-common. text.c declares extern unsigned char font_data[256][16]; instead; the header is generated data and is better left alone.
  • The built-in font is CP437, not Latin-1 (known trap in this codebase). to_cp437() maps the ~25 codepoints worth having — box drawing, blocks, arrows, degree. That table exists to be deleted when a real font lands; it is a bridge, not a design.
  • A missing glyph draws U+25A1 or ?, never nothing. Silently shorter text hides the fault.
  • utf8_next() always advances, even on malformed input — a decoder that does not is a hang, not a rendering artefact. Rejects surrogates and overlong encodings.

The typefaces, decided 2026-08-09

  • Roboto for the DE / WM / compositor. Apache-2.0 -- the same licence as Schunix, so it needs only a NOTICE entry.
  • JetBrains Mono for the terminal / TTY. SIL OFL 1.1, and it declares no Reserved Font Name (checked: the copyright line names none; the only "reserved font name" hit in OFL.txt is the licence defining the term). So a modified version may keep the name -- which matters, because pre-rasterising a font into bitmaps at build time is a modification and an RFN would have forbidden keeping the name on the result.

OFL keeps the font under OFL inside an Apache-2.0 system, does not reach anything else, and only forbids selling the font on its own. Both fetched and verified at source, not assumed. Full note in docs/licensing.md ("Fonts").

Neither is in the tree yet -- there is no rasteriser to use them, and shipping a TTF nothing can read would be dead weight.

Not done

The console still uses the old path; nothing is ported to this yet. Porting vga_putc onto it is the obvious follow-up and would delete the cell grid for good. A TTF rasteriser is a separate provider behind font_ops — the layer is what was blocking it, and it no longer is.