93 lines
2.7 KiB
Python
93 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 pathlib import Path
|
|
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>"""
|