175 lines
7.1 KiB
Markdown
175 lines
7.1 KiB
Markdown
# 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
|
||
|
|
|
||
|
|
```go
|
||
|
|
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:
|
||
|
|
|
||
|
|
```html
|
||
|
|
<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:
|
||
|
|
|
||
|
|
```html
|
||
|
|
<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
|
||
|
|
|
||
|
|
```sh
|
||
|
|
go get sourcedock.dev/petrbalvin/scriptorium
|
||
|
|
```
|
||
|
|
|
||
|
|
Requires Go 1.27.1 or newer.
|
||
|
|
|
||
|
|
## Documentation
|
||
|
|
|
||
|
|
- [docs/API.md](docs/API.md): the surface in full, with the exact
|
||
|
|
boundary of the mathematics surface and every command that degrades
|
||
|
|
- [docs/ARCHITECTURE.md](docs/ARCHITECTURE.md): how the engines are built
|
||
|
|
- [docs/DEVELOPMENT.md](docs/DEVELOPMENT.md): the workflow and the recipes
|
||
|
|
- [CHANGELOG.md](CHANGELOG.md): the user-visible history
|
||
|
|
- [CONTRIBUTING.md](CONTRIBUTING.md): how to contribute
|
||
|
|
- [SECURITY.md](SECURITY.md): how to report a vulnerability
|
||
|
|
|
||
|
|
## Development
|
||
|
|
|
||
|
|
```sh
|
||
|
|
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](LICENSE).
|
||
|
|
|
||
|
|
Copyright © 2026 [Petr Balvín](https://petrbalvin.org)
|