mathra

Docs

mathra turns an equation (LaTeX, MathML or AsciiMath) into an SVG that carries its own speech and braille. The JavaScript, Python, Rust and terminal packages run the same core, so an equation gives the same SVG, byte for byte, on all four. New here? Watch the 60-second demo.

Install

WhereInstallRegistry
JavaScript and TypeScript: Node, bundlers, browsersnpm install @mathlabs/mathranpm
Python 3.9+: wheels for Linux, macOS and Windowspip install mathraPyPI
Terminalcargo install mathra-clicrates.io
Rustcargo add mathracrates.io · docs.rs

Types are included for TypeScript and Python. Nothing else is needed: the font (STIX Two Math) is embedded.

First equation

Node

import { texToSvg, render } from "@mathlabs/mathra";   // or require("@mathlabs/mathra")

const svg = texToSvg(String.raw`x = \frac{-b \pm \sqrt{b^2 - 4ac}}{2a}`);
const { speech } = render(String.raw`\frac{3}{4}`, { locale: "fr" });   // "trois quarts"

Browsers and bundlers (Vite, webpack, esbuild)

Await init() once. It fetches the WebAssembly next to the module.

import { init, texToSvg } from "@mathlabs/mathra";

await init();
document.querySelector("#eq").innerHTML = texToSvg(String.raw`e^{i\pi} + 1 = 0`);

Without a bundler, from a CDN:

<script type="module">
  import { init, texToSvg } from "https://cdn.jsdelivr.net/npm/@mathlabs/mathra/web/index.js";
  await init();
  document.body.innerHTML = texToSvg("3,14", { locale: "pt-BR" });
</script>

Python

import mathra

svg = mathra.tex_to_svg(r"\sin x = 0.5")
r = mathra.render(r"\frac{3}{4}", locale="fr")   # a dict
r["speech"]                                       # 'trois quarts'

Terminal

$ mathra 'f(1, 5) = \sin x - 2.5' -o function.svg
$ mathra '3,14' --locale pt-BR --explain

Rust

let r = mathra::convert("3.14", &mathra::Options::default())?;
println!("{}", r.svg);
println!("{}", r.speech);

Options

The same five options everywhere, under each platform's spelling.

OptionJavaScriptPythonTerminalRust (Options)
Languagelocale: "pt-BR"locale="pt-BR"-l, --locale pt-BRlocale: Locale::PtBr
Inline styledisplay: falsedisplay=False--inlinedisplay: false
Refuse broken LaTeXstrict: truestrict=True--strictstrict: true
Input notationinput: "mathml"input="mathml"-f, --from mathmlinput: Input::MathMl
Braille codebraille: "cmu"braille="cmu"--braille-code cmubraille: Some(BrailleCode::Cmu)
The SVG writes itselfanimate: trueanimate=True--animateanimate: true
Each element numbered by its stepsteps: truesteps=True--stepssteps: true
  • Language: en by default. The 16 codes are under Languages. A region is accepted (es-MX, zh-CN). In JavaScript, a plain string as the second argument is read as the locale: texToSvg(tex, "fr").
  • Inline style: display style (as in $$…$$) is the default; inline is the style of $…$, with smaller limits and fractions.
  • Strict: off by default, so broken input is drawn and reported as warnings. On, it is refused like TeX does: an exception in JavaScript, ValueError in Python, an Err in Rust, exit code 1 in the terminal.
  • Input: latex by default, mathml or asciimath. MathML passed as LaTeX (starting with <math) is recognized on its own.
  • Braille code: by default the language's: CMU in pt-BR and es, Nemeth elsewhere.

What comes back

texToSvg / tex_to_svg return the SVG as a string. render (and convert in Rust) return everything at once:

FieldWhat it is
svgThe standalone SVG (see Showing it in a page).
speechThe phrase a screen reader says, in the language of the locale. The same phrase is in the SVG's <title>.
textLinear text close to UnicodeMath: (n ¦ k), x^2. It is the SVG's selectable text layer.
braille, braille_codeThe equation in Unicode braille cells, and the code used: "cmu" or "nemeth".
treeThe tree of meaning: what each piece is ({"type": "number", "value": "3.14"}). Also in the SVG's <metadata>.
appliedThe numbered rules that changed something in this equation, as {rule, description}, in the order they acted.
warningsWhat tolerant mode fixed (E-REC-*), as {rule, description}. Always empty in strict mode.

Also: locales() lists the languages as {code, name}, and version (JavaScript), core_version (Python) or mathra::VERSION (Rust) give the version of the core inside the package. Python also has tex_to_tree, the tree as a JSON string.

Showing it in a page

Inline, in the HTML

Put the SVG string in the page (innerHTML, a template, server-side rendering). This is the way to get everything:

  • Color: glyphs are fill="currentColor", so the equation takes the color of the text around it, and dark mode just works.
  • Size and baseline: width and height are in em, so the equation follows the font size, and vertical-align lines its baseline up with the text.
  • Selection: a transparent text layer holds the linear text, so selecting, copying and find-in-page work.
  • Many on one page: ids are unique to each equation, so they do not clash.

