feat(cli): add shared PDF-to-image rendering and document usage

add shared render_pdf_pages helper for consistent rasterization
reuse the same renderer in regression test and CLI flow
add new render-image CLI command to export PDF pages as PNG
support single-page render (--page) and full export (--all-pages)
add configurable output path and DPI options
document the new rendering command and options in README
This commit is contained in:
Rob committed 2026-04-17 13:48:27 +02:00
1 parent 34fe877bb2
commit 34e9aa49d6
8 files changed
+188 -55

No files matched your search

+3
View File
@@ -64,6 +64,9 @@ def load_config(config_path: Path | None = None) -> Dict[str, Any]:
f"config.json fehlen Pflichtfelder: {', '.join(missing)}"
)
# Optional settings with backward-compatible defaults
data.setdefault("pdf_need_appearances", False)
_config_cache = data
_config_path_cache = config_path
return data
+25 -4
View File
@@ -247,6 +247,14 @@ def fill_pdf(
_apply_opt_mappings(fields, opt_maps)
SUMMARY_FIELDS_FOR_AP = (
"anwesend_sum",
"anwesend_gessum",
"pauschale_5_sum",
"pauschale_25_sum",
"pauschale_sum",
)
writer = PdfWriter()
writer.clone_document_from_reader(reader)
@@ -255,11 +263,24 @@ def fill_pdf(
_update_widget_visibility(writer, fields)
# Tell the PDF viewer to regenerate appearance streams from field values.
# Without this flag, dropdown fields (plan, pauschale) display stale text
# instead of their actual selected option (e.g. "8,60" for pauschale).
# Second pass: regenerate AP only for summary text fields.
# This keeps footer/buttons visible with NeedAppearances=False while still
# rendering summary values in image-based comparisons.
summary_fields = {
name: fields[name]
for name in SUMMARY_FIELDS_FOR_AP
if name in fields
}
if summary_fields:
for page in writer.pages:
writer.update_page_form_field_values(page, summary_fields, auto_regenerate=True)
# Configure viewer-side appearance regeneration via config switch.
# Default is False for stable pymupdf rendering (footer/buttons visible).
if "/AcroForm" in writer._root_object:
writer._root_object["/AcroForm"][NameObject("/NeedAppearances")] = BooleanObject(True)
writer._root_object["/AcroForm"][NameObject("/NeedAppearances")] = BooleanObject(
bool(config.get("pdf_need_appearances", False))
)
output_path.parent.mkdir(parents=True, exist_ok=True)
with output_path.open("wb") as fh:
+24
View File
@@ -0,0 +1,24 @@
"""Shared PDF rasterization helpers used by tests and CLI."""
from __future__ import annotations
from pathlib import Path
import numpy as np
import pymupdf
def render_pdf_pages(pdf_path: Path, dpi: int = 150) -> list[np.ndarray]:
"""Render all pages of *pdf_path* to RGB numpy arrays using pymupdf."""
zoom = dpi / 72.0
mat = pymupdf.Matrix(zoom, zoom)
doc = pymupdf.open(str(pdf_path))
images: list[np.ndarray] = []
for page in doc:
pix = page.get_pixmap(matrix=mat)
img = np.frombuffer(pix.samples, dtype=np.uint8).reshape(pix.h, pix.w, pix.n)
if pix.n == 4:
img = img[:, :, :3]
images.append(img.copy())
doc.close()
return images