| <!DOCTYPE html> |
| <html lang="en"> |
| <head> |
| <meta charset="UTF-8"> |
| <meta name="viewport" content="width=device-width, initial-scale=1.0"> |
| <title>ScribbleSense - Draw and Recognize Text</title> |
| <script src="https://cdn.tailwindcss.com"></script> |
| <script src="https://unpkg.com/feather-icons"></script> |
| <script src="https://cdn.jsdelivr.net/npm/tesseract.js@4/dist/tesseract.min.js"></script> |
| </head> |
| <body class="bg-gradient-to-br from-indigo-100 to-purple-50 min-h-screen flex flex-col items-center justify-center p-4"> |
| <div class="max-w-4xl w-full mx-auto text-center"> |
| <h1 class="text-4xl md:text-5xl font-bold text-indigo-800 mb-2">ScribbleSense ✍️</h1> |
| <p class="text-lg text-indigo-600 mb-8">Draw anything and watch it transform into text!</p> |
| |
| <div class="bg-white rounded-xl shadow-xl p-6 mb-8"> |
| <div class="relative"> |
| <canvas id="drawingCanvas" class="border-2 border-gray-300 rounded-lg w-full h-64 md:h-96 touch-none bg-white"></canvas> |
| <div id="clearBtn" class="absolute top-4 right-4 p-2 bg-white rounded-full shadow-md cursor-pointer hover:bg-gray-100 transition"> |
| <i data-feather="x" class="text-gray-600"></i> |
| </div> |
| </div> |
| </div> |
| |
| <div id="resultContainer" class="bg-white rounded-xl shadow-xl p-6 hidden"> |
| <h2 class="text-2xl font-semibold text-indigo-700 mb-4">Recognized Text</h2> |
| <div id="resultText" class="text-3xl md:text-4xl font-mono p-4 bg-gray-50 rounded-lg min-h-20 break-all"> |
| |
| </div> |
| <button id="copyBtn" class="mt-4 px-4 py-2 bg-indigo-600 text-white rounded-lg hover:bg-indigo-700 transition flex items-center justify-center gap-2 mx-auto"> |
| <i data-feather="copy" class="w-5 h-5"></i> Copy Text |
| </button> |
| </div> |
| |
| <div class="mt-8 text-sm text-gray-500"> |
| <p>Draw with left mouse button pressed. The system will automatically recognize your writing after a pause.</p> |
| <p class="mt-1">Supports: Roman alphabets, numbers, emojis, Chinese, Hangul, and more!</p> |
| </div> |
| </div> |
|
|
| <script> |
| document.addEventListener('DOMContentLoaded', function() { |
| feather.replace(); |
| |
| const canvas = document.getElementById('drawingCanvas'); |
| const ctx = canvas.getContext('2d'); |
| const resultContainer = document.getElementById('resultContainer'); |
| const resultText = document.getElementById('resultText'); |
| const clearBtn = document.getElementById('clearBtn'); |
| const copyBtn = document.getElementById('copyBtn'); |
| |
| |
| function resizeCanvas() { |
| const containerWidth = canvas.parentElement.clientWidth; |
| canvas.width = containerWidth - 32; |
| canvas.height = Math.min(window.innerHeight * 0.6, 600); |
| ctx.lineWidth = 4; |
| ctx.lineCap = 'round'; |
| ctx.strokeStyle = '#4f46e5'; |
| } |
| |
| window.addEventListener('resize', resizeCanvas); |
| resizeCanvas(); |
| |
| |
| let isDrawing = false; |
| let lastX = 0; |
| let lastY = 0; |
| let inactivityTimer; |
| let lastDrawTime = 0; |
| |
| |
| function startDrawing(e) { |
| isDrawing = true; |
| [lastX, lastY] = getPosition(e); |
| clearTimeout(inactivityTimer); |
| } |
| |
| function draw(e) { |
| if (!isDrawing) return; |
| |
| const [x, y] = getPosition(e); |
| |
| ctx.beginPath(); |
| ctx.moveTo(lastX, lastY); |
| ctx.lineTo(x, y); |
| ctx.stroke(); |
| |
| [lastX, lastY] = [x, y]; |
| lastDrawTime = Date.now(); |
| |
| |
| clearTimeout(inactivityTimer); |
| inactivityTimer = setTimeout(performOCR, 500); |
| } |
| |
| function stopDrawing() { |
| isDrawing = false; |
| } |
| |
| function getPosition(e) { |
| let x, y; |
| if (e.type.includes('touch')) { |
| const rect = canvas.getBoundingClientRect(); |
| x = e.touches[0].clientX - rect.left; |
| y = e.touches[0].clientY - rect.top; |
| } else { |
| x = e.offsetX; |
| y = e.offsetY; |
| } |
| return [x, y]; |
| } |
| |
| |
| function performOCR() { |
| if (Date.now() - lastDrawTime < 2) return; |
| |
| Tesseract.recognize( |
| canvas, |
| 'eng+chi_sim+chi_tra+kor+jpn+equ', |
| { |
| logger: m => console.log(m) |
| } |
| ).then(({ data: { text } }) => { |
| resultText.textContent = text.trim() || "Couldn't recognize any text"; |
| resultContainer.classList.remove('hidden'); |
| |
| |
| resultContainer.scrollIntoView({ behavior: 'smooth', block: 'nearest' }); |
| }); |
| } |
| |
| |
| function clearCanvas() { |
| ctx.clearRect(0, 0, canvas.width, canvas.height); |
| resultContainer.classList.add('hidden'); |
| clearTimeout(inactivityTimer); |
| } |
| |
| |
| function copyText() { |
| navigator.clipboard.writeText(resultText.textContent).then(() => { |
| const originalText = copyBtn.innerHTML; |
| copyBtn.innerHTML = '<i data-feather="check" class="w-5 h-5"></i> Copied!'; |
| setTimeout(() => { |
| copyBtn.innerHTML = originalText; |
| feather.replace(); |
| }, 2000); |
| }); |
| } |
| |
| |
| canvas.addEventListener('mousedown', startDrawing); |
| canvas.addEventListener('mousemove', draw); |
| canvas.addEventListener('mouseup', stopDrawing); |
| canvas.addEventListener('mouseout', stopDrawing); |
| |
| |
| canvas.addEventListener('touchstart', (e) => { |
| e.preventDefault(); |
| startDrawing(e); |
| }); |
| canvas.addEventListener('touchmove', (e) => { |
| e.preventDefault(); |
| draw(e); |
| }); |
| canvas.addEventListener('touchend', stopDrawing); |
| |
| clearBtn.addEventListener('click', clearCanvas); |
| copyBtn.addEventListener('click', copyText); |
| |
| |
| setTimeout(() => { |
| const tooltip = document.createElement('div'); |
| tooltip.className = 'absolute bg-indigo-600 text-white px-4 py-2 rounded-lg shadow-lg z-10 animate-bounce'; |
| tooltip.style.top = '20px'; |
| tooltip.style.left = '50%'; |
| tooltip.style.transform = 'translateX(-50%)'; |
| tooltip.textContent = 'Draw here with your mouse or finger!'; |
| canvas.parentElement.appendChild(tooltip); |
| |
| setTimeout(() => { |
| tooltip.remove(); |
| }, 3000); |
| }, 1000); |
| }); |
| </script> |
| </body> |
| </html> |
|
|