PageSourceSearch

https://wildroofjournal.com/wp-content/plugins/pdf-smart-viewer-fo…/assets/js/pdf-smart-viewer.js?ver=1.0.4

js wildroofjournal.com collected 2026-10-01 13:12:58 UTC 34,372 bytes, 882 lines download raw bytes

1/**
2 * Elementor PDF Smart Viewer JavaScript
3 *
4 * @package ElementorPDFSmartViewer
5 * @version 1.0.4
6 */
7
8(function($) {
9    'use strict';
10
11    class PDFSmartViewer {
12        constructor(container) {
13            this.container = container;
14            this.canvas = container.querySelector('.epsv-canvas');
15            
16            // Check if canvas exists and get context
17            if (!this.canvas) {
18                console.error('Canvas element not found');
19                return;
20            }
21            
22            this.ctx = this.canvas.getContext('2d');
23            if (!this.ctx) {
24                console.error('Canvas 2D context not supported');
25                return;
26            }
27            
28            this.pdfUrl = container.getAttribute('data-pdf-url');
29            this.widgetId = container.getAttribute('data-widget-id');
30            this.initialZoom = parseFloat(container.getAttribute('data-initial-zoom')) || 1;
31            
32            // Control visibility settings
33            this.showZoomControls = container.getAttribute('data-show-zoom') === 'true';
34            this.showFullscreen = container.getAttribute('data-show-fullscreen') === 'true';
35            this.showDownload = container.getAttribute('data-show-download') === 'true';
36            this.showPrint = container.getAttribute('data-show-print') === 'true';
37            this.showPageInfo = container.getAttribute('data-show-page-info') === 'true';
38            
39            // State variables
40            this.pdfDoc = null;
41            this.currentPage = 1;
42            this.currentZoom = this.initialZoom;
43            this.isRendering = false;
44            this.pendingPage = null;
45            this.isFullscreen = false;
46            this.currentRenderTask = null;
47            this.hasRenderedFirstPage = false;
48            
49            // DOM elements
50            this.loadingEl = container.querySelector('.epsv-loading');
51            this.errorEl = container.querySelector('.epsv-error');
52            this.zoomLevelEl = container.querySelector('.epsv-zoom-level');
53            this.pageCurrentEl = container.querySelector('.epsv-page-current');
54            this.pageTotalEl = container.querySelector('.epsv-page-total');
55            
56            // Buttons
57            this.prevBtn = container.querySelector('.epsv-prev');
58            this.nextBtn = container.querySelector('.epsv-next');
59            this.zoomInBtn = container.querySelector('.epsv-zoom-in');
60            this.zoomOutBtn = container.querySelector('.epsv-zoom-out');
61            this.fullscreenBtn = container.querySelector('.epsv-fullscreen');
62            this.downloadBtn = container.querySelector('.epsv-download');
63            this.printBtn = container.querySelector('.epsv-print');
64            
65            this.init();
66        }
67        
68        init() {
69            if (!this.canvas || !this.ctx) {
70                this.showError('Canvas not available');
71                return;
72            }
73            
74            if (!this.pdfUrl) {
75                this.showError('No PDF URL provided');
76                return;
77            }
78            
79            // Pre-hide canvas for first page to prevent any blink
80            this.canvas.style.visibility = 'hidden';
81            this.canvas.style.opacity = '0';
82            
83            this.bindEvents();
84            this.loadPDF();
85        }
86        
87        bindEvents() {
88            // Navigation events
89            if (this.prevBtn) {
90                this.prevBtn.addEventListener('click', (e) => {
91                    e.preventDefault();
92                    this.previousPage();
93                });
94            }
95            
96            if (this.nextBtn) {
97                this.nextBtn.addEventListener('click', (e) => {
98                    e.preventDefault();
99                    this.nextPage();
100                });
101            }
102            
103            // Zoom events
104            if (this.zoomInBtn) {
105                this.zoomInBtn.addEventListener('click', (e) => {
106                    e.preventDefault();
107                    this.zoomIn();
108                });
109            }
110            
111            if (this.zoomOutBtn) {
112                this.zoomOutBtn.addEventListener('click', (e) => {
113                    e.preventDefault();
114                    this.zoomOut();
115                });
116            }
117            
118            // Fullscreen events
119            if (this.fullscreenBtn) {
120                this.fullscreenBtn.addEventListener('click', (e) => {
121                    e.preventDefault();
122                    this.toggleFullscreen();
123                });
124            }
125            
126            // Download events
127            if (this.downloadBtn) {
128                this.downloadBtn.addEventListener('click', (e) => {
129                    e.preventDefault();
130                    this.downloadPDF();
131                });
132            }
133            
134            // Print events
135            if (this.printBtn) {
136                this.printBtn.addEventListener('click', (e) => {
137                    e.preventDefault();
138                    this.printPDF();
139                });
140            }
141            
142            // Keyboard events
143            document.addEventListener('keydown', (e) => {
144                if (!this.container.contains(document.activeElement) && !this.isFullscreen) {
145                    return;
146                }
147                
148                switch(e.key) {
149                    case 'ArrowLeft':
150                        e.preventDefault();
151                        this.previousPage();
152                        break;
153                    case 'ArrowRight':
154                        e.preventDefault();
155                        this.nextPage();
156                        break;
157                    case '+':
158                    case '=':
159                        e.preventDefault();
160                        this.zoomIn();
161                        break;
162                    case '-':
163                        e.preventDefault();
164                        this.zoomOut();
165                        break;
166                    case 'f':
167                    case 'F':
168                        e.preventDefault();
169                        this.toggleFullscreen();
170                        break;
171                    case 'Escape':
172                        if (this.isFullscreen) {
173                            e.preventDefault();
174                            this.exitFullscreen();
175                        }
176                        break;
177                }
178            });
179            
180            // Resize events
181            this.debouncedResize = this.debounce(() => {
182                this.renderCurrentPage();
183            }, 250);
184            
185            window.addEventListener('resize', this.debouncedResize);
186            
187            // Fullscreen change events
188            document.addEventListener('fullscreenchange', () => {
189                this.handleFullscreenChange();
190            });
191        }
192        
193        loadPDF() {
194            this.showLoading();
195            
196            // Reset first page rendering flag
197            this.hasRenderedFirstPage = false;
198            
199            // Ensure canvas is initially visible (will be hidden during first page render if needed)
200            this.canvas.style.visibility = 'visible';
201            this.canvas.style.opacity = '1';
202            
203            // Pre-initialize canvas context for incognito mode fix
204            this.preInitializeCanvas();
205            
206            // Set PDF.js worker path
207            if (typeof pdfjsLib !== 'undefined') {
208                pdfjsLib.GlobalWorkerOptions.workerSrc = pdfsmvif_ajax.plugin_url + 'assets/js/pdf.worker.min.js';
209                
210                // Check if it's a remote URL that might have CORS issues
211                const isRemoteUrl = this.isRemoteUrl(this.pdfUrl);
212                
213                if (isRemoteUrl) {
214                    this.loadPDFWithFallback();
215                } else {
216                    this.loadPDFDirect();
217                }
218            } else {
219                this.showError('PDF.js library not loaded');
220            }
221        }
222        
223        preInitializeCanvas() {
224            // Pre-initialize canvas context to prevent incognito mode issues
225            if (this.canvas && this.ctx) {
226                try {
227                    // Force canvas context to be ready
228                    this.canvas.width = 1;
229                    this.canvas.height = 1;
230                    this.ctx.clearRect(0, 0, 1, 1);
231                    
232                    // Test context operations
233                    this.ctx.save();
234                    this.ctx.restore();
235                    
236                    // Reset canvas
237                    this.canvas.width = 0;
238                    this.canvas.height = 0;
239                } catch (e) {
240                    console.warn('Canvas pre-initialization failed:', e);
241                }
242            }
243        }
244        
245        isRemoteUrl(url) {
246            try {
247                const urlObj = new URL(url);
248                const currentOrigin = window.location.origin;
249                return urlObj.origin !== currentOrigin;
250            } catch (e) {
251                return false;
252            }
253        }
254        
255        loadPDFDirect() {
256            pdfjsLib.getDocument(this.pdfUrl).promise
257                .then((pdf) => {
258                    this.pdfDoc = pdf;
259                    this.hideLoading();
260                    this.updatePageTotal();
261                    
262                    // Small delay to ensure everything is ready for first render in incognito mode
263                    setTimeout(() => {
264                        this.renderCurrentPage();
265                        this.updateControls();
266                    }, 50);
267                })
268                .catch((error) => {
269                    console.error('Error loading PDF directly:', error);
270                    this.showError('Failed to load PDF. Please check the URL or file.');
271                });
272        }
273        
274        loadPDFWithFallback() {
275            // For remote URLs, try direct loading first (some servers allow it)
276            
277            pdfjsLib.getDocument(this.pdfUrl).promise
278                .then((pdf) => {
279                    this.pdfDoc = pdf;
280                    this.hideLoading();
281                    this.updatePageTotal();
282                    
283                    // Small delay to ensure everything is ready for first render in incognito mode
284                    setTimeout(() => {
285                        this.renderCurrentPage();
286                        this.updateControls();
287                    }, 50);
288                })
289                .catch((directError) => {
290                    console.error('Error loading PDF directly:', directError);
291                    
292                    // If direct loading fails, try the proxy
293                    const proxyUrl = this.getProxyUrl(this.pdfUrl);
294                    
295                    pdfjsLib.getDocument(proxyUrl).promise
296                        .then((pdf) => {
297                            this.pdfDoc = pdf;
298                            this.hideLoading();
299                            this.updatePageTotal();
300                            
301                            // Small delay to ensure everything is ready for first render in incognito mode
302                            setTimeout(() => {
303                                this.renderCurrentPage();
304                                this.updateControls();
305                            }, 50);
306                        })
307                        .catch((proxyError) => {
308                            console.error('Error loading PDF via proxy:', proxyError);
309                            
310                            // If both PDF.js methods fail, try iframe fallback
311                            this.loadPDFWithIframe();
312                        });
313                });
314        }
315
316        loadPDFWithIframe() {
317            // Hide the canvas and show download link instead
318            if (this.canvas) {
319                this.canvas.style.display = 'none';
320            }
321            
322            // Create a download link for external PDFs
323            const downloadContainer = document.createElement('div');
324            downloadContainer.className = 'epsv-external-pdf';
325            downloadContainer.style.cssText = `
326                text-align: center;
327                padding: 40px 20px;
328                background: #f8f9fa;
329                border: 2px dashed #dee2e6;
330                border-radius: 8px;
331                margin: 20px 0;
332            `;
333            
334            const icon = document.createElement('div');
335            icon.innerHTML = '📄';
336            icon.style.cssText = `
337                font-size: 48px;
338                margin-bottom: 20px;
339            `;
340            
341            const title = document.createElement('h3');
342            title.textContent = 'External PDF Document';
343            title.style.cssText = `
344                margin: 0 0 15px 0;
345                color: #495057;
346                font-size: 18px;
347            `;
348            
349            const description = document.createElement('p');
350            description.textContent = 'This PDF is hosted on an external server. Due to security restrictions, it cannot be displayed directly in the viewer.';
351            description.style.cssText = `
352                margin: 0 0 20px 0;
353                color: #6c757d;
354                line-height: 1.5;
355            `;
356            
357            const downloadBtn = document.createElement('a');
358            downloadBtn.href = this.pdfUrl;
359            downloadBtn.target = '_blank';
360            downloadBtn.textContent = 'Open PDF in New Tab';
361            downloadBtn.className = 'button button-primary';
362            downloadBtn.style.cssText = `
363                display: inline-block;
364                padding: 12px 24px;
365                background: #0073aa;
366                color: white;
367                text-decoration: none;
368                border-radius: 4px;
369                font-weight: 500;
370                transition: background 0.3s;
371            `;
372            
373            downloadBtn.addEventListener('mouseenter', () => {
374                downloadBtn.style.background = '#005a87';
375            });
376            
377            downloadBtn.addEventListener('mouseleave', () => {
378                downloadBtn.style.background = '#0073aa';
379            });
380            
381            // Add elements to container
382            downloadContainer.appendChild(icon);
383            downloadContainer.appendChild(title);
384            downloadContainer.appendChild(description);
385            downloadContainer.appendChild(downloadBtn);
386            
387            // Add to the viewer container
388            const viewerContainer = this.canvas.parentElement;
389            viewerContainer.appendChild(downloadContainer);
390            
391            // Hide loading
392            this.hideLoading();
393            
394            // Hide controls since we're showing a download link
395            if (this.container.querySelector('.epsv-controls')) {
396                this.container.querySelector('.epsv-controls').style.display = 'none';
397            }
398        }
399
400        getProxyUrl(pdfUrl) {
401            // Create proxy URL with nonce for security
402            const proxyUrl = new URL(pdfsmvif_ajax.ajax_url);
403            proxyUrl.searchParams.set('action', 'pdfsmvif_proxy_pdf');
404            proxyUrl.searchParams.set('url', encodeURIComponent(pdfUrl));
405            proxyUrl.searchParams.set('nonce', pdfsmvif_ajax.proxy_nonce);
406            return proxyUrl.toString();
407        }
408        
409        renderCurrentPage() {
410            if (!this.pdfDoc) return;
411
412            // If a render is in progress or being cancelled, queue the latest request
413            if (this.isRendering || this.currentRenderTask) {
414                this.pendingPage = this.currentPage;
415                this.pendingZoom = this.currentZoom;
416                // If a render task exists, cancel it (if not already cancelling)
417                if (this.currentRenderTask && !this.currentRenderTask._cancelling) {
418                    this.currentRenderTask._cancelling = true;
419                    this.currentRenderTask.cancel();
420                }
421                return;
422            }
423
424            this.isRendering = true;
425            this.pendingPage = null;
426            this.pendingZoom = null;
427
428            // IMMEDIATELY hide canvas for first page to prevent any blink
429            if (this.currentPage === 1 && !this.hasRenderedFirstPage) {
430                this.canvas.style.visibility = 'hidden';
431                this.canvas.style.opacity = '0';
432            }
433
434            this.pdfDoc.getPage(this.currentPage).then((page) => {
435                const containerWidth = this.canvas.parentElement.clientWidth;
436                const viewport = page.getViewport({ scale: 1 });
437                const scale = (containerWidth / viewport.width) * this.currentZoom;
438                const scaledViewport = page.getViewport({ scale });
439
440                // Comprehensive fix for incognito mode upside-down first page issue
441                if (this.currentPage === 1 && !this.hasRenderedFirstPage) {
442                    this.renderFirstPageWithFix(page, scaledViewport);
443                } else {
444                    this.renderPageNormally(page, scaledViewport);
445                }
446            }).catch((error) => {
447                if (typeof console !== 'undefined' && console.error) {
448                    console.error('Error getting page:', error);
449                }
450                this.isRendering = false;
451                this.currentRenderTask = null;
452                this.showError('Failed to load page');
453            });
454        }
455        
456        renderFirstPageWithFix(page, scaledViewport) {
457            // Hide canvas during first page rendering to prevent blink
458            this.canvas.style.visibility = 'hidden';
459            
460            // Safety timeout to ensure canvas becomes visible even if something goes wrong
461            const safetyTimeout = setTimeout(() => {
462                this.canvas.style.visibility = 'visible';
463                this.canvas.style.opacity = '1';
464                console.warn('Safety timeout: Canvas made visible after 3 seconds');
465            }, 3000);
466            
467            // Multiple strategies to fix incognito mode upside-down issue
468            const attemptRender = (attempt = 0) => {
469                if (attempt >= 3) {
470                    // Final fallback - render normally but keep hidden until ready
471                    this.renderPageNormallyWithVisibilityControl(page, scaledViewport);
472                    return;
473                }
474                
475                // Strategy 1: Force canvas reset and context recreation
476                this.canvas.width = scaledViewport.width;
477                this.canvas.height = scaledViewport.height;
478                this.ctx = this.canvas.getContext('2d');
479                
480                // Strategy 2: Clear canvas completely
481                this.ctx.clearRect(0, 0, this.canvas.width, this.canvas.height);
482                
483                // Strategy 3: Force a small delay to ensure context is ready
484                setTimeout(() => {
485                    try {
486                        // Strategy 4: Test context with a simple operation
487                        this.ctx.save();
488                        this.ctx.restore();
489                        
490                        // Strategy 5: Render with explicit viewport settings
491                        const renderContext = {
492                            canvasContext: this.ctx,
493                            viewport: scaledViewport,
494                            transform: null // Force PDF.js to use default transform
495                        };
496                        
497                        this.currentRenderTask = page.render(renderContext);
498                        
499                        this.currentRenderTask.promise.then(() => {
500                            // Clear safety timeout
501                            clearTimeout(safetyTimeout);
502                            
503                            // Show canvas only after successful render
504                            this.canvas.style.visibility = 'visible';
505                            this.canvas.style.opacity = '1';
506                            
507                            this.isRendering = false;
508                            this.currentRenderTask = null;
509                            this.hasRenderedFirstPage = true;
510                            this.updatePageInfo();
511                            this.updateControls();
512                            
513                            // If a render is pending, start it now
514                            if (this.pendingPage !== null) {
515                                this.currentPage = this.pendingPage;
516                                this.currentZoom = this.pendingZoom || this.currentZoom;
517                                this.pendingPage = null;
518                                this.pendingZoom = null;
519                                this.renderCurrentPage();
520                            }
521                        }).catch((error) => {
522                            // Clear safety timeout
523                            clearTimeout(safetyTimeout);
524                            
525                            if (error && error.name !== 'RenderingCancelled') {
526                                console.error('First page render error, retrying:', error);
527                                // Retry with next attempt
528                                attemptRender(attempt + 1);
529                            } else {
530                                this.isRendering = false;
531                                this.currentRenderTask = null;
532                                // Show canvas even on cancellation
533                                this.canvas.style.visibility = 'visible';
534                                this.canvas.style.opacity = '1';
535                            }
536                        });
537                        
538                    } catch (e) {
539                        console.error('Context error, retrying:', e);
540                        attemptRender(attempt + 1);
541                    }
542                }, 50 + (attempt * 25)); // Increasing delay with each attempt
543            };
544            
545            attemptRender();
546        }
547        
548        renderPageNormallyWithVisibilityControl(page, scaledViewport) {
549            // Safety timeout to ensure canvas becomes visible
550            const safetyTimeout = setTimeout(() => {
551                this.canvas.style.visibility = 'visible';
552                this.canvas.style.opacity = '1';
553                console.warn('Safety timeout: Canvas made visible after 2 seconds (fallback)');
554            }, 2000);
555            
556            // Set canvas dimensions
557            this.canvas.width = scaledViewport.width;
558            this.canvas.height = scaledViewport.height;
559            
560            // Clear the canvas
561            this.ctx.clearRect(0, 0, this.canvas.width, this.canvas.height);
562            
563            // Render the page
564            const renderContext = {
565                canvasContext: this.ctx,
566                viewport: scaledViewport
567            };
568
569            this.currentRenderTask = page.render(renderContext);
570
571            this.currentRenderTask.promise.then(() => {
572                // Clear safety timeout
573                clearTimeout(safetyTimeout);
574                
575                // Show canvas after successful render
576                this.canvas.style.visibility = 'visible';
577                this.canvas.style.opacity = '1';
578                
579                this.isRendering = false;
580                this.currentRenderTask = null;
581                this.hasRenderedFirstPage = true;
582                this.updatePageInfo();
583                this.updateControls();
584                
585                // If a render is pending, start it now
586                if (this.pendingPage !== null) {
587                    this.currentPage = this.pendingPage;
588                    this.currentZoom = this.pendingZoom || this.currentZoom;
589                    this.pendingPage = null;
590                    this.pendingZoom = null;
591                    this.renderCurrentPage();
592                }
593            }).catch((error) => {
594                // Clear safety timeout
595                clearTimeout(safetyTimeout);
596                
597                // Show canvas even on error
598                this.canvas.style.visibility = 'visible';
599                this.canvas.style.opacity = '1';
600                
601                this.isRendering = false;
602                this.currentRenderTask = null;
603                // Only show error if it's not a cancellation
604                if (error && error.name !== 'RenderingCancelled') {
605                    if (typeof console !== 'undefined' && console.error) {
606                        console.error('Error rendering page:', error);
607                    }
608                    this.showError('Failed to render page');
609                }
610                // If a render is pending, start it now
611                if (this.pendingPage !== null) {
612                    this.currentPage = this.pendingPage;
613                    this.currentZoom = this.pendingZoom || this.currentZoom;
614                    this.pendingPage = null;
615                    this.pendingZoom = null;
616                    this.renderCurrentPage();
617                }
618            });
619        }
620        
621        renderPageNormally(page, scaledViewport) {
622            // Ensure canvas is visible for subsequent pages
623            this.canvas.style.visibility = 'visible';
624            
625            // Set canvas dimensions
626            this.canvas.width = scaledViewport.width;
627            this.canvas.height = scaledViewport.height;
628            
629            // Clear the canvas
630            this.ctx.clearRect(0, 0, this.canvas.width, this.canvas.height);
631            
632            // Render the page
633            const renderContext = {
634                canvasContext: this.ctx,
635                viewport: scaledViewport
636            };
637
638            this.currentRenderTask = page.render(renderContext);
639
640            this.currentRenderTask.promise.then(() => {
641                this.isRendering = false;
642                this.currentRenderTask = null;
643                this.updatePageInfo();
644                this.updateControls();
645                
646                // Mark that we've rendered the first page successfully
647                if (this.currentPage === 1) {
648                    this.hasRenderedFirstPage = true;
649                }
650
651                // If a render is pending, start it now
652                if (this.pendingPage !== null) {
653                    this.currentPage = this.pendingPage;
654                    this.currentZoom = this.pendingZoom || this.currentZoom;
655                    this.pendingPage = null;
656                    this.pendingZoom = null;
657                    this.renderCurrentPage();
658                }
659            }).catch((error) => {
660                this.isRendering = false;
661                this.currentRenderTask = null;
662                // Only show error if it's not a cancellation
663                if (error && error.name !== 'RenderingCancelled') {
664                    if (typeof console !== 'undefined' && console.error) {
665                        console.error('Error rendering page:', error);
666                    }
667                    this.showError('Failed to render page');
668                }
669                // If a render is pending, start it now
670                if (this.pendingPage !== null) {
671                    this.currentPage = this.pendingPage;
672                    this.currentZoom = this.pendingZoom || this.currentZoom;
673                    this.pendingPage = null;
674                    this.pendingZoom = null;
675                    this.renderCurrentPage();
676                }
677            });
678        }
679        
680        previousPage() {
681            if (this.currentPage <= 1) return;
682            this.currentPage--;
683            this.renderCurrentPage();
684        }
685        
686        nextPage() {
687            if (!this.pdfDoc || this.currentPage >= this.pdfDoc.numPages) return;
688            this.currentPage++;
689            this.renderCurrentPage();
690        }
691        
692        zoomIn() {
693            this.currentZoom = Math.min(this.currentZoom * 1.25, 3);
694            this.updateZoomLevel();
695            this.renderCurrentPage();
696        }
697        
698        zoomOut() {
699            this.currentZoom = Math.max(this.currentZoom / 1.25, 0.25);
700            this.updateZoomLevel();
701            this.renderCurrentPage();
702        }
703        
704        toggleFullscreen() {
705            if (this.isFullscreen) {
706                this.exitFullscreen();
707            } else {
708                this.enterFullscreen();
709            }
710        }
711        
712        enterFullscreen() {
713            if (this.container.requestFullscreen) {
714                this.container.requestFullscreen();
715            } else if (this.container.webkitRequestFullscreen) {
716                this.container.webkitRequestFullscreen();
717            } else if (this.container.msRequestFullscreen) {
718                this.container.msRequestFullscreen();
719            }
720        }
721        
722        exitFullscreen() {
723            if (document.exitFullscreen) {
724                document.exitFullscreen();
725            } else if (document.webkitExitFullscreen) {
726                document.webkitExitFullscreen();
727            } else if (document.msExitFullscreen) {
728                document.msExitFullscreen();
729            }
730        }
731        
732        handleFullscreenChange() {
733            this.isFullscreen = !!document.fullscreenElement;
734            this.container.classList.toggle('epsv-fullscreen', this.isFullscreen);
735            
736            if (this.isFullscreen) {
737                this.renderCurrentPage();
738            }
739        }
740        
741        downloadPDF() {
742            const link = document.createElement('a');
743            link.href = this.pdfUrl;
744            link.download = this.getFileNameFromUrl(this.pdfUrl);
745            document.body.appendChild(link);
746            link.click();
747            document.body.removeChild(link);
748        }
749        
750        printPDF() {
751            const printWindow = window.open(this.pdfUrl, '_blank');
752            if (printWindow) {
753                printWindow.onload = function() {
754                    printWindow.print();
755                };
756            }
757        }
758        
759        updatePageInfo() {
760            if (this.pageCurrentEl) {
761                this.pageCurrentEl.textContent = this.currentPage;
762            }
763        }
764        
765        updatePageTotal() {
766            if (this.pageTotalEl && this.pdfDoc) {
767                this.pageTotalEl.textContent = this.pdfDoc.numPages;
768            }
769        }
770        
771        updateZoomLevel() {
772            if (this.zoomLevelEl) {
773                this.zoomLevelEl.textContent = Math.round(this.currentZoom * 100) + '%';
774            }
775        }
776        
777        updateControls() {
778            // Update navigation buttons
779            if (this.prevBtn) {
780                this.prevBtn.disabled = this.currentPage <= 1;
781            }
782            
783            if (this.nextBtn) {
784                this.nextBtn.disabled = !this.pdfDoc || this.currentPage >= this.pdfDoc.numPages;
785            }
786            
787            // Update zoom buttons
788            if (this.zoomInBtn) {
789                this.zoomInBtn.disabled = this.currentZoom >= 3;
790            }
791            
792            if (this.zoomOutBtn) {
793                this.zoomOutBtn.disabled = this.currentZoom <= 0.25;
794            }
795        }
796        
797        showLoading() {
798            if (this.loadingEl) {
799                this.loadingEl.style.display = 'block';
800            }
801            if (this.errorEl) {
802                this.errorEl.style.display = 'none';
803            }
804        }
805        
806        hideLoading() {
807            if (this.loadingEl) {
808                this.loadingEl.style.display = 'none';
809            }
810        }
811        
812        showError(message) {
813            this.hideLoading();
814            if (this.errorEl) {
815                const span = this.errorEl.querySelector('span');
816                if (span) {
817                    span.textContent = message;
818                }
819                this.errorEl.style.display = 'block';
820            }
821        }
822        
823        getFileNameFromUrl(url) {
824            try {
825                const urlObj = new URL(url);
826                const pathname = urlObj.pathname;
827                const filename = pathname.substring(pathname.lastIndexOf('/') + 1);
828                return filename || 'document.pdf';
829            } catch (e) {
830                return 'document.pdf';
831            }
832        }
833        
834        debounce(func, wait) {
835            let timeout;
836            return function executedFunction(...args) {
837                clearTimeout(timeout);
838                timeout = setTimeout(() => {
839                    func.apply(this, args);
840                }, wait);
841            };
842        }
843        
844        destroy() {
845            // Cancel any ongoing render task
846            if (this.currentRenderTask) {
847                this.currentRenderTask.cancel();
848                this.currentRenderTask = null;
849            }
850            
851            // Remove event listeners
852            if (this.debouncedResize) {
853                window.removeEventListener('resize', this.debouncedResize);
854            }
855            
856            // Clear any pending operations
857            this.isRendering = false;
858            this.pendingPage = null;
859        }
860    }
861    
862    // Initialize viewers when DOM is ready
863    $(document).ready(function() {
864        const containers = document.querySelectorAll('.epsv-container');
865        containers.forEach(container => {
866            new PDFSmartViewer(container);
867        });
868    });
869    
870    // Re-initialize on Elementor frontend init
871    $(window).on('elementor/frontend/init', function() {
872        elementorFrontend.hooks.addAction('frontend/element_ready/global', function($scope) {
873            const containers = $scope.find('.epsv-container');
874            containers.each(function() {
875                if (!this.epsvInstance) {
876                    this.epsvInstance = new PDFSmartViewer(this);
877                }
878            });
879        });
880    });
881    
882})(jQuery); 

Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.