"""Printable NCR PDF (WeasyPrint) — a clean single-document rendering of the complete NCR for hard-copy travelers and audits. WeasyPrint is imported lazily so environments without the Pango/Cairo system libraries (e.g. unit tests) can still import the app. """ from functools import partial from pathlib import Path import anyio from jinja2 import Environment, FileSystemLoader, select_autoescape from app.domain import STAGE_LABELS, Stage from app.models import Ncr from app.models.base import utcnow from app.services.storage import attachment_abs_path _TEMPLATES_DIR = Path(__file__).resolve().parent.parent / "templates" _env = Environment( loader=FileSystemLoader(_TEMPLATES_DIR), autoescape=select_autoescape(["html"]), ) def _render_html(ncr: Ncr) -> str: images = [] other_files = [] for att in ncr.attachments: entry = { "filename": att.original_filename, "uploaded_by": att.uploaded_by.display_name, "uploaded_at": att.uploaded_at, "size_kb": max(1, att.size_bytes // 1024), } path = attachment_abs_path(att.stored_path) if att.is_image and path.is_file(): entry["src"] = path.as_uri() images.append(entry) else: other_files.append(entry) template = _env.get_template("ncr_pdf.html") return template.render( ncr=ncr, stage_label=STAGE_LABELS[Stage(ncr.stage)], stage_labels=STAGE_LABELS, Stage=Stage, images=images, other_files=other_files, generated_at=utcnow(), ) def _html_to_pdf(html: str) -> bytes: from weasyprint import HTML # lazy: needs Pango/Cairo system libs return HTML(string=html).write_pdf() async def render_ncr_pdf(ncr: Ncr) -> bytes: html = _render_html(ncr) # WeasyPrint rendering is CPU-bound; keep it off the event loop. return await anyio.to_thread.run_sync(partial(_html_to_pdf, html))