How to Implement the OcrEngine Trait for Custom OCR Backends in LiteParse
To implement the OcrEngine trait for custom OCR backends in LiteParse, define a Send + Sync struct that implements the name() and recognize() methods, then register your engine using LiteParse::with_ocr_engine wrapped in an Arc to override the default selection logic.
LiteParse provides a pluggable abstraction for OCR processing through the OcrEngine trait defined in crates/liteparse/src/ocr/mod.rs. This trait enables developers to integrate any OCR backend—including cloud APIs, on-device machine learning models, or custom algorithms—while maintaining full compatibility with LiteParse's async document parsing pipeline.
Understanding the OcrEngine Trait Interface
The OcrEngine trait serves as the core abstraction between LiteParse's parsing logic and underlying OCR implementations. According to the source code in crates/liteparse/src/ocr/mod.rs, the trait definition includes platform-specific requirements:
// crates/liteparse/src/ocr/mod.rs
pub trait OcrEngine: Send + Sync {
fn name(&self) -> &str;
fn recognize<'a, 'b: 'a, 'c: 'a>(
&'a self,
image_data: &'c [u8],
width: u32,
height: u32,
options: &'b OcrOptions,
) -> Pin<
Box<
dyn Future<
Output = Result<Vec<OcrResult>, Box<dyn std::error::Error + Send + Sync>>
> + Send + '_,
>,
>;
}
The trait requires two key methods:
name()– Returns a human-readable identifier for logging and debugging purposes.recognize()– An async method receiving raw image bytes (typically PNG), dimensions, andOcrOptions, returning a pinned future that resolves to a vector ofOcrResultstructs.
Platform-specific constraints: On native targets (#[cfg(not(target_arch = "wasm32"))]), the returned future must be Send to allow the async runtime to move the engine across threads. On WebAssembly (wasm32), the trait remains Send + Sync but the future does not require Send because the runtime is single-threaded.
Step-by-Step Implementation Guide
Creating the Engine Struct
First, define a struct to hold your backend's configuration and state. This struct must be thread-safe for native platforms.
// crates/liteparse/src/ocr/my_custom.rs
use super::{OcrEngine, OcrOptions, OcrResult};
use std::future::Future;
use std::pin::Pin;
/// Custom OCR engine integrating with an external API.
pub struct MyCustomEngine {
api_key: String,
client: reqwest::Client,
}
impl MyCustomEngine {
pub fn new(api_key: String) -> Self {
Self {
api_key,
client: reqwest::Client::new(),
}
}
}
Implementing the Trait Methods
Implement OcrEngine for your struct, ensuring the future is Send for native targets. The recognize method receives raw image data and must return bounding boxes in [x1, y1, x2, y2] pixel coordinates.
impl OcrEngine for MyCustomEngine {
fn name(&self) -> &str {
"my-custom-ocr"
}
fn recognize<'a, 'b: 'a, 'c: 'a>(
&'a self,
image_data: &'c [u8],
width: u32,
height: u32,
options: &'b OcrOptions,
) -> Pin<Box<dyn Future<Output = Result<Vec<OcrResult>, Box<dyn std::error::Error + Send + Sync>>> + Send + '_>>
{
Box::pin(async move {
// Send image_data to your OCR service
// Parse the response and map to OcrResult structs
Ok(vec![OcrResult {
text: "Extracted text".to_string(),
bbox: [0.0, 0.0, width as f32, height as f32],
confidence: 0.95,
}])
})
}
}
Handling Error Types
Errors must be boxed as dyn std::error::Error + Send + Sync. LiteParse surfaces these as LiteParseError in the calling code.
Complete Working Example
Below is a minimal "echo" implementation demonstrating the trait structure without external dependencies:
// crates/liteparse/src/ocr/echo_engine.rs
use super::{OcrEngine, OcrOptions, OcrResult};
use std::future::Future;
use std::pin::Pin;
/// A demonstration OCR engine that returns the requested language as text.
pub struct EchoEngine;
impl EchoEngine {
pub fn new() -> Self {
EchoEngine
}
}
impl OcrEngine for EchoEngine {
fn name(&self) -> &str {
"echo"
}
fn recognize<'a, 'b: 'a, 'c: 'a>(
&'a self,
_image_data: &'c [u8],
_width: u32,
_height: u32,
options: &'b OcrOptions,
) -> Pin<Box<dyn Future<Output = Result<Vec<OcrResult>, Box<dyn std::error::Error + Send + Sync>>> + Send + '_>>
{
Box::pin(async move {
Ok(vec![OcrResult {
text: format!("language={}", options.language),
bbox: [0.0, 0.0, 100.0, 20.0],
confidence: 1.0,
}])
})
}
}
Registering Your Custom Engine
The LiteParse struct in crates/liteparse/src/parser.rs (lines 43-57 and 71-78) selects OCR engines based on configuration. To override this selection and use your custom implementation, wrap your engine in Arc and call with_ocr_engine:
use liteparse::parser::LiteParse;
use liteparse::ocr::echo_engine::EchoEngine;
use std::sync::Arc;
let config = liteparse::config::LiteParseConfig::default();
let parser = LiteParse::new(config)
.with_ocr_engine(Arc::new(EchoEngine::new()));
// Subsequent calls to parser.parse_input(...) will use EchoEngine
// instead of the default HTTP OCR or Tesseract backends.
The with_ocr_engine method stores the engine in the ocr_engine_override field, which takes precedence over the built-in selection logic.
Critical Implementation Details
When implementing custom OCR backends in LiteParse, adhere to the following constraints:
- Thread Safety: On native platforms, ensure all internal state (HTTP clients, model handles) is
Send + Sync. Usereqwest::Clientfor HTTP connections or protect mutable state withMutex/RwLock. - Bounding Box Format: The
bboxfield inOcrResultexpects[x1, y1, x2, y2]coordinates in pixels relative to the input image dimensions. Ensure your OCR service returns coordinates matching thewidthandheightparameters passed torecognize. - Language Handling: The
OcrOptionsstruct currently exposes thelanguagefield fromLiteParseConfig. Respect this value when constructing requests to multilingual OCR services. - Future Type: The boxed future must include
+ Sendon native builds but exclude it forwasm32targets. Use conditional compilation or the trait's built-in platform abstraction to handle this.
Reference Implementations
Study the following source files for production-ready patterns:
crates/liteparse/src/ocr/tesseract.rs– Reference implementation using the Tesseract OCR library with local image processing.crates/liteparse/src/ocr/http_simple.rs– Reference implementation for HTTP-based OCR services, demonstrating async HTTP client usage and error handling.crates/liteparse/src/config.rs– Configuration structure carrying OCR language settings and feature flags.
Summary
- Implement
OcrEngineincrates/liteparse/src/ocr/mod.rsby defining a struct that isSend + Syncand providing thename()andrecognize()methods. - Return a pinned future from
recognize()that resolves toVec<OcrResult>with text content, bounding boxes in[x1, y1, x2, y2]format, and confidence scores. - Register via
with_ocr_engineby wrapping your engine inArc<dyn OcrEngine>to override the default HTTP or Tesseract selection incrates/liteparse/src/parser.rs. - Handle platform differences by ensuring
Sendbounds on futures for native targets while maintaining compatibility with WebAssembly's single-threaded runtime.
Frequently Asked Questions
What image format does the recognize method receive?
The image_data parameter receives raw PNG bytes by default. LiteParse renders the input document to a PNG image before passing it to the OCR engine. Your implementation should either process PNG directly or decode it using an image library before sending to your backend service.
How do I handle authentication keys for external OCR APIs?
Store sensitive credentials in your engine struct (e.g., api_key: String) during construction. Implement Send + Sync for the struct to ensure thread safety. Use Arc<str> or String for the API key, and consider using environment variables or secure vaults to load these values when calling MyCustomEngine::new().
Can I use different OCR engines for different document types?
While LiteParse currently selects one engine per LiteParse instance, you can implement a composite engine that internally routes to different backends based on the OcrOptions or by analyzing the image content. Alternatively, create multiple LiteParse instances with different engines and route documents at the application level based on file extension or content type.
Why does my custom engine fail to compile on WebAssembly?
The OcrEngine trait requires Send + Sync on all platforms, but the future returned by recognize must only be Send on native targets. If you are manually implementing the future bounds, ensure you use the same conditional compilation flags (#[cfg(not(target_arch = "wasm32"))]) used in the LiteParse source code to add or remove the Send bound on the boxed future type.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →