[ Content & formats ]
@sdxc/math
TeX math to MathML Core, with a markdown walk visitor and a component
- Installs with
- @sdxc/markdown@sdxc/result
- Depends on
remix- Source
- packages/math
TeX math to MathML Core, with a markdown walk visitor and a component.
Browsers render MathML Core natively, so a formula needs no stylesheet, no web font and no client JavaScript. This package reads a practical subset of TeX into a JSON tree of MathML elements and writes it as markup or as component elements.
Installation
npm add @sdxc/math
The root entry depends only on @sdxc/result.
@sdxc/math/markdown walks documents from
@sdxc/markdown, and @sdxc/math/ui renders
with remix/component; both install alongside this package.
Usage
Convert TeX
import { toMathML } from "@sdxc/math";
import { isFailure } from "@sdxc/result";
let result = toMathML("\\frac{a}{b}", { display: true });
if (isFailure(result)) throw result.error;
result.data;
// '<math xmlns="http://www.w3.org/1998/Math/MathML" display="block"><semantics>
// <mfrac><mi>a</mi><mi>b</mi></mfrac>
// <annotation encoding="application/x-tex">\frac{a}{b}</annotation>
// </semantics></math>'
The formula travels with its TeX as an annotation, which screen readers and copy-paste can read back.
Render Math In Markdown
import { Markdown } from "@sdxc/markdown";
import { toHTML } from "@sdxc/markdown/html";
import { math, renderMath } from "@sdxc/math/markdown";
import { isFailure } from "@sdxc/result";
let parsed = Markdown.parse("Euler: $`e^{i\\pi} + 1 = 0`$\n\n```math\n\\sum_{i=1}^n i\n```\n");
if (isFailure(parsed)) throw parsed.error;
let walked = Markdown.walk(parsed.data.document, math);
if (isFailure(walked)) throw walked.error;
let html = toHTML(walked.data, { tags: { math: renderMath } });
The visitor reads the two forms GitHub renders: a fenced block whose language is math, and
inline code wrapped in dollar signs, $`x^2`$. Each becomes a math tag node carrying
{ tex, display }.
Render With Remix
import { toRemix } from "@sdxc/markdown/remix";
import { MathFormula, MathTag } from "@sdxc/math/ui";
<p>
The area is <MathFormula tex="\pi r^2" />.
</p>;
<article>{toRemix(walked.data, { components: { math: MathTag } })}</article>;
API
toMathML(tex: string, options?: MathOptions): Result<string, MathError>
Converts TeX to a MathML string, every value escaped, ready for HTML or XHTML. Every element has an explicit closing tag, so HTML and XML parsers read it the same way.
parseMath(tex: string, options?: MathOptions): Result<MathElement, MathError>
Reads TeX into the tree toMathML serializes, rooted at the math element. The tree is plain
JSON, so it caches and travels in a payload.
let tree = parseMath("x^2");
// { type: "element", name: "math", attributes: { xmlns: "…", display: "inline" }, children: [ … ] }
MathOptions
display: a block formula. Setsdisplay="block"and moves the limits of\sum,\prod,\limand similar operators under and over them. Defaults tofalse.
MathError
The first construct the parser refused. reason says what went wrong; index (0-based),
line and column (1-based) locate it in the TeX, and the message ends with line:column.
parseMath("a + \\foo"); // failure: "Unknown command \foo at 1:5"
Types
MathNode, MathElement, MathText
A node is an element, { type: "element", name, attributes, children } with string
attribute values, or text, { type: "text", value } holding the unescaped characters.
@sdxc/math/markdown
math
The Markdown.walk visitor. A formula that does not convert fails the walk, and the failure's
cause is the MathError, so a broken formula stops a build. Every handler is synchronous,
so the walk returns a Result, never a promise.
createMathVisitor(options?: MathVisitorOptions)
Builds the visitor with options. invalid: "keep" leaves a formula that does not convert as
the code block or inline code it was written as; invalid: "fail" is the default.
renderMath
A tag renderer for toHTML's tags option. A tag whose TeX does not convert renders as the
escaped TeX inside <code>.
@sdxc/math/ui
MathFormula
A remix/component component taking tex and display. It builds the MathML as elements and
renders the TeX in a <code> when it does not convert. Render it on the server: the browser's
HTML parser places the elements in the MathML namespace, which a client-side render does not.
MathTag
MathFormula shaped for toRemix's components option, which hands every component its
children alongside the tag's attributes.
Supported TeX
Numbers, letters (each an identifier), operators
+ - = < > , ; : ! ? / * . ', grouping{}Scripts
^and_, alone or together, with TeX's one-token rule:x^23raises only the 2\frac,\dfrac,\tfrac,\binom,\sqrt{x},\sqrt[n]{x}Greek letters
\alpha…\omega, the\varforms, and uppercase\Gamma…\OmegaOperators, relations and arrows such as
\cdot \times \pm \leq \neq \approx \in,\subseteq \cup \to \Rightarrow \iff \forall \exists \ldots \cdots, and\infty \partial \nablaBig operators
\sum \prod \coprod \bigcup \bigcapwith limits in display mode, integrals\int \iint \iiint \ointwith limits beside them, and\limits/\nolimitsFunctions
\sin \cos \tan \log \ln \exp \det \max \min \sup \inf \limand more, plus\operatorname{…}\left … \rightwith( ) [ ] | / .and\{ \} \langle \rangle \lvert \rvert \| \lfloor \rfloor \lceil \rceil\text{…},\mathrm,\mathbf,\mathit,\mathbbAccents
\hat \bar \vec \tilde \dot \ddotSpacing
\, \: \; \! \quad \qquad \enspace,~and\Environments
matrix,pmatrix,bmatrix,Bmatrix,vmatrix,Vmatrixandcases, with&between cells and\\between rows; both are errors outside an environment
Any other command is a MathError naming it.
Pattern: Keep Building When A Formula Breaks
A preview pane renders what it can while an author types. Walk with invalid: "keep" so a
half-written formula stays code instead of failing the page.
import { Markdown } from "@sdxc/markdown";
import { toHTML } from "@sdxc/markdown/html";
import { createMathVisitor, renderMath } from "@sdxc/math/markdown";
import { isFailure } from "@sdxc/result";
let preview = createMathVisitor({ invalid: "keep" });
function render(source: string): string {
let parsed = Markdown.parse(source);
if (isFailure(parsed)) return "";
let walked = Markdown.walk(parsed.data.document, preview);
if (isFailure(walked)) return "";
return toHTML(walked.data, { tags: { math: renderMath } });
}