/** * Reusable PDF Viewer for MkDocs Material * * Uses PDF.js to render PDF files in a canvas. * Looks for elements with class 'pdf-viewer-container' and a 'data-url' attribute. */ document$.subscribe(({ body }) => { const containers = body.querySelectorAll('.pdf-viewer-container[data-url]'); if (containers.length === 0) return; // Load PDF.js worker if not already loaded if (window['pdfjs-dist/build/pdf'] && !window['pdfjs-dist/build/pdf'].GlobalWorkerOptions.workerSrc) { window['pdfjs-dist/build/pdf'].GlobalWorkerOptions.workerSrc = 'https://cdnjs.cloudflare.com/ajax/libs/pdf.js/2.16.105/pdf.worker.min.js'; } containers.forEach(container => { initPDFViewer(container); }); }); function initPDFViewer(container) { const url = container.getAttribute('data-url'); const canvas = container.querySelector('canvas'); const pageNumDisplay = container.querySelector('.page-num'); const pageCountDisplay = container.querySelector('.page-count'); const prevBtn = container.querySelector('.prev-btn'); const nextBtn = container.querySelector('.next-btn'); const loadingMsg = container.querySelector('.pdf-viewer-loading'); if (!canvas || !url) return; let pdfDoc = null; let pageNum = 1; let pageRendering = false; let pageNumPending = null; const scale = 2.0; const ctx = canvas.getContext('2d'); function renderPage(num) { pageRendering = true; pdfDoc.getPage(num).then(page => { if (loadingMsg) loadingMsg.style.display = 'none'; const viewport = page.getViewport({ scale: scale }); canvas.height = viewport.height; canvas.width = viewport.width; const renderContext = { canvasContext: ctx, viewport: viewport }; const renderTask = page.render(renderContext); renderTask.promise.then(() => { pageRendering = false; if (pageNumPending !== null) { renderPage(pageNumPending); pageNumPending = null; } }); }); if (pageNumDisplay) pageNumDisplay.textContent = num; } function queueRenderPage(num) { if (pageRendering) { pageNumPending = num; } else { renderPage(num); } } if (prevBtn) { prevBtn.addEventListener('click', () => { if (pageNum <= 1) return; pageNum--; queueRenderPage(pageNum); }); } if (nextBtn) { nextBtn.addEventListener('click', () => { if (pageNum >= pdfDoc.numPages) return; pageNum++; queueRenderPage(pageNum); }); } // Load the document const pdfjsLib = window['pdfjs-dist/build/pdf']; if (!pdfjsLib) { console.error('PDF.js library not found'); return; } pdfjsLib.getDocument(url).promise.then(pdfDoc_ => { pdfDoc = pdfDoc_; if (pageCountDisplay) pageCountDisplay.textContent = pdfDoc.numPages; renderPage(pageNum); }).catch(err => { console.error('Error loading PDF:', err); if (loadingMsg) loadingMsg.textContent = "Error loading PDF."; }); }