diff --git a/.DS_Store b/.DS_Store new file mode 100644 index 0000000..b3f8c89 Binary files /dev/null and b/.DS_Store differ diff --git a/README.md b/README.md index b5076a5..19de862 100644 --- a/README.md +++ b/README.md @@ -29,7 +29,8 @@ All constants live in `engine/config.py`: | `HEADLINE_LIMIT` | `1000` | Total headlines per session | | `FEED_TIMEOUT` | `10` | Per-feed HTTP timeout (seconds) | | `MIC_THRESHOLD_DB` | `50` | dB floor above which glitches spike | -| `FONT_PATH` | hardcoded path | Path to your OTF/TTF display font | +| `FONT_DIR` | `fonts/` | Folder scanned for `.otf`, `.ttf`, `.ttc` files used by the font picker | +| `FONT_PATH` | first supported font in `fonts/` | Active display font file selected at startup | | `FONT_SZ` | `60` | Font render size (affects block density) | | `RENDER_H` | `8` | Terminal rows per headline line | | `SSAA` | `4` | Super-sampling factor (render at 4× then downsample) | @@ -41,7 +42,7 @@ All constants live in `engine/config.py`: | `NTFY_POLL_INTERVAL` | `15` | Seconds between ntfy polls | | `MESSAGE_DISPLAY_SECS` | `30` | How long an ntfy message holds the screen | -**Font:** `FONT_PATH` is hardcoded to a local path. Update it to point to whatever display font you want — anything with strong contrast and wide letterforms works well. +**Font:** Put your `.otf`, `.ttf`, or `.ttc` files in `fonts/`. Startup opens the font picker from that folder and applies your selected font before streaming. --- diff --git a/engine/app.py b/engine/app.py index 68b1845..98cddd1 100644 --- a/engine/app.py +++ b/engine/app.py @@ -90,7 +90,7 @@ def _draw_font_picker(faces, selected): print() print(f" {G_HI}FONT PICKER{RST}") print(f" {W_GHOST}{'─' * (w - 4)}{RST}") - print(f" {W_DIM}{config.FONT_PATH[:max_preview_w]}{RST}") + print(f" {W_DIM}{config.FONT_DIR[:max_preview_w]}{RST}") print(f" {W_GHOST}↑/↓ move · Enter select · q accept current{RST}") print() @@ -99,7 +99,7 @@ def _draw_font_picker(faces, selected): active = pos == selected pointer = "▶" if active else " " color = G_HI if active else W_DIM - print(f" {color}{pointer} [{face['index']}] {face['name']}{RST}") + print(f" {color}{pointer} {face['name']}{RST}{W_GHOST} · {face['file_name']}{RST}") if top > 0: print(f" {W_GHOST}… {top} above{RST}") @@ -108,53 +108,93 @@ def _draw_font_picker(faces, selected): print() print(f" {W_GHOST}{'─' * (w - 4)}{RST}") - print(f" {W_DIM}Preview: {faces[selected]['name']}{RST}") + print( + f" {W_DIM}Preview: {faces[selected]['name']} · {faces[selected]['file_name']}{RST}" + ) preview_rows = faces[selected]["preview_rows"][:preview_h] for row in preview_rows: shown = row[:max_preview_w] print(f" {shown}") def pick_font_face(): - """Interactive startup picker for selecting a face from a font file.""" + """Interactive startup picker for selecting a face from repo OTF files.""" if not config.FONT_PICKER: return - print(CLR, end="") - print(CURSOR_OFF, end="") - print() - print(f" {G_HI}FONT PICKER{RST}") - print(f" {W_GHOST}{'─' * (tw() - 4)}{RST}") - print(f" {W_DIM}{config.FONT_PATH}{RST}") - print() - - try: - faces = render.list_font_faces(config.FONT_PATH, max_faces=64) - except Exception as exc: - print(f" {G_DIM}> unable to load font file: {exc}{RST}") - print(f" {W_GHOST}> startup aborted (font selection is required){RST}") - time.sleep(1.4) + font_files = config.list_repo_font_files() + if not font_files: + print(CLR, end="") + print(CURSOR_OFF, end="") + print() + print(f" {G_HI}FONT PICKER{RST}") + print(f" {W_GHOST}{'─' * (tw() - 4)}{RST}") + print(f" {G_DIM}> no .otf/.ttf/.ttc files found in: {config.FONT_DIR}{RST}") + print(f" {W_GHOST}> add font files to the fonts folder, then rerun{RST}") + time.sleep(1.8) sys.exit(1) - face_ids = [face["index"] for face in faces] - default = config.FONT_INDEX if config.FONT_INDEX in face_ids else faces[0]["index"] - prepared = [] - for face in faces: - idx = face["index"] - name = face["name"] + for font_path in font_files: try: - fnt = render.load_font_face(config.FONT_PATH, idx) - rows = _normalize_preview_rows(render.render_line(name, fnt)) + faces = render.list_font_faces(font_path, max_faces=64) except Exception: - rows = ["(preview unavailable)"] - prepared.append({"index": idx, "name": name, "preview_rows": rows}) + fallback = os.path.splitext(os.path.basename(font_path))[0] + faces = [{"index": 0, "name": fallback}] + for face in faces: + idx = face["index"] + name = face["name"] + file_name = os.path.basename(font_path) + try: + fnt = render.load_font_face(font_path, idx) + rows = _normalize_preview_rows(render.render_line(name, fnt)) + except Exception: + rows = ["(preview unavailable)"] + prepared.append( + { + "font_path": font_path, + "font_index": idx, + "name": name, + "file_name": file_name, + "preview_rows": rows, + } + ) + + if not prepared: + print(CLR, end="") + print(CURSOR_OFF, end="") + print() + print(f" {G_HI}FONT PICKER{RST}") + print(f" {W_GHOST}{'─' * (tw() - 4)}{RST}") + print(f" {G_DIM}> no readable font faces found in: {config.FONT_DIR}{RST}") + time.sleep(1.8) + sys.exit(1) + + def _same_path(a, b): + try: + return os.path.samefile(a, b) + except Exception: + return os.path.abspath(a) == os.path.abspath(b) + + selected = next( + ( + i + for i, f in enumerate(prepared) + if _same_path(f["font_path"], config.FONT_PATH) + and f["font_index"] == config.FONT_INDEX + ), + 0, + ) - selected = next((i for i, f in enumerate(prepared) if f["index"] == default), 0) if not sys.stdin.isatty(): - selected_index = prepared[selected]["index"] - config.set_font_selection(font_index=selected_index) + selected_font = prepared[selected] + config.set_font_selection( + font_path=selected_font["font_path"], + font_index=selected_font["font_index"], + ) render.clear_font_cache() - print(f" {G_DIM}> using face {selected_index}{RST}") + print( + f" {G_DIM}> using {selected_font['name']} ({selected_font['file_name']}){RST}" + ) time.sleep(0.8) print(CLR, end="") print(CURSOR_OFF, end="") @@ -179,11 +219,13 @@ def pick_font_face(): finally: termios.tcsetattr(fd, termios.TCSADRAIN, old_settings) - selected = prepared[selected]["index"] - - config.set_font_selection(font_index=selected) + selected_font = prepared[selected] + config.set_font_selection( + font_path=selected_font["font_path"], + font_index=selected_font["font_index"], + ) render.clear_font_cache() - print(f" {G_DIM}> using face {selected}{RST}") + print(f" {G_DIM}> using {selected_font['name']} ({selected_font['file_name']}){RST}") time.sleep(0.8) print(CLR, end="") print(CURSOR_OFF, end="") diff --git a/engine/config.py b/engine/config.py index 2944f02..f509b23 100644 --- a/engine/config.py +++ b/engine/config.py @@ -5,6 +5,8 @@ Configuration constants, CLI flags, and glyph tables. import sys from pathlib import Path +_REPO_ROOT = Path(__file__).resolve().parent.parent +_FONT_EXTENSIONS = {".otf", ".ttf", ".ttc"} def _arg_value(flag): @@ -31,8 +33,24 @@ def _resolve_font_path(raw_path): p = Path(raw_path).expanduser() if p.is_absolute(): return str(p) - repo_root = Path(__file__).resolve().parent.parent - return str((repo_root / p).resolve()) + return str((_REPO_ROOT / p).resolve()) + + +def _list_font_files(font_dir): + """List supported font files within a font directory.""" + font_root = Path(font_dir) + if not font_root.exists() or not font_root.is_dir(): + return [] + return [ + str(path.resolve()) + for path in sorted(font_root.iterdir()) + if path.is_file() and path.suffix.lower() in _FONT_EXTENSIONS + ] + + +def list_repo_font_files(): + """Public helper for discovering repository font files.""" + return _list_font_files(FONT_DIR) # ─── RUNTIME ────────────────────────────────────────────── HEADLINE_LIMIT = 1000 FEED_TIMEOUT = 10 @@ -46,9 +64,13 @@ NTFY_POLL_INTERVAL = 15 # seconds between polls MESSAGE_DISPLAY_SECS = 30 # how long a message holds the screen # ─── FONT RENDERING ────────────────────────────────────── -FONT_PATH = _resolve_font_path( - _arg_value('--font-file') - or "/Users/genejohnson/Documents/CS Bishop Drawn/CSBishopDrawn-Italic.otf" +FONT_DIR = _resolve_font_path(_arg_value('--font-dir') or "fonts") +_FONT_FILE_ARG = _arg_value('--font-file') +_FONT_FILES = _list_font_files(FONT_DIR) +FONT_PATH = ( + _resolve_font_path(_FONT_FILE_ARG) + if _FONT_FILE_ARG + else (_FONT_FILES[0] if _FONT_FILES else "") ) FONT_INDEX = max(0, _arg_int('--font-index', 0)) FONT_PICKER = '--no-font-picker' not in sys.argv diff --git a/engine/render.py b/engine/render.py index 105646d..1ef5adf 100644 --- a/engine/render.py +++ b/engine/render.py @@ -57,6 +57,10 @@ _FONT_CACHE = {} def font(): """Lazy-load the primary OTF font (path + face index aware).""" global _FONT_OBJ, _FONT_OBJ_KEY + if not config.FONT_PATH: + raise FileNotFoundError( + f"No primary font selected. Add .otf/.ttf/.ttc files to {config.FONT_DIR}." + ) key = (config.FONT_PATH, config.FONT_INDEX, config.FONT_SZ) if _FONT_OBJ is None or _FONT_OBJ_KEY != key: _FONT_OBJ = ImageFont.truetype( diff --git a/fonts/Eyekons.otf b/fonts/Eyekons.otf new file mode 100644 index 0000000..8e9d2db Binary files /dev/null and b/fonts/Eyekons.otf differ diff --git a/fonts/Pixel Sparta.otf b/fonts/Pixel Sparta.otf new file mode 100644 index 0000000..c8c160a Binary files /dev/null and b/fonts/Pixel Sparta.otf differ diff --git a/fonts/Xeonic.ttf b/fonts/Xeonic.ttf new file mode 100644 index 0000000..f2e29e0 Binary files /dev/null and b/fonts/Xeonic.ttf differ