A LaTeX typesetting engine, rewritten from the ground up in modern C++.
mLaTeX (Modernized LaTeX) reads LaTeX as people already write it and turns it into PDF with a single program. There is no TeX distribution behind it, no format files, no package tree on disk and no auxiliary files between runs. The lexer, macro expander, parser, line breaker, page builder, font shaper and PDF writer are all part of one engine, written in C++26.
The language stays the same. A document written for pdfLaTeX, its preamble included, is meant to compile unchanged. What changes is the machinery underneath: the work LaTeX does with thousands of lines of TeX macros is done once, in compiled code.
- Reads existing LaTeX. 509 packages and 14 document classes, each
checked by a sample document of its own in
tests/packages/, plus a glossary of about 630 more commands from CTAN packages that have no folder of their own. An unknown package produces a warning, not a failed build. - Fast. On a release build, a one-page article takes a few milliseconds,
and the 11-page sample paper in
build/main.mtex, two passes included, takes under 30 ms. - Self-contained. Packages and English hyphenation are compiled into the
binary. Fonts (New Computer Modern, STIX Two, Fira Math, TeX Gyre and
others) and hyphenation patterns for 79 languages and variants ship in
assets/. - The parts people use. Mathematics; tables; floats; cross-references resolved by a second pass in memory; tables of contents and indexes; hyperref links and bookmarks; TikZ, pgfplots, tikz-cd, xy-pic and forest pictures; beamer slides with overlays and themes; BibTeX bibliographies in the standard, natbib and biblatex styles; right-to-left and CJK text; PNG, JPEG, WebP, EPS and SVG pictures; PDF/A.
- Embeddable. The same engine is available as a C++ library, a C library that any language with a foreign-function interface can bind, and a WebAssembly module. Programs can supply values, commands they implement, and files from memory, then get the PDF back as bytes -- a whole document, or a snippet: one formula, table or picture on a page cropped to it, to show inside an app or a web page.
- Safe by design. A document cannot run shell commands (
\write18does not exist), cannot write files, and never loads.styor.clscode from disk. With--offlineit cannot reach the network either. See SECURITY.md.
mLaTeX is pre-release software (version 0.1.0). The aot target, which
writes the PDF directly, is complete. The command line's jit target is
accepted but not yet implemented; it is reserved in
src/jit. WebAssembly is not a target of the
command line but a build of the engine itself, for browsers and Node; see
Embedding the engine. See
CHANGELOG.md for what has been done and what is
intentionally left out.
Prebuilt packages have not been published yet. When they are, each release will include:
| Platform | Package | Installs to |
|---|---|---|
| Windows | .msi installer, .zip |
C:\Program Files\mLaTeX |
| macOS | .pkg installer |
/Applications/mLaTeX |
| macOS | .dmg disk image, .tar.gz, .zip |
wherever the mLaTeX folder is dragged or unpacked |
| Linux | .deb, .rpm |
/opt/mlatex |
| Linux | .tar.gz, .zip |
wherever it is unpacked |
| Web | -wasm.zip: latex.js, latex.wasm, latex.data |
beside the page or script that loads it |
Each package is named for its platform and architecture, for example
mlatex-0.1.0-windows-x64.msi or mlatex_0.1.0_arm64.deb. The program is
bin/mlatex inside the installed folder. Add that bin folder to your PATH
to run it from anywhere. It is named mlatex so that it never collides with
the latex a TeX distribution installs.
- CMake 3.25 or later and Ninja.
- A C++26 compiler with
#embed: Clang 19 or later, or GCC 15 or later. On Windows, use theclang-clthat Visual Studio installs underVC/Tools/Llvm. The build selects it automatically if your IDE profile names MSVC (cl) or MinGW. - vcpkg with HarfBuzz, libpng, libjpeg-turbo, libwebp and zlib installed for your target triplet.
- gperf.
- Optional: libcurl on Linux and macOS for
\includegraphicsofhttps://sources (Windows uses WinHTTP); Doxygen for the API reference; WiX (version 3, or thewix.NET tool, version 4 or later) for an.msi. WiX 7 needs its EULA accepted once per machine,wix eula accept wix7, and theWixToolset.UI.wixextandWixToolset.Util.wixextextensions (wix extension add --global ...); see WiX's maintenance fee.
vcpkg install harfbuzz libpng libjpeg-turbo libwebp zlibcmake -B cmake-build-release -G Ninja -DCMAKE_BUILD_TYPE=Release -DCMAKE_TOOLCHAIN_FILE=<vcpkg>/scripts/buildsystems/vcpkg.cmakecmake --build cmake-build-releasectest --test-dir cmake-build-release --output-on-failureOn Windows, the debug, release and wasm presets in
CMakePresets.json set all of this up (cmake --preset release). If you
leave out the toolchain file, the build looks for vcpkg in VCPKG_ROOT and
then in C:/vcpkg.
The build produces three targets: latex (the command-line program, built as mlatex), core
(the engine as a static library, which every other target links) and ffi
(the C library, latex_ffi). Every source file has a matching test under
tests/.
From a Release build tree:
cpack --config cmake-build-release/CPackConfig.cmakeThis puts the packages listed under Installation for the
platform and architecture of the build into the release/ folder at the
top of the project. To package another
architecture, run a separate build for it, for example clang-cl targeting
ARM64 with the arm64-windows triplet. On macOS,
-DCMAKE_OSX_ARCHITECTURES="arm64;x86_64" with universal libraries
produces one disk image for both.
mlatex paperThis compiles paper.mtex or, failing that, paper.tex, and writes
paper.pdf beside it. Options accept one or two dashes and are written as
words separated by dashes. TeX's own spellings are accepted too, so scripts
written for pdfLaTeX keep working.
mlatex --interaction=batch-mode --halt-on-error papermlatex --output-directory=out --job-name=final paper.texmlatex -I styles -I figures papermlatex --watch --open papermlatex --time paper| Option | Meaning |
|---|---|
-o, --output-directory=DIR |
Write the PDF into DIR |
-j, --job-name=NAME |
Name the PDF NAME.pdf |
-n, --draft-mode |
Check the document and write no PDF |
--halt-on-error |
Write no PDF if the document has an error |
-I, --include-directory=DIR |
Also look in DIR for inputs, packages, pictures and bibliographies |
--offline |
Make no network requests; for documents you did not write |
-w, --watch |
Recompile every time the document is saved |
-O, --open |
Open the PDF in the system viewer when it is written |
-i, --interaction=MODE |
batch-mode prints only errors |
--file-line-error |
Print errors as file:line:column: message |
-T, --time |
Print how long the run took |
--time-statistics |
Print every stage of the engine and its timing |
mlatex --help lists every option. The exit status is 0 for a clean
document, 1 for a document with errors (a PDF is still written unless
--halt-on-error is given), and 2 for a mistake on the command line.
\bibliography{refs} and biblatex's \addbibresource{refs.bib} read a
.bib file directly, so there is no separate BibTeX step. A .bib file can
also be imported with \input{refs.bib}. Running mlatex refs.bib
typesets every entry in the file, which is useful for checking a
bibliography before citing it.
A package is a folder containing main.mtex, written in the same language
as a document. See src/modules/packages.md.
The command line is a thin layer over latex::Session, and every binding
exposes that same interface under the same names.
C++
latex::Session session(latex::locate(program));
session.set("customer", "Acme Ltd.");
session.define({.name = "balance", .arity = 1, .handler = [&](auto arguments) {
return ledger.balance(arguments[0]);
}});
session.provide("chart.png", png);
if (!session.typeset(document)) std::cerr << session.error;
send(session.pdf);A whole document goes to typeset. A snippet -- a formula, a table, a
picture, with no preamble -- goes to snippet, which sets it as a
standalone document with amsmath and amssymb loaded and crops the page to
it, for an app or a web page to show among its own content:
session.snippet(R"(\[ \int_0^1 x^2\,dx = \frac{1}{3} \])");
show(session.pdf); // a page exactly the formula's size\documentclass{standalone} does the same for a document written in full.
C, or any language that can call C (Python's ctypes, C#'s P/Invoke,
Rust, Go). The library exports a single function, engine(), declared in
include/protocols/ffi.hpp:
const Latex* table = engine();
Session* session = table->compose(NULL);
table->set(session, "customer", "Acme Ltd.");
const unsigned char* pdf;
size_t size;
if (!table->typeset(session, source, strlen(source), &pdf, &size)) {
fprintf(stderr, "%s", table->error(session));
}
const char* formula = "\\[ e^{i\\pi} + 1 = 0 \\]";
table->snippet(session, formula, strlen(formula), &pdf, &size);
table->dispose(session);The library ships in every package: latex_ffi.dll on Windows,
liblatex_ffi.dylib on macOS and liblatex_ffi.so on Linux, with
include/latex/ffi.hpp.
JavaScript, through the WebAssembly module: latex.js, latex.wasm and
latex.data (the fonts), from the mlatex-<version>-wasm.zip of a release
or built with the wasm preset (see include/render/wasm.hpp). It runs in a
web page, a worker or Node:
const engine = await Latex();
const session = new engine.Session();
session.set("customer", "Acme Ltd.");
if (!session.typeset(source)) console.error(session.error());
const pdf = session.pdf();
session.snippet("\\[ e^{i\\pi} + 1 = 0 \\]");
const formula = session.pdf(); // a Uint8Array: hand it to PDF.js, or offer it as a download
session.delete();The API reference is generated from the sources with Doxygen:
cmake --build cmake-build-release --target docsIt is written to cmake-build-release/docs/html. The reference and
src/modules/packages.md cover the engine's internals and
package authoring. CHANGELOG.md describes every feature in
detail.
| Path | Contents |
|---|---|
include/, src/ |
The engine: syntax (lexer, expander, parser), render (layout, typography, graphics, PDF), memory (arenas, tables), protocols (HTTP, the C library) |
src/modules/ |
Packages and classes, one folder each, compiled into the binary |
src/jit/ |
Reserved for the just-in-time target |
assets/ |
Fonts, hyphenation patterns and the command glossary |
build/ |
The sample paper, main.mtex |
tests/ |
One test per source file, at the same relative path, and in tests/packages/ one sample document per package |
Contributions are welcome. Please open an issue to report a bug or to discuss a feature before starting on a large change. Pull requests should:
- build without warnings and pass
ctest; - add a test in the file that mirrors the source file they change;
- follow the existing style: every identifier is one real word, with no underscores and no camelCase, and every declaration has a Doxygen comment;
- add an entry to CHANGELOG.md.
Everyone taking part is expected to follow the Code of Conduct.
The project's Discord server is the place to ask questions, discuss contributions and follow its direction: https://discord.gg/UUbPg5YtJF.
mLaTeX is maintained by Andres Hernandez (@ApaxPhoenix). For questions about the project or its direction, write to andromedeyz@hotmail.com. To report a security issue, follow SECURITY.md instead of opening a public issue.
mLaTeX is free software, licensed under the GNU Affero General Public License v3.0.
mLaTeX builds on HarfBuzz for text shaping and font subsetting, FreeType, libpng, libjpeg-turbo, libwebp and zlib. Its fonts include New Computer Modern, STIX Two, Fira Math, Asana Math and the TeX Gyre math fonts, and its hyphenation patterns come from the hyph-utf8 project. Above all, it owes its language to Donald Knuth's TeX and Leslie Lamport's LaTeX.