npm package: @fartinmartin/canvas-paint
Description: ``` npm i @fartinmartin/canvas-paint ```
Latest version: 1.1.0

# canvas-paint

## Install

```
npm i @fartinmartin/canvas-paint
```

## Usage

```js
import { Paint } from "@fartinmartin/canvas-paint";

const root = document.getElementById("root");
const paint = new Paint(root, { width: 200, height: 200 });
```

## Options

```ts
new Paint(root, {
  // Canvas
  width: 200,           // document width in px
  height: 200,          // document height in px
  bgColor: undefined,   // CSS color string; sets background of root element
  margin: 0,            // drawable area beyond document boundary (in document units)
  debounce: 500,        // ms delay for ResizeObserver

  // Brush
  brush: {
    size: 5,            // stroke width
    color: "#ffaa00",   // CSS color string
    mode: "draw",       // "draw" | "erase" | "fill"
    cap: "round",       // "butt" | "round" | "square"
    join: "round",      // "round" | "bevel" | "miter"
    tolerance: 30,      // point simplification tolerance
  },

  // Lazy brush
  lazy: {
    radius: 0,          // lazy radius in px (0 = disabled)
    enabled: true,
    lerpFactor: 1,      // multiplier on how far you must draw before reaching full radius
  },

  // UI overlay (brush preview, pointer, chain)
  ui: {
    show: true,
    brush: true,        // show brush size/shape preview at brush position
    pointer: {
      show: true,
      size: 8,
      color: "#000000",
    },
    center: {
      show: true,
      size: 4,
      color: "#000000",
    },
    chain: {
      show: false,      // catenary chain between pointer and brush (requires lazy.radius > 0)
      dash: [2, 4],
      width: 2,
      color: "#000000",
    },
  },

  // Grid / boundary overlay
  grid: {
    show: true,
    lines: {
      show: false,      // grid lines (default off)
      size: 50,         // spacing in document units
      color: "rgba(0, 0, 0, 0.15)",
    },
    boundary: {
      show: true,       // document boundary rect (default on when margin > 0)
      dash: [4, 4],
      width: 1,
      color: "rgba(0, 0, 0, 0.2)",
    },
  },
});
```

## Brush settings

Brush settings can be read and written directly at any time:

```js
paint.brush.size = 10;
paint.brush.color = "red";
paint.brush.mode = "erase";
paint.brush.cap = "square";
paint.brush.join = "bevel";
paint.brush.tolerance = 20;
paint.brush.radius = 40;      // lazy radius
paint.brush.lerpFactor = 2;
```

## Methods

```js
paint.undo()
paint.redo()
paint.clear()                 // clears canvas and adds a clear entry to history
paint.reset()                 // clears canvas and wipes history (no undo)
paint.destroy()

paint.setBgColor(color)       // update background color immediately
paint.setSize(width, height)  // resize the document
paint.setMargin(margin)       // update margin

// Playback options: { delay?: number (ms per point) } or { duration?: number (total ms) }
// If both are given, duration takes precedence and delay is ignored.
paint.drawHistory(options?)               // redraws all paths; animates if delay/duration given
paint.save()                              // returns a serializable SaveData object
await paint.load(data, options?)          // clears and replays a saved SaveData

await paint.toBlob(type?, quality?)       // exports document area as Blob
await paint.toVideo(options?)             // records drawHistory to a webm Blob; accepts fps too

paint.removeListeners()                   // detach all input event listeners
```

## Properties

```js
paint.history.canUndo   // boolean
paint.history.canRedo   // boolean
paint.scale             // current CSS px per document unit
paint.aspectRatio       // width / height (including margin)
paint.path              // the Path currently being drawn
```

## Events

Subscribe via `paint.events.on(event, handler)`. Handlers are fully typed when using TypeScript.

| event          | payload         | description                                       |
| -------------- | --------------- | ------------------------------------------------- |
| `start`        | `BrushPayload`  | pointer down, drawing began                       |
| `draw`         | `BrushPayload`  | pointer move while drawing                        |
| `end`          | `BrushPayload`  | pointer up, path committed                        |
| `leave`        | `BrushPayload`  | pointer left canvas while drawing, path committed |
| `resizing`     | —               | ResizeObserver fired, resize in progress          |
| `resized`      | —               | resize and history redraw complete                |
| `drawing`      | —               | `drawHistory` started                             |
| `drawingPath`  | —               | each path committed during animated playback      |
| `drawn`        | —               | `drawHistory` complete                            |

Brush-level events are also available via `paint.brush.events.on(event, handler)`:

| event          | payload         | description              |
| -------------- | --------------- | ------------------------ |
| `brushUpdate`  | `BrushPayload`  | any brush setting changed |
| `move`         | `BrushPayload`  | pointer moved            |
| `down`         | `BrushPayload`  | pointer down             |
| `up`           | `BrushPayload`  | pointer up               |
| `leave`        | `BrushPayload`  | pointer left canvas      |

## TypeScript

Key types exported from the package:

```ts
import type {
  PaintOptions,
  PaintEvents,
  BrushEvents,
  BrushPayload,
  PlaybackOptions,
  SaveData,           // shape returned by paint.save()
  RenderData,         // input shape for render()
  VideoOptions,
} from "@fartinmartin/canvas-paint";
```

## Server-side rendering

`render()` and `renderVideo()` work in Node.js via [`node-canvas`](https://github.com/Automattic/node-canvas). Import from the `/render` entry point to avoid browser-only DOM dependencies.

**Image:**

```ts
import { render } from "@fartinmartin/canvas-paint/render";
import { createCanvas } from "canvas";

const canvas = render(data, { createCanvas });
canvas.toBuffer("image/png");
```

**Video** — requires a custom encoder since `captureStream` and `MediaRecorder` are browser-only. The library ships no ffmpeg dependency; bring your own:

```ts
import { renderVideo } from "@fartinmartin/canvas-paint/render";
import { createCanvas } from "canvas";

const blob = await renderVideo(data, {
  createCanvas,
  encode: myFfmpegEncoder, // (opts: VideoEncodeOpts) => { start(): void; stop(): Promise<Blob> }
  fps: 30,
  delay: 10,      // ms per point
  duration: 3000, // total ms (converted to per-point delay); takes precedence over delay
});
```

The `encode` function receives `VideoEncodeOpts` (`artboard`, `temp`, `crop`, `width`, `height`, `bgColor`, `fps`, `mkCanvas`) and must return `{ start(): void; stop(): Promise<Blob> }` — the same interface as the browser default.