Recs for JS libraries to let web users select their own signature field's area? #299
-
Okay, strictly speaking, this has nothing to do with pyHanko, so I apologize Matthias if you consider this off-topic and understand if you want to lock/delete it. I am writing a web app to allow users to upload arbitrary PDFs for other users to sign. My users are not necessarily PDF experts so their PDFs likely will not contain preexisting signature fields (but might). So I'm thinking of a workflow where:
I'm mostly writing a good old server-side web app in Python, but for bandwidth and responsivity reasons I think this bit should be client-side, which means Javascript. But the Javascript ecosystem is terrifyingly huge and I'm not that familiar with it. I'm piecing together my own solution, but I thought I'd raise the issue in this forum because this does not seem to be that unusual of a use case, and maybe someone here will immediate react, "Oh, of course, you want PDFSignFieldSelect.js" -- if anyone has such a recommendation, it would be very much appreciated. |
Beta Was this translation helpful? Give feedback.
Replies: 1 comment 1 reply
-
So I ended up rolling my own solution with Mozilla's pdf.js. Both the code quality and the user experience of this is pretty poor, but it gets the job done. I'm including it below if anyone else wants to use this to get started: <script src="https://cdnjs.cloudflare.com/ajax/libs/pdf.js/3.9.179/pdf.js"></script>
<script>
pdfjsLib.GlobalWorkerOptions.workerSrc = "https://cdnjs.cloudflare.com/ajax/libs/pdf.js/3.9.179/pdf.worker.min.js";
let canvasElement = null;
let canvasScale = 1;
let selectionCoords = {beginning: null, end: null};
const inputElement = document.getElementById("upload-unsigned-file-input")
function resetCoords() {
selectionCoords.beginning = null;
selectionCoords.end = null;
for (const ele of inputElement.form.getElementsByTagName("button")) {
ele.remove();
}
}
function repaintPage(page) {
var context = canvasElement.getContext('2d');
var viewport = page.getViewport({ scale: 1, });
canvasScale = canvasElement.offsetWidth / viewport.width;
canvasElement.width = canvasElement.offsetWidth;
canvasElement.height = viewport.height * canvasScale;
var scaledViewport = page.getViewport({ scale: canvasScale, });
var renderContext = {
canvasContext: context,
viewport: scaledViewport,
};
page.render(renderContext).promise.then(() => {
if (selectionCoords.beginning === null) return;
context.arc(
selectionCoords.beginning.x * canvasScale,
selectionCoords.beginning.y * canvasScale,
5,
0,
2 * Math.PI
);
context.fill();
if (selectionCoords.end === null) return;
context.fillStyle = "rgba(0, 0, 0, 0.5)";
context.fillRect(
selectionCoords.beginning.x * canvasScale,
selectionCoords.beginning.y * canvasScale,
selectionCoords.end.x * canvasScale
- selectionCoords.beginning.x * canvasScale,
selectionCoords.end.y * canvasScale
- selectionCoords.beginning.y * canvasScale,
);
context.fillStyle = "black";
context.moveTo(
selectionCoords.end.x * canvasScale,
selectionCoords.end.y * canvasScale,
);
context.arc(
selectionCoords.end.x * canvasScale,
selectionCoords.end.y * canvasScale,
5,
0,
2 * Math.PI
);
context.fill();
});
};
function getMousePosition(e) {
let clientX, clientY;
if (e instanceof MouseEvent) {
clientX = e.clientX;
clientY = e.clientY;
} else {
const tEvent = e.touches[0];
clientX = tEvent.clientX;
clientY = tEvent.clientY;
}
const rect = canvasElement.getBoundingClientRect();
const ret = {
x: clientX - rect.left,
y: clientY - rect.top,
};
return ret;
}
function displayPdf(pdf) {
let pdfCurrentPageNum = 1;
canvasElement = document.createElement("canvas");
canvasElement.style.width = "100%";
inputElement.insertAdjacentElement("afterend", canvasElement);
canvasElement.onmousedown = (e) => {
let coords = getMousePosition(e);
if (selectionCoords.beginning === null || selectionCoords.end !== null) {
resetCoords()
selectionCoords.beginning = {
x: coords.x / canvasScale,
y: coords.y / canvasScale
};
} else {
selectionCoords.end = {
x: coords.x / canvasScale,
y: coords.y / canvasScale
};
inputElement.form.page.value = pdfCurrentPageNum;
inputElement.form.x1.value = Math.round(selectionCoords.beginning.x);
inputElement.form.y1.value = Math.round(selectionCoords.beginning.y);
inputElement.form.x2.value = Math.round(coords.x / canvasScale);
inputElement.form.y2.value = Math.round(coords.y / canvasScale);
submitButton = document.createElement("button");
submitButton.appendChild(document.createTextNode("Upload"));
inputElement.form.insertAdjacentElement("beforeend", submitButton);
}
pdf.getPage(pdfCurrentPageNum).then(repaintPage);
};
document.defaultView.onresize = () => {
pdf.getPage(pdfCurrentPageNum).then(repaintPage);
};
if (pdf.numPages > 1) {
const prevButton = document.createElement("button");
prevButton.appendChild(document.createTextNode("Previous"));
prevButton.disabled = true;
const nextButton = document.createElement("button");
nextButton.appendChild(document.createTextNode("Next"));
function pageBack() {
if (pdfCurrentPageNum <= 1) {
pdfCurrentPageNum = 1;
return;
}
pdfCurrentPageNum--;
resetCoords();
pdf.getPage(pdfCurrentPageNum).then(repaintPage);
nextButton.disabled = false;
if (pdfCurrentPageNum === 1) {
if (pdfCurrentPageNum === 1) {
prevButton.disabled = true;
}
}
function pageForward() {
if (pdfCurrentPageNum >= pdf.numPages) {
pdfCurrentPageNum = pdf.numPages;
return;
}
pdfCurrentPageNum++;
resetCoords();
pdf.getPage(pdfCurrentPageNum).then(repaintPage);
prevButton.disabled = false;
if (pdfCurrentPageNum === pdf.numPages) {
nextButton.disabled = true;
}
}
prevButton.onclick = pageBack;
nextButton.onclick = pageForward;
inputElement.insertAdjacentElement("afterend", prevButton)
prevButton.insertAdjacentElement("afterend", nextButton)
}
pdf.getPage(pdfCurrentPageNum).then(repaintPage);
};
function loadSelectedPdf(e) {
const file = e.target.files[0];
const fileReader = new FileReader();
fileReader.onload = function() {
const typedarray = new Uint8Array(this.result);
const loadingTask = pdfjsLib.getDocument(typedarray);
loadingTask.promise.then(displayPdf);
};
fileReader.readAsArrayBuffer(file);
}
inputElement.onchange = loadSelectedPdf;
</script> |
Beta Was this translation helpful? Give feedback.
So I ended up rolling my own solution with Mozilla's pdf.js. Both the code quality and the user experience of this is pretty poor, but it gets the job done. I'm including it below if anyone else wants to use this to get started: