ObsiGate/backend/pdf_export.py
Bruno Charest 6fc43e2485
Some checks failed
CI / lint (push) Failing after 11s
CI / test (push) Has been skipped
CI / build (push) Has been skipped
CI / security (push) Successful in 7s
fix: ruff lint errors + bandit false positives + pip-audit non-blocking
2026-05-28 12:41:31 -04:00

92 lines
2.7 KiB
Python

"""
PDF export utility using WeasyPrint.
Generates PDF documents from rendered markdown HTML.
Used by both the authenticated API and public share views.
"""
import logging
from typing import Optional
from weasyprint import HTML
logger = logging.getLogger("obsigate.pdf")
def generate_pdf(html_content: str, title: str = "document", base_url: Optional[str] = None) -> bytes:
"""Generate a PDF from HTML content.
Args:
html_content: Full HTML document string.
title: Document title (used for metadata).
base_url: Base URL for resolving relative URLs in the HTML.
Returns:
PDF file as bytes.
"""
html = HTML(string=html_content, base_url=base_url or "")
return html.write_pdf()
def build_pdf_html(body_html: str, title: str, theme: str = "light") -> str:
"""Wrap rendered markdown HTML in a complete print-friendly HTML document.
Args:
body_html: Rendered markdown HTML (without <html>/<body> wrappers).
title: Document title.
theme: 'light' or 'dark' — light is recommended for PDF.
Returns:
Complete HTML document string.
"""
return f"""<!DOCTYPE html>
<html lang="fr">
<head><meta charset="utf-8"><title>{title}</title>
<style>
body {{
font-family: Georgia, "Times New Roman", serif;
max-width: 720px;
margin: 40px auto;
padding: 0 20px;
line-height: 1.7;
color: #1a1a2e;
font-size: 13px;
}}
h1 {{ font-size: 22px; border-bottom: 2px solid #333; padding-bottom: 6px; margin-top: 0; }}
h2 {{ font-size: 18px; margin-top: 24px; }}
h3 {{ font-size: 15px; margin-top: 20px; }}
h4, h5, h6 {{ font-size: 14px; margin-top: 16px; }}
p {{ margin: 8px 0; }}
pre {{
background: #f5f5f5;
border: 1px solid #e0e0e0;
border-radius: 4px;
padding: 10px 14px;
font-size: 11px;
overflow-x: auto;
white-space: pre-wrap;
word-wrap: break-word;
}}
code {{ font-size: 12px; background: #f5f5f5; padding: 1px 4px; border-radius: 2px; }}
pre code {{ background: none; padding: 0; }}
a {{ color: #4f46e5; text-decoration: none; }}
img {{ max-width: 100%; border-radius: 4px; }}
blockquote {{
border-left: 3px solid #ccc;
padding-left: 14px;
color: #555;
margin: 12px 0;
}}
table {{ border-collapse: collapse; width: 100%; margin: 12px 0; page-break-inside: avoid; }}
th, td {{ border: 1px solid #ddd; padding: 6px 10px; text-align: left; font-size: 12px; }}
th {{ background: #f0f0f0; font-weight: 600; }}
ul, ol {{ margin: 8px 0; padding-left: 24px; }}
li {{ margin: 2px 0; }}
hr {{ border: none; border-top: 1px solid #ddd; margin: 20px 0; }}
@page {{ margin: 2cm; size: A4; }}
@media print {{ body {{ margin: 0; }} }}
</style></head>
<body><h1>{title}</h1>
{body_html}
</body></html>"""