SchOS/tools/python/mknvfont.py
Nexoniarz dee3affa3b Boot manager: Niveus window, keys and wells, Instrument Sans, pointer, other systems, working power entries
The F8 menu is redrawn in the Niveus language: a window with three lights,
bevelled keys with a lit edge, wells for panels, anti-aliased corners and a
soft shadow, set in Instrument Sans rasterised into include/gfx/nvfont.h
(tools/python/mknvfont.py, make nvfont). Scale doubles on screens 1400 px tall
or more. Lists taller than the screen scroll.

The keyboard still drives everything, and a mouse, touchpad or tablet does
too: both UEFI pointer protocols are polled, hover moves the highlight, a
click activates. The cursor stays hidden until the pointer has really done
something, since the firmware installs a pointer protocol with or without a
mouse. The 'keep this resolution?' countdown is one ten-second budget however
many times the pointer moves.

Loaders found at well-known paths on any volume (Windows, Ubuntu, Debian,
Fedora, Arch, openSUSE, systemd-boot, rEFInd, other BOOTX64.EFI) are listed
and chain-loaded with LoadImage/StartImage. 'Reboot to firmware' used to halt;
it now sets OsIndications and restarts, and Restart and Shut Down call
ResetSystem.

Checked under QEMU+OVMF: menu, resolution list (incl. 2560x1600 at double
scale), system information, shut down, firmware setup, chain-loading a copy of
the loader from a second disk, and a full boot of the ISO to the login prompt.
Pointer hover and click were checked only with synthetic events: in this setup
a USB mouse or tablet delivered no event to the loader (one USB I/O handle
appeared but no pointer protocol came from it), and why was not established.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EVRhxi9zpdW2uDUN4FVcao
2026-10-26 15:31:39 +01:00

80 lines
3.3 KiB
Python

#!/usr/bin/env python3
"""Rasterise Niveus's interface face into a header the boot manager can draw.
The loader cannot carry a TrueType rasteriser, and the answer to that has been
the same since include/gfx/font.h: do the work at build time and commit the
result. This takes Instrument Sans (SIL OFL, see docs/Website/niveus/fonts/)
at three sizes and writes 8-bit coverage bitmaps, one per printable ASCII
character, with the metrics needed to lay them out proportionally.
python3 tools/python/mknvfont.py docs/Website/niveus/fonts/static include/gfx/nvfont.h
Needs Pillow; nothing else, and nothing at all at build time of the OS.
"""
import sys, os
from PIL import ImageFont
# Each size twice: x1 for ordinary screens and x2 for the ones where a 15 px
# face would be a speck. Rendering at the real size beats doubling pixels.
SIZES = []
for _scale in (1, 2):
SIZES += [
("body%d" % _scale, "InstrumentSans-Medium.ttf", 15 * _scale),
("small%d" % _scale, "InstrumentSans-Regular.ttf", 12 * _scale),
("title%d" % _scale, "InstrumentSans-SemiBold.ttf", 24 * _scale),
]
FIRST, LAST = 32, 126
def render(path, px):
f = ImageFont.truetype(path, px)
asc, desc = f.getmetrics()
glyphs, blob = [], bytearray()
for code in range(FIRST, LAST + 1):
ch = chr(code)
adv = int(round(f.getlength(ch) * 64)) # 26.6 fixed point: whole-pixel advances make text look cramped
mask, (ox, oy) = f.getmask2(ch, mode="L", anchor="ls")
w, h = mask.size
data = bytes(mask) if (w and h) else b""
glyphs.append((len(blob), w, h, ox, oy, adv))
blob += data
return asc, desc, glyphs, blob
def main():
src, out = sys.argv[1], sys.argv[2]
lines = [
"#pragma once",
"",
"// GENERATED by tools/python/mknvfont.py -- do not edit. Run `make nvfont`.",
"// source: docs/Website/niveus/fonts/static (Instrument Sans, SIL OFL 1.1)",
"//",
"// 8-bit coverage bitmaps for printable ASCII, proportionally spaced, used by",
"// the boot manager (boot/common/ui.c). Not included by the kernel.",
"",
"struct nvglyph { unsigned short off; unsigned char w, h; signed char ox, oy; unsigned short adv; /* 1/64 px */ };",
"struct nvfont { unsigned char ascent, descent; const struct nvglyph *g; const unsigned char *bits; };",
"#define NVFONT_FIRST %d" % FIRST,
"#define NVFONT_LAST %d" % LAST,
"",
]
for name, fn, px in SIZES:
asc, desc, glyphs, blob = render(os.path.join(src, fn), px)
lines.append("static const struct nvglyph nvg_%s[] = {" % name)
for (o, w, h, ox, oy, adv) in glyphs:
lines.append(" {%d,%d,%d,%d,%d,%d}," % (o, w, h, ox, oy, adv))
lines.append("};")
lines.append("static const unsigned char nvb_%s[] = {" % name)
for i in range(0, len(blob), 24):
lines.append(" " + ",".join(str(b) for b in blob[i:i + 24]) + ",")
if not blob:
lines.append(" 0")
lines.append("};")
lines.append("static const struct nvfont nvfont_%s = { %d, %d, nvg_%s, nvb_%s };" %
(name, asc, desc, name, name))
lines.append("")
open(out, "w").write("\n".join(lines) + "\n")
print("wrote", out)
main()