df-ocr-switcher 0.1.0

Document OCR pipeline in pure Rust: scanned PDF / multi-page TIFF / image → Markdown. PaddleOCR PP-OCRv6 and Tesseract 5.5 as interchangeable engines, PP-DocLayoutV3 layout for both.
Documentation

df-ocr-switcher

Document OCR pipeline in pure Rust — scanned PDF / multi-page TIFF / image → Markdown.

Crates.io License: MIT Rust 2021

Two OCR engines, one interface:

Engine Crate Feature flag
PaddleOCR PP-OCRv6 (default) ppocr-rs (always on)
Tesseract 5.5 tesseract5-rs tesseract-engine

Layout analysis (PP-DocLayoutV3) and document-orientation correction are shared by both engines. Table structure recognition (SLANet_plus → GFM Markdown) is available in the PaddleOCR path.


Installation

[dependencies]
# PaddleOCR engine only (default):
df-ocr-switcher = "0.1"

# + Tesseract engine:
df-ocr-switcher = { version = "0.1", features = ["tesseract-engine"] }

Runtime requirement — ONNX Runtime shared library. Set ORT_DYLIB_PATH to the path of onnxruntime.dll / libonnxruntime.so before running. Download from github.com/microsoft/onnxruntime/releases.


Quick start

Process a TIFF to Markdown (PaddleOCR)

use df_ocr_switcher::{DocPipeline, OutputFormat, PpOcrEngine, PpOcrEngineConfig};
use std::path::PathBuf;

fn main() -> Result<(), Box<dyn std::error::Error>> {
    // PP-DocLayoutV3 model (download from PaddlePaddle ModelHub or build ppocr-rs)
    let layout_model = PathBuf::from("models/paddleocr/layout/PP-DocLayoutV3.onnx");

    // Engine with auto-download of PP-OCRv6 Tiny models
    let engine = PpOcrEngine::new(PpOcrEngineConfig::default())?;
    let mut pipeline = DocPipeline::new(Box::new(engine), &layout_model)?;

    let markdown = pipeline.process_file(
        &PathBuf::from("document.tiff"),
        OutputFormat::Markdown,
    )?;
    println!("{markdown}");
    Ok(())
}

With local model paths (no auto-download)

use df_ocr_switcher::{DocPipeline, OcrModelPaths, OutputFormat, PpOcrEngine, PpOcrEngineConfig};
use std::path::PathBuf;

fn main() -> Result<(), Box<dyn std::error::Error>> {
    let models = PathBuf::from("models/paddleocr");

    let engine = PpOcrEngine::new(PpOcrEngineConfig {
        ocr_models: Some(OcrModelPaths {
            det:  models.join("latin/det.onnx"),
            rec:  models.join("latin/rec_latin.onnx"),
            dict: models.join("latin/dict_latin.txt"),
        }),
        ..PpOcrEngineConfig::default()
    })?;

    let layout_model = models.join("layout/PP-DocLayoutV3.onnx");
    let mut pipeline = DocPipeline::new(Box::new(engine), &layout_model)?;

    let md = pipeline.process_file(&PathBuf::from("scan.tiff"), OutputFormat::Markdown)?;
    println!("{md}");
    Ok(())
}

With table recognition (GFM Markdown)

use df_ocr_switcher::{DocPipeline, OcrModelPaths, OutputFormat,
                       PpOcrEngine, PpOcrEngineConfig, TableModelPaths};
use std::path::PathBuf;

fn main() -> Result<(), Box<dyn std::error::Error>> {
    let models = PathBuf::from("models/paddleocr");

    let engine = PpOcrEngine::new(PpOcrEngineConfig {
        ocr_models: Some(OcrModelPaths {
            det:  models.join("latin/det.onnx"),
            rec:  models.join("latin/rec_latin.onnx"),
            dict: models.join("latin/dict_latin.txt"),
        }),
        enable_tables: true,
        table_models: Some(TableModelPaths {
            structure_onnx: models.join("table/SLANet_plus.onnx"),
            structure_dict: models.join("table/table_structure_dict.txt"),
            input_size:     Some(488),  // SLANet_plus uses 488×488
        }),
        ..PpOcrEngineConfig::default()
    })?;

    let layout_model = models.join("layout/PP-DocLayoutV3.onnx");
    let mut pipeline = DocPipeline::new(Box::new(engine), &layout_model)?;

    let md = pipeline.process_file(&PathBuf::from("document_with_tables.tiff"), OutputFormat::Markdown)?;
    // Tables are rendered as GFM:
    //   | Col A | Col B |
    //   |-------|-------|
    //   | val 1 | val 2 |
    println!("{md}");
    Ok(())
}

Tesseract engine (requires features = ["tesseract-engine"])

#[cfg(feature = "tesseract-engine")]
use df_ocr_switcher::{DocPipeline, OutputFormat, TesseractEngine, TesseractEngineConfig};
use std::path::PathBuf;

#[cfg(feature = "tesseract-engine")]
fn main() -> Result<(), Box<dyn std::error::Error>> {
    let layout_model = PathBuf::from("models/paddleocr/layout/PP-DocLayoutV3.onnx");

    let engine = TesseractEngine::new(TesseractEngineConfig {
        lang: "ita+eng".into(),
        tessdata: Some(PathBuf::from("/usr/share/tessdata")),
        ori_model: None,
        psm: None,
    })?;

    let mut pipeline = DocPipeline::new(Box::new(engine), &layout_model)?;
    let md = pipeline.process_file(&PathBuf::from("document.tiff"), OutputFormat::Markdown)?;
    println!("{md}");
    Ok(())
}

CLI — ocr-doc

The crate ships an ocr-doc binary:

# ARM64 Windows
$env:ORT_DYLIB_PATH = "C:\path\to\onnxruntime.dll"
$env:PPOCR_MODELS_DIR = "models\paddleocr"

# Basic OCR → Markdown
ocr-doc document.tiff

# With table recognition
ocr-doc document.tiff --tables

# Tesseract engine
ocr-doc document.tiff --engine tesseract --lang ita+eng

# Save to file
ocr-doc document.tiff --tables --output result.md

Output formats

Format OutputFormat Notes
Markdown OutputFormat::Markdown GFM tables, # headings, $$ LaTeX equations
JSON OutputFormat::Json Per-page structured output with bounding boxes

Environment variables

Variable Description
ORT_DYLIB_PATH Path to onnxruntime.dll / libonnxruntime.so (required)
PPOCR_MODELS_DIR Base dir for models (layout/, latin/, table/)
PPOCR_LAYOUT_MODEL Override PP-DocLayoutV3.onnx path
TESSDATA_PREFIX tessdata directory (Tesseract engine)

Features

Feature Default Description
tesseract-engine off Enable Tesseract 5.5 as alternate OCR engine
searchable-pdf off Add invisible text layer to PDF output (lopdf)

License

MIT — see LICENSE.