scripts/_raster.py
scripts/_raster.pyBrowse 21 files
685 tokens
2,614 bytes
Token encoding: o200k_base
Snapshot 24fd22b
← Back to SKILL.md
1"""Shared page rasterizer with a fallback chain: pypdfium2 -> pdftoppm.2 3Returns PIL Images so callers can annotate/save. Not a CLI.4"""5from __future__ import annotations6 7import shutil8import subprocess9import tempfile10from pathlib import Path11 12 13def available_backends() -> list[str]:14 """Names of usable rasterizer backends, in preference order."""15 backends = []16 try:17 import pypdfium2 # noqa: F40118 backends.append("pypdfium2")19 except ImportError:20 pass21 if shutil.which("pdftoppm"):22 backends.append("pdftoppm")23 return backends24 25 26def missing_hints() -> list[str]:27 """Install hints for when no backend is available."""28 return [29 "python3 -m pip install pypdfium2",30 "poppler-utils (provides pdftoppm), e.g. apt-get install poppler-utils",31 ]32 33 34def rasterize_page(pdf_path: str, page: int, dpi: int = 150, password: str | None = None):35 """Render one 1-based page to a PIL Image, or None if no backend works.36 37 Raises ValueError for an out-of-range page when a backend is present.38 """39 for backend in available_backends():40 if backend == "pypdfium2":41 return _via_pdfium(pdf_path, page, dpi, password)42 if backend == "pdftoppm":43 img = _via_pdftoppm(pdf_path, page, dpi, password)44 if img is not None:45 return img46 return None47 48 49def _via_pdfium(pdf_path: str, page: int, dpi: int, password: str | None):50 import pypdfium2 as pdfium51 doc = pdfium.PdfDocument(pdf_path, password=password)52 try:53 if not 1 <= page <= len(doc):54 raise ValueError(f"page {page} out of range 1-{len(doc)}")55 bitmap = doc[page - 1].render(scale=dpi / 72.0)56 return bitmap.to_pil().convert("RGB")57 finally:58 doc.close()59 60 61def _via_pdftoppm(pdf_path: str, page: int, dpi: int, password: str | None):62 from PIL import Image63 with tempfile.TemporaryDirectory() as tmp:64 prefix = str(Path(tmp) / "page")65 cmd = ["pdftoppm", "-png", "-r", str(dpi), "-f", str(page), "-l", str(page)]66 if password:67 cmd += ["-upw", password]68 cmd += [pdf_path, prefix]69 proc = subprocess.run(cmd, capture_output=True, text=True, encoding="utf-8")70 if proc.returncode != 0:71 raise ValueError(f"pdftoppm failed: {proc.stderr.strip()}")72 produced = sorted(Path(tmp).glob("page*.png"))73 if not produced:74 raise ValueError(f"page {page} out of range (pdftoppm produced no image)")75 with Image.open(produced[0]) as img:76 return img.convert("RGB")77