Math

There are three ways to put an equation on a page, and all three carry LaTeX. The math block is a centred equation on a line of its own. The inline patterns $…$ and $$…$$ put one inside a sentence. All three end at the same node — a Content::Math holding the LaTeX — and each output target typesets it from there.

The typesetting is RaTeX, a pure-Rust pipeline, and every equation comes out as one self-contained SVG: the glyphs are <path> outlines embedded in it, not characters in a webfont. No script and no font download stands between a reader and an equation.

§ 1A page with equations

Save this as quadratic.wcl:

quadratic.wclwcl
import <wdoc.wcl>

site notes {
  title = "Notes"
  toc { chapter "The quadratic formula" { page = quadratic } }
}

page quadratic {
  title = "The quadratic formula"

  h1 "The quadratic formula"

  p "A quadratic $ax^2 + bx + c = 0$ has two real roots whenever its discriminant $b^2 - 4ac$ is positive. Both of them come out of one expression:"

  math <<'TEX'
x = \frac{-b \pm \sqrt{b^2 - 4ac}}{2a}
TEX

  p "The plus-or-minus is the whole story: $\\pm$ is one symbol standing for two roots."
}
text
$ wcl wdoc build quadratic.wcl --out _site
wrote 1 page

That page carries four equations of two kinds. Three are inline — two in the first sentence, one in the last: they sit on the text baseline and take the surrounding font size. The fourth is a math block: centred, on a line of its own, and typeset in display style, which is why its fraction and its root come out full size. Here is the same content, rendered by this book:

A quadratic has two real roots whenever its discriminant is positive. Both of them come out of one expression:

The plus-or-minus is the whole story: is one symbol standing for two roots.

§ 2The math block

math takes its LaTeX as a label — the value between the kind and the braces — so a one-line equation needs no body at all:

wcl
math "E = mc^2"

Three fields, one of them the label:

FieldTypeWhat it does
sourceutf8, @inline(0)The LaTeX. Written as the block's first label, not as a named field.
ididentifier?An HTML id on the wrapping div, so a link may point at the equation.
classlist<utf8>?Extra classes on that same wrapper — the hook for resizing or recolouring it.
wcl
math "e^{i\\pi} + 1 = 0" {
  id    = euler
  class = ["accent-eq"]
}

The block renders as a <div class="wdoc-math …"> holding one self-contained <svg>. The stdlib gives that wrapper its centring, its vertical margin and a horizontal scrollbar for an equation wider than the column. Anything you pass in class is added to that same element, after wdoc-math — see Themes and styling for declaring one.

§ 2.1Quote it or heredoc it

LaTeX is mostly backslashes, and a quoted WCL string reads escapes. In a quoted label every backslash has to be doubled:

wcl
math "\\frac{-b \\pm \\sqrt{b^2 - 4ac}}{2a}"

A raw heredoc takes the same LaTeX verbatim, which is what you want for anything longer than a fraction:

wcl
math <<'TEX'
\begin{bmatrix}
  a & b \\
  c & d
\end{bmatrix}
TEX

The quotes on the heredoc tag are not decoration

<<'TEX' is the raw form and is verbatim. Plain <<TEX is the interpolating form: it reads escapes and ${…} slots, so \frac fails the parse outright with invalid escape '\f' — before any of this chapter's machinery runs. Always quote the tag for LaTeX. Values and primitives covers the two heredoc forms.

A math block is always display style. There is no field that makes one flow with prose — for that, reach for an inline pattern instead.

§ 3Inline math

Two inline patterns turn LaTeX inside ordinary prose into an equation. They work anywhere prose is read: a p body, a heading, a list item, a callout body, a table cell. Text and formatting covers the pattern system they belong to.

wcl
p "Text style $\\sum_{i=1}^{n} i$ against display style $$\\sum_{i=1}^{n} i$$ — the same LaTeX, twice."

Text style against display style — the same LaTeX, twice.

The difference is the math style, not the placement. Both stay in the line and both align to the text baseline. Display style is the one that puts the summation's limits above and below the sign and sets a fraction at full size; text style tucks the limits alongside and shrinks the fraction so the line keeps its spacing. Reach for $…$ by default, and for $$…$$ when the cramped form is the harder one to read. $$…$$ is declared first, so it wins at a $$ opener.

§ 3.1The delimiter rule

No inline equation may cross a line: both patterns stop at a newline, so a $…$ you wrapped across two lines stays prose. The $…$ pattern carries a second rule on top of that, because a single $ is also a currency sign — its body may not have whitespace touching either delimiter:

WrittenResult
$x^2$An equation.
$ x^2 $Prose. A space touches both delimiters.
Pay $10 or $20Prose. The candidate body 10 or ends in a space.
Costs $5 eachProse. There is no closing delimiter.
A range of $10-$20An equation reading 10-, and the trap below.

$$…$$ has no such guard — nothing in prose opens with two dollar signs by accident, so $$ x^2 $$ is an equation, spaces and all.

There is no backslash escape for a dollar sign

\$10-\$20 does not help: the backslash is prose, the pattern still matches, and you get an error marker where the price was. Two things do work. Put the text in a code span — $10-$20 between backticks is verbatim and is never re-tokenized — or keep a space after the dollar sign. Read the rule as a guard against the common case, not as a promise.

§ 4Which of the three to reach for

The three forms differ in what they are for, and that is easier to see side by side than in three separate places:

FormMath styleWhere it landsTakes id / classReach for it when
math blockDisplayIts own centred line, in a divYesThe equation is the point of the paragraph
$$…$$DisplayIn the sentence, on the baselineNoA sum or an integral has to stay in the sentence
$…$TextIn the sentence, on the baselineNoA symbol or a small expression is part of the prose

