"""PDF rendering: Chromium via Playwright, US Letter, running header + footer. Requires: pip install playwright && playwright install chromium (Claude's cloud workspace already has both.) """ import base64 import re from pathlib import Path from . import brand, i18n MARGIN = {"top": "1.0in", "bottom": "0.7in", "left": "0.62in", "right": "0.62in"} def _header(doc, fonts, logo): return (f'
' f'
' f'' f'
{doc.header_label.upper()}
' f'
{doc.number} · REV {doc.rev}
') def _footer(doc, fonts): """MESSAGE POINT MEDIA · tagline (left) | CONTEXT [· MARKING] · NUMBER · Page x of y (right).""" ctx = doc.footer.upper().replace("MESSAGE POINT MEDIA · ", "", 1) return (f'
' f'MESSAGE POINT MEDIA · {brand.TAGLINE}' f'{ctx} · {doc.number} · {i18n.page_of(getattr(doc, "lang", "en"))}
') def _header_logo(width=1400, path=None): """Brand logo scaled for the running header (the MPM master PNG is ~5800 px wide).""" raw = (path or brand.LOGO_HEADER).read_bytes() try: import io from PIL import Image im = Image.open(io.BytesIO(raw)) if im.width > width: im = im.resize((width, round(im.height * width / im.width)), Image.LANCZOS) buf = io.BytesIO() im.save(buf, "PNG", optimize=True) return buf.getvalue() except ImportError: pass return raw PAGE_H_PT = 792.0 # US Letter CONTENT_BOTTOM_PT = PAGE_H_PT - 0.7 * 72 # bottom margin 0.7 in. HEADER_BOTTOM_PT = 1.0 * 72 def _rev_spacer_px(pdf_bytes, content_bottom_pt=None, header_bottom_pt=None): """Find the revision block via its sentinels. If it shares the last page with other content, return the spacer (px) that moves it to the bottom of that page; else 0. Needs poppler's pdftotext.""" import re import shutil import subprocess import tempfile if not shutil.which("pdftotext"): import sys print("warning: poppler's pdftotext not found; the revision block will not be anchored to the page " "bottom (install poppler: brew install poppler / apt install poppler-utils)", file=sys.stderr) return 0, None with tempfile.NamedTemporaryFile(suffix=".pdf") as f: f.write(pdf_bytes) f.flush() xml = subprocess.run(["pdftotext", "-bbox", f.name, "-"], capture_output=True, text=True).stdout pages = re.findall(r"]*>(.*?)", xml, re.S) word = re.compile(r'([^<]*)') for i, pg in enumerate(pages): words = [(float(a), float(b), t) for a, b, t in word.findall(pg)] top = [w for w in words if "QQREVTOPQQ" in w[2]] end = [w for w in words if "QQREVENDQQ" in w[2]] if not top or not end: continue y_top, y_end = top[0][0], end[0][1] above = [w for w in words if (header_bottom_pt or HEADER_BOTTOM_PT) < w[1] < y_top - 1 and "QQREV" not in w[2]] if not above: # block sits alone on its page: leave it at the top return 0, len(pages) free_pt = (content_bottom_pt or CONTENT_BOTTOM_PT) - y_end return max(0.0, free_pt * 96 / 72 - 4), len(pages) return 0, len(pages) def render_pdf(html_path, out_path, doc): from playwright.sync_api import sync_playwright fonts = brand.FONTS_CSS.read_text() logo = "data:image/png;base64," + base64.b64encode(_header_logo()).decode() if getattr(doc, "lang", "en") != "en": fonts += doc._cjk_css # same subset the page loaded (see Doc.html) hdr, ftr, margin = _header(doc, fonts, logo), _footer(doc, fonts), MARGIN if getattr(doc, "lang", "en") != "en": hdr, ftr = i18n.add_fallback(hdr), i18n.add_fallback(ftr) bounds = (CONTENT_BOTTOM_PT, HEADER_BOTTOM_PT) opts = dict(format="Letter", print_background=True, display_header_footer=True, header_template=hdr, footer_template=ftr, margin=margin) with sync_playwright() as p: b = p.chromium.launch() pg = b.new_page() pg.goto(Path(html_path).resolve().as_uri(), wait_until="load") # wait for every image to decode and every font to load, so pass 1 measures the final layout pg.wait_for_function("Array.from(document.images).every(i => i.complete && i.naturalWidth > 0)") pg.evaluate("() => Promise.all([document.fonts.ready, ...Array.from(document.images).map(i => i.decode().catch(() => null))])") pg.wait_for_timeout(150) first = pg.pdf(**opts) # pass 1: find the revision block spacer, n_pages = _rev_spacer_px(first, *bounds) pg.add_style_tag(content=f".sent{{display:none}}#rev-spacer{{height:{spacer:.1f}px}}") final = pg.pdf(**opts) # pass 2: block anchored, sentinels hidden if spacer and n_pages and len(re.findall(rb"/Type\s*/Page[^s]", final)) != n_pages: pg.add_style_tag(content="#rev-spacer{height:0}") final = pg.pdf(**opts) b.close() Path(out_path).write_bytes(final)