(function () { // Stick to pdf.js 3.11 UMD + classic worker for compatibility. const PDF_JS_SRC = "https://cdnjs.cloudflare.com/ajax/libs/pdf.js/3.11.174/pdf.min.js"; const WORKER_SRC = "https://cdnjs.cloudflare.com/ajax/libs/pdf.js/3.11.174/pdf.worker.min.js"; const state = { pdfDoc: null, pdfBytes: null, lastViewport: null, pdfJsReady: null, }; function base64ToUint8(base64) { const binStr = atob(base64); const len = binStr.length; const bytes = new Uint8Array(len); for (let i = 0; i < len; i++) { bytes[i] = binStr.charCodeAt(i); } return bytes; } async function reloadFromBase64(base64) { state.pdfBytes = base64ToUint8(base64); state.pdfDoc = await pdfjsLib.getDocument({ data: state.pdfBytes }).promise; return { pages: state.pdfDoc.numPages }; } function dataUrlDownload(dataUrl, filename) { const a = document.createElement('a'); a.href = dataUrl; a.download = filename; document.body.appendChild(a); a.click(); document.body.removeChild(a); } const pointerPads = new Map(); function loadScriptOnce(url) { return new Promise((resolve, reject) => { // If already present, resolve immediately const existing = Array.from(document.getElementsByTagName('script')).find(s => s.src === url); if (existing && existing.dataset.loaded === "true") { resolve(); return; } const script = existing || document.createElement('script'); script.src = url; script.defer = true; script.onload = () => { script.dataset.loaded = "true"; resolve(); }; script.onerror = (e) => reject(new Error(`Script load failed: ${url}`)); if (!existing) { document.head.appendChild(script); } }); } async function ensurePdfJsLoaded() { if (typeof pdfjsLib !== "undefined") { return; } if (!state.pdfJsReady) { state.pdfJsReady = loadScriptOnce(PDF_JS_SRC); } await state.pdfJsReady; if (typeof pdfjsLib === "undefined") { throw new Error("pdfjsLib could not be loaded"); } } window.pdfInterop = { ensureReady: async () => { // Ensure pdf.js is present and the worker path is set explicitly. await ensurePdfJsLoaded(); if (pdfjsLib && pdfjsLib.GlobalWorkerOptions) { if (pdfjsLib.GlobalWorkerOptions.workerSrc !== WORKER_SRC) { pdfjsLib.GlobalWorkerOptions.workerSrc = WORKER_SRC; } } else { throw new Error("pdf.js not available after load"); } }, loadPdf: async (base64) => { await ensurePdfJsLoaded(); try { const result = await reloadFromBase64(base64); if (!result || !result.pages) { throw new Error("PDF has keine Seiten erkannt"); } return result; } catch (err) { console.error("pdfInterop.loadPdf failed", err); throw err; } }, renderPage: async (pageIndex, canvasId, targetWidth) => { await ensurePdfJsLoaded(); if (!state.pdfDoc) { throw new Error('PDF not loaded'); } const page = await state.pdfDoc.getPage(pageIndex + 1); const rawViewport = page.getViewport({ scale: 1 }); const scale = targetWidth / rawViewport.width; const viewport = page.getViewport({ scale }); let canvas = document.getElementById(canvasId); if (!canvas) { // give the UI a tiny delay to render the canvas into the DOM await new Promise(r => setTimeout(r, 40)); canvas = document.getElementById(canvasId); } if (!canvas) { console.error("renderPage: canvas not found", canvasId); throw new Error('Canvas not found'); } const ctx = canvas.getContext('2d'); canvas.width = viewport.width; canvas.height = viewport.height; await page.render({ canvasContext: ctx, viewport }).promise; state.lastViewport = { width: viewport.width, height: viewport.height, pageWidth: rawViewport.width, pageHeight: rawViewport.height, }; return state.lastViewport; }, applySignature: async (payload) => { const { base64, pageIndex, left, top, width, height, renderWidth, renderHeight, dataUrl, autoDate, } = payload; const pdfDoc = await PDFLib.PDFDocument.load(base64ToUint8(base64)); const page = pdfDoc.getPage(pageIndex); const scaleX = page.getWidth() / renderWidth; const scaleY = page.getHeight() / renderHeight; const pngImage = await pdfDoc.embedPng(dataUrl); const drawWidth = width * scaleX; const drawHeight = height * scaleY; const x = left * scaleX; const y = page.getHeight() - (top + height) * scaleY; page.drawImage(pngImage, { x, y, width: drawWidth, height: drawHeight, }); if (autoDate) { const text = `Signed ${new Date().toLocaleString()}`; page.drawText(text, { x, y: y - 14 * scaleY, size: 14 * scaleX, color: PDFLib.rgb(0.11, 0.25, 0.56), }); } const updatedBase64 = await pdfDoc.saveAsBase64({ dataUri: false }); await reloadFromBase64(updatedBase64); return updatedBase64; }, applyText: async (payload) => { const { base64, pageIndex, left, top, width, height, renderWidth, renderHeight, text, fontSize, } = payload; const pdfDoc = await PDFLib.PDFDocument.load(base64ToUint8(base64)); const page = pdfDoc.getPage(pageIndex); const scaleX = page.getWidth() / renderWidth; const scaleY = page.getHeight() / renderHeight; const x = left * scaleX; const y = page.getHeight() - (top + height) * scaleY; page.drawText(text, { x, y, size: fontSize * scaleX, color: PDFLib.rgb(0.2, 0.23, 0.28), }); const updatedBase64 = await pdfDoc.saveAsBase64({ dataUri: false }); await reloadFromBase64(updatedBase64); return updatedBase64; }, downloadPdf: (base64, filename) => { dataUrlDownload(`data:application/pdf;base64,${base64}`, filename); }, initSignaturePad: (canvasId) => { const canvas = document.getElementById(canvasId); if (!canvas) return; const ctx = canvas.getContext('2d'); ctx.lineWidth = 2; ctx.lineCap = 'round'; ctx.strokeStyle = '#1c3d8f'; const padState = { drawing: false, lastX: 0, lastY: 0, }; function start(e) { padState.drawing = true; const rect = canvas.getBoundingClientRect(); padState.lastX = e.clientX - rect.left; padState.lastY = e.clientY - rect.top; } function move(e) { if (!padState.drawing) return; const rect = canvas.getBoundingClientRect(); const x = e.clientX - rect.left; const y = e.clientY - rect.top; ctx.beginPath(); ctx.moveTo(padState.lastX, padState.lastY); ctx.lineTo(x, y); ctx.stroke(); padState.lastX = x; padState.lastY = y; } function end() { padState.drawing = false; } canvas.onpointerdown = start; canvas.onpointermove = move; canvas.onpointerup = end; canvas.onpointerleave = end; pointerPads.set(canvasId, { ctx, canvas }); }, registerDropHandler: (dotNetRef) => { if (window.__pdfDropRegistered) return; window.__pdfDropRegistered = true; const prevent = (e) => { e.preventDefault(); e.stopPropagation(); }; ['dragenter', 'dragover', 'dragleave'].forEach(evt => { document.addEventListener(evt, prevent, false); }); document.addEventListener('drop', (e) => { prevent(e); const files = e.dataTransfer?.files; if (!files || files.length === 0) { return; } const file = files[0]; const reader = new FileReader(); reader.onload = () => { const result = reader.result; if (typeof result === 'string') { const base64 = result.split(',')[1] || result; dotNetRef?.invokeMethodAsync('LoadPdfFromBase64', base64); } }; reader.readAsDataURL(file); }, false); }, clearSignaturePad: (canvasId) => { const pad = pointerPads.get(canvasId); if (!pad) return; pad.ctx.clearRect(0, 0, pad.canvas.width, pad.canvas.height); }, getSignatureDataUrl: (canvasId) => { const pad = pointerPads.get(canvasId); if (!pad) return null; return pad.canvas.toDataURL('image/png'); }, capturePointer: (element, pointerId) => { if (element && element.setPointerCapture) { try { element.setPointerCapture(pointerId); } catch (err) { console.warn('capturePointer failed', err); } } }, releasePointer: (element, pointerId) => { if (element && element.releasePointerCapture) { try { element.releasePointerCapture(pointerId); } catch (err) { console.warn('releasePointer failed', err); } } } }; })();