from __future__ import annotations from pathlib import Path import subprocess import shutil from jinja2 import Environment, FileSystemLoader, select_autoescape from .models import Invoice from .calc import compute_totals from .types import RenderResult from .utils import money_fmt def render_html(inv: Invoice, template_path: Path) -> str: env = Environment( loader=FileSystemLoader(str(template_path.parent)), autoescape=select_autoescape(["html", "xml"]), ) env.globals["money"] = lambda x: money_fmt(x, inv.meta.currency) tpl = env.get_template(template_path.name) totals = compute_totals(inv) items = [] for it in inv.items: d = it.model_dump() d["line_total_net"] = float(it.quantity) * float(it.unit_price_net) items.append(type("Obj", (), d)) ctx = { "meta": inv.meta.model_dump(), "seller": inv.seller.model_dump(), "buyer": inv.buyer.model_dump(), "payment": inv.payment.model_dump(), "items": items, "totals": totals, } return tpl.render(**ctx) def _weasyprint_available() -> bool: try: import weasyprint # noqa return True except Exception: return False def render_pdf(html: str, out_pdf: Path, logger, prefer_weasyprint: bool = True, base_url: str | None = None) -> RenderResult: out_pdf.parent.mkdir(parents=True, exist_ok=True) if prefer_weasyprint and _weasyprint_available(): logger.info("Rendering PDF using WeasyPrint") try: from weasyprint import HTML # type: ignore HTML(string=html, base_url=base_url).write_pdf(str(out_pdf)) return RenderResult(ok=True, backend="weasyprint", path=out_pdf) except Exception as e: logger.exception("WeasyPrint rendering failed; trying wkhtmltopdf if available: %s", e) wk = shutil.which("wkhtmltopdf") if wk: logger.info("Rendering PDF using wkhtmltopdf (%s)", wk) tmp_html = out_pdf.with_suffix(".tmp.html") tmp_html.write_text(html, encoding="utf-8") try: proc = subprocess.run( [wk, "--quiet", "--enable-local-file-access", str(tmp_html), str(out_pdf)], capture_output=True, text=True, check=False, ) if proc.returncode != 0: logger.error("wkhtmltopdf failed (rc=%s). stderr=%s", proc.returncode, proc.stderr.strip()) return RenderResult(ok=False, backend="wkhtmltopdf", path=None, error="wkhtmltopdf failed") return RenderResult(ok=True, backend="wkhtmltopdf", path=out_pdf) finally: try: tmp_html.unlink(missing_ok=True) except Exception: pass return RenderResult(ok=False, backend="none", path=None, error="No PDF backend available (install weasyprint or wkhtmltopdf).")