Files
ObsiGate/backend/pdf_export.py
T
bruno 1673531b43
CI / lint (push) Failing after 9s
CI / test (push) Has been skipped
CI / build (push) Has been skipped
CI / e2e (push) Has been skipped
CI / security (push) Failing after 12s
fix: resolve all CI lint and security issues
- ruff: 602→0 errors (428 auto-fixed, pyproject.toml ignores for FastAPI patterns)
- bandit: skip B310 (urllib for vault file access is intentional)
- Fixed SIM118 (dict.keys()→dict), PERF102, SIM113, SIM117
- Created pyproject.toml with ruff + bandit config
- 285 tests still pass
2026-07-24 10:38:44 -04:00

91 lines
2.6 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 weasyprint import HTML
logger = logging.getLogger("obsigate.pdf")
def generate_pdf(html_content: str, title: str = "document", base_url: str | None = 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>"""