npm.io
0.1.1 • Published 20h ago

sketch-svg-kit

Licence
MIT
Version
0.1.1
Deps
5
Size
433 kB
Vulns
0
Weekly
0

sketch-svg-kit

Strongly typed, deterministic hand-drawn graphics for SVG and Canvas. The API covers the Rough.js drawing primitives and fill styles, adds safe whole-SVG conversion, and works in browsers and Node.js without a runtime dependency on Rough.js.

Install

npm install sketch-svg-kit

The package publishes ESM and CommonJS entry points with matching TypeScript declarations. Node.js 22.18 or newer is supported.

Generate SVG

import { createGenerator, renderSvg } from 'sketch-svg-kit';

const sketch = createGenerator({
  seed: 42,
  defaults: { roughness: 1.2, stroke: '#172554' },
});

const shapes = [
  sketch.rectangle({
    x: 10,
    y: 10,
    width: 180,
    height: 100,
    style: {
      fill: '#fde68a',
      fillStyle: 'cross-hatch',
      hachureGap: 5,
    },
  }),
  sketch.line({ x1: 20, y1: 125, x2: 180, y2: 125 }),
];

const svg = renderSvg(shapes, {
  width: 200,
  height: 140,
  title: 'Sketch card',
  ariaLabel: 'A hand-drawn card and line',
});

Drawable values contain only readonly JSON-compatible drawing operations. Rendering the same drawable always produces the same result.

Primitives

Every method accepts one named object and returns a Drawable:

sketch.line({ x1, y1, x2, y2, style });
sketch.rectangle({ x, y, width, height, style });
sketch.ellipse({ cx, cy, width, height, style });
sketch.circle({ cx, cy, diameter, style });
sketch.linearPath({ points, style });
sketch.polygon({ points, style });
sketch.arc({ cx, cy, width, height, start, stop, closed, style });
sketch.curve({ points, style });
sketch.path({ d, style });

Angles are in radians. Points are readonly [x, y] tuples. Widths, heights, and diameters must be non-negative; every coordinate must be finite.

Fill styles are a closed TypeScript union:

type FillStyle =
  | 'hachure'
  | 'solid'
  | 'zigzag'
  | 'cross-hatch'
  | 'dots'
  | 'dashed'
  | 'zigzag-line';

The style types expose roughness, bowing, curve fitting/tightness, randomness, stroke and fill weights, hachure settings, line dashes, path simplification, multi-stroke controls, vertex preservation, decimal precision, and seeded randomness. Fill-specific settings are discriminated by fillStyle, and line styles cannot accidentally receive fill-only settings.

Set style.strokeCount to any positive integer to control how many times each outline is drawn (2 by default). It can also be set once in createGenerator({ defaults: { strokeCount: 4 } }). The older disableMultiStroke option remains supported.

SVG paths and low-level output

import { opsToPath, toPaths } from 'sketch-svg-kit';

const drawable = sketch.path({
  d: 'M10 80 A45 45 0 0 1 100 80 L55 10 Z',
  style: { fill: 'tomato', simplification: 0.8 },
});

const paths = toPaths(drawable); // readonly SVG path descriptions
const firstOperationSet = drawable.sets[0];
const d = firstOperationSet ? opsToPath(firstOperationSet, 2) : '';

Browser SVG DOM

import { createGenerator } from 'sketch-svg-kit';
import { renderSvgElement } from 'sketch-svg-kit/dom';

const shape = createGenerator({ seed: 7 }).circle({
  cx: 50,
  cy: 50,
  diameter: 80,
  style: { fill: 'gold', fillStyle: 'dots' },
});

document.body.append(renderSvgElement(shape, { width: 100, height: 100 }));

Importing the root package never accesses the DOM.

Animated SVG

Create a standalone animated SVG string in Node.js or the browser:

import { createGenerator, renderAnimatedSvg } from 'sketch-svg-kit';

const sketch = createGenerator({ seed: 7 });
const shape = sketch.circle({
  cx: 50,
  cy: 50,
  diameter: 80,
  style: { fill: 'gold', fillStyle: 'dots' },
});

const svg = renderAnimatedSvg(shape, { width: 100, height: 100 }, {
  duration: 600,
  delay: 100,
  stagger: 80,
  fillDuration: 200,
  order: 'outline-first',
  sequence: 'element',
});

