33< head >
44 < title > jsPDF Test Report</ title >
55 < link href ="
https://cdn.jsdelivr.net/npm/[email protected] /dist/tailwind.min.css "
rel ="
stylesheet "
> 6+ < script src ="https://cdnjs.cloudflare.com/ajax/libs/pdf.js/3.11.174/pdf.min.js "> </ script >
67 < style >
78 /* Preserve existing styles that are still needed */
8- .pdf-container iframe {
9+ .pdf-container {
910 width : 100% ;
1011 height : 800px ;
11- border : none;
12+ position : relative;
13+ overflow : hidden;
14+ background : # 525659 ;
15+ display : flex;
16+ flex-direction : column;
17+ }
18+ .pdf-canvas-container {
19+ flex : 1 ;
20+ position : relative;
21+ overflow : hidden;
22+ cursor : grab;
23+ }
24+ .pdf-canvas-container .panning {
25+ cursor : grabbing;
26+ }
27+ .pdf-canvas-wrapper {
28+ position : absolute;
29+ top : 0 ;
30+ left : 0 ;
31+ width : 100% ;
32+ height : 100% ;
33+ overflow : auto;
34+ }
35+ .pdf-canvas {
36+ position : relative;
37+ margin : 0 auto;
38+ display : block;
39+ user-select : none;
40+ -webkit-user-select : none;
1241 }
1342 .differences {
1443 white-space : pre-wrap;
6392 width : 1rem ;
6493 height : 1rem ;
6594 }
95+ .pdf-controls {
96+ padding : 0.75rem ;
97+ background : rgba (0 , 0 , 0 , 0.5 );
98+ display : flex;
99+ gap : 1rem ;
100+ align-items : center;
101+ justify-content : center;
102+ color : white;
103+ border-bottom : 1px solid rgba (255 , 255 , 255 , 0.1 );
104+ }
105+ .pdf-controls button {
106+ padding : 0.25rem 0.5rem ;
107+ border-radius : 0.25rem ;
108+ background : rgba (255 , 255 , 255 , 0.2 );
109+ border : 1px solid rgba (255 , 255 , 255 , 0.3 );
110+ color : white;
111+ cursor : pointer;
112+ }
113+ .pdf-controls button : hover {
114+ background : rgba (255 , 255 , 255 , 0.3 );
115+ }
116+ .pdf-controls input {
117+ width : 4rem ;
118+ padding : 0.25rem ;
119+ border-radius : 0.25rem ;
120+ background : rgba (255 , 255 , 255 , 0.1 );
121+ border : 1px solid rgba (255 , 255 , 255 , 0.2 );
122+ color : white;
123+ text-align : center;
124+ }
66125 </ style >
67126</ head >
68127< body class ="antialiased ">
@@ -95,9 +154,17 @@ <h3 class="text-sm font-medium text-purple-900">Failing PDFs</h3>
95154
96155 < script >
97156 let selectedItem = null ;
157+ let currentPdfState = {
158+ scale : 1.0 ,
159+ page : 1 ,
160+ actualPdf : null ,
161+ referencePdf : null
162+ } ;
98163
99164 const documentIcon = `<svg class="icon" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"></path><polyline points="14 2 14 8 20 8"></polyline><line x1="16" y1="13" x2="8" y2="13"></line><line x1="16" y1="17" x2="8" y2="17"></line><line x1="10" y1="9" x2="8" y2="9"></line></svg>` ;
100165
166+ pdfjsLib . GlobalWorkerOptions . workerSrc = 'https://cdnjs.cloudflare.com/ajax/libs/pdf.js/3.11.174/pdf.worker.min.js' ;
167+
101168 function formatSummary ( results ) {
102169 return `
103170 <div class="flex items-center justify-between">
@@ -119,7 +186,188 @@ <h3 class="text-sm font-medium text-purple-900">Failing PDFs</h3>
119186 ` ;
120187 }
121188
122- function showPdfComparison ( item ) {
189+ async function loadPdf ( url , containerId ) {
190+ try {
191+ const loadingTask = pdfjsLib . getDocument ( url ) ;
192+ const pdf = await loadingTask . promise ;
193+ const container = document . getElementById ( containerId ) ;
194+ const canvas = container . querySelector ( 'canvas' ) ;
195+ const context = canvas . getContext ( '2d' ) ;
196+
197+ // Store PDF reference
198+ if ( containerId === 'actualPdfContainer' ) {
199+ currentPdfState . actualPdf = pdf ;
200+ } else {
201+ currentPdfState . referencePdf = pdf ;
202+ }
203+
204+ // Update page count in controls
205+ const pageCount = pdf . numPages ;
206+ container . querySelector ( '.page-count' ) . textContent = pageCount ;
207+
208+ // Calculate initial scale to fit width
209+ const page = await pdf . getPage ( 1 ) ;
210+ const viewport = page . getViewport ( { scale : 1.0 } ) ;
211+ const containerWidth = container . querySelector ( '.pdf-canvas-container' ) . clientWidth ;
212+ const initialScale = ( containerWidth - 40 ) / viewport . width ; // 40px padding
213+ currentPdfState . scale = initialScale ;
214+
215+ // Update zoom display
216+ document . querySelectorAll ( '.pdf-controls span:last-of-type' ) . forEach ( span => {
217+ span . textContent = `${ Math . round ( initialScale * 100 ) } %` ;
218+ } ) ;
219+
220+ // Render current page
221+ await renderPage ( pdf , currentPdfState . page , currentPdfState . scale , canvas , context ) ;
222+
223+ return pdf ;
224+ } catch ( error ) {
225+ console . error ( 'Error loading PDF:' , error ) ;
226+ }
227+ }
228+
229+ async function renderPage ( pdf , pageNumber , scale , canvas , context ) {
230+ const page = await pdf . getPage ( pageNumber ) ;
231+ const pixelRatio = window . devicePixelRatio || 1 ;
232+ const viewport = page . getViewport ( { scale : scale * pixelRatio } ) ;
233+
234+ canvas . width = viewport . width ;
235+ canvas . height = viewport . height ;
236+ canvas . style . width = `${ viewport . width / pixelRatio } px` ;
237+ canvas . style . height = `${ viewport . height / pixelRatio } px` ;
238+
239+ await page . render ( {
240+ canvasContext : context ,
241+ viewport
242+ } ) . promise ;
243+ }
244+
245+ async function updateBothPdfs ( ) {
246+ if ( currentPdfState . actualPdf ) {
247+ const actualCanvas = document . querySelector ( '#actualPdfContainer canvas' ) ;
248+ await renderPage (
249+ currentPdfState . actualPdf ,
250+ currentPdfState . page ,
251+ currentPdfState . scale ,
252+ actualCanvas ,
253+ actualCanvas . getContext ( '2d' )
254+ ) ;
255+ }
256+ if ( currentPdfState . referencePdf ) {
257+ const referenceCanvas = document . querySelector ( '#referencePdfContainer canvas' ) ;
258+ await renderPage (
259+ currentPdfState . referencePdf ,
260+ currentPdfState . page ,
261+ currentPdfState . scale ,
262+ referenceCanvas ,
263+ referenceCanvas . getContext ( '2d' )
264+ ) ;
265+ }
266+ }
267+
268+ function createPdfViewer ( id , title ) {
269+ return `
270+ <div class="card">
271+ <div class="p-3 border-b">
272+ <h3 class="font-medium text-purple-900">${ title } </h3>
273+ </div>
274+ <div id="${ id } " class="pdf-container">
275+ <div class="pdf-controls">
276+ <button onclick="changePage(-1)">◀</button>
277+ <span>Page <input type="number" value="1" min="1" onchange="setPage(this.value)"> of <span class="page-count">0</span></span>
278+ <button onclick="changePage(1)">▶</button>
279+ <button onclick="changeZoom(-0.1)">-</button>
280+ <span>${ Math . round ( currentPdfState . scale * 100 ) } %</span>
281+ <button onclick="changeZoom(0.1)">+</button>
282+ <button onclick="fitToWidth()">Fit</button>
283+ </div>
284+ <div class="pdf-canvas-container" onmousedown="startPan(event, this)">
285+ <div class="pdf-canvas-wrapper" onwheel="handleScroll(event, this)">
286+ <canvas class="pdf-canvas"></canvas>
287+ </div>
288+ </div>
289+ </div>
290+ </div>
291+ ` ;
292+ }
293+
294+ // Add scroll synchronization
295+ function handleScroll ( e , wrapper ) {
296+ const containers = document . querySelectorAll ( '.pdf-canvas-wrapper' ) ;
297+ containers . forEach ( container => {
298+ if ( container !== wrapper ) {
299+ container . scrollTop = wrapper . scrollTop ;
300+ container . scrollLeft = wrapper . scrollLeft ;
301+ }
302+ } ) ;
303+ }
304+
305+ // Add panning functionality
306+ let isPanning = false ;
307+ let startPoint = { x : 0 , y : 0 } ;
308+ let scrollPositions = { x : 0 , y : 0 } ;
309+ let activePanContainer = null ;
310+
311+ function startPan ( e , container ) {
312+ if ( e . button !== 0 ) return ; // Only left mouse button
313+ isPanning = true ;
314+ activePanContainer = container ;
315+ container . classList . add ( 'panning' ) ;
316+
317+ startPoint = { x : e . clientX , y : e . clientY } ;
318+ const wrapper = container . querySelector ( '.pdf-canvas-wrapper' ) ;
319+ scrollPositions = {
320+ x : wrapper . scrollLeft ,
321+ y : wrapper . scrollTop
322+ } ;
323+
324+ document . addEventListener ( 'mousemove' , handlePan ) ;
325+ document . addEventListener ( 'mouseup' , endPan ) ;
326+ e . preventDefault ( ) ;
327+ }
328+
329+ function handlePan ( e ) {
330+ if ( ! isPanning || ! activePanContainer ) return ;
331+
332+ const dx = startPoint . x - e . clientX ;
333+ const dy = startPoint . y - e . clientY ;
334+
335+ const containers = document . querySelectorAll ( '.pdf-canvas-wrapper' ) ;
336+ containers . forEach ( wrapper => {
337+ wrapper . scrollLeft = scrollPositions . x + dx ;
338+ wrapper . scrollTop = scrollPositions . y + dy ;
339+ } ) ;
340+
341+ e . preventDefault ( ) ;
342+ }
343+
344+ function endPan ( ) {
345+ if ( ! isPanning || ! activePanContainer ) return ;
346+
347+ isPanning = false ;
348+ activePanContainer . classList . remove ( 'panning' ) ;
349+ activePanContainer = null ;
350+
351+ document . removeEventListener ( 'mousemove' , handlePan ) ;
352+ document . removeEventListener ( 'mouseup' , endPan ) ;
353+ }
354+
355+ async function fitToWidth ( ) {
356+ if ( ! currentPdfState . actualPdf ) return ;
357+
358+ const page = await currentPdfState . actualPdf . getPage ( currentPdfState . page ) ;
359+ const viewport = page . getViewport ( { scale : 1.0 } ) ;
360+ const container = document . querySelector ( '.pdf-canvas-container' ) ;
361+ const newScale = ( container . clientWidth - 40 ) / viewport . width ;
362+
363+ currentPdfState . scale = newScale ;
364+ document . querySelectorAll ( '.pdf-controls span:last-of-type' ) . forEach ( span => {
365+ span . textContent = `${ Math . round ( newScale * 100 ) } %` ;
366+ } ) ;
367+ await updateBothPdfs ( ) ;
368+ }
369+
370+ async function showPdfComparison ( item ) {
123371 const details = document . getElementById ( 'details' ) ;
124372
125373 // Update selected state in sidebar
@@ -129,29 +377,23 @@ <h3 class="text-sm font-medium text-purple-900">Failing PDFs</h3>
129377 document . getElementById ( item . id ) . classList . add ( 'selected' ) ;
130378 selectedItem = item . id ;
131379
380+ // Reset PDF state
381+ currentPdfState = {
382+ scale : 1.0 ,
383+ page : 1 ,
384+ actualPdf : null ,
385+ referencePdf : null
386+ } ;
387+
132388 details . innerHTML = `
133389 <div class="card">
134390 <div class="p-4 border-b">
135391 <h2 class="text-lg font-semibold text-purple-900">${ item . name } </h2>
136392 </div>
137393 <div class="p-4">
138394 <div class="grid grid-cols-2 gap-6">
139- <div class="card">
140- <div class="p-3 border-b">
141- <h3 class="font-medium text-purple-900">Actual PDF</h3>
142- </div>
143- <div class="pdf-container">
144- <iframe src="/${ item . actualPdf } "></iframe>
145- </div>
146- </div>
147- <div class="card">
148- <div class="p-3 border-b">
149- <h3 class="font-medium text-purple-900">Reference PDF</h3>
150- </div>
151- <div class="pdf-container">
152- <iframe src="/${ item . referencePdf } "></iframe>
153- </div>
154- </div>
395+ ${ createPdfViewer ( 'actualPdfContainer' , 'Actual PDF' ) }
396+ ${ createPdfViewer ( 'referencePdfContainer' , 'Reference PDF' ) }
155397 </div>
156398 ${ item . error ? `
157399 <div class="mt-6">
@@ -168,6 +410,43 @@ <h3 class="font-medium text-purple-900">Differences</h3>
168410 </div>
169411 </div>
170412 ` ;
413+
414+ // Load both PDFs
415+ await Promise . all ( [
416+ loadPdf ( item . actualPdf , 'actualPdfContainer' ) ,
417+ loadPdf ( item . referencePdf , 'referencePdfContainer' )
418+ ] ) ;
419+ }
420+
421+ async function changePage ( delta ) {
422+ const newPage = currentPdfState . page + delta ;
423+ if ( currentPdfState . actualPdf && newPage >= 1 && newPage <= currentPdfState . actualPdf . numPages ) {
424+ currentPdfState . page = newPage ;
425+ document . querySelectorAll ( '.pdf-controls input[type="number"]' ) . forEach ( input => {
426+ input . value = newPage ;
427+ } ) ;
428+ await updateBothPdfs ( ) ;
429+ }
430+ }
431+
432+ async function setPage ( pageNum ) {
433+ const newPage = parseInt ( pageNum , 10 ) ;
434+ if ( currentPdfState . actualPdf && newPage >= 1 && newPage <= currentPdfState . actualPdf . numPages ) {
435+ currentPdfState . page = newPage ;
436+ document . querySelectorAll ( '.pdf-controls input[type="number"]' ) . forEach ( input => {
437+ input . value = newPage ;
438+ } ) ;
439+ await updateBothPdfs ( ) ;
440+ }
441+ }
442+
443+ async function changeZoom ( delta ) {
444+ const newScale = Math . max ( 0.1 , Math . min ( 5.0 , currentPdfState . scale + delta ) ) ;
445+ currentPdfState . scale = newScale ;
446+ document . querySelectorAll ( '.pdf-controls span:last-of-type' ) . forEach ( span => {
447+ span . textContent = `${ Math . round ( newScale * 100 ) } %` ;
448+ } ) ;
449+ await updateBothPdfs ( ) ;
171450 }
172451
173452 async function init ( ) {
0 commit comments