"""Angular rune and hieroglyph bitmaps for 9-row portal panels.""" from __future__ import annotations from typing import TYPE_CHECKING, Any, Sequence if TYPE_CHECKING: from leds.pattern import Param Glyph = tuple[str, ...] RGB = tuple[int, int, int] LIT = frozenset("#Xx1") # Elder Futhark, carved from straight strokes on 5×7. FUTHARK: tuple[Glyph, ...] = ( ("#...#", "#..#.", "###..", "#..#.", "#...#", "#....", "#...."), # fehu ("#...#", "#...#", "#...#", "#...#", "##..#", "#.#.#", "#..##"), # uruz ("#....", "#.##.", "##..#", "#.##.", "#....", "#....", "#...."), # thurisaz ("#...#", "#..#.", "#.#..", "##...", "#.#..", "#..#.", "#...#"), # ansuz ("####.", "#...#", "#...#", "####.", "#.#..", "#..#.", "#...#"), # raidho ("#...#", ".#.#.", "..#..", ".#.#.", "#...#", ".....", "....."), # kenaz ("#...#", ".#.#.", "..#..", ".#.#.", "#...#", ".#.#.", "#...#"), # gebo ("####.", "#...#", "#...#", "####.", "#....", "#....", "#...."), # wunjo ("#...#", "#.#.#", "#.#.#", "#####", "#.#.#", "#.#.#", "#...#"), # hagalaz ("#...#", ".#..#", "..#.#", "...##", "..#.#", ".#..#", "#...#"), # naudiz ("..#..", "..#..", "..#..", "..#..", "..#..", "..#..", "..#.."), # isa ("....#", "...#.", "#.#.#", ".#.#.", "#.#.#", ".#...", "#...."), # jera ("....#", "#..#.", "#.#..", "##...", "#.#..", "#..#.", "#...."), # eihwaz (".####", "#....", "#....", "#....", "#....", "#....", ".####"), # pertho ("#...#", ".#.#.", "..#..", "..#..", "..#..", "..#..", "..#.."), # algiz ("...##", "..#..", ".#...", "#....", ".#...", "..#..", "...##"), # sowilo ("#####", "..#..", "..#..", "..#..", "..#..", "..#..", "..#.."), # tiwaz ("####.", "#...#", "#...#", "####.", "#...#", "#...#", "####."), # berkano ("#...#", "#...#", "#.#.#", "##.##", "#.#.#", "#...#", "#...#"), # ehwaz ("#...#", "##.##", "#.#.#", "#.#.#", "#.#.#", "##.##", "#...#"), # mannaz ("#....", "#....", "#....", "#....", "#..#.", "#...#", ".##.."), # laguz ("..#..", ".#.#.", "#...#", ".#.#.", "..#..", ".....", "....."), # ingwaz ("#...#", "##.##", "#.#.#", "#.#.#", "#.#.#", "##.##", "#...#"), # dagaz ("..#..", ".#.#.", "#...#", "#...#", ".#.#.", "#...#", "#...#"), # othala ) # Egyptian-style pictograms, 7×7, with one pixel of vertical padding on 9 rows. HIEROGLYPHS: tuple[Glyph, ...] = ( ("..###..", "..#.#..", "..###..", "...#...", ".#####.", "...#...", "...#..."), # ankh (".......", ".#####.", "#..#..#", "#.#####", "#..#..#", ".#####.", "......."), # eye ("#.....#", ".#...#.", ".#####.", "##.###.", ".#.#...", ".#..#..", "#......"), # falcon ("...#...", "..#.#..", "..#.#..", "...#...", "...#...", "...#...", "..###.."), # reed (".#...#.", "##.#.##", ".#####.", "#.#.#.#", ".#####.", "..###..", ".#...#."), # owl (".......", ".#..#..", "#.##.##", ".......", "..#..#.", ".##.##.", "......."), # water ("...#...", ".#.#.#.", "..###..", "#######", "..###..", ".#.#.#.", "...#..."), # sun (".###...", "#...#..", "...#...", "..#....", ".#...#.", "#.....#", ".#####."), # snake ("...#...", "..#.#..", "..#.#..", ".#...#.", ".#...#.", "#######", "#.....#"), # pyramid ("#.....#", ".##.##.", "#.#####", "#######", "#.#####", ".##.##.", "#.....#"), # scarab (".......", ".......", "#.....#", ".#...#.", "..###..", ".......", "......."), # basket ("...#...", "..###..", ".#.#.#.", "...#...", "...#...", "...#...", "..#.#.."), # staff ) # Ogham-style stem with side notches (5×7). OGHAM: tuple[Glyph, ...] = ( ("..#..", "..#..", "..#..", "..#..", "..#..", "..#..", "..#.."), # ailm ("#.#..", "..#..", "..#..", "..#..", "..#..", "..#..", "..#.."), ("#.#.#", "..#..", "..#..", "..#..", "..#..", "..#..", "..#.."), (".#.#.", "..#..", "..#..", "..#..", "..#..", "..#..", "..#.."), ("..#.#", "..#..", "..#..", "..#..", "..#..", "..#..", "..#.."), ("..#..", "#.#..", "..#..", "..#..", "..#..", "..#..", "..#.."), ("..#..", "#.#.#", "..#..", "..#..", "..#..", "..#..", "..#.."), ("..#..", ".#.#.", "..#..", "..#..", "..#..", "..#..", "..#.."), ("..#..", "..#.#", "..#..", "..#..", "..#..", "..#..", "..#.."), ("..#..", "..#..", "#.#..", "..#..", "..#..", "..#..", "..#.."), ("..#..", "..#..", "#.#.#", "..#..", "..#..", "..#..", "..#.."), ("..#..", "..#..", ".#.#.", "..#..", "..#..", "..#..", "..#.."), ("..#..", "..#..", "..#.#", "..#..", "..#..", "..#..", "..#.."), ("#####", "..#..", "..#..", "..#..", "..#..", "..#..", "..#.."), ("..#..", "#####", "..#..", "..#..", "..#..", "..#..", "..#.."), ("..#..", "..#..", "#####", "..#..", "..#..", "..#..", "..#.."), ("#...#", ".#.#.", "..#..", ".#.#.", "#...#", ".....", "....."), (".....", "#...#", ".#.#.", "..#..", ".#.#.", "#...#", "....."), (".###.", "#...#", "....#", "..##.", "....#", "#...#", ".###."), ("##.##", "#.#.#", "#####", "#.#.#", "##.##", ".....", "....."), ) # Wedge / nailhead marks (6×7). CUNEIFORM: tuple[Glyph, ...] = ( ("#.....", "##....", "###...", "####..", "###...", "##....", "#....."), (".....#", "....##", "...###", "..####", "...###", "....##", ".....#"), ("######", ".#....", "..#...", "...#..", "....#.", ".....#", "......"), ("######", "....#.", "...#..", "..#...", ".#....", "#.....", "......"), ("..##..", ".####.", "######", ".####.", "..##..", "...#..", "...#.."), ("#....#", ".#..#.", "..##..", "..##..", ".#..#.", "#....#", "......"), ("###...", "#.#...", "###...", "..#...", "..#...", "..###.", "......"), ("...###", "...#.#", "...###", "...#..", "...#..", ".###..", "......"), (".####.", "#....#", "#.##.#", "#....#", "#....#", ".####.", "......"), ("#.#.#.", ".###..", "#.#.#.", ".###..", "#.#.#.", "......", "......"), ("##..##", "##..##", "..##..", "..##..", "##..##", "##..##", "......"), (".#..#.", "#.##.#", ".#..#.", "#.##.#", ".#..#.", "......", "......"), ("#.....", "#.#...", "#.#.#.", "######", "#.#.#.", "#.#...", "#....."), ("#####.", "#...#.", "#...#.", "#####.", "#.#...", "#..#..", "#...#."), ("..#...", ".###..", "#.#.#.", "..#...", ".#.#..", "#...#.", "......"), ("#...#.", ".#.#..", "..#...", ".#.#..", "#...#.", ".#.#..", "..#..."), ) # Alchemical / planetary marks (7×7). ALCHEMY: tuple[Glyph, ...] = ( ("...#...", "..###..", ".#...#.", "#.....#", ".#...#.", "..###..", "...#..."), # sun (".......", ".#####.", "#.....#", "#.....#", "#.....#", ".#####.", "...#..."), # moon ("...#...", "..#.#..", ".#...#.", "#..#..#", ".#...#.", "..###..", "...#..."), # mercury ("#.....#", ".#...#.", "..#.#..", "...#...", "..#.#..", ".#...#.", "#.....#"), # antimony ("...#...", "..###..", ".#####.", "...#...", "...#...", "..###..", ".#####."), # sulfur ("#######", "#.....#", "#.###.#", "#.#.#.#", "#.###.#", "#.....#", "#######"), # salt ("..###..", ".#...#.", "#.....#", "#..#..#", ".#...#.", "..#.#..", "...#..."), # venus ("...#...", "..###..", ".#.#.#.", "#..#..#", "...#...", "...#...", "..###.."), # mars (".......", ".#.#.#.", "#.#.#.#", ".#####.", "#.#.#.#", ".#.#.#.", "......."), # jupiter ("#.....#", ".#.#.#.", "..###..", ".#####.", "..###..", ".#.#.#.", "#.....#"), # saturn ("...#...", ".#####.", "#..#..#", "...#...", ".#####.", "...#...", "...#..."), # earth ("..###..", ".#...#.", "#.#.#.#", "#.....#", "#.#.#.#", ".#...#.", "..###.."), # spirit (".#...#.", "##.#.##", ".#####.", "..###..", ".#####.", "##.#.##", ".#...#."), # fire (".......", "#.....#", ".#...#.", "..###..", ".#...#.", "#.....#", "......."), # air ("#######", ".#...#.", "..###..", "...#...", "..###..", ".#...#.", "#######"), # water ("...#...", "..#.#..", ".#...#.", "#.....#", ".#####.", "...#...", "..###.."), # copper ) # Geometric occult sigils (7×7). SIGILS: tuple[Glyph, ...] = ( ("...#...", "..#.#..", ".#...#.", "#.....#", ".#...#.", "..#.#..", "...#..."), ("#######", "#.....#", "#.#.#.#", "#.....#", "#.#.#.#", "#.....#", "#######"), ("#.....#", ".#.#.#.", "..#.#..", ".#####.", "..#.#..", ".#.#.#.", "#.....#"), ("..###..", ".#...#.", "#..#..#", "#.#.#.#", "#..#..#", ".#...#.", "..###.."), ("#.#.#.#", ".#.#.#.", "#.#.#.#", ".#.#.#.", "#.#.#.#", ".#.#.#.", "#.#.#.#"), ("...#...", ".#####.", "#.....#", ".#####.", "#.....#", ".#####.", "...#..."), (".#####.", "#.....#", "#.#.#.#", "#.#.#.#", "#.#.#.#", "#.....#", ".#####."), ("#.....#", "##...##", "#.#.#.#", "#..#..#", "#.#.#.#", "##...##", "#.....#"), ("..#.#..", ".#.#.#.", "#.#.#.#", ".#####.", "#.#.#.#", ".#.#.#.", "..#.#.."), ("#####.#", "#...#.#", "#.###.#", "#.#...#", "#.#####", ".......", "......."), (".#.###.", "#.#...#", ".#.##.#", "...#.#.", "###.#..", ".......", "......."), ("#..#..#", ".##.##.", "#..#..#", ".##.##.", "#..#..#", ".##.##.", "#..#..#"), ("...#...", "..###..", ".##.##.", "##...##", ".##.##.", "..###..", "...#..."), ("##...##", "#.#.#.#", ".##.##.", "..###..", ".##.##.", "#.#.#.#", "##...##"), ("#.#.#.#", ".......", "#.#.#.#", ".......", "#.#.#.#", ".......", "#.#.#.#"), ("#######", ".......", "#######", ".......", "#######", ".......", "#######"), ) def _bitmap_rows_to_glyph(rows: tuple[int, ...], *, width: int = 5) -> Glyph: """Convert 5×7 bit rows (bit width-1 = left) into ``#``.`` strings.""" out: list[str] = [] for row in rows: cells: list[str] = [] for col in range(width - 1, -1, -1): cells.append("#" if (int(row) >> col) & 1 else ".") out.append("".join(cells)) return tuple(out) # Latin capitals from the shared 5×7 LED text font. def _load_latin() -> tuple[Glyph, ...]: from leds.text import _FONT return tuple(_bitmap_rows_to_glyph(_FONT[ch]) for ch in "ABCDEFGHIJKLMNOPQRSTUVWXYZ") LATIN: tuple[Glyph, ...] = _load_latin() FONT_NAMES: tuple[str, ...] = ( "hieroglyphs", "runes", "ogham", "cuneiform", "alchemy", "sigils", "latin", "mixed", "ancient", ) FONT_SETS: dict[str, tuple[Glyph, ...]] = { "hieroglyphs": HIEROGLYPHS, "runes": FUTHARK, "ogham": OGHAM, "cuneiform": CUNEIFORM, "alchemy": ALCHEMY, "sigils": SIGILS, "latin": LATIN, "mixed": FUTHARK + HIEROGLYPHS, "ancient": FUTHARK + HIEROGLYPHS + OGHAM + CUNEIFORM + ALCHEMY + SIGILS, } def font_param(default: str = "hieroglyphs") -> Param: # Lazy import avoids circular import with leds.pattern. from leds.pattern import Param choice = default if default in FONT_NAMES else "hieroglyphs" return Param(choice, kind="choice", choices=FONT_NAMES, label="Font") def resolve_glyphs(params: dict[str, Any] | None, *, default: str = "hieroglyphs") -> tuple[Glyph, ...]: """Return the glyph set selected on the preset (shared Font control).""" extras = (params or {}).get("extras") extras = extras if isinstance(extras, dict) else {} name = str(extras.get("font") or default).strip().lower() if name not in FONT_SETS: name = default if default in FONT_SETS else "hieroglyphs" return FONT_SETS[name] def glyph_width(glyph: Glyph) -> int: return max((len(row) for row in glyph), default=0) def sequence_width(glyphs: Sequence[Glyph], gap: int = 1) -> int: if not glyphs: return 0 return sum(glyph_width(g) for g in glyphs) + gap * (len(glyphs) - 1) def vcenter(panel_h: int, glyph_h: int) -> int: return max(0, (panel_h - glyph_h) // 2) def blit_glyph(surface, origin_x: int, origin_y: int, glyph: Glyph, color: RGB) -> None: from leds.pattern import set_pixel, size w, h = size(surface) for gy, row in enumerate(glyph): y = origin_y + gy if y < 0 or y >= h: continue for gx, ch in enumerate(row): if ch not in LIT: continue x = origin_x + gx if 0 <= x < w: set_pixel(surface, x, y, color) def blit_sequence( surface, origin_x: int, origin_y: int, glyphs: Sequence[Glyph], color: RGB, *, gap: int = 1, wrap: int | None = None, ) -> None: cursor = origin_x for glyph in glyphs: blit_glyph(surface, cursor, origin_y, glyph, color) if wrap is not None: blit_glyph(surface, cursor + wrap, origin_y, glyph, color) cursor += glyph_width(glyph) + gap