Firemedic15's picture
download
raw
7.67 kB
#!/usr/bin/env python3
"""render_preview - render a poster HTML to print-ready PDF + thumbnail.
Canvas-agnostic: reads ``@page { size: <W> <H> }`` from the input HTML
or accepts ``--canvas '<W>x<H>in'`` / ``--canvas 'A0 portrait'`` as
override. Print-emulates Chromium so MathJax typesets against the
``@media print`` layout from the start.
This is the SOFT path (vs the HARD ``measure`` gate): a MathJax
typeset timeout or a missing ``<mjx-container>`` warns and continues
— users would rather see raw ``$…$`` on the rendered PDF than a
silent abort.
Outputs:
<stem>_preview.pdf exact-size PDF
<stem>_preview.png scaled thumbnail (default 0.35×)
"""
from __future__ import annotations
import argparse
import os
import sys
from pathlib import Path
# Make `_posterly` importable when run directly.
_THIS_DIR = os.path.dirname(os.path.abspath(__file__))
if _THIS_DIR not in sys.path:
sys.path.insert(0, _THIS_DIR)
from _posterly import canvas as _canvas # noqa: E402
from _posterly import render as _render # noqa: E402
from _posterly.textutil import ascii_safe # noqa: E402
def _eprint(*args: object, **kw: object) -> None:
print(*args, file=sys.stderr, **kw) # type: ignore[arg-type]
def build_parser() -> argparse.ArgumentParser:
p = argparse.ArgumentParser(
description=(__doc__ or "").splitlines()[0]
)
p.add_argument("html", help="poster HTML file")
p.add_argument(
"--pdf", default=None,
help="output PDF path (default: <stem>_preview.pdf)",
)
p.add_argument(
"--png", default=None,
help="output PNG thumbnail path (default: <stem>_preview.png)",
)
p.add_argument(
"--thumb-scale", type=float, default=0.35,
help="thumbnail scale factor (default 0.35)",
)
p.add_argument(
"--mathjax-timeout-ms", type=int, default=15000,
help="timeout for MathJax typesetting (default 15000); "
"render is the SOFT path; timeout warns, not fails",
)
p.add_argument(
"--canvas", type=_canvas.parse_canvas_arg, default=None,
help="override canvas (e.g. '60x36in' / 'A0 portrait'); "
"by default we parse @page from the HTML",
)
return p
def main() -> int:
args = build_parser().parse_args()
html_path = Path(args.html).resolve()
if not html_path.exists():
_eprint(f"ERROR: HTML not found: {ascii_safe(html_path)}")
return 2
pdf_path = (
Path(args.pdf) if args.pdf
else html_path.with_name(html_path.stem + "_preview.pdf")
)
png_path = (
Path(args.png) if args.png
else html_path.with_name(html_path.stem + "_preview.png")
)
resolved = _canvas.resolve_canvas(
html_path, args.canvas, label="[render_preview]"
)
if resolved is None:
_eprint(
"ERROR: could not find `@page { size: <W> <H> }` in HTML. "
"Add an @page rule (units: in/mm/cm/pt) or pass "
"`--canvas <W>x<H>in` / `--canvas 'A0 portrait'`. "
"Refusing to silently fall back."
)
return 2
canvas, viewport = resolved
w_in, h_in = canvas
try:
from playwright.sync_api import sync_playwright
from playwright.sync_api import TimeoutError as PWTimeoutError
except ImportError:
_eprint("ERROR: playwright not installed. Run:")
_eprint(" python -m pip install playwright")
_eprint(" python -m playwright install chromium")
return 2
with sync_playwright() as p_:
browser, _ctx, page = _render.open_print_emulated_page(
p_, viewport
)
# Soft path: a hung CDN (blocked MathJax fetch, unreachable
# web font) must not hard-crash render. Playwright's default
# `page.goto` waits for `load` (all subresources), which can
# block ~30s on a single blocked CDN. settle_page below has
# its own bounded waits; let it surface MathJax issues as
# warnings, not tracebacks.
try:
page.goto(html_path.as_uri(), timeout=args.mathjax_timeout_ms)
except PWTimeoutError:
_eprint(
f"[render_preview] WARN: page.goto did not reach `load` "
f"within {args.mathjax_timeout_ms} ms; continuing with "
f"whatever has loaded (a CDN or external resource is "
f"likely blocked)."
)
try:
page.wait_for_load_state(
"networkidle", timeout=args.mathjax_timeout_ms,
)
except PWTimeoutError:
_eprint(
f"[render_preview] WARN: network never went idle within "
f"{args.mathjax_timeout_ms} ms; continuing with whatever "
f"loaded (likely a slow/blocked external resource)."
)
settle = _render.settle_page(
page,
mathjax_timeout_ms=args.mathjax_timeout_ms,
settle_ms=1500,
)
# Render is soft path: warn but continue, even on MathJax
# problems — the user can SEE raw $...$ on the resulting PDF.
if settle.mathjax_status == "timeout":
_eprint(
f"[render_preview] WARN: MathJax typeset timed out "
f"after {args.mathjax_timeout_ms} ms."
)
elif settle.mathjax_status == "error":
_eprint(
f"[render_preview] WARN: MathJax error: "
f"{ascii_safe(settle.mathjax_error)}"
)
if settle.mathjax_intended and settle.tex_without_mathjax:
_eprint(
"[render_preview] WARN: page intended to load MathJax "
"but no <mjx-container> rendered -- MathJax may have "
"failed to load. PDF will show raw $...$ text."
)
# ---- PDF: exact poster size, print-emulated ----
page.pdf(
path=str(pdf_path),
width=f"{w_in}in",
height=f"{h_in}in",
print_background=True,
margin={"top": "0", "bottom": "0",
"left": "0", "right": "0"},
)
# ---- PNG: scaled thumbnail of `.poster` (or document body) ----
s = args.thumb_scale
page.evaluate(
f"""() => {{
const el = document.querySelector(
'[data-measure-role="poster"]')
|| document.querySelector('.poster')
|| document.body;
el.style.transformOrigin = 'top left';
el.style.transform = 'scale({s})';
document.body.style.width =
(el.offsetWidth * {s}) + 'px';
document.body.style.height =
(el.offsetHeight * {s}) + 'px';
document.body.style.overflow = 'hidden';
document.body.style.margin = '0';
}}"""
)
thumb_w = int(round(w_in * 96 * s))
thumb_h = int(round(h_in * 96 * s))
page.set_viewport_size(
{"width": thumb_w, "height": thumb_h}
)
page.screenshot(
path=str(png_path),
full_page=False,
clip={"x": 0, "y": 0,
"width": thumb_w, "height": thumb_h},
)
browser.close()
print(
f"[render_preview] PDF -> {ascii_safe(pdf_path)} "
f"({pdf_path.stat().st_size / 1024:.1f} KB)"
)
print(
f"[render_preview] PNG -> {ascii_safe(png_path)} "
f"({png_path.stat().st_size / 1024:.1f} KB)"
)
return 0
if __name__ == "__main__":
sys.exit(main())

Xet Storage Details

Size:
7.67 kB
·
Xet hash:
118dbfc3926259cc25710ab4b891ba14987113f9d3f286ab25741e78e2d77319

Xet efficiently stores files, intelligently splitting them into unique chunks and accelerating uploads and downloads. More info.