212 lines
6.7 KiB
JavaScript

(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');
}
};
})();