#!/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, '' '' '', ), "1": (20, ''), "2": ( 20, '' '' '', ), "3": ( 20, '' '' '', ), "4": ( 20, '' '', ), "5": ( 20, '' '' '', ), "6": ( 20, '' '' '', ), "7": ( 20, '' '', ), "8": ( 20, '' '' '' '', ), "9": ( 20, '' '' '', ), ":": (12, ''), "+": (16, ''), "-": (16, ''), " ": (8, ""), "/": ( 14, '' '', ), } OVERTIME_CUSTOM: dict[str, tuple[int, str]] = { "G": ( 20, '' '' '', ), "M": ( 24, '' '' '', ), "T": ( 20, '', ), "Y": ( 20, '' '', ), } @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"" return f"" 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 = [ '") 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()