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

+85
View File
@@ -10,6 +10,7 @@ import sys
from pathlib import Path
import click
from PIL import Image
# Add project root to sys.path so 'src' is importable
sys.path.insert(0, str(Path(__file__).parent))
@@ -17,6 +18,7 @@ sys.path.insert(0, str(Path(__file__).parent))
from src.config_loader import load_config
from src.excel_reader import extract_attendance, extract_persons, extract_year_from_filename
from src.pdf_filler import fill_pdf
from src.pdf_render import render_pdf_pages
from src.validators import show_preview_and_confirm, validate
@@ -145,5 +147,88 @@ def generate_cmd(excel_file: str, month: str, output_dir: str | None) -> None:
sys.exit(1)
@cli.command("render-image")
@click.option(
"--pdf",
"pdf_file",
required=True,
type=click.Path(exists=True, file_okay=True, dir_okay=False),
help="Pfad zur PDF-Datei, die als Bild gerendert werden soll.",
)
@click.option(
"--out",
"output_path",
required=False,
default=None,
type=click.Path(file_okay=True, dir_okay=True),
help="Ausgabepfad (Datei für Einzelseite oder Ordner für --all-pages).",
)
@click.option(
"--dpi",
required=False,
default=150,
show_default=True,
type=int,
help="Render-Auflösung in DPI.",
)
@click.option(
"--page",
required=False,
default=1,
show_default=True,
type=int,
help="1-basierter Seitenindex für Einzelseiten-Render.",
)
@click.option(
"--all-pages",
is_flag=True,
default=False,
help="Alle Seiten rendern (ignoriert --page).",
)
def render_image_cmd(
pdf_file: str,
output_path: str | None,
dpi: int,
page: int,
all_pages: bool,
) -> None:
"""Rendert eine PDF direkt als PNG-Bild(er) über die Regression-Renderfunktion."""
pdf_path = Path(pdf_file).resolve()
images = render_pdf_pages(pdf_path, dpi=dpi)
if not images:
click.echo("Keine Seiten in der PDF gefunden.", err=True)
sys.exit(1)
if all_pages:
if output_path:
out_dir = Path(output_path).resolve()
else:
out_dir = pdf_path.parent / f"{pdf_path.stem}_images"
out_dir.mkdir(parents=True, exist_ok=True)
for idx, img in enumerate(images, start=1):
out_file = out_dir / f"{pdf_path.stem}_seite_{idx:02d}.png"
Image.fromarray(img).save(out_file)
click.echo(f"[OK] {out_file}")
return
if page < 1 or page > len(images):
click.echo(
f"Ungültige Seite: {page}. PDF hat {len(images)} Seite(n).",
err=True,
)
sys.exit(1)
if output_path:
out_file = Path(output_path).resolve()
else:
out_file = pdf_path.parent / f"{pdf_path.stem}_seite_{page:02d}.png"
out_file.parent.mkdir(parents=True, exist_ok=True)
Image.fromarray(images[page - 1]).save(out_file)
click.echo(f"[OK] {out_file}")
if __name__ == "__main__":
cli()