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