@yuzhouu/canvas-kit 0.1.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 (263) hide show
  1. package/README.md +145 -0
  2. package/_vendor/canvas-core/bindings/BindingUtil.ts +92 -0
  3. package/_vendor/canvas-core/bindings/BindingUtilRegistry.ts +22 -0
  4. package/_vendor/canvas-core/bindings/arrow/arrowBindingHelpers.ts +52 -0
  5. package/_vendor/canvas-core/contextMenu.ts +85 -0
  6. package/_vendor/canvas-core/editor/Editor.ts +2614 -0
  7. package/_vendor/canvas-core/editor/HistoryManager.ts +83 -0
  8. package/_vendor/canvas-core/editor/StateNode.ts +162 -0
  9. package/_vendor/canvas-core/editor/camera.ts +6 -0
  10. package/_vendor/canvas-core/editor/clipboard.ts +478 -0
  11. package/_vendor/canvas-core/editor/commands.ts +833 -0
  12. package/_vendor/canvas-core/editor/edgeScroll.ts +37 -0
  13. package/_vendor/canvas-core/editor/editorComputedHelpers.ts +257 -0
  14. package/_vendor/canvas-core/editor/editorSelectionTransforms.ts +116 -0
  15. package/_vendor/canvas-core/editor/events.ts +93 -0
  16. package/_vendor/canvas-core/editor/index.ts +12 -0
  17. package/_vendor/canvas-core/editor/interactions.ts +101 -0
  18. package/_vendor/canvas-core/editor/internal.ts +22 -0
  19. package/_vendor/canvas-core/editor/managers/BindingManager.ts +262 -0
  20. package/_vendor/canvas-core/editor/managers/CameraManager.ts +147 -0
  21. package/_vendor/canvas-core/editor/managers/ClipboardManager.ts +32 -0
  22. package/_vendor/canvas-core/editor/managers/DocumentSnapshotManager.ts +256 -0
  23. package/_vendor/canvas-core/editor/managers/InputsManager.ts +319 -0
  24. package/_vendor/canvas-core/editor/managers/InteractionTransientManager.ts +218 -0
  25. package/_vendor/canvas-core/editor/managers/QueryManager.ts +837 -0
  26. package/_vendor/canvas-core/editor/managers/RenderWindowManager.ts +347 -0
  27. package/_vendor/canvas-core/editor/managers/SelectionLayoutManager.ts +196 -0
  28. package/_vendor/canvas-core/editor/managers/SelectionManager.ts +98 -0
  29. package/_vendor/canvas-core/editor/managers/SelectionOverlayManager.ts +308 -0
  30. package/_vendor/canvas-core/editor/managers/ShapeEditingManager.ts +226 -0
  31. package/_vendor/canvas-core/editor/managers/ShapeGeometryManager.ts +162 -0
  32. package/_vendor/canvas-core/editor/managers/ShapeHierarchyManager.ts +797 -0
  33. package/_vendor/canvas-core/editor/managers/SideEffectManager.ts +83 -0
  34. package/_vendor/canvas-core/editor/managers/SnapManager.ts +433 -0
  35. package/_vendor/canvas-core/editor/managers/SnapSnapshotBuilder.ts +430 -0
  36. package/_vendor/canvas-core/editor/managers/SpatialIndexManager.ts +354 -0
  37. package/_vendor/canvas-core/editor/managers/ToolManager.ts +81 -0
  38. package/_vendor/canvas-core/editor/managers/queryState.ts +583 -0
  39. package/_vendor/canvas-core/editor/managers/selectionGeometry.ts +287 -0
  40. package/_vendor/canvas-core/editor/managers/shapeOrder.ts +350 -0
  41. package/_vendor/canvas-core/editor/managers/shapeReparenting.ts +160 -0
  42. package/_vendor/canvas-core/editor/managers/snapCalculations.ts +412 -0
  43. package/_vendor/canvas-core/editor/managers/snapGeometry.ts +202 -0
  44. package/_vendor/canvas-core/editor/managers/snapIndicators.ts +403 -0
  45. package/_vendor/canvas-core/editor/managers/snapTypes.ts +122 -0
  46. package/_vendor/canvas-core/editor/queries/shapeTransforms.ts +171 -0
  47. package/_vendor/canvas-core/editor/renderWindow.ts +439 -0
  48. package/_vendor/canvas-core/editor/resizeGeometry.ts +654 -0
  49. package/_vendor/canvas-core/editor/selection.ts +489 -0
  50. package/_vendor/canvas-core/editor/selectionHandleInteractions.ts +43 -0
  51. package/_vendor/canvas-core/editor/selectionOverlay.ts +567 -0
  52. package/_vendor/canvas-core/exports/integrity.ts +174 -0
  53. package/_vendor/canvas-core/exports/svg.ts +494 -0
  54. package/_vendor/canvas-core/geometry/Box.ts +80 -0
  55. package/_vendor/canvas-core/geometry/Ellipse2d.ts +67 -0
  56. package/_vendor/canvas-core/geometry/Geometry2d.ts +52 -0
  57. package/_vendor/canvas-core/geometry/Mat.ts +92 -0
  58. package/_vendor/canvas-core/geometry/Polyline2d.ts +63 -0
  59. package/_vendor/canvas-core/geometry/Rectangle2d.ts +64 -0
  60. package/_vendor/canvas-core/geometry/Vec.ts +123 -0
  61. package/_vendor/canvas-core/geometry/index.ts +13 -0
  62. package/_vendor/canvas-core/geometry/segments.ts +65 -0
  63. package/_vendor/canvas-core/index.ts +391 -0
  64. package/_vendor/canvas-core/plugins/index.ts +5 -0
  65. package/_vendor/canvas-core/plugins/types.ts +53 -0
  66. package/_vendor/canvas-core/reactivity.ts +16 -0
  67. package/_vendor/canvas-core/schema/records.ts +221 -0
  68. package/_vendor/canvas-core/schema/snapshots.ts +527 -0
  69. package/_vendor/canvas-core/shapes/ShapeUtil.ts +516 -0
  70. package/_vendor/canvas-core/shapes/ShapeUtilRegistry.ts +26 -0
  71. package/_vendor/canvas-core/shapes/capabilities.ts +67 -0
  72. package/_vendor/canvas-core/shapes/types.ts +20 -0
  73. package/_vendor/canvas-core/toolbar.ts +148 -0
  74. package/_vendor/canvas-core/tools/hand/HandTool.ts +75 -0
  75. package/_vendor/canvas-core/tools/index.ts +2 -0
  76. package/_vendor/canvas-core/tools/select/SelectTool.ts +33 -0
  77. package/_vendor/canvas-core/tools/select/selectContainment.ts +104 -0
  78. package/_vendor/canvas-core/tools/select/selectToolHelpers.ts +801 -0
  79. package/_vendor/canvas-core/tools/select/states/BrushingState.ts +145 -0
  80. package/_vendor/canvas-core/tools/select/states/DraggingSelectionHandleInteractionState.ts +116 -0
  81. package/_vendor/canvas-core/tools/select/states/IdleState.ts +159 -0
  82. package/_vendor/canvas-core/tools/select/states/PointingCanvasState.ts +41 -0
  83. package/_vendor/canvas-core/tools/select/states/PointingResizeHandleState.ts +78 -0
  84. package/_vendor/canvas-core/tools/select/states/PointingRotateHandleState.ts +64 -0
  85. package/_vendor/canvas-core/tools/select/states/PointingSelectionHandleInteractionState.ts +34 -0
  86. package/_vendor/canvas-core/tools/select/states/PointingShapeState.ts +140 -0
  87. package/_vendor/canvas-core/tools/select/states/ResizingState.ts +171 -0
  88. package/_vendor/canvas-core/tools/select/states/RotatingState.ts +124 -0
  89. package/_vendor/canvas-core/tools/select/states/SelectStateNode.ts +128 -0
  90. package/_vendor/canvas-core/tools/select/states/TranslatingState.ts +139 -0
  91. package/_vendor/canvas-core/tools/select/states/index.ts +11 -0
  92. package/_vendor/canvas-plugin-api/feature.ts +204 -0
  93. package/_vendor/canvas-plugin-api/index.ts +142 -0
  94. package/_vendor/canvas-product-react/api/apiRegistry.ts +35 -0
  95. package/_vendor/canvas-product-react/api/canvasProductApi.ts +422 -0
  96. package/_vendor/canvas-product-react/api/index.ts +31 -0
  97. package/_vendor/canvas-product-react/capabilities/clipboard/clipboardService.ts +36 -0
  98. package/_vendor/canvas-product-react/capabilities/clipboard/systemClipboard.ts +245 -0
  99. package/_vendor/canvas-product-react/capabilities/context-menu/systemContextMenu.ts +396 -0
  100. package/_vendor/canvas-product-react/capabilities/context-menu/useContextMenuSurface.ts +59 -0
  101. package/_vendor/canvas-product-react/capabilities/export/useSvgExport.ts +156 -0
  102. package/_vendor/canvas-product-react/capabilities/history/useHistoryControls.ts +23 -0
  103. package/_vendor/canvas-product-react/capabilities/pages/usePageControls.ts +41 -0
  104. package/_vendor/canvas-product-react/capabilities/toolbar/systemToolbar.ts +490 -0
  105. package/_vendor/canvas-product-react/capabilities/toolbar/useToolbarSurfaces.ts +92 -0
  106. package/_vendor/canvas-product-react/capabilities/zoom/useZoomControls.ts +27 -0
  107. package/_vendor/canvas-product-react/index.ts +84 -0
  108. package/_vendor/canvas-product-react/persistence/index.ts +18 -0
  109. package/_vendor/canvas-product-react/persistence/localSnapshotPersistence.ts +294 -0
  110. package/_vendor/canvas-product-react/runtime/CanvasProductRoot.tsx +250 -0
  111. package/_vendor/canvas-product-react/runtime/productDefinition.ts +72 -0
  112. package/_vendor/canvas-product-react/runtime/productServices.ts +96 -0
  113. package/_vendor/canvas-react/components/CanvasView.tsx +796 -0
  114. package/_vendor/canvas-react/components/OverlayLayer.tsx +879 -0
  115. package/_vendor/canvas-react/components/PageSpace.tsx +34 -0
  116. package/_vendor/canvas-react/components/ShapeLayer.tsx +428 -0
  117. package/_vendor/canvas-react/components/index.ts +4 -0
  118. package/_vendor/canvas-react/hooks/useEditor.ts +11 -0
  119. package/_vendor/canvas-react/hooks/useValue.ts +14 -0
  120. package/_vendor/canvas-react/host/CanvasRendererRegistryContext.tsx +14 -0
  121. package/_vendor/canvas-react/host/contextMenuRegistry.ts +61 -0
  122. package/_vendor/canvas-react/host/domEventGuards.ts +61 -0
  123. package/_vendor/canvas-react/host/index.ts +38 -0
  124. package/_vendor/canvas-react/host/pluginHost.ts +635 -0
  125. package/_vendor/canvas-react/host/rendererRegistry.tsx +58 -0
  126. package/_vendor/canvas-react/host/toolbarRegistry.ts +100 -0
  127. package/_vendor/canvas-react/index.ts +3 -0
  128. package/_vendor/plugin-arrow/ArrowBindingUtil.ts +239 -0
  129. package/_vendor/plugin-arrow/ArrowShapeUtil.ts +311 -0
  130. package/_vendor/plugin-arrow/ArrowTool.ts +208 -0
  131. package/_vendor/plugin-arrow/arrowGeometry.ts +441 -0
  132. package/_vendor/plugin-arrow/arrowTerminalBindingResolver.ts +253 -0
  133. package/_vendor/plugin-arrow/arrowTerminalCommands.ts +230 -0
  134. package/_vendor/plugin-arrow/canvas.tsx +92 -0
  135. package/_vendor/plugin-arrow/core.ts +409 -0
  136. package/_vendor/plugin-arrow/feature.ts +20 -0
  137. package/_vendor/plugin-arrow/index.ts +28 -0
  138. package/_vendor/plugin-arrow/selectionHandleInteractions.ts +154 -0
  139. package/_vendor/plugin-arrow/types.ts +63 -0
  140. package/_vendor/plugin-draw/DrawShapeRenderer.tsx +60 -0
  141. package/_vendor/plugin-draw/DrawShapeUtil.ts +372 -0
  142. package/_vendor/plugin-draw/DrawTool.ts +444 -0
  143. package/_vendor/plugin-draw/canvas.tsx +24 -0
  144. package/_vendor/plugin-draw/core.ts +515 -0
  145. package/_vendor/plugin-draw/drawGeometry.ts +491 -0
  146. package/_vendor/plugin-draw/drawHitTest.ts +271 -0
  147. package/_vendor/plugin-draw/drawInterpolation.ts +250 -0
  148. package/_vendor/plugin-draw/drawPath.ts +550 -0
  149. package/_vendor/plugin-draw/drawSegments.ts +106 -0
  150. package/_vendor/plugin-draw/drawSelectionTransform.ts +67 -0
  151. package/_vendor/plugin-draw/drawShapeGeometry.ts +307 -0
  152. package/_vendor/plugin-draw/drawStrokeGeometry.ts +454 -0
  153. package/_vendor/plugin-draw/feature.ts +20 -0
  154. package/_vendor/plugin-draw/index.ts +31 -0
  155. package/_vendor/plugin-draw/types.ts +36 -0
  156. package/_vendor/plugin-eraser/EraserTool.ts +297 -0
  157. package/_vendor/plugin-eraser/canvas.tsx +616 -0
  158. package/_vendor/plugin-eraser/core.ts +49 -0
  159. package/_vendor/plugin-eraser/feature.ts +20 -0
  160. package/_vendor/plugin-eraser/index.ts +4 -0
  161. package/_vendor/plugin-frame/FrameShapeUtil.ts +173 -0
  162. package/_vendor/plugin-frame/FrameTool.ts +225 -0
  163. package/_vendor/plugin-frame/canvas.tsx +75 -0
  164. package/_vendor/plugin-frame/core.ts +534 -0
  165. package/_vendor/plugin-frame/feature.ts +20 -0
  166. package/_vendor/plugin-frame/index.ts +6 -0
  167. package/_vendor/plugin-geo/GeoShapeUtil.ts +463 -0
  168. package/_vendor/plugin-geo/GeoTools.ts +247 -0
  169. package/_vendor/plugin-geo/canvas.tsx +90 -0
  170. package/_vendor/plugin-geo/core.ts +495 -0
  171. package/_vendor/plugin-geo/feature.ts +20 -0
  172. package/_vendor/plugin-geo/index.ts +7 -0
  173. package/_vendor/plugin-geo/types.ts +27 -0
  174. package/_vendor/plugin-group/GroupShapeUtil.ts +59 -0
  175. package/_vendor/plugin-group/canvas.tsx +59 -0
  176. package/_vendor/plugin-group/core.ts +40 -0
  177. package/_vendor/plugin-group/feature.ts +20 -0
  178. package/_vendor/plugin-group/index.ts +5 -0
  179. package/_vendor/plugin-image/ImageShapeUtil.ts +95 -0
  180. package/_vendor/plugin-image/canvas.tsx +212 -0
  181. package/_vendor/plugin-image/core.ts +542 -0
  182. package/_vendor/plugin-image/crop.ts +770 -0
  183. package/_vendor/plugin-image/cropInteractions.ts +350 -0
  184. package/_vendor/plugin-image/cropSession.ts +38 -0
  185. package/_vendor/plugin-image/feature.ts +20 -0
  186. package/_vendor/plugin-image/index.ts +29 -0
  187. package/_vendor/plugin-image/types.ts +55 -0
  188. package/_vendor/plugin-image-product-react/ImageImportController.tsx +143 -0
  189. package/_vendor/plugin-image-product-react/imageAssetService.ts +52 -0
  190. package/_vendor/plugin-image-product-react/imageBlobStore.ts +315 -0
  191. package/_vendor/plugin-image-product-react/imageHostActions.ts +438 -0
  192. package/_vendor/plugin-image-product-react/imageProductApi.ts +88 -0
  193. package/_vendor/plugin-image-product-react/imageToolbarHostBridge.ts +48 -0
  194. package/_vendor/plugin-image-product-react/imageToolbarRuntime.ts +21 -0
  195. package/_vendor/plugin-image-product-react/index.ts +60 -0
  196. package/_vendor/plugin-image-product-react/snapshotGuards.ts +33 -0
  197. package/_vendor/plugin-image-product-react/svgImageAssetInliner.ts +54 -0
  198. package/_vendor/plugin-image-product-react/useImageImport.ts +64 -0
  199. package/_vendor/plugin-laser/LaserTool.ts +154 -0
  200. package/_vendor/plugin-laser/canvas.tsx +645 -0
  201. package/_vendor/plugin-laser/core.ts +49 -0
  202. package/_vendor/plugin-laser/feature.ts +20 -0
  203. package/_vendor/plugin-laser/index.ts +4 -0
  204. package/_vendor/plugin-text/ArrowLabelBindingUtil.ts +336 -0
  205. package/_vendor/plugin-text/GeoLabelBindingUtil.ts +391 -0
  206. package/_vendor/plugin-text/TextShapeSvgExporter.ts +101 -0
  207. package/_vendor/plugin-text/TextShapeUtil.ts +284 -0
  208. package/_vendor/plugin-text/TextTool.ts +228 -0
  209. package/_vendor/plugin-text/canvas.tsx +115 -0
  210. package/_vendor/plugin-text/core.ts +422 -0
  211. package/_vendor/plugin-text/feature.ts +20 -0
  212. package/_vendor/plugin-text/host/RichTextEditingBox.tsx +144 -0
  213. package/_vendor/plugin-text/host/RichTextToolbar.tsx +266 -0
  214. package/_vendor/plugin-text/host/TiptapTextEditor.tsx +191 -0
  215. package/_vendor/plugin-text/host/richTextRendering.tsx +49 -0
  216. package/_vendor/plugin-text/host/textDomSelection.ts +93 -0
  217. package/_vendor/plugin-text/host/textEditingBoxStyle.ts +36 -0
  218. package/_vendor/plugin-text/host/textEntryFocus.ts +307 -0
  219. package/_vendor/plugin-text/host/textFontLoader.ts +149 -0
  220. package/_vendor/plugin-text/host/textMeasurement.ts +258 -0
  221. package/_vendor/plugin-text/host/textMeasurementSync.ts +77 -0
  222. package/_vendor/plugin-text/host/textRangeSelection.ts +134 -0
  223. package/_vendor/plugin-text/host/textShapeRendererRuntime.tsx +89 -0
  224. package/_vendor/plugin-text/host/textShapeSync.ts +173 -0
  225. package/_vendor/plugin-text/host/tiptapTextEditorController.ts +148 -0
  226. package/_vendor/plugin-text/index.ts +18 -0
  227. package/_vendor/plugin-text/richTextUtils.ts +259 -0
  228. package/_vendor/plugin-text/textInteractions.ts +366 -0
  229. package/_vendor/plugin-text/textLabelHelpers.ts +235 -0
  230. package/_vendor/plugin-text/textShapeHelpers.ts +123 -0
  231. package/_vendor/plugin-text/textStyle.ts +96 -0
  232. package/_vendor/plugin-text/textTypes.ts +48 -0
  233. package/_vendor/signals/atom.ts +74 -0
  234. package/_vendor/signals/computed.ts +132 -0
  235. package/_vendor/signals/core.ts +130 -0
  236. package/_vendor/signals/effect.ts +117 -0
  237. package/_vendor/signals/index.ts +9 -0
  238. package/_vendor/signals/types.ts +17 -0
  239. package/_vendor/store/Store.ts +171 -0
  240. package/_vendor/store/diff.ts +229 -0
  241. package/_vendor/store/history.ts +177 -0
  242. package/_vendor/store/index.ts +32 -0
  243. package/_vendor/store/types.ts +37 -0
  244. package/api/canvasKitApi.d.ts +29 -0
  245. package/index.css +3 -0
  246. package/index.d.ts +16 -0
  247. package/index.mjs +1 -0
  248. package/internal-modules.d.ts +187 -0
  249. package/package.json +48 -0
  250. package/product/Shell.d.ts +19 -0
  251. package/product/chromeContext.d.ts +70 -0
  252. package/product/createCanvasKitEditor.d.ts +6 -0
  253. package/product/index.d.ts +7 -0
  254. package/product/persistence.d.ts +3 -0
  255. package/product/product.d.ts +22 -0
  256. package/product/useCanvasClipboardShortcuts.d.ts +6 -0
  257. package/product-iyBbEPIs.mjs +4 -0
  258. package/product.mjs +1 -0
  259. package/standard/extensions.d.ts +1 -0
  260. package/standard/index.d.ts +3 -0
  261. package/standard/standardApi.d.ts +91 -0
  262. package/standard-BmrxwqpJ.mjs +44 -0
  263. package/standard.mjs +1 -0
