Files
scriptorium/README.md

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

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