browser handwriting api engine
This commit is contained in:
@@ -0,0 +1,101 @@
|
|||||||
|
import type { Stroke, RecognitionCandidate } from './types'
|
||||||
|
|
||||||
|
interface HandwritingPrediction {
|
||||||
|
text?: string | null
|
||||||
|
}
|
||||||
|
|
||||||
|
interface HandwritingDrawing {
|
||||||
|
addStroke(stroke: { points: { x: number; y: number; t: number }[] }): void
|
||||||
|
getPrediction(): Promise<HandwritingPrediction[]>
|
||||||
|
clear(): void
|
||||||
|
}
|
||||||
|
|
||||||
|
interface HandwritingRecognizer {
|
||||||
|
startDrawing(options: {
|
||||||
|
recognitionType: string
|
||||||
|
inputType: string
|
||||||
|
textContext: string
|
||||||
|
alternatives: number
|
||||||
|
}): HandwritingDrawing
|
||||||
|
}
|
||||||
|
|
||||||
|
let recognizer: HandwritingRecognizer | null = null
|
||||||
|
let initAttempted = false
|
||||||
|
|
||||||
|
export function isSupported(): boolean {
|
||||||
|
return 'createHandwritingRecognizer' in navigator
|
||||||
|
}
|
||||||
|
|
||||||
|
export function isReady(): boolean {
|
||||||
|
return recognizer !== null
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function init(): Promise<void> {
|
||||||
|
if (initAttempted) return
|
||||||
|
initAttempted = true
|
||||||
|
|
||||||
|
if (!isSupported()) {
|
||||||
|
console.log('Web Handwriting Recognition API not available')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
// SAFETY: createHandwritingRecognizer only exists on Chromium navigators and isSupported() gates this call
|
||||||
|
const chromiumNavigator = navigator as Navigator & {
|
||||||
|
createHandwritingRecognizer: (options: { languages: string[] }) => Promise<HandwritingRecognizer>
|
||||||
|
}
|
||||||
|
recognizer = await chromiumNavigator.createHandwritingRecognizer({
|
||||||
|
languages: ['en'],
|
||||||
|
})
|
||||||
|
console.log('Web Handwriting Recognition API ready')
|
||||||
|
} catch (e) {
|
||||||
|
console.warn('Failed to create handwriting recognizer:', e)
|
||||||
|
recognizer = null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function recognize(strokes: Stroke[]): Promise<RecognitionCandidate[]> {
|
||||||
|
if (!recognizer || strokes.length === 0) return []
|
||||||
|
|
||||||
|
try {
|
||||||
|
const drawing = recognizer.startDrawing({
|
||||||
|
recognitionType: 'text',
|
||||||
|
inputType: 'mouse',
|
||||||
|
textContext: '',
|
||||||
|
alternatives: 5,
|
||||||
|
})
|
||||||
|
|
||||||
|
for (const stroke of strokes) {
|
||||||
|
const points = stroke.map(pt => ({
|
||||||
|
x: pt.x,
|
||||||
|
y: pt.y,
|
||||||
|
t: pt.timestamp,
|
||||||
|
}))
|
||||||
|
drawing.addStroke({ points })
|
||||||
|
}
|
||||||
|
|
||||||
|
const prediction = await drawing.getPrediction()
|
||||||
|
drawing.clear()
|
||||||
|
|
||||||
|
if (!prediction || prediction.length === 0) return []
|
||||||
|
|
||||||
|
const candidates: RecognitionCandidate[] = []
|
||||||
|
for (const p of prediction) {
|
||||||
|
const text = p.text?.trim().toUpperCase()
|
||||||
|
if (text && text.length === 1 && text >= 'A' && text <= 'Z') {
|
||||||
|
// avoid duplicates
|
||||||
|
if (!candidates.some(c => c.letter === text)) {
|
||||||
|
candidates.push({
|
||||||
|
letter: text,
|
||||||
|
confidence: Math.max(0.3, 1 - candidates.length * 0.15),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return candidates
|
||||||
|
} catch (e) {
|
||||||
|
console.warn('Web handwriting recognition failed:', e)
|
||||||
|
return []
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user