@reekon-tools/boldr-utils 1.15.0 → 1.15.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (88) hide show
  1. package/dist/annotation/canvas/AnnotationCanvasInner.js +5 -2
  2. package/dist/annotation/canvas/AnnotationCanvasInner.native.js +10 -3
  3. package/dist/annotation/canvas/AnnotationCanvasSkia.d.ts +2 -1
  4. package/dist/annotation/canvas/AnnotationCanvasSkia.js +17 -3
  5. package/dist/annotation/canvas/Tool.d.ts +1 -0
  6. package/dist/annotation/canvas/elements/PlaneElement.d.ts +18 -0
  7. package/dist/annotation/canvas/elements/PlaneElement.js +115 -0
  8. package/dist/annotation/canvas/planeGeometry.d.ts +48 -0
  9. package/dist/annotation/canvas/planeGeometry.js +507 -0
  10. package/dist/annotation/canvas/tools/measurementTool.d.ts +1 -0
  11. package/dist/annotation/canvas/tools/measurementTool.js +13 -0
  12. package/dist/annotation/canvas/tools/planeTool.d.ts +12 -0
  13. package/dist/annotation/canvas/tools/planeTool.js +357 -0
  14. package/dist/annotation/canvas/tools/selectTool.js +9 -0
  15. package/dist/annotation/canvas/useAnnotationCanvasState.d.ts +3 -1
  16. package/dist/annotation/canvas/useAnnotationCanvasState.js +17 -0
  17. package/dist/canvas/AnnotationCanvas.d.ts +11 -0
  18. package/dist/canvas/AnnotationCanvas.js +10 -0
  19. package/dist/canvas/AnnotationCanvas.native.d.ts +8 -0
  20. package/dist/canvas/AnnotationCanvas.native.js +6 -0
  21. package/dist/canvas/AnnotationCanvasInner.d.ts +39 -0
  22. package/dist/canvas/AnnotationCanvasInner.js +219 -0
  23. package/dist/canvas/AnnotationCanvasInner.native.d.ts +35 -0
  24. package/dist/canvas/AnnotationCanvasInner.native.js +138 -0
  25. package/dist/canvas/AnnotationCanvasSkia.d.ts +27 -0
  26. package/dist/canvas/AnnotationCanvasSkia.js +20 -0
  27. package/dist/canvas/Tool.d.ts +38 -0
  28. package/dist/canvas/Tool.js +1 -0
  29. package/dist/canvas/elements/BackgroundImageElement.d.ts +9 -0
  30. package/dist/canvas/elements/BackgroundImageElement.js +37 -0
  31. package/dist/canvas/elements/MeasurementStampElement.d.ts +13 -0
  32. package/dist/canvas/elements/MeasurementStampElement.js +30 -0
  33. package/dist/canvas/elements/ShapeElement.d.ts +7 -0
  34. package/dist/canvas/elements/ShapeElement.js +62 -0
  35. package/dist/canvas/elements/StrokeElement.d.ts +7 -0
  36. package/dist/canvas/elements/StrokeElement.js +18 -0
  37. package/dist/canvas/measurementPicker.d.ts +10 -0
  38. package/dist/canvas/measurementPicker.js +1 -0
  39. package/dist/canvas/measurementStampOverlay.d.ts +11 -0
  40. package/dist/canvas/measurementStampOverlay.js +1 -0
  41. package/dist/canvas/pointerAdapter.d.ts +3 -0
  42. package/dist/canvas/pointerAdapter.js +19 -0
  43. package/dist/canvas/stampLayout.d.ts +5 -0
  44. package/dist/canvas/stampLayout.js +14 -0
  45. package/dist/canvas/tools/measurementStampTool.d.ts +9 -0
  46. package/dist/canvas/tools/measurementStampTool.js +37 -0
  47. package/dist/canvas/tools/panTool.d.ts +5 -0
  48. package/dist/canvas/tools/panTool.js +25 -0
  49. package/dist/canvas/tools/penTool.d.ts +13 -0
  50. package/dist/canvas/tools/penTool.js +68 -0
  51. package/dist/canvas/tools/selectTool.d.ts +2 -0
  52. package/dist/canvas/tools/selectTool.js +182 -0
  53. package/dist/canvas/useAnnotationCanvasState.d.ts +54 -0
  54. package/dist/canvas/useAnnotationCanvasState.js +210 -0
  55. package/dist/canvas/viewport.d.ts +16 -0
  56. package/dist/canvas/viewport.js +54 -0
  57. package/dist/data/AnnotationDataContext.d.ts +8 -0
  58. package/dist/data/AnnotationDataContext.js +11 -0
  59. package/dist/data/AnnotationDataProvider.d.ts +65 -0
  60. package/dist/data/AnnotationDataProvider.js +4 -0
  61. package/dist/data/InMemoryAnnotationProvider.d.ts +30 -0
  62. package/dist/data/InMemoryAnnotationProvider.js +197 -0
  63. package/dist/data/canvasPersistence.d.ts +3 -0
  64. package/dist/data/canvasPersistence.js +26 -0
  65. package/dist/data/hooks/useAnnotationCanvasDoc.d.ts +33 -0
  66. package/dist/data/hooks/useAnnotationCanvasDoc.js +314 -0
  67. package/dist/data/hooks/useAnnotationDoc.d.ts +7 -0
  68. package/dist/data/hooks/useAnnotationDoc.js +33 -0
  69. package/dist/data/hooks/useAnnotationList.d.ts +7 -0
  70. package/dist/data/hooks/useAnnotationList.js +26 -0
  71. package/dist/data/hooks/useAnnotationMutations.d.ts +9 -0
  72. package/dist/data/hooks/useAnnotationMutations.js +11 -0
  73. package/dist/exports.d.ts +2 -0
  74. package/dist/exports.js +2 -0
  75. package/dist/hooks/useParseMeasurement.d.ts +4 -0
  76. package/dist/hooks/useParseMeasurement.js +14 -0
  77. package/dist/types/annotation.d.ts +37 -0
  78. package/dist/types/annotation.js +30 -0
  79. package/dist/types/firestore.d.ts +5 -0
  80. package/dist/utils/evaluateFormula.d.ts +20 -0
  81. package/dist/utils/evaluateFormula.js +31 -0
  82. package/package.json +1 -1
  83. package/dist/theme/index.d.ts +0 -3
  84. package/dist/theme/index.js +0 -3
  85. package/dist/theme/preset.d.ts +0 -120
  86. package/dist/theme/preset.js +0 -25
  87. package/dist/theme/semantic.d.ts +0 -136
  88. package/dist/theme/semantic.js +0 -116
