document.addEventListener('DOMContentLoaded', () => { if (window.pdfjsLib) { pdfjsLib.GlobalWorkerOptions.workerSrc = 'https://cdnjs.cloudflare.com/ajax/libs/pdf.js/3.10.111/pdf.worker.min.js'; } const $ = (sel) => document.querySelector(sel); const fileInput = $('#wpfocr-file'); const drop = $('#wpfocr-drop'); const startBtn = $('#wpfocr-start'); const clearBtn = $('#wpfocr-clear'); const langSelect = $('#wpfocr-lang'); const enhanceCb = $('#wpfocr-enhance'); const statusEl = $('#wpfocr-status'); const bar = $('#wpfocr-bar'); const output = $('#wpfocr-output'); const copyBtn = $('#wpfocr-copy'); const dlBtn = $('#wpfocr-download'); const cameraBtn = $('#wpfocr-camera'); const camWrap = $('#wpfocr-camera-wrap'); const camVideo = $('#wpfocr-video'); const camClose = $('#wpfocr-cam-close'); const camCapture = $('#wpfocr-capture'); const canvas = $('#wpfocr-canvas'); const ctx = canvas.getContext('2d'); let queue = []; function status(msg) { statusEl.textContent = msg; } const addFiles = (files) => { let added = 0; for (const f of files) { if (!f.type) continue; if (f.type.startsWith('image/') || f.type === 'application/pdf') { queue.push(f); added++; } } status(`Added ${added} file(s). Ready.`); }; // Drag & drop drop.addEventListener('dragover', (e) => { e.preventDefault(); drop.classList.add('drag'); }); drop.addEventListener('dragleave', () => drop.classList.remove('drag')); drop.addEventListener('drop', (e) => { e.preventDefault(); drop.classList.remove('drag'); if (e.dataTransfer.files?.length) addFiles(e.dataTransfer.files); }); // File input fileInput.addEventListener('change', (e) => { if (e.target.files?.length) addFiles(e.target.files); fileInput.value = ''; }); // Clear clearBtn.addEventListener('click', () => { queue = []; bar.style.width = '0%'; output.value = ''; status('Cleared.'); }); // Copy / Download copyBtn.addEventListener('click', async () => { try { await navigator.clipboard.writeText(output.value); status('Copied to clipboard.'); } catch (e) { status('Copy failed: ' + e.message); } }); dlBtn.addEventListener('click', () => { const blob = new Blob([output.value], { type: 'text/plain;charset=utf-8' }); const a = document.createElement('a'); a.href = URL.createObjectURL(blob); a.download = 'ocr.txt'; a.click(); }); // Enhancement (simple grayscale + auto-threshold) function enhanceCanvas() { const imageData = ctx.getImageData(0, 0, canvas.width, canvas.height); const d = imageData.data; let sum = 0, count = 0; for (let i=0; i thr ? 255 : 0; d[i] = d[i+1] = d[i+2] = v; } ctx.putImageData(imageData, 0, 0); } async function recognizeCanvas(lang) { return Tesseract.recognize(canvas, lang, { logger: m => { if (m.status === 'recognizing text' && m.progress != null) { status(`OCR: ${Math.floor(m.progress * 100)}%`); } } }).then(res => res.data.text); } async function processImageFile(file, lang) { status(`Loading image: ${file.name}`); const img = new Image(); const buf = await file.arrayBuffer(); const blobUrl = URL.createObjectURL(new Blob([buf], { type: file.type })); await new Promise((resolve, reject) => { img.onload = resolve; img.onerror = reject; img.src = blobUrl; }); const maxDim = 2000; const scale = Math.min(1, maxDim / Math.max(img.width, img.height)); canvas.width = Math.round(img.width * scale); canvas.height = Math.round(img.height * scale); ctx.drawImage(img, 0, 0, canvas.width, canvas.height); URL.revokeObjectURL(blobUrl); if (enhanceCb.checked) enhanceCanvas(); const text = await recognizeCanvas(lang); return `# ${file.name}\n${text}\n`; } async function processPdfFile(file, lang) { if (!window.pdfjsLib) throw new Error('PDF.js not loaded'); status(`Loading PDF: ${file.name}`); const buf = await file.arrayBuffer(); const pdf = await pdfjsLib.getDocument({ data: buf }).promise; let out = `# ${file.name}\n`; for (let i = 1; i <= pdf.numPages; i++) { status(`Rendering PDF page ${i}/${pdf.numPages}`); const page = await pdf.getPage(i); const viewport = page.getViewport({ scale: 2 }); // 2x for better OCR canvas.width = viewport.width; canvas.height = viewport.height; await page.render({ canvasContext: ctx, viewport }).promise; if (enhanceCb.checked) enhanceCanvas(); const pageText = await recognizeCanvas(lang); out += `\n--- Page ${i} ---\n${pageText}\n`; bar.style.width = Math.floor((i / pdf.numPages) * 100) + '%'; } return out; } // Start OCR startBtn.addEventListener('click', async () => { if (!queue.length) { status('No files selected.'); return; } output.value = ''; const lang = langSelect.value; bar.style.width = '0%'; let results = ''; for (let idx = 0; idx < queue.length; idx++) { const f = queue[idx]; bar.style.width = Math.floor((idx / queue.length) * 100) + '%'; try { const text = f.type === 'application/pdf' ? await processPdfFile(f, lang) : await processImageFile(f, lang); results += text + '\n'; } catch (e) { results += `# ${f.name}\n[Error: ${e.message}]\n`; } } bar.style.width = '100%'; output.value = results.trim(); status('Done.'); }); // Camera (scan to text) let mediaStream = null; const stopCam = () => { if (mediaStream) { mediaStream.getTracks().forEach(t => t.stop()); mediaStream = null; } }; cameraBtn.addEventListener('click', async () => { try { mediaStream = await navigator.mediaDevices.getUserMedia({ video: { facingMode: 'environment' } }); camVideo.srcObject = mediaStream; camWrap.classList.remove('hidden'); } catch (e) { status('Camera error: ' + e.message + ' (Use HTTPS)'); } }); camClose.addEventListener('click', () => { stopCam(); camWrap.classList.add('hidden'); }); camCapture.addEventListener('click', async () => { if (!mediaStream) return; canvas.width = camVideo.videoWidth; canvas.height = camVideo.videoHeight; ctx.drawImage(camVideo, 0, 0); if (enhanceCb.checked) enhanceCanvas(); const text = await recognizeCanvas(langSelect.value); output.value = (output.value + '\n# Camera Capture\n' + text).trim(); status('Camera capture OCR done.'); }); });