val_latex
LaTeX math typesetting in pure Dart.
A TeX parser and layout engine with OpenType math fonts.
Runs anywhere Dart runs: the VM, Flutter on every platform, servers and the web.
✨ Why val_latex?
- Pure Dart — no WebView, no JavaScript, no native code. The same engine runs in Flutter apps, on servers, in command-line tools and on the web.
- TeX's own rules — layout follows the TeXbook's Appendix G and the
OpenType
MATHtable of the font, so formulas are spaced the way LaTeX spaces them. - Broad coverage — 3,882 commands and 68 environments: amsmath, mathtools, the physics package, mhchem chemistry, siunitx units, diffcoeff, unicode-math names, text tables and lists.
- Never throws on bad input — errors become nodes and diagnostics, and input that is still arriving (an LLM streaming a reply) renders as far as it goes.
- Wraps to a width — breaks at relations and operators the way a person would, and re-breaks for a new width without laying out again.
- Right-to-left math — Arabic notation, Arabic-Indic and Persian digits, mirrored operators and brackets.
- Fast — about 4 µs to parse and 5 µs to lay out a typical formula on native code.
For Flutter, use
val_latex_flutter. It adds theMathandMathTextwidgets on top of this package.
🛠️ Quick start
dart pub add val_latex
import 'package:val_latex/val_latex.dart';
final result = parseLatex(r'x = \frac{-b \pm \sqrt{b^2-4ac}}{2a}');
final layout = layoutFormula(result);
print(layout.width); // in em
print(result.diagnostics); // problems, never an exception
layout.items is a render-agnostic display list — glyphs, rules, lines,
paths and text runs, positioned in em. Draw it on any canvas, or use
val_latex_flutter to draw it in Flutter.
📚 What it understands
| Area | Highlights |
|---|---|
| Core LaTeX and AMS | fractions, roots, scripts and limits, accents, delimiters at every size, every math alphabet, colours (148 named) |
| Environments | align, gather, multline, split, cases, every matrix, array with rules and \multicolumn, CD and tikzcd diagrams, bussproofs proof trees, equation numbers and tags |
| Packages | amsmath, mathtools, physics, mhchem (\ce, \pu), siunitx, diffcoeff, commath, nccmath, empheq, esint, tensor, stmaryrd, wasysym, unicode-math names |
| Text | \text with math inside, tabular and booktabs tables, itemize / enumerate lists, justified paragraphs with hyphenation |
| Macros | \def, \newcommand, \newenvironment, \NewDocumentCommand, \DeclareMathOperator, \DeclarePairedDelimiter, TeX conditionals and registers, shared across formulas with one newMacroTable() |
| Unicode input | α, ∑, x², a₁, combining accents, Arabic letters and digits |
Common package commands such as \abs, \dv, \sfrac and \celsius
work without loading anything. LatexCatalog.instance lists everything
val_latex understands.
📐 Layout and fonts
final layout = renderLatex(
r'\sum_{k=1}^{n} k = \frac{n(n+1)}{2}',
layoutOptions: const LayoutOptions(displayMode: true, maxWidth: 12),
);
- Latin Modern Math is bundled, with STIX Two Math as the fallback
for symbols it lacks. Any OpenType math font works:
MathFont.fromBytes(bytes). - Variable fonts at any axis values:
MathFont.fromBytes(bytes, variations: {'wght': 700}). - Line breaking —
LayoutOptions.maxWidth;prepareFormulalays a formula out once andlayoutAt(width)re-runs only the line breaker. - Right-to-left —
LayoutOptions(mathDirection: MathDirection.rtl). - In the background —
layoutInBackgroundlays a formula out in a worker isolate.
🔌 Streaming and text with formulas
parseLatex treats unfinished input (an open group, a half-typed command)
as incomplete rather than wrong, and keeps node ids stable as more input
arrives. MathScanner finds $…$, $$…$$, \(…\) and \[…\] formulas
(and bare \begin{align}, cases, pmatrix…) in Markdown or chat
text: prices stay text, code spans and blocks are skipped, and LLM output
with doubled backslashes (\\[…\\]) still works.
🗣️ Speech, selection and source
toSpeechreads a formula aloud for screen readers, in English and Arabic (Spanish, French, German, Portuguese, Bengali and Hindi as drafts), at three levels of detail.- Every node carries its source span;
hitTestLayoutandselectionUnitsmap taps and selections back to the source. toLatexwrites a formula back as normalised LaTeX.
⚡ Performance
Measured on a MacBook (just bench), per formula of the coverage corpus:
| Native | JavaScript | |
|---|---|---|
| Parse | 4.5 µs | 13 µs |
| Layout | 5.4 µs | 18 µs |
Re-parsing and laying out a 160-character formula on every streamed character takes about 19 µs per update.
Built by Val
val_latex is part of Val, the live visual layer
for AI agents.
💬 Community
Found a bug, or have an idea or feedback? Open an issue at github.com/useval/val_latex. If the package helps your project, consider giving it a like on pub.dev.
📄 License
BSD 3-Clause — see LICENSE. The bundled fonts keep their own
licences: Latin Modern Math (GUST Font License) and STIX Two Math (SIL
Open Font License), in fonts/. Work by others that val_latex contains
is credited in THIRD_PARTY_NOTICES.md.