#!/usr/bin/env python3 """Render a .humans/ HTML page to a self-contained PDF via headless Chrome. The pages in `.humans/` reference screenshots relatively (`../tools/tailor/screenshots/...`), so the HTML only works from inside a repo checkout. The PDF embeds those images, which makes it the thing you actually send to someone. The HTML stays the source of truth: edit the page, re-run this, commit both. python tools/humans_to_pdf.py .humans/marvelous-designer.html python tools/humans_to_pdf.py --all """ from __future__ import annotations import argparse import subprocess import sys import tempfile from pathlib import Path CHROME_CANDIDATES = [ r"C:\Program Files\Google\Chrome\Application\chrome.exe", r"C:\Program Files (x86)\Google\Chrome\Application\chrome.exe", r"C:\Program Files\Microsoft\Edge\Application\msedge.exe", r"C:\Program Files (x86)\Microsoft\Edge\Application\msedge.exe", "/Applications/Google Chrome.app/Contents/MacOS/Google Chrome", "/usr/bin/google-chrome", "/usr/bin/chromium", ] def find_chrome() -> Path: for c in CHROME_CANDIDATES: p = Path(c) if p.exists(): return p raise SystemExit("error: no Chrome or Edge found; tried:\n " + "\n ".join(CHROME_CANDIDATES)) def to_pdf(html: Path, chrome: Path) -> Path: out = html.with_suffix(".pdf") # Chrome refuses to reuse a running profile, so give it a throwaway one. with tempfile.TemporaryDirectory() as profile: cmd = [ str(chrome), "--headless=new", "--disable-gpu", f"--user-data-dir={profile}", "--no-pdf-header-footer", # drop the URL/date furniture "--virtual-time-budget=15000", # let local images decode first f"--print-to-pdf={out}", html.resolve().as_uri(), ] r = subprocess.run(cmd, capture_output=True, text=True, timeout=180) if not out.exists(): sys.stderr.write(r.stderr or "") raise SystemExit(f"error: Chrome produced no PDF for {html}") return out def main() -> int: ap = argparse.ArgumentParser(description=__doc__, formatter_class=argparse.RawDescriptionHelpFormatter) ap.add_argument("page", type=Path, nargs="?", help="HTML page under .humans/") ap.add_argument("--all", action="store_true", help="render every .humans/*.html") args = ap.parse_args() repo = Path(__file__).resolve().parent.parent if args.all: pages = sorted((repo / ".humans").glob("*.html")) elif args.page: pages = [args.page] else: ap.error("give a page, or --all") if not pages: print("nothing to render") return 0 chrome = find_chrome() print(f"using {chrome.name}") for html in pages: if not html.is_file(): print(f" skip (missing): {html}") continue out = to_pdf(html, chrome) print(f" {out.stat().st_size / 1048576:.2f} MB {out}") return 0 if __name__ == "__main__": raise SystemExit(main())