Nothing stops a math block from being one symbol, or a $$…$$ from being a three-line derivation. Both read badly. The rule of thumb is the one above: if the sentence needs the equation to finish, keep it inline; if the equation needs the reader to stop, give it a block.

§ 5The LaTeX subset

RaTeX implements KaTeX's function set, so the subset is KaTeX's rather than a full TeX distribution's. Every family below was built as an SVG before this table was written:

FamilyExamples
Fractions and roots\frac, \dfrac, \tfrac, \binom, \sqrt, \sqrt[3]{x}
Scripts and limitsx^2, a_{i,j}, \sum_{i=1}^{n}, \prod, \int_0^1, \lim_{x \to \infty}
Greek and symbols\alpha, \Omega, \infty, \emptyset, \nabla, \partial, \forall, \exists
Relations and operators\le, \ne, \approx, \equiv, \in, \subset, \times, \pm, \otimes
Arrows\to, \rightarrow, \Rightarrow, \leftrightarrow, \mapsto
Named functions\sin, \cos, \log, \ln, \exp, \max, \operatorname{argmin}
Accents and decorations\vec, \hat, \bar, \tilde, \overline, \overbrace, \underbrace
Delimiters\left( … \right), \left\{ … \right\}, \langle, \lVert
Alphabets\mathbb, \mathcal, \mathfrak, \mathbf, \mathrm, \mathsf, \mathtt
Text and colour\text, \textbf, \textit, \textcolor{red}{x}, \color
Environmentsmatrix, pmatrix, bmatrix, vmatrix, smallmatrix, array, cases, aligned, align, gather, split, equation
Spacing and layout\quad, \qquad, \,, \hspace, \substack, \stackrel, \phantom, \displaystyle

Two things follow from that, and both are worth knowing before you paste a page of a paper into a block.

A command KaTeX does not define is an error, however ordinary it looks in a .tex file. \includegraphics{a.png} fails with Undefined control sequence: \includegraphics, and so does anything else a LaTeX package would have supplied. What you do get beyond the core is what KaTeX itself carries: \ce{H2O} for chemistry, and \href{…}{…} for a link.

Each equation is a document of its own. \def and \newcommand work, and their effect ends at the closing delimiter. Define a macro in one math block and the next one reports Undefined control sequence. There is no page-wide preamble, so a macro you want twice has to be written twice. Nor can you lift the LaTeX into a WCL binding and point a block at it. source is a label, and a label is a literal: math tex with a let tex beside it reads tex as an identifier, and the build stops with block 'math' lowered to a malformed content node, naming latex as the field that is not a utf8.

§ 6When the LaTeX is wrong

A malformed equation never fails the build. It renders as an error marker in place: the source text, in monospace, in the wdoc-math-error colour, with the parser's message on the element's title. Three real ones, copied out of a build:

WrittenMessage on the marker
\frac{ParseError at position 6: Unexpected end of input in a macro argument
\notacommand{x}ParseError at position 0: Undefined control sequence: \notacommand
\begin{unknownenv} x \end{unknownenv}ParseError: No such environment: unknownenv

The same marker replaces an inline equation, so a $10-$20 that matched by accident shows up as red monospace in the middle of the sentence rather than as a build failure. Layout is guarded the same way: a panic inside the layout engine on pathological input becomes the marker math layout panicked, not a crashed build.

Markdown does not typeset, so it does not check

The error marker comes from typesetting. The Markdown target passes the LaTeX through as text instead, so a broken equation reaches the reader untouched and unremarked. Build the HTML at least once before you trust a page of equations.

§ 7Colour and size

An equation follows the colour of the text around it. RaTeX paints its glyphs black; the renderer rewrites that default black to currentColor on the way out, so a theme, a class, or the light/dark toggle carries the equation with it. A colour you asked for explicitly is not black and is therefore left alone — \textcolor{red}{x} stays red under every theme.

Size works the same way. The <svg> is sized in em from the layout's own metrics, not in pixels, so an equation scales with the font of whatever contains it. To make one bigger, change the font size of the wrapper through class rather than reaching for a LaTeX sizing command:

wcl
# A `class` is a top-level declaration — beside the page, not inside it.
# `block kind 'class' is not allowed inside 'page'` is what you get otherwise.
class "big-eq" { css = "font-size: 1.6em;" }

page notes {
  math "\\int_0^1 x^2 \\, dx" {
    class = ["big-eq"]
  }
}

§ 8How an equation reaches each target

math is not a native block. It has a real lower, written in WCL, and all that lowering does is name the equation:

wcl
lower = fn(m: Math) -> list<Content> [
  Content::Math {
    latex:   m.source,
    display: true,
    id:      m.id,
    class:   m.class,
  },
]

That matters more than it looks. Content::Math is a node of the semantic content IR — the closed union every backend matches exhaustively — and its payload is fixed: some LaTeX, a style, an optional id and some classes. The typesetting is still Rust's work, because a layout engine is not expressible in WCL. But each target does that work from the same payload rather than from its own reading of the block, and adding a target means adding one arm. See Writing your own blocks for the lower-or-@native rule this is the good half of, and Output targets for the targets themselves.

TargetWhat it does with Content::Math
HTMLTypesets it: a <div class="wdoc-math"> around a self-contained <svg>, id applied.
PDFTypesets the same SVG and places it as a block. The id is dropped — there is nothing to link to.
MarkdownEmits the LaTeX between $$ fences, untypeset. Inline math keeps its own $…$ or $$…$$.

Inline math travels the same road one level down. $…$ and $$…$$ produce an InlineSpan::Math, HTML and PDF hand it to the same typesetter that serves the block, and Markdown writes the delimiters back out. One LaTeX-to-SVG leaf serves both forms and both typesetting targets.

§ 9Where to go next