Google-OCR / canvas.js
shethjenil's picture
pushed
21db406
Raw History Blame Contribute Delete
7.34 kB
(function () {
const $ = (s, c) => (c || document).querySelector(s),
$$ = (s, c) => (c || document).querySelectorAll(s),
O = $('#ocr-svg-overlay'),
I = $('#ocr-svg-image'),
P = $('#canvas-placeholder-msg'),
D = $('#details-content-area'),
V = $('.canvas-viewport-container'),
W = $('.viewport-wrapper'),
Z = $('#zoom-level-text'),
btn = id => $(`#btn-zoom-${id}`);
let z = 1, px = 0, py = 0, dr = 0, dragging = false, sx = 0, sy = 0;
const upd = () => { W && (W.style.transform = `translate(${px}px,${py}px) scale(${z})`); Z && (Z.innerText = `${Math.round(z * 100)}%`); },
zoom = f => { z = Math.min(5, Math.max(.5, z * f)); upd(); },
reset = () => { z = 1; px = 0; py = 0; upd(); };
[['In', () => zoom(1.2)], ['Out', () => zoom(1 / 1.2)], ['Reset', reset]].forEach(([id, fn]) => {
const b = btn(id.toLowerCase());
b && b.addEventListener('click', e => { e.stopPropagation(); fn(); });
});
V && (I.addEventListener('dragstart', e => e.preventDefault()),
V.addEventListener('wheel', e => { e.preventDefault(); zoom(e.deltaY < 0 ? 1.15 : .85); }, { passive: !1 }),
V.addEventListener('mousedown', e => {
if (e.target.closest('button,input')) return;
dragging = !0; dr = 0; sx = e.clientX - px; sy = e.clientY - py;
V.style.cursor = 'grabbing'; e.preventDefault();
}),
window.addEventListener('mousemove', e => {
if (!dragging) return;
const dx = e.clientX - (sx + px), dy = e.clientY - (sy + py);
dr += Math.sqrt(dx * dx + dy * dy);
px = e.clientX - sx; py = e.clientY - sy; upd();
}),
window.addEventListener('mouseup', () => { dragging && (dragging = !1, V.style.cursor = 'grab'); }));
const src = () => { const c = $('#input-image'); return c && c.querySelector('img')?.src || null; },
CELL = (k, v, c = 'text-foreground') => `<span class="text-xs font-medium text-muted-foreground">${k}</span><span class="text-xs font-semibold ${c}">${v}</span>`,
EMPTY = '<div class="flex h-full min-h-[200px] items-center justify-center px-2"><p class="text-center text-xs text-muted-foreground">Click any element inside the canvas to inspect its properties</p></div>';
function populate(data) {
O.innerHTML = '';
if (!data) return;
const el = [];
['blocks', 'paragraphs'].forEach(k => (data[k] || []).forEach(v => el.push({ t: k == 'blocks' ? 'Block' : 'Paragraph', d: v })));
(data.lines || []).forEach(l => {
el.push({ t: 'Line', d: l });
(l.words || []).forEach(w => {
el.push({ t: 'Word', d: w });
(w.symbols || []).forEach(s => el.push({ t: 'Symbol', d: s }));
w.whitespace_bounding_box && el.push({ t: 'Whitespace', d: { bounding_box: w.whitespace_bounding_box } });
});
});
const order = ['Block', 'Paragraph', 'Line', 'Word', 'Symbol', 'Whitespace'];
el.sort((a, b) => order.indexOf(a.t) - order.indexOf(b.t));
window.ocr = el;
el.forEach((e, i) => {
const b = e.d.bounding_box;
if (!b) return;
const r = document.createElementNS('http://www.w3.org/2000/svg', 'rect');
r.setAttribute('class', `ocr-box box-${e.t.toLowerCase()}`);
r.setAttribute('x', b.x); r.setAttribute('y', b.y);
r.setAttribute('width', b.width); r.setAttribute('height', b.height);
b.angle && r.setAttribute('transform', `rotate(${b.angle},${b.x + b.width / 2},${b.y + b.height / 2})`);
r.setAttribute('data-idx', i);
O.appendChild(r);
});
}
const C = { Block: ['#f472b6', 'bg-pink-500/20 text-pink-400'], Paragraph: ['#22d3ee', 'bg-cyan-500/20 text-cyan-400'], Line: ['#c084fc', 'bg-violet-500/20 text-violet-400'], Word: ['#34d399', 'bg-emerald-500/20 text-emerald-400'], Symbol: ['#fbbf24', 'bg-amber-500/20 text-amber-400'], Whitespace: ['#f87171', 'bg-red-500/20 text-red-400'] };
function meta(item) {
const d = item.d, b = d.bounding_box, t = item.t, cl = (C[t] || ['#818cf8', 'bg-indigo-500/20 text-indigo-400'])[1];
let rows = [CELL('Type', `<span class="${cl} rounded px-2 py-0.5 text-xs font-semibold">${t}</span>`),
CELL('Confidence', d.confidence !== void 0 ? (d.confidence * 100).toFixed(1) + '%' : 'N/A', 'text-emerald-400')];
if (t == 'Line' || t == 'Word') rows.push(CELL('Language', d.language || 'N/A'), CELL('Direction', d.direction || 'N/A'), CELL('Content Type', d.content_type || 'N/A'));
if (t == 'Word') {
const f = d.foreground_rgb_hex || 'N/A', g = d.background_rgb_hex || 'N/A';
rows.push(CELL('FG Color', `<span class="inline-flex items-center gap-1.5"><span class="inline-block h-3 w-3 rounded border border-white/20" style="background:${f}"></span>${f}</span>`),
CELL('BG Color', `<span class="inline-flex items-center gap-1.5"><span class="inline-block h-3 w-3 rounded border border-white/20" style="background:${g}"></span>${g}</span>`),
CELL('Gray', `${d.foreground_gray_value} / ${d.background_gray_value}`),
CELL('Space After', d.has_space_after ? 'Yes' : 'No'));
}
rows.push(CELL('BBox', `<span class="font-mono text-[11px]">${b.x}, ${b.y}, ${b.width}×${b.height}</span>`),
CELL('Rotation', `<span class="font-mono text-[11px] text-amber-400">${(b.angle || 0).toFixed(1)}°</span>`));
D.innerHTML = `<div class="flex flex-col gap-2">${rows.map(r => `<div class="flex items-center justify-between border-b border-border pb-1.5">${r}</div>`).join('')}</div>
<div class="mt-3 flex flex-col gap-1.5"><span class="text-[11px] font-medium text-muted-foreground">Extracted text</span><div class="rounded-lg border border-primary/30 bg-primary/10 px-3 py-2 text-sm font-bold text-foreground">${d.utf8_string || 'N/A'}</div></div>`;
}
O.addEventListener('click', e => {
if (dr > 5 || !window.ocr) return;
const el = e.target.closest('.ocr-box');
if (!el) return;
$$('.ocr-box.selected').forEach(x => x.classList.remove('selected'));
el.classList.add('selected');
const item = window.ocr[+el.getAttribute('data-idx')];
item && meta(item);
});
function loadImg(src) {
reset();
if (!src) { I.removeAttribute('src'); O.innerHTML = ''; P.style.display = 'block'; D.innerHTML = EMPTY; return; }
P.style.display = 'none';
I.onload = () => O.setAttribute('viewBox', `0 0 ${I.naturalWidth || I.width} ${I.naturalHeight || I.height}`);
I.src = src;
}
window.loadStreamedData = function (raw) {
if (!raw || !raw.trim()) { loadImg(null); I.style.filter = 'none'; return; }
try {
const p = JSON.parse(raw);
I.style.filter = 'none'; P.style.display = 'none'; D.innerHTML = EMPTY;
populate(p.annotation);
loadImg(src());
} catch (e) { console.error(e); }
};
(function init() {
const el = $('#input-image');
if (!el) { setTimeout(init, 150); return; }
new MutationObserver(() => {
const s = src();
if (s && s !== I.src) { window.ocr = null; loadImg(s); I.style.filter = 'blur(8px)'; }
else if (!s) { loadImg(null); I.style.filter = 'none'; }
}).observe(el, { childList: true, subtree: true, attributes: true });
const s = src();
s && loadImg(s);
})();
$$('.canvas-toggles input').forEach(el => {
const fn = () => O.classList.toggle('show-' + el.id.replace('cb-', ''), el.checked);
el.addEventListener('change', fn); fn();
});
})();