tesseract-rs is a Rust binding for Tesseract OCR with built-in compilation of Tesseract and Leptonica libraries. This project aims to provide a safe and idiomatic Rust interface to Tesseract's functionality while handling the complexity of compiling the underlying C++ libraries.
- Safe Rust bindings for Tesseract OCR
- Built-in compilation of Tesseract and Leptonica
- Automatic download of Tesseract training data (English and Turkish)
- High-level Rust API for common OCR tasks
- Caching of compiled libraries for faster subsequent builds
- Support for multiple operating systems (Linux, macOS, Windows, FreeBSD)
- Optional embedded tessdata for single-binary deployment
Add this to your Cargo.toml:
[dependencies]
tesseract-rs = { version = "0.4.0", features = ["build-tesseract"] }For single-binary deployment with embedded tessdata:
[dependencies]
tesseract-rs = { version = "0.4.0", features = ["embed-tessdata"] }By default, both English and Turkish tessdata are embedded. To embed only specific languages, set the TESSERACT_EMBED_LANGUAGES environment variable during build:
# Embed only English
TESSERACT_EMBED_LANGUAGES=eng cargo build --features embed-tessdata
# Embed multiple languages
TESSERACT_EMBED_LANGUAGES=eng,fra,deu cargo build --features embed-tessdataInstead of compiling the bundled Tesseract/Leptonica sources (minutes), you
can link against a system-installed Tesseract with the use-system-tesseract
feature. The build then locates tesseract.pc via pkg-config:
[dependencies]
tesseract-rs = { version = "0.4.0", default-features = false, features = ["use-system-tesseract"] }# macOS
brew install tesseract
# Debian/Ubuntu
sudo apt install libtesseract-dev libleptonica-devTESSDATA_PREFIX or a tessdata directory passed to init() is still used to
locate language data.
The workspace also ships tesseract-rs-cli, a small OCR command-line tool:
cargo install tesseract-rs-cli # bundled Tesseract
cargo install tesseract-rs-cli --no-default-features --features use-system-tesseract # system Tesseract
tesseract-rs image.png
tesseract-rs -l eng+tur --psm 6 -o hocr scanned.pngFor development and testing, you'll also need these dependencies:
[dev-dependencies]
image = "0.25.10"
imageproc = "0.27.0"To build this crate, you need:
- A C++ compiler (e.g., gcc, clang)
- CMake
- Internet connection (for downloading Tesseract training data)
- Rust 1.88 or later
The following environment variables affect the build and test process:
CARGO_CLEAN: If set, cleans the cache directory before buildingRUSTC_WRAPPER: If set to "sccache", enables compiler caching with sccacheCC: Compiler selection for C code (affects Linux builds)HOME(Unix) orAPPDATA(Windows): Used to determine cache directory location
TESSDATA_PREFIX(Optional): Path to override the default tessdata directory. If not set, the crate will use its default cache directory.
The crate uses the following directory structure based on your operating system:
- macOS:
~/Library/Application Support/tesseract-rs - Linux:
~/.tesseract-rs - FreeBSD:
~/.tesseract-rs - Windows:
%APPDATA%/tesseract-rs
The cache includes:
- Compiled Tesseract and Leptonica libraries
- Downloaded training data (eng.traineddata, tur.traineddata) in the
tessdatasubdirectory - Third-party source code
The training data files are automatically downloaded and placed in the appropriate tessdata subdirectory during the build process. You don't need to manually set up the tessdata directory unless you want to use a custom location.
The project includes several integration tests that verify OCR functionality. To run the tests:
-
Ensure you have the required test dependencies:
[dev-dependencies] image = "0.25.10" imageproc = "0.27.0"
-
Run the tests:
cargo test
Note: Setting TESSDATA_PREFIX is optional. If not set, the tests will use the default tessdata directory in the cache location. If you want to use a custom tessdata directory, you can set it:
# Linux/macOS
export TESSDATA_PREFIX=/path/to/custom/tessdata
# Windows (PowerShell)
$env:TESSDATA_PREFIX="C:\path\to\custom\tessdata"Available test cases:
test_multiple_languages_with_lstm: Tests LSTM engine with multiple languagestest_ocr_on_real_image: Tests OCR on a sample English text imagetest_multiple_languages: Tests recognition of mixed English and Turkish texttest_digit_recognition: Tests digit-only recognition with whitelisttest_error_handling: Tests error cases and invalid inputs
Test images are located in the tests/test_images/ directory:
sample_text.png: English text samplemultilang_sample.png: Mixed English and Turkish text- Additional test images can be added to this directory
Here's a basic example of how to use tesseract-rs:
use std::path::PathBuf;
use std::error::Error;
use tesseract_rs::TesseractAPI;
fn get_default_tessdata_dir() -> PathBuf {
if cfg!(target_os = "macos") {
let home_dir = std::env::var("HOME").expect("HOME environment variable not set");
PathBuf::from(home_dir)
.join("Library")
.join("Application Support")
.join("tesseract-rs")
.join("tessdata")
} else if cfg!(target_os = "linux") {
let home_dir = std::env::var("HOME").expect("HOME environment variable not set");
PathBuf::from(home_dir)
.join(".tesseract-rs")
.join("tessdata")
} else if cfg!(target_os = "freebsd") {
let home_dir = std::env::var("HOME").expect("HOME environment variable not set");
PathBuf::from(home_dir)
.join(".tesseract-rs")
.join("tessdata")
} else if cfg!(target_os = "windows") {
PathBuf::from(std::env::var("APPDATA").expect("APPDATA environment variable not set"))
.join("tesseract-rs")
.join("tessdata")
} else {
panic!("Unsupported operating system");
}
}
fn get_tessdata_dir() -> PathBuf {
match std::env::var("TESSDATA_PREFIX") {
Ok(dir) => {
let path = PathBuf::from(dir);
println!("Using TESSDATA_PREFIX directory: {:?}", path);
path
}
Err(_) => {
let default_dir = get_default_tessdata_dir();
println!(
"TESSDATA_PREFIX not set, using default directory: {:?}",
default_dir
);
default_dir
}
}
}
fn main() -> Result<(), Box<dyn Error>> {
let api = TesseractAPI::new();
// Get tessdata directory (uses default location or TESSDATA_PREFIX if set)
let tessdata_dir = get_tessdata_dir();
api.init(tessdata_dir.to_str().unwrap(), "eng")?;
let width = 24;
let height = 24;
let bytes_per_pixel = 1;
let bytes_per_line = width * bytes_per_pixel;
// Initialize image data with all white pixels
let mut image_data = vec![255u8; width * height];
// Draw number 9 with clearer distinction
for y in 4..19 {
for x in 7..17 {
// Top bar
if y == 4 && x >= 8 && x <= 15 {
image_data[y * width + x] = 0;
}
// Top curve left side
if y >= 4 && y <= 10 && x == 7 {
image_data[y * width + x] = 0;
}
// Top curve right side
if y >= 4 && y <= 11 && x == 16 {
image_data[y * width + x] = 0;
}
// Middle bar
if y == 11 && x >= 8 && x <= 15 {
image_data[y * width + x] = 0;
}
// Bottom right vertical line
if y >= 11 && y <= 18 && x == 16 {
image_data[y * width + x] = 0;
}
// Bottom bar
if y == 18 && x >= 8 && x <= 15 {
image_data[y * width + x] = 0;
}
}
}
// Set the image data
api.set_image(
&image_data,
width.try_into().unwrap(),
height.try_into().unwrap(),
bytes_per_pixel.try_into().unwrap(),
bytes_per_line.try_into().unwrap(),
)?;
// Set whitelist for digits only
api.set_variable("tessedit_char_whitelist", "0123456789")?;
// Set PSM mode to single character
api.set_variable("tessedit_pageseg_mode", "10")?;
// Get the recognized text
let text = api.get_utf8_text()?;
println!("Recognized text: {}", text.trim());
Ok(())
}When using the embed-tessdata feature, tessdata files are embedded directly into your binary, eliminating the need to ship separate tessdata files:
use tesseract_rs::TesseractAPI;
use std::error::Error;
fn main() -> Result<(), Box<dyn Error>> {
let api = TesseractAPI::new();
// Initialize with embedded tessdata - no external files needed!
api.init_embedded("eng")?;
// Check what languages are available
let available_languages = api.embedded_languages();
println!("Available embedded languages: {:?}", available_languages);
let width = 24;
let height = 24;
let bytes_per_pixel = 1;
let bytes_per_line = width * bytes_per_pixel;
// Create simple test image
let mut image_data = vec![255u8; width * height];
// Draw a simple "9"
for y in 4..19 {
for x in 7..17 {
if y == 4 && x >= 8 && x <= 15 {
image_data[y * width + x] = 0;
}
if y >= 4 && y <= 10 && x == 7 {
image_data[y * width + x] = 0;
}
if y >= 4 && y <= 11 && x == 16 {
image_data[y * width + x] = 0;
}
if y == 11 && x >= 8 && x <= 15 {
image_data[y * width + x] = 0;
}
if y >= 11 && y <= 18 && x == 16 {
image_data[y * width + x] = 0;
}
if y == 18 && x >= 8 && x <= 15 {
image_data[y * width + x] = 0;
}
}
}
api.set_image(
&image_data,
width.try_into().unwrap(),
height.try_into().unwrap(),
bytes_per_pixel.try_into().unwrap(),
bytes_per_line.try_into().unwrap(),
)?;
api.set_variable("tessedit_char_whitelist", "0123456789")?;
api.set_variable("tessedit_pageseg_mode", "10")?;
let text = api.get_utf8_text()?;
println!("Recognized text: {}", text.trim());
Ok(())
}The API provides additional functionality for more complex OCR tasks, including thread-safe operations:
use tesseract_rs::TesseractAPI;
use std::sync::Arc;
use std::thread;
use std::error::Error;
fn main() -> Result<(), Box<dyn Error>> {
let tessdata_dir = get_tessdata_dir();
let api = TesseractAPI::new();
// Initialize the main API
api.init(tessdata_dir.to_str().unwrap(), "eng")?;
api.set_variable("tessedit_pageseg_mode", "1")?;
// Load and prepare image data
let (image_data, width, height) = load_test_image("sample_text.png")?;
// Share image data across threads
let image_data = Arc::new(image_data);
let mut handles = vec![];
// Spawn multiple threads for parallel OCR processing
for _ in 0..3 {
let api_clone = api.try_clone()?; // Clone the API with all configurations
let image_data = Arc::clone(&image_data);
let handle = thread::spawn(move || {
// Set image in each thread
let res = api_clone.set_image(
&image_data,
width as i32,
height as i32,
3,
3 * width as i32,
);
assert!(res.is_ok());
// Perform OCR in parallel
let text = api_clone.get_utf8_text()
.expect("Failed to get text");
println!("Thread result: {}", text);
});
handles.push(handle);
}
// Wait for all threads to complete
for handle in handles {
handle.join().unwrap();
}
Ok(())
}
// Helper function to get tessdata directory
fn get_tessdata_dir() -> PathBuf {
// ... (implementation as shown in basic example)
}
// Helper function to load test image
fn load_test_image(filename: &str) -> Result<(Vec<u8>, u32, u32), Box<dyn Error>> {
let img = image::open(filename)?
.to_rgb8();
let (width, height) = img.dimensions();
Ok((img.into_raw(), width, height))
}The crate will automatically download and compile Tesseract and Leptonica during the build process. This may take some time on the first build, but subsequent builds will use the cached libraries.
To clean the cache and force a rebuild:
CARGO_CLEAN=1 cargo buildFor more detailed information, please check the API documentation.
This project is licensed under the MIT License - see the LICENSE file for details.
- Cafer Can Gündoğdu — author and maintainer
- Michael Stowe — FreeBSD support and embedded tessdata
- Yage — deflate-only
zipconfiguration - York Xiang — Windows
$HOMEhandling
We welcome contributions! Please see our Contributing Guide for details.
- Fork and clone the repository
- Install development dependencies:
./setup-hooks.sh
- Make your changes following our commit message format
- Run tests:
cargo test - Submit a Pull Request
Our commit messages follow the Conventional Commits specification.
This project uses Tesseract OCR and Leptonica. We are grateful to the maintainers and contributors of these projects.