use std::cell::RefCell;
use image::GrayImage;
use js_sys::{Array, Function, Reflect, Uint8Array};
use wasm_bindgen::JsValue;
use super::{OcrEngine, OcrWord};
thread_local! {
static HOST_BACKEND: RefCell<Option<Function>> = const { RefCell::new(None) };
}
pub fn register_host_ocr_backend(callback: Option<Function>) {
HOST_BACKEND.with(|slot| {
*slot.borrow_mut() = callback;
});
}
pub fn host_ocr_registered() -> bool {
HOST_BACKEND.with(|slot| slot.borrow().is_some())
}
pub struct HostOcrEngine;
impl HostOcrEngine {
pub fn try_new() -> Option<Self> {
if host_ocr_registered() {
Some(Self)
} else {
None
}
}
}
impl OcrEngine for HostOcrEngine {
fn recognize(&self, gray: &GrayImage) -> Vec<OcrWord> {
HOST_BACKEND.with(|slot| {
let borrowed = slot.borrow();
let Some(cb) = borrowed.as_ref() else {
return Vec::new();
};
let width = gray.width();
let height = gray.height();
let pixels: Vec<u8> = gray.pixels().map(|p| p.0[0]).collect();
let ua = Uint8Array::new_with_length(pixels.len() as u32);
ua.copy_from(&pixels);
let Ok(raw) = cb.call3(
&JsValue::NULL,
&ua.into(),
&JsValue::from_f64(f64::from(width)),
&JsValue::from_f64(f64::from(height)),
) else {
return Vec::new();
};
if raw.is_undefined() || raw.is_null() {
return Vec::new();
}
let Ok(arr) = Array::try_from(raw) else {
return Vec::new();
};
let mut out = Vec::with_capacity(arr.length() as usize);
for i in 0..arr.length() {
let item = arr.get(i);
if item.is_undefined() || item.is_null() {
continue;
}
let text = Reflect::get(&item, &JsValue::from_str("text"))
.ok()
.and_then(|v| v.as_string())
.unwrap_or_default();
if text.trim().is_empty() {
continue;
}
let left = js_u32(&item, "left");
let top = js_u32(&item, "top");
let w = js_u32(&item, "width").max(1);
let h = js_u32(&item, "height").max(1);
let confidence = Reflect::get(&item, &JsValue::from_str("confidence"))
.ok()
.and_then(|v| v.as_f64())
.unwrap_or(80.0);
out.push(OcrWord {
line_key: (0, 0, i),
left,
top,
width: w,
height: h,
text,
confidence,
});
}
out
})
}
}
fn js_u32(obj: &JsValue, key: &str) -> u32 {
Reflect::get(obj, &JsValue::from_str(key))
.ok()
.and_then(|v| v.as_f64())
.unwrap_or(0.0)
.max(0.0) as u32
}