#[derive(Debug, Clone)]
pub struct OcrRegion {
pub text: String,
pub confidence: f32,
pub bbox: Option<[f32; 4]>,
}
#[derive(Debug, Clone)]
pub struct OcrResult {
pub regions: Vec<OcrRegion>,
pub full_text: String,
pub processing_ms: u64,
}
pub trait OcrEngine: Send + Sync {
fn extract(&self, image_bytes: &[u8]) -> Result<OcrResult, OcrError>;
}
#[derive(Debug, thiserror::Error)]
pub enum OcrError {
#[error("Model not loaded: {0}")]
ModelNotLoaded(String),
#[error("Invalid image format: {0}")]
InvalidImage(String),
#[error("OCR processing failed: {0}")]
ProcessingFailed(String),
}
pub struct StubOcrEngine;
impl OcrEngine for StubOcrEngine {
fn extract(&self, _image_bytes: &[u8]) -> Result<OcrResult, OcrError> {
Ok(OcrResult {
regions: vec![],
full_text: String::new(),
processing_ms: 0,
})
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_stub_ocr_returns_empty() {
let engine = StubOcrEngine;
let result = engine.extract(b"fake image data").unwrap();
assert!(result.regions.is_empty());
assert!(result.full_text.is_empty());
}
#[test]
fn test_ocr_region_construction() {
let region = OcrRegion {
text: "Hello World".to_string(),
confidence: 0.95,
bbox: Some([10.0, 20.0, 100.0, 30.0]),
};
assert_eq!(region.text, "Hello World");
assert!(region.confidence > 0.9);
assert!(region.bbox.is_some());
}
}