Native Rust port of TexTeller - convert images of math formulas into LaTeX, byte-exact with the upstream Python implementation.
Rust
0
19 commits
updated May 2, 2026
Native Rust port of TexTeller — convert images of math formulas into LaTeX, byte-exact with the upstream Python implementation.
TexTeller-Rust is a clean reimplementation in Rust of the inference pipeline used by OleehyO/TexTeller. It loads the same official ONNX models from HuggingFace and produces token-by-token identical output to the reference Python implementation (optimum.ORTModelForVision2Seq).
pip install. One executable embeds the ONNX Runtime native bindings.TexTeller::predict() from any Rust application (drawing apps, note-taking tools, document scanners).Input — samples/equation.png:

Command:
./target/release/texteller-rust samples/equation.png
Raw output (to stdout):
\frac{ \sqrt{2x}}{2x}= \sum_{i=0}^{ \infty}3i+2+5x
Rendered:
$$\frac{\sqrt{2x}}{2x} = \sum_{i=0}^{\infty} 3i + 2 + 5x$$
On samples/equation.png, the tokens generated and the resulting LaTeX string match the upstream Python implementation byte-by-byte:
PYTHON TexTeller: \frac{ \sqrt{2x}}{2x}= \sum_{i=0}^{ \infty}3i+2+5x
RUST our: \frac{ \sqrt{2x}}{2x}= \sum_{i=0}^{ \infty}3i+2+5x
Verified by dumping intermediate tensors and diffing them numerically across the entire pipeline (preprocessing → encoder → decoder → postprocessing).
image (PNG/JPG)
│
│ preprocess.rs
│ 1. RGB load
│ 2. trim white border (matches Python's Counter.most_common ordering)
│ 3. RGB → grayscale (ITU-R 601, truncation, exact torchvision coeffs)
│ 4. resize aspect-preserving (BICUBIC + antialias, fast_image_resize)
│ 5. normalize (mean=0.9545, std=0.154)
│ 6. pad to 448×448 with 0.0 (top-left placement)
▼
tensor [1, 1, 448, 448]
│
│ encoder.rs (encoder_model.onnx, ViT)
▼
encoder_hidden_states [1, 785, 768]
│
│ decoder.rs (decoder_model.onnx + decoder_with_past_model.onnx, TrOCR)
│ - First step: input_ids = [bos_token_id], no past
│ - Subsequent: input_ids = [last_token], past_key_values from prev step
│ - Greedy argmax until EOS or max_len (1024)
▼
token_ids
│
│ tokenizer.json (HuggingFace tokenizers)
▼
raw LaTeX string
│
│ postprocess.rs (faithful port of texteller's to_katex + remove_style)
▼
clean LaTeX string
git clone https://github.com/daxrpm/texteller-rust.git
cd texteller-rust
cargo build --release
The first build downloads the ONNX Runtime native library automatically (via ort's download-binaries feature) and takes ~3 minutes. Subsequent builds are seconds.
Run the binary on any image — the model files (~2 GB) are downloaded transparently from HuggingFace on the first run and cached under ~/.cache/huggingface/hub/. Subsequent runs use the cache.
./target/release/texteller-rust samples/equation.png
First run output:
Downloading 6 file(s) from 'OleehyO/TexTeller' on HuggingFace Hub...
[1/6] encoder_model.onnx [==============================] 344MB/344MB 45MB/s ETA 0s
[2/6] decoder_model.onnx [==============================] 909MB/909MB 50MB/s ETA 0s
[3/6] decoder_with_past_model.onnx [==============================] 833MB/833MB 50MB/s ETA 0s
[4/6] tokenizer.json [==============================] 1.0MB/1.0MB 300MB/s ETA 0s
[5/6] config.json [==============================] 4.5KB/4.5KB 100MB/s ETA 0s
[6/6] generation_config.json [==============================] 154B/154B 100KB/s ETA 0s
\frac{ \sqrt{2x}}{2x}= \sum_{i=0}^{ \infty}3i+2+5x
Cache is shared with the Python huggingface_hub package, so if you already pulled the models with another tool, no re-download happens.
# Default: auto-downloads models on first run, prints KaTeX-friendly LaTeX.
./target/release/texteller-rust samples/equation.png
# → \frac{ \sqrt{2x}}{2x}= \sum_{i=0}^{ \infty}3i+2+5x
# Verbose: progress logs to stderr.
./target/release/texteller-rust -v samples/equation.png
# Raw output (no postprocessing — keeps \[...\] wrappers).
./target/release/texteller-rust -f raw samples/equation.png
# → \[\frac{ \sqrt{2x}}{2x}= \sum_{i=0}^{ \infty}3i+2+5x\]
# Use a local directory of models (skip the HuggingFace cache lookup).
./target/release/texteller-rust --models-dir /path/to/models my_image.png
# Pin to a different HuggingFace repository (e.g. a fine-tune).
./target/release/texteller-rust --hf-repo my-org/my-checkpoint my_image.png
--help prints the full reference.
TexTeller-Rust is also a Rust library you can embed in your own application:
# Cargo.toml
[dependencies]
texteller-rust = "0.1"
use texteller_rust::TexTeller;
fn main() -> anyhow::Result<()> {
// First call downloads ~2 GB from HuggingFace (cached for next time);
// subsequent calls return immediately. Build once, reuse.
let mut model = TexTeller::from_huggingface()?;
let latex = model.predict("samples/equation.png")?;
println!("{latex}");
for img in &["a.png", "b.png", "c.png"] {
println!("{}", model.predict(img)?);
}
Ok(())
}
Three loaders are available:
TexTeller::from_huggingface] — download from OleehyO/TexTeller and cache.TexTeller::from_huggingface_repo] — same with a custom repo (e.g. a fine-tune).TexTeller::from_directory] — point at a local directory of model files.predict_raw() returns the model output without to_katex / remove_style
post-processing for callers that need full control.
src/
├── lib.rs Public API (TexTeller struct, top-level docs)
├── main.rs Thin CLI binary (clap-based)
├── config.rs Constants tied to the upstream model checkpoint
├── download.rs HuggingFace Hub auto-download with byte-level progress
├── ort_util.rs ort::Error<T> → anyhow::Error helper
├── preprocess.rs Image trim + grayscale + resize + normalize + pad
├── encoder.rs ViT ONNX session wrapper
├── decoder.rs TrOCR ONNX greedy decoder with KV cache
└── postprocess.rs to_katex + remove_style + change_all (faithful port)
Tests: 13 unit tests + 1 doctest. Run with:
cargo test --release
On an x86_64 CPU (no GPU), release build, samples/equation.png (1401×354 px → 30 tokens):
| Phase | Time |
|---|---|
| Cold start (model load) | ~1.0 s |
| Encoder forward | ~0.3 s |
| Decoder generation (30 tokens, KV cache) | ~1.5 s |
| Total per inference | ~1.8 s |
GPU support is available via ONNX Runtime providers (CUDA, ROCm, DirectML, CoreML, TensorRT) but is not exposed through the CLI yet — see ort documentation if you need it.
All the heavy lifting — the model architecture, training data, training code, and ONNX checkpoints used at runtime — is the work of OleehyO and the TexTeller contributors.
This project is purely a Rust runtime port. The upstream documentation, paper references, and training scripts are the source of truth for everything model-related.
This Rust crate is licensed under the GNU General Public License v3.0 or later, aligning with the free-software values of the project and with downstream consumers such as Rnote (also GPL-3.0-or-later).
The Rust source here is an independent reimplementation of the upstream TexTeller algorithms; no upstream code is copied verbatim. Apache-2.0 → GPL is one-way compatible, so basing this port on Apache-licensed work and releasing it under GPL is permitted. See NOTICE for attribution details.
Model weights are downloaded at runtime from HuggingFace under their original Apache-2.0 license; they are NOT redistributed by this project, and consuming them in your own projects falls under their own Apache terms.
19 commits
Rust
100.0%
Native Rust port of TexTeller - convert images of math formulas into LaTeX, byte-exact with the upstream Python implementation.
Rust
0
19 commits
updated May 2, 2026
Native Rust port of TexTeller — convert images of math formulas into LaTeX, byte-exact with the upstream Python implementation.
TexTeller-Rust is a clean reimplementation in Rust of the inference pipeline used by OleehyO/TexTeller. It loads the same official ONNX models from HuggingFace and produces token-by-token identical output to the reference Python implementation (optimum.ORTModelForVision2Seq).
pip install. One executable embeds the ONNX Runtime native bindings.TexTeller::predict() from any Rust application (drawing apps, note-taking tools, document scanners).Input — samples/equation.png:

