feat: big title

This commit is contained in:
2026-04-01 12:16:16 +02:00
parent 8d3245b7b1
commit 175de893aa
8 changed files with 317 additions and 1 deletions

209
backend/uframe/fonts.py Normal file
View File

@@ -0,0 +1,209 @@
"""µFrame Big Text Fonts — multi-line ASCII art letter definitions.
Each font is a dict mapping characters to a list of strings (one per line).
All characters in a font have the same height.
"""
from __future__ import annotations
# ---------------------------------------------------------------------------
# Block font — solid █ with box-drawing. Height: 6 lines.
# ---------------------------------------------------------------------------
_BLOCK: dict[str, list[str]] = {
"A": [" █████╗ ", "██╔══██╗", "███████║", "██╔══██║", "██║ ██║", "╚═╝ ╚═╝"],
"B": ["██████╗ ", "██╔══██╗", "██████╔╝", "██╔══██╗", "██████╔╝", "╚═════╝ "],
"C": [" ██████╗", "██╔════╝", "██║ ", "██║ ", "╚██████╗", " ╚═════╝"],
"D": ["██████╗ ", "██╔══██╗", "██║ ██║", "██║ ██║", "██████╔╝", "╚═════╝ "],
"E": ["███████╗", "██╔════╝", "█████╗ ", "██╔══╝ ", "███████╗", "╚══════╝"],
"F": ["███████╗", "██╔════╝", "█████╗ ", "██╔══╝ ", "██║ ", "╚═╝ "],
"G": [" ██████╗ ", "██╔════╝ ", "██║ ███╗", "██║ ██║", "╚██████╔╝", " ╚═════╝ "],
"H": ["██╗ ██╗", "██║ ██║", "███████║", "██╔══██║", "██║ ██║", "╚═╝ ╚═╝"],
"I": ["██╗", "██║", "██║", "██║", "██║", "╚═╝"],
"J": [" ██╗", " ██║", " ██║", "██ ██║", "╚█████╔╝", " ╚════╝ "],
"K": ["██╗ ██╗", "██║ ██╔╝", "█████╔╝ ", "██╔═██╗ ", "██║ ██╗", "╚═╝ ╚═╝"],
"L": ["██╗ ", "██║ ", "██║ ", "██║ ", "███████╗", "╚══════╝"],
"M": ["███╗ ███╗", "████╗ ████║", "██╔████╔██║", "██║╚██╔╝██║", "██║ ╚═╝ ██║", "╚═╝ ╚═╝"],
"N": ["███╗ ██╗", "████╗ ██║", "██╔██╗ ██║", "██║╚██╗██║", "██║ ╚████║", "╚═╝ ╚═══╝"],
"O": [" ██████╗ ", "██╔═══██╗", "██║ ██║", "██║ ██║", "╚██████╔╝", " ╚═════╝ "],
"P": ["██████╗ ", "██╔══██╗", "██████╔╝", "██╔═══╝ ", "██║ ", "╚═╝ "],
"Q": [" ██████╗ ", "██╔═══██╗", "██║ ██║", "██║▄▄ ██║", "╚██████╔╝", " ╚══▀▀═╝ "],
"R": ["██████╗ ", "██╔══██╗", "██████╔╝", "██╔══██╗", "██║ ██║", "╚═╝ ╚═╝"],
"S": ["███████╗", "██╔════╝", "███████╗", "╚════██║", "███████║", "╚══════╝"],
"T": ["████████╗", "╚══██╔══╝", " ██║ ", " ██║ ", " ██║ ", " ╚═╝ "],
"U": ["██╗ ██╗", "██║ ██║", "██║ ██║", "██║ ██║", "╚██████╔╝", " ╚═════╝ "],
"V": ["██╗ ██╗", "██║ ██║", "██║ ██║", "╚██╗ ██╔╝", " ╚████╔╝ ", " ╚═══╝ "],
"W": ["██╗ ██╗", "██║ ██║", "██║ █╗ ██║", "██║███╗██║", "╚███╔███╔╝", " ╚══╝╚══╝ "],
"X": ["██╗ ██╗", "╚██╗██╔╝", " ╚███╔╝ ", " ██╔██╗ ", "██╔╝ ██╗", "╚═╝ ╚═╝"],
"Y": ["██╗ ██╗", "╚██╗ ██╔╝", " ╚████╔╝ ", " ╚██╔╝ ", " ██║ ", " ╚═╝ "],
"Z": ["███████╗", "╚══███╔╝", " ███╔╝ ", " ███╔╝ ", "███████╗", "╚══════╝"],
"0": [" ██████╗ ", "██╔═══██╗", "██║ ██║", "██║ ██║", "╚██████╔╝", " ╚═════╝ "],
"1": [" ██╗", "███║", "╚██║", " ██║", " ██║", " ╚═╝"],
"2": ["██████╗ ", "╚════██╗", " █████╔╝", "██╔═══╝ ", "███████╗", "╚══════╝"],
"3": ["██████╗ ", "╚════██╗", " █████╔╝", " ╚═══██╗", "██████╔╝", "╚═════╝ "],
"4": ["██╗ ██╗", "██║ ██║", "███████║", "╚════██║", " ██║", " ╚═╝"],
"5": ["███████╗", "██╔════╝", "███████╗", "╚════██║", "███████║", "╚══════╝"],
"6": [" ██████╗", "██╔════╝", "██████╗ ", "██╔══██╗", "╚█████╔╝", " ╚════╝ "],
"7": ["███████╗", "╚════██║", " ██╔╝", " ██╔╝ ", " ██║ ", " ╚═╝ "],
"8": [" █████╗ ", "██╔══██╗", "╚█████╔╝", "██╔══██╗", "╚█████╔╝", " ╚════╝ "],
"9": [" █████╗ ", "██╔══██╗", "╚██████║", " ╚═══██║", " █████╔╝", " ╚════╝ "],
"-": [" ", " ", "██████╗ ", "╚═════╝ ", " ", " "],
" ": [" ", " ", " ", " ", " ", " "],
".": [" ", " ", " ", " ", "██╗", "╚═╝"],
"!": ["██╗", "██║", "██║", "╚═╝", "██╗", "╚═╝"],
}
# ---------------------------------------------------------------------------
# Thin font — light single-stroke. Height: 3 lines.
# ---------------------------------------------------------------------------
_THIN: dict[str, list[str]] = {
"A": ["┌─┐", "├─┤", "┘ └"],
"B": ["┬─┐", "├─┤", "┴─┘"],
"C": ["┌─ ", "", "└─ "],
"D": ["┬─┐", "│ │", "┴─┘"],
"E": ["┬──", "├─ ", "┴──"],
"F": ["┬──", "├─ ", ""],
"G": ["┌─ ", "│ ┐", "└─┘"],
"H": ["┐ ┌", "├─┤", "┘ └"],
"I": ["", "", ""],
"J": ["", "", "└─┘"],
"K": ["┐ ┌", "├┬┘", "┘└ "],
"L": ["", "", "└──"],
"M": ["┌┬┐", "│││", "┘ └"],
"N": ["┌┐ ", "│└┐", "┘ └"],
"O": ["┌─┐", "│ │", "└─┘"],
"P": ["┌─┐", "├─┘", ""],
"Q": ["┌─┐", "│ │", "└─┤"],
"R": ["┌─┐", "├─┤", "┘ └"],
"S": ["┌─ ", "└─┐", " ─┘"],
"T": ["┬─┬", "", ""],
"U": ["┐ ┌", "│ │", "└─┘"],
"V": ["┐ ┌", "│ │", "└┬┘"],
"W": ["┐ ┌", "│││", "└┴┘"],
"X": ["", " ", ""],
"Y": ["┐ ┌", "└┬┘", ""],
"Z": ["──┐", " ", "└──"],
"0": ["┌─┐", "│ │", "└─┘"],
"1": ["", "", ""],
"2": ["─┐", "┌┘", "└─"],
"3": ["─┐", "", "─┘"],
"4": ["┐ ┐", "└─┤", ""],
"5": ["┌─", "└┐", "─┘"],
"6": ["┌─", "├┐", "└┘"],
"7": ["──┐", "", ""],
"8": ["┌┐", "├┤", "└┘"],
"9": ["┌┐", "└┤", "─┘"],
"-": [" ", "── ", " "],
" ": [" ", " ", " "],
".": [" ", " ", "·"],
"!": ["", " ", "·"],
}
# ---------------------------------------------------------------------------
# Pixel font — retro bitmap. Height: 3 lines.
# ---------------------------------------------------------------------------
_PIXEL: dict[str, list[str]] = {
"A": ["▀▀▀▄", "█▀▀█", "▀ ▀"],
"B": ["▀▀▀▄", "█▀▀▄", "▀▀▀ "],
"C": ["▄▀▀▀", "", "▀▀▀ "],
"D": ["▀▀▀▄", "█ █", "▀▀▀ "],
"E": ["▀▀▀ ", "█▀ ", "▀▀▀ "],
"F": ["▀▀▀ ", "█▀ ", ""],
"G": ["▄▀▀ ", "█ ▀█", "▀▀▀ "],
"H": ["▀ ▀ ", "█▀▀█", "▀ ▀"],
"I": ["", "", ""],
"J": ["", "", "▀▀ "],
"K": ["▀ ▄ ", "█▀▄ ", "▀ ▀"],
"L": ["", "", "▀▀▀ "],
"M": ["▀▄▄▀", "█▀▀█", "▀ ▀"],
"N": ["▀▄ ▀", "█ ▀█", "▀ ▀"],
"O": ["▄▀▀▄", "█ █", "▀▀▀ "],
"P": ["▀▀▀▄", "█▀▀ ", ""],
"Q": ["▄▀▀▄", "█ █", "▀▀▀▄"],
"R": ["▀▀▀▄", "█▀▀▄", "▀ ▀"],
"S": ["▄▀▀ ", "▀▀▀▄", " ▀▀ "],
"T": ["▀▀▀▀", "", ""],
"U": ["▀ ▀", "█ █", "▀▀▀ "],
"V": ["▀ ▀", "█ █", " ▀▀ "],
"W": ["▀ ▀", "█▄▄█", "▀▀▀▀"],
"X": ["▀ ▀", " ▀▀ ", "▀ ▀"],
"Y": ["▀ ▀", " ▀▀ ", ""],
"Z": ["▀▀▀▀", " ▄▀ ", "▀▀▀▀"],
"0": ["▄▀▀▄", "█ █", "▀▀▀ "],
"1": ["", "", ""],
"2": ["▀▀▄", " ▄▀", "▀▀▀"],
"3": ["▀▀▄", " ▀▄", "▀▀ "],
"4": ["▀ ▀", "▀▀█", ""],
"5": ["▀▀▀", "▀▀▄", "▀▀ "],
"6": ["▄▀▀", "█▀▄", "▀▀ "],
"7": ["▀▀▀", "", ""],
"8": ["▄▀▄", "█▀█", "▀▀ "],
"9": ["▄▀▄", "▀▀█", "▀▀ "],
"-": [" ", " ▀▀ ", " "],
" ": [" ", " ", " "],
".": [" ", " ", ""],
"!": ["", " ", ""],
}
# ---------------------------------------------------------------------------
# Font registry
# ---------------------------------------------------------------------------
FONTS: dict[str, dict[str, list[str]]] = {
"block": _BLOCK,
"thin": _THIN,
"pixel": _PIXEL,
}
FONT_HEIGHTS: dict[str, int] = {
"block": 6,
"thin": 3,
"pixel": 3,
}
def render_big_text(text: str, font_name: str = "block", kerning: int = 1) -> list[str]:
"""Render text as multi-line ASCII art using the specified font.
Args:
text: the string to render (uppercase recommended for block/pixel)
font_name: "block", "thin", or "pixel"
kerning: space between characters (0=tight, 1=normal, 2=wide)
Returns:
List of strings, one per line of the rendered text.
"""
font = FONTS.get(font_name, _BLOCK)
height = FONT_HEIGHTS.get(font_name, 6)
text_upper = text.upper()
# Build each line by concatenating character columns
lines: list[str] = ["" for _ in range(height)]
spacer = " " * kerning
for i, ch in enumerate(text_upper):
glyph = font.get(ch, font.get("?", [" " * 3] * height))
for row in range(height):
if row < len(glyph):
lines[row] += glyph[row]
else:
lines[row] += " " * (len(glyph[0]) if glyph else 3)
if i < len(text_upper) - 1:
lines[row] += spacer
return lines
def get_text_width(text: str, font_name: str = "block", kerning: int = 1) -> int:
"""Calculate the rendered width of big text without rendering it."""
font = FONTS.get(font_name, _BLOCK)
text_upper = text.upper()
width = 0
for i, ch in enumerate(text_upper):
glyph = font.get(ch, font.get("?", [" " * 3]))
width += len(glyph[0]) if glyph else 3
if i < len(text_upper) - 1:
width += kerning
return width