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.

Pub Version Pub Points BSD-3-Clause license

🦋 Flutter widgets · 📦 pub.dev


✨ 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 MATH table 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 the Math and MathText widgets 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; prepareFormula lays a formula out once and layoutAt(width) re-runs only the line breaker.
  • Right-to-left — LayoutOptions(mathDirection: MathDirection.rtl).
  • In the background — layoutInBackground lays 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

  • toSpeech reads 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; hitTestLayout and selectionUnits map taps and selections back to the source.
  • toLatex writes 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.

Libraries

advanced
Engine-level access to val_latex: the lexer, the macro expander, the parser class and the symbol tables.
val_latex
TeX/LaTeX math typesetting in pure Dart.