#!/usr/bin/env python3 from __future__ import annotations """Build SD card fonts from a declarative YAML config. Reads sd-fonts.yaml, downloads any missing source fonts, runs fontconvert_sdcard.py in parallel for each family, and optionally generates the fonts.json manifest. Usage: # Generate fonts (output in ./output/) python3 build-sd-fonts.py # Generate fonts + manifest python3 build-sd-fonts.py --manifest --base-url "http://localhost:8000/" # Custom config / output paths python3 build-sd-fonts.py --config my-fonts.yaml --output-dir dist/ # Generate only specific families python3 build-sd-fonts.py --only Literata,IBMPlexMono """ import argparse import shutil import subprocess import sys import urllib.request from concurrent.futures import ProcessPoolExecutor, as_completed from pathlib import Path import yaml SCRIPT_DIR = Path(__file__).parent FONTCONVERT = SCRIPT_DIR / "fontconvert_sdcard.py" EPDFONTS_DIR = SCRIPT_DIR.parent # lib/EpdFont DEFAULT_CONFIG = SCRIPT_DIR / "sd-fonts.yaml" DEFAULT_OUTPUT = SCRIPT_DIR / "output" DOWNLOAD_DIR = SCRIPT_DIR / "downloaded_fonts" INSTANCE_DIR = SCRIPT_DIR / "instanced_fonts" def download_font(url: str, dest: Path) -> Path: """Download a font file if not already cached. Returns the local path.""" if dest.exists(): return dest dest.parent.mkdir(parents=True, exist_ok=True) print(f" Downloading {dest.name}...") try: urllib.request.urlretrieve(url, dest) except Exception as e: dest.unlink(missing_ok=True) raise RuntimeError(f"Failed to download {url}: {e}") from e size_kb = dest.stat().st_size / 1024 print(f" Downloaded {dest.name} ({size_kb:.0f} KB)") return dest def extract_static_instance(source_path: Path, axes: dict, family_name: str, style_name: str) -> Path: """Use fonttools instancer to pin variable font axes, producing a static TTF. Caches the result in INSTANCE_DIR//