"""A half-block wordmark font for the header hero. Every shipped catalogue draws its brand the same way: four-column capitals, six pixels tall, emitted as three rows of half blocks (``▀``/``▄``/``█``) — the same shape as the original SomaFM logo. Catalogues differ only in the word they spell and the ANSI colours they paint it with, so the font lives here and each provider's ``hero.py`` is one line of data. Glyphs are 6x4 bitmaps (``M`` is 6x5). Two pixel rows become one text row, so a wordmark is always three rows tall and the header keeps a fixed height. """ from __future__ import annotations from collections.abc import Sequence #: 6-row bitmap per capital; all rows of one glyph are the same width. GLYPHS: dict[str, tuple[str, ...]] = { "A": (".##.", "#..#", "####", "#..#", "#..#", "#..#"), "B": ("###.", "#..#", "###.", "#..#", "#..#", "###."), "C": (".##.", "#..#", "#...", "#...", "#..#", ".##."), "D": ("###.", "#..#", "#..#", "#..#", "#..#", "###."), "E": ("####", "#...", "###.", "#...", "#...", "####"), "F": ("####", "#...", "###.", "#...", "#...", "#..."), "G": (".##.", "#..#", "#...", "#.##", "#..#", ".##."), "H": ("#..#", "#..#", "####", "#..#", "#..#", "#..#"), "I": ("###", ".#.", ".#.", ".#.", ".#.", "###"), "K": ("#..#", "#.#.", "##..", "##..", "#.#.", "#..#"), "L": ("#...", "#...", "#...", "#...", "#...", "####"), # 5 wide on purpose: the M is the odd one out in the original wordmark. "M": ("#...#", "##.##", "#.#.#", "#.#.#", "#...#", "#...#"), "N": ("#..#", "##.#", "##.#", "#.##", "#..#", "#..#"), "O": (".##.", "#..#", "#..#", "#..#", "#..#", ".##."), "P": ("###.", "#..#", "###.", "#...", "#...", "#..."), "R": ("###.", "#..#", "###.", "#.#.", "#..#", "#..#"), "S": (".###", "#...", "###.", "...#", "...#", "###."), "T": ("###", ".#.", ".#.", ".#.", ".#.", ".#."), "U": ("#..#", "#..#", "#..#", "#..#", "#..#", ".##."), "V": ("#..#", "#..#", "#..#", "#..#", ".##.", ".##."), "W": ("#...#", "#...#", "#.#.#", "#.#.#", "##.##", "#...#"), "Y": ("#..#", "#..#", ".##.", "..#.", "..#.", "..#."), } ROWS = 3 _GAP = " " def _cell(top: str, bottom: str) -> str: if top == "#" and bottom == "#": return "█" if top == "#": return "▀" if bottom == "#": return "▄" return " " def _glyph_rows(char: str) -> tuple[str, ...]: bitmap = GLYPHS.get(char) if bitmap is None: raise KeyError(f"no wordmark glyph for {char!r}") return tuple( "".join(_cell(bitmap[row * 2][col], bitmap[row * 2 + 1][col]) for col in range(len(bitmap[0]))) for row in range(ROWS) ) def render(text: str, styles: Sequence[str | None] | None = None) -> tuple[str, ...]: """Spell ``text`` as three rows of half blocks. ``styles`` gives one rich-markup style per character (``None`` leaves it in the terminal's default colour). Contiguous runs of the same style share a markup span, so ``render("BASSDRIVE", ...)`` paints the first four letters and the last five separately. """ if styles is not None and len(styles) != len(text): raise ValueError("styles must provide one entry per character") glyphs = [_glyph_rows(char.upper()) for char in text] rows: list[str] = [] for row in range(ROWS): cells: list[tuple[str | None, str]] = [] for index, glyph in enumerate(glyphs): style = styles[index] if styles is not None else None if index: cells.append((style, _GAP)) cells.extend((style, char) for char in glyph[row]) while cells and cells[-1][1] == " ": cells.pop() segments: list[tuple[str | None, str]] = [] for style, char in cells: if segments and segments[-1][0] == style: segments[-1] = (style, segments[-1][1] + char) else: segments.append((style, char)) rows.append( "".join(f"[{style}]{body}[/]" if style else body for style, body in segments) ) return tuple(rows)