As an image

<img src="equation.svg" alt="…"> works too, but an image cannot see the page: it draws in a fixed dark ink (#111), and nothing in it is selectable. Give the <img> the speech as its alt.

What else travels inside

role="img" and aria-labelledby point to the <title> with the speech. <metadata> keeps the source with its notation, the tree, the braille, the font name and any warnings, so the equation can be recovered from the file. The same input always gives the same bytes. The full contract is spec/svg.md.

Broken input

LaTeX copied from OCR, chat apps, HTML editors or unescaped JSON is often broken. By default mathra draws what it can and returns a warning for each fix. With strict, it refuses instead. A valid equation gives the same result in both modes.

$ mathra '\vertA\vert = C_{16,2}' --text
mathra: warning E-REC-1: column 1: unsupported command: \vertA; read as \vert A
|A| = C_(16, 2)
RuleProblemWhat mathra does
E-REC-1Unknown commandReads \vertA as \vert A, \nlesssim as \not\lesssim; otherwise writes the name in red
E-REC-2Leftover JSON escape (\n, ²)A space, or the character
E-REC-3Double script (x_1_2)The second script goes on an empty base
E-REC-4Unbalanced bracesA stray } is ignored; an open { is closed at the end
E-REC-5Unpaired \left, \right, \begin, \endClosed with an empty delimiter, or ignored
E-REC-6Unknown environmentRead as a centered table
E-REC-7Missing argument (\frac{1})Left empty
E-REC-8Character the font lacksA box (□)
E-REC-9Others: stray #, invalid color, control characters, \divchave with a zero divisor…Drawn as written, or ignored
E-REC-10MathML: unknown element or entity, wrong number of childrenChildren read as a row; unknown entity dropped

Warnings appear in warnings, on the terminal's stderr, in --explain and in the SVG's <metadata>. Broken XML in MathML, and inputs past the size limits (64 levels of nesting, 2,000 macro expansions), are errors in both modes. Details: spec/tolerance.md.

The command line

mathra <equation> [options]
echo '<equation>' | mathra [options]
OptionWhat it does
-o, --output <file>Write the SVG to a file instead of printing it
-l, --locale <code>The language (en by default)
-f, --from <format>latex (default), mathml or asciimath
--inlineInline style, as in $…$
--strictRefuse invalid LaTeX, like TeX
--explainShow the text, the speech, the braille, the tree and the rules applied
--tree, --speech, --text, --braillePrint only that output
--braille-code <c>cmu or nemeth
--animateThe SVG writes itself in the browser, in the order the layout set it (CSS in the SVG)
--stepsEach element numbered by that order (data-step), with no animation
--batchOne equation per input line; with -o <folder> (it must exist), writes 1.svg, 2.svg…; prints ok, warning⇥… or error⇥… per line
-V, --version, -h, --helpVersion and help
$ mathra --batch -o out/ < equations.txt
ok
warning	E-REC-1 column 1: unsupported command: \vertA; read as \vert A

Languages

The locale decides how the equation is read, written and spoken. In pt-BR 3,14 is one number with a decimal comma, \sin is written sen and ]a, b[ is an open interval. In fr \frac{3}{4} is spoken "trois quarts".

en (default), pt-BR, es, fr, de, it, ru, zh, ja, ko, hi, ar, id, tr, nl, pl.

English and pt-BR are reviewed. The speech of the other languages still needs a native speaker's review; corrections are welcome as pull requests to the language's JSON file (spec/locales.md).

Braille

Every equation also comes out in braille, as Unicode braille cells: in the CMU (Código Matemático Unificado) for Portuguese and Spanish, and in Nemeth for the other languages, or in either by choice.

$ mathra '\frac{3}{4} + \sqrt{x}' --locale pt-BR --braille     # ⠼⠒⠙⠖⠫⠱⠭
$ mathra 'x^2 + 1' --braille                                     # ⠭⠘⠆⠐⠬⠂ (Nemeth)

Both codes follow their official documents and were compared with MathCAT, but they have not been reviewed by a braille transcriber yet. How far to trust them.

What it converts

Fractions, roots, scripts and limits, stretchy delimiters, matrices and array, cases, alignments, accents and braces, \text, alphabets, every amssymb symbol and negation, \cancel, \boxed, colors, siunitx, mhchem, physics, braket, tensor, mathtools, \newcommand and friends, commutative diagrams (amscd, tikz-cd, xymatrix), structural chemistry (chemfig structures, reaction schemes and mechanism arrows, read aloud by their molecular formulas), and the computed \divchave and \contaarmada. Every construction, drawn and in braille, is in the reference; the full list is in the README.

Out of scope, because mathra is for equations: TikZ and TeX programming (\ifx, \catcode). Not yet: automatic line breaking, MathML and PNG output, content MathML.

Specs and source

Every decision is a numbered rule with a test: rules, the SVG, input notations, tolerance, languages, braille, chemistry, the boundary. The ideas behind them are in the philosophy. The code is on GitHub, under MIT or Apache-2.0.