Please bookmark this page to avoid losing your image tool!

Image Passport Page Template Generator

(Free & Supports Bulk Upload)

Drag & drop your images here or

The result will appear here...
You can edit the below JavaScript code to customize the image tool.
async function processImage(
    originalImg,
    passportType = "P",
    countryCode = "UTO", // Utopian States of Oceania
    passportNumber = "L898902C",
    surname = "DOE",
    givenNames = "JANE ANNA",
    nationality = "UTOPIAN",
    dateOfBirth = "01 JAN 1990", // Display format: DD MMM YYYY
    sex = "F",
    placeOfBirth = "CAPITAL CITY",
    dateOfIssue = "04 JUL 2023",
    dateOfExpiry = "04 JUL 2033",
    issuingAuthority = "MINISTRY OF STATE AFFAIRS",
    countryFullName = "UTOPIAN STATES OF OCEANIA",
    personalNumber = "" // Optional, for MRZ
) {
    const canvas = document.createElement('canvas');
    const ctx = canvas.getContext('2d');

    const W = 800; // Width of the passport page
    const H = 560; // Height of the passport page
    canvas.width = W;
    canvas.height = H;

    // Ensure image is decoded, especially if it was just new Image() and src set
    // However, assuming originalImg is a fully loaded Image object as per common interpretation
    try {
        if (!(originalImg.complete && originalImg.naturalHeight !== 0)) {
            await originalImg.decode();
        }
    } catch (error) {
        console.error("Image decoding failed:", error);
        // Fallback: draw error message in photo area if image cannot be decoded
        // This part will be handled later when drawing the photo if needed
    }

    // 1. Background
    ctx.fillStyle = '#F0F8FF'; // AliceBlue, a very light blue, good for official docs
    ctx.fillRect(0, 0, W, H);

    // Optional: A thin border for aesthetics
    ctx.strokeStyle = '#B0C4DE'; // LightSteelBlue
    ctx.lineWidth = 2;
    ctx.strokeRect(5, 5, W - 10, H - 10);
    
    // Placeholder for a subtle background pattern (e.g. country emblem repeated)
    // This is complex; for now, a simple background.

    // 2. Header Text
    ctx.fillStyle = '#003366'; // Dark blue, common official color
    ctx.textAlign = 'center';
    ctx.font = 'bold 28px "Times New Roman", serif';
    ctx.fillText("PASSPORT", W / 2, 45);

    ctx.font = 'bold 20px "Times New Roman", serif';
    ctx.fillText(countryFullName.toUpperCase(), W / 2, 75);

    // Horizontal line separator
    ctx.beginPath();
    ctx.moveTo(30, 90);
    ctx.lineTo(W - 30, 90);
    ctx.strokeStyle = '#003366'; // Dark blue line
    ctx.lineWidth = 1;
    ctx.stroke();

    // 3. Photo Area & Photo
    const photoX = 40;
    const photoY = 110;
    const photoW = 130; 
    const photoH = 170; 

    ctx.strokeStyle = '#666666'; // Border for photo
    ctx.lineWidth = 1;
    ctx.strokeRect(photoX, photoY, photoW, photoH);

    // Draw the image, scaled to fit (cover behavior)
    if (originalImg.complete && originalImg.naturalHeight !== 0) {
        const imgAR = originalImg.width / originalImg.height;
        const photoAR = photoW / photoH;
        let drawW, drawH, imgDrawX, imgDrawY;

        if (imgAR > photoAR) { // Image wider than area
            drawH = photoH;
            drawW = drawH * imgAR;
            imgDrawX = photoX - (drawW - photoW) / 2;
            imgDrawY = photoY;
        } else { // Image taller or same AR
            drawW = photoW;
            drawH = drawW / imgAR;
            imgDrawX = photoX;
            imgDrawY = photoY - (drawH - photoH) / 2;
        }
        // Create a clipping path for the photo
        ctx.save();
        ctx.beginPath();
        ctx.rect(photoX, photoY, photoW, photoH);
        ctx.clip();
        ctx.drawImage(originalImg, imgDrawX, imgDrawY, drawW, drawH);
        ctx.restore();
    } else {
        // Fallback if image is not loaded or errored
        ctx.fillStyle = '#EEEEEE';
        ctx.fillRect(photoX, photoY, photoW, photoH);
        ctx.fillStyle = '#888888';
        ctx.textAlign = 'center';
        ctx.font = '12px Arial';
        ctx.fillText("Photo N/A", photoX + photoW / 2, photoY + photoH / 2);
    }


    // 4. Data Fields Area
    const fieldBlockX = photoX + photoW + 30; // X start for data fields
    let currentY = photoY - 5; // Align data fields with top of photo slightly

    function drawDataEntry(label, value, x, y, valueMaxWidth, labelFont='11px Arial', valueFont='bold 13px Arial') {
        ctx.font = labelFont;
        ctx.fillStyle = '#555'; 
        ctx.fillText(label.toUpperCase(), x, y);

        ctx.font = valueFont;
        ctx.fillStyle = '#000';
        
        let displayValue = value.toUpperCase();
        if (valueMaxWidth && ctx.measureText(displayValue).width > valueMaxWidth) {
            let fittingValue = displayValue;
            while (ctx.measureText(fittingValue + "...").width > valueMaxWidth && fittingValue.length > 0) {
                fittingValue = fittingValue.slice(0, -1);
            }
            displayValue = fittingValue + "...";
        }
        ctx.fillText(displayValue, x, y + 16); 
    }
    
    const col1X = fieldBlockX;
    const col2X = fieldBlockX + 220; 
    const col3X = fieldBlockX + 380;
    
    const valWidthShort = 80;
    const valWidthMedium = W - col2X - 30; // Max width for a value in col2
    const valWidthFull = W - col1X - 30;   // Max width for a value spanning the block

    // Row 1: Type, Code, Passport No.
    drawDataEntry("Type/Type", passportType, col1X, currentY, valWidthShort);
    drawDataEntry("Issuing State Code/Code État", countryCode, col2X, currentY, valWidthShort);
    drawDataEntry("Passport No./Nº Passeport", passportNumber, col3X, currentY, W - col3X - 30);
    currentY += 38;

    // Row 2: Surname
    drawDataEntry("Surname/Nom", surname, col1X, currentY, valWidthFull);
    currentY += 38;

    // Row 3: Given Names
    drawDataEntry("Given Names/Prénoms", givenNames, col1X, currentY, valWidthFull);
    currentY += 38;

    // Row 4: Nationality
    drawDataEntry("Nationality/Nationalité", nationality, col1X, currentY, valWidthFull);
    currentY += 38;

    // Row 5: Date of Birth, Sex
    drawDataEntry("Date of Birth/Date de naissance", dateOfBirth, col1X, currentY, valWidthMedium);
    drawDataEntry("Sex/Sexe", sex, col2X, currentY, valWidthShort);
    currentY += 38;
    
    // Row 6: Place of Birth
    drawDataEntry("Place of Birth/Lieu de naissance", placeOfBirth, col1X, currentY, valWidthFull);
    currentY += 38; // This takes it to currentY for next data row.

    // Signature Area (Below Photo)
    const sigAreaY = photoY + photoH + 10;
    ctx.font = '10px Arial';
    ctx.fillStyle = '#555';
    ctx.textAlign = 'center'; // Center "Signature of bearer"
    ctx.fillText("SIGNATURE OF BEARER / SIGNATURE DU TITULAIRE", photoX + photoW/2, sigAreaY + 10);
    // Line for signature:
    ctx.beginPath();
    ctx.moveTo(photoX + 5, sigAreaY + 15);
    ctx.lineTo(photoX + photoW - 5, sigAreaY + 15);
    ctx.strokeStyle = '#AAAAAA';
    ctx.lineWidth = 0.75;
    ctx.stroke();
    // Placeholder for actual signature image or drawing with italic font as a visual cue
    ctx.font = 'italic 18px "Brush Script MT", cursive'; // Fallback to generic cursive
    ctx.fillStyle = '#333333';
    // Truncate name for signature if too long
    let sigName = surname.length > 12 ? surname.substring(0,10)+"." : surname;
    ctx.fillText(givenNames.split(" ")[0].charAt(0) + ". " + sigName, photoX + photoW/2, sigAreaY + 35);


    // Row 7: Dates of Issue and Expiry (align with potentially lower signature area)
    // currentY is height of data block. Ensure it's below photo+signature block.
    currentY = Math.max(currentY, sigAreaY + 45); 
    
    drawDataEntry("Date of Issue/Date de délivrance", dateOfIssue, col1X, currentY, valWidthMedium);
    drawDataEntry("Date of Expiry/Date d'expiration", dateOfExpiry, col2X, currentY, valWidthMedium);
    currentY += 38;

    // Row 8: Authority
    drawDataEntry("Authority/Autorité", issuingAuthority, col1X, currentY, valWidthFull);
    currentY += 38;


    // 5. Machine-Readable Zone (MRZ)
    const mrzStartY = H - 70; // Y position for the top of MRZ block
    ctx.beginPath(); // Line above MRZ
    ctx.moveTo(20, mrzStartY - 10);
    ctx.lineTo(W - 20, mrzStartY - 10);
    ctx.strokeStyle = '#AAAAAA';
    ctx.lineWidth = 1;
    ctx.stroke();

    ctx.font = 'bold 20px "Courier New", monospace';
    ctx.fillStyle = '#000000';
    ctx.textAlign = 'left';

    function mrzSanitize(str, maxLength) {
        let s = str.toUpperCase().replace(/[^A-Z0-9<]/g, '<').replace(/\s+/g, '<');
        return s.padEnd(maxLength, '<').substring(0, maxLength);
    }

    function formatDateForMRZ(dateStr) {
        try {
            const date = new Date(dateStr.replace(/(\d+)\s([A-Z]+)\s(\d+)/, "$2 $1, $3")); // JAN 01 1990
            const year = date.getFullYear().toString().slice(-2);
            const month = (date.getMonth() + 1).toString().padStart(2, '0');
            const day = date.getDate().toString().padStart(2, '0');
            if (isNaN(date.getFullYear())) return "YYMMDD"; // Invalid date
            return `${year}${month}${day}`;
        } catch (e) {
            return "??????"; // Fallback for truly invalid date strings
        }
    }
    
    function calculateCheckDigit(str) {
        let allFiller = true;
        for (let i = 0; i < str.length; i++) {
            if (str[i] !== '<') { allFiller = false; break; }
        }
        if (allFiller && str.length > 0) return '<';

        const weights = [7, 3, 1];
        let sum = 0;
        for (let i = 0; i < str.length; i++) {
            const char = str[i];
            let val = 0;
            if (char >= '0' && char <= '9') val = parseInt(char, 10);
            else if (char >= 'A' && char <= 'Z') val = char.charCodeAt(0) - 'A'.charCodeAt(0) + 10;
            else if (char === '<') val = 0;
            // Other characters are ignored or treated as 0 per ICAO if they slip through sanitize
            sum += val * weights[i % 3];
        }
        return (sum % 10).toString();
    }
    
    // MRZ Line 1
    let mrzL1 = "P"; 
    mrzL1 += (passportType.toUpperCase() === "P" || passportType === "") ? "<" : mrzSanitize(passportType.charAt(0), 1);
    mrzL1 += mrzSanitize(countryCode, 3);
    const mrzSurname = surname.toUpperCase().replace(/[^A-Z0-9<]/g, '');
    const mrzGivenNames = givenNames.toUpperCase().replace(/[^A-Z0-9<\s]/g, '').replace(/\s+/g, '<');
    const nameField = mrzSurname + "<<" + mrzGivenNames;
    mrzL1 += mrzSanitize(nameField, 39);
    mrzL1 = mrzL1.substring(0, 44);

    // MRZ Line 2
    let mrzL2_PassNum = mrzSanitize(passportNumber, 9);
    let mrzL2_cd_PassNum = calculateCheckDigit(mrzL2_PassNum);
    let mrzL2_Nationality = mrzSanitize(countryCode, 3); // Typically issuing state code
    let mrzL2_DOB = formatDateForMRZ(dateOfBirth);
    let mrzL2_cd_DOB = calculateCheckDigit(mrzL2_DOB);
    let mrzL2_Sex = (sex.charAt(0).toUpperCase() === 'M' || sex.charAt(0).toUpperCase() === 'F') ? sex.charAt(0).toUpperCase() : '<'; 
    let mrzL2_Expiry = formatDateForMRZ(dateOfExpiry);
    let mrzL2_cd_Expiry = calculateCheckDigit(mrzL2_Expiry);
    let mrzL2_PersonalNum = mrzSanitize(personalNumber, 14); 
    let mrzL2_cd_PersonalNum = calculateCheckDigit(mrzL2_PersonalNum);
    
    let overallCDString = mrzL2_PassNum + mrzL2_cd_PassNum +
                          mrzL2_DOB + mrzL2_cd_DOB +
                          mrzL2_Expiry + mrzL2_cd_Expiry +
                          mrzL2_PersonalNum + mrzL2_cd_PersonalNum;
    let mrzL2_OverallCD = calculateCheckDigit(overallCDString);

    let mrzL2 = mrzL2_PassNum + mrzL2_cd_PassNum +
                mrzL2_Nationality +
                mrzL2_DOB + mrzL2_cd_DOB +
                mrzL2_Sex +
                mrzL2_Expiry + mrzL2_cd_Expiry +
                mrzL2_PersonalNum + mrzL2_cd_PersonalNum +
                mrzL2_OverallCD;
    mrzL2 = mrzL2.substring(0,44);

    ctx.fillText(mrzL1, 20, mrzStartY + 5); // Adjusted Y for MRZ text
    ctx.fillText(mrzL2, 20, mrzStartY + 30);


    // Optional: Faint "seal" or emblem as watermark underneath data fields
    ctx.globalAlpha = 0.08; 
    ctx.font = 'bold 80px "Times New Roman", serif';
    ctx.fillStyle = "#003366"; 
    ctx.textAlign = 'center';
    // Simple text based seal: Country code
    // Position it somewhat centrally in the data area
    const sealX = fieldBlockX + (W - fieldBlockX) / 2 - 25;
    const sealY = photoY + (currentY - photoY) / 2 + 20; // Dynamically center based on data block height
    ctx.fillText(countryCode, sealX, sealY);
    ctx.globalAlpha = 1.0; // Reset alpha

    return canvas;
}

