Type-preserving serialization for Typst. Serialize Typst values and work with them in Rust WASM plugins using typed data structures.
Sertyp bridges Typst and Rust for WASM plugin development. Instead of passing untyped bytes or parsing strings, you get:
- Typst package: Serialize any Typst value (colors, lengths, content, etc.)
- Rust library: Deserialize into typed structs with utility methods
- Full roundtrips: Lossless — serialize and deserialize without degradation and reconstruct displayble values in typst.
Example: Pass rgb(255, 128, 0) to a plugin, work with it as
Color { space: RGB, components: [255, 128, 0] } in Rust, and return it back to
typst. The color can then still be used as a real typst color.
use wasm_minimal_protocol::*;
use sertyp::*;
#[cfg(target_arch = "wasm32")]
initiate_protocol!();
#[typst_func]
pub fn fibonacci<'a>(
n: Integer,
) -> sertyp::Result<'a, Integer> {
let n: i32 = match n.try_into() {
Ok(n) => n,
Err(_) => return Err("Invalid integer range".into()).into()
};
let (mut v0, mut v1) = (0, 1);
for _ in 0..n {
(v0, v1) = (v1, v0 + v1);
}
Ok(v1.into()).into()
}#import "@preview/sertyp:0.1.2";
#let fibonacci(n) = {
let plugin = plugin("<...>.wasm");
sertyp.call(plugin.fibonacci, n);
}
#assert(fibonacci(10) == 89)- Primitives: bool, int, float, decimal, string, bytes, none, auto
- Collections: array, dictionary
- Visual: color (RGB, CMYK, OkLab, HSL, etc.), stroke, gradient, alignment
- Numeric: length, angle, ratio, fraction, duration, relative
- Content: content, label, symbol, regex
- Advanced: function, module, type, arguments, styles, datetime, version
See typst/README.md for the full list and examples.
Typst's built-in WASM communication uses untyped bytes. You either:
- Parse
repr()strings (fragile, loses structure) - Use
cbor.encode()directly (loses type information) - Manually build serialization for each type
Sertyp provides:
- Type preservation: Lengths stay lengths, colors stay colors
- Structured access: Extract color components, decompose lengths, parse function names
- Zero boilerplate: Automatic serialization for all supported types
- Typst package: API reference, examples
- Rust library: Data structures, common patterns, plugin development
Issues and PRs welcome
- Security: Deserialization uses
eval()— only deserialize trusted data - Dynamic types:
contextand runtime-dependent elements cannot be fully serialized - Closures: Inline functions lose their captured state
See individual READMEs for details.