feat: render Markdown, mathematics and Mermaid diagrams server-side

This commit is contained in:
2026-09-27 19:15:50 +02:00
commit 35bc42623b
72 changed files with 8914 additions and 0 deletions
+124
View File
@@ -0,0 +1,124 @@
# API
Scriptorium is a library with one package and five exported functions. The
signatures below are the whole public surface; everything else lives under
`internal/` and reaches no caller. `go doc sourcedock.dev/petrbalvin/scriptorium`
is the authority on the signatures, and this file explains what each function
does, when it fails, and where the edges of the rendered surfaces are.
## Library
### `Render(source []byte) []byte`
Renders Markdown to HTML. The grammar is CommonMark 0.31.2 in full, with the
GitHub Flavored Markdown extensions on top: tables, strikethrough, task lists
and extended autolinks, plus footnotes and definition lists. Rendering never
fails and never returns an error: any input parses, and a construct the
grammar does not know stays in the output as literal text.
The extended autolinks supersede three CommonMark examples: a bare
`www.`/`http(s)://`/`ftp://` address or a bare email address becomes a link
where CommonMark without the extension would leave it as text. That is the
specification's own trade, and GitHub's reference implementation makes it
too.
```go
html := scriptorium.Render([]byte("# Title\n\nText with [a link](/uri).\n"))
```
The Markdown engine embeds no mathematics and no diagrams: a `$…$` run stays
what the CommonMark grammar says it is. A consumer that wants mathematics
inside its documents calls `RenderMath` on the math spans it recognises and
splices the results into the HTML itself.
### `RenderMath(source []byte) []byte`
Renders TeX mathematics to a complete MathML Core `<math>` element in the
inline form. Returns one line of markup with the namespace declared, ready to
embed in HTML. Never fails: see the degradation contract below.
### `RenderMathDisplay(source []byte) []byte`
The display form: the same engine, with `display="block"` on the element and
the movable limits of big operators set above and below instead of beside.
### `RenderDiagram(source []byte) ([]byte, error)`
Renders Mermaid source to a self-contained SVG document. The first line names
the diagram type; only two families are carried:
- `flowchart` and its historical `graph` alias, with any direction
(`TB`/`TD`, `BT`, `LR`, `RL`), every node shape, every edge kind, subgraphs
with their own direction, and the `classDef`, `class`, `style` and
`linkStyle` statements;
- `sequenceDiagram`, with participants and actors, all arrow kinds, notes,
activations in both spellings, the `alt`, `opt`, `loop`, `par`, `critical`
and `break` blocks, coloured `rect` regions, `autonumber` and dividers.
Returns an error when the type is outside the two families, when a direction
is unknown, or when a statement does not parse; every error names the diagram
type or the source line, so the author can find the sentence at fault. The
`click` statement is refused on purpose: the SVG is static and carries no
interactivity.
### `SupportedDiagram(kind string) bool`
Reports whether `RenderDiagram` carries the named Mermaid type. It answers
the question the error path would answer, before the call.
## What every function guarantees
- **Determinism.** The same input produces byte-identical output on every
call, in every process, on every machine. No layout decision reads a map
iteration order; the tests assert byte equality across repeated renders.
- **No sanitisation.** The input is trusted, and the output is written to
HTML, MathML or SVG verbatim. Whether the output may reach a given audience
is the consumer's policy: a consumer of untrusted input keeps its own
sanitiser and applies it to the rendered result.
- **Concurrency safety.** The engines hold no state between calls and share
nothing, so every function is safe for concurrent use.
- **No dependencies.** The standard library alone; there is no `go.sum`
because there is nothing to sum.
## The mathematics surface
The mathematics engine renders the standard TeX command surface that maps
into MathML Core, measured against the coverage list of KaTeX as the external
standard: the Greek letters including the variants, the relation and operator
tables including the negations and the colon relations, the arrows including
the extensible `\x` family, the big operators, the delimiters, the amsmath
environments, accents, the font and spacing commands, colours, and bounded
macros (`\newcommand`, `\renewcommand`, `\providecommand`, `\def`,
`\DeclareMathOperator`) with expansion limits.
Multi-character relations have no single Unicode character, so they render as
the linear two-character operators KaTeX's own MathML branch produces
(`\coloneqq` as `≔`, `\coloneq` as `:−`, `\Coloneqq` as `∷=`).
### Degradation, not failure
A construct outside the mappable surface degrades in place: the offending
command stays in the output as its verbatim source inside an `<merror>`
element, and the rest of the expression still renders. The author sees what
was not understood, in the document, at the place it happened.
The commands that degrade are the ones no MathML Core construct can carry:
positioning (`\raisebox`, `\vcenter`, `\smash`, the `\llap`/`\rlap`/
`\mathclap` family, `\kern`, `\mkern`, `\hskip`), boxes and overlays
(`\boxed`, `\colorbox`, `\fcolorbox`, `\sideset`), text annotations of the
page rather than the formula (`\tag`, `\label`, `\notag`, `\verb`,
`\char`, `\mathstrut`), and the environments outside the table family
(`\begin{CD}`, `\begin{tikzpicture}` and every other unknown name).
A macro whose expansion would not terminate is cut by the expansion limits
and degrades at the call site.
A degraded render is still a valid render: the output remains well-formed
MathML Core, and the tests assert well-formedness for every degraded corpus
case.
## Notes
The exported surface is documented in godoc form in the source, and `go doc ./...` is
the authority on signatures and types. This file explains what the surface is for and
how the parts fit together. It does not repeat the signatures, because a copy of a
signature is a future lie.