101 lines
4.0 KiB
Python
101 lines
4.0 KiB
Python
"""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)
|