pdf-ocr 1.1.0

OCR integration for scanned PDFs with pluggable engine support
Documentation

pdf-ocr

OCR integration for PDFluent — bring your own recognizer, we do the PDF.

This crate is part of PDFluent, a pure-Rust PDF SDK. Source: https://github.com/pdfluent/pdfluent-sdk.

AGPL-3.0 or a commercial licence, at your option — see the Licence section below.

What it does

Turns a scanned PDF into a searchable one. You provide the character recognition; this crate renders the pages, hands them to your recognizer, and writes the words back as an invisible text layer positioned over the image.

The recognizer is a trait:

pub trait OcrEngine: Send + Sync {
    fn recognize(&self, image_data: &[u8], width: u32, height: u32, dpi: u32)
        -> Result<OcrPageResult, String>;
    fn supported_languages(&self) -> Vec<String>;
}

Implement it against whatever you already use — Google Cloud Vision, AWS Textract, Azure Document Intelligence, a self-hosted service, a local binary — and pass it to make_searchable together with a page-render callback. You get back an OcrReport per page.

Three local backends ship behind their own feature flags, and they are not equivalent:

paddle — PaddleOCR via ONNX. Detection with DBNet, recognition with SVTR, optional angle classification. Handles English, Latin scripts, Chinese, Japanese, Korean and Arabic. It uses ort with load-dynamic, so ONNX Runtime is loaded as a shared library at run time rather than compiled in, and the model weights are downloaded once (tens of megabytes for the current detection model) and cached in the OS cache directory. After that first fetch it runs entirely offline, in-process, with no cloud calls.

tesseract — Tesseract 4 or 5 through leptess. Requires the Tesseract and Leptonica system libraries to be installed.

tesseract-cli — the system tesseract binary as a subprocess (TesseractCliEngine): the page raster goes to its stdin as PNG and the TSV comes back as words. Standard library only, so it adds no dependency and nothing to the package. The binary is found on PATH or at PDFLUENT_TESSERACT; where neither names one, constructing the engine fails and the caller runs without OCR.

Neither is on by default, and the pdfluent facade wires neither, so a plain pdfluent dependency pulls in no system libraries and downloads no models. Opting in is a decision you make rather than a side effect of using the SDK.

Status

The seam and the text-layer work are stable in shape. Recognition quality is entirely your provider's.

Usage

Depend on this crate directly. The ocr-tesseract and ocr-paddle flags on the pdfluent facade are reserved names that enable nothing — the facade does not wire OCR at all, precisely because the local engines need C libraries.

use pdf_ocr::{make_searchable, OcrConfig, OcrEngine, OcrPageResult, OcrWord};

struct MyCloudOcr { /* http client, credentials */ }

impl OcrEngine for MyCloudOcr {
    fn recognize(&self, rgb: &[u8], width: u32, height: u32, dpi: u32)
        -> Result<OcrPageResult, String>
    {
        // POST the image to your provider, then map each word onto
        //   OcrWord { text, bbox_px: [x0, y0, x1, y1], confidence }
        // with bbox_px in pixels of the image you were handed, and return
        //   OcrPageResult { words, confidence, image_width, image_height }
        // so the text layer can be scaled back onto the page.
        todo!()
    }
    fn supported_languages(&self) -> Vec<String> { vec!["eng".into()] }
}

Then call make_searchable(&mut doc, &engine, &config, render_fn).

For PaddleOCR or Tesseract instead of a cloud service, enable the matching feature and use the engine it provides — the rest of the call is identical, because they implement the same trait:

pdf-ocr = { version = "1", features = ["paddle"] }     # or ["tesseract"]
// Weights you already have on disk. Nothing is fetched.
let config = pdf_ocr::paddle::PaddleOcrConfig {
    model_dir: "/opt/ocr-models".into(),
    ..Default::default()
};
let engine = pdf_ocr::PaddleOcrEngine::with_config(config)?;

// or Tesseract, which uses the system installation:
let engine = pdf_ocr::TesseractEngine::new("eng", None)?;

Model weights are never fetched behind your back

Constructing a PaddleOCR engine does not download anything. If the weights are not in model_dir you get an error naming the missing files and the directory they belong in — not a silent transfer of tens of megabytes from a host you did not choose.

If you do want this crate to fetch them, say so and pin what you expect:

use std::collections::BTreeMap;
use pdf_ocr::paddle::models::ModelSource;

let mut digests = BTreeMap::new();
digests.insert("detection/v3/det.onnx".into(), "…64 hex chars…".into());
digests.insert("latin/rec.onnx".into(),        "…".into());
digests.insert("latin/dict.txt".into(),        "…".into());

config.model_source = ModelSource::Verified {
    base_url: pdf_ocr::paddle::models::REFERENCE_MODEL_BASE_URL.into(),
    digests,
};

Each file is held in memory, hashed, and only written if it matches. A file with no pinned digest is refused rather than fetched — something unverifiable should not land on your disk at all. REFERENCE_MODEL_BASE_URL points at the public third-party repository the ONNX exports come from; it is offered for convenience, not as an endorsement, which is exactly why pinning is your call and not our default.

For the full signature, see https://pdfluent.com/docs.

Licence

AGPL-3.0 or a commercial licence, at your option. The AGPL is the default and the complete product: there is no licence key, no activation call and no tier, and every feature works in every build. If you cannot accept the copyleft obligation, the commercial licence is sold yearly and self-service at pdfluent.com/sdk/pricing, in four options: Commercial (per organisation), OEM Startup and OEM (per product), and Priority support (an add-on). The two texts are LICENSE and LICENSE-COMMERCIAL in this crate.

Links