How to Implement Custom OCR Engines with LiteParse's OcrEngine Trait
Implement the OcrEngine trait defined in crates/liteparse/src/ocr/mod.rs by providing name() and recognize() methods, wrap your implementation in std::sync::Arc, and inject it via LiteParse::with_ocr_engine to override the default HTTP or Tesseract backends.
LiteParse, the Rust document parsing library from run-llama/liteparse, abstracts optical character recognition behind a platform-aware trait interface. By implementing the OcrEngine trait, you can integrate custom OCR backends—ranging from proprietary cloud APIs to experimental on-device models—while maintaining full compatibility with LiteParse's async document processing pipeline.
Understanding the OcrEngine Trait
The OcrEngine trait in crates/liteparse/src/ocr/mod.rs serves as the core abstraction that enables LiteParse to work with arbitrary OCR backends without hardcoding specific implementations.
Trait Definition and Platform Constraints
LiteParse adapts the trait bounds based on the compilation target. On native platforms (#[cfg(not(target_arch = "wasm32"))]), the trait requires Send + Sync so the async runtime can migrate the engine across threads. Under WebAssembly, the trait remains Send + Sync, but the returned future explicitly drops the Send requirement because the WASM runtime is single-threaded.
// 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 + '_,
>,
>;
}
Method Requirements
The trait specifies two required methods. The name() method returns a static string identifier used for logging and debugging. The recognize() method accepts raw image bytes, pixel dimensions, and OcrOptions, returning a pinned boxed future that resolves to a Vec<OcrResult>. The lifetime bounds 'b: 'a and 'c: 'a ensure that references to options and image data outlive the returned future.
Implementing a Custom OCR Engine
Follow these implementation steps to create a compatible engine that plugs into the LiteParse ecosystem.
Step 1: Define Your Engine Structure
Create a struct to hold your OCR backend's configuration state, such as HTTP clients, API keys, or model handles. Ensure all internal types are thread-safe when targeting native platforms.
Step 2: Implement the Trait Methods
Provide implementations for name() and recognize(). The recognize() method must perform the async OCR logic and convert your backend's response into LiteParse's OcrResult format, using [x1, y1, x2, y2] pixel coordinates for the bounding box.
// crates/liteparse/src/ocr/my_ocr.rs
use super::{OcrEngine, OcrOptions, OcrResult};
use std::future::Future;
use std::pin::Pin;
/// A demonstration "echo" OCR engine that returns the requested language.
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 with LiteParse
LiteParse selects OCR engines inside LiteParse::parse_input (see crates/liteparse/src/parser.rs, lines 43-57 and 71-78). By default, the parser chooses between an HTTP OCR server or the built-in Tesseract engine. Override this selection using with_ocr_engine, which accepts an Arc<dyn OcrEngine> and stores it in the ocr_engine_override field.
// crates/liteparse/src/parser.rs (excerpt)
pub fn with_ocr_engine(mut self, engine: std::sync::Arc<dyn OcrEngine>) -> Self {
self.ocr_engine_override = Some(engine);
self
}
Wire your implementation into the parser as follows:
use liteparse::parser::LiteParse;
use liteparse::ocr::my_ocr::EchoEngine;
use std::sync::Arc;
let cfg = liteparse::config::LiteParseConfig::default();
let parser = LiteParse::new(cfg)
.with_ocr_engine(Arc::new(EchoEngine::new()));
// Subsequent calls to parser.parse_input(...) will use EchoEngine.
Platform-Specific Considerations
When implementing a custom OCR engine, account for these technical constraints to ensure cross-platform compatibility.
Thread Safety and Async Bounds
On native platforms, your engine and any internal HTTP clients must implement Send + Sync. Use thread-safe HTTP clients like reqwest::Client or protect mutable state with Mutex and RwLock. The boxed future returned by recognize() must carry the + Send bound for non-WASM targets, though the #[cfg(target_arch = "wasm32")] implementation in LiteParse drops this requirement automatically.
Coordinate System and Error Handling
The bbox field in OcrResult expects [f64; 4] representing [x1, y1, x2, y2] in the coordinate system of the rendered page image. Ensure your OCR service returns coordinates that match the width and height parameters passed to recognize(). Propagate failures using Box<dyn std::error::Error + Send + Sync>, which LiteParse surfaces as a LiteParseError to calling code.
Language Configuration
The OcrOptions struct carries the language field (populated from ocr_language in LiteParseConfig defined in crates/liteparse/src/config.rs). Respect this value when constructing requests to your OCR backend to maintain consistency with user configuration.
Reference Implementations
Study the existing implementations in the LiteParse repository as templates for your custom engine. crates/liteparse/src/ocr/tesseract.rs demonstrates integration with the local Tesseract OCR library, while crates/liteparse/src/ocr/http_simple.rs shows how to implement a remote HTTP-based engine with proper error handling and async polling patterns.
Summary
- Implement
OcrEnginefromcrates/liteparse/src/ocr/mod.rsby definingname()and the asyncrecognize()method with correct lifetime bounds. - Handle platform differences: Maintain
Send + Syncfor native targets; the trait automatically adapts for WebAssembly by relaxing theSendbound on futures. - Use
Arc<dyn OcrEngine>to share your engine across async boundaries and inject it viaLiteParse::with_ocr_engineto bypass default selection logic. - Return proper coordinates in
[x1, y1, x2, y2]format and propagate errors as boxeddyn std::error::Error + Send + Sync. - Consult reference implementations in
tesseract.rsandhttp_simple.rsfor production-ready patterns.
Frequently Asked Questions
Do I need to modify LiteParse's source code to use a custom OCR engine?
No. LiteParse exposes the with_ocr_engine method on the LiteParse struct specifically for external injection. You can define your implementation in your own crate or a local module, then pass it as Arc<dyn OcrEngine> without forking the repository.
Can I use the same OCR engine implementation for both native and WebAssembly targets?
Yes. The OcrEngine trait definition in crates/liteparse/src/ocr/mod.rs uses conditional compilation to handle platform differences automatically. Your implementation remains the same, though you should ensure internal state is Send + Sync for native targets.
How does LiteParse handle errors from my custom engine's recognize method?
LiteParse captures any Box<dyn std::error::Error + Send + Sync> returned by your recognize() future and surfaces it as a LiteParseError within the document processing pipeline. The error will propagate up through parse_input, allowing calling code to handle OCR failures appropriately.
What image format does the recognize method expect?
The image_data parameter expects raw bytes of a rendered page image, typically PNG format generated by LiteParse's internal rendering pipeline. The width and height parameters correspond to these bytes. Ensure your OCR backend can decode the provided format or implement conversion logic before sending to your external service.
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 →