# ─── PDF Generation ────────────────────────────────────────────────────────────
#
# WeasyPrint PDF generation and PDF storage operations.
#
# ─── Imports ───────────────────────────────────────────────────────────────────
from pathlib import Path
import uuid
from weasyprint import HTML
# ─── Constants ─────────────────────────────────────────────────────────────────
# Where generated PDFs live on disk
PDF_STORAGE_DIR = Path("/app/data/documents")
# ─── PDF Generation ────────────────────────────────────────────────────────────
def generate_pdf(html_content: str, style: dict = None) -> bytes:
"""Generate PDF from HTML content using WeasyPrint.
Args:
html_content: Rendered HTML string (templates own their @page rules)
style: Style config (passed to templates via render_document_html)
Returns:
PDF bytes
Note: Templates define their own @page rules inline. We do NOT inject
an external stylesheet here — that was overriding template @page/body
rules and breaking full-bleed layouts (Stripe, HubSpot, Notion, etc.)
which use @page { margin: 0 } with internal padding.
"""
html_doc = HTML(string=html_content, base_url=str(Path(__file__).parent.parent))
return html_doc.write_pdf()
# ─── PDF Storage ───────────────────────────────────────────────────────────────
def save_pdf(pdf_bytes: bytes, document_id: str) -> str:
"""Save PDF bytes to disk. Returns relative path."""
PDF_STORAGE_DIR.mkdir(parents=True, exist_ok=True)
filename = f"{document_id}.pdf"
filepath = PDF_STORAGE_DIR / filename
filepath.write_bytes(pdf_bytes)
return str(filepath)
def get_pdf_path(document_id: str) -> Path:
"""Get absolute path for a stored PDF."""
return PDF_STORAGE_DIR / f"{document_id}.pdf"