# How the ocr_engine_override Mechanism Works in LiteParse: Plugging Custom OCR Engines

> Discover how LiteParse's ocr_engine_override lets you seamlessly plug custom OCR engines bypassing built-in logic. Enhance your parsing with this powerful feature.

- Repository: [LlamaIndex/liteparse](https://github.com/run-llama/liteparse)
- Tags: internals
- Published: 2026-05-31

---

**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`](https://github.com/run-llama/liteparse/blob/main/crates/liteparse/src/parser.rs), which stores an optional, type-erased OCR engine:

```rust
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`](https://github.com/run-llama/liteparse/blob/main/crates/liteparse/src/parser.rs#L37-L42), 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`](https://github.com/run-llama/liteparse/blob/main/crates/liteparse/src/ocr/mod.rs#L28-L43):

```rust
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`](https://github.com/run-llama/liteparse/blob/main/crates/liteparse/src/parser.rs#L52-L57):

```rust
/// 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`](https://github.com/run-llama/liteparse/blob/main/crates/liteparse/src/parser.rs#L143-L151):

```rust
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:

```rust
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`](https://github.com/run-llama/liteparse/blob/main/crates/liteparse-wasm/src/lib.rs#L63-L86) wraps this object in `JsOcrEngine`, which implements the `OcrEngine` trait and forwards calls to JavaScript:

```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**: `LiteParse` holds an `Option<Arc<dyn OcrEngine>>` in the `ocr_engine_override` field at [`parser.rs:37-42`](https://github.com/run-llama/liteparse/blob/main/crates/liteparse/src/parser.rs#L37-L42).
- **Installation**: Use `with_ocr_engine()` to inject a custom implementation, as defined at [`parser.rs:52-57`](https://github.com/run-llama/liteparse/blob/main/crates/liteparse/src/parser.rs#L52-L57).
- **Precedence**: The override bypasses all built-in logic (HTTP OCR, Tesseract) unconditionally during `parse_input` at [`parser.rs:143-151`](https://github.com/run-llama/liteparse/blob/main/crates/liteparse/src/parser.rs#L143-L151).
- **Contract**: Custom engines must implement `name()` and `recognize()` from the `OcrEngine` trait in [[`ocr/mod.rs`](https://github.com/run-llama/liteparse/blob/main/ocr/mod.rs)](https://github.com/run-llama/liteparse/blob/main/crates/liteparse/src/ocr/mod.rs).
- **WASM Support**: JavaScript callbacks are supported via `JsOcrEngine` in 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`](https://github.com/run-llama/liteparse/blob/main/crates/liteparse/src/parser.rs#L143-L151) 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`](https://github.com/run-llama/liteparse/blob/main/crates/liteparse/src/parser.rs#L143-L151), 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.