Tools,

OCR in Rust, tested on scanned pages

1,743

of 1,955 words found

Get the commands

Run ocrs on a scanned PDF

35 s for 10 pages, Mac CPU, 2 threads

12 MB of models, fetched once

With Rust and poppler, next to scan.pdf:

cargo install ocrs-cli --locked
pdfimages -j scan.pdf p
for f in p-*.jpg; do ocrs "$f"; done > scan.txt

ocrs reads images, not PDFs, so pdfimages takes out the page JPEGs as they are. The install builds ocrs from source: 3 min 52 s with 2 jobs. After the first run, a run with no network gave the same text.

The PDF error

A PDF in place of an image stops:

Error: Failed to read image from scan.pdf

Caused by:
    The file extension `."pdf"` was not recognized as an image format

ocrs reads the Latin alphabet only, so English and other languages in Latin letters.

ocrs as a Rust library

Next to the page JPEGs:

cargo new rocr && cd rocr
cargo add ocrs@0.14 rten@0.28
cargo add image@0.25 --no-default-features --features jpeg,png
curl -sO https://ocrs-models.s3-accelerate.amazonaws.com/text-detection.onnx
curl -sO https://ocrs-models.s3-accelerate.amazonaws.com/text-recognition.onnx
cargo run --release -- p-*.jpg > scan.txt

Before the last line, move the JPEGs into rocr and save src/main.rs:

use ocrs::{ImageSource, OcrEngine, OcrEngineParams};
use rten::Model;

fn main() -> Result<(), Box<dyn std::error::Error>> {
    let engine = OcrEngine::new(OcrEngineParams {
        detection_model: Some(Model::load_file("text-detection.onnx")?),
        recognition_model: Some(Model::load_file("text-recognition.onnx")?),
        ..Default::default()
    })?;
    for path in std::env::args().skip(1) {
        let img = image::open(&path)?.into_rgb8();
        let src = ImageSource::from_bytes(img.as_raw(), img.dimensions())?;
        let input = engine.prepare_input(src)?;
        println!("{}", engine.get_text(&input)?);
    }
    Ok(())
}

Same text as the ocrs program. First build: 2 min 7 s with 2 jobs. Then 10 pages in 30 s.

Tesseract from Rust: leptess

With Homebrew Tesseract, add leptess = "=0.14.0" to Cargo.toml:

use leptess::LepTess;

fn main() -> Result<(), Box<dyn std::error::Error>> {
    let mut lt = LepTess::new(None, "eng")?;
    for path in std::env::args().skip(1) {
        lt.set_image(&path)?;
        print!("{}", lt.get_utf8_text()?);
    }
    Ok(())
}

It found the same 1,877 words as the Tesseract program, in 28 s. The build took 31 s. leptess has had no release since February 2023, and it still builds on Tesseract 5.5.4.

ocrs vs Tesseract, RapidOCR and PaddleOCR

Same 10 pages, 1,955 words
Tool Words found Time
ocrs 0.14.0 1,743 35 s
ocrs --beam 1,742 77 s
Tesseract 5.5.4 1,877 47 s
RapidOCR 3.10.0 1,894 55 s
PaddleOCR 3.7.0 1,908 333 s
Our Actor 1,874

ocrs is the fastest here, and it missed 134 more words than Tesseract. Its README says so first: early preview. The --beam option found 1 word less, in twice the time.

The other rows are from my RapidOCR test, on the same pages.

What the text looks like

The Boy Scouts of the Air
" Keep your head, Hatton," commanded
Hardy;  don't talk tommyrot. What in the
name of herring could one of the yacht's boats

One line per text line. It drops some quote marks and question marks, and misreads a few letters: OVer, nsed, bim, con?irm.

Other Rust OCR crates

crates.io, November 13
Crate Downloads, 90 days Newest
ocrs 355,652 Nov 2026
oar-ocr 88,401 Oct 2026
leptess 77,052 Feb 2023
tesseract 62,445 Apr 2025

leptess and tesseract call the Tesseract C library, so Tesseract must be installed too. I tested ocrs and leptess.

Need text with nothing to install?

Our Actor runs Tesseract, and RapidOCR on English pages that Tesseract reads badly. It takes PDF, JPG and PNG files and returns Markdown. On the same 10 pages, inside a PDF, it found 1,874 words.

curl -s -X POST "https://api.apify.com/v2/acts/brasspage~pdf-ocr-to-markdown/run-sync-get-dataset-items" \
  -H "Authorization: Bearer $APIFY_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"documentUrls": ["https://api.apify.com/v2/key-value-stores/sTnmNrGoSsi7tLQvs/records/anythingllm-mixed-11-pages.pdf"]}' \
  | jq -r '.[].markdown' > book.md

It costs $0.006 a page.

How I tested it
  • Pages: the 10 scans of OCR in n8n, proofread Wikisource book pages, 1,955 true words. Each page is one color JPEG, 1,920 x 3,079 pixels, at 300 ppi.
  • ocrs-cli 0.14.0 (ocrs 0.14.0, rten 0.28.0), built with Rust 1.96.1 on an Apple Silicon Mac: an 8.7 MB program. pdfimages from poppler 26.03.0. The library: image 0.25.10. leptess 0.14.0 on Homebrew Tesseract 5.5.4.
  • CPU, 2 threads (RTEN_NUM_THREADS=2), lowest priority, other jobs on the Mac. Times are rough. Peak memory of the ocrs program: 0.4 GB.
  • Score: true words found in order, with the scorer of the tessdata test.
  • The Actor: one real run, build 0.1.65, on November 12. Its file holds the same 10 pages plus a typed cover page.
What I did not test
  • The tesseract and oar-ocr crates.
  • Photos, other languages, Windows, Linux and WebAssembly.
Sources

Two ways to skip the work

Run it yourself. Our Apify Actor turns scanned PDFs into Markdown, tables included.

Try the Actor

Or send us the scan. Tack, our AI, does the job for $29, up to 50 pages. First page free.

Get one page free

Questions people ask

Is ocrs better than Tesseract?

Not on scanned book pages yet. ocrs 0.14.0 found 1,743 of 1,955 words in 35 s, and Tesseract 5.5.4 found 1,877 in 47 s. The ocrs README calls it an early preview.

Can ocrs read a PDF?

No. ocrs reads images. Take out the page images first, for example with pdfimages -j scan.pdf p, then run ocrs on each one.

Does ocrs need a network connection?

Only on the first run, to download its 2 models (12 MB) to ~/.cache/ocrs. After that, a run with no network gave the same text.

Can I use Tesseract from Rust?

Yes, with the leptess crate and Tesseract installed. leptess 0.14.0 found the same 1,877 words as the Tesseract program on the same 10 pages.