#!/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())