3
3
< head >
4
4
< title > jsPDF Test Report</ title >
5
5
< 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 >
6
7
< style >
7
8
/* Preserve existing styles that are still needed */
8
- .pdf-container iframe {
9
+ .pdf-container {
9
10
width : 100% ;
10
11
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;
12
41
}
13
42
.differences {
14
43
white-space : pre-wrap;
63
92
width : 1rem ;
64
93
height : 1rem ;
65
94
}
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
+ }
66
125
</ style >
67
126
</ head >
68
127
< body class ="antialiased ">
@@ -95,9 +154,17 @@ <h3 class="text-sm font-medium text-purple-900">Failing PDFs</h3>
95
154
96
155
< script >
97
156
let selectedItem = null ;
157
+ let currentPdfState = {
158
+ scale : 1.0 ,
159
+ page : 1 ,
160
+ actualPdf : null ,
161
+ referencePdf : null
162
+ } ;
98
163
99
164
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>` ;
100
165
166
+ pdfjsLib . GlobalWorkerOptions . workerSrc = 'https://cdnjs.cloudflare.com/ajax/libs/pdf.js/3.11.174/pdf.worker.min.js' ;
167
+
101
168
function formatSummary ( results ) {
102
169
return `
103
170
<div class="flex items-center justify-between">
@@ -119,7 +186,188 @@ <h3 class="text-sm font-medium text-purple-900">Failing PDFs</h3>
119
186
` ;
120
187
}
121
188
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 ) {
123
371
const details = document . getElementById ( 'details' ) ;
124
372
125
373
// Update selected state in sidebar
@@ -129,29 +377,23 @@ <h3 class="text-sm font-medium text-purple-900">Failing PDFs</h3>
129
377
document . getElementById ( item . id ) . classList . add ( 'selected' ) ;
130
378
selectedItem = item . id ;
131
379
380
+ // Reset PDF state
381
+ currentPdfState = {
382
+ scale : 1.0 ,
383
+ page : 1 ,
384
+ actualPdf : null ,
385
+ referencePdf : null
386
+ } ;
387
+
132
388
details . innerHTML = `
133
389
<div class="card">
134
390
<div class="p-4 border-b">
135
391
<h2 class="text-lg font-semibold text-purple-900">${ item . name } </h2>
136
392
</div>
137
393
<div class="p-4">
138
394
<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' ) }
155
397
</div>
156
398
${ item . error ? `
157
399
<div class="mt-6">
@@ -168,6 +410,43 @@ <h3 class="font-medium text-purple-900">Differences</h3>
168
410
</div>
169
411
</div>
170
412
` ;
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 ( ) ;
171
450
}
172
451
173
452
async function init ( ) {
0 commit comments