Kurvst curve and path API
Kurvst is GammaLoop’s Typst-facing WebAssembly layer for Bezier and Hobby paths, arc-length trimming, sampled path patterns, parallel curves, and CeTZ conversion. This page embeds the complete maintained Kurvst manual used by the drawing pipeline.
Kurvst transforms curve geometry. Linnet and Linnest own graph topology and layout; GammaLoop’s drawing templates combine those results with Kurvst paths and styles.
kurvst is a Typst package backed by kurvst.wasm, a small Kurbo-based plugin. Its public API is path-based: construct a path dictionary, pass that path into transforms, and draw the returned path.
It exposes Bezier utilities that are awkward or unavailable in native Typst:
- constructing cubic and Hobby paths,
- trimming paths by arc length,
- generating sampled path patterns,
- fitting Kurbo offset/parallel paths,
- converting returned path geometry to CeTZ drawing commands.
Choose an import path
Kurvst is currently a bundled source package, not a Typst Universe package. A Clinnet run writes it below build/templates/crates/kurvst/typst/; a custom template in build/templates/ can therefore import it with:
#import "crates/kurvst/typst/src/lib.typ" as kurvstRepository examples live one directory beside src/ and instead use #import "../src/lib.typ" as kurvst. Keep kurvst.wasm, typst.toml, and the src/ tree together when copying the package to another Typst project.
Path geometry points are two-item tuples:
#let p = (1.2, -0.4)Cubic segments use start, control-start, control-end, and end:
#let segment = (
start: (0, 0),
control-start: (1, 0.5),
control-end: (2, -0.5),
end: (3, 0),
)Use from-cubic(segment) once to turn that cubic dictionary into a path dictionary. Returned paths contain a curve-command wire path in path. Rust converts that wire path to Kurbo’s BezPath internally, and every path-level Kurvst function accepts either a returned dictionary or the raw wire path.
#let path = kurvst.from-cubic(segment)
#let trimmed = kurvst.trim(path, start-outset: 0.2, end-outset: 0.1)
#let shifted = kurvst.parallel(trimmed, distance: 0.15)Path Wire Format
A Kurvst wire path is a dictionary with one elements array. The elements mirror Typst’s native curve commands, but points are plain numeric tuples:
#let path = (
elements: (
(kind: "move", start: (0, 0)),
(kind: "line", end: (0.6, 0.3)),
(kind: "quad", control: (1.0, 1.0), end: (1.4, 0.3)),
(
kind: "cubic",
control-start: (1.8, -0.4),
control-end: (2.4, 1.0),
end: (3.0, 0),
),
(kind: "close", mode: "straight"),
),
)The supported element kinds are:
move: starts a subpath atstart.line: adds a straight segment ending atend.quad: adds a quadratic segment usingcontrolandend.cubic: adds a cubic segment usingcontrol-start,control-end, andend.close: closes the current subpath.modeis optional and defaults to"straight"when emitted by Kurvst.
Prefer the path fragment helpers for new code. line, quad, and cubic include their start points, so fragments can be built independently:
#let custom = kurvst.path(
kurvst.line((0, 0), (0.6, 0.3)),
kurvst.quad((0.6, 0.3), (1.0, 1.0), (1.4, 0.3)),
kurvst.cubic((1.4, 0.3), (1.8, -0.4), (2.4, 1.0), (3.0, 0)),
)path and append flatten fragments. If an appended fragment starts where the current path ends, its leading move is skipped; if it starts elsewhere, the move begins a new subpath. The edited path can go straight back into Kurvst transforms:
#let base = kurvst.from-cubic(segment)
#let extended = kurvst.append(base, kurvst.line(segment.end, (3.4, 0.2)))
#let trimmed = kurvst.trim(extended, start-outset: 0.15)
#let shifted = kurvst.parallel(trimmed, distance: 0.1)
#kurvst.to-native(shifted, unit: 36pt, stroke: rgb("#1b7f4c") + 0.7pt)Use elements when you need the raw command stream, points for the visited endpoints, segments for cubic segment dictionaries, to-native for native Typst drawing, and to-cetz-data or to-cetz for CeTZ.
A typical chain keeps the returned dictionaries intact until the final drawing step:
#let base = kurvst.hobby-through(demo-start, demo-through, demo-end)
#let trimmed = kurvst.trim(base, start-outset: 0.2, end-outset: 0.1)
#let shifted = kurvst.parallel(trimmed, distance: 0.15)
#cetz.canvas({
kurvst.to-cetz(base, stroke: rgb("#bbbbbb") + 0.4pt)
kurvst.to-cetz(trimmed, stroke: rgb("#d72638") + 0.6pt)
kurvst.to-cetz(shifted, stroke: rgb("#1b7f4c") + 0.6pt)
})Paths And Trimming
cubic constructs a path from one cubic Bezier. trim trims by curve distance from the start and/or end and returns another path dictionary.
#let path = kurvst.from-cubic(segment)
#let trimmed = kurvst.trim(path, start-outset: 0.15, end-outset: 0.1)hobby-through(start, through, end) builds a smooth open curve through three points; segments returns the two cubic halves split at through. hobby-spline(points) does the same for any open point sequence with at least two points. Both return path dictionaries and can be passed directly to path-level helpers.
#let spline = kurvst.hobby-spline((
(0.0, 0.0),
(0.9, 0.8),
(1.8, -0.3),
(2.8, 0.4),
))
#let shifted = kurvst.parallel(spline, distance: 0.14)
#let wiggle = kurvst.pattern(
spline,
pattern: kurvst.wave(samples-per-period: 24),
amplitude: 0.1,
wavelength: 0.55,
)
#cetz.canvas({
kurvst.to-cetz(spline, stroke: rgb("#bbbbbb") + 0.45pt)
kurvst.to-cetz(shifted, stroke: rgb("#1b7f4c") + 0.6pt)
kurvst.to-cetz(wiggle, stroke: rgb("#355c9a") + 0.55pt)
})split-through combines Hobby spline construction with endpoint trimming and returns one path part between each consecutive point. Consumers such as Linnest can wrap it for graph-edge geometry.
Path Patterns
pattern generates a one-dimensional pattern along any path dictionary. Built-ins are regular Typst dictionaries:
#let wave = kurvst.wave()
#let zigzag = kurvst.zigzag()
#let coil = kurvst.coil(longitudinal-scale: 1.6)String names "wave", "zigzag", and "coil" are accepted for convenience, but they are resolved in Typst before calling wasm.
Custom patterns use the same object shape:
#let hook = (
kind: "points",
name: "hook",
interpolation: "linear",
points: (
(at: 0, x: 0, y: 0),
(at: 0.25, x: 0.15, y: 1),
(at: 0.5, x: 0, y: 0),
(at: 0.75, x: -0.15, y: -1),
(at: 1, x: 0, y: 0),
),
)at is the phase position inside one wavelength, from 0 to 1. The x coordinate offsets along the path tangent and y offsets along the path normal; both are scaled by amplitude. If at is omitted, points are spaced evenly. Use interpolation: "linear" for corners and "smooth" for a spline through sampled points.
endpoint-ramp: true tapers amplitude at anchored endpoints. This is useful for coils, whose longitudinal offset would otherwise put the first and last visible points inside the turn near nodes.
#let base = kurvst.from-cubic(segment)
#let path = kurvst.pattern(
base,
pattern: hook,
amplitude: 0.15,
wavelength: 0.7,
)
#native-scene({
native-cubics(kurvst.segments(base), stroke: rgb("#c8c8c8") + 0.45pt)
native-polyline(kurvst.points(path), stroke: rgb("#1b7f4c") + 0.8pt)
})Parallel Paths
parallel uses Kurbo’s offset curve fitter to produce a path at a fixed normal distance from the source path. Positive distances follow the left normal of the path direction; negative distances follow the right normal. start-outset and end-outset trim the fitted path by arc length after the offset is computed.
#let base = kurvst.from-cubic(segment)
#let left = kurvst.parallel(base, distance: 0.18, start-outset: 0.35, end-outset: 0.35)
#let right = kurvst.parallel(base, distance: -0.18)
#native-scene({
native-cubics(kurvst.segments(base), stroke: rgb("#c8c8c8") + 0.45pt)
native-cubics(kurvst.segments(left), stroke: rgb("#1b7f4c") + 0.8pt)
native-cubics(kurvst.segments(right), stroke: rgb("#355c9a") + 0.8pt)
})Path Layers
layer is a convenience wrapper for drawing derived visible paths. It combines side-aware offsetting, endpoint outsets, and centered shortening into one path-in/path-out operation. length is a fixed visible arc length, ratio is a fraction of the input path length, and resolve-length decides how to combine them. The default "min" keeps whichever limit is shorter.
#let base = kurvst.hobby-spline((
(0.0, 0.0),
(0.9, 0.8),
(1.8, -0.3),
(2.8, 0.4),
))
#let layer = kurvst.layer(
base,
offset: 0.16,
length: 1.6,
ratio: 0.5,
resolve-length: "min",
)
#cetz.canvas({
kurvst.to-cetz(base, stroke: rgb("#bbbbbb") + 0.45pt)
kurvst.to-cetz(layer, stroke: rgb("#1b7f4c") + 0.8pt)
})Use side-point to choose the sign of the offset from a point on the desired side of the path. Drawing packages can use this to place derived layers on the same side as an edge label without knowing anything about the physics or graph style that requested the layer.
Native Drawing Primitives
Kurvst returns dictionaries and arrays. The core geometry can be drawn without CeTZ by emitting native curve content:
#kurvst.to-native(kurvst.from-cubic(segment), unit: 36pt, stroke: black + 0.7pt)The generated reference below keeps drawing code inline. It only uses the small common helpers above for coordinate scaling, native curve construction, point markers, and fixed-size preview blocks.
CeTZ Interoperability
The CeTZ helpers are thin adapters over the same returned geometry. Use them when the surrounding document already lives in a CeTZ canvas, or when you want CeTZ path merging and styling:
#cetz.canvas({
let base = kurvst.from-cubic(segment)
kurvst.to-cetz(base, stroke: rgb("#d72638") + 0.6pt)
let path = kurvst.pattern(
base,
pattern: kurvst.coil(longitudinal-scale: 1.6),
amplitude: 0.12,
wavelength: 0.55,
)
kurvst.to-cetz(path, stroke: rgb("#355c9a") + 0.55pt)
})Generated Reference
kurvst
- append()
- center-outset()
- close()
- coil()
- cubic()
- cubic-point()
- cubic-tangent()
- cubic-to()
- elements()
- from-cubic()
- from-elements()
- hobby-spline()
- hobby-through()
- layer()
- length()
- line()
- line-segment()
- line-to()
- move-to()
- outset-point()
- parallel()
- path()
- pattern()
- point()
- points()
- quad()
- quad-to()
- resolve-length()
- segments()
- split-through()
- to-cetz()
- to-cetz-data()
- to-native()
- trim()
- wave()
- zigzag()
Variables
- layer-defaults
append
Return a path with additional fragments or elements appended.
Parameters
path
dictionary
Base Kurvst path dictionary.
..parts
any
Path fragments, path elements, or element arrays to append.
center-outset
Compute the symmetric trim needed to center a shorter path layer.
Parameters
base-length
int or float
Full base path arc length.
length
none or int or float
Fixed target visible length.
Default: none
ratio
none or int or float
Relative target visible length as a fraction of base-length.
Default: none
resolve-length
string or function
Resolution strategy for fixed and relative targets.
Default: "min"
start-outset
int or float
Already-applied trim at the start of the path.
Default: 0
end-outset
int or float
Already-applied trim at the end of the path.
Default: 0
close
Build a close path element.
Parameters
mode
string
Native Typst curve close mode.
Default: "straight"
coil
A smooth coil path pattern.
Parameters
samples-per-period
int
Number of samples used to approximate one coil period.
Default: 16
longitudinal-scale
int or float
Horizontal scale of the coil before it is mapped onto a path.
Default: 1.25
cubic
Build a cubic path fragment.
Parameters
start
array
Start point.
control-start
array
Cubic control point near start.
control-end
array
Cubic control point near end.
end
array
Endpoint.
cubic-point
Evaluate a cubic segment at parameter t.
Parameters
segment
dictionary
Segment with start, control-start, control-end, and end.
t
int or float
Segment parameter in the range [0, 1].
cubic-tangent
Evaluate the tangent of a cubic segment at parameter t.
Parameters
segment
dictionary
Segment with start, control-start, control-end, and end.
t
int or float
Segment parameter in the range [0, 1].
cubic-to
Build a cubic path element.
Parameters
control-start
array
Cubic control point near the start point.
control-end
array
Cubic control point near the endpoint.
end
array
Cubic endpoint.
elements
Return the command elements that make up a Kurvst path.
Parameters
path
dictionary
Kurvst path dictionary to inspect.
from-cubic
Build a path fragment from a cubic segment dictionary.
Parameters
segment
dictionary
Segment with start, control-start, control-end, and end.
from-elements
Build a path dictionary from an existing element array.
Parameters
elements
array
Array of Kurvst path elements.
hobby-spline
Construct a Hobby spline through an arbitrary point sequence.
Parameters
points
array
Two or more points for the open spline to pass through.
omega
float
Hobby curl/tension parameter.
Default: 1.0
accuracy
float
Geometry approximation accuracy passed to the Rust geometry engine.
Default: 0.001
hobby-through
Construct a cubic Hobby path through three points.
Parameters
start
array
Start point.
through
array
Intermediate point that the curve passes through.
end
array
Endpoint.
omega
float
Hobby curl/tension parameter.
Default: 1.0
accuracy
float
Geometry approximation accuracy passed to the Rust geometry engine.
Default: 0.001
layer
Build a derived visible path layer.
Parameters
path
dictionary
Base Kurvst path dictionary.
offset
int or float
Signed normal offset distance.
Default: 0
length
none or int or float
Fixed target visible length.
Default: none
ratio
none or int or float
Relative target visible length as a fraction of the base length.
Default: none
resolve-length
string or function
Resolution strategy for fixed and relative targets.
Default: "min"
start-outset
int or float
Arc length removed from the start.
Default: 0
end-outset
int or float
Arc length removed from the end.
Default: 0
side-point
none or array
Optional point used to choose the sign of offset.
Default: none
accuracy
float
Geometry approximation accuracy passed to the Rust geometry engine.
Default: 0.001
optimize
bool
Let Kurbo simplify/optimize fitted parallel paths.
Default: true
length
Compute the arc length of a path dictionary.
Parameters
path
dictionary
Kurvst path dictionary to measure.
accuracy
float
Arc-length approximation accuracy passed to the Rust geometry engine.
Default: 0.001
line
Build a straight-line path fragment.
Parameters
start
array
Start point.
end
array
Endpoint.
line-segment
Build a cubic segment dictionary for a straight line.
Parameters
start
array
Start point.
end
array
Endpoint.
line-to
Build a line path element.
Parameters
end
array
Line endpoint.
move-to
Build a move path element.
Parameters
start
array
New current point and subpath start.
outset-point
Return from moved toward toward by distance.
Parameters
from
array
Point to move.
toward
array
Target point that defines the direction.
distance
int or float
Distance to move from from toward toward.
Default: 0
parallel
Generate a parallel path for a path.
Parameters
path
dictionary
Base Kurvst path dictionary.
distance
int or float
Signed normal offset distance.
Default: 0
start-outset
int or float
Arc length removed from the start before offsetting.
Default: 0
end-outset
int or float
Arc length removed from the end before offsetting.
Default: 0
accuracy
float
Geometry approximation accuracy passed to the Rust geometry engine.
Default: 0.001
optimize
bool
Let Kurbo simplify/optimize the fitted path.
Default: true
path
Build a path dictionary from path fragments or elements.
Parameters
..parts
any
Path fragments, path elements, or element arrays to concatenate.
pattern
Apply a repeated path pattern to a base path.
Parameters
path
dictionary
Base Kurvst path dictionary.
pattern
string or dictionary
Pattern dictionary or built-in pattern name.
Default: "wave"
amplitude
int or float
Normal amplitude of the pattern in path units.
Default: 0.1
wavelength
int or float
Arc length of one pattern period.
Default: 1.0
phase
int or float
Initial phase offset in pattern periods.
Default: 0
samples-per-period
int
Samples per period for string-resolved smooth patterns.
Default: 16
coil-longitudinal-scale
int or float
Longitudinal scale used when resolving the built-in coil pattern.
Default: 1.25
anchor-start
bool
Force the generated path to start on the base path.
Default: true
anchor-end
bool
Force the generated path to end on the base path.
Default: true
accuracy
float
Geometry approximation accuracy passed to the Rust geometry engine.
Default: 0.001
point
Build a numeric point tuple.
Parameters
x
int or float
X coordinate.
y
int or float
Y coordinate.
points
Return the points visited by a Kurvst path’s command stream.
Parameters
path
dictionary
Kurvst path dictionary to inspect.
quad
Build a quadratic path fragment.
Parameters
start
array
Start point.
control
array
Quadratic control point.
end
array
Endpoint.
quad-to
Build a quad path element.
Parameters
control
array
Quadratic control point.
end
array
Quadratic endpoint.
resolve-length
Resolve a fixed and relative visible path length.
Parameters
base-length
int or float
Full base path arc length.
length
none or int or float
Fixed target arc length.
Default: none
ratio
none or int or float
Relative target length as a fraction of base-length.
Default: none
method
string or function
Resolution strategy for fixed and relative targets.
Default: "min"
segments
Return drawable cubic segments for any Kurvst path dictionary.
Parameters
path
dictionary
Kurvst path dictionary to convert.
split-through
Split a path through a point sequence into per-span paths.
Parameters
points
array
Two or more points for the curve to pass through.
omega
float
Hobby curl/tension parameter.
Default: 1.0
start-outset
int or float
Arc length removed from the first span start.
Default: 0
end-outset
int or float
Arc length removed from the last span end.
Default: 0
accuracy
float
Geometry approximation accuracy passed to the Rust geometry engine.
Default: 0.001
to-cetz
Draw a path dictionary through CeTZ.
Parameters
path
dictionary
Kurvst path dictionary to draw.
unit
int or float or length or ratio
Coordinate multiplier for emitted CeTZ points.
Default: 1
..style
any
CeTZ draw style arguments forwarded to merge-path.
to-cetz-data
Emit a Kurvst path as CeTZ path data.
Parameters
path
dictionary
Kurvst path dictionary to convert.
unit
int or float or length or ratio
Coordinate multiplier for emitted CeTZ points.
Default: 1
to-native
Emit a Kurvst path as native Typst curve content.
Parameters
path
dictionary
Kurvst path dictionary to emit.
unit
int or float or length or ratio
Coordinate multiplier for emitted Typst curve points.
Default: 1
..style
any
Native curve style arguments.
trim
Trim a path by arc length from each end.
Parameters
path
dictionary
Kurvst path dictionary to trim.
start-outset
int or float
Arc length removed from the start.
Default: 0
end-outset
int or float
Arc length removed from the end.
Default: 0
accuracy
float
Arc-length approximation accuracy passed to the Rust geometry engine.
Default: 0.001
wave
A smooth sinusoidal path pattern.
Parameters
samples-per-period
int
Number of samples used to approximate one wave period.
Default: 16
zigzag
A straight-segment triangular path pattern.
Parameters
Default geometry options for derived path layers.