Files
MeshTerm/meshterm/ui/fontset.py
T
Jean-Pierre MartineauandClaude Opus 5.5 50ea38faea The console host is meshterm.emulator
"Host" already meant two things here: a TCP companion's network host,
and battery="host" for this machine itself. And with a PicoCalc preview
coming, the package is less a console host than an emulator of a
handheld's display: its panel, grid, font, colours and keys. So it is
meshterm.emulator, Platform.console_host is own_display, its desktop
window is window.py (EmulatorWindow) and its device front end
framebuffer.py, since "sim" and "device" now mean the radio simulator and
the device being emulated. MESHTERM_HOST_FONTS is MESHTERM_EMULATOR_FONTS.

The package's own description opens on what it is not: nothing runs the
handheld's processor or operating system; it mimics the display's
constraints so the window obeys the device's.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-04 14:03:55 -04:00

130 lines
7.6 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# SPDX-License-Identifier: Apache-2.0
"""The handhelds' glyph inventories — the glyph contract.
The PicoCalc panel draws through the kernel console with one 512-glyph PSF font
(``meshterm.psf.gz``, built by ``scripts/picocalc-lyra/calculinux-console-font-6x12.sh``): the stock
Terminus 6×12 base (a Cyrillic-coverage table — note there are **no accented Latin
letters**), the full braille block, MeshTerm's own marks drawn into donor slots, and a
few aliases. A character outside this set renders as a blank on the panel, so *nothing
outside it may ever be emitted* on the picocalc-lyra platform — the render boundary folds
text down to it (:func:`meshterm.ui.theme.fold_text`), and the dual-platform gallery
asserts the fold held.
This table is the **single source of truth** for that contract, shared by the runtime
fold and the tests. It mirrors the font actually installed on the device: the base
inventory was dumped live from the PSF's Unicode table (2026-08-01), minus the donor
codepoints the build script now repurposes, plus the marks it draws in their place.
If the build script's ``MARKS``/``DONORS``/``ALIASES`` change, this table must move in
the same commit — and vice versa.
The Cardputer Zero has a contract of its own, :data:`CARDPUTER_ZERO_CODEPOINTS`: what its console
host draws, which no kernel caps. :data:`FONTS` names both, for ``Platform.font``.
"""
from __future__ import annotations
#: Inclusive ``(first, last)`` codepoint ranges present in the font. 519 codepoints over
#: 512 glyphs — aliases (the rounded corners onto the square ones, ``⋯`` onto ``…``) and
#: shared slots give a few glyphs more than one codepoint. Verified against a live table
#: dump of the installed font (2026-08-01, post-P3 build).
# fmt: off
FONT_RANGES: tuple[tuple[int, int], ...] = (
(0x0020, 0x007F), (0x00A0, 0x00A0), (0x00A7, 0x00A7), (0x00A9, 0x00A9),
(0x00B0, 0x00B0), (0x00B2, 0x00B2), (0x00B6, 0x00B7), (0x03C0, 0x03C0),
(0x0401, 0x0401),
(0x0404, 0x0404), (0x0406, 0x0407), (0x0410, 0x044F), (0x0451, 0x0451),
(0x0454, 0x0454), (0x0456, 0x0457), (0x0490, 0x0491), (0x2014, 0x2014),
(0x2022, 0x2022), (0x2026, 0x2026), (0x2190, 0x2195), (0x21A8, 0x21A8),
(0x21BB, 0x21BB), (0x21E7, 0x21E7), (0x221A, 0x221A), (0x221F, 0x221F),
(0x2248, 0x2248), (0x2264, 0x2265), (0x22EF, 0x22EF), (0x2302, 0x2302),
(0x2316, 0x2316), (0x232B, 0x232B), (0x2500, 0x2500), (0x2502, 0x2502),
(0x250C, 0x250C), (0x2510, 0x2510), (0x2514, 0x2514), (0x2518, 0x2518),
(0x251C, 0x251C), (0x2524, 0x2524), (0x252C, 0x252C), (0x2534, 0x2534),
(0x253C, 0x253C), (0x2550, 0x2551), (0x2554, 0x2554), (0x2557, 0x2557),
(0x2559, 0x255B),
(0x255D, 0x2561), (0x2563, 0x2563), (0x2566, 0x256A), (0x256C, 0x2570),
(0x2580, 0x2580), (0x2584, 0x2584), (0x2588, 0x2588), (0x258C, 0x258C),
(0x2590, 0x2593), (0x25A0, 0x25A0), (0x25AC, 0x25AC), (0x25B2, 0x25B2),
(0x25B6, 0x25B6), (0x25B8, 0x25B8), (0x25BA, 0x25BA), (0x25BC, 0x25BC),
(0x25C0, 0x25C0), (0x25C4, 0x25C4), (0x25C9, 0x25C9), (0x25CB, 0x25CB),
(0x25CF, 0x25CF), (0x25F7, 0x25F7), (0x2605, 0x2605), (0x263C, 0x263C),
(0x2699, 0x2699), (0x26A0, 0x26A0), (0x26BF, 0x26BF), (0x2713, 0x2713),
(0x2717, 0x2717), (0x276F, 0x276F), (0x2800, 0x28FF), (0xFFFD, 0xFFFD),
)
# fmt: on
#: Every codepoint the console font can draw, as one frozen set (built once at import;
#: 520 members, so the set is small and the per-character membership test is a dict hit).
FONT_CODEPOINTS: frozenset[int] = frozenset(
cp for first, last in FONT_RANGES for cp in range(first, last + 1)
)
def in_font(char: str) -> bool:
"""Whether the console font has a glyph for ``char``."""
return ord(char) in FONT_CODEPOINTS
#: The Cardputer Zero's inventory: what its emulator draws (:mod:`meshterm.emulator.font`)
#: — Terminus 4.49's 12-pixel face, every one of its 1,356 glyphs, with MeshTerm's own
#: marks drawn over it and ``⋯`` aliased onto the ellipsis, as on the PicoCalc. No 512-glyph
#: cap here, so braille, the powerline chevrons and accented Latin are all simply present.
#: Generated from ``ter-u12n.bdf`` plus the host's ``MARKS`` and ``ALIASES``; if either
#: changes, this table moves in the same commit (``tests/test_emulator.py`` checks the
#: marks, and the font itself wherever it is installed).
# fmt: off
CARDPUTER_RANGES: tuple[tuple[int, int], ...] = (
(0x0020, 0x007E), (0x00A0, 0x017F), (0x0186, 0x0186), (0x018E, 0x0190),
(0x0192, 0x0192), (0x019D, 0x019E), (0x01B5, 0x01B7), (0x01CD, 0x01D4),
(0x01E2, 0x01F0), (0x01F4, 0x01F5), (0x01FC, 0x01FF), (0x0218, 0x021B),
(0x0232, 0x0233), (0x0237, 0x0237), (0x0254, 0x0254), (0x0258, 0x0259),
(0x025B, 0x025B), (0x0272, 0x0272), (0x0292, 0x0292), (0x02BB, 0x02BD),
(0x02C6, 0x02C7), (0x02D8, 0x02D9), (0x02DB, 0x02DD), (0x0300, 0x0308),
(0x030A, 0x030C), (0x0329, 0x0329), (0x0384, 0x038A), (0x038C, 0x038C),
(0x038E, 0x03A1), (0x03A3, 0x03CE), (0x03D1, 0x03D1), (0x03D5, 0x03D5),
(0x03F0, 0x03F6), (0x0400, 0x045F), (0x0462, 0x0463), (0x046A, 0x046B),
(0x0490, 0x049D), (0x04A0, 0x04A5), (0x04AA, 0x04AB), (0x04AE, 0x04B3),
(0x04B6, 0x04BB), (0x04C0, 0x04C2), (0x04CF, 0x04DF), (0x04E2, 0x04F5),
(0x04F8, 0x04F9), (0x05D0, 0x05EA), (0x1E0C, 0x1E0D), (0x1E34, 0x1E37),
(0x1E40, 0x1E47), (0x1E6C, 0x1E6D), (0x1EB8, 0x1EB9), (0x1EBC, 0x1EBD),
(0x1ECA, 0x1ECD), (0x1EE4, 0x1EE5), (0x1EF8, 0x1EF9), (0x2000, 0x2022),
(0x2026, 0x2026), (0x2030, 0x2030), (0x2032, 0x2033), (0x2039, 0x203A),
(0x203C, 0x203C), (0x203E, 0x203E), (0x2070, 0x2071), (0x2074, 0x208E),
(0x2090, 0x2098), (0x209A, 0x209A), (0x20A7, 0x20A7), (0x20AA, 0x20AA),
(0x20AC, 0x20AC), (0x20AE, 0x20AE), (0x2102, 0x2102), (0x210E, 0x210F),
(0x2115, 0x2116), (0x211A, 0x211A), (0x211D, 0x211D), (0x2122, 0x2122),
(0x2124, 0x2124), (0x2126, 0x2126), (0x2135, 0x2135), (0x2190, 0x2195),
(0x21A4, 0x21A4), (0x21A6, 0x21A6), (0x21A8, 0x21A8), (0x21B2, 0x21B3),
(0x21B5, 0x21B5), (0x21BB, 0x21BB), (0x21CB, 0x21CC), (0x21D0, 0x21D5),
(0x21E7, 0x21E7), (0x2200, 0x2200), (0x2203, 0x220D), (0x2212, 0x2216),
(0x2219, 0x221A), (0x221E, 0x221F), (0x2225, 0x2225), (0x2227, 0x222A),
(0x2248, 0x2248), (0x2260, 0x2261), (0x2264, 0x2265), (0x226A, 0x226B),
(0x2282, 0x2283), (0x2286, 0x2287), (0x22A5, 0x22A5), (0x22C2, 0x22C3),
(0x22EF, 0x22EF), (0x2300, 0x2300), (0x2302, 0x2302), (0x2308, 0x230B),
(0x2310, 0x2310), (0x2316, 0x2316), (0x2319, 0x2319), (0x2320, 0x2321),
(0x232B, 0x232B), (0x239B, 0x23A9), (0x23AB, 0x23AF), (0x23BA, 0x23BD),
(0x23D0, 0x23D0), (0x2409, 0x240D), (0x2424, 0x2424), (0x2500, 0x2503),
(0x2508, 0x254B), (0x2550, 0x2593), (0x2596, 0x25A0), (0x25AC, 0x25AC),
(0x25AE, 0x25AE), (0x25B2, 0x25B2), (0x25B6, 0x25B6), (0x25B8, 0x25B8),
(0x25BA, 0x25BA), (0x25BC, 0x25BC), (0x25C0, 0x25C0), (0x25C4, 0x25C4),
(0x25C6, 0x25C6), (0x25C9, 0x25CB), (0x25CF, 0x25CF), (0x25D8, 0x25D9),
(0x25F7, 0x25F7), (0x2605, 0x2605), (0x263A, 0x263C), (0x2640, 0x2640),
(0x2642, 0x2642), (0x2660, 0x2660), (0x2663, 0x2663), (0x2665, 0x2666),
(0x266A, 0x266B), (0x2699, 0x2699), (0x26A0, 0x26A0), (0x26BF, 0x26BF),
(0x2713, 0x2714), (0x2717, 0x2718), (0x276F, 0x276F), (0x27E8, 0x27EB),
(0x2800, 0x28FF), (0x2E2C, 0x2E2C), (0xE0A0, 0xE0A2), (0xE0B0, 0xE0B3),
(0xF6BE, 0xF6BE), (0xFFFD, 0xFFFD),
)
# fmt: on
#: The Cardputer's inventory as one frozen set.
CARDPUTER_ZERO_CODEPOINTS: frozenset[int] = frozenset(
cp for first, last in CARDPUTER_RANGES for cp in range(first, last + 1)
)
#: Every handheld's glyph inventory, by the name ``Platform.font`` gives it.
FONTS: dict[str, frozenset[int]] = {
"picocalc-lyra": FONT_CODEPOINTS,
"cardputer-zero": CARDPUTER_ZERO_CODEPOINTS,
}