# How to Implement Custom OCR Engines with LiteParse's OcrEngine Trait

> Learn to implement custom OCR engines with LiteParse's OcrEngine trait. Override default backends by providing name and recognize methods and injecting your implementation.

- Repository: [LlamaIndex/liteparse](https://github.com/run-llama/liteparse)
- Tags: how-to-guide
- Published: 2026-06-01

---

**Implement the `OcrEngine` trait defined in [`crates/liteparse/src/ocr/mod.rs`](https://github.com/run-llama/liteparse/blob/main/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`](https://github.com/run-llama/liteparse/blob/main/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.

```rust
// 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.

```rust
// 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`](https://github.com/run-llama/liteparse/blob/main/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.

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

```rust
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`](https://github.com/run-llama/liteparse/blob/main/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`](https://github.com/run-llama/liteparse/blob/main/crates/liteparse/src/ocr/tesseract.rs) demonstrates integration with the local Tesseract OCR library, while [`crates/liteparse/src/ocr/http_simple.rs`](https://github.com/run-llama/liteparse/blob/main/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 `OcrEngine`** from [`crates/liteparse/src/ocr/mod.rs`](https://github.com/run-llama/liteparse/blob/main/crates/liteparse/src/ocr/mod.rs) by defining `name()` and the async `recognize()` method with correct lifetime bounds.
- **Handle platform differences**: Maintain `Send + Sync` for native targets; the trait automatically adapts for WebAssembly by relaxing the `Send` bound on futures.
- **Use `Arc<dyn OcrEngine>`** to share your engine across async boundaries and inject it via `LiteParse::with_ocr_engine` to bypass default selection logic.
- **Return proper coordinates** in `[x1, y1, x2, y2]` format and propagate errors as boxed `dyn std::error::Error + Send + Sync`.
- **Consult reference implementations** in [`tesseract.rs`](https://github.com/run-llama/liteparse/blob/main/tesseract.rs) and [`http_simple.rs`](https://github.com/run-llama/liteparse/blob/main/http_simple.rs) for 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`](https://github.com/run-llama/liteparse/blob/main/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.