For a browser element, use renderAnimatedSvgElement from sketch-svg-kit/dom. Set order to outline-first to complete the outside strokes before patterned or solid fills, or to fill-first for the reverse. Set sequence to element to finish each drawable—including its fill—before starting the next; the default document mode orders all drawable layers together. Animation uses native SVG/CSS, needs no runtime JavaScript, honors prefers-reduced-motion, and leaves the completed sketch visible when CSS animation is unavailable.

Canvas

import { createGenerator } from 'sketch-svg-kit';
import { drawCanvas } from 'sketch-svg-kit/canvas';

const canvas = document.querySelector('canvas')!;
const context = canvas.getContext('2d')!;
const shape = createGenerator({ seed: 9 }).polygon({
  points: [[10, 10], [90, 20], [50, 90]],
  style: { fill: 'royalblue', fillStyle: 'zigzag' },
});

drawCanvas(context, shape);

drawCanvas accepts a small structural context interface, so compatible Node Canvas implementations work without becoming package dependencies.

Convert complete SVG documents

String conversion works identically in browsers and Node.js:

import { convertSvg } from 'sketch-svg-kit/convert';

const result = convertSvg(`
  <svg viewBox="0 0 100 100">
    <rect x="10" y="10" width="80" height="80"
          style="fill: coral; stroke: navy; stroke-width: 2"/>
    <text x="50" y="55" text-anchor="middle">Hello</text>
  </svg>
`, {
  seed: 11,
  defaults: { roughness: 1.5, fillStyle: 'hachure' },
});

console.log(result.svg);
console.table(result.warnings);

line, rect (including rounded corners), circle, ellipse, polyline, polygon, and path are sketched. Groups, nested SVGs, transforms, viewports, accessibility metadata, inherited presentation attributes, inline styles, and local references are retained. Safe text, embedded raster images, gradients, patterns, clipping, masks, and filters are preserved.

Browser conversion can resolve stylesheet rules through computed styles:

import { convertSvgElement } from 'sketch-svg-kit/dom';

const source = document.querySelector('svg')!;
const { element, warnings } = convertSvgElement(source, { seed: 11 });
source.replaceWith(element);

String conversion intentionally does not implement a CSS cascade. <style> elements are removed with a stylesheet-not-resolved warning; use presentation attributes, inline styles, or the DOM adapter when computed CSS matters.

Conversion security

Conversion treats input as untrusted XML:

  • DTD and entity declarations are rejected.
  • Scripts, animation elements, foreignObject, and event attributes are removed.
  • JavaScript URLs, external URLs, external images, and external paint servers are removed.
  • Images are preserved only for embedded PNG, JPEG, GIF, or WebP data URLs.
  • Local fragment references such as url(#gradient) and href="#symbol" are allowed.
  • Unsupported or invalid geometry is preserved when safe and reported through a structured warning.

The warning codes are part of the public API, so applications can log or reject lossy conversions according to their own policy.

Determinism

Set a generator seed for repeatable shapes:

const a = createGenerator({ seed: 123 }).ellipse({ cx: 20, cy: 20, width: 30, height: 10 });
const b = createGenerator({ seed: 123 }).ellipse({ cx: 20, cy: 20, width: 30, height: 10 });

JSON.stringify(a) === JSON.stringify(b); // true

Without a configured seed, each new drawable receives a cryptographically generated non-zero seed, which is stored in drawable.options.seed. A per-shape style.seed overrides the generator seed.

Rough.js migration

Rough.js sketch-svg-kit
rough.generator(options) createGenerator({ defaults, seed })
gen.rectangle(x, y, w, h, options) gen.rectangle({ x, y, width: w, height: h, style })
rough.svg(svg).draw(drawable) renderSvgElement(drawable, viewport)
rough.canvas(canvas).draw(drawable) drawCanvas(context, drawable)
generator.toPaths(drawable) toPaths(drawable)
Browser-only SVG element output SVG strings in Node and SVG elements in browsers

The package targets feature parity, not matching random geometry, serialized output, positional signatures, or mutable Rough.js data structures.

Development

npm run typecheck
npm test
npm run build
npm run test:package
npm run test:browser
npm run check

npm run check verifies strict types, unit/security tests, dual ESM/CommonJS builds, declaration resolution, package metadata, and installation of the packed tarball. Browser tests use Chromium through Playwright.

The browser suite includes a side-by-side fixture for every primitive and fill style at test/browser/rough-comparison.html. It renders this package beside Rough.js 4.6.6 as a visual capability reference; the tests intentionally do not require identical randomized geometry.

License and attribution

MIT. The renderer algorithms are adapted from Rough.js under its MIT license; see THIRD_PARTY_NOTICES.md.

Keywords