Import AITURK IDE 1.0.0-beta.1 from Hermes 63279301; preserve MIT license
This commit is contained in:
@@ -0,0 +1,76 @@
|
||||
"""Shared page rasterizer with a fallback chain: pypdfium2 -> pdftoppm.
|
||||
|
||||
Returns PIL Images so callers can annotate/save. Not a CLI.
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
import shutil
|
||||
import subprocess
|
||||
import tempfile
|
||||
from pathlib import Path
|
||||
|
||||
|
||||
def available_backends() -> list[str]:
|
||||
"""Names of usable rasterizer backends, in preference order."""
|
||||
backends = []
|
||||
try:
|
||||
import pypdfium2 # noqa: F401
|
||||
backends.append("pypdfium2")
|
||||
except ImportError:
|
||||
pass
|
||||
if shutil.which("pdftoppm"):
|
||||
backends.append("pdftoppm")
|
||||
return backends
|
||||
|
||||
|
||||
def missing_hints() -> list[str]:
|
||||
"""Install hints for when no backend is available."""
|
||||
return [
|
||||
"python3 -m pip install pypdfium2",
|
||||
"poppler-utils (provides pdftoppm), e.g. apt-get install poppler-utils",
|
||||
]
|
||||
|
||||
|
||||
def rasterize_page(pdf_path: str, page: int, dpi: int = 150, password: str | None = None):
|
||||
"""Render one 1-based page to a PIL Image, or None if no backend works.
|
||||
|
||||
Raises ValueError for an out-of-range page when a backend is present.
|
||||
"""
|
||||
for backend in available_backends():
|
||||
if backend == "pypdfium2":
|
||||
return _via_pdfium(pdf_path, page, dpi, password)
|
||||
if backend == "pdftoppm":
|
||||
img = _via_pdftoppm(pdf_path, page, dpi, password)
|
||||
if img is not None:
|
||||
return img
|
||||
return None
|
||||
|
||||
|
||||
def _via_pdfium(pdf_path: str, page: int, dpi: int, password: str | None):
|
||||
import pypdfium2 as pdfium
|
||||
doc = pdfium.PdfDocument(pdf_path, password=password)
|
||||
try:
|
||||
if not 1 <= page <= len(doc):
|
||||
raise ValueError(f"page {page} out of range 1-{len(doc)}")
|
||||
bitmap = doc[page - 1].render(scale=dpi / 72.0)
|
||||
return bitmap.to_pil().convert("RGB")
|
||||
finally:
|
||||
doc.close()
|
||||
|
||||
|
||||
def _via_pdftoppm(pdf_path: str, page: int, dpi: int, password: str | None):
|
||||
from PIL import Image
|
||||
with tempfile.TemporaryDirectory() as tmp:
|
||||
prefix = str(Path(tmp) / "page")
|
||||
cmd = ["pdftoppm", "-png", "-r", str(dpi), "-f", str(page), "-l", str(page)]
|
||||
if password:
|
||||
cmd += ["-upw", password]
|
||||
cmd += [pdf_path, prefix]
|
||||
proc = subprocess.run(cmd, capture_output=True, text=True, encoding="utf-8")
|
||||
if proc.returncode != 0:
|
||||
raise ValueError(f"pdftoppm failed: {proc.stderr.strip()}")
|
||||
produced = sorted(Path(tmp).glob("page*.png"))
|
||||
if not produced:
|
||||
raise ValueError(f"page {page} out of range (pdftoppm produced no image)")
|
||||
with Image.open(produced[0]) as img:
|
||||
return img.convert("RGB")
|
||||
Reference in New Issue
Block a user