7.1 KiB
Scriptorium
Scriptorium renders scientific documents server-side in Go: Markdown into HTML, TeX mathematics into MathML Core, and Mermaid flowchart and sequence diagrams into SVG. Three engines, each written from scratch in this repository on the Go standard library alone, producing markup the reader's browser already knows how to draw: no JavaScript runs on the page that reads the result.
The strengths, each with its proof
Complete Markdown. The engine renders the full CommonMark 0.31.2 grammar plus the GitHub Flavored Markdown extensions. Measured against the official suites: 649 of 652 CommonMark examples pass, and the three exceptions are bare URLs and email addresses that the GFM extended autolinks turn into links, the same trade GitHub's own reference implementation makes; all 23 extension examples of the GFM specification pass byte for byte. Footnotes and definition lists come on top, rendered in the shapes GitHub and Pandoc established.
Complete mathematics. Every one of the 391 mathematical symbols in the KaTeX coverage table maps to MathML Core, verified by a systematic diff against KaTeX's own source tables, together with fractions, radicals, scripts with movable limits, stretchy delimiters, the amsmath environments, accents, styles, colours, extensible arrows and bounded macros. Where a KaTeX-compatible renderer needs JavaScript in the reader's browser, scriptorium emits MathML Core that Chromium, Firefox and Safari draw natively.
Deterministic. The same input produces byte-identical output on every call, in every process, on every machine. Diagram layouts break ties by the order of appearance and never by map iteration; tests assert byte equality across repeated renders in all three engines. Output is safe to cache, to diff and to sign.
Zero dependencies. The Go standard library alone, direct and
transitive. There is no go.sum, because there is nothing to sum: no
parser to supply-chain, no renderer to version-pin, no JavaScript bundle to
ship. Compiles wherever Go compiles.
Honest about its edges. A construct outside a mappable surface is never dropped and never guessed at: mathematics degrades in place, the unknown command standing as its verbatim source inside a marked element while the rest of the expression still renders, and a diagram type outside the two grammars is refused with an error naming the type and the source line.
Ready for servers. The engines hold no state between calls, so every function is safe for concurrent use. Rendering never fails on malformed input: Markdown and mathematics always produce output, and a diagram error tells the author the line to fix.
No sanitisation, by contract. The library renders trusted input and writes the output verbatim; whether the result 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.
A taste
package main
import (
"os"
"sourcedock.dev/petrbalvin/scriptorium"
)
func main() {
os.Stdout.Write(scriptorium.Render([]byte(
"# Measured, not guessed\n\nA *claim* with [a source](/uri) and some `code`.\n")))
os.Stdout.Write(scriptorium.RenderMathDisplay([]byte(`\sum_{i=1}^{n} \frac{1}{i^2}`)))
svg, err := scriptorium.RenderDiagram([]byte("flowchart LR\n A[Input] --> B{Model} --> C[Result]\n"))
if err != nil {
panic(err)
}
os.Stdout.Write(svg)
}
The first call prints exactly:
<h1>Measured, not guessed</h1>
<p>A <em>claim</em> with <a href="/uri">a source</a> and some <code>code</code>.</p>
The second prints a complete MathML Core element, the summation limits above and below the operator the way display mathematics sets them:
<math xmlns="http://www.w3.org/1998/Math/MathML" display="block"><munderover><mo movablelimits="true">∑</mo><mrow><mi>i</mi><mo>=</mo><mn>1</mn></mrow><mi>n</mi></munderover><mfrac><mn>1</mn><msup><mi>i</mi><mn>2</mn></msup></mfrac></math>
The third returns a self-contained SVG document of the flowchart, every element positioned by the library's own layout in whole pixels: ranks by longest path, ordering by barycentre sweeps.
The engines
Markdown
CommonMark 0.31.2 in full, with the GFM extensions: tables with per column
alignment, strikethrough, task lists and extended autolinks (www.,
http(s)://, ftp:// and bare email addresses, with the punctuation,
parenthesis and entity trimming rules of the specification), plus footnotes
and definition lists. Tables render in the exact shape of GitHub's
reference implementation.
Mathematics
The standard TeX command surface that maps to MathML Core, measured against
the KaTeX coverage list: the Greek letters including the variants, the
relation and operator tables including negations and the colon relations,
the arrows including the extensible \x family, the big operators, the
delimiters, and the letter-like symbols; the amsmath environments (matrix
and every delimited variant, cases, aligned, align, gather,
equation, array, substack); accents and over and under lines; the
font commands; spacing; colours; and bounded \newcommand, \def and
\DeclareMathOperator macros with expansion limits that turn a runaway
definition into an honest error rather than a hang.
Diagrams
The two Mermaid grammars the forge's documents actually use, a coverage
measured across every repository on the forge. Flowchart (including the
historical graph alias): every node shape, every edge kind with labels,
branching, subgraphs with their own direction, classes and styles.
SequenceDiagram: participants and actors, all arrow kinds, notes,
activations, the alt/opt/loop/par/critical/break blocks,
coloured regions, autonumber and dividers. Everything else is refused
with a clear error naming the type.
The API
| Function | Renders | Fails |
|---|---|---|
Render(source []byte) []byte |
Markdown to HTML | never |
RenderMath(source []byte) []byte |
TeX to inline MathML Core | never |
RenderMathDisplay(source []byte) []byte |
TeX to display MathML Core | never |
RenderDiagram(source []byte) ([]byte, error) |
Mermaid to SVG | on an unsupported type or a malformed statement |
SupportedDiagram(kind string) bool |
reports the Mermaid scope |
Install
go get sourcedock.dev/petrbalvin/scriptorium
Requires Go 1.27.1 or newer.
Documentation
- docs/API.md: the surface in full, with the exact boundary of the mathematics surface and every command that degrades
- docs/ARCHITECTURE.md: how the engines are built
- docs/DEVELOPMENT.md: the workflow and the recipes
- CHANGELOG.md: the user-visible history
- CONTRIBUTING.md: how to contribute
- SECURITY.md: how to report a vulnerability
Development
just build # compile every package
just test # the test suite with the coverage floor, under a memory fence
just fmt # format
just gates # the definition of done: build, format, vet, tests, race
Licence
MIT. See LICENSE.
Copyright © 2026 Petr Balvín