any-file to PDF (Phase 1): converters/ dispatcher, images/text/md/csv/json/xml/svg/pdf-passthrough, 112 tests green
This commit is contained in:
@@ -0,0 +1,53 @@
|
||||
"""Raster images (.png/.jpg/.jpeg/.webp/.tiff/.bmp) -> PDF, one page per image.
|
||||
|
||||
Rendered directly by Pillow (img.save(format='PDF')) — no WeasyPrint in
|
||||
this path, so image fidelity is exactly what the encoder produces.
|
||||
Transparency (RGBA/P) is flattened to RGB over white. Images wider or
|
||||
taller than MAX_DIM are downscaled proportionally to keep the PDF sane
|
||||
for e.g. 12 MP camera photos.
|
||||
|
||||
An unopenable/corrupt file is a job error: "Invalid image: ..." (the
|
||||
contract asserts 'image' in error.lower()).
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
import io
|
||||
from pathlib import Path
|
||||
|
||||
from PIL import Image
|
||||
|
||||
from . import common
|
||||
|
||||
MAX_DIM = 2000
|
||||
|
||||
|
||||
def convert(path: Path, job_id: str, progress_cb=None, output_dir=None,
|
||||
source_name=None) -> str:
|
||||
"""Render a raster image to a one-page PDF."""
|
||||
try:
|
||||
img = Image.open(path)
|
||||
img.load() # force full decode: a truncated file fails here, not later
|
||||
except Exception as exc: # noqa: BLE001 - PIL raises many types
|
||||
raise ValueError(f"Invalid image: {path.name} could not be opened ({exc})") from exc
|
||||
|
||||
if img.mode in ("RGBA", "P"):
|
||||
bg = Image.new("RGB", img.size, (255, 255, 255))
|
||||
if img.mode == "P":
|
||||
img = img.convert("RGBA")
|
||||
bg.paste(img, mask=img.getchannel("A"))
|
||||
img = bg
|
||||
elif img.mode != "RGB":
|
||||
img = img.convert("RGB")
|
||||
|
||||
w, h = img.size
|
||||
scale = min(1.0, MAX_DIM / max(w, h))
|
||||
if scale < 1.0:
|
||||
img = img.resize((max(1, int(w * scale)), max(1, int(h * scale))), Image.LANCZOS)
|
||||
|
||||
common.report(progress_cb, 30.0, f"Rendering {path.name} (1/1)")
|
||||
buf = io.BytesIO()
|
||||
img.save(buf, format="PDF")
|
||||
out_dir = common.default_output_dir(output_dir)
|
||||
filename = common.derive_export_name(source_name or path.name, out_dir)
|
||||
return common.finalize_pdf(buf.getvalue(), out_dir, filename, job_id,
|
||||
progress_cb=progress_cb)
|
||||
Reference in New Issue
Block a user