# How to Implement the OcrEngine Trait for Custom OCR Backends in LiteParse

> Learn how to implement the OcrEngine trait for custom OCR backends in LiteParse. Define your struct, implement methods, and register your engine to extend LiteParse's OCR capabilities.

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

---

**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`](https://github.com/run-llama/liteparse/blob/main/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`](https://github.com/run-llama/liteparse/blob/main/crates/liteparse/src/ocr/mod.rs), the trait definition includes platform-specific requirements:

```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 + '_,
        >,
    >;
}

```

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, and `OcrOptions`, returning a pinned future that resolves to a vector of `OcrResult` structs.

**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.

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

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

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

```rust
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`. Use `reqwest::Client` for HTTP connections or protect mutable state with `Mutex`/`RwLock`.
- **Bounding Box Format:** The `bbox` field in `OcrResult` expects `[x1, y1, x2, y2]` coordinates in pixels relative to the input image dimensions. Ensure your OCR service returns coordinates matching the `width` and `height` parameters passed to `recognize`.
- **Language Handling:** The `OcrOptions` struct currently exposes the `language` field from `LiteParseConfig`. Respect this value when constructing requests to multilingual OCR services.
- **Future Type:** The boxed future must include `+ Send` on native builds but exclude it for `wasm32` targets. 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`](https://github.com/run-llama/liteparse/blob/main/crates/liteparse/src/ocr/tesseract.rs)** – Reference implementation using the Tesseract OCR library with local image processing.
- **[`crates/liteparse/src/ocr/http_simple.rs`](https://github.com/run-llama/liteparse/blob/main/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`](https://github.com/run-llama/liteparse/blob/main/crates/liteparse/src/config.rs)** – Configuration structure carrying OCR language settings and feature flags.

## Summary

- **Implement `OcrEngine`** in [`crates/liteparse/src/ocr/mod.rs`](https://github.com/run-llama/liteparse/blob/main/crates/liteparse/src/ocr/mod.rs) by defining a struct that is `Send + Sync` and providing the `name()` and `recognize()` methods.
- **Return a pinned future** from `recognize()` that resolves to `Vec<OcrResult>` with text content, bounding boxes in `[x1, y1, x2, y2]` format, and confidence scores.
- **Register via `with_ocr_engine`** by wrapping your engine in `Arc<dyn OcrEngine>` to override the default HTTP or Tesseract selection in [`crates/liteparse/src/parser.rs`](https://github.com/run-llama/liteparse/blob/main/crates/liteparse/src/parser.rs).
- **Handle platform differences** by ensuring `Send` bounds 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.