@@ -306,12 +306,15 @@ export const AnnotationCanvasInner = (props) => {
306
306
  // act on them (view mode's pan tool selects measurements but must not
307
307
  // advertise draggability).
308
308
  selectedId: selection?.ids[0] ?? null,
309
- handleRadius: activeTool?.dragSelection
309
+ // Plane-calibration handles reuse the same screen-constant sizing —
310
+ // the plane tool (planeEditing) is the only tool that can drag them.
311
+ handleRadius: activeTool?.dragSelection || activeTool?.planeEditing
310
312
  ? HANDLE_PX / state.viewport.zoom
311
313
  : undefined,
312
- handleRingWidth: activeTool?.dragSelection
314
+ handleRingWidth: activeTool?.dragSelection || activeTool?.planeEditing
313
315
  ? HANDLE_RING_PX / state.viewport.zoom
314
316
  : undefined,
317
+ showPlaneHandles: activeTool?.planeEditing === true,
315
318
  customPreview,
316
319
  }), renderMeasurementStamp && (_jsx("div", { style: {
317
320
  position: 'absolute',
@@ -1603,9 +1603,16 @@ export const AnnotationCanvasInner = (props) => {
1603
1603
  // Endpoint/corner handles are drag affordances — only the select tool can
1604
1604
  // act on them, so suppress them when the active tool has no drag support
1605
1605
  // (e.g. view mode's pan tool, where a selected measurement still shows its
1606
- // tile chrome but must not advertise draggability).
1607
- handleRadius: activeTool?.dragSelection ? handleRadius : undefined,
1608
- handleRingWidth: activeTool?.dragSelection ? handleRingWidth : undefined,
1606
+ // tile chrome but must not advertise draggability). Plane-calibration
1607
+ // handles reuse the same screen-constant sizing; the plane tool
1608
+ // (planeEditing) is the only tool that can drag those.
1609
+ handleRadius: activeTool?.dragSelection || activeTool?.planeEditing
1610
+ ? handleRadius
1611
+ : undefined,
1612
+ handleRingWidth: activeTool?.dragSelection || activeTool?.planeEditing
1613
+ ? handleRingWidth
1614
+ : undefined,
1615
+ showPlaneHandles: activeTool?.planeEditing === true,
1609
1616
  customPreview,
1610
1617
  };
1611
1618
  // Lens window for the loupe: an opaque white base + the magnified scene
@@ -88,9 +88,10 @@ export interface AnnotationCanvasSkiaProps {
88
88
  handleRingWidth?: number | {
89
89
  value: number;
90
90
  };
91
+ showPlaneHandles?: boolean;
91
92
  customPreview?: ReactNode;
92
93
  wrapContent?: (content: ReactNode) => ReactNode;
93
94
  canvasRef?: RefObject<CanvasRef | null>;
94
95
  }
95
- export declare const AnnotationCanvasSkia: ({ width, height, effectiveCanvas, worldTransform, viewport, resolveImageUrl, backgroundSkImages, valueFont, textFontMgr, penDrawingStroke, livePreview, shapePreview, draggingId, dragTransform, resizingId, resizeTransform, selectedId, endpointDragId, shapeEndpointDragId, liveLineP1, liveLineP2, rectDragId, liveRect, shapeResizeId, shapeResizePath, shapeResizeBox, handleRadius, handleRingWidth, customPreview, wrapContent, canvasRef, }: AnnotationCanvasSkiaProps) => import("react/jsx-runtime").JSX.Element;
96
+ export declare const AnnotationCanvasSkia: ({ width, height, effectiveCanvas, worldTransform, viewport, resolveImageUrl, backgroundSkImages, valueFont, textFontMgr, penDrawingStroke, livePreview, shapePreview, draggingId, dragTransform, resizingId, resizeTransform, selectedId, endpointDragId, shapeEndpointDragId, liveLineP1, liveLineP2, rectDragId, liveRect, shapeResizeId, shapeResizePath, shapeResizeBox, handleRadius, handleRingWidth, showPlaneHandles, customPreview, wrapContent, canvasRef, }: AnnotationCanvasSkiaProps) => import("react/jsx-runtime").JSX.Element;
96
97
  export {};
@@ -6,6 +6,7 @@ import { SELECTION_PAD, textResizeGeometry, textShapeBounds, } from './textGeome
6
6
  import { visualShapeBounds } from './shapeGeometry.js';
7
7
  import { backgroundLayerDocRect, backgroundLayersOf, } from './backgroundLayers.js';
8
8
  import { BackgroundLayerElement } from './elements/BackgroundImageElement.js';
9
+ import { PlaneElement } from './elements/PlaneElement.js';
9
10
  import { ShapeElement } from './elements/ShapeElement.js';
10
11
  import { StrokeElement } from './elements/StrokeElement.js';
11
12
  // Default visual constants for the measurement-annotation line (the tile itself
@@ -79,8 +80,21 @@ const SelectionBox = ({ bounds, isDragging, transform, }) => (_jsx(DraggableElem
79
80
  // since the function-call pattern works identically on native we use it
80
81
  // in both Inners for consistency. Don't add hooks here; this is a plain
81
82
  // JSX-returning helper, not a component.
82
- export const AnnotationCanvasSkia = ({ width, height, effectiveCanvas, worldTransform, viewport, resolveImageUrl, backgroundSkImages, valueFont, textFontMgr, penDrawingStroke, livePreview, shapePreview, draggingId, dragTransform, resizingId, resizeTransform, selectedId, endpointDragId, shapeEndpointDragId, liveLineP1, liveLineP2, rectDragId, liveRect, shapeResizeId, shapeResizePath, shapeResizeBox, handleRadius, handleRingWidth, customPreview, wrapContent, canvasRef, }) => {
83
- const world = (_jsxs(Group, { transform: worldTransform, children: [backgroundLayersOf(effectiveCanvas.viewport).map((layer) => (_jsx(DraggableElement, { isDragging: layer.id === draggingId || layer.id === resizingId, transform: layer.id === resizingId ? resizeTransform : dragTransform, children: _jsx(BackgroundLayerElement, { layer: layer, resolveUrl: resolveImageUrl, skImage: backgroundSkImages?.[layer.id], viewport: viewport, screenWidth: width, screenHeight: height }) }, layer.id))), effectiveCanvas.strokes.map((stroke) => (_jsx(DraggableElement, { isDragging: stroke.id === draggingId, transform: dragTransform, children: _jsx(StrokeElement, { stroke: stroke }) }, stroke.id))), effectiveCanvas.shapes.map((shape) => {
83
+ export const AnnotationCanvasSkia = ({ width, height, effectiveCanvas, worldTransform, viewport, resolveImageUrl, backgroundSkImages, valueFont, textFontMgr, penDrawingStroke, livePreview, shapePreview, draggingId, dragTransform, resizingId, resizeTransform, selectedId, endpointDragId, shapeEndpointDragId, liveLineP1, liveLineP2, rectDragId, liveRect, shapeResizeId, shapeResizePath, shapeResizeBox, handleRadius, handleRingWidth, showPlaneHandles, customPreview, wrapContent, canvasRef, }) => {
84
+ // Doc rect a SCALE-mode plane grid covers: the first background layer (the
85
+ // photo being calibrated), else the document rect. Pinned to layer 0 like
86
+ // the tile-sizing reference — adding a second image must not re-span the
87
+ // grid.
88
+ const firstLayer = backgroundLayersOf(effectiveCanvas.viewport)[0];
89
+ const planeScaleBounds = firstLayer
90
+ ? backgroundLayerDocRect(firstLayer)
91
+ : {
92
+ x: 0,
93
+ y: 0,
94
+ width: effectiveCanvas.viewport.width,
95
+ height: effectiveCanvas.viewport.height,
96
+ };
97
+ const world = (_jsxs(Group, { transform: worldTransform, children: [backgroundLayersOf(effectiveCanvas.viewport).map((layer) => (_jsx(DraggableElement, { isDragging: layer.id === draggingId || layer.id === resizingId, transform: layer.id === resizingId ? resizeTransform : dragTransform, children: _jsx(BackgroundLayerElement, { layer: layer, resolveUrl: resolveImageUrl, skImage: backgroundSkImages?.[layer.id], viewport: viewport, screenWidth: width, screenHeight: height }) }, layer.id))), (effectiveCanvas.planes ?? []).map((plane) => (_jsx(PlaneElement, { plane: plane, scaleBounds: planeScaleBounds, showHandles: showPlaneHandles === true, handleRadius: showPlaneHandles ? handleRadius : undefined, handleRingWidth: showPlaneHandles ? handleRingWidth : undefined }, plane.id))), effectiveCanvas.strokes.map((stroke) => (_jsx(DraggableElement, { isDragging: stroke.id === draggingId, transform: dragTransform, children: _jsx(StrokeElement, { stroke: stroke }) }, stroke.id))), effectiveCanvas.shapes.map((shape) => {
84
98
  // Line/arrow shapes support endpoint editing. When selected they show
85
99
  // grab handles at both ends; during an endpoint drag the line renders
86
100
  // from the live endpoints (one following the finger) — the shape twin
@@ -173,7 +187,7 @@ export const AnnotationCanvasSkia = ({ width, height, effectiveCanvas, worldTran
173
187
  const startArrow = placed.line && !isEndpointDrag && placed.lineStartCap === 'arrow'
174
188
  ? headPath(placed.line.a, placed.line.b)
175
189
  : null;
176
- const content = (_jsxs(_Fragment, { children: [_jsx(Line, { p1: p1, p2: p2, color: lineColor, style: "stroke", strokeWidth: lineWidth, strokeCap: toSkiaStrokeCap(placed.lineCap), children: placed.lineDash && (_jsx(DashPathEffect, { intervals: dashIntervals(lineWidth) })) }), startArrow && (_jsx(Path, { path: startArrow, color: lineColor, style: "fill" })), endArrow && (_jsx(Path, { path: endArrow, color: lineColor, style: "fill" })), isSelected && handleRadius != null && (_jsxs(_Fragment, { children: [_jsx(Handle, { c: p1, r: handleRadius, ringWidth: handleRingWidth }), _jsx(Handle, { c: p2, r: handleRadius, ringWidth: handleRingWidth })] }))] }));
190
+ const content = (_jsxs(_Fragment, { children: [_jsx(Line, { p1: p1, p2: p2, color: lineColor, style: "stroke", strokeWidth: lineWidth, strokeCap: toSkiaStrokeCap(placed.lineCap), children: placed.lineDash && (_jsx(DashPathEffect, { intervals: dashIntervals(lineWidth) })) }), startArrow && (_jsx(Path, { path: startArrow, color: lineColor, style: "fill" })), endArrow && (_jsx(Path, { path: endArrow, color: lineColor, style: "fill" })), isSelected && handleRadius != null && !placed.planeEdge && (_jsxs(_Fragment, { children: [_jsx(Handle, { c: p1, r: handleRadius, ringWidth: handleRingWidth }), _jsx(Handle, { c: p2, r: handleRadius, ringWidth: handleRingWidth })] }))] }));
177
191
  return isEndpointDrag ? (_jsx(Group, { children: content }, placed.id)) : (_jsx(DraggableElement, { isDragging: placed.id === draggingId, transform: dragTransform, children: content }, placed.id));
178
192
  }), (() => {
179
193
  if (!selectedId)
@@ -99,6 +99,7 @@ export interface Tool {
99
99
  shapeDraw?: ShapeDrawConfig;
100
100
  panViewport?: boolean;
101
101
  dragSelection?: DragSelectionConfig;
102
+ planeEditing?: boolean;
102
103
  onPointerDown?(event: CanvasPointerEvent, ctx: ToolContext, state: ToolState): ToolState | void;
103
104
  onPointerMove?(event: CanvasPointerEvent, ctx: ToolContext, state: ToolState): ToolState | void;
104
105
  onPointerUp?(event: CanvasPointerEvent, ctx: ToolContext, state: ToolState): ToolState | void;
@@ -0,0 +1,18 @@
1
+ import type { PlaneCalibration } from '../../../types/annotation.js';
2
+ type AnimatedNumber = number | {
3
+ value: number;
4
+ };
5
+ export interface PlaneElementProps {
6
+ plane: PlaneCalibration;
7
+ scaleBounds: {
8
+ x: number;
9
+ y: number;
10
+ width: number;
11
+ height: number;
12
+ };
13
+ showHandles: boolean;
14
+ handleRadius?: AnimatedNumber;
15
+ handleRingWidth?: AnimatedNumber;
16
+ }
17
+ export declare const PlaneElement: import("react").MemoExoticComponent<({ plane, scaleBounds, showHandles, handleRadius, handleRingWidth, }: PlaneElementProps) => import("react/jsx-runtime").JSX.Element>;
18
+ export {};
@@ -0,0 +1,115 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { Circle, Group, Path, Skia } from '@shopify/react-native-skia';
3
+ import { memo, useMemo } from 'react';
4
+ import { planeGridSegments, planeGridSegmentsForScale, resolvePlaneGridSpacingUm, } from '../planeGeometry.js';
5
+ import { FormulaColors } from '../../../theme/colors.js';
6
+ // Plane-calibration chrome colors. Deliberately NOT the selection blue
7
+ // (#0066FF — measurement chrome) so the calibration reads as its own layer of
8
+ // information: the formula purple outlines the calibrated quad / reference
9
+ // line (and inks its handles and dimension-tile lines — see planeTool), while
10
+ // the grid it casts gets its own pale pink so the two never blur.
11
+ const PLANE_COLOR = FormulaColors.purple;
12
+ const PLANE_GRID_COLOR = '#FFD1F8';
13
+ const PLANE_GRID_OPACITY = 0.9;
14
+ // Doc-space stroke widths (scale with zoom, like the drawn shapes).
15
+ const PLANE_GRID_WIDTH = 2.5;
16
+ const PLANE_OUTLINE_WIDTH = 4;
17
+ // Cross-tick half-length (doc units) marking the ends of a scale-mode
18
+ // reference line, so it reads as "a measured span", not just a line.
19
+ const REF_TICK_LEN = 18;
20
+ const segmentsPath = (segments) => {
21
+ const path = Skia.Path.Make();
22
+ for (const { a, b } of segments) {
23
+ path.moveTo(a.x, a.y);
24
+ path.lineTo(b.x, b.y);
25
+ }
26
+ return path;
27
+ };
28
+ // The calibration overlay: grid + outline (+ editing handles). Memoized like
29
+ // ShapeElement — applyOp replaces the plane object only when a setPlane op
30
+ // lands (previews included), so unrelated re-renders reuse the built paths.
31
+ // The grid respects the plane's stored gridSpacingUm (hosts stamp it at
32
+ // dimension-entry time from the unit the user typed); absent, it falls back
33
+ // to an auto "nice metric" spacing for the calibrated extent.
34
+ export const PlaneElement = memo(({ plane, scaleBounds, showHandles, handleRadius, handleRingWidth, }) => {
35
+ const gridVisible = plane.gridVisible !== false;
36
+ const gridPath = useMemo(() => {
37
+ if (!gridVisible)
38
+ return null;
39
+ const bounds = {
40
+ x: scaleBounds.x,
41
+ y: scaleBounds.y,
42
+ width: scaleBounds.width,
43
+ height: scaleBounds.height,
44
+ };
45
+ const spacing = resolvePlaneGridSpacingUm(plane, 'metric', {
46
+ width: bounds.width,
47
+ height: bounds.height,
48
+ });
49
+ if (spacing == null)
50
+ return null;
51
+ const segments = plane.mode === 'perspective'
52
+ ? planeGridSegments(plane, spacing, bounds)
53
+ : planeGridSegmentsForScale(plane, spacing, bounds);
54
+ return segments.length > 0 ? segmentsPath(segments) : null;
55
+ // Keyed on the bounds' VALUES: the caller derives the rect object fresh
56
+ // each render (backgroundLayerDocRect), so identity would thrash.
57
+ // eslint-disable-next-line react-hooks/exhaustive-deps
58
+ }, [
59
+ plane,
60
+ gridVisible,
61
+ scaleBounds.x,
62
+ scaleBounds.y,
63
+ scaleBounds.width,
64
+ scaleBounds.height,
65
+ ]);
66
+ const outlinePath = useMemo(() => {
67
+ const path = Skia.Path.Make();
68
+ if (plane.mode === 'perspective') {
69
+ const [tl, tr, br, bl] = plane.corners;
70
+ path.moveTo(tl.x, tl.y);
71
+ path.lineTo(tr.x, tr.y);
72
+ path.lineTo(br.x, br.y);
73
+ path.lineTo(bl.x, bl.y);
74
+ path.close();
75
+ return path;
76
+ }
77
+ // Scale mode: the reference line with a perpendicular tick at each end.
78
+ const { a, b } = plane.refLine;
79
+ const dx = b.x - a.x;
80
+ const dy = b.y - a.y;
81
+ const len = Math.sqrt(dx * dx + dy * dy);
82
+ path.moveTo(a.x, a.y);
83
+ path.lineTo(b.x, b.y);
84
+ if (len > 0) {
85
+ const nx = (-dy / len) * REF_TICK_LEN;
86
+ const ny = (dx / len) * REF_TICK_LEN;
87
+ path.moveTo(a.x - nx, a.y - ny);
88
+ path.lineTo(a.x + nx, a.y + ny);
89
+ path.moveTo(b.x - nx, b.y - ny);
90
+ path.lineTo(b.x + nx, b.y + ny);
91
+ }
92
+ return path;
93
+ }, [plane]);
94
+ const handlePoints = showHandles
95
+ ? plane.mode === 'perspective'
96
+ ? plane.corners
97
+ : [plane.refLine.a, plane.refLine.b]
98
+ : [];
99
+ return (_jsxs(Group, { children: [gridPath && (_jsx(Path, { path: gridPath, color: PLANE_GRID_COLOR, style: "stroke", strokeWidth: PLANE_GRID_WIDTH, opacity: PLANE_GRID_OPACITY })), _jsx(Path, { path: outlinePath, color: PLANE_COLOR, style: "stroke", strokeWidth: PLANE_OUTLINE_WIDTH, strokeJoin: "round" }), handleRadius != null &&
100
+ handlePoints.map((p, i) => (_jsxs(Group, { children: [_jsx(Circle, { c: p, r: handleRadius, color: PLANE_COLOR }), _jsx(Circle, { c: p, r: handleRadius, color: "#FFFFFF", style: "stroke", strokeWidth: handleRingWidth })] }, i)))] }));
101
+ },
102
+ // Value-compare the bounds rect: AnnotationCanvasSkia derives it fresh each
103
+ // render (backgroundLayerDocRect returns a new object), so the default
104
+ // shallow compare would re-render this element on every canvas render and
105
+ // defeat the grid-path memoization. Everything else compares by identity —
106
+ // applyOp replaces the plane object only when a setPlane op lands.
107
+ (prev, next) => prev.plane === next.plane &&
108
+ prev.showHandles === next.showHandles &&
109
+ prev.handleRadius === next.handleRadius &&
110
+ prev.handleRingWidth === next.handleRingWidth &&
111
+ prev.scaleBounds.x === next.scaleBounds.x &&
112
+ prev.scaleBounds.y === next.scaleBounds.y &&
113
+ prev.scaleBounds.width === next.scaleBounds.width &&
114
+ prev.scaleBounds.height === next.scaleBounds.height);
115
+ PlaneElement.displayName = 'PlaneElement';
@@ -0,0 +1,48 @@
1
+ import type { PlacedMeasurementRef, PlaneCalibration, PlaneEdge, PlanePerspectiveCalibration, PlaneScaleCalibration, Vec2 } from '../../types/annotation.js';
2
+ export interface Homography {
3
+ a: number;
4
+ b: number;
5
+ c: number;
6
+ d: number;
7
+ e: number;
8
+ f: number;
9
+ g: number;
10
+ h: number;
11
+ }
12
+ export declare const homographyFromQuad: (corners: readonly [Vec2, Vec2, Vec2, Vec2]) => Homography | null;
13
+ export declare const invertHomography: (m: Homography) => Homography | null;
14
+ export declare const applyHomography: (m: Homography, p: Vec2) => Vec2 | null;
15
+ export declare const isQuadConvexNonDegenerate: (corners: readonly [Vec2, Vec2, Vec2, Vec2]) => boolean;
16
+ export declare const normalizeQuadOrder: (pts: readonly Vec2[]) => [Vec2, Vec2, Vec2, Vec2] | null;
17
+ export declare const docToPlaneUm: (cal: PlanePerspectiveCalibration, p: Vec2) => Vec2 | null;
18
+ export declare const planeDistanceUm: (cal: PlaneCalibration, a: Vec2, b: Vec2) => number | null;
19
+ export declare const planeEdgeLine: (cal: PlaneCalibration, edge: PlaneEdge) => {
20
+ a: Vec2;
21
+ b: Vec2;
22
+ } | null;
23
+ export declare const planeDimensionValueUm: (cal: PlaneCalibration, edge: PlaneEdge) => number | null;
24
+ export declare const withPlaneDimensionUm: (cal: PlaneCalibration, edge: PlaneEdge, um: number) => PlaneCalibration;
25
+ export declare const planeLengthForPlaced: (planes: PlaneCalibration[] | undefined, placed: PlacedMeasurementRef) => number | null;
26
+ export declare const nicePlaneGridSpacingUm: (extentUm: number, system: "metric" | "imperial") => number;
27
+ export declare const resolvePlaneGridSpacingUm: (cal: PlaneCalibration, system: "metric" | "imperial", scaleBounds?: {
28
+ width: number;
29
+ height: number;
30
+ }) => number | null;
31
+ export declare const planeGridSegments: (cal: PlanePerspectiveCalibration, spacingUm: number, bounds: {
32
+ x: number;
33
+ y: number;
34
+ width: number;
35
+ height: number;
36
+ }) => {
37
+ a: Vec2;
38
+ b: Vec2;
39
+ }[];
40
+ export declare const planeGridSegmentsForScale: (cal: PlaneScaleCalibration, spacingUm: number, bounds: {
41
+ x: number;
42
+ y: number;
43
+ width: number;
44
+ height: number;
45
+ }) => {
46
+ a: Vec2;
47
+ b: Vec2;
48
+ }[];