The core functionality of this site is to take Typst math syntax as input and render it to an SVG in real-time.
It does so using the typst compiler in WASM. Everything runs client-side.
It roughly follows the The Elm Architecture
┌─────────────────────────────────────────────────────────────┐
│ Browser │
│ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │
│ │ Elm App │─────▶│ MsgBridge │─────▶│ Rust/WASM │ │
│ │ (elm.js) │◀─────│ (index.html)│◀─────│ (pkg/) │ │
│ └─────────────┘ └─────────────┘ └─────────────┘ │
│ UI State Typst Compiler │
│ [compiled] [compiled] │
└─────────────────────────────────────────────────────────────┘
- Main.elm - The Elm Architecture application managing UI state
- Ports.elm - Port definitions for JavaScript interop
The Elm app handles:
- User input (math expression text field)
- UI rendering and styling
- Sending render requests via
renderMathport - Receiving SVG results via
mathRenderedport
The <script type="module"> block in index.html:
- Initializes the WASM module
- Starts the Elm app
- Subscribes to
app.ports.renderMathto receive expressions from Elm - Calls the WASM
renderMath()function - Injects the resulting SVG into the DOM
- Sends results back to Elm via
app.ports.mathRendered
- Cargo.toml - Dependencies on
typst,typst-svg,typst-assets,wasm-bindgen - src/lib.rs - WASM-compatible Typst compiler
The Rust code:
- Implements the
typst::Worldtrait (MathWorld) for WASM - Embeds fonts from
typst-assets - Wraps math expressions in minimal Typst document markup
- Compiles to a
PagedDocumentand renders to SVG viatypst-svg - Exports
renderMath(expression) -> Result<String, String>to JavaScript
elm.js - Generated by elm make src/Main.elm --output=public/elm.js:
- Elm runtime (curried functions, Virtual DOM, ports infrastructure)
- Compiled application code from
Main.elmandPorts.elm - All imported Elm packages (
elm/html,elm/browser,elm/json)
pkg/ - Generated by wasm-pack build --target web:
typst_math_svg.js- ES module wrapper for the WASMtypst_math_svg_bg.wasm- Compiled WebAssembly binary- Type definitions and glue code
- User provides data in the input field
- Elm's
onInputtriggersExpressionChangedmessage updatesends expression throughPorts.renderMath- JS bridge receives via
app.ports.renderMath.subscribe() - JS calls WASM
renderMath(expression) - Rust compiles Typst → SVG
- JS injects SVG into
#svg-outputdiv - JS sends SVG back via
app.ports.mathRendered.send() - Elm receives via
Ports.mathRenderedsubscription
Nix Flake (flake.nix) provides:
- Rust toolchain with
wasm32-unknown-unknowntarget - Elm compiler and tools
wasm-pack,wasm-bindgen-cli,binaryen- Bun and Node.js
Makefile targets:
make build- Build WASM and Elmmake dev- Build and serve on port 8000, usingbunxmake deploy- Pushpublic/togh-pagesbranchmake clean- Remove build artifacts