Spaces:
Sleeping
Sleeping
Download canvas.js from shethjenil/Google-OCR: direct link, hf CLI and curl.
- Browser
- Download file 7.34 kB
-
https://huggingface.co/spaces/shethjenil/Google-OCR/resolve/main/canvas.js
- Command line
-
hf download hf://spaces/shethjenil/Google-OCR/canvas.js
-
curl -L -o canvas.js https://huggingface.co/spaces/shethjenil/Google-OCR/resolve/main/canvas.js
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(); | |
| }); | |
| })(); | |