feat(bassdrive): add the drum & bass relay and a shared wordmark font
This commit is contained in:
1 parent
84ec3b08f6
commit
50a9a735f7
14 files changed
+1166
-79
No files matched your search
@@ -0,0 +1,91 @@
|
||||
"""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": ("###.", "#..#", "###.", "#..#", "#..#", "###."),
|
||||
"D": ("###.", "#..#", "#..#", "#..#", "#..#", "###."),
|
||||
"E": ("####", "#...", "###.", "#...", "#...", "####"),
|
||||
"F": ("####", "#...", "###.", "#...", "#...", "#..."),
|
||||
"I": ("###", ".#.", ".#.", ".#.", ".#.", "###"),
|
||||
"L": ("#...", "#...", "#...", "#...", "#...", "####"),
|
||||
# 5 wide on purpose: the M is the odd one out in the original wordmark.
|
||||
"M": ("#...#", "##.##", "#.#.#", "#.#.#", "#...#", "#...#"),
|
||||
"O": (".##.", "#..#", "#..#", "#..#", "#..#", ".##."),
|
||||
"R": ("###.", "#..#", "###.", "#.#.", "#..#", "#..#"),
|
||||
"S": (".###", "#...", "###.", "...#", "...#", "###."),
|
||||
"T": ("###", ".#.", ".#.", ".#.", ".#.", ".#."),
|
||||
"V": ("#..#", "#..#", "#..#", "#..#", ".##.", ".##."),
|
||||
}
|
||||
|
||||
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)
|
||||
Reference in new issue
Block a user