# df-ocr-switcher
Document OCR pipeline in pure Rust — scanned PDF / multi-page TIFF / image → **Markdown**.
[](https://crates.io/crates/df-ocr-switcher)
[](LICENSE)
[](https://www.rust-lang.org/)
Two OCR engines, one interface:
| **PaddleOCR PP-OCRv6** (default) | [`ppocr-rs`](https://crates.io/crates/ppocr-rs) | _(always on)_ |
| **Tesseract 5.5** | [`tesseract5-rs`](https://crates.io/crates/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
```toml
[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](https://github.com/microsoft/onnxruntime/releases).
---
## Quick start
### Process a TIFF to Markdown (PaddleOCR)
```rust
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)
```rust
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)
```rust
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"]`)
```rust
#[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:
```powershell
# 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
| Markdown | `OutputFormat::Markdown` | GFM tables, `#` headings, `$$` LaTeX equations |
| JSON | `OutputFormat::Json` | Per-page structured output with bounding boxes |
---
## Environment variables
| `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
| `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](LICENSE).