handbook_engine / app /core /fonts.py
internationalscholarsprogram's picture
fix: ISP handbook styling overhaul - margins, typography, emphasis, benefits, CSS cascade
ec94fc1
"""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