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:
1 parent
34fe877bb2
commit
34e9aa49d6
8 files changed
+188
-55
No files matched your search
+85
@@ -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()
|
||||
Reference in new issue
Block a user