(function () { const state = { pdfDoc: null, pdfBytes: null, lastViewport: 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(); window.pdfInterop = { ensureReady: () => { if (pdfjsLib && pdfjsLib.GlobalWorkerOptions) { // worker is already loaded via CDN include return; } }, loadPdf: async (base64) => { return await reloadFromBase64(base64); }, renderPage: async (pageIndex, canvasId, targetWidth) => { 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 }); const canvas = document.getElementById(canvasId); if (!canvas) { 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.07, 0.54, 0.26), }); } 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.9, 0.9, 0.9), }); 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 = '#22d3ee'; 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 }); }, 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'); } }; })();