"""Render an agenda item's packet pages to images for the Gradio gallery (fallback UI). Uses the same confidence-gated slicer as the Agenda Item Report (:func:`webapp.backend._slice_packet_for_item`) to pick the page range, then rasterizes those pages with pypdfium2. Reads the uploaded packet from the local cache (keyed by ``upload_id``); the heavy work runs only when the user clicks "View packet pages". """ from __future__ import annotations from webapp import backend # Cap how many pages we rasterize at once so a large slice doesn't render dozens of # images per click. MAX_RENDER_PAGES = 12 RENDER_SCALE = 2.0 # ~144 dpi — legible without being huge def render_item_pages( upload_id: str, item_titles: list[str], item_index: int, start: int = 0, end: int = 0, ) -> tuple[list, str]: """Return ``(gallery_items, caption_markdown)`` for one agenda item's pages. ``gallery_items`` is a list of ``(PIL.Image, "p.N")`` tuples for a ``gr.Gallery``; it's empty (with an explanatory caption) when the packet is missing or the item's pages can't be confidently located. An explicit page range (``end > start``) is used directly; otherwise the item is anchored. """ cp = backend.cached_packet(upload_id) if cp is None: return [], "_The packet is no longer on the server — re-upload it._" pages = backend.cached_packet_pages(upload_id) if not pages: return [], "_No extractable packet text — the PDF may be scanned/image-only._" s = max(0, int(start or 0)) e = min(len(pages), int(end or 0)) if e > s: info = {"sliced": True, "pages": f"{s + 1}-{e}", "start": s, "end": e} else: outline = backend.cached_packet_outline(upload_id) _, info = backend._slice_packet_for_item(pages, item_titles, item_index, outline=outline) if not info["sliced"]: return [], ( f"_Couldn't pinpoint this item's pages in the {len(pages)}-page packet. " "Set the item's page range, or open the full packet._" ) sl_start, sl_end = info["start"], info["end"] total = sl_end - sl_start n = min(total, MAX_RENDER_PAGES) images = _render_pages(str(cp[0]), sl_start, sl_start + n) caption = f"**Packet pages {info['pages']}** — {total} page(s) for this item" if n < total: caption += f"; showing the first {n}." return images, caption def _render_pages(src: bytes | str, start: int, end: int) -> list: """Rasterize pages ``[start, end)`` of a PDF to ``(PIL.Image, label)`` tuples.""" import pypdfium2 as pdfium out: list = [] with backend._PDFIUM_LOCK: # PDFium is not thread-safe — serialize all access pdf = pdfium.PdfDocument(src) try: for i in range(start, end): pil = pdf[i].render(scale=RENDER_SCALE).to_pil() out.append((pil, f"p.{i + 1}")) finally: pdf.close() return out