@@ -0,0 +1,463 @@
1
+ import {
2
+ Box,
3
+ ShapeUtil,
4
+ Vec,
5
+ pagePointToShapePositionedPoint,
6
+ positionedShapePointToPagePoint,
7
+ type ShapeBindingPointInput,
8
+ type ShapeHandleSnapGeometry,
9
+ type ShapeLineSegmentHitTestInput,
10
+ } from "@land/canvas-core";
11
+ import type { GeoShapeKind, GeoShapeProps, GeoShapeRecord } from "./types";
12
+
13
+ const GEO_KINDS: readonly GeoShapeKind[] = ["rectangle", "ellipse", "diamond"];
14
+ const GEO_HANDLE_SNAP_ELLIPSE_SEGMENTS = 32;
15
+
16
+ function clampNumber(value: unknown, fallback: number): number {
17
+ return typeof value === "number" && Number.isFinite(value) ? value : fallback;
18
+ }
19
+
20
+ function isGeoKind(value: unknown): value is GeoShapeKind {
21
+ return typeof value === "string" && GEO_KINDS.includes(value as GeoShapeKind);
22
+ }
23
+
24
+ function cross(a: Vec, b: Vec): number {
25
+ return a.x * b.y - a.y * b.x;
26
+ }
27
+
28
+ function distanceToSegment(point: Vec, start: Vec, end: Vec): number {
29
+ const segment = end.sub(start);
30
+ const lengthSquared = segment.len2();
31
+ if (lengthSquared === 0) {
32
+ return point.dist(start);
33
+ }
34
+
35
+ const offset = point.sub(start);
36
+ const t = Math.max(
37
+ 0,
38
+ Math.min(1, (offset.x * segment.x + offset.y * segment.y) / lengthSquared),
39
+ );
40
+ return point.dist(start.add(segment.mul(t)));
41
+ }
42
+
43
+ function distanceBetweenSegments(
44
+ firstStart: Vec,
45
+ firstEnd: Vec,
46
+ secondStart: Vec,
47
+ secondEnd: Vec,
48
+ ): number {
49
+ if (intersectSegments(firstStart, firstEnd, secondStart, secondEnd)) {
50
+ return 0;
51
+ }
52
+
53
+ return Math.min(
54
+ distanceToSegment(firstStart, secondStart, secondEnd),
55
+ distanceToSegment(firstEnd, secondStart, secondEnd),
56
+ distanceToSegment(secondStart, firstStart, firstEnd),
57
+ distanceToSegment(secondEnd, firstStart, firstEnd),
58
+ );
59
+ }
60
+
61
+ function intersectSegments(
62
+ a1: Vec,
63
+ a2: Vec,
64
+ b1: Vec,
65
+ b2: Vec,
66
+ ): { point: Vec; t: number } | null {
67
+ const r = a2.sub(a1);
68
+ const s = b2.sub(b1);
69
+ const denominator = cross(r, s);
70
+ if (Math.abs(denominator) < 1e-8) {
71
+ return null;
72
+ }
73
+
74
+ const delta = b1.sub(a1);
75
+ const t = cross(delta, s) / denominator;
76
+ const u = cross(delta, r) / denominator;
77
+ if (t < 0 || t > 1 || u < 0 || u > 1) {
78
+ return null;
79
+ }
80
+
81
+ return {
82
+ point: a1.add(r.mul(t)),
83
+ t,
84
+ };
85
+ }
86
+
87
+ function intersectPolygonBoundary(
88
+ raw: Vec,
89
+ opposite: Vec,
90
+ points: Vec[],
91
+ ): Vec | null {
92
+ let best: { point: Vec; t: number } | null = null;
93
+
94
+ for (let index = 0; index < points.length; index += 1) {
95
+ const start = points[index];
96
+ const end = points[(index + 1) % points.length] ?? points[0];
97
+ if (!start || !end) {
98
+ continue;
99
+ }
100
+
101
+ const intersection = intersectSegments(raw, opposite, start, end);
102
+ if (!intersection || intersection.t <= 1e-6) {
103
+ continue;
104
+ }
105
+
106
+ if (!best || intersection.t < best.t) {
107
+ best = intersection;
108
+ }
109
+ }
110
+
111
+ return best?.point ?? null;
112
+ }
113
+
114
+ function doesSegmentIntersectPolygon(
115
+ start: Vec,
116
+ end: Vec,
117
+ points: readonly Vec[],
118
+ margin: number,
119
+ ): boolean {
120
+ if (points.length < 3) {
121
+ return false;
122
+ }
123
+
124
+ for (let index = 0; index < points.length; index += 1) {
125
+ const edgeStart = points[index];
126
+ const edgeEnd = points[(index + 1) % points.length];
127
+ if (
128
+ edgeStart &&
129
+ edgeEnd &&
130
+ distanceBetweenSegments(start, end, edgeStart, edgeEnd) <= margin
131
+ ) {
132
+ return true;
133
+ }
134
+ }
135
+
136
+ return false;
137
+ }
138
+
139
+ function intersectEllipseBoundary(
140
+ bounds: Box,
141
+ raw: Vec,
142
+ opposite: Vec,
143
+ ): Vec | null {
144
+ const center = bounds.center;
145
+ const radiiX = bounds.w / 2;
146
+ const radiiY = bounds.h / 2;
147
+ if (radiiX <= 0 || radiiY <= 0) {
148
+ return null;
149
+ }
150
+
151
+ const origin = raw.sub(center);
152
+ const delta = opposite.sub(raw);
153
+ const dx = delta.x / radiiX;
154
+ const dy = delta.y / radiiY;
155
+ const ox = origin.x / radiiX;
156
+ const oy = origin.y / radiiY;
157
+ const a = dx * dx + dy * dy;
158
+ const b = 2 * (ox * dx + oy * dy);
159
+ const c = ox * ox + oy * oy - 1;
160
+ const discriminant = b * b - 4 * a * c;
161
+ if (a === 0 || discriminant < 0) {
162
+ return null;
163
+ }
164
+
165
+ const sqrt = Math.sqrt(discriminant);
166
+ const roots = [(-b - sqrt) / (2 * a), (-b + sqrt) / (2 * a)]
167
+ .filter((root) => root > 1e-6 && root <= 1)
168
+ .sort((left, right) => left - right);
169
+ const t = roots[0];
170
+ return t === undefined ? null : raw.add(delta.mul(t));
171
+ }
172
+
173
+ function doesSegmentIntersectEllipse(
174
+ bounds: Box,
175
+ start: Vec,
176
+ end: Vec,
177
+ margin: number,
178
+ ): boolean {
179
+ const center = bounds.center;
180
+ const radiiX = bounds.w / 2 + margin;
181
+ const radiiY = bounds.h / 2 + margin;
182
+ if (radiiX <= 0 || radiiY <= 0) {
183
+ return false;
184
+ }
185
+
186
+ const delta = end.sub(start);
187
+ const origin = start.sub(center);
188
+ const dx = delta.x / radiiX;
189
+ const dy = delta.y / radiiY;
190
+ const ox = origin.x / radiiX;
191
+ const oy = origin.y / radiiY;
192
+ const a = dx * dx + dy * dy;
193
+ const b = 2 * (ox * dx + oy * dy);
194
+ const c = ox * ox + oy * oy - 1;
195
+ if (c <= 0) {
196
+ return true;
197
+ }
198
+ const discriminant = b * b - 4 * a * c;
199
+ if (a === 0 || discriminant < 0) {
200
+ return false;
201
+ }
202
+
203
+ const sqrt = Math.sqrt(discriminant);
204
+ const first = (-b - sqrt) / (2 * a);
205
+ const second = (-b + sqrt) / (2 * a);
206
+ return (first >= 0 && first <= 1) || (second >= 0 && second <= 1);
207
+ }
208
+
209
+ function getGeoPositionedBoundaryPoint(
210
+ shape: GeoShapeRecord,
211
+ raw: Vec,
212
+ opposite: Vec,
213
+ ): Vec | null {
214
+ const bounds = new Box(shape.x, shape.y, shape.props.w, shape.props.h);
215
+ switch (shape.props.geo) {
216
+ case "ellipse":
217
+ return intersectEllipseBoundary(bounds, raw, opposite);
218
+ case "diamond":
219
+ return intersectPolygonBoundary(raw, opposite, [
220
+ new Vec(bounds.center.x, bounds.minY),
221
+ new Vec(bounds.maxX, bounds.center.y),
222
+ new Vec(bounds.center.x, bounds.maxY),
223
+ new Vec(bounds.minX, bounds.center.y),
224
+ ]);
225
+ case "rectangle":
226
+ default:
227
+ return intersectPolygonBoundary(raw, opposite, [
228
+ new Vec(bounds.minX, bounds.minY),
229
+ new Vec(bounds.maxX, bounds.minY),
230
+ new Vec(bounds.maxX, bounds.maxY),
231
+ new Vec(bounds.minX, bounds.maxY),
232
+ ]);
233
+ }
234
+ }
235
+
236
+ function getGeoLocalOutline(shape: GeoShapeRecord): Vec[] {
237
+ const bounds = new Box(0, 0, shape.props.w, shape.props.h);
238
+ switch (shape.props.geo) {
239
+ case "ellipse": {
240
+ const points: Vec[] = [];
241
+ for (let index = 0; index < GEO_HANDLE_SNAP_ELLIPSE_SEGMENTS; index += 1) {
242
+ const angle =
243
+ (index / GEO_HANDLE_SNAP_ELLIPSE_SEGMENTS) * Math.PI * 2;
244
+ points.push(
245
+ new Vec(
246
+ bounds.center.x + (bounds.w / 2) * Math.cos(angle),
247
+ bounds.center.y + (bounds.h / 2) * Math.sin(angle),
248
+ ),
249
+ );
250
+ }
251
+ return points;
252
+ }
253
+ case "diamond":
254
+ return [
255
+ new Vec(bounds.center.x, bounds.minY),
256
+ new Vec(bounds.maxX, bounds.center.y),
257
+ new Vec(bounds.center.x, bounds.maxY),
258
+ new Vec(bounds.minX, bounds.center.y),
259
+ ];
260
+ case "rectangle":
261
+ default:
262
+ return [
263
+ new Vec(bounds.minX, bounds.minY),
264
+ new Vec(bounds.maxX, bounds.minY),
265
+ new Vec(bounds.maxX, bounds.maxY),
266
+ new Vec(bounds.minX, bounds.maxY),
267
+ ];
268
+ }
269
+ }
270
+
271
+ function getGeoHandleSnapPoints(shape: GeoShapeRecord): Vec[] {
272
+ const bounds = new Box(0, 0, shape.props.w, shape.props.h);
273
+ return [
274
+ new Vec(bounds.minX, bounds.minY),
275
+ new Vec(bounds.center.x, bounds.minY),
276
+ new Vec(bounds.maxX, bounds.minY),
277
+ new Vec(bounds.minX, bounds.center.y),
278
+ bounds.center,
279
+ new Vec(bounds.maxX, bounds.center.y),
280
+ new Vec(bounds.minX, bounds.maxY),
281
+ new Vec(bounds.center.x, bounds.maxY),
282
+ new Vec(bounds.maxX, bounds.maxY),
283
+ ];
284
+ }
285
+
286
+ export class GeoShapeUtil extends ShapeUtil<GeoShapeRecord> {
287
+ readonly type = "geo" as const;
288
+
289
+ getDefaultProps(): GeoShapeProps {
290
+ return {
291
+ w: 160,
292
+ h: 108,
293
+ geo: "rectangle",
294
+ fill: "#e2e8f0",
295
+ stroke: "#334155",
296
+ strokeWidth: 2,
297
+ };
298
+ }
299
+
300
+ validateProps(props: unknown): GeoShapeProps {
301
+ const candidate =
302
+ props && typeof props === "object"
303
+ ? (props as Partial<GeoShapeProps>)
304
+ : {};
305
+ const fallback = this.getDefaultProps();
306
+ return {
307
+ w: Math.max(24, clampNumber(candidate.w, fallback.w)),
308
+ h: Math.max(24, clampNumber(candidate.h, fallback.h)),
309
+ geo: isGeoKind(candidate.geo) ? candidate.geo : fallback.geo,
310
+ fill: typeof candidate.fill === "string" ? candidate.fill : fallback.fill,
311
+ stroke:
312
+ typeof candidate.stroke === "string"
313
+ ? candidate.stroke
314
+ : fallback.stroke,
315
+ strokeWidth: Math.max(
316
+ 1,
317
+ clampNumber(candidate.strokeWidth, fallback.strokeWidth),
318
+ ),
319
+ };
320
+ }
321
+
322
+ getBounds(shape: GeoShapeRecord): Box {
323
+ return new Box(shape.x, shape.y, shape.props.w, shape.props.h);
324
+ }
325
+
326
+ getPositionedOutlineVertices(shape: GeoShapeRecord): Vec[] {
327
+ const bounds = this.getBounds(shape);
328
+
329
+ switch (shape.props.geo) {
330
+ case "ellipse": {
331
+ const cx = bounds.center.x;
332
+ const cy = bounds.center.y;
333
+ const rx = bounds.w / 2;
334
+ const ry = bounds.h / 2;
335
+ const segments = 32;
336
+ const points: Vec[] = [];
337
+
338
+ for (let i = 0; i < segments; i += 1) {
339
+ const angle = (i / segments) * Math.PI * 2;
340
+ points.push(new Vec(cx + rx * Math.cos(angle), cy + ry * Math.sin(angle)));
341
+ }
342
+
343
+ return points;
344
+ }
345
+ case "diamond":
346
+ return [
347
+ new Vec(bounds.center.x, bounds.minY),
348
+ new Vec(bounds.maxX, bounds.center.y),
349
+ new Vec(bounds.center.x, bounds.maxY),
350
+ new Vec(bounds.minX, bounds.center.y),
351
+ ];
352
+ case "rectangle":
353
+ default:
354
+ return super.getPositionedOutlineVertices(shape);
355
+ }
356
+ }
357
+
358
+ getHandleSnapGeometry(shape: GeoShapeRecord): ShapeHandleSnapGeometry {
359
+ return {
360
+ points: getGeoHandleSnapPoints(shape),
361
+ outline: getGeoLocalOutline(shape),
362
+ };
363
+ }
364
+
365
+ hitTest(shape: GeoShapeRecord, positionedPoint: Vec): boolean {
366
+ const bounds = this.getBounds(shape);
367
+
368
+ switch (shape.props.geo) {
369
+ case "ellipse": {
370
+ const rx = bounds.w / 2;
371
+ const ry = bounds.h / 2;
372
+ if (rx <= 0 || ry <= 0) {
373
+ return false;
374
+ }
375
+
376
+ const dx = positionedPoint.x - bounds.center.x;
377
+ const dy = positionedPoint.y - bounds.center.y;
378
+ return (dx * dx) / (rx * rx) + (dy * dy) / (ry * ry) <= 1;
379
+ }
380
+ case "diamond": {
381
+ const rx = bounds.w / 2;
382
+ const ry = bounds.h / 2;
383
+ if (rx <= 0 || ry <= 0) {
384
+ return false;
385
+ }
386
+
387
+ const dx = Math.abs(positionedPoint.x - bounds.center.x) / rx;
388
+ const dy = Math.abs(positionedPoint.y - bounds.center.y) / ry;
389
+ return dx + dy <= 1;
390
+ }
391
+ case "rectangle":
392
+ default:
393
+ return bounds.containsPoint(positionedPoint);
394
+ }
395
+ }
396
+
397
+ hitTestLineSegment(
398
+ shape: GeoShapeRecord,
399
+ input: ShapeLineSegmentHitTestInput,
400
+ ): boolean {
401
+ const bounds = this.getBounds(shape);
402
+
403
+ switch (shape.props.geo) {
404
+ case "ellipse":
405
+ return doesSegmentIntersectEllipse(
406
+ bounds,
407
+ input.positionedStart,
408
+ input.positionedEnd,
409
+ input.margin,
410
+ );
411
+ case "diamond": {
412
+ const points = [
413
+ new Vec(bounds.center.x, bounds.minY),
414
+ new Vec(bounds.maxX, bounds.center.y),
415
+ new Vec(bounds.center.x, bounds.maxY),
416
+ new Vec(bounds.minX, bounds.center.y),
417
+ ];
418
+ return (
419
+ this.hitTest(shape, input.positionedStart) ||
420
+ this.hitTest(shape, input.positionedEnd) ||
421
+ doesSegmentIntersectPolygon(
422
+ input.positionedStart,
423
+ input.positionedEnd,
424
+ points,
425
+ input.margin,
426
+ )
427
+ );
428
+ }
429
+ case "rectangle":
430
+ default:
431
+ return super.hitTestLineSegment(shape, input);
432
+ }
433
+ }
434
+
435
+ resolveBindingPoint(
436
+ shape: GeoShapeRecord,
437
+ input: ShapeBindingPointInput,
438
+ ): Vec {
439
+ const rawPositionedPoint = pagePointToShapePositionedPoint(
440
+ shape,
441
+ input.rawPagePoint,
442
+ this,
443
+ );
444
+ const oppositePositionedPoint = pagePointToShapePositionedPoint(
445
+ shape,
446
+ input.oppositePagePoint,
447
+ this,
448
+ );
449
+ const intersectionPositionedPoint = getGeoPositionedBoundaryPoint(
450
+ shape,
451
+ rawPositionedPoint,
452
+ oppositePositionedPoint,
453
+ );
454
+
455
+ return intersectionPositionedPoint
456
+ ? positionedShapePointToPagePoint(
457
+ shape,
458
+ intersectionPositionedPoint,
459
+ this,
460
+ )
461
+ : input.rawPagePoint;
462
+ }
463
+ }
@@ -0,0 +1,247 @@
1
+ import {
2
+ Box,
3
+ StateNode,
4
+ Vec,
5
+ reparentShapeToContainingParent,
6
+ type Editor,
7
+ type PointerEditorEvent,
8
+ type ShapeId,
9
+ } from "@land/canvas-core";
10
+ import type { GeoShapeKind, GeoShapeProps } from "./types";
11
+
12
+ const GEO_CREATE_HISTORY_MARK = "geo.create";
13
+ const MIN_SHAPE_SIZE = 1;
14
+ const DRAG_CREATE_THRESHOLD = 4;
15
+
16
+ interface CreatingGeoInfo {
17
+ originPagePoint: Vec;
18
+ currentPagePoint: Vec;
19
+ shapeId: ShapeId | null;
20
+ shiftKey: boolean;
21
+ }
22
+
23
+ function getNormalizedBounds(
24
+ origin: Vec,
25
+ current: Vec,
26
+ lockAspectRatio: boolean,
27
+ ): Box {
28
+ let deltaX = current.x - origin.x;
29
+ let deltaY = current.y - origin.y;
30
+
31
+ if (lockAspectRatio) {
32
+ const size = Math.max(Math.abs(deltaX), Math.abs(deltaY));
33
+ deltaX = (deltaX < 0 ? -1 : 1) * size;
34
+ deltaY = (deltaY < 0 ? -1 : 1) * size;
35
+ }
36
+
37
+ const end = new Vec(origin.x + deltaX, origin.y + deltaY);
38
+
39
+ return new Box(
40
+ Math.min(origin.x, end.x),
41
+ Math.min(origin.y, end.y),
42
+ Math.max(Math.abs(end.x - origin.x), MIN_SHAPE_SIZE),
43
+ Math.max(Math.abs(end.y - origin.y), MIN_SHAPE_SIZE),
44
+ );
45
+ }
46
+
47
+ function getDefaultBoundsAtPoint(point: Vec, props: GeoShapeProps): Box {
48
+ return new Box(
49
+ point.x - props.w / 2,
50
+ point.y - props.h / 2,
51
+ props.w,
52
+ props.h,
53
+ );
54
+ }
55
+
56
+ class BoxGeoTool extends StateNode {
57
+ constructor(
58
+ editor: Editor,
59
+ id: string,
60
+ private readonly geo: GeoShapeKind,
61
+ ) {
62
+ super(editor, id);
63
+ this.initial = "idle";
64
+ this.addChild(new GeoIdleState(editor, this));
65
+ this.addChild(new GeoCreatingState(editor, this));
66
+ }
67
+
68
+ getDefaultProps(): GeoShapeProps {
69
+ const props = this.editor.getDefaultShapeProps<GeoShapeProps>("geo");
70
+ return {
71
+ ...props,
72
+ geo: this.geo,
73
+ };
74
+ }
75
+ }
76
+
77
+ class GeoIdleState extends StateNode {
78
+ constructor(editor: Editor, parent: BoxGeoTool) {
79
+ super(editor, "idle", parent);
80
+ }
81
+
82
+ protected onEnter(): void {
83
+ this.editor.clearSelectionTransients();
84
+ }
85
+
86
+ protected onPointerDown(event: PointerEditorEvent): void {
87
+ this.transition("creating", {
88
+ originPagePoint: event.pagePoint.clone(),
89
+ currentPagePoint: event.pagePoint.clone(),
90
+ shapeId: null,
91
+ shiftKey: event.shiftKey,
92
+ } satisfies CreatingGeoInfo);
93
+ }
94
+ }
95
+
96
+ class GeoCreatingState extends StateNode {
97
+ private info: CreatingGeoInfo | null = null;
98
+
99
+ constructor(
100
+ editor: Editor,
101
+ private readonly tool: BoxGeoTool,
102
+ ) {
103
+ super(editor, "creating", tool);
104
+ }
105
+
106
+ protected onEnter(info?: unknown): void {
107
+ this.info = info as CreatingGeoInfo;
108
+ this.editor.history.mark(GEO_CREATE_HISTORY_MARK);
109
+ }
110
+
111
+ protected onPointerMove(event: PointerEditorEvent): void {
112
+ this.updateShape(event.pagePoint, event.shiftKey);
113
+ }
114
+
115
+ protected onPointerUp(event: PointerEditorEvent): void {
116
+ if (!this.info) {
117
+ this.transition("idle");
118
+ return;
119
+ }
120
+
121
+ this.updateShape(event.pagePoint, event.shiftKey, true);
122
+ if (this.info.shapeId) {
123
+ reparentShapeToContainingParent(this.editor, this.info.shapeId, {
124
+ source: "user",
125
+ });
126
+ }
127
+ this.editor.history.squashToMark(GEO_CREATE_HISTORY_MARK);
128
+
129
+ if (this.editor.isToolLocked()) {
130
+ this.transition("idle");
131
+ return;
132
+ }
133
+
134
+ this.editor.commands.setActiveTool("select", { source: "system" });
135
+ }
136
+
137
+ protected onCancel(): void {
138
+ this.editor.history.bailToMark(GEO_CREATE_HISTORY_MARK);
139
+ this.transition("idle");
140
+ }
141
+
142
+ protected onExit(): void {
143
+ this.info = null;
144
+ }
145
+
146
+ private updateShape(
147
+ pagePoint: Vec,
148
+ shiftKey: boolean,
149
+ allowClickCreate = false,
150
+ ): void {
151
+ if (!this.info) {
152
+ return;
153
+ }
154
+
155
+ this.info.currentPagePoint = pagePoint.clone();
156
+ this.info.shiftKey = shiftKey;
157
+
158
+ const dragDistance = this.info.originPagePoint.dist(pagePoint);
159
+
160
+ if (!this.info.shapeId && dragDistance < DRAG_CREATE_THRESHOLD) {
161
+ if (!allowClickCreate) {
162
+ return;
163
+ }
164
+
165
+ const bounds = getDefaultBoundsAtPoint(
166
+ pagePoint,
167
+ this.tool.getDefaultProps(),
168
+ );
169
+ const created = this.createShape(bounds);
170
+ this.info.shapeId = created.id;
171
+ return;
172
+ }
173
+
174
+ const bounds = getNormalizedBounds(
175
+ this.info.originPagePoint,
176
+ pagePoint,
177
+ shiftKey,
178
+ );
179
+
180
+ if (!this.info.shapeId) {
181
+ const created = this.createShape(bounds);
182
+ this.info.shapeId = created.id;
183
+ return;
184
+ }
185
+
186
+ this.editor.commands.updateShapes(
187
+ [
188
+ {
189
+ id: this.info.shapeId,
190
+ x: bounds.x,
191
+ y: bounds.y,
192
+ props: {
193
+ w: bounds.w,
194
+ h: bounds.h,
195
+ },
196
+ },
197
+ ],
198
+ { source: "user" },
199
+ );
200
+ }
201
+
202
+ private createShape(bounds: Box) {
203
+ const [created] = this.editor.commands.createShapes(
204
+ [
205
+ {
206
+ type: "geo",
207
+ x: bounds.x,
208
+ y: bounds.y,
209
+ props: {
210
+ ...this.tool.getDefaultProps(),
211
+ w: bounds.w,
212
+ h: bounds.h,
213
+ },
214
+ },
215
+ ],
216
+ { source: "user" },
217
+ );
218
+
219
+ if (!created) {
220
+ throw new Error("Failed to create geo shape");
221
+ }
222
+
223
+ this.editor.commands.setSelectedShapeIds([created.id], {
224
+ source: "system",
225
+ });
226
+
227
+ return created;
228
+ }
229
+ }
230
+
231
+ export class RectangleTool extends BoxGeoTool {
232
+ constructor(editor: Editor) {
233
+ super(editor, "rectangle", "rectangle");
234
+ }
235
+ }
236
+
237
+ export class EllipseTool extends BoxGeoTool {
238
+ constructor(editor: Editor) {
239
+ super(editor, "ellipse", "ellipse");
240
+ }
241
+ }
242
+
243
+ export class DiamondTool extends BoxGeoTool {
244
+ constructor(editor: Editor) {
245
+ super(editor, "diamond", "diamond");
246
+ }
247
+ }