"""Font file management — Century Gothic only. Mirrors the PHP handbook_select_font_family / handbook_font_face_css logic. """ import base64 import os from pathlib import Path from app.core.config import get_settings class FontError(RuntimeError): pass VARIANTS = ("regular", "bold", "italic", "bold_italic") FILE_MAP = { "regular": "GOTHIC.TTF", "bold": "GOTHICB.TTF", "italic": "GOTHICI.TTF", "bold_italic": "GOTHICBI.TTF", } def _font_dir() -> Path: return Path(get_settings().font_dir) def select_font_family() -> dict: """Return font metadata dict. Raises FontError if any file is missing.""" font_dir = _font_dir() paths: dict[str, Path] = {} for variant, filename in FILE_MAP.items(): p = font_dir / filename if not p.is_file(): raise FontError( f'Century Gothic font file missing for variant "{variant}": {p}' ) paths[variant] = p return { "family": "Century Gothic", "regular": str(paths["regular"]), "bold": str(paths["bold"]), "italic": str(paths["italic"]), "bold_italic": str(paths["bold_italic"]), "status": "primary", } def font_face_css(font_meta: dict | None = None) -> str: """Generate @font-face CSS with base64-embedded TTF data.""" meta = font_meta or select_font_family() family = meta.get("family", "Century Gothic") encoded: dict[str, str] = {} for variant in VARIANTS: path = meta.get(variant) if not path or not os.path.isfile(path): raise FontError( f'Century Gothic font file missing for variant "{variant}": {path}' ) with open(path, "rb") as f: data = base64.b64encode(f.read()).decode("ascii") if not data: raise FontError(f"Failed to read/encode font file: {path}") encoded[variant] = data css_parts = [] weight_style = { "regular": ("400", "normal"), "bold": ("700", "normal"), "italic": ("400", "italic"), "bold_italic": ("700", "italic"), } for variant, (weight, style) in weight_style.items(): css_parts.append( f"@font-face {{\n" f" font-family: '{family}';\n" f" src: url('data:font/ttf;base64,{encoded[variant]}') format('truetype');\n" f" font-weight: {weight};\n" f" font-style: {style};\n" f"}}" ) return "\n".join(css_parts) def font_diagnostics() -> dict: """Return diagnostic info about font availability.""" font_dir = _font_dir() result = { "font_dir": str(font_dir), "font_dir_exists": font_dir.is_dir(), "variants": {}, } for variant, filename in FILE_MAP.items(): p = font_dir / filename result["variants"][variant] = { "path": str(p), "exists": p.is_file(), "size_bytes": p.stat().st_size if p.is_file() else 0, } return result