tesseract fallback engine

This commit is contained in:
2026-03-03 14:55:10 +02:00
parent 38fe278e15
commit daf87cc377
@@ -0,0 +1,57 @@
import type { Stroke, RecognitionCandidate } from './types'
import { strokesToCanvas } from './preprocess'
import type Tesseract from 'tesseract.js'
import type { PSM } from 'tesseract.js'
let worker: Tesseract.Worker | null = null
let loading = false
export function isReady(): boolean {
return worker !== null
}
export async function init(): Promise<void> {
if (worker || loading) return
loading = true
try {
const Tesseract = await import('tesseract.js')
worker = await Tesseract.createWorker('eng')
await worker.setParameters({
tessedit_char_whitelist: 'ABCDEFGHIJKLMNOPQRSTUVWXYZ',
// SAFETY: '10' is exactly PSM.SINGLE_CHAR, so the literal lands inside the enum's value set
tessedit_pageseg_mode: '10' as PSM, // single character
})
} catch (e) {
console.warn('Tesseract init failed:', e)
worker = null
}
loading = false
}
export async function recognize(strokes: Stroke[]): Promise<RecognitionCandidate[]> {
if (!worker) return []
try {
const imageData = strokesToCanvas(strokes, 128)
// convert ImageData to canvas for tesseract
const canvas = new OffscreenCanvas(128, 128)
const ctx = canvas.getContext('2d')!
ctx.putImageData(imageData, 0, 0)
const blob = await canvas.convertToBlob({ type: 'image/png' })
const result = await worker.recognize(blob)
const text = result.data.text.trim().toUpperCase()
if (text.length === 1 && text >= 'A' && text <= 'Z') {
const confidence = result.data.confidence / 100
return [{ letter: text, confidence }]
}
return []
} catch (e) {
console.warn('Tesseract recognition failed:', e)
return []
}
}