Open History to see the last commit
1import type { PreviewAnnotationPoint, PreviewAnnotationRect } from "@t3tools/contracts";
2
3// Setting a path's `d` makes Chromium parse the whole string again, so a long
4// stroke is drawn as a run of paths and only the last one is rewritten per move.
5export const STROKE_SEGMENTS_PER_PATH = 100;
6
7export interface StrokeGeometry {
8 readonly points: PreviewAnnotationPoint[];
9 minX: number;
10 minY: number;
11 maxX: number;
12 maxY: number;
13 // The curve through the newest point is still open: where it ends depends on
14 // the point after it. This holds only the settled segments of the last path.
15 openPath: string;
16 openSegments: number;
17}
18
19export function startStroke(point: PreviewAnnotationPoint): StrokeGeometry {
20 return {
21 points: [point],
22 minX: point.x,
23 minY: point.y,
24 maxX: point.x,
25 maxY: point.y,
26 openPath: `M ${point.x} ${point.y}`,
27 openSegments: 0,
28 };
29}
30
31// Returns the final `d` of the path this point filled, if it filled one.
32export function addStrokePoint(
33 geometry: StrokeGeometry,
34 point: PreviewAnnotationPoint,
35): string | null {
36 const previous = geometry.points[geometry.points.length - 1]!;
37 let closedPath: string | null = null;
38 if (geometry.points.length > 1) {
39 const midX = (previous.x + point.x) / 2;
40 const midY = (previous.y + point.y) / 2;
41 geometry.openPath += ` Q ${previous.x} ${previous.y} ${midX} ${midY}`;
42 geometry.openSegments += 1;
43 if (geometry.openSegments === STROKE_SEGMENTS_PER_PATH) {
44 closedPath = geometry.openPath;
45 geometry.openPath = `M ${midX} ${midY}`;
46 geometry.openSegments = 0;
47 }
48 }
49 geometry.points.push(point);
50 geometry.minX = Math.min(geometry.minX, point.x);
51 geometry.minY = Math.min(geometry.minY, point.y);
52 geometry.maxX = Math.max(geometry.maxX, point.x);
53 geometry.maxY = Math.max(geometry.maxY, point.y);
54 return closedPath;
55}
56
57export function openStrokePath(geometry: StrokeGeometry): string {
58 const last = geometry.points[geometry.points.length - 1]!;
59 return `${geometry.openPath} L ${last.x} ${last.y}`;
60}
61
62export function strokeBounds(geometry: StrokeGeometry, width: number): PreviewAnnotationRect {
63 const padding = width + 3;
64 const left = geometry.minX - padding;
65 const top = geometry.minY - padding;
66 const right = geometry.maxX + padding;
67 const bottom = geometry.maxY + padding;
68 return { x: left, y: top, width: right - left, height: bottom - top };
69}