Redraw is currently in technical preview, available to start-react-native.dev subscribers. API is unstable.
Paths
Paths in Redraw split into two flavors: open paths and closed paths.
Open paths have no interior to be filled; they exist only to be stroked. This is where the SingleStrokeBrush API comes in handy.
Closed paths have a defined interior that can be filled.
| Path kind | Drawn with | Best for |
|---|---|---|
| Open path | SingleStrokeBrush + drawPath | Calligraphy, signatures, single-stroke animations. |
| Closed path | addGeometry + drawGeometry | Filled shapes, repeated draws of the same outline. |
Building paths
You can build a path using SVG:
import { parseSVG } from "redraw";
const path = parseSVG("M 10 10 L 90 90 C 100 50, 50 50, 10 10 Z");
or using the path builder API:
import { PathBuilder, vec } from "redraw";
const cx = 100, cy = 100, size = 80;
const heart = new PathBuilder();
heart.moveTo(vec(cx, cy - size * 0.4));
heart.cubicTo(
vec(cx + size * 0.5, cy - size * 0.8),
vec(cx + size, cy - size * 0.2),
vec(cx, cy + size * 0.6),
);
heart.cubicTo(
vec(cx - size, cy - size * 0.2),
vec(cx - size * 0.5, cy - size * 0.8),
vec(cx, cy - size * 0.4),
);
heart.close();
// Paths are immutable
const path = heart.makePath();
PathBuilder is the imperative API: moveTo, lineTo, quadTo,
cubicTo, close. Use close() when you want a closed contour;
omit it for open ones. close() also starts a fresh contour, so
chaining close().moveTo(...) works as expected for multi-contour
paths.
Open paths
An open path doesn't enclose a region; there's no "interior", just a curve
to stroke. The fastest way to render one is to pair parseSVG with
fitPath, attach it to a SingleStrokeBrush, and call canvas.drawPath:
import { fitPath, SingleStrokeBrush, GradientAlongPath } from "redraw";
const pathGeo = fitPath(svgString, width, height, { maxWidth: 800 });
const brush = new SingleStrokeBrush();
brush.addStroke(new GradientAlongPath(palette), 25);
const path = canvas.drawPath(pathGeo, brush);
// Animate the reveal:
path.segment(0, progress);
drawPath returns a handle whose segment(t0, t1) method draws only the
portion of the path between two arc-length parameters. Perfect for
draw-in effects.
SingleStrokeBrush (see Brushes) is what unlocks the
optimized path: the renderer skips interior evaluation and exposes the
full geometry context (ctx.t, ctx.tan, ctx.grad) to your TypeGPU
functions.
Closed paths
A closed path encloses a region, and the renderer needs to evaluate the SDF over the whole interior. For shapes you draw repeatedly (say, an animated heart with multiple ghost outlines, or a static logo behind a gradient), build the geometry once and reuse it:
const heartPath = createHeartPath(cx, cy, baseSize); // closed Path
const geometry = canvas.addGeometry(heartPath);
// Now draw it many times with different brushes:
canvas.drawGeometry(geometry, mainBrush);
canvas.drawGeometry(geometry, glowBrush);
for (const ghost of ghosts) {
canvas.pushTransform();
canvas.transform(ghost.transform);
canvas.drawGeometry(geometry, ghost.brush);
canvas.popTransform();
}
addGeometry rasterizes the path's SDF into a tile once and uploads it
to the GPU. drawGeometry then samples from that buffer, much cheaper
than rebuilding the SDF for each draw. The win compounds when you use
the same shape with multiple brushes or under a transform stack.
The Heartbeat demo below builds a closed heart path, calls addGeometry
once, then drawGeometry for the main fill, the inner glow, and five
ghost outlines that spawn on each beat, all sharing one geometry buffer:
Path helpers
These are available on any Path instance, regardless of whether you
draw it as open or closed:
path.bounds(): axis-aligned bounding box.path.segment(t0, t1): sub-path between two arc-length parameters[0, 1].path.splitContours(): array of single-contour paths. Useful when an SVG has multiple disconnected sub-paths and you want them rendered independently.path.fit(mode, src, dst): explicit fit ("contain" / "cover" / "fill").
And as module-level functions:
fitPath(svgString, w, h, { padding, maxWidth }): convenient parse-and-fit-to-canvas wrapper.fitbox(mode, src, dst): returns the fit matrix without applying it.