File size: 2,818 Bytes
436dbdd c982278 89111aa 94a3264 89111aa 94a3264 89111aa c982278 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 | import fitz
from PIL import Image
from core.constants import RENDER_DPI
def page_count(pdf_path: str) -> int:
doc = fitz.open(pdf_path)
try:
return doc.page_count
finally:
doc.close()
def render_pages(
pdf_path: str, page_nums: list[int], dpi: int = RENDER_DPI
) -> list[Image.Image]:
"""Render 1-based pages of a PDF to RGB images (document opened once)."""
doc = fitz.open(pdf_path)
try:
images = []
for num in page_nums:
if num < 1 or num > doc.page_count:
raise ValueError(
f"Page {num} out of range — this PDF has {doc.page_count} pages."
)
pix = doc.load_page(num - 1).get_pixmap(dpi=dpi)
images.append(Image.frombytes("RGB", (pix.width, pix.height), pix.samples))
return images
finally:
doc.close()
def render_page(pdf_path: str, page_num: int, dpi: int = RENDER_DPI) -> Image.Image:
return render_pages(pdf_path, [page_num], dpi)[0]
def render_page_png(pdf_path: str, page_num: int, dpi: int = RENDER_DPI) -> bytes:
"""Render a 1-based page straight to PNG bytes (for the /page route).
Same DPI as the model/parse renders, so parse bboxes map 1:1 onto it."""
doc = fitz.open(pdf_path)
try:
if page_num < 1 or page_num > doc.page_count:
raise ValueError(
f"Page {page_num} out of range — this PDF has {doc.page_count} pages."
)
return doc.load_page(page_num - 1).get_pixmap(dpi=dpi).tobytes("png")
finally:
doc.close()
def pdf_outline(pdf_path: str) -> list[dict]:
"""The PDF's bookmark outline as [{title, page_start, page_end}] — the
manual's own clean chapter structure (e.g. 16 entries for a 1151-page
Hyundai manual), unlike the noisy parse-derived headings. Empty when the
PDF has no bookmarks."""
doc = fitz.open(pdf_path)
try:
entries = [
{"title": " ".join(title.split()), "page_start": page}
for _, title, page in doc.get_toc()
if title.strip() and page >= 1
]
for i, e in enumerate(entries):
e["page_end"] = (
max(e["page_start"], entries[i + 1]["page_start"] - 1)
if i + 1 < len(entries)
else doc.page_count
)
return entries
finally:
doc.close()
def page_size(pdf_path: str, page_num: int, dpi: int = RENDER_DPI) -> tuple[int, int]:
"""(width, height) in pixels a 1-based page renders to at this DPI,
without rendering it."""
doc = fitz.open(pdf_path)
try:
rect = doc.load_page(page_num - 1).rect
scale = dpi / 72
return round(rect.width * scale), round(rect.height * scale)
finally:
doc.close()
|