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 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 | 855x 855x 855x 326x 6x 5x 5x 5x 9x 9x 5x 5x 5x | /**
* Min-distance point filtering — the first stage of the ink pipeline
* (SPEC §4.2). Pure and framework-free.
*/
import type { SignaturePoint } from "../types";
/**
* Euclidean distance between two points, in CSS px.
*
* @example
* ```ts
* import { pointDistance } from "@usefy/signature-pad/headless";
*
* pointDistance({ x: 0, y: 0 }, { x: 3, y: 4 }); // 5
* ```
*/
export function pointDistance(
a: Pick<SignaturePoint, "x" | "y">,
b: Pick<SignaturePoint, "x" | "y">,
): number {
const dx = b.x - a.x;
const dy = b.y - a.y;
return Math.sqrt(dx * dx + dy * dy);
}
/**
* The single accept/drop predicate shared by the batch {@link filterPoints}
* and the engine's incremental capture path — both MUST agree or replay
* would diverge from live drawing.
*
* A point passes when it is at least `minDistance` px from the previously
* kept point. `minDistance <= 0` accepts everything (including exact
* duplicates).
*
* @example
* ```ts
* import { passesMinDistance } from "@usefy/signature-pad/headless";
*
* passesMinDistance({ x: 0, y: 0 }, { x: 2, y: 0 }, 2); // true (exactly at threshold)
* passesMinDistance({ x: 0, y: 0 }, { x: 1.9, y: 0 }, 2); // false
* ```
*/
export function passesMinDistance(
prev: Pick<SignaturePoint, "x" | "y">,
next: Pick<SignaturePoint, "x" | "y">,
minDistance: number,
): boolean {
return pointDistance(prev, next) >= minDistance;
}
/**
* Drop points closer than `minDistance` to the last **kept** point.
* The first point is always kept. Pure — the input array is not mutated;
* kept points are the original references.
*
* @example
* ```ts
* import { filterPoints } from "@usefy/signature-pad/headless";
*
* const pts = [
* { x: 0, y: 0, time: 0, pressure: 0 },
* { x: 1, y: 0, time: 5, pressure: 0 }, // 1px from (0,0) — dropped
* { x: 3, y: 0, time: 10, pressure: 0 }, // 3px from (0,0) — kept
* ];
* filterPoints(pts, 2).map((p) => p.x); // [0, 3]
* ```
*/
export function filterPoints(
points: readonly SignaturePoint[],
minDistance: number,
): SignaturePoint[] {
if (points.length === 0) return [];
const kept: SignaturePoint[] = [points[0]];
let last = points[0];
for (let i = 1; i < points.length; i++) {
const p = points[i];
if (passesMinDistance(last, p, minDistance)) {
kept.push(p);
last = p;
}
}
return kept;
}
|