How the ocr_engine_override Mechanism Works in LiteParse: Plugging Custom OCR Engines
The ocr_engine_override field in LiteParse lets you inject a custom OcrEngine implementation via the with_ocr_engine() builder method, completely bypassing the built-in HTTP or Tesseract selection logic at parse time.
LiteParse from the run-llama/liteparse repository is a Rust library that extracts text from PDFs and performs OCR on image-only regions. When you need to integrate a proprietary cloud OCR service, a local GPU inference engine, or a JavaScript callback in WebAssembly environments, the ocr_engine_override mechanism provides the only pathway to replace the default engine selection entirely.
Architecture of the ocr_engine_override Mechanism
The override system centers on the LiteParse struct in crates/liteparse/src/parser.rs, which stores an optional, type-erased OCR engine:
pub struct LiteParse {
config: LiteParseConfig,
/// Optional caller‑provided OCR engine. When set, this overrides the
/// built‑in selection logic (HTTP OCR / Tesseract). This is the primary
/// mechanism for plugging an OCR engine in environments without the
/// built‑ins (e.g. WASM, where the JS side supplies a callback engine).
ocr_engine_override: Option<std::sync::Arc<dyn OcrEngine>>,
}
According to the source at parser.rs:37-42, this field accepts any type implementing the OcrEngine trait wrapped in an Arc. When Some(engine) is present, the parser uses it exclusively; when None, it falls back to the built-in selection logic.
The OcrEngine Trait Contract
Any custom engine must implement the OcrEngine trait defined in ocr/mod.rs:28-43:
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 recognize method receives raw image bytes, dimensions, and language options, returning a future that resolves to a vector of OcrResult structs. On native targets, the trait requires Send + Sync bounds; on wasm32, the Send bound is omitted to accommodate JavaScript's single-threaded model.
Setting the Override with with_ocr_engine
You install a custom engine through the builder method defined at parser.rs:52-57:
/// Override the OCR engine. When set, the engine is used regardless of
/// `ocr_server_url` / built‑in Tesseract availability.
pub fn with_ocr_engine(mut self, engine: std::sync::Arc<dyn OcrEngine>) -> Self {
self.ocr_engine_override = Some(engine);
self
}
This consumes the LiteParse instance and stores the engine for the lifetime of the parser. Once set, the override takes precedence over any configuration values like ocr_server_url or the tesseract feature flag.
Runtime Engine Selection Logic
During parse_input, the parser evaluates the override at parser.rs:143-151:
let engine: std::sync::Arc<dyn OcrEngine> = if let Some(e) = self.ocr_engine_override.clone() {
// ✅ Custom engine supplied → use it
e
} else {
// ⬇️ Fall back to built‑in logic
#[cfg(not(target_arch = "wasm32"))] {
if let Some(ref url) = self.config.ocr_server_url {
std::sync::Arc::new(HttpOcrEngine::new(url.clone()))
} else {
#[cfg(feature = "tesseract")]
{
std::sync::Arc::new(TesseractOcrEngine::new(self.config.tessdata_path.clone()))
}
#[cfg(not(feature = "tesseract"))] {
return Err("OCR enabled but no --ocr-server-url provided and tesseract feature is disabled".into());
}
}
}
#[cfg(target_arch = "wasm32")] {
return Err("OCR enabled but no `ocrEngine` callback was provided (WASM builds have no built‑in OCR engine)".into());
}
};
This logic demonstrates that the override is all-or-nothing: if ocr_engine_override is Some, the custom engine wins immediately. Only when it is None does LiteParse consider HttpOcrEngine or TesseractOcrEngine.
Implementing a Custom Engine in Rust
The following example creates a dummy engine that implements the required trait and plugs it into the parser:
use liteparse::parser::LiteParse;
use liteparse::ocr::{OcrEngine, OcrOptions, OcrResult};
use std::pin::Pin;
use std::future::Future;
use std::sync::Arc;
// Minimal custom engine that always returns “hello”.
struct DummyEngine;
impl OcrEngine for DummyEngine {
fn name(&self) -> &str { "dummy" }
fn recognize<'a, 'b: 'a, 'c: 'a>(
&'a self,
_image_data: &'c [u8],
_width: u32,
_height: u32,
opts: &'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!("lang={}", opts.language),
bbox: [0.0, 0.0, 10.0, 10.0],
confidence: 1.0,
}])
})
}
}
let cfg = liteparse::config::LiteParseConfig::default();
let parser = LiteParse::new(cfg)
.with_ocr_engine(Arc::new(DummyEngine));
let result = parser.parse_input(liteparse::types::PdfInput::Path("sample.pdf".into()))
.await
.expect("parse failed");
println!("OCR output: {:?}", result.pages[0].text);
The engine must be wrapped in Arc<dyn OcrEngine> before passing to with_ocr_engine. The parser then invokes DummyEngine::recognize for every page requiring OCR.
WASM Integration: JavaScript OCR Callbacks
In WebAssembly builds, LiteParse cannot bundle native OCR libraries. Instead, you supply a JavaScript object that conforms to the engine interface. The WASM bridge at liteparse-wasm/src/lib.rs:63-86 wraps this object in JsOcrEngine, which implements the OcrEngine trait and forwards calls to JavaScript:
// JavaScript side
import init, { LiteParse } from "liteparse-wasm";
async function main() {
await init();
const ocrEngine = {
// Must return a Promise resolving to an array of {text, bbox, confidence}
async recognize(imageData, width, height, language) {
const resp = await fetch("https://my-ocr.example.com/ocr", {
method: "POST",
body: JSON.stringify({
image: Array.from(imageData),
width,
height,
language
})
});
return resp.json(); // → [{text:"…", bbox:[…], confidence:…}, …]
}
};
const parser = new LiteParse({ ocrEngine, ocrEnabled: true });
const pdfBytes = await fetch("sample.pdf").then(r => r.arrayBuffer());
const result = await parser.parse(new Uint8Array(pdfBytes));
console.log(result.text);
}
The JsOcrEngine converts the JavaScript promise into a Rust Future and maps the returned JSON to OcrResult structs, transparently satisfying the trait requirements.
Summary
- Storage:
LiteParseholds anOption<Arc<dyn OcrEngine>>in theocr_engine_overridefield atparser.rs:37-42. - Installation: Use
with_ocr_engine()to inject a custom implementation, as defined atparser.rs:52-57. - Precedence: The override bypasses all built-in logic (HTTP OCR, Tesseract) unconditionally during
parse_inputatparser.rs:143-151. - Contract: Custom engines must implement
name()andrecognize()from theOcrEnginetrait in [ocr/mod.rs](https://github.com/run-llama/liteparse/blob/main/crates/liteparse/src/ocr/mod.rs). - WASM Support: JavaScript callbacks are supported via
JsOcrEnginein the WASM bridge, enabling browser-based OCR without native binaries.
Frequently Asked Questions
Can I use ocr_engine_override alongside the built-in HTTP or Tesseract engines?
No. When you provide an engine via with_ocr_engine(), the ocr_engine_override field becomes Some(engine), causing the runtime selection logic at parser.rs:143-151 to skip the built-in HttpOcrEngine and TesseractOcrEngine entirely. The custom engine receives 100% of OCR calls.
What are the Send and Sync requirements for a custom OcrEngine?
On native targets (not(target_arch = "wasm32")), the OcrEngine trait requires Send + Sync because the engine may be moved across threads during async parsing. On wasm32, the Send bound is removed because JavaScript objects are not thread-safe, allowing single-threaded WASM usage.
How do I handle errors inside my custom recognize implementation?
The recognize method returns Result<Vec<OcrResult>, Box<dyn std::error::Error + Send + Sync>>. You can return any error type that implements the standard Error trait. LiteParse will propagate this error up through parse_input, allowing you to handle OCR failures at the call site.
Does LiteParse provide a default OCR engine in WASM builds?
No. According to the source code at parser.rs:143-151, WASM builds return an error if OCR is enabled but no override is provided: "OCR enabled but no ocrEngine callback was provided (WASM builds have no built‑in OCR engine)". You must always supply a JavaScript callback or custom engine in WebAssembly environments.
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 →