A Rust port of LaTeXML
latexml-oxide turns LaTeX sources into accessible web documents. The project is in active public beta as of July 2026, approaching mainline LaTeXML parity.
- LaTeXML is too slow for large-scale production use.
- Perl 5's ecosystem and tooling have aged out of the mainstream.
- LaTeXML is urgently needed for turning LaTeX sources into responsive, accessible web documents.
- Faithfully rewrite the LaTeXML code base as-is, staying as close as possible to the original Perl sources.
- Use idiomatic Rust when possible, especially when refactoring Perl idioms.
- Carefully address the newly required resource constraints.
- Offer a platform for iteratively increasing coverage to 90% of all of arXiv.
Try it first — no install: latexml.rs/editor — type LaTeX, see the converted document live.
Two binaries: latexml_oxide for documents, latexmlmath_oxide for a single formula.
# A document → HTML5, with MathML for the math (format inferred from the extension)
$ latexml_oxide paper.tex --destination=paper.html
# One formula → Presentation MathML (the default; --cmml for Content MathML)
$ latexmlmath_oxide 'a^2+b^2=c^2'--format takes html5, html, xhtml, xml, or epub — inferred from the
--destination extension when omitted. --help lists every option.
Working from a checkout instead of an installed binary? Prefix with
cargo run --bin latexml_oxide --.
The Releases page has prebuilt binaries for Linux, macOS and Windows, each a single, fully self-contained executable.
You still need a working TeX installation, TeX Live or MiKTeX, at runtime, to resolve TeX packages, classes and fonts. A few external tools are called as subprocesses, mainly to convert graphics files.
The steps below use a version variable — set it to the release you want:
$ VERSION=0.7.5
The .deb declares its runtime apt dependencies (libraries + TeX Live + the graphics tools), so it is the easiest way in.
Built on Ubuntu 22.04 (glibc 2.35), so it runs on Ubuntu 22.04+ and Debian 12+.
$ curl -LO https://github.com/dginev/latexml-oxide/releases/download/$VERSION/latexml-oxide_${VERSION}-1_amd64.deb
$ sudo apt install ./latexml-oxide_${VERSION}-1_amd64.deb
If you prefer the portable tarball, the binary is statically linked against libxml2/libxslt/libkpathsea, so you only need the external tools + TeX Live:
$ curl -LO https://github.com/dginev/latexml-oxide/releases/download/$VERSION/latexml-oxide-$VERSION-x86_64-unknown-linux-gnu.tar.gz
$ tar xzf latexml-oxide-$VERSION-x86_64-unknown-linux-gnu.tar.gz
$ sudo cp latexml-oxide-$VERSION-x86_64-unknown-linux-gnu/latexml_oxide /usr/local/bin/
$ sudo apt install imagemagick mupdf-tools poppler-utils ghostscript dvipng dvisvgm \
texlive-latex-base texlive-latex-extra texlive-science
On 64-bit ARM (AWS Graviton, Ampere, Raspberry Pi OS 64-bit) swap amd64 → arm64 in the .deb name
and x86_64-unknown-linux-gnu → aarch64-unknown-linux-gnu in the tarball name; everything else is identical.
Recommended — Homebrew. Installs the right binary for your Mac, and pulls the graphics tools it needs:
$ brew install dginev/tap/latexml-oxide
You still need a TeX distribution (brew info latexml-oxide repeats this):
$ brew install texlive # Homebrew's full TeX Live (~5 GB)
# …or MacTeX / BasicTeX — https://tug.org/mactex/ (put /Library/TeX/texbin on PATH)
Alternative — download the tarball directly. Pick the one matching your Mac (uname -m prints arm64 or x86_64):
# Apple Silicon
$ curl -LO https://github.com/dginev/latexml-oxide/releases/download/$VERSION/latexml-oxide-$VERSION-aarch64-apple-darwin.tar.gz
$ tar xzf latexml-oxide-$VERSION-aarch64-apple-darwin.tar.gz
$ sudo cp latexml-oxide-$VERSION-aarch64-apple-darwin/latexml_oxide /usr/local/bin/
# Intel (built with a macOS 10.13 deployment target, so it runs on older Intel Macs)
$ curl -LO https://github.com/dginev/latexml-oxide/releases/download/$VERSION/latexml-oxide-$VERSION-x86_64-apple-darwin.tar.gz
$ tar xzf latexml-oxide-$VERSION-x86_64-apple-darwin.tar.gz
$ sudo cp latexml-oxide-$VERSION-x86_64-apple-darwin/latexml_oxide /usr/local/bin/
$ brew install imagemagick mupdf-tools poppler ghostscript dvisvgm
$ brew install texlive # or install MacTeX / BasicTeX (provides dvipng)
The prebuilt binary links libkpathsea statically, so it finds your TeX files in-process on either
Homebrew's texlive or MacTeX/BasicTeX — just make sure the TeX binaries are on PATH
(/Library/TeX/texbin for MacTeX).
Gatekeeper / "unidentified developer" (tarball, browser downloads). The
curl+tar xzfinstall above is warning-free: terminal downloads and command-linetardon't set macOS'scom.apple.quarantineflag. But if you download the tarball in a browser and unpack it by double-clicking in Finder, macOS may refuse to run the binary as "from an unidentified developer" — the binaries are ad-hoc signed, not Apple-notarized. Clear it once withxattr -d com.apple.quarantine /usr/local/bin/latexml_oxide, or right-click the binary in Finder → Open. The Homebrew install above avoids this entirely, sincebrewstrips the flag.
(From 0.7.4.) A single self-contained latexml_oxide.exe (fully static: no VC++ redistributable needed),
shipped in a .zip. In PowerShell run:
> $VERSION = "0.7.5"
> curl.exe -LO "https://github.com/dginev/latexml-oxide/releases/download/$VERSION/latexml-oxide-$VERSION-x86_64-pc-windows-msvc.zip"
> Expand-Archive "latexml-oxide-$VERSION-x86_64-pc-windows-msvc.zip" -DestinationPath .
> .\latexml-oxide-$VERSION-x86_64-pc-windows-msvc\latexml_oxide.exe --version
Put latexml_oxide.exe somewhere on your PATH.
A TeX distribution (TeX Live for Windows or MiKTeX) must be on PATH for host TeX resolution;
the binary auto-selects the fast in-process backend on TeX Live, and falls back to subprocess kpsewhich on MiKTeX.
ImageMagick (magick), Ghostscript (gswin64c / MiKTeX mgs) and MuPDF (mutool)
are optional, for figure conversion.
A batteries-included image (latexml_oxide + a reproducible TeX Live + the graphics tools)
is published to the GitHub Container Registry for both amd64 and arm64.
No local TeX Live needed — bind-mount your document tree and convert:
$ docker run --rm -v "$PWD:/work" ghcr.io/dginev/latexml-oxide:$VERSION paper.tex
:latest tracks the most recent release. The container builds its own binary against the image's TeX Live,
so the embedded kernel dumps match the bundled texmf tree exactly.
The binary is self-contained (libxml2/libxslt/kpathsea are linked in), but at runtime it shells out
to external tools for graphics conversion, and reads TeX assets from your TeX Live tree.
None are bundled — install the ones your documents need.
When a required tool is missing, the conversion log names it and the package to install.
The .deb declares all of these, so apt install ./latexml-oxide_*.deb pulls them automatically.
Tool (command) |
apt package | Homebrew | Used for |
|---|---|---|---|
convert |
imagemagick |
imagemagick |
raster image conversion |
mutool |
mupdf-tools |
mupdf-tools |
primary PDF graphics (fast) |
pdftocairo |
poppler-utils |
poppler |
vector-SVG from PDF |
gs, ps2pdf |
ghostscript |
ghostscript |
PDF/PostScript conversion |
dvipng |
dvipng |
TeX Live | raster LaTeX-image output |
dvisvgm |
dvisvgm |
TeX Live | vector-SVG LaTeX-image output |
kpsewhich, latex, pdflatex, tftopl |
texlive-latex-base (+-extra, -science) |
texlive / MacTeX |
TeX package/class/font resolution |
$ cargo +nightly install latexml
This builds the latexml_oxide CLI from source, so it needs a recent Rust
nightly, the system build dependencies listed under
Build from source below, and a TeX distribution at runtime
like every other install route.
It starts slower than the prebuilt binaries — until you generate the dumps
once. The releases embed precompiled TeX kernel dumps for a 5-year TeX Live
window; those are generated at release time and are far too large to ship in a
crate, so a fresh cargo install reconstructs the kernel state at every startup.
Conversions themselves run at the same speed — only startup pays.
To get the fast startup, generate the dumps for your TeX Live once. --init
writes to ./resources/dumps/, and the binary looks in
<its own dir>/../resources/dumps, so run it from the parent of bin/:
$ cd ~/.cargo && latexml_oxide --init=plain.tex && latexml_oxide --init=latex.ltx
That writes ~/.cargo/resources/dumps/{plain,latex}.YYYY.dump.txt, which the
binary at ~/.cargo/bin/latexml_oxide then finds on every run. Prefer somewhere
else? Point LATEXML_DUMP_DIR at it instead. Re-run after a TeX Live upgrade —
dumps are per-TL-year (this is the same "build the formats once" step TeX itself
does with fmtutil).
As a library:
[dependencies]
latexml = "0.7"let xml = latexml::api::convert_to_xml(tex)?; // TeX → LaTeXML XML
let html = latexml::api::convert_to_html(tex)?; // TeX → HTML5 + Presentation MathMLFor finer control (preloads, search paths, --whatsin, split, encoding, …), drive
latexml::converter::Converter and latexml::post directly.
Requires a recent Rust nightly to compile.
You need the same non-Perl OS dependencies as LaTeXML, adapted for Rust.
Example for Ubuntu:
$ sudo apt install libxml2-dev libxslt1-dev texlive-latex-base imagemagick ghostscript libkpathsea-dev libkpathsea6 mold \
texlive texlive-latex-extra texlive-science \
texlive-bibtex-extra texlive-publishers poppler-utils
Example for macOS (Apple Silicon / arm64; the full test suite runs on macOS CI):
$ brew install libxml2 libxslt texlive
$ export PKG_CONFIG_PATH="$(brew --prefix libxml2)/lib/pkgconfig:$(brew --prefix libxslt)/lib/pkgconfig"
$ cargo build --bin latexml_oxide
libxml2 and libxslt are keg-only in Homebrew, hence the PKG_CONFIG_PATH export
(put it in your shell profile for regular work).
Homebrew's texlive ships libkpathsea + kpathsea.pc, so the build links it in-process.
Using MacTeX/BasicTeX instead? That works too. MacTeX ships no libkpathsea at all
(no header, no dylib, no .pc), so the build prints a one-time kpathsea_sys notice and
falls back to resolving TeX files through your distribution's own kpsewhich executable
(/Library/TeX/texbin must be on PATH — the MacTeX installer sets this up).
Same conversions, slightly slower cold file lookups. You still need brew install libxml2 libxslt either way.
mutool (MuPDF) and pdftocairo (poppler-utils) are optional but recommended:
they convert PDF figures faster than ImageMagick. latexml-oxide tries the
available delegates fastest-first and falls back through the chain, so a figure is
never lost when a tool is missing.
Cargo.toml defines named profiles for day-to-day development (test), the
RAM-bounded CI runner (ci), local perf measurement (release), and the shipped
distribution binaries (maxperf / maxperf-cortex). Use the default profile
(cargo build / cargo test, no flag) for everyday work; see
CLAUDE.md for the full profile table.
To enable linting quality control via rustfmt and clippy, you can activate the included hooks via:
$ rustup component add rustfmt --toolchain nightly
$ rustup component add clippy --toolchain nightly
$ git config --local core.hooksPath .githooks/This workspace is heavy for rust-analyzer (large proc-macro bodies); the checked-in .vscode/settings.json
ships a stability profile that keeps the IDE responsive.
Terminal cargo build / cargo test are unaffected.
To generate the project documentation locally, run:
$ cargo doc --workspace --no-deps --openIMPORTANT: There is a compile-time plugin that collects the files in the test suite.
So when adding a new test [name].tex and [name].xml pair of files, you may need to
manually execute cargo clean to rediscover the entry. On a related note, running
cargo clean every few days of active development frees up a lot of disk space
taken by stale builds.
Cargo features, and what the shipped binary turns on. The releases are built with
--no-default-features --features runtime-bindings — that drops test-utils but
deliberately keeps the Rhai runtime bindings below.
| Feature | Crate | In releases? | What it does |
|---|---|---|---|
runtime-bindings |
latexml → latexml_contrib |
yes (also a default) | Embeds the Rhai interpreter so you can add or override TeX package/class bindings at runtime — no Rust toolchain, no recompile. See below. |
kpathsea |
latexml_core |
yes (a default) | Resolves .sty/.cls/.tfm from your TeX tree via libkpathsea in-process, falling back to the kpsewhich subprocess when the library is absent (e.g. MacTeX). |
test-utils |
latexml |
no (dropped) | Test-suite discovery helpers; drops phf + glob and 4 transitive crates from the distribution build. |
token-locators |
latexml (+4 crates) |
no | Widens Token from 8 to 12 bytes so each token carries its exact source offset through expansion into digestion — the precision build behind --source-map. Off everywhere else so normal conversions don't pay. |
cortex |
latexml |
no (worker image only) | The cortex_worker binary for the arXiv-scale fleet. |
jemalloc, dhat-heap |
latexml |
no | Allocator swap / heap profiling, for performance work. |
runtime-bindings is on in every released binary, so this works out of the box.
Drop a file named <pkg>.sty.rhai (or <class>.cls.rhai) next to your
document, and \usepackage{<pkg>} finds and loads it:
// mypkg.sty.rhai — note: backslashes are doubled in Rhai strings
DefMacro("\\greet{}", |name| "Hello, " + name + "!");
DefConstructor("\\mytext{}", "<ltx:text class=\"rhai\">#1</ltx:text>");Discovery rides the ordinary search paths (the document's own directory, plus any
--path DIR), and the .rhai tier is consulted first — so a
article.cls.rhai deliberately overrides the built-in article binding, which
is how you patch a class without touching the binary.
- Worked example:
docs/examples/sample.sty.rhai— a tour of the whole surface (macros, constructors in template and imperative form, environments, options, counters, keyvals, math, rewrites). It is load-tested in CI, so it never rots. - Interface reference:
latexml_contrib/src/script_bindings/API.md— every function you can call, grouped by handle (global,document,Node,whatsit(),LookupDefinition(),Command). It is generated from the live engine and re-checked on every test run, so it cannot drift from what is actually registered. It also renders intocargo docaslatexml_contrib::script_bindings::interface, socargo doc --workspace --open(and docs.rs) show it beside the Rust API. - Semantics and design notes:
docs/parity/script_bindings_plan.md— what the calls mean, the namespace rules, and what a failing binding degrades to.
Because a .rhai beside a document is executed by a plain conversion, treat it as
you would any code shipped with an untrusted source tree — see
docs/release/SAFETY.md §H. Builds that want the
capability gone entirely can use --no-default-features without
--features runtime-bindings.
latexml-oxide's source code and original resources are dedicated to the public domain under CC0 1.0 Universal.
The release binary also embeds and links third-party material that keeps its own license: most notably compiled TeX format dumps derived from TeX Live (the LaTeX kernel, LPPL 1.3c; plain TeX, Knuth), the libxml2/libxslt libraries (MIT), the libmarpa parser engine (MIT, with LGPL parts) and libkpathsea (LGPL-2.1).
These are attributed in THIRD-PARTY-NOTICES, published as an asset on every release
and bundled inside every download — the tarballs, the Windows .zip, the .debs, and the container
images (under /usr/local/share/doc/latexml-oxide).
The public-domain dedication above applies to the latexml-oxide source and original resources,
not to that embedded or linked third-party material.
Full breakdown: docs/release/LICENSE_INVENTORY.md.