Free Image Tool Creator

Can't find the image tool you're looking for?
Create one based on your own needs now!

Description

The Image Passport Page Template Generator is an online tool designed to create a digital representation of a passport page. Users can upload their personal photo and input information such as passport type, country code, passport number, surname, given names, nationality, date of birth, and other relevant details. This tool is useful for individuals needing a visual template for passport applications, travel documents, or personal record keeping. It allows for customization of data fields and ensures compliance with standard passport formatting requirements, making it ideal for anyone preparing to submit passport applications or create mockups for educational purposes.

Leave a Reply

Your email address will not be published. Required fields are marked *

Other Image Tools:

Image Old Map Frame With Compass Rose Decorator

Image Diploma and Degree Certificate Framer

Image Soviet Propaganda Poster Style Generator

Image Yu-Gi-Oh Card Template Creator

Image Ancient Roman Greek Tablet Frame Creator

Image Marriage Certificate Template Creator

Image Video Game Achievement Frame Creator

Image Newspaper Front Page Template Creator

Image Botanical Illustration Frame Creator

Image Vinyl Record Sleeve Template Creator

Vintage Photo Booth Strip Template Generator

Image Cyberpunk Interface Frame Designer

Image Detective Novel Cover Template

Image Achievement Certificate Framer

Image Illuminated Manuscript Frame Generator

Image Art Deco Poster Frame Creator

Image Egyptian Papyrus Scroll Frame Designer

Image Vintage Postage Stamp Frame Creator

Image Magic: The Gathering Card Frame Generator

Image Birth Certificate Template Generator

Image Driver’s License Template Creator

Image Scout Explorer Badge Template Creator

Image Steampunk Document Frame Creator

Image Vintage Scientific Illustration Frame Creator

Image Magazine Cover Template Creator

Image Album Cover Template Creator

Image Medieval Bestiary Page Template

Image Polaroid Instant Photo Frame

Image Pulp Fiction Book Cover Template

Image Medieval Manuscript Frame Creator

Image Vintage Advertisement Poster Generator

Victorian Image Calling Card Template

Image Award Certificate Template

Image Military Insignia Patch Template Creator

Image ID Card Template Creator

Image Film Strip Frame Creator

See All →