mirror of
git://f0xx.org/ac/ac-be-hub
synced 2026-07-29 04:18:53 +03:00
initial
This commit is contained in:
366
assets/build_lcd_alphabet.py
Normal file
366
assets/build_lcd_alphabet.py
Normal file
@@ -0,0 +1,366 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Build full Overtime and Quartz 7-segment LCD SVG symbol sets."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from dataclasses import dataclass
|
||||
from pathlib import Path
|
||||
|
||||
# Segment indices: 0=top, 1=UL, 2=UR, 3=mid, 4=LL, 5=LR, 6=bottom
|
||||
PATTERNS: dict[str, list[int]] = {
|
||||
" ": [],
|
||||
"0": [0, 1, 2, 4, 5, 6],
|
||||
"1": [2, 5],
|
||||
"2": [0, 1, 3, 5, 6],
|
||||
"3": [0, 2, 3, 5, 6],
|
||||
"4": [1, 2, 3, 5],
|
||||
"5": [0, 2, 3, 5, 6],
|
||||
"6": [0, 2, 3, 4, 5, 6],
|
||||
"7": [0, 1, 2],
|
||||
"8": [0, 1, 2, 3, 4, 5, 6],
|
||||
"9": [0, 1, 2, 3, 5, 6],
|
||||
"A": [0, 1, 2, 3, 4, 5],
|
||||
"B": [0, 1, 2, 3, 5, 6],
|
||||
"C": [0, 1, 4, 6],
|
||||
"D": [1, 2, 3, 5, 6],
|
||||
"E": [0, 3, 4, 5, 6],
|
||||
"F": [0, 3, 4, 5],
|
||||
"G": [0, 2, 4, 5, 6],
|
||||
"H": [1, 2, 3, 4, 5],
|
||||
"I": [0, 6],
|
||||
"J": [1, 2, 4, 5, 6],
|
||||
"K": [1, 3, 4, 5],
|
||||
"L": [4, 5, 6],
|
||||
"M": [0, 1, 2, 4, 5],
|
||||
"N": [1, 2, 4, 5],
|
||||
"O": [0, 1, 2, 4, 5, 6],
|
||||
"P": [0, 1, 2, 3, 4],
|
||||
"Q": [0, 1, 2, 4, 5, 6],
|
||||
"R": [0, 1, 2, 3, 4, 5],
|
||||
"S": [0, 2, 3, 5, 6],
|
||||
"T": [0, 3, 6],
|
||||
"U": [1, 2, 4, 5, 6],
|
||||
"V": [1, 4, 5],
|
||||
"W": [1, 2, 4, 5, 6],
|
||||
"X": [1, 2, 4, 5],
|
||||
"Y": [1, 2, 3],
|
||||
"Z": [0, 1, 4, 6],
|
||||
}
|
||||
|
||||
EXTRA_RECTS: dict[str, list[tuple]] = {
|
||||
"M": [
|
||||
("7.1", "5", "2.6", "13.2", 'transform="rotate(28 8.4 11.6)"'),
|
||||
("14.3", "5", "2.6", "13.2", 'transform="rotate(-28 15.6 11.6)"'),
|
||||
],
|
||||
"N": [
|
||||
("6.5", "6", "2.4", "12", 'transform="rotate(32 7.7 12)"'),
|
||||
("14.8", "14", "2.4", "12", 'transform="rotate(32 16 20)"'),
|
||||
],
|
||||
"K": [
|
||||
("10", "4", "2.2", "11", 'transform="rotate(-30 11.1 9.5)"'),
|
||||
("10", "17", "2.2", "11", 'transform="rotate(30 11.1 22.5)"'),
|
||||
],
|
||||
"V": [
|
||||
("7", "17", "2.2", "12", 'transform="rotate(24 8.1 23)"'),
|
||||
("12", "17", "2.2", "12", 'transform="rotate(-24 13.1 23)"'),
|
||||
],
|
||||
"W": [
|
||||
("5", "17", "2", "11", 'transform="rotate(18 6 22.5)"'),
|
||||
("9.5", "17", "2", "11", 'transform="rotate(-18 10.5 22.5)"'),
|
||||
("14", "17", "2", "11", 'transform="rotate(18 15 22.5)"'),
|
||||
],
|
||||
"X": [
|
||||
("7", "5", "2.2", "11", 'transform="rotate(32 8.1 10.5)"'),
|
||||
("11", "16", "2.2", "11", 'transform="rotate(-32 12.1 21.5)"'),
|
||||
],
|
||||
"Q": [("14", "18", "2.2", "10", 'transform="rotate(38 15.1 23)"')],
|
||||
"R": [("12", "17", "2.2", "10", 'transform="rotate(38 13.1 22)"')],
|
||||
"/": [
|
||||
("13", "4", "2.2", "10", 'transform="rotate(52 14.1 9)"'),
|
||||
("5", "18", "2.2", "10", 'transform="rotate(52 6.1 23)"'),
|
||||
],
|
||||
":": [("4", "10", "4", "4"), ("4", "20", "4", "4")],
|
||||
"+": [("6", "8", "4", "16"), ("2", "14", "12", "4")],
|
||||
"-": [("2", "14", "12", "4")],
|
||||
}
|
||||
|
||||
# Hand-tuned Overtime Bold shapes (from hub digit geometry).
|
||||
# Hand-tuned glyphs from hub (do not regenerate numerics).
|
||||
OVERTIME_VERBATIM: dict[str, tuple[int, str]] = {
|
||||
"0": (
|
||||
20,
|
||||
'<rect x="3" y="1" width="14" height="3" rx="1"/><rect x="16" y="4" width="3" height="11" rx="1"/>'
|
||||
'<rect x="16" y="17" width="3" height="11" rx="1"/><rect x="3" y="28" width="14" height="3" rx="1"/>'
|
||||
'<rect x="1" y="17" width="3" height="11" rx="1"/><rect x="1" y="4" width="3" height="11" rx="1"/>',
|
||||
),
|
||||
"1": (20, '<rect x="16" y="4" width="3" height="11" rx="1"/><rect x="16" y="17" width="3" height="11" rx="1"/>'),
|
||||
"2": (
|
||||
20,
|
||||
'<rect x="3" y="1" width="14" height="3" rx="1"/><rect x="1" y="4" width="3" height="11" rx="1"/>'
|
||||
'<rect x="3" y="15" width="14" height="3" rx="1"/><rect x="16" y="17" width="3" height="11" rx="1"/>'
|
||||
'<rect x="3" y="28" width="14" height="3" rx="1"/>',
|
||||
),
|
||||
"3": (
|
||||
20,
|
||||
'<rect x="3" y="1" width="14" height="3" rx="1"/><rect x="16" y="4" width="3" height="11" rx="1"/>'
|
||||
'<rect x="3" y="15" width="14" height="3" rx="1"/><rect x="16" y="17" width="3" height="11" rx="1"/>'
|
||||
'<rect x="3" y="28" width="14" height="3" rx="1"/>',
|
||||
),
|
||||
"4": (
|
||||
20,
|
||||
'<rect x="1" y="4" width="3" height="11" rx="1"/><rect x="3" y="15" width="14" height="3" rx="1"/>'
|
||||
'<rect x="16" y="4" width="3" height="11" rx="1"/><rect x="16" y="17" width="3" height="11" rx="1"/>',
|
||||
),
|
||||
"5": (
|
||||
20,
|
||||
'<rect x="3" y="1" width="14" height="3" rx="1"/><rect x="1" y="4" width="3" height="11" rx="1"/>'
|
||||
'<rect x="3" y="15" width="14" height="3" rx="1"/><rect x="16" y="17" width="3" height="11" rx="1"/>'
|
||||
'<rect x="3" y="28" width="14" height="3" rx="1"/>',
|
||||
),
|
||||
"6": (
|
||||
20,
|
||||
'<rect x="3" y="1" width="14" height="3" rx="1"/><rect x="1" y="4" width="3" height="11" rx="1"/>'
|
||||
'<rect x="3" y="15" width="14" height="3" rx="1"/><rect x="1" y="17" width="3" height="11" rx="1"/>'
|
||||
'<rect x="16" y="17" width="3" height="11" rx="1"/><rect x="3" y="28" width="14" height="3" rx="1"/>',
|
||||
),
|
||||
"7": (
|
||||
20,
|
||||
'<rect x="3" y="1" width="14" height="3" rx="1"/><rect x="16" y="4" width="3" height="11" rx="1"/>'
|
||||
'<rect x="16" y="17" width="3" height="11" rx="1"/>',
|
||||
),
|
||||
"8": (
|
||||
20,
|
||||
'<rect x="3" y="1" width="14" height="3" rx="1"/><rect x="1" y="4" width="3" height="11" rx="1"/>'
|
||||
'<rect x="16" y="4" width="3" height="11" rx="1"/><rect x="3" y="15" width="14" height="3" rx="1"/>'
|
||||
'<rect x="1" y="17" width="3" height="11" rx="1"/><rect x="16" y="17" width="3" height="11" rx="1"/>'
|
||||
'<rect x="3" y="28" width="14" height="3" rx="1"/>',
|
||||
),
|
||||
"9": (
|
||||
20,
|
||||
'<rect x="3" y="1" width="14" height="3" rx="1"/><rect x="1" y="4" width="3" height="11" rx="1"/>'
|
||||
'<rect x="3" y="15" width="14" height="3" rx="1"/><rect x="16" y="4" width="3" height="11" rx="1"/>'
|
||||
'<rect x="16" y="17" width="3" height="11" rx="1"/><rect x="3" y="28" width="14" height="3" rx="1"/>',
|
||||
),
|
||||
":": (12, '<rect x="4" y="10" width="4" height="4" rx="1"/><rect x="4" y="20" width="4" height="4" rx="1"/>'),
|
||||
"+": (16, '<rect x="6" y="8" width="4" height="16" rx="1"/><rect x="2" y="14" width="12" height="4" rx="1"/>'),
|
||||
"-": (16, '<rect x="2" y="14" width="12" height="4" rx="1"/>'),
|
||||
" ": (8, ""),
|
||||
"/": (
|
||||
14,
|
||||
'<rect x="11" y="5" width="2.2" height="10" rx="1" transform="rotate(52 12.1 10)"/>'
|
||||
'<rect x="4" y="19" width="2.2" height="10" rx="1" transform="rotate(52 5.1 24)"/>',
|
||||
),
|
||||
}
|
||||
|
||||
OVERTIME_CUSTOM: dict[str, tuple[int, str]] = {
|
||||
"G": (
|
||||
20,
|
||||
'<rect x="3" y="1" width="14" height="3" rx="1"/><rect x="1" y="4" width="3" height="11" rx="1"/>'
|
||||
'<rect x="1" y="17" width="3" height="11" rx="1"/><rect x="3" y="28" width="14" height="3" rx="1"/>'
|
||||
'<rect x="16" y="17" width="3" height="11" rx="1"/><rect x="10" y="15" width="9" height="3" rx="1"/>',
|
||||
),
|
||||
"M": (
|
||||
24,
|
||||
'<rect x="1" y="4" width="3" height="24" rx="1"/><rect x="20" y="4" width="3" height="24" rx="1"/>'
|
||||
'<rect x="7.1" y="5" width="2.6" height="13.2" rx="1" transform="rotate(28 8.4 11.6)"/>'
|
||||
'<rect x="14.3" y="5" width="2.6" height="13.2" rx="1" transform="rotate(-28 15.6 11.6)"/>',
|
||||
),
|
||||
"T": (
|
||||
20,
|
||||
'<rect x="3" y="1" width="14" height="3" rx="1"/><rect x="8" y="4" width="4" height="24" rx="1"/>',
|
||||
),
|
||||
"Y": (
|
||||
20,
|
||||
'<rect x="1" y="4" width="3" height="11" rx="1"/><rect x="16" y="4" width="3" height="11" rx="1"/>'
|
||||
'<rect x="8" y="15" width="4" height="14" rx="1"/>',
|
||||
),
|
||||
}
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class FontStyle:
|
||||
prefix: str
|
||||
outfile: str
|
||||
comment: str
|
||||
h_pad: float
|
||||
top_y: float
|
||||
mid_y: float
|
||||
bot_y: float
|
||||
seg_h: float
|
||||
v_w: float
|
||||
v_h: float
|
||||
left_x: float
|
||||
right_x: float
|
||||
seg_w: float
|
||||
|
||||
|
||||
FONTS: dict[str, FontStyle] = {
|
||||
"overtime": FontStyle(
|
||||
prefix="ot",
|
||||
outfile="overtime-01-alphabet.svg",
|
||||
comment="Overtime Bold style 7-segment LCD (full alphabet)",
|
||||
h_pad=3.0,
|
||||
top_y=1.0,
|
||||
mid_y=15.0,
|
||||
bot_y=28.0,
|
||||
seg_h=3.0,
|
||||
v_w=3.0,
|
||||
v_h=11.0,
|
||||
left_x=1.0,
|
||||
right_x=16.0,
|
||||
seg_w=14.0,
|
||||
),
|
||||
"quartz": FontStyle(
|
||||
prefix="qz",
|
||||
outfile="quartz-alphabet.svg",
|
||||
comment="Quartz (TS) style 7-segment LCD (full alphabet)",
|
||||
h_pad=0.0,
|
||||
top_y=0.0,
|
||||
mid_y=0.0,
|
||||
bot_y=0.0,
|
||||
seg_h=0.0,
|
||||
v_w=0.0,
|
||||
v_h=0.0,
|
||||
left_x=0.0,
|
||||
right_x=0.0,
|
||||
seg_w=0.0,
|
||||
),
|
||||
}
|
||||
|
||||
|
||||
def _rect(x, y, width, height, extra: str = "") -> str:
|
||||
attrs = f'x="{x}" y="{y}" width="{width}" height="{height}" rx="1"'
|
||||
if extra:
|
||||
return f"<rect {attrs} {extra}/>"
|
||||
return f"<rect {attrs}/>"
|
||||
|
||||
|
||||
def _seg_defs(style: FontStyle, w: float, h: float) -> list[tuple[float, float, float, float]]:
|
||||
if style.prefix == "qz":
|
||||
h_pad = max(1.0, w * 0.12)
|
||||
top_y = max(1.0, h * 0.04)
|
||||
mid_y = h * 0.47
|
||||
bot_y = h * 0.86
|
||||
seg_w = max(6.0, w - h_pad * 2)
|
||||
seg_h = max(2.0, h * 0.09)
|
||||
v_w = max(2.0, w * 0.14)
|
||||
v_h = max(6.0, h * 0.33)
|
||||
left_x = max(1.0, h_pad * 0.45)
|
||||
right_x = max(left_x + v_w + 1, w - left_x - v_w)
|
||||
upper_y = top_y + seg_h
|
||||
lower_y = mid_y + seg_h * 0.6
|
||||
return [
|
||||
(h_pad, top_y, seg_w, seg_h),
|
||||
(left_x, upper_y, v_w, v_h),
|
||||
(right_x, upper_y, v_w, v_h),
|
||||
(h_pad, mid_y, seg_w, seg_h),
|
||||
(left_x, lower_y, v_w, v_h),
|
||||
(right_x, lower_y, v_w, v_h),
|
||||
(h_pad, bot_y, seg_w, seg_h),
|
||||
]
|
||||
|
||||
upper_y = style.top_y + style.seg_h
|
||||
lower_y = 17.0
|
||||
return [
|
||||
(style.h_pad, style.top_y, style.seg_w, style.seg_h),
|
||||
(style.left_x, upper_y, style.v_w, style.v_h),
|
||||
(style.right_x, upper_y, style.v_w, style.v_h),
|
||||
(style.h_pad, style.mid_y, style.seg_w, style.seg_h),
|
||||
(style.left_x, lower_y, style.v_w, style.v_h),
|
||||
(style.right_x, lower_y, style.v_w, style.v_h),
|
||||
(style.h_pad, style.bot_y, style.seg_w, style.seg_h),
|
||||
]
|
||||
|
||||
|
||||
def _symbol_id(prefix: str, ch: str) -> str:
|
||||
if ch == " ":
|
||||
return f"{prefix}-space"
|
||||
if ch == ":":
|
||||
return f"{prefix}-colon"
|
||||
if ch == "+":
|
||||
return f"{prefix}-plus"
|
||||
if ch == "-":
|
||||
return f"{prefix}-minus"
|
||||
if ch == "/":
|
||||
return f"{prefix}-slash"
|
||||
if len(ch) == 1 and "0" <= ch <= "9":
|
||||
return f"{prefix}-{ch}"
|
||||
if len(ch) == 1 and "A" <= ch <= "Z":
|
||||
return f"{prefix}-{ch}"
|
||||
return f"{prefix}-u{ord(ch):04x}"
|
||||
|
||||
|
||||
def _symbol_body(style: FontStyle, ch: str, width: int) -> str:
|
||||
if style.prefix == "ot" and ch in OVERTIME_VERBATIM:
|
||||
_, body = OVERTIME_VERBATIM[ch]
|
||||
return body
|
||||
if style.prefix == "ot" and ch in OVERTIME_CUSTOM:
|
||||
_, body = OVERTIME_CUSTOM[ch]
|
||||
return body
|
||||
|
||||
h = 32.0
|
||||
w = float(width)
|
||||
segs = PATTERNS.get(ch, [])
|
||||
parts: list[str] = []
|
||||
defs = _seg_defs(style, w, h)
|
||||
for idx in segs:
|
||||
if idx < 7:
|
||||
x, y, rw, rh = defs[idx]
|
||||
parts.append(_rect(f"{x:.2f}", f"{y:.2f}", f"{rw:.2f}", f"{rh:.2f}"))
|
||||
for item in EXTRA_RECTS.get(ch, []):
|
||||
if len(item) == 4:
|
||||
parts.append(_rect(*item))
|
||||
else:
|
||||
parts.append(_rect(item[0], item[1], item[2], item[3], item[4]))
|
||||
return "".join(parts)
|
||||
|
||||
|
||||
def _char_width(ch: str) -> int:
|
||||
if ch in "MWKNQRX":
|
||||
return 24
|
||||
if ch == ":":
|
||||
return 12
|
||||
if ch in "+-":
|
||||
return 16
|
||||
if ch == "/":
|
||||
return 14
|
||||
if ch == " ":
|
||||
return 8
|
||||
if ch in OVERTIME_VERBATIM:
|
||||
return OVERTIME_VERBATIM[ch][0]
|
||||
if ch in OVERTIME_CUSTOM:
|
||||
return OVERTIME_CUSTOM[ch][0]
|
||||
return 20
|
||||
|
||||
|
||||
def build_font(style: FontStyle, out_dir: Path) -> None:
|
||||
chars = (
|
||||
[" "] + [str(d) for d in range(10)] + [chr(c) for c in range(ord("A"), ord("Z") + 1)]
|
||||
+ [":", "+", "-", "/"]
|
||||
)
|
||||
lines = [
|
||||
'<svg xmlns="http://www.w3.org/2000/svg" aria-hidden="true" '
|
||||
'style="position:absolute;width:0;height:0;overflow:hidden">',
|
||||
f" <!-- {style.comment} -->",
|
||||
" <defs>",
|
||||
]
|
||||
for ch in chars:
|
||||
width = _char_width(ch)
|
||||
sid = _symbol_id(style.prefix, ch)
|
||||
body = _symbol_body(style, ch, width)
|
||||
lines.append(f' <symbol id="{sid}" viewBox="0 0 {width} 32">{body}</symbol>')
|
||||
lines.append(" </defs>")
|
||||
lines.append("</svg>")
|
||||
out_path = out_dir / style.outfile
|
||||
out_path.write_text("\n".join(lines) + "\n", encoding="utf-8")
|
||||
print(f"Wrote {out_path} ({len(chars)} symbols)")
|
||||
|
||||
|
||||
def main() -> None:
|
||||
out_dir = Path(__file__).resolve().parent
|
||||
for style in FONTS.values():
|
||||
build_font(style, out_dir)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
Reference in New Issue
Block a user