Photo Enhancer
Brightness
Contrast
Extracted Details

Color Aadhaar Maker

1

Upload e-Aadhaar PDF

Select the official PDF downloaded from UIDAI.

2

Enter Password

Enter your 8-character password to unlock locally.

3

Auto-Render HD Card

Our AI text extraction engine will parse your details and render a stunning Color PVC card.

Photo Enhancer
Brightness
Contrast
Extracted Details

Color Aadhaar Maker

1

Upload e-Aadhaar PDF

Select the official PDF downloaded from UIDAI.

2

Enter Password

Enter your 8-character password to unlock locally.

3

Auto-Render HD Card

Our AI text extraction engine will parse your details and render a stunning Color PVC card.

'front.webp', 'samples/frontadhar.webp', 'samples/frontadhar.png', 'https://cdn.jsdelivr.net/gh/imranmobilecenter/IKPRINTHUB@main/samples/frontadhar.webp', 'https://cdn.jsdelivr.net/gh/imranmobilecenter/IKPRINTHUB@main/samples/frontadhar.png' ], back: [ 'back.webp', 'samples/backadhar.webp', 'samples/backadhar.png', 'https://cdn.jsdelivr.net/gh/imranmobilecenter/IKPRINTHUB@main/samples/backadhar.webp', 'https://cdn.jsdelivr.net/gh/imranmobilecenter/IKPRINTHUB@main/samples/backadhar.png' ] }; let templateFrontImg = new Image(); let templateBackImg = new Image(); templateFrontImg.crossOrigin = 'anonymous'; templateBackImg.crossOrigin = 'anonymous'; function loadTemplate(type, imgObj) { const urls = TEMPLATE_SOURCES[type]; let i = 0; function tryNext() { if (i >= urls.length) return; imgObj.src = urls[i++]; imgObj.onload = () => { if (pdfDoc) renderColorCards(); }; imgObj.onerror = tryNext; } tryNext(); } loadTemplate('front', templateFrontImg); loadTemplate('back', templateBackImg); function getRegionalAddressPrefix(lang) { const map = { hindi: 'पता:', marathi: 'पत्ता:', bengali: 'ঠিকানা:', gujarati: 'સરનામું:', telugu: 'చిరునామా:', tamil: 'முகவரி:', kannada: 'ವಿಳಾಸ:' }; return map[lang] || 'पता:'; } async function setLanguage(lang) { selectedCardLanguage = lang; document.querySelectorAll('.lang-btn').forEach(b => b.classList.remove('active')); document.getElementById(`btn-lang-${lang}`)?.classList.add('active'); document.getElementById('group-name-reg').style.display = (lang === 'english') ? 'none' : 'block'; document.getElementById('group-addr-reg').style.display = (lang === 'english') ? 'none' : 'block'; if (lang === 'bengali') { extData.dob_label = 'জন্ম তারিখ / DOB:'; extData.gender_label = 'লিঙ্গ / GENDER:'; } else if (lang === 'hindi') { extData.dob_label = 'जन्म तिथि / DOB:'; extData.gender_label = 'लिंग / GENDER:'; } else if (lang === 'gujarati') { extData.dob_label = 'જન્મ તારીખ / DOB:'; extData.gender_label = 'જાતિ / GENDER:'; } else if (lang === 'telugu') { extData.dob_label = 'పుట్టిన తేదీ / DOB:'; extData.gender_label = 'లింగం / GENDER:'; } else if (lang === 'marathi') { extData.dob_label = 'जन्म तारीख / DOB:'; extData.gender_label = 'लिंग / GENDER:'; } else if (lang === 'tamil') { extData.dob_label = 'பிறந்த தேதி / DOB:'; extData.gender_label = 'பாலினம் / GENDER:'; } else if (lang === 'kannada') { extData.dob_label = 'ಹುಟ್ಟಿದ ದಿನಾಂಕ / DOB:'; extData.gender_label = 'ಲಿಂಗ / GENDER:'; } else { extData.dob_label = 'DOB:'; extData.gender_label = 'GENDER:'; } // Update gender display format according to language if (extData.gender) { const isFemale = /(?:মহিলা|महिला|பெண்|ಹೆಣ್ಣು|ಮಹಿಳೆ|FEMALE)/i.test(extData.gender); const isMale = /(?:পুরুষ|पुरुष|ஆண்|ಗಂಡು|ಪುರುಷ|MALE)/i.test(extData.gender); if (lang === 'english') { extData.gender = isFemale ? 'FEMALE' : (isMale ? 'MALE' : 'TRANSGENDER'); } else if (lang === 'hindi' || lang === 'marathi') { extData.gender = isFemale ? 'महिला / FEMALE' : (isMale ? 'पुरुष / MALE' : 'TRANSGENDER'); } else if (lang === 'bengali') { extData.gender = isFemale ? 'মহিলা / FEMALE' : (isMale ? 'পুরুষ / MALE' : 'TRANSGENDER'); } document.getElementById('inp-gender').value = extData.gender; } if (pdfDoc) { await extractTextData(pdfDoc); renderColorCards(); } else { if (lang !== 'english') await autoTransliterateFromEnglish(false); renderColorCards(); } } function onMobileToggleChange(checked) { includeMobileOnCard = checked; document.getElementById('group-mobile').style.display = checked ? 'block' : 'none'; renderColorCards(); } // Handle File Drop & Upload const fileInput = document.getElementById('file-input'); fileInput.addEventListener('change', (e) => { if (e.target.files.length) handleFile(e.target.files[0]); }); const dropzoneEl = document.getElementById('dropzone'); if (dropzoneEl) { dropzoneEl.addEventListener('dragover', (e) => { e.preventDefault(); }); dropzoneEl.addEventListener('drop', (e) => { e.preventDefault(); if (e.dataTransfer && e.dataTransfer.files && e.dataTransfer.files.length) { handleFile(e.dataTransfer.files[0]); } }); } function extractPasswordCandidatesFromFilename(fileName) { if (!fileName || typeof fileName !== 'string') return ['']; const candidates = ['']; const baseName = fileName.replace(/\.pdf$/i, '').trim(); const yearMatch = baseName.match(/(?:^|[^0-9])((?:19|20)\d{2}|\d{4})(?:[^0-9]|$)/); const year = yearMatch ? yearMatch[1] : null; const words = baseName.match(/[A-Za-z]+/g) || []; const ignored = new Set(['eaadhaar', 'aadhaar', 'aadhar', 'pdf', 'signed', 'download', 'card']); if (year) { for (const w of words) { if (ignored.has(w.toLowerCase())) continue; if (w.length >= 4) { const cand = (w.slice(0, 4) + year).toUpperCase(); if (!candidates.includes(cand)) candidates.push(cand); } } } const directMatches = baseName.match(/[A-Za-z]{4}\d{4}/g); if (directMatches) { directMatches.forEach(m => { const u = m.toUpperCase(); if (!candidates.includes(u)) candidates.push(u); }); } return candidates; } function handleFile(file) { if (file.type !== 'application/pdf' && !file.name.toLowerCase().endsWith('.pdf')) { Swal.fire({ icon: 'warning', title: 'PDF Required', text: 'Please upload a valid e-Aadhaar PDF file.' }); return; } uploadedFileName = file.name || ''; const reader = new FileReader(); reader.onload = async (evt) => { rawPdfBytes = new Uint8Array(evt.target.result); const candidates = extractPasswordCandidatesFromFilename(uploadedFileName); for (const cand of candidates) { const unlocked = await attemptUnlock(cand, true); if (unlocked) return; } document.getElementById('loading-overlay').style.display = 'none'; document.getElementById('password-modal').style.display = 'flex'; document.getElementById('pass-error').style.display = 'none'; document.getElementById('pdf-password').focus(); }; reader.readAsArrayBuffer(file); } async function attemptUnlock(password, silent = false) { if (!silent) document.getElementById('loading-overlay').style.display = 'flex'; try { const dataCopy = new Uint8Array(rawPdfBytes); pdfDoc = await pdfjsLib.getDocument({ data: dataCopy, password: password, cMapUrl: 'https://cdnjs.cloudflare.com/ajax/libs/pdf.js/3.11.174/cmaps/', cMapPacked: true, standardFontDataUrl: 'https://cdnjs.cloudflare.com/ajax/libs/pdf.js/3.11.174/standard_fonts/' }).promise; document.getElementById('password-modal').style.display = 'none'; await processPDF(); return true; } catch (err) { if (!silent) { document.getElementById('loading-overlay').style.display = 'none'; if (err.name === 'PasswordException' || err.code === 1 || err.code === 2) { document.getElementById('password-modal').style.display = 'flex'; if (password !== '') document.getElementById('pass-error').style.display = 'block'; } else { Swal.fire({ icon: 'error', title: 'PDF Error', text: err.message }); } } return false; } } function submitPassword() { const p = document.getElementById('pdf-password').value.trim(); attemptUnlock(p, false); } document.getElementById('pdf-password')?.addEventListener('keypress', (e) => { if (e.key === 'Enter') submitPassword(); }); function preloadImageObj(src) { if (!src) return Promise.resolve(null); return new Promise((resolve) => { const img = new Image(); img.onload = () => resolve(img); img.onerror = () => resolve(null); img.src = src; }); } async function processPDF() { try { document.getElementById('loading-overlay').style.display = 'flex'; const page = await pdfDoc.getPage(1); // 1. Render High-Res Canvas (Scale 8.0) strictly for Photo & QR Cropping const scale = 8.0; const viewport = page.getViewport({ scale }); const canvas = document.createElement('canvas'); canvas.width = viewport.width; canvas.height = viewport.height; const ctx = canvas.getContext('2d', { willReadFrequently: true }); ctx.imageSmoothingEnabled = true; ctx.imageSmoothingQuality = 'high'; await page.render({ canvasContext: ctx, viewport }).promise; // Crop Photo & QR using calibrated presets extPhoto = cropCanvas(canvas, { x: 11.9, y: 76.8, w: 8.3, h: 7.9 }); extQr = cropCanvas(canvas, { x: 76.0, y: 76.4, w: 14.7, h: 11.3 }); cachedPhotoImg = await preloadImageObj(extPhoto); cachedQrImg = await preloadImageObj(extQr); // 2. Extract Text using 1.0 Scale Viewport (Fixes the 8x coordinate mismatch bug!) await extractTextData(pdfDoc); // Show Card Stage & Action Buttons document.getElementById('empty-state').style.display = 'none'; document.getElementById('card-stage-container').style.display = 'flex'; document.getElementById('action-buttons').style.display = 'flex'; document.getElementById('loading-overlay').style.display = 'none'; renderColorCards(); } catch (e) { document.getElementById('loading-overlay').style.display = 'none'; Swal.fire({ icon: 'error', title: 'Processing Error', text: e.message }); } } function cropCanvas(source, box) { const sw = source.width, sh = source.height; const cx = (box.x / 100) * sw, cy = (box.y / 100) * sh; const cw = (box.w / 100) * sw, ch = (box.h / 100) * sh; const out = document.createElement('canvas'); out.width = Math.round(cw); out.height = Math.round(ch); const ctx = out.getContext('2d'); ctx.imageSmoothingEnabled = true; ctx.imageSmoothingQuality = 'high'; ctx.drawImage(source, cx, cy, cw, ch, 0, 0, out.width, out.height); return out.toDataURL('image/png'); } function formatAddressWithSeparateHeader(rawAddr) { if (!rawAddr || typeof rawAddr !== 'string') return ''; let text = rawAddr.trim(); text = text.replace(/([CSWD])\s*\/\s*O\b/gi, '$1/O'); text = text.replace(/\s+,/g, ','); text = text.replace(/\s+([::])/g, '$1'); text = text.replace(/^(?:ঠিকানা|पता|સરનામું|முகவரி|చిరునామా|पत्ता|ವಿಳಾಸ|മേൽവിലാസം|ଠିକଣା|ਪਤਾ)[\s::]*/i, ''); text = text.replace(/^Address[\s::]*/i, '').trim(); text = text.replace(/\n\s*\n+/g, '\n'); return text ? `Address:\n${text}` : ''; } function formatRegionalAddressWithSeparateHeader(rawAddr) { if (!rawAddr || typeof rawAddr !== 'string') return ''; let text = rawAddr.trim(); text = text.replace(/\s+,/g, ','); text = text.replace(/\s+([::])/g, '$1'); const regLabelMatch = text.match(/^(ঠিকানা|पता|સરનામું|முகவரி|చిరునామా|पत्ता|ವಿಳಾಸ|മേൽവിലാസം|ଠିକଣା|ਪਤਾ)[\s::]*/i); if (regLabelMatch) { const label = regLabelMatch[1]; const body = text.slice(regLabelMatch[0].length).trim(); return body ? `${label}:\n${body}` : ''; } const prefix = getRegionalAddressPrefix(selectedCardLanguage); return `${prefix}\n${text}`; } function fixRegionalPdfText(str) { if (!str || typeof str !== 'string') return ''; let s = str; // 1. Remove stray spaces inserted before combining marks / matras / halants / nuktas s = s.replace(/\s+([\u0900-\u0903\u093A-\u094F\u0951-\u0957\u0962\u0963\u0981-\u0983\u09BC-\u09CD\u0A01-\u0A4D\u0A81-\u0ACD\u0B01-\u0B4D\u0B82-\u0BCD\u0C00-\u0C4D\u0C81-\u0CCD\u0D00-\u0D4D])/g, '$1'); // 2. Remove stray spaces right after a halant (्) so half-letters join their next consonant s = s.replace(/([\u094D\u09CD\u0A4D\u0ACD\u0B4D\u0BCD\u0C4D\u0CCD\u0D4D])\s+/g, '$1'); // 3. Fix visual-order chhoti 'i' matra (ि - \u093F) that appears BEFORE a consonant in UIDAI PDFs s = s.replace(/(^|\s|[\u0904-\u0914\u093E-\u094C])\u093F((?:[\u0915-\u0939\u0958-\u095F]\u093C?\u094D)*[\u0915-\u0939\u0958-\u095F]\u093C?)/g, '$1$2\u093F'); // 4. Clean up duplicate matras or broken replacement chars s = s.replace(/\uFFFD/g, '').replace(/\s{2,}/g, ' ').trim(); return s; } function isGarbledRegionalText(str) { if (!str || !str.trim()) return true; const clean = str.trim(); // Check for unattached matras at start of words or excessive replacement characters if (/^[\u093E-\u094D]/.test(clean) || /\s[\u093E-\u094D]/.test(clean)) return true; // Must contain actual regional script letters if (!/[\u0904-\u0939\u0985-\u09B9\u0A85-\u0AB9\u0B85-\u0BB9\u0C05-\u0C39\u0C85-\u0CB9]/.test(clean)) return true; return false; } async function transliterateSingleLineToRegional(text, langCode = 'hi') { if (!text || !text.trim()) return ''; const itcMap = { hindi: 'hi-t-i0-und', marathi: 'mr-t-i0-und', bengali: 'bn-t-i0-und', gujarati: 'gu-t-i0-und', telugu: 'te-t-i0-und', tamil: 'ta-t-i0-und', kannada: 'kn-t-i0-und' }; const itc = itcMap[langCode] || 'hi-t-i0-und'; try { // Tokenize words so punctuation, numbers, and pin codes stay intact const tokens = text.split(/([A-Za-z]+)/); const alphaWords = tokens.filter(t => /^[A-Za-z]+$/.test(t)); if (alphaWords.length === 0) return text; const query = alphaWords.join(' '); const url = `https://inputtools.google.com/request?text=${encodeURIComponent(query)}&itc=${itc}&num=1&cp=0&cs=1&ie=utf-8&oe=utf-8`; const res = await fetch(url); const data = await res.json(); if (Array.isArray(data) && data[0] === 'SUCCESS' && data[1]?.[0]?.[1]?.[0]) { const convertedWords = data[1][0][1][0].split(/\s+/); let wIdx = 0; return tokens.map(tok => { if (/^[A-Za-z]+$/.test(tok)) { return convertedWords[wIdx++] || tok; } return tok; }).join(''); } } catch (e) {} return text; } async function convertEnglishAddressToRegional(enAddr, lang = 'hindi') { if (!enAddr || !enAddr.trim()) return ''; let body = enAddr.replace(/^Address[\s::]*/i, '').trim(); if (!body) return ''; // Replace common Aadhaar address terms before phonetic transliteration for Hindi/Marathi if (lang === 'hindi' || lang === 'marathi') { body = body .replace(/\bS\s*\/\s*O\s*[::]?/gi, 'आत्मज: ') .replace(/\bD\s*\/\s*O\s*[::]?/gi, 'पुत्री: ') .replace(/\bW\s*\/\s*O\s*[::]?/gi, 'पत्नी: ') .replace(/\bC\s*\/\s*O\s*[::]?/gi, 'द्वारा: ') .replace(/\bHouse\s*No\.?\s*/gi, 'मकान सं ') .replace(/\bWard\s*No\.?\s*/gi, 'वार्ड सं ') .replace(/\bVill(?:age)?\s*[-::]?\s*/gi, 'ग्राम ') .replace(/\bPost\s*(?:Office)?\s*[-::]?\s*/gi, 'डाकघर ') .replace(/\bP\.?O\.?\s*[-::]?\s*/gi, 'डाकघर ') .replace(/\bP\.?S\.?\s*[-::]?\s*/gi, 'थाना ') .replace(/\bSub\s*Dist(?:rict)?\s*[-::]?\s*/gi, 'उप-जिला ') .replace(/\bDist(?:rict)?\s*[-::]?\s*/gi, 'जिला ') .replace(/\bJharkhand\b/gi, 'झारखंड') .replace(/\bBihar\b/gi, 'बिहार') .replace(/\bUttar\s*Pradesh\b/gi, 'उत्तर प्रदेश') .replace(/\bMadhya\s*Pradesh\b/gi, 'मध्य प्रदेश') .replace(/\bChhattisgarh\b/gi, 'छत्तीसगढ़') .replace(/\bWest\s*Bengal\b/gi, 'पश्चिम बंगाल') .replace(/\bMaharashtra\b/gi, 'महाराष्ट्र') .replace(/\bRajasthan\b/gi, 'राजस्थान') .replace(/\bGujarat\b/gi, 'गुजरात') .replace(/\bOdisha\b/gi, 'ओडिशा') .replace(/\bDelhi\b/gi, 'दिल्ली'); } const lines = body.split('\n'); const convertedLines = []; for (const ln of lines) { const conv = await transliterateSingleLineToRegional(ln, lang); if (conv.trim()) convertedLines.push(conv.trim()); } const prefix = getRegionalAddressPrefix(lang); return convertedLines.length ? `${prefix}\n${convertedLines.join('\n')}` : ''; } async function autoTransliterateFromEnglish(forceOverwrite = false) { readInputs(); if (selectedCardLanguage === 'english') { await setLanguage('hindi'); return; } if ((forceOverwrite || isGarbledRegionalText(extData.name_reg)) && extData.name_en) { extData.name_reg = await transliterateSingleLineToRegional(extData.name_en, selectedCardLanguage); } if ((forceOverwrite || isGarbledRegionalText(extData.addr_reg)) && extData.addr_en) { extData.addr_reg = await convertEnglishAddressToRegional(extData.addr_en, selectedCardLanguage); } syncInputs(); renderColorCards(); } async function extractTextData(pdf) { let allRawItems = []; for (let p = 1; p <= Math.min(pdf.numPages, 2); p++) { const page = await pdf.getPage(p); const viewport = page.getViewport({ scale: 1.0 }); const pageWidth = viewport.width || 595.28; const pageHeight = viewport.height || 841.89; const content = await page.getTextContent(); content.items.forEach(it => { const rawStr = it.str || ''; if (!rawStr.trim()) return; const x = it.transform ? it.transform[4] : 0; const y = it.transform ? it.transform[5] : 0; const w = it.width || (rawStr.length * 4.2); const isVertical = it.transform ? (Math.abs(it.transform[1]) > 0.05) : false; const pctX = (x / pageWidth) * 100; const pctW = (w / pageWidth) * 100; const pctY = ((pageHeight - y) / pageHeight) * 100; allRawItems.push({ str: rawStr, x, y, w, pctX, pctW, pctY, isVertical, page: p }); }); } function clusterPdfItemsIntoLines(items) { if (!items || items.length === 0) return []; const valid = items.filter(it => it && !it.isVertical && typeof it.str === 'string' && it.str.trim().length > 0); if (valid.length === 0) return []; const sorted = valid.slice().sort((a, b) => a.pctY - b.pctY); const lineBuckets = []; const Y_THRESHOLD = 0.85; sorted.forEach(item => { let matchedBucket = null; for (const bucket of lineBuckets) { if (Math.abs(item.pctY - bucket.avgY) <= Y_THRESHOLD) { matchedBucket = bucket; break; } } if (matchedBucket) { matchedBucket.items.push(item); matchedBucket.avgY = matchedBucket.items.reduce((sum, it) => sum + it.pctY, 0) / matchedBucket.items.length; } else { lineBuckets.push({ avgY: item.pctY, items: [item] }); } }); lineBuckets.sort((a, b) => a.avgY - b.avgY); return lineBuckets.map(b => { b.items.sort((a, b) => a.pctX - b.pctX); let lineStr = ''; for (let i = 0; i < b.items.length; i++) { const curr = b.items[i]; const currText = curr.str.trim(); if (!currText) continue; if (i === 0) { lineStr = currText; } else { const prev = b.items[i - 1]; const gapPct = curr.pctX - (prev.pctX + (prev.pctW || 0)); const startsWithMark = /^[\u0900-\u0903\u093A-\u094F\u0951-\u0957\u0962\u0963\u0981-\u0983\u09BC-\u09CD\u0A01-\u0A4D\u0A81-\u0ACD\u0B01-\u0B4D\u0B82-\u0BCD\u0C00-\u0C4D\u0C81-\u0CCD\u0D00-\u0D4D,.:;\/-]/.test(currText); const endsWithHalant = /[\u094D\u09CD\u0A4D\u0ACD\u0B4D\u0BCD\u0C4D\u0CCD\u0D4D\/-]$/.test(lineStr); // Join without space if glyphs touch or belong to the same Indic word if (gapPct < 0.38 || startsWithMark || endsWithHalant) { lineStr += currText; } else { lineStr += ' ' + currText; } } } return { pctY: b.avgY, text: fixRegionalPdfText(lineStr) }; }).filter(l => l.text.length > 0); } // Spatial Partitioning of Page 1 (Excluding rotated vertical date items!) const p1Items = allRawItems.filter(it => it.page === 1); const verticalItems = p1Items.filter(it => it.isVertical); // Front card text box (to the right of candidate photo: pctX 19.5% to 48.5%) const frontCardItems = p1Items.filter(it => !it.isVertical && it.pctY >= 68 && it.pctY <= 88 && it.pctX >= 19.0 && it.pctX < 48.5); // Back card address box (pctX 49.5% to 76.5% so 'पता:' at ~50.5% is never cut off, and QR code at 76%+ is excluded) const backAddressItems = p1Items.filter(it => !it.isVertical && it.pctY >= 68 && it.pctY <= 88 && it.pctX >= 49.5 && it.pctX <= 76.8); // Top letter section ("To" block with recipient name & address) const letterItems = p1Items.filter(it => !it.isVertical && it.pctY >= 15 && it.pctY < 66 && it.pctX < 62); const frontLines = clusterPdfItemsIntoLines(frontCardItems).map(l => l.text); const backLines = clusterPdfItemsIntoLines(backAddressItems).map(l => l.text); const letterLines = clusterPdfItemsIntoLines(letterItems).map(l => l.text); const allLines = clusterPdfItemsIntoLines(p1Items).map(l => l.text); const verticalText = verticalItems.map(it => it.str.trim()).join(' '); const fullText = allLines.join(' \n ') + ' \n ' + verticalText; // Auto-detect Hindi/Bengali/Regional script if present in PDF if (selectedCardLanguage === 'english') { const sampleAll = (frontLines.join(' ') + ' ' + backLines.join(' ') + ' ' + letterLines.join(' ')); if (/[\u0900-\u097F]/.test(sampleAll)) { selectedCardLanguage = 'hindi'; document.querySelectorAll('.lang-btn').forEach(b => b.classList.remove('active')); document.getElementById('btn-lang-hindi')?.classList.add('active'); document.getElementById('group-name-reg').style.display = 'block'; document.getElementById('group-addr-reg').style.display = 'block'; } else if (/[\u0980-\u09FF]/.test(sampleAll)) { selectedCardLanguage = 'bengali'; document.querySelectorAll('.lang-btn').forEach(b => b.classList.remove('active')); document.getElementById('btn-lang-bengali')?.classList.add('active'); document.getElementById('group-name-reg').style.display = 'block'; document.getElementById('group-addr-reg').style.display = 'block'; } } // 1. Aadhaar Number (12 digits) const cleanText = fullText.replace(/VID\s*:\s*\d{4}\s*\d{4}\s*\d{4}\s*\d{4}/gi, ''); const aadharMatch = cleanText.match(/\b\d{4}\s\d{4}\s\d{4}\b/); if (aadharMatch) extData.aadhaar = aadharMatch[0]; // 2. VID (16 digits) const vidMatch = fullText.match(/VID\s*:\s*(\d{4}\s\d{4}\s\d{4}\s\d{4})/i); if (vidMatch) extData.vid = `VID : ${vidMatch[1]}`; // 3. Issue Date & Details As On Date (Check vertical items first) const issueRegex = /(?:Aadhaar(?:\s*no\.?)?\s*issued|Issue\s*Date|Date\s*of\s*Issue|Download\s*Date|Print\s*Date|\bissued\b)[\s:]*([0-9]{2}[\/-][0-9]{2}[\/-][0-9]{4})/i; let issueMatch = verticalText.match(issueRegex) || fullText.match(issueRegex); if (!issueMatch) { const anyVerticalDate = verticalText.match(/([0-9]{2}[\/-][0-9]{2}[\/-][0-9]{4})/); if (anyVerticalDate) issueMatch = anyVerticalDate; } if (issueMatch) { const matchedDate = issueMatch[1].trim(); extData.issue_date = `Issue Date : ${matchedDate}`; extData.details_as_on = `Details as on : ${matchedDate}`; } else { const todayStr = new Date().toLocaleDateString('en-GB'); extData.issue_date = `Issue Date : ${todayStr}`; extData.details_as_on = `Details as on : ${todayStr}`; } const detailsMatch = fullText.match(/(?:Details\s*as\s*on|As\s*on)[\s:]*([0-9]{2}[\/-][0-9]{2}[\/-][0-9]{4})/i); if (detailsMatch) { extData.details_as_on = `Details as on : ${detailsMatch[1].trim()}`; } // 4. Letter Section ("To" block) Name extraction let letterName = ''; let letterRegName = ''; const toIdx = letterLines.findIndex(l => /^To\b/i.test(l.trim())); if (toIdx !== -1 && toIdx < letterLines.length - 1) { for (let i = toIdx + 1; i < Math.min(toIdx + 6, letterLines.length); i++) { const cand = letterLines[i].trim(); if (!cand) continue; if (/^(?:C\/O|S\/O|W\/O|D\/O|Care of|Son of|Wife of|Daughter of|House|Vill|PO|Dist|Pin|\d)/i.test(cand)) break; if (/[\u0900-\u0D7F]/.test(cand) && !/भारत|सरकार|प्राधिकरण|पता/i.test(cand) && !letterRegName) { letterRegName = fixRegionalPdfText(cand); } else if (/^[A-Za-z\s.]+$/.test(cand) && !/Government|India|Authority|UIDAI|Enrolment/i.test(cand) && !letterName) { letterName = cand; break; } } } // 5. Front Card Details (Regional Name, English Name, DOB, Gender) extData.name_reg = ''; extData.name_en = ''; let dobIdx = frontLines.findIndex(l => /(?:DOB|Birth|তারিখ|तिथि|தேதி|తేదీ|ದಿನಾಂಕ|ಹುಟ್ಟಿದ|[0-9]{2}[\/-][0-9]{2}[\/-][0-9]{4})/i.test(l)); if (dobIdx === -1) dobIdx = frontLines.findIndex(l => /\b(MALE|FEMALE|TRANSGENDER)\b/i.test(l)); if (dobIdx !== -1) { const dobLine = frontLines[dobIdx]; const dobValMatch = dobLine.match(/([0-9]{2}[\/-][0-9]{2}[\/-][0-9]{4}|[0-9]{4})/); if (dobValMatch) extData.dob = dobValMatch[1]; if (selectedCardLanguage !== 'english') { if (/তারিখ/i.test(dobLine) || selectedCardLanguage === 'bengali') extData.dob_label = 'জন্ম তারিখ / DOB:'; else if (/तिथि/i.test(dobLine) || selectedCardLanguage === 'hindi') extData.dob_label = 'जन्म तिथि / DOB:'; else if (selectedCardLanguage === 'marathi') extData.dob_label = 'जन्म तारीख / DOB:'; else if (/தேதி/i.test(dobLine) || selectedCardLanguage === 'tamil') extData.dob_label = 'பிறந்த தேதி / DOB:'; else if (/తేదీ/i.test(dobLine) || selectedCardLanguage === 'telugu') extData.dob_label = 'పుట్టిన తేదీ / DOB:'; else if (/ದಿನಾಂಕ|ಹುಟ್ಟಿದ/i.test(dobLine) || selectedCardLanguage === 'kannada') extData.dob_label = 'ಹುಟ್ಟಿದ ದಿನಾಂಕ / DOB:'; else if (selectedCardLanguage === 'gujarati') extData.dob_label = 'જન્મ તારીખ / DOB:'; else extData.dob_label = 'DOB:'; } else { extData.dob_label = 'DOB:'; } // Gender search let genderLine = frontLines.find((l, idx) => idx >= dobIdx && /(?:পুরুষ|মহিলা|पुरुष|महिला|ஆண்|பெண்|ಗಂಡು|ಹೆಣ್ಣು|ಪುರುಷ|ಮಹಿಳೆ|\bMALE\b|\bFEMALE\b|\bTRANSGENDER\b)/i.test(l)); if (!genderLine) genderLine = fullText.match(/(?:পুরুষ\s*\/\s*MALE|মহিলা\s*\/\s*FEMALE|पुरुष\s*\/\s*MALE|महिला\s*\/\s*FEMALE|ஆண்\s*\/\s*MALE|பெண்\s*\/\s*FEMALE|ಗಂಡು\s*\/\s*MALE|ಹೆಣ್ಣು\s*\/\s*FEMALE|ಪುರುಷ\s*\/\s*MALE|ಮಹಿಳೆ\s*\/\s*FEMALE|\bMALE\b|\bFEMALE\b|\bTRANSGENDER\b)/i)?.[0]; if (genderLine) { if (/(?:মহিলা|महिला|பெண்|ಹೆಣ್ಣು|ಮಹಿಳೆ|FEMALE)/i.test(genderLine)) { extData.gender = (selectedCardLanguage === 'hindi' || selectedCardLanguage === 'marathi') ? 'महिला / FEMALE' : (selectedCardLanguage === 'bengali' ? 'মহিলা / FEMALE' : 'FEMALE'); } else if (/(?:পুরুষ|पुरुष|ஆண்|ಗಂಡು|ಪುರುಷ|MALE)/i.test(genderLine)) { extData.gender = (selectedCardLanguage === 'hindi' || selectedCardLanguage === 'marathi') ? 'पुरुष / MALE' : (selectedCardLanguage === 'bengali' ? 'পুরুষ / MALE' : 'MALE'); } else { extData.gender = 'TRANSGENDER'; } } // Extract Name lines strictly above DOB in Front Card Box const candidateNameLines = frontLines.slice(0, dobIdx).filter(l => { const t = l.trim(); if (!t) return false; if (/Government|India|Authority|UIDAI|Unique|भारत|सरकार|प्राधिकरण|Enrollment|Enrolment|www\.|help@|Address|Issue|issued|Details as on|Mera Aadhaar|Meri Pehchan|आधार/i.test(t)) return false; if (/^\d+$/.test(t.replace(/\s+/g, ''))) return false; return true; }); for (const line of candidateNameLines) { if (/[\u0900-\u0D7F]/.test(line) && !extData.name_reg) { extData.name_reg = fixRegionalPdfText(line); } else if (/^[A-Za-z\s.]+$/.test(line)) { extData.name_en = line.trim(); } } } if (!extData.name_en && letterName) extData.name_en = letterName; if (selectedCardLanguage !== 'english' && isGarbledRegionalText(extData.name_reg) && !isGarbledRegionalText(letterRegName)) { extData.name_reg = letterRegName; } // 6. Back Card Address (Regional & English) let addrRegParts = []; let addrEnParts = []; let seenEnglishAddress = false; const regHeaderRegex = /^(?:ঠিকানা|पता|સરનામું|முகவரி|చిరునామా|पत्ता|ವಿಳಾಸ|മേൽവിലാസം|ଠିକଣା|ਪਤਾ)\s*[:\uff1a]?\s*$/i; const regStartRegex = /(?:ঠিকানা\s*[:\uff1a]?|पता\s*[:\uff1a]?|સરનામું\s*[:\uff1a]?|முகவரி\s*[:\uff1a]?|చిరునామా\s*[:\uff1a]?|पत्ता\s*[:\uff1a]?|ವಿಳಾಸ\s*[:\uff1a]?|മേൽവിലാസം\s*[:\uff1a]?|ଠିକଣା\s*[:\uff1a]?|ਪਤਾ\s*[:\uff1a]?)/i; const enStartRegex = /^(?:Address\s*[:\uff1a]?|C\/O\s*[:\uff1a]?|S\/O\s*[:\uff1a]?|W\/O\s*[:\uff1a]?|D\/O\s*[:\uff1a]?|Care of|Son of|Wife of|Daughter of)/i; const stopRegex = /\b\d{4}\s\d{4}\s\d{4}\b|VID\s*[:\uff1a]|www\.uidai|1947|help@uidai/i; for (const line of backLines) { let trimmed = line.trim(); if (!trimmed) continue; if (/Unique Identification Authority|भारतीय विशिष्ट पहचान|Government of India|भारत सरकार|आधार\s*-\s*आम आदमी/i.test(trimmed)) continue; if (/(?:Date of Birth|DOB|GENDER|MALE|FEMALE)/i.test(trimmed) && !/(?:C\/O|S\/O|W\/O|Village|PO|DIST|PIN)/i.test(trimmed)) continue; trimmed = trimmed.replace(/(?:Details\s*as\s*on|Issue\s*Date)\s*[:\uff1a]?\s*[0-9\/-]+/gi, '').trim(); if (!trimmed) continue; if (stopRegex.test(trimmed)) break; // Check if English address has started if (enStartRegex.test(trimmed) && !/UIDAI|help@|www\./i.test(trimmed)) { seenEnglishAddress = true; addrEnParts.push(trimmed); continue; } if (!seenEnglishAddress) { // Any line containing regional characters OR starting with पता: before English Address is part of Regional Address! if (/[\u0900-\u0D7F]/.test(trimmed) || regStartRegex.test(trimmed) || (addrRegParts.length > 0 && /^\d{6}$/.test(trimmed))) { if (!regHeaderRegex.test(trimmed)) { addrRegParts.push(fixRegionalPdfText(trimmed)); } continue; } // Pure ASCII line without regional script when no English header was seen yet if (/^[A-Za-z0-9\s,.:\/()-]+$/.test(trimmed) && /[A-Za-z]{2,}/.test(trimmed)) { seenEnglishAddress = true; addrEnParts.push(trimmed); } } else { // Inside English address block if (!/[\u0900-\u0D7F]/.test(trimmed)) { addrEnParts.push(trimmed); } } } if (addrEnParts.length > 0) { extData.addr_en = formatAddressWithSeparateHeader(addrEnParts.join('\n')); } else if (letterLines.length > 0) { let fallbackLines = []; let capturing = false; letterLines.forEach(line => { const trimmed = line.trim(); if (/(?:C\/O|S\/O|W\/O|D\/O|Care of|Son of|Wife of)\s*[:\uff1a]?/i.test(trimmed) && !/UIDAI|help@|www\./i.test(trimmed)) { capturing = true; } if (capturing) { if (stopRegex.test(trimmed) || /(?:Download Date|Issue Date|Mobile|Enrolment)/i.test(trimmed)) { capturing = false; } else if (!/[\u0900-\u0D7F]/.test(trimmed)) { fallbackLines.push(trimmed); } } }); if (fallbackLines.length > 0) { extData.addr_en = formatAddressWithSeparateHeader(fallbackLines.join('\n')); } } if (selectedCardLanguage !== 'english') { if (addrRegParts.length > 0) { extData.addr_reg = formatRegionalAddressWithSeparateHeader(addrRegParts.join('\n')); } else { extData.addr_reg = ''; } // Automatic Smart Fallback: If PDF had broken/garbled Hindi fonts or missing Hindi Name/Address, auto-convert from English! if (isGarbledRegionalText(extData.name_reg) && extData.name_en) { extData.name_reg = await transliterateSingleLineToRegional(extData.name_en, selectedCardLanguage); } if ((isGarbledRegionalText(extData.addr_reg) || extData.addr_reg.split('\n').length < 2) && extData.addr_en) { extData.addr_reg = await convertEnglishAddressToRegional(extData.addr_en, selectedCardLanguage); } } else { extData.name_reg = ''; extData.addr_reg = ''; } // 7. Mobile Number const mobileText = (letterLines.length > 0 ? letterLines.join(' \n ') : fullText); const mobileMatch = mobileText.match(/(?:Mobile|Phone|মোবাইল|मोबाइल|தொலைபேசி)[\s:]*([0-9Xx\s]{10,12})/i); if (mobileMatch) { extData.mobile_no = mobileMatch[1].replace(/\s+/g, '').slice(0, 10).trim(); } else { const tenDigitMatch = mobileText.match(/\b([6-9]\d{9})\b/); if (tenDigitMatch) extData.mobile_no = tenDigitMatch[1]; } syncInputs(); } function syncInputs() { document.getElementById('inp-name-en').value = extData.name_en || ''; document.getElementById('inp-name-reg').value = extData.name_reg || ''; document.getElementById('inp-dob').value = extData.dob || ''; document.getElementById('inp-gender').value = extData.gender || ''; document.getElementById('inp-mobile').value = extData.mobile_no || ''; document.getElementById('inp-aadhaar').value = extData.aadhaar || ''; document.getElementById('inp-vid').value = extData.vid || ''; document.getElementById('inp-issue-date').value = extData.issue_date || ''; document.getElementById('inp-details-date').value = extData.details_as_on || ''; document.getElementById('inp-addr-en').value = extData.addr_en || ''; document.getElementById('inp-addr-reg').value = extData.addr_reg || ''; } function readInputs() { extData.name_en = document.getElementById('inp-name-en').value; extData.name_reg = document.getElementById('inp-name-reg').value; extData.dob = document.getElementById('inp-dob').value; extData.gender = document.getElementById('inp-gender').value; extData.mobile_no = document.getElementById('inp-mobile').value; extData.aadhaar = document.getElementById('inp-aadhaar').value; extData.vid = document.getElementById('inp-vid').value; extData.issue_date = document.getElementById('inp-issue-date').value; extData.details_as_on = document.getElementById('inp-details-date').value; extData.addr_en = document.getElementById('inp-addr-en').value; extData.addr_reg = document.getElementById('inp-addr-reg').value; cardCoords.photo.brightness = parseFloat(document.getElementById('photo-bright')?.value) || 110; cardCoords.photo.contrast = parseFloat(document.getElementById('photo-contrast')?.value) || 115; cardCoords.frontText.nameY = parseFloat(document.getElementById('coord-front-name-y')?.value) || 28.0; cardCoords.frontText.x = parseFloat(document.getElementById('coord-front-text-x')?.value) || 33.5; } function renderColorCards() { readInputs(); const W = 2598, H = 1632; const fCanvas = document.getElementById('render-canvas-front'); const bCanvas = document.getElementById('render-canvas-back'); const fCtx = fCanvas.getContext('2d'); const bCtx = bCanvas.getContext('2d'); fCtx.clearRect(0, 0, W, H); bCtx.clearRect(0, 0, W, H); // 1. Draw Front Background Template if (templateFrontImg.complete && templateFrontImg.naturalWidth > 0) { fCtx.drawImage(templateFrontImg, 0, 0, W, H); } else { fCtx.fillStyle = '#ffffff'; fCtx.fillRect(0, 0, W, H); } // 2. Draw Front Photo & Ghost Photo (Synchronous from cached image) if (cachedPhotoImg && cachedPhotoImg.complete && cachedPhotoImg.naturalWidth > 0) { const px = (cardCoords.photo.x / 100) * W; const py = (cardCoords.photo.y / 100) * H; const pw = (cardCoords.photo.w / 100) * W; const ph = (cardCoords.photo.h / 100) * H; fCtx.save(); fCtx.imageSmoothingEnabled = true; fCtx.imageSmoothingQuality = 'high'; fCtx.filter = `brightness(${cardCoords.photo.brightness}%) contrast(${cardCoords.photo.contrast}%)`; fCtx.drawImage(cachedPhotoImg, px, py, pw, ph); if (cardCoords.photo.border) { fCtx.strokeStyle = '#111111'; fCtx.lineWidth = 3; fCtx.strokeRect(px, py, pw, ph); } fCtx.restore(); // Ghost Photo + Micro Aadhaar Number if (cardCoords.ghostPhoto && cardCoords.ghostPhoto.enabled) { const gx = (cardCoords.ghostPhoto.x / 100) * W; const gy = (cardCoords.ghostPhoto.y / 100) * H; const gw = (cardCoords.ghostPhoto.w / 100) * W; const gh = (cardCoords.ghostPhoto.h / 100) * H; fCtx.save(); fCtx.imageSmoothingEnabled = true; fCtx.imageSmoothingQuality = 'high'; fCtx.filter = `grayscale(100%) contrast(125%) brightness(105%)`; fCtx.globalAlpha = (cardCoords.ghostPhoto.opacity || 80) / 100; fCtx.drawImage(cachedPhotoImg, gx, gy, gw, gh); fCtx.strokeStyle = 'rgba(0, 0, 0, 0.25)'; fCtx.lineWidth = 2; fCtx.strokeRect(gx, gy, gw, gh); fCtx.restore(); if (extData.aadhaar) { fCtx.save(); fCtx.fillStyle = '#0f172a'; fCtx.font = `700 ${cardCoords.ghostPhoto.textSize || 29}px "Lato", sans-serif`; fCtx.textAlign = 'center'; fCtx.textBaseline = 'top'; fCtx.fillText(extData.aadhaar, gx + (gw / 2), (cardCoords.ghostPhoto.textY / 100) * H); fCtx.restore(); } } } // 3. Draw Front Text (Name, DOB, Gender, Mobile, Aadhaar, VID, Vertical Issue Date) fCtx.save(); fCtx.fillStyle = '#0f172a'; fCtx.textAlign = 'left'; fCtx.textBaseline = 'top'; const tx = (cardCoords.frontText.x / 100) * W; let curY = (cardCoords.frontText.nameY / 100) * H; const regName = (extData.name_reg || '').trim(); const enName = (extData.name_en || '').trim(); if (selectedCardLanguage !== 'english' && regName) { fCtx.font = `700 ${cardCoords.frontText.nameSize}px "Noto Sans Bengali", "Noto Sans Devanagari", "Noto Sans Tamil", "Noto Sans Telugu", "Noto Sans Gujarati", "Noto Sans Kannada", "Hind", "Lato", sans-serif`; fCtx.fillText(regName, tx, curY); curY += cardCoords.frontText.nameSize + 12; } if (enName) { fCtx.font = `700 ${cardCoords.frontText.nameSize - 2}px "Lato", sans-serif`; fCtx.fillText(enName, tx, curY); curY += cardCoords.frontText.nameSize + 22; } if (extData.dob && extData.dob.trim()) { let dobText = extData.dob.trim(); if (selectedCardLanguage === 'english') { const dm = dobText.match(/([0-9]{2}[\/-][0-9]{2}[\/-][0-9]{4}|[0-9]{4})/); dobText = dm ? `DOB: ${dm[1]}` : (`DOB: ${dobText.replace(/^DOB[\s::]*/i, '')}`); } else if (!/(?:DOB|Birth|তারিখ|तिथि|தேதி|తేదీ|ದಿನಾಂಕ)/i.test(dobText)) { dobText = `${extData.dob_label || 'DOB:'} ${dobText}`; } fCtx.font = `400 ${cardCoords.frontText.bodySize}px "Lato", "Noto Sans Bengali", "Noto Sans Devanagari", "Noto Sans Kannada", "Noto Sans Telugu", "Noto Sans Tamil", "Noto Sans Gujarati", sans-serif`; fCtx.fillText(dobText, tx, curY); curY += cardCoords.frontText.bodySize + 16; } if (extData.gender && extData.gender.trim()) { let genText = extData.gender.trim(); if (selectedCardLanguage === 'english') { if (/FEMALE/i.test(genText)) genText = 'FEMALE'; else if (/MALE/i.test(genText)) genText = 'MALE'; else if (/TRANSGENDER/i.test(genText)) genText = 'TRANSGENDER'; } fCtx.font = `400 ${cardCoords.frontText.bodySize}px "Lato", "Noto Sans Bengali", "Noto Sans Devanagari", "Noto Sans Kannada", "Noto Sans Telugu", "Noto Sans Tamil", "Noto Sans Gujarati", sans-serif`; fCtx.fillText(genText, tx, curY); curY += cardCoords.frontText.bodySize + 14; } if (includeMobileOnCard && extData.mobile_no && extData.mobile_no.trim()) { const mobX = (cardCoords.frontText.mobileX / 100) * W; const mobY = (cardCoords.frontText.mobileY / 100) * H; let mobStr = extData.mobile_no.trim(); if (!/^Mobile\s*:/i.test(mobStr)) mobStr = `Mobile: ${mobStr}`; fCtx.font = `400 ${cardCoords.frontText.mobileSize || 60}px "Lato", sans-serif`; fCtx.fillText(mobStr, mobX, mobY); } if (extData.aadhaar) { fCtx.font = `700 ${cardCoords.frontText.aadhaarSize}px "Lato", sans-serif`; fCtx.textAlign = 'center'; fCtx.fillText(extData.aadhaar, W / 2, (cardCoords.frontText.aadhaarY / 100) * H); } if (extData.vid) { fCtx.font = `600 ${cardCoords.frontText.vidSize}px "Lato", sans-serif`; fCtx.textAlign = 'center'; fCtx.fillText(extData.vid, W / 2, (cardCoords.frontText.vidY / 100) * H); } if (extData.issue_date) { fCtx.save(); fCtx.translate((cardCoords.frontText.issueDateX / 100) * W, (cardCoords.frontText.issueDateY / 100) * H); fCtx.rotate(-Math.PI / 2); fCtx.font = `600 ${cardCoords.frontText.issueDateSize || 50}px "Lato", sans-serif`; fCtx.fillStyle = '#475569'; fCtx.textAlign = 'left'; fCtx.fillText(extData.issue_date, 0, 0); fCtx.restore(); } fCtx.restore(); // 4. Draw Back Background Template if (templateBackImg.complete && templateBackImg.naturalWidth > 0) { bCtx.drawImage(templateBackImg, 0, 0, W, H); } else { bCtx.fillStyle = '#ffffff'; bCtx.fillRect(0, 0, W, H); } // 5. Draw Back Text, QR, Aadhaar, VID, and Vertical Details Date bCtx.save(); bCtx.fillStyle = '#0f172a'; bCtx.textAlign = 'left'; bCtx.textBaseline = 'top'; const bx = (cardCoords.backText.addrX / 100) * W; const maxW = (cardCoords.backText.addrW / 100) * W; let curBackY = (cardCoords.backText.addrRegY / 100) * H; if (selectedCardLanguage !== 'english' && extData.addr_reg && extData.addr_reg.trim()) { bCtx.font = `400 ${cardCoords.backText.addrSize}px "Noto Sans Bengali", "Hind Siliguri", "Noto Sans Devanagari", "Hind", "Noto Sans Gujarati", "Noto Sans Tamil", "Noto Sans Telugu", "Noto Sans Kannada", "Lato", sans-serif`; curBackY = wrapText(bCtx, formatRegionalAddressWithSeparateHeader(extData.addr_reg), bx, curBackY, maxW, cardCoords.backText.addrSize * 1.35); curBackY += 16; } let addrToDraw = (extData.addr_en || '').trim(); if (!addrToDraw && extData.addr_reg) addrToDraw = extData.addr_reg.trim(); if (addrToDraw) { addrToDraw = formatAddressWithSeparateHeader(addrToDraw); bCtx.font = `400 ${cardCoords.backText.addrSize}px "Lato", sans-serif`; wrapText(bCtx, addrToDraw, bx, curBackY, maxW, cardCoords.backText.addrSize * 1.35); } if (cachedQrImg && cachedQrImg.complete && cachedQrImg.naturalWidth > 0) { const qx = (cardCoords.qr.x / 100) * W; const qy = (cardCoords.qr.y / 100) * H; const qs = (cardCoords.qr.size / 100) * W; bCtx.save(); bCtx.imageSmoothingEnabled = true; bCtx.imageSmoothingQuality = 'high'; bCtx.drawImage(cachedQrImg, qx, qy, qs, qs); bCtx.restore(); } if (extData.aadhaar) { bCtx.font = `700 ${cardCoords.backText.aadhaarSize}px "Lato", sans-serif`; bCtx.textAlign = 'center'; bCtx.fillText(extData.aadhaar, W / 2, (cardCoords.backText.aadhaarY / 100) * H); } if (extData.vid) { bCtx.font = `600 ${cardCoords.backText.vidSize}px "Lato", sans-serif`; bCtx.textAlign = 'center'; bCtx.fillText(extData.vid, W / 2, (cardCoords.backText.vidY / 100) * H); } if (extData.details_as_on) { bCtx.save(); bCtx.translate((cardCoords.backText.detailsDateX / 100) * W, (cardCoords.backText.detailsDateY / 100) * H); bCtx.rotate(-Math.PI / 2); bCtx.font = `600 ${cardCoords.backText.detailsDateSize || 50}px "Lato", sans-serif`; bCtx.fillStyle = '#475569'; bCtx.textAlign = 'left'; bCtx.fillText(extData.details_as_on, 0, 0); bCtx.restore(); } bCtx.restore(); updateViewports(); } function wrapText(ctx, text, x, y, maxW, lineH) { if (!text) return y; const paras = String(text).split('\n'); let cy = y; paras.forEach(p => { const words = p.trim().split(/\s+/).filter(Boolean); if (!words.length) return; let line = ''; words.forEach(w => { const test = line ? `${line} ${w}` : w; if (ctx.measureText(test).width > maxW && line) { ctx.fillText(line, x, cy); line = w; cy += lineH; } else { line = test; } }); if (line) { ctx.fillText(line, x, cy); cy += lineH; } }); return cy; } function updateViewports() { const pf = document.getElementById('preview-canvas-front'); const pb = document.getElementById('preview-canvas-back'); const rf = document.getElementById('render-canvas-front'); const rb = document.getElementById('render-canvas-back'); const fCtx = pf.getContext('2d'); const bCtx = pb.getContext('2d'); fCtx.imageSmoothingQuality = 'high'; bCtx.imageSmoothingQuality = 'high'; fCtx.clearRect(0, 0, pf.width, pf.height); bCtx.clearRect(0, 0, pb.width, pb.height); fCtx.drawImage(rf, 0, 0, pf.width, pf.height); bCtx.drawImage(rb, 0, 0, pb.width, pb.height); } // Export Utilities function downloadPNG(side) { renderColorCards(); const canvas = document.getElementById(`render-canvas-${side}`); const link = document.createElement('a'); link.download = `Color_Aadhaar_${side.toUpperCase()}_300DPI.png`; link.href = canvas.toDataURL('image/png'); link.click(); } function downloadMergedPNG() { renderColorCards(); const f = document.getElementById('render-canvas-front'); const b = document.getElementById('render-canvas-back'); const out = document.createElement('canvas'); const gap = 60, margin = 40; out.width = (f.width * 2) + gap + (margin * 2); out.height = f.height + (margin * 2); const ctx = out.getContext('2d'); ctx.fillStyle = '#ffffff'; ctx.fillRect(0, 0, out.width, out.height); ctx.drawImage(f, margin, margin); ctx.drawImage(b, margin + f.width + gap, margin); const link = document.createElement('a'); link.download = 'Color_Aadhaar_Both_Sides_HD.png'; link.href = out.toDataURL('image/png'); link.click(); } function downloadPDF(format = 'a4') { renderColorCards(); const { jsPDF } = window.jspdf; const cw = 85.6, ch = 53.98; const f = document.getElementById('render-canvas-front').toDataURL('image/png'); const b = document.getElementById('render-canvas-back').toDataURL('image/png'); if (format === '4x6') { const doc = new jsPDF({ orientation: 'portrait', unit: 'mm', format: [101.6, 152.4] }); const leftMargin = (101.6 - cw) / 2; doc.addImage(f, 'PNG', leftMargin, 14, cw, ch); doc.addImage(b, 'PNG', leftMargin, 74, cw, ch); doc.save('Color_Aadhaar_4x6.pdf'); } else { const doc = new jsPDF({ orientation: 'portrait', unit: 'mm', format: 'a4' }); doc.addImage(f, 'PNG', 18, 35, cw, ch); doc.addImage(b, 'PNG', 106, 35, cw, ch); doc.save('Color_Aadhaar_A4.pdf'); } }