1. Preparation
The plugin support was a pleasant surprise, and I naturally wanted to use Zig ⚡ for this purpose.
Note: You will want to have both
zig
andtypst
installed if you are following along.(Precompiled binaries are available for both)
I’ve made a small module (called typ) that hopefully will be useful when writing plugins for Typst.
2. Dependency
The typ
module can be fetched and saved into your build.zig.zon
like this;
zig fetch --save git+https://github.com/peterhellberg/typ#main
Then you will add the dependency in build.zig
(under the call to b.addExecutable
)
build.zig
const std = @import("std");
pub fn build(b: *std.Build) void {
const target = b.resolveTargetQuery(.{
.cpu_arch = .wasm32,
.os_tag = .freestanding,
});
const exe = b.addExecutable(.{
.name = "hello",
.root_source_file = b.path("hello.zig"),
.strip = true,
.target = target,
.optimize = .ReleaseSmall,
});
const typ = b.dependency("typ", .{}).module("typ");
exe.root_module.addImport("typ", typ);
exe.entry = .disabled;
exe.rdynamic = true;
b.installArtifact(exe);
}
We are now ready to write a little Typst plugin in Zig.
3. Plugin
Now we should be able to use the typ
module to write a plugin.
hello.zig
const typ = @import("typ");
export fn hello() i32 {
const msg = "*Hello* from `hello.wasm` written in Zig!";
return typ.str(msg);
}
export fn echo(n: usize) i32 {
const data = typ.alloc(u8, n) catch
return typ.err("alloc failed");
defer typ.free(data);
typ.write(data.ptr);
return typ.ok(data);
}
Two functions are exported from this plugin;
- The
hello
function, which does not take any arguments and it can not fail. - The
echo
function on the other hand takes a single (Typstbytes
value) argument as input and echoes it back to the host, this function can fail on allocation (unlikely to happen for this example)
Build the zig-out/bin/hello.wasm
by calling zig build
and
then we can proceed with writing a hello.typ
using
the plugin we just compiled.
hello.typ
#set page(width: 35cm, height: 22cm)
#set text(font: "Inter", size: 40pt)
#let wasm = plugin("zig-out/bin/hello.wasm")
#let gy = gradient.linear(green, yellow)
== A WebAssembly plugin for Typst
#line(length: 100%, stroke: gy + 4pt)
#emph[
Typst is capable of interfacing
with plugins compiled to WebAssembly.
]
#line(length: 100%, stroke: gy + 4pt)
#eval(str(wasm.hello()), mode: "markup")
#let imgdata = read("wave.png", encoding: none)
#image.decode(wasm.echo(imgdata), width: 100pt)
Important: The Typst CLI is all you need! (No requirement on using the collaborative online editor)
Running typst compile hello.typ hello.svg
should, if all goes well, result
in a SVG file that looks something like this;