Signature Crop Tool
body {
margin: 0;
font-family: ‘Segoe UI’, sans-serif;
background: #0d1117;
color: #f0f0f0;
text-align: center;
}
.signature-tool-wrapper {
max-width: 1200px;
margin: auto;
padding: 20px;
}
h1 { color: #00ffff; }
input, button {
margin: 10px;
padding: 10px;
border-radius: 5px;
border: none;
}
input[type=”file”], input[type=”number”], input[type=”text”] {
background: #111;
color: #f0f0f0;
border: 1px solid #333;
}
.size-inputs, .button-group {
display: flex;
justify-content: center;
flex-wrap: wrap;
gap: 10px;
}
canvas#mainCanvas {
display: block;
max-width: 100%;
margin: 10px auto;
border: 1px dashed #00ffff;
touch-action: none;
}
.canvas-wrapper {
display: flex;
flex-wrap: wrap;
gap: 15px;
justify-content: center;
margin-top: 20px;
}
.canvas-box {
background: #1c1c1c;
padding: 10px;
border-radius: 8px;
border: 1px solid #00ffff;
text-align: center;
}
.canvas-box canvas {
display: block;
margin: auto;
max-width: 100%;
height: auto;
border: none;
background: none;
}
button {
background: #00ffff;
color: #000;
cursor: pointer;
font-weight: bold;
}
button:hover {
background: #00cccc;
}
.rename-input {
margin-top: 5px;
width: 90%;
padding: 6px;
background: #111;
border: 1px solid #333;
color: #fff;
}
.canvas-box a {
color: white;
text-decoration: underline;
margin-top: 5px;
display: inline-block;
}
.reset-individual {
background: #ff4444;
color: white;
margin-top: 5px;
padding: 5px 10px;
border-radius: 5px;
cursor: pointer;
}
pdfjsLib.GlobalWorkerOptions.workerSrc = ‘https://cdnjs.cloudflare.com/ajax/libs/pdf.js/2.16.105/pdf.worker.min.js’;
const upload = document.getElementById(‘upload’);
const mainCanvas = document.getElementById(‘mainCanvas’);
const ctx = mainCanvas.getContext(‘2d’);
const outWidthInput = document.getElementById(‘outWidth’);
const outHeightInput = document.getElementById(‘outHeight’);
const cropInfo = document.getElementById(‘cropInfo’);
const signaturePreviews = document.getElementById(‘signaturePreviews’);
let img = new Image();
let cropping = false;
let cropStartX = 0, cropStartY = 0;
let cropRects = [];
// signatureBlobs will now store an object with blob, input element, and original cropped canvas data
let signatureBlobs = [];
let currentStep = 0, maxSignatures = 5;
upload.addEventListener(‘change’, (e) => {
const file = e.target.files[0];
if (!file) return;
resetCanvas(false);
const ext = file.name.toLowerCase();
if (ext.endsWith(‘.pdf’)) {
const reader = new FileReader();
reader.onload = function () {
pdfjsLib.getDocument(reader.result).promise.then(pdf => {
pdf.getPage(1).then(page => {
// Scale up for better resolution
Marriage Officer Sourav Mukherjee from PDF
const viewport = page.getViewport({ scale: 3 }); // Increased scale for PDF
mainCanvas.width = viewport.width;
mainCanvas.height = viewport.height;
const renderContext = { canvasContext: ctx, viewport: viewport };
mainCanvas.style.display = “block”;
page.render(renderContext).promise.then(() => {
img.src = mainCanvas.toDataURL();
drawBoxes();
});
});
});
};
reader.readAsArrayBuffer(file);
} else if ([‘image/jpeg’, ‘image/jpg’, ‘image/png’].includes(file.type)) {
const reader = new FileReader();
reader.onload = function (event) {
img.onload = function () {
mainCanvas.width = img.width;
mainCanvas.height = img.height;
ctx.drawImage(img, 0, 0);
mainCanvas.style.display = “block”;
drawBoxes();
};
img.src = event.target.result;
};
reader.readAsDataURL(file);
} else {
alert(“Only JPG, PNG, or PDF files allowed.”);
}
});
function getCanvasCoordinates(e) {
const rect = mainCanvas.getBoundingClientRect();
const scaleX = mainCanvas.width / rect.width;
const scaleY = mainCanvas.height / rect.height;
let clientX = e.clientX || (e.touches && e.touches[0].clientX);
let clientY = e.clientY || (e.touches && e.touches[0].clientY);
return { x: (clientX – rect.left) * scaleX, y: (clientY – rect.top) * scaleY };
}
mainCanvas.addEventListener(‘mousedown’, e => {
if (currentStep >= maxSignatures) return;
const pos = getCanvasCoordinates(e);
cropStartX = pos.x;
cropStartY = pos.y;
cropping = true;
});
mainCanvas.addEventListener(‘mousemove’, e => {
if (!cropping) return;
const pos = getCanvasCoordinates(e);
drawBoxes();
ctx.strokeStyle = ‘#00ffff’;
ctx.lineWidth = 3;
ctx.strokeRect(cropStartX, cropStartY, pos.x – cropStartX, pos.y – cropStartY);
});
mainCanvas.addEventListener(‘mouseup’, e => finishCrop(getCanvasCoordinates(e)));
mainCanvas.addEventListener(‘touchstart’, e => {
e.preventDefault();
if (currentStep >= maxSignatures) return;
const pos = getCanvasCoordinates(e);
cropStartX = pos.x;
cropStartY = pos.y;
cropping = true;
}, { passive: false });
mainCanvas.addEventListener(‘touchmove’, e => {
e.preventDefault();
if (!cropping) return;
const pos = getCanvasCoordinates(e);
drawBoxes();
ctx.strokeStyle = ‘#ff0000’;
ctx.lineWidth = 2;
ctx.strokeRect(cropStartX, cropStartY, pos.x – cropStartX, pos.y – cropStartY);
}, { passive: false });
mainCanvas.addEventListener(‘touchend’, e => {
e.preventDefault();
finishCrop(getCanvasCoordinates(e.changedTouches[0]));
});
function finishCrop(pos) {
if (!cropping || currentStep >= maxSignatures) return;
cropping = false;
const width = pos.x – cropStartX;
const height = pos.y – cropStartY;
const finalX = Math.min(cropStartX, pos.x);
const finalY = Math.min(cropStartY, pos.y);
const finalWidth = Math.abs(width);
const finalHeight = Math.abs(height);
if (finalWidth < 10 || finalHeight < 10) return; // Minimum crop size
const cropRect = { x: finalX, y: finalY, w: finalWidth, h: finalHeight };
cropRects.push(cropRect);
drawBoxes();
const outW = parseInt(outWidthInput.value) || 300; // Use new default
const outH = parseInt(outHeightInput.value) || 120; // Use new default
createSignaturePreview(cropRect, currentStep, outW, outH);
currentStep++;
cropInfo.textContent = currentStep ctx.strokeRect(r.x, r.y, r.w, r.h));
}
function createSignaturePreview(rect, index, outW, outH) {
const cropCanvas = document.createElement(‘canvas’);
cropCanvas.width = outW;
cropCanvas.height = outH;
const cropCtx = cropCanvas.getContext(‘2d’);
cropCtx.fillStyle = ‘#fff’;
cropCtx.fillRect(0, 0, outW, outH);
// Draw from mainCanvas onto the smaller cropCanvas, scaling as needed
cropCtx.drawImage(mainCanvas, rect.x, rect.y, rect.w, rect.h, 0, 0, outW, outH);
// Function to generate the blob and update the link
const generateAndSetBlob = (canvas, filenameInput, downloadLink) => {
canvas.toBlob(blob => {
// Increased size limit to 40KB
if (blob.size > 40000 && signatureBlobs.length > index) { // Only check size if it’s a new crop, or if the user is changing settings for an existing one.
alert(`Signature ${index + 1} exceeds 40KB. Try smaller crop or adjust output dimensions/quality.`);
// If it’s a new crop, remove it from cropRects and signatureBlobs
if (signatureBlobs.length === index) {
cropRects.pop();
drawBoxes();
currentStep–;
cropInfo.textContent = `Please crop Signature ${currentStep + 1}`;
}
return;
}
if (signatureBlobs.length {
// Re-generate blob to update the download link with the new filename
generateAndSetBlob(cropCanvas, input, link);
});
const resetBtn = document.createElement(‘button’);
resetBtn.className = ‘reset-individual’;
resetBtn.textContent = ‘🗑️ Reset’;
resetBtn.onclick = () => {
// Find the actual index of the box to remove
const boxIndex = Array.from(signaturePreviews.children).indexOf(box);
if (boxIndex > -1) {
signaturePreviews.removeChild(box);
cropRects.splice(boxIndex, 1);
signatureBlobs.splice(boxIndex, 1);
currentStep–;
drawBoxes();
// Update remaining signature numbers in UI
signaturePreviews.querySelectorAll(‘.canvas-box’).forEach((sigBox, i) => {
sigBox.querySelector(‘p’).textContent = `Signature ${i + 1}`;
const sigInput = sigBox.querySelector(‘.rename-input’);
if (sigInput.value.startsWith(‘signature’)) {
sigInput.value = `signature${i + 1}.jpg`;
}
});
cropInfo.textContent = `Please crop Signature ${currentStep + 1}`;
}
};
box.innerHTML = `
Signature ${index + 1}
See also: chhatna marriage registration office near
`;
box.appendChild(cropCanvas);
box.appendChild(input);
box.appendChild(link);
box.appendChild(resetBtn);
signaturePreviews.appendChild(box);
}
function resetCanvas(keepImage = false) {
cropRects = [];
signatureBlobs = [];
currentStep = 0;
signaturePreviews.innerHTML = ”;
cropInfo.textContent = “Please crop Signature 1”;
ctx.clearRect(0, 0, mainCanvas.width, mainCanvas.height);
if (keepImage && img.src) {
ctx.drawImage(img, 0, 0, mainCanvas.width, mainCanvas.height);
} else {
mainCanvas.style.display = “none”;
img.src = “”;
upload.value = ”; // Clear the file input
}
}
async function downloadAll() {
if (!signatureBlobs.length) {
alert(“No signatures to download!”);
return;
}
const zip = new JSZip();
signatureBlobs.forEach(sig => {
const name = sig.input.value.endsWith(‘.jpg’) ? sig.input.value : sig.input.value + ‘.jpg’;
zip.file(name, sig.blob);
});
const content = await zip.generateAsync({ type: “blob” });
const a = document.createElement(‘a’);
a.href = URL.createObjectURL(content);
a.download = “signatures.zip”;
a.click();
}
document.getElementById(“resetBtn”).onclick = () => resetCanvas(true);
document.getElementById(“downloadBtn”).onclick = downloadAll;