Command:
./target/release/texteller-rust samples/equation.png
Raw output (to stdout):
\frac{ \sqrt{2x}}{2x}= \sum_{i=0}^{ \infty}3i+2+5x
Rendered:
$$\frac{\sqrt{2x}}{2x} = \sum_{i=0}^{\infty} 3i + 2 + 5x$$
On samples/equation.png, the tokens generated and the resulting LaTeX string match the upstream Python implementation byte-by-byte:
PYTHON TexTeller: \frac{ \sqrt{2x}}{2x}= \sum_{i=0}^{ \infty}3i+2+5x
RUST our: \frac{ \sqrt{2x}}{2x}= \sum_{i=0}^{ \infty}3i+2+5x
Verified by dumping intermediate tensors and diffing them numerically across the entire pipeline (preprocessing → encoder → decoder → postprocessing).
image (PNG/JPG)
│
│ preprocess.rs
│ 1. RGB load
│ 2. trim white border (matches Python's Counter.most_common ordering)
│ 3. RGB → grayscale (ITU-R 601, truncation, exact torchvision coeffs)
│ 4. resize aspect-preserving (BICUBIC + antialias, fast_image_resize)
│ 5. normalize (mean=0.9545, std=0.154)
│ 6. pad to 448×448 with 0.0 (top-left placement)
▼
tensor [1, 1, 448, 448]
│
│ encoder.rs (encoder_model.onnx, ViT)
▼
encoder_hidden_states [1, 785, 768]
│
│ decoder.rs (decoder_model.onnx + decoder_with_past_model.onnx, TrOCR)
│ - First step: input_ids = [bos_token_id], no past
│ - Subsequent: input_ids = [last_token], past_key_values from prev step
│ - Greedy argmax until EOS or max_len (1024)
▼
token_ids
│
│ tokenizer.json (HuggingFace tokenizers)
▼
raw LaTeX string
│
│ postprocess.rs (faithful port of texteller's to_katex + remove_style)
▼
clean LaTeX string
git clone https://github.com/daxrpm/texteller-rust.git
cd texteller-rust
cargo build --release
The first build downloads the ONNX Runtime native library automatically (via ort's download-binaries feature) and takes ~3 minutes. Subsequent builds are seconds.
Run the binary on any image — the model files (~2 GB) are downloaded transparently from HuggingFace on the first run and cached under ~/.cache/huggingface/hub/. Subsequent runs use the cache.
./target/release/texteller-rust samples/equation.png
First run output:
Downloading 6 file(s) from 'OleehyO/TexTeller' on HuggingFace Hub...
[1/6] encoder_model.onnx [==============================] 344MB/344MB 45MB/s ETA 0s
[2/6] decoder_model.onnx [==============================] 909MB/909MB 50MB/s ETA 0s
[3/6] decoder_with_past_model.onnx [==============================] 833MB/833MB 50MB/s ETA 0s
[4/6] tokenizer.json [==============================] 1.0MB/1.0MB 300MB/s ETA 0s
[5/6] config.json [==============================] 4.5KB/4.5KB 100MB/s ETA 0s
[6/6] generation_config.json [==============================] 154B/154B 100KB/s ETA 0s
\frac{ \sqrt{2x}}{2x}= \sum_{i=0}^{ \infty}3i+2+5x
Cache is shared with the Python huggingface_hub package, so if you already pulled the models with another tool, no re-download happens.
# Default: auto-downloads models on first run, prints KaTeX-friendly LaTeX.
./target/release/texteller-rust samples/equation.png
# → \frac{ \sqrt{2x}}{2x}= \sum_{i=0}^{ \infty}3i+2+5x
# Verbose: progress logs to stderr.
./target/release/texteller-rust -v samples/equation.png
# Raw output (no postprocessing — keeps \[...\] wrappers).
./target/release/texteller-rust -f raw samples/equation.png
# → \[\frac{ \sqrt{2x}}{2x}= \sum_{i=0}^{ \infty}3i+2+5x\]
# Use a local directory of models (skip the HuggingFace cache lookup).
./target/release/texteller-rust --models-dir /path/to/models my_image.png
# Pin to a different HuggingFace repository (e.g. a fine-tune).
./target/release/texteller-rust --hf-repo my-org/my-checkpoint my_image.png
--help prints the full reference.
TexTeller-Rust is also a Rust library you can embed in your own application:
# Cargo.toml
[dependencies]
texteller-rust = "0.1"
use texteller_rust::TexTeller;
fn main() -> anyhow::Result<()> {
// First call downloads ~2 GB from HuggingFace (cached for next time);
// subsequent calls return immediately. Build once, reuse.
let mut model = TexTeller::from_huggingface()?;
let latex = model.predict("samples/equation.png")?;
println!("{latex}");
for img in &["a.png", "b.png", "c.png"] {
println!("{}", model.predict(img)?);
}
Ok(())
}
Three loaders are available:
TexTeller::from_huggingface] — download from OleehyO/TexTeller and cache.TexTeller::from_huggingface_repo] — same with a custom repo (e.g. a fine-tune).TexTeller::from_directory] — point at a local directory of model files.predict_raw() returns the model output without to_katex / remove_style
post-processing for callers that need full control.
src/
├── lib.rs Public API (TexTeller struct, top-level docs)
├── main.rs Thin CLI binary (clap-based)
├── config.rs Constants tied to the upstream model checkpoint
├── download.rs HuggingFace Hub auto-download with byte-level progress
├── ort_util.rs ort::Error<T> → anyhow::Error helper
├── preprocess.rs Image trim + grayscale + resize + normalize + pad
├── encoder.rs ViT ONNX session wrapper
├── decoder.rs TrOCR ONNX greedy decoder with KV cache
└── postprocess.rs to_katex + remove_style + change_all (faithful port)
Tests: 13 unit tests + 1 doctest. Run with:
cargo test --release
On an x86_64 CPU (no GPU), release build, samples/equation.png (1401×354 px → 30 tokens):
| Phase | Time |
|---|---|
| Cold start (model load) | ~1.0 s |
| Encoder forward | ~0.3 s |
| Decoder generation (30 tokens, KV cache) | ~1.5 s |
| Total per inference | ~1.8 s |
GPU support is available via ONNX Runtime providers (CUDA, ROCm, DirectML, CoreML, TensorRT) but is not exposed through the CLI yet — see ort documentation if you need it.
All the heavy lifting — the model architecture, training data, training code, and ONNX checkpoints used at runtime — is the work of OleehyO and the TexTeller contributors.
This project is purely a Rust runtime port. The upstream documentation, paper references, and training scripts are the source of truth for everything model-related.
This Rust crate is licensed under the GNU General Public License v3.0 or later, aligning with the free-software values of the project and with downstream consumers such as Rnote (also GPL-3.0-or-later).
The Rust source here is an independent reimplementation of the upstream TexTeller algorithms; no upstream code is copied verbatim. Apache-2.0 → GPL is one-way compatible, so basing this port on Apache-licensed work and releasing it under GPL is permitted. See NOTICE for attribution details.
Model weights are downloaded at runtime from HuggingFace under their original Apache-2.0 license; they are NOT redistributed by this project, and consuming them in your own projects falls under their own Apache terms.
19 commits
Rust
100.0%