11"use client" ;
22
33import { useEffect , useRef , useState } from "react" ;
4+ import type { PDFPageProxy } from "pdfjs-dist" ;
45
56/**
6- * Renders the real invoice PDF (the bytes the vision model actually reads) to a
7- * canvas with pdf.js. This is the document shown in the extraction reveal and the
8- * queue hover preview — an actual PDF, not an HTML mock.
7+ * Renders the real invoice PDF (the same bytes the vision model reads) to a
8+ * canvas with pdf.js — the document shown in the right pane's extraction reveal.
9+ * An actual PDF, not an HTML mock.
910 *
1011 * pdf.js is loaded dynamically (client-only) and its worker is resolved through
1112 * the bundler via `new URL(..., import.meta.url)`, so there's no CDN dependency.
@@ -29,44 +30,69 @@ export function PdfDocument({ src, dim }: { src: string; dim: boolean }) {
2930 setReady ( false ) ;
3031 setError ( false ) ;
3132
33+ // Keep the loaded page around so a container resize can re-render at the new
34+ // width (sharp at any size) without re-fetching the PDF.
35+ let page : PDFPageProxy | null = null ;
36+ let rendering : Promise < unknown > | null = null ;
37+
38+ async function loadPage ( buf : ArrayBuffer ) {
39+ const pdfjs = await import ( "pdfjs-dist" ) ;
40+ pdfjs . GlobalWorkerOptions . workerSrc = new URL (
41+ "pdfjs-dist/build/pdf.worker.min.mjs" ,
42+ import . meta. url ,
43+ ) . toString ( ) ;
44+ return pdfjs . getDocument ( { data : buf } ) . promise ;
45+ }
46+
47+ async function renderAtCurrentWidth ( ) {
48+ if ( ! page || ! canvas ) return ;
49+ const cssWidth = canvas . parentElement ?. clientWidth ?? 360 ;
50+ if ( cssWidth === 0 ) return ;
51+ const base = page . getViewport ( { scale : 1 } ) ;
52+ const dpr = Math . min ( globalThis . devicePixelRatio || 1 , 2 ) ;
53+ const viewport = page . getViewport ( {
54+ scale : ( cssWidth / base . width ) * dpr ,
55+ } ) ;
56+ const ctx = canvas . getContext ( "2d" ) ;
57+ if ( ! ctx ) throw new Error ( "no 2d context" ) ;
58+ canvas . width = viewport . width ;
59+ canvas . height = viewport . height ;
60+ await page . render ( { canvasContext : ctx , viewport } ) . promise ;
61+ }
62+
63+ const ro = new ResizeObserver ( ( ) => {
64+ // Serialise renders — pdf.js throws if a render starts while one is live.
65+ rendering = ( rendering ?? Promise . resolve ( ) )
66+ . catch ( ( ) => { } )
67+ . then ( renderAtCurrentWidth )
68+ . catch ( ( ) => { } ) ;
69+ } ) ;
70+
3271 ( async ( ) => {
3372 try {
34- const pdfjs = await import ( "pdfjs-dist" ) ;
35- pdfjs . GlobalWorkerOptions . workerSrc = new URL (
36- "pdfjs-dist/build/pdf.worker.min.mjs" ,
37- import . meta. url ,
38- ) . toString ( ) ;
39-
4073 const buf = await fetch ( src ) . then ( ( r ) => {
4174 if ( ! r . ok ) throw new Error ( `pdf fetch ${ r . status } ` ) ;
4275 return r . arrayBuffer ( ) ;
4376 } ) ;
4477 if ( cancelled ) return ;
4578
46- const pdf = await pdfjs . getDocument ( { data : buf } ) . promise ;
47- const page = await pdf . getPage ( 1 ) ;
79+ const pdf = await loadPage ( buf ) ;
80+ if ( cancelled ) return ;
81+ page = await pdf . getPage ( 1 ) ;
4882 if ( cancelled ) return ;
4983
50- const cssWidth = canvas . parentElement ?. clientWidth ?? 360 ;
51- const base = page . getViewport ( { scale : 1 } ) ;
52- const dpr = Math . min ( globalThis . devicePixelRatio || 1 , 2 ) ;
53- const scale = ( cssWidth / base . width ) * dpr ;
54- const viewport = page . getViewport ( { scale } ) ;
55-
56- const ctx = canvas . getContext ( "2d" ) ;
57- if ( ! ctx ) throw new Error ( "no 2d context" ) ;
58- canvas . width = viewport . width ;
59- canvas . height = viewport . height ;
60-
61- await page . render ( { canvasContext : ctx , viewport } ) . promise ;
62- if ( ! cancelled ) setReady ( true ) ;
84+ await renderAtCurrentWidth ( ) ;
85+ if ( cancelled ) return ;
86+ setReady ( true ) ;
87+ if ( canvas . parentElement ) ro . observe ( canvas . parentElement ) ;
6388 } catch {
6489 if ( ! cancelled ) setError ( true ) ;
6590 }
6691 } ) ( ) ;
6792
6893 return ( ) => {
6994 cancelled = true ;
95+ ro . disconnect ( ) ;
7096 } ;
7197 } , [ src ] ) ;
7298
0 commit comments