You can edit the below JavaScript code to customize the image tool.
Apply Changes
async function processImage(
originalImg,
surname = "PÉREZ",
givenNames = "JUAN CARLOS",
nationality = "ARGENTINA",
dateOfBirth = "01 JAN / ENE 1990",
sex = "M",
dateOfIssue = "01 JAN / ENE 2023",
dateOfExpiry = "01 JAN / ENE 2033",
authority = "RENAPER",
documentNo = "12.345.678",
passportNo = "AAA123456",
signatureText = "Juan Carlos Pérez"
) {
// 1. Setup Canvas
const width = 1250;
const height = 880;
const canvas = document.createElement('canvas');
canvas.width = width;
canvas.height = height;
const ctx = canvas.getContext('2d');
// 2. Load Fonts
try {
const signatureFont = new FontFace('Great Vibes', 'url(https://fonts.gstatic.com/s/greatvibes/v15/RWmMoKWR9v4ksMvYd2g_05o.woff2)');
const mrzFont = new FontFace('OCR-A Std', 'url(https://fonts.gstatic.com/s/ocrastd/v18/PlI-FlOai_a20zia1RMC0w.woff2)');
await Promise.all([signatureFont.load(), mrzFont.load()]);
document.fonts.add(signatureFont);
document.fonts.add(mrzFont);
} catch (e) {
console.error("Font loading failed, continuing with fallback fonts.", e);
}
// 3. Draw Background
const gradient = ctx.createLinearGradient(0, 0, width, height);
gradient.addColorStop(0, '#e8f4ff');
gradient.addColorStop(1, '#d0e8ff');
ctx.fillStyle = gradient;
ctx.fillRect(0, 0, width, height);
// Faint background text "ARG"
ctx.font = 'bold 250px Arial';
ctx.fillStyle = 'rgba(0, 90, 158, 0.08)';
ctx.textAlign = 'center';
ctx.textBaseline = 'middle';
ctx.fillText('ARG', width / 2, height / 2 + 50);
// Simplified Coat of Arms (Sol de Mayo) in the background
ctx.fillStyle = 'rgba(255, 223, 0, 0.15)';
ctx.beginPath();
const sunX = width * 0.65;
const sunY = height * 0.55;
const sunRadius = 180;
ctx.arc(sunX, sunY, sunRadius, 0, Math.PI * 2);
ctx.fill();
for (let i = 0; i < 16; i++) {
ctx.save();
ctx.translate(sunX, sunY);
ctx.rotate(i * Math.PI / 8);
ctx.beginPath();
ctx.moveTo(0, sunRadius);
ctx.lineTo(-20, sunRadius + 50);
ctx.lineTo(20, sunRadius + 50);
ctx.closePath();
ctx.fill();
ctx.restore();
}
ctx.textAlign = 'left';
ctx.textBaseline = 'alphabetic';
// 4. Draw Header
ctx.fillStyle = '#005a9e';
ctx.font = 'bold 30px Arial';
ctx.fillText('REPÚBLICA ARGENTINA', 400, 60);
ctx.font = '24px Arial';
ctx.fillText('MERCOSUR', 400, 95);
ctx.font = 'bold 42px Arial';
ctx.fillText('Pasaporte / Passport', 400, 150);
// 5. Draw Photo and Ghost Image
ctx.drawImage(originalImg, 50, 180, 300, 400);
ctx.strokeStyle = '#ccc';
ctx.strokeRect(50, 180, 300, 400);
ctx.globalAlpha = 0.2;
ctx.drawImage(originalImg, 400, 500, 150, 200);
ctx.globalAlpha = 1.0;
// 6. Draw Data Fields
const startX = 400;
const startY = 200;
const lineSpacing = 65;
const colSpacing1 = 280;
const colSpacing2 = 560;
const drawField = (label, value, x, y) => {
ctx.font = '18px Arial';
ctx.fillStyle = '#666';
ctx.fillText(label, x, y);
ctx.font = 'bold 22px "Courier New", monospace';
ctx.fillStyle = '#000';
ctx.fillText(value.toUpperCase(), x, y + 25);
};
drawField('Tipo / Type', 'P', startX, startY);
drawField('Código del país / Code', 'ARG', startX + colSpacing1, startY);
drawField('N° de Pasaporte / Passport No.', passportNo, startX + colSpacing2, startY);
drawField('Apellidos / Surnames', surname, startX, startY + lineSpacing);
drawField('Nombres / Given Names', givenNames, startX, startY + lineSpacing * 2);
drawField('Nacionalidad / Nationality', nationality, startX, startY + lineSpacing * 3);
drawField('N° de Documento / Document No.', documentNo, startX + colSpacing1, startY + lineSpacing * 3);
drawField('Fecha de Nacimiento / Date of Birth', dateOfBirth, startX, startY + lineSpacing * 4);
drawField('Sexo / Sex', sex, startX + colSpacing1, startY + lineSpacing * 4);
drawField('Fecha de Emisión / Date of Issue', dateOfIssue, startX, startY + lineSpacing * 5);
drawField('Fecha de Vencimiento / Date of Expiry', dateOfExpiry, startX + colSpacing1, startY + lineSpacing * 5);
drawField('Autoridad / Authority', authority, startX, startY + lineSpacing * 6);
// 7. Draw Signature
const sigX = 850;
const sigY = 680;
ctx.font = '18px Arial';
ctx.fillStyle = '#666';
ctx.fillText("Firma del titular / Holder's signature", sigX, sigY + 30);
ctx.beginPath();
ctx.moveTo(sigX, sigY + 10);
ctx.lineTo(sigX + 350, sigY + 10);
ctx.strokeStyle = '#aaa';
ctx.lineWidth = 1;
ctx.stroke();
ctx.fillStyle = '#111';
ctx.font = `50px "Great Vibes", cursive`;
ctx.textAlign = 'center';
ctx.fillText(signatureText, sigX + 175, sigY);
ctx.textAlign = 'left';
// 8. Generate and Draw Machine Readable Zone (MRZ)
const mrzStartY = height - 100;
ctx.fillStyle = '#fff';
ctx.fillRect(0, mrzStartY - 10, width, 110);
ctx.font = '36px "OCR-A Std", "Courier New", monospace';
ctx.fillStyle = '#000';
const charValues = {
'<': 0, '0': 0, '1': 1, '2': 2, '3': 3, '4': 4, '5': 5, '6': 6, '7': 7, '8': 8, '9': 9,
'A': 10, 'B': 11, 'C': 12, 'D': 13, 'E': 14, 'F': 15, 'G': 16, 'H': 17, 'I': 18, 'J': 19,
'K': 20, 'L': 21, 'M': 22, 'N': 23, 'O': 24, 'P': 25, 'Q': 26, 'R': 27, 'S': 28, 'T': 29,
'U': 30, 'V': 31, 'W': 32, 'X': 33, 'Y': 34, 'Z': 35
};
const calculateCheckDigit = (data) => {
const weights = [7, 3, 1];
let sum = 0;
for (let i = 0; i < data.length; i++) {
sum += (charValues[data[i]] || 0) * weights[i % 3];
}
return sum % 10;
};
const normalize = (str, len) => String(str).toUpperCase().replace(/[^A-Z0-9]/g, '').padEnd(len, '<');
const normalizeName = (str) => str.toUpperCase().normalize("NFD").replace(/[\u0300-\u036f]/g, "").replace(/[^A-Z]/g, ' ').replace(/\s+/g, '<');
const monthMap = {JAN: "01", ENE: "01", FEB: "02", MAR: "03", APR: "04", ABR: "04", MAY: "05", JUN: "06", JUL: "07", AUG: "08", AGO: "08", SEP: "09", OCT: "10", NOV: "11", DEC: "12", DIC: "12" };
const parseDate = (dateStr) => {
const parts = dateStr.toUpperCase().split(/[\s/]+/);
const year = parts[parts.length - 1].slice(-2);
const month = monthMap[parts[1]] || "01";
const day = parts[0];
return `${year}${month}${day}`;
};
// Line 1
const mrzName = (normalizeName(surname) + '<<' + normalizeName(givenNames)).padEnd(39, '<');
const line1 = ('P<ARG' + mrzName).substring(0, 44);
// Line 2
const mrzPassportNo = normalize(passportNo, 9);
const passNoCD = calculateCheckDigit(mrzPassportNo);
const mrzDob = parseDate(dateOfBirth);
const dobCD = calculateCheckDigit(mrzDob);
const mrzGender = sex.toUpperCase() === 'M' ? 'M' : sex.toUpperCase() === 'F' ? 'F' : '<';
const mrzExpiry = parseDate(dateOfExpiry);
const expiryCD = calculateCheckDigit(mrzExpiry);
const mrzPersonalNo = normalize(documentNo.replace(/\./g, ''), 14);
const personalNoCD = calculateCheckDigit(mrzPersonalNo);
const composite = `${mrzPassportNo}${passNoCD}${mrzDob}${dobCD}${mrzExpiry}${expiryCD}${mrzPersonalNo.substring(0,14)}${personalNoCD}`;
const finalCD = calculateCheckDigit(composite);
let line2 = mrzPassportNo + passNoCD;
line2 += 'ARG';
line2 += mrzDob + dobCD;
line2 += mrzGender;
line2 += mrzExpiry + expiryCD;
line2 += mrzPersonalNo + personalNoCD;
line2 += finalCD;
line2 = line2.padEnd(44, '<');
ctx.fillText(line1, 30, mrzStartY + 30);
ctx.fillText(line2.substring(0, 44), 30, mrzStartY + 80);
return canvas;
}
Apply Changes