#!/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()