@rougher-stuff/roughjs
v0.0.1
Published
Create graphics using HTML Canvas or SVG with a hand-drawn, sketchy, appearance.
Maintainers
Readme
Rough.js
This workspace contains the publishable @rougher-stuff/roughjs package. See the repository root README for
installation, usage, and monorepo development instructions.
Compound SVG paths accept fillRule: 'nonzero' | 'evenodd' (default nonzero) for solid and
pattern fills. Public geometry methods reject non-finite coordinates with a RangeError, and a
fixed non-zero seed produces the same operation stream for every fill style.
Rounded rectangles and polygons compile from the shared nominal path model before roughening:
generator.rectangle(10, 10, 200, 100, {
cornerRadius: { topLeft: 16, topRight: 4, bottomRight: 20, bottomLeft: 8 },
});
generator.polygon(points, {
cornerRadius: [4, 8, 12, 8],
cornerSmoothing: 0.35,
});roundedRectangle() and roundedPolygon() are equivalent convenience methods on the generator,
Canvas, and SVG APIs. Oversized radii are clamped to adjacent edge lengths.
Instrument style presets
createRoughStyle() turns a named writing instrument into options accepted by the generator, Canvas,
and SVG APIs. A seed and any regular Rough.js option can be supplied alongside the preset.
import rough from '@rougher-stuff/roughjs';
import { createRoughStyle } from '@rougher-stuff/roughjs/style';
const pen = createRoughStyle({
preset: 'ballpoint',
seed: 42,
stroke: '#174a7e',
});
rough.canvas(canvas).line(20, 20, 220, 100, pen);Available presets are pencil, mechanical-pencil, ballpoint, felt-tip, marker, chalk,
crayon, brush-pen, technical-pen, and dry-marker. Each preset has a normalized stroke
personality describing width and pressure variation, edge and path noise, gaps, bleed, overshoot,
and retracing. Override traits to make a variant; explicit Rough.js options take precedence over the
projected values.
const wornMarker = createRoughStyle({
preset: 'dry-marker',
personality: { gapProbability: 0.5, edgeNoise: 0.65 },
strokeWidth: 5,
});The helper is also available as rough.createRoughStyle(). Use ROUGH_STYLE_PRESETS from the
focused @rougher-stuff/roughjs/style entry point to enumerate the built-in preset names.
Semantic drawable pipeline
The additive @rougher-stuff/roughjs/semantic entry point preserves editable geometry separately from compiled
rough operations. Existing generator APIs remain unchanged during the retained-rendering migration.
import { compileDrawable, createDrawable, Dirty } from '@rougher-stuff/roughjs/semantic';
const source = createDrawable(
{ type: 'rectangle', x: 20, y: 20, width: 160, height: 80, radius: 16 },
{
seed: 42,
stroke: {
width: 8,
align: 'outside',
paint: {
type: 'linear-gradient',
from: { x: 0, y: 0 },
to: { x: 160, y: 0 },
stops: [
{ offset: 0, color: '#f00' },
{ offset: 1, color: '#00f' },
],
},
},
},
);
const compiled = compileDrawable(source);
compiled.renderPlan.layers;applyDrawableUpdate() reports Dirty.Paint, Dirty.Transform, Dirty.RoughGeometry, or
Dirty.Geometry so retained surfaces can avoid rough regeneration for paint and transform updates.
@rougher-stuff/roughjs/render-plan compiles linear/radial paints to scoped SVG paint servers or Canvas gradients.
Inside strokes use doubled-width nominal clipping; outside strokes use SVG masks or Canvas even-odd
clipping. Open shapes reject inside/outside alignment at compilation time.
import { renderCompiledCanvas, renderCompiledSvg } from '@rougher-stuff/roughjs/render-plan';
renderCompiledSvg(svg, compiled);
renderCompiledCanvas(context, compiled);Retained surfaces mount semantic drawables and reuse compiled rough paths for transform-only and paint-only updates. SVG patches transforms directly; Canvas performs a correctness-first scene redraw.
import { svgSurface } from '@rougher-stuff/roughjs/retained';
const surface = svgSurface(svg);
const node = surface.mount(source);
node.update({ transform: { a: 1, b: 0, c: 0, d: 1, e: 100, f: 30 } });
node.update({ style: { stroke: { paint: { type: 'solid', color: '#111' } } } });
node.remove();Drawing animation uses the shared @rougher-stuff/animation controller and geometry path metrics.
It therefore supports deterministic external progress without SVGPathElement.getTotalLength().
const animation = node.animate({ autoplay: false, duration: 800 });
animation.seek(0.4);
animation.play();
await animation.finished;Hachure, cross-hatch, and dots patterns accept deterministic linear or radial density fields. Density changes candidate spacing and seeded geometry selection rather than paint opacity.
const shaded = createDrawable(
{ type: 'polygon', points },
{
seed: 9,
fill: {
pattern: 'hachure',
paint: { type: 'solid', color: '#111' },
density: { type: 'linear', from: 0.1, to: 1, angle: 90 },
},
},
);