import React, { useEffect, useRef, useState } from 'react' import * as pdfjsLib from 'pdfjs-dist' import workerSrc from 'pdfjs-dist/build/pdf.worker.min.mjs?url' pdfjsLib.GlobalWorkerOptions.workerSrc = workerSrc const SCALE = 1.35 /** * Renders all pages of the PDF and draws highlight overlays from * {page, rects[]} coming from the backend. The backend emits rects in * PDF points with a TOP-LEFT origin (PyMuPDF convention), which maps to a * rendered canvas by a plain multiply with the zoom factor — no coordinate * flipping needed. */ export default function PdfViewer({ fileUrl, highlight }) { const containerRef = useRef(null) const pageRefs = useRef({}) const [numPages, setNumPages] = useState(0) const [pdf, setPdf] = useState(null) useEffect(() => { let cancelled = false setPdf(null) setNumPages(0) pdfjsLib.getDocument(fileUrl).promise.then((doc) => { if (cancelled) return setPdf(doc) setNumPages(doc.numPages) }) return () => { cancelled = true } }, [fileUrl]) useEffect(() => { if (!highlight) return const el = pageRefs.current[highlight.page] if (el) { // scroll the first highlight rect into view, not just the page top const firstRect = highlight.rects?.[0] const offset = firstRect ? firstRect.y0 * SCALE - 120 : 0 const container = containerRef.current container.scrollTo({ top: el.offsetTop + offset, behavior: 'smooth' }) } }, [highlight]) return (
{!pdf &&
Loading PDF…
} {pdf && Array.from({ length: numPages }, (_, i) => ( (pageRefs.current[i] = el)} rects={highlight ? highlight.rects.filter((r) => r.page === i) : []} /> ))}
) } function Page({ pdf, pageNumber, rects, refCb }) { const canvasRef = useRef(null) const [size, setSize] = useState({ w: 0, h: 0 }) useEffect(() => { let cancelled = false let renderTask = null pdf.getPage(pageNumber).then((page) => { if (cancelled) return const viewport = page.getViewport({ scale: SCALE }) const canvas = canvasRef.current if (!canvas) return const dpr = window.devicePixelRatio || 1 canvas.width = viewport.width * dpr canvas.height = viewport.height * dpr canvas.style.width = `${viewport.width}px` canvas.style.height = `${viewport.height}px` setSize({ w: viewport.width, h: viewport.height }) const ctx = canvas.getContext('2d') ctx.scale(dpr, dpr) renderTask = page.render({ canvasContext: ctx, viewport }) }) return () => { cancelled = true renderTask?.cancel() } }, [pdf, pageNumber]) return (
{rects.map((r, i) => (
))}
{pageNumber}
) }