56 lines
2.1 KiB
Python
56 lines
2.1 KiB
Python
"""SVG -> PDF (vector render via WeasyPrint).
|
|
|
|
SVG is XML by spec, so the document is first well-formedness-checked with
|
|
lxml — an unbalanced file (e.g. "<svg><g></path>") is a *job* error with
|
|
"Invalid SVG: <reason>", never a silent blank render (HTML5 foreign-content
|
|
parsing would otherwise swallow the mismatch and succeed with an empty page).
|
|
|
|
The SVG is then embedded verbatim in a minimal A4 HTML document; WeasyPrint
|
|
renders SVG natively as a vector (text stays sharp, no rasterization). A
|
|
forced page break guarantees at least one PDF page even for a degenerate
|
|
document. A WeasyPrint render failure is also reported as "Invalid SVG: ...".
|
|
"""
|
|
from __future__ import annotations
|
|
|
|
from pathlib import Path
|
|
|
|
from lxml import etree
|
|
|
|
from . import common
|
|
|
|
_DOC = (
|
|
"<!DOCTYPE html><html><head><style>"
|
|
"@page { size: A4; margin: 20mm; }"
|
|
"body { margin: 0; font-kerning: none; }"
|
|
"svg { max-width: 100%; height: auto; }"
|
|
"</style></head><body>{svg}<div style='page-break-after: always'></div>"
|
|
"</body></html>"
|
|
)
|
|
|
|
|
|
def convert(path: Path, job_id: str, progress_cb=None, output_dir=None,
|
|
source_name=None) -> str:
|
|
"""Render an SVG file to a vector PDF."""
|
|
raw = path.read_bytes()
|
|
if not raw.strip():
|
|
raise ValueError("empty file: nothing to convert")
|
|
svg_text = common.decode_text(raw)
|
|
try:
|
|
etree.fromstring(svg_text.encode("utf-8"))
|
|
except etree.XMLSyntaxError as exc:
|
|
raise ValueError(f"Invalid SVG: {exc}") from exc
|
|
|
|
doc = _DOC.replace("{svg}", svg_text)
|
|
out_dir = common.default_output_dir(output_dir)
|
|
|
|
common.report(progress_cb, 30.0, f"Rendering {path.name} (1/1)")
|
|
try:
|
|
pdf_bytes = common.html_to_pdf_bytes(doc)
|
|
except ValueError:
|
|
raise # empty-file errors keep their own message
|
|
except Exception as exc: # noqa: BLE001 - render failure is a job error
|
|
raise ValueError(f"Invalid SVG: {exc}") from exc
|
|
filename = common.derive_export_name(source_name or path.name, out_dir)
|
|
return common.finalize_pdf(pdf_bytes, out_dir, filename, job_id,
|
|
progress_cb=progress_cb)
|