| """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 |
|
|