All files / signature-pad/src/engine render.ts

100% Statements 14/14
100% Branches 4/4
100% Functions 3/3
100% Lines 13/13

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65                                              401x 326x 326x 326x 10360x 10360x   326x                         182x 182x 4x 4x                           210x 73x      
/**
 * Canvas rendering for the signature engine — dot-stamped draw steps
 * (SPEC §4.2 flatten model) for both the incremental live path and full
 * replay. All coordinates are CSS px; the engine's DPR transform maps
 * them onto the backing store.
 *
 * Framework-free; only touches the 2D context it is handed.
 */
import type { SignatureStroke } from "../types";
import type { FlatStep } from "../ink/bezier";
import { strokeSteps } from "../ink/strokeWalker";
 
/**
 * Stamp a batch of draw steps as filled dots of the step's radius —
 * one `beginPath` + `fill` per batch, `moveTo` + `arc` per dot (the
 * signature_pad render model; `moveTo` to the arc's start point prevents
 * connecting lines between dots inside the shared path).
 */
export function drawSteps(
  ctx: CanvasRenderingContext2D,
  color: string,
  steps: readonly FlatStep[],
): void {
  if (steps.length === 0) return;
  ctx.fillStyle = color;
  ctx.beginPath();
  for (const step of steps) {
    ctx.moveTo(step.x + step.width, step.y);
    ctx.arc(step.x, step.y, step.width, 0, Math.PI * 2);
  }
  ctx.fill();
}
 
/**
 * Wipe the canvas (CSS-px viewport) and repaint the optional opaque
 * background beneath where the ink will go.
 */
export function clearAndFill(
  ctx: CanvasRenderingContext2D,
  width: number,
  height: number,
  background: string | undefined,
): void {
  ctx.clearRect(0, 0, width, height);
  if (background !== undefined) {
    ctx.fillStyle = background;
    ctx.fillRect(0, 0, width, height);
  }
}
 
/**
 * Draw every committed stroke from scratch by walking the exact same
 * pipeline as incremental input ({@link strokeSteps}) — deterministic
 * replay is identical geometry by construction (SPEC §3.1). Strokes are
 * self-contained, so no geometry options are needed.
 */
export function replayStrokes(
  ctx: CanvasRenderingContext2D,
  strokes: readonly SignatureStroke[],
): void {
  for (const stroke of strokes) {
    drawSteps(ctx, stroke.color, strokeSteps(stroke));
  }
}