math-core
v0.8.2
Published
Convert LaTeX to MathML Core
Readme
math-core
A Node.js library for converting LaTeX math expressions to MathML Core.
Overview
math-core converts LaTeX mathematical expressions into MathML Core, a streamlined subset of MathML that is supported by all major web browsers. It lets you render mathematical content on the web without requiring JavaScript libraries or polyfills.
Features
- Convert LaTeX math expressions to MathML Core
- Support for both inline and display (block) math
- Define custom LaTeX macros for extended functionality
- Global and local counter for numbered equations
- Batch conversion which resolves forward references between snippets
- Pretty-printing option for readable MathML output
- Comprehensive error handling with descriptive error messages
Installation
npm i math-coreNote: This package is for Node.js (and compatible runtimes like Bun) only. It cannot be used in the browser, because it uses
readFileSyncfromnode:fsto load the underlying WASM module.
Quick Start
import { LatexToMathML } from "math-core";
const converter = new LatexToMathML({});
// Convert inline math
const inline = converter.convert_with_local_state("x^2 + y^2 = z^2", false);
console.log(inline);
// Output: <math><msup><mi>x</mi><mn>2</mn></msup><mo>+</mo><msup><mi>y</mi><mn>2</mn></msup><mo>=</mo><msup><mi>z</mi><mn>2</mn></msup></math>
// Convert display math
const display = converter.convert_with_local_state("\\frac{1}{2}", true);
console.log(display);
// Output: <math display="block"><mfrac><mn>1</mn><mn>2</mn></mfrac></math>Usage
Basic Usage
import { LatexToMathML } from "math-core";
const converter = new LatexToMathML({ prettyPrint: "always" });
try {
const mathml = converter.convert_with_local_state("\\sqrt{x^2 + 1}", false);
console.log(mathml);
} catch (e) {
console.error(`Conversion error: ${e.message}`);
}Custom LaTeX Macros
Define custom macros to extend or modify LaTeX command behavior:
const macros = new Map();
macros.set("d", "\\mathrm{d}"); // Differential d
macros.set("R", "\\mathbb{R}"); // Real numbers
macros.set("vec", "\\mathbf{#1}"); // Vector notation
const converter = new LatexToMathML({ macros });
const mathml = converter.convert_with_local_state("\\d x", false);Numbered Equations with Global State
For documents with multiple numbered equations:
const converter = new LatexToMathML({});
// First equation gets (1)
const eq1 = converter.convert_with_global_state(
"\\begin{align}E = mc^2\\end{align}",
true,
);
// Second equation gets (2)
const eq2 = converter.convert_with_global_state(
"\\begin{align}F = ma\\end{align}",
true,
);
// Reset counter when starting a new chapter/section
converter.reset_global_state();
// This equation gets (1) again
const eq3 = converter.convert_with_global_state(
"\\begin{align}p = mv\\end{align}",
true,
);Local Counter for Independent Numbering
Use local counters when equation numbers should restart within each conversion:
const converter = new LatexToMathML({});
// Each conversion has independent numbering
const doc1 = converter.convert_with_local_state(
"\\begin{align}a &= b\\\\c &= d\\end{align}",
true,
); // Contains (1) and (2)
const doc2 = converter.convert_with_local_state(
"\\begin{align}x &= y\\\\z &= w\\end{align}",
true,
); // Also contains (1) and (2)Converting a Whole Document at Once
convert_all converts a collection of snippets in one go, which is the only way to get
forward references right: a snippet may refer to an equation which is only defined in a later
snippet.
const converter = new LatexToMathML({});
const [reference, equation] = converter.convert_all([
{ latex: "\\eqref{eq:euler}", displayStyle: false },
{
latex: "\\begin{align}e^{i\\pi} + 1 = 0\\label{eq:euler}\\end{align}",
displayStyle: true,
},
]);
// `reference` refers to equation (1), even though it comes first.Equation numbering restarts with every call to convert_all; the state of
convert_with_global_state is neither used nor modified.
Error Handling
By default, conversion errors throw a LatexError with detailed diagnostics:
const converter = new LatexToMathML({});
try {
converter.convert_with_local_state("\\begin{foobar}", false);
} catch (e) {
console.log(e.message); // 'Unknown environment "foobar".'
console.log(e.report); // Formatted diagnostic with source spans
console.log(e.context); // The relevant LaTeX source
console.log(e.start); // Start offset of the error
console.log(e.end); // End offset of the error
}Set throwOnError: false to return an HTML error snippet instead of throwing:
const converter = new LatexToMathML({ throwOnError: false });
const result = converter.convert_with_local_state("\\invalid", false);
// Returns: <span class="math-core-error" title="..."><code>\invalid</code></span>API Reference
LatexToMathML
The main converter class.
Constructor:
new LatexToMathML(options: MathCoreOptions)Options:
| Option | Type | Default | Description |
|---|---|---|---|
| prettyPrint | "never" \| "always" \| "auto" | "never" | Whether to pretty-print the MathML output. "auto" pretty-prints block equations only. |
| macros | Map<string, string> | — | Custom LaTeX macros. |
| xmlNamespace | boolean | false | Include xmlns="http://www.w3.org/1998/Math/MathML" in the <math> tag. |
| throwOnError | boolean | true | Throw LatexError on conversion errors. If false, returns an HTML error snippet instead. |
| ignoreUnknownCommands | boolean | false | Render unknown commands as red text instead of erroring. |
| annotation | boolean | false | Include the original LaTeX as an annotation in the MathML output. |
| allowUnreliableRendering | boolean | false | Enable commands whose MathML Core output is rendered unreliably across browsers, such as \widetilde, \widecheck and \utilde. If false, these commands are treated as unknown. |
| globalGroup | boolean | false | Run the conversion in the global group, so that commands defined with \newcommand stay defined for subsequent calls to convert_with_global_state. If false, such definitions are local to the snippet which contains them. |
| unicodeSubstitution | "never" \| "conventional" | "conventional" | Whether commands like \coloneqq are rendered with a dedicated Unicode symbol (≔) or as a combination of more basic symbols (: and =). "conventional" substitutes wherever the LaTeX package unicode-math would, which is a good middle ground between semantics and faithfulness to the LaTeX output; "never" disables the substitutions. |
| maxExpansions | number | 1000 | How many custom commands may be expanded in one snippet before the conversion gives up. The limit exists because a macro may expand to itself, directly or indirectly. |
| idPrefix | string | "" | Added to the beginning of every id and anchor reference. Use this to avoid conflicts when embedding the output. |
Methods:
convert_with_global_state(latex: string, displaystyle: boolean): string— Convert LaTeX to MathML using global state.convert_with_local_state(latex: string, displaystyle: boolean): string— Convert LaTeX to MathML using local state.convert_all(snippets: MathSnippet[]): string[]— Convert all snippets of a document at once, resolving forward references between them. AMathSnippetis{ latex: string, displayStyle: boolean }. Uses a fresh state for the batch, independent of the global state.reset_global_state(): void— Reset the global state (e.g., set the equation counter to zero).
LatexError
Error thrown when LaTeX parsing or conversion fails.
Properties:
| Property | Type | Description |
|---|---|---|
| message | string | Description of the error. |
| report | string \| undefined | Formatted diagnostic report with source spans. |
| context | string \| undefined | The relevant LaTeX source. |
| start | number | Start offset of the error in the source. |
| end | number | End offset of the error in the source. |
| index | number \| undefined | Index of the failing snippet; only set by convert_all. |
Why MathML Core?
MathML Core is a carefully selected subset of MathML 4 that focuses on essential mathematical notation while ensuring consistent rendering across browsers. Unlike full MathML or JavaScript-based solutions:
- Native browser support: No JavaScript required
- Accessibility: Better screen reader support
- Performance: Faster rendering than JS solutions
- SEO-friendly: Search engines can index mathematical content
- Future-proof: Part of web standards with ongoing browser support
Browser Support
Firefox currently has the most complete support for MathML Core, with Chrome close behind. Safari has the least support and some rendering issues exist when using MathML Core, but it is improving with each release.
Contributing
Contributions are welcome! Please feel free to submit a Pull Request.
License
This project is licensed under the MIT License - see the LICENSE file for details.
