myke69's picture
Add files using upload-large-folder tool
3752e5d verified
Raw
History Blame Contribute Delete
3.49 kB
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 (
<div className="pdf-container" ref={containerRef}>
{!pdf && <div className="pdf-loading">Loading PDF…</div>}
{pdf &&
Array.from({ length: numPages }, (_, i) => (
<Page
key={i}
pdf={pdf}
pageNumber={i + 1}
refCb={(el) => (pageRefs.current[i] = el)}
rects={highlight ? highlight.rects.filter((r) => r.page === i) : []}
/>
))}
</div>
)
}
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 (
<div className="pdf-page" ref={refCb} style={{ width: size.w || undefined }}>
<canvas ref={canvasRef} />
{rects.map((r, i) => (
<div
key={i}
className="hl"
style={{
left: r.x0 * SCALE - 2,
top: r.y0 * SCALE - 2,
width: (r.x1 - r.x0) * SCALE + 4,
height: (r.y1 - r.y0) * SCALE + 4,
}}
/>
))}
<div className="page-num">{pageNumber}</div>
</div>
)
}