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
//! OCR with table recognition: TIFF → Markdown with GFM tables.
//!
//! Tables detected by PP-DocLayoutV3 are passed through SLANet_plus
//! structure recognition and rendered as GitHub Flavored Markdown tables.
//!
//! ## Usage
//!
//! ```powershell
//! $env:ORT_DYLIB_PATH = "C:\path\to\onnxruntime.dll"
//! $env:PPOCR_MODELS_DIR = "models\paddleocr"  # must contain table/SLANet_plus.onnx
//! cargo run --example with_tables -- path\to\document.tiff
//! ```
//!
//! ## Model layout expected in PPOCR_MODELS_DIR
//!
//! ```text
//! models/paddleocr/
//!   layout/PP-DocLayoutV3.onnx
//!   latin/det.onnx
//!   latin/rec_latin.onnx
//!   latin/dict_latin.txt
//!   table/SLANet_plus.onnx          ← table structure model
//!   table/table_structure_dict.txt  ← vocab for SLANet_plus
//! ```

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

fn main() -> Result<(), Box<dyn std::error::Error>> {
    let input: PathBuf = std::env::args().nth(1)
        .or_else(|| std::env::var("OCR_INPUT").ok())
        .map(PathBuf::from)
        .unwrap_or_else(|| {
            eprintln!("Usage: with_tables <input_file>");
            std::process::exit(1);
        });

    let base = PathBuf::from(
        std::env::var("PPOCR_MODELS_DIR").unwrap_or_else(|_| "models/paddleocr".into())
    );

    let layout_model = base.join("layout/PP-DocLayoutV3.onnx");

    let ocr_models = {
        let latin = base.join("latin");
        OcrModelPaths {
            det:  latin.join("det.onnx"),
            rec:  latin.join("rec_latin.onnx"),
            dict: latin.join("dict_latin.txt"),
        }
    };

    let table_models = {
        let tbl = base.join("table");
        let onnx = tbl.join("SLANet_plus.onnx");
        let dict = tbl.join("table_structure_dict.txt");
        if onnx.exists() && dict.exists() {
            Some(TableModelPaths { structure_onnx: onnx, structure_dict: dict, input_size: Some(488) })
        } else {
            eprintln!("[warn] SLANet_plus.onnx not found — table recognition disabled");
            None
        }
    };

    let enable_tables = table_models.is_some();
    let engine = PpOcrEngine::new(PpOcrEngineConfig {
        ocr_models: Some(ocr_models),
        table_models,
        enable_tables,
        ..PpOcrEngineConfig::default()
    })?;

    let mut pipeline = DocPipeline::new(Box::new(engine), &layout_model)?;
    let md = pipeline.process_file(&input, OutputFormat::Markdown)?;
    print!("{md}");

    Ok(())
}