212 lines
6.7 KiB
JavaScript
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');
|
|
}
|
|
};
|
|
})();
|