@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,354 @@
1
+ import { computed, type Signal } from "@land/signals";
2
+ import { Box } from "../../geometry/Box";
3
+ import { Vec } from "../../geometry/Vec";
4
+ import type { PageId, ShapeRecord } from "../../schema/records";
5
+ import type { Editor } from "../Editor";
6
+ import { pagePointToShapePositionedPoint } from "../queries/shapeTransforms";
7
+ import type { QueryManager } from "./QueryManager";
8
+
9
+ interface SpatialIndexEntry {
10
+ shapeId: ShapeRecord["id"];
11
+ bounds: Box;
12
+ }
13
+
14
+ interface SpatialIndexBackend<TEntry> {
15
+ search(bounds: Box): TEntry[];
16
+ }
17
+
18
+ const DEFAULT_GRID_CELL_SIZE = 256;
19
+ const MIN_GRID_CELL_SIZE = 16;
20
+
21
+ function computeGridCellSize(entries: readonly { bounds: Box }[]): number {
22
+ if (entries.length === 0) {
23
+ return DEFAULT_GRID_CELL_SIZE;
24
+ }
25
+
26
+ let total = 0;
27
+ for (const entry of entries) {
28
+ total += Math.max(entry.bounds.w, entry.bounds.h);
29
+ }
30
+ const average = total / entries.length;
31
+ // Cells sized a little larger than the average shape keep most shapes inside
32
+ // one or two cells while bounding the per-query cell scan.
33
+ return Math.max(MIN_GRID_CELL_SIZE, Math.round(average) || DEFAULT_GRID_CELL_SIZE);
34
+ }
35
+
36
+ /**
37
+ * Uniform grid spatial index. Buckets entries into fixed-size cells so a bounds
38
+ * query only scans entries in overlapping cells instead of every entry. Search
39
+ * results preserve the original entry order (back-to-front z-order) that hit-test
40
+ * callers depend on.
41
+ */
42
+ class UniformGridSpatialIndex<
43
+ TEntry extends { bounds: Box },
44
+ > implements SpatialIndexBackend<TEntry> {
45
+ private readonly cellSize: number;
46
+ private readonly cells = new Map<string, TEntry[]>();
47
+ private readonly order = new Map<TEntry, number>();
48
+
49
+ constructor(private readonly entries: TEntry[]) {
50
+ this.cellSize = computeGridCellSize(entries);
51
+ for (let index = 0; index < entries.length; index += 1) {
52
+ const entry = entries[index];
53
+ if (!entry) continue;
54
+ this.order.set(entry, index);
55
+ this.insert(entry);
56
+ }
57
+ }
58
+
59
+ private cellKey(col: number, row: number): string {
60
+ return `${col}:${row}`;
61
+ }
62
+
63
+ private insert(entry: TEntry): void {
64
+ const minCol = Math.floor(entry.bounds.minX / this.cellSize);
65
+ const minRow = Math.floor(entry.bounds.minY / this.cellSize);
66
+ const maxCol = Math.floor(entry.bounds.maxX / this.cellSize);
67
+ const maxRow = Math.floor(entry.bounds.maxY / this.cellSize);
68
+
69
+ for (let row = minRow; row <= maxRow; row += 1) {
70
+ for (let col = minCol; col <= maxCol; col += 1) {
71
+ const key = this.cellKey(col, row);
72
+ const bucket = this.cells.get(key);
73
+ if (bucket) {
74
+ bucket.push(entry);
75
+ } else {
76
+ this.cells.set(key, [entry]);
77
+ }
78
+ }
79
+ }
80
+ }
81
+
82
+ search(bounds: Box): TEntry[] {
83
+ const minCol = Math.floor(bounds.minX / this.cellSize);
84
+ const minRow = Math.floor(bounds.minY / this.cellSize);
85
+ const maxCol = Math.floor(bounds.maxX / this.cellSize);
86
+ const maxRow = Math.floor(bounds.maxY / this.cellSize);
87
+ const cellCount = (maxCol - minCol + 1) * (maxRow - minRow + 1);
88
+
89
+ // When the query spans more cells than there are entries, scanning every
90
+ // entry once is cheaper than walking the grid.
91
+ if (cellCount > this.entries.length) {
92
+ return this.entries.filter((entry) => entry.bounds.intersects(bounds));
93
+ }
94
+
95
+ const seen = new Set<TEntry>();
96
+ const result: TEntry[] = [];
97
+ for (let row = minRow; row <= maxRow; row += 1) {
98
+ for (let col = minCol; col <= maxCol; col += 1) {
99
+ const bucket = this.cells.get(this.cellKey(col, row));
100
+ if (!bucket) continue;
101
+ for (const entry of bucket) {
102
+ if (seen.has(entry)) continue;
103
+ seen.add(entry);
104
+ if (entry.bounds.intersects(bounds)) {
105
+ result.push(entry);
106
+ }
107
+ }
108
+ }
109
+ }
110
+
111
+ if (result.length > 1) {
112
+ result.sort(
113
+ (a, b) => (this.order.get(a) ?? 0) - (this.order.get(b) ?? 0),
114
+ );
115
+ }
116
+ return result;
117
+ }
118
+ }
119
+
120
+ export interface SpatialQueryOptions {
121
+ pageId?: PageId;
122
+ }
123
+
124
+ export interface ShapeAtPointOptions extends SpatialQueryOptions {
125
+ filter?: (shape: ShapeRecord) => boolean;
126
+ }
127
+
128
+ export interface ShapeLineSegmentHitTestOptions extends SpatialQueryOptions {
129
+ margin?: number;
130
+ filter?: (shape: ShapeRecord) => boolean;
131
+ }
132
+
133
+ function createPointQueryBounds(point: Vec): Box {
134
+ return new Box(point.x - 0.5, point.y - 0.5, 1, 1);
135
+ }
136
+
137
+ function createLineSegmentQueryBounds(start: Vec, end: Vec, margin: number): Box {
138
+ return Box.FromPoints([start, end]).expand(Math.max(0.5, margin));
139
+ }
140
+
141
+ function getSegmentIntersectionParameter(
142
+ start: Vec,
143
+ end: Vec,
144
+ edgeStart: Vec,
145
+ edgeEnd: Vec,
146
+ ): number | null {
147
+ const segment = end.sub(start);
148
+ const edge = edgeEnd.sub(edgeStart);
149
+ const denominator = segment.x * edge.y - segment.y * edge.x;
150
+ if (Math.abs(denominator) < 1e-8) {
151
+ return null;
152
+ }
153
+
154
+ const delta = edgeStart.sub(start);
155
+ const t = (delta.x * edge.y - delta.y * edge.x) / denominator;
156
+ const u = (delta.x * segment.y - delta.y * segment.x) / denominator;
157
+
158
+ return t >= 0 && t <= 1 && u >= 0 && u <= 1 ? t : null;
159
+ }
160
+
161
+ function doesLineSegmentIntersectBox(start: Vec, end: Vec, bounds: Box): boolean {
162
+ if (bounds.containsPoint(start) || bounds.containsPoint(end)) {
163
+ return true;
164
+ }
165
+
166
+ const corners = [
167
+ new Vec(bounds.minX, bounds.minY),
168
+ new Vec(bounds.maxX, bounds.minY),
169
+ new Vec(bounds.maxX, bounds.maxY),
170
+ new Vec(bounds.minX, bounds.maxY),
171
+ ];
172
+
173
+ for (let index = 0; index < corners.length; index += 1) {
174
+ const edgeStart = corners[index];
175
+ const edgeEnd = corners[(index + 1) % corners.length];
176
+ if (
177
+ edgeStart &&
178
+ edgeEnd &&
179
+ getSegmentIntersectionParameter(start, end, edgeStart, edgeEnd) !== null
180
+ ) {
181
+ return true;
182
+ }
183
+ }
184
+
185
+ return false;
186
+ }
187
+
188
+ export class SpatialIndexManager {
189
+ private readonly pageIndexSignals = new Map<
190
+ PageId,
191
+ Signal<SpatialIndexBackend<SpatialIndexEntry>>
192
+ >();
193
+
194
+ constructor(
195
+ private readonly editor: Editor,
196
+ private readonly queries: QueryManager,
197
+ ) {}
198
+
199
+ getShapesInBounds(bounds: Box, options?: SpatialQueryOptions): ShapeRecord[] {
200
+ return this.getShapeIdsInBounds(bounds, options)
201
+ .map((shapeId) => this.editor.getShape(shapeId))
202
+ .filter((shape): shape is ShapeRecord => !!shape);
203
+ }
204
+
205
+ getShapeIdsInBounds(bounds: Box, options?: SpatialQueryOptions): ShapeRecord["id"][] {
206
+ return this.getPageIndex(options?.pageId)
207
+ .search(bounds)
208
+ .map((entry) => entry.shapeId);
209
+ }
210
+
211
+ getShapesInViewport(
212
+ bounds: Box,
213
+ options?: SpatialQueryOptions,
214
+ ): ShapeRecord[] {
215
+ return this.getShapesInBounds(bounds, options);
216
+ }
217
+
218
+ getShapeIdsInViewport(
219
+ bounds: Box,
220
+ options?: SpatialQueryOptions,
221
+ ): ShapeRecord["id"][] {
222
+ return this.getShapeIdsInBounds(bounds, options);
223
+ }
224
+
225
+ getShapeAtPoint(
226
+ point: Vec,
227
+ options?: ShapeAtPointOptions,
228
+ ): ShapeRecord | null {
229
+ return this.getShapesAtPoint(point, options)[0] ?? null;
230
+ }
231
+
232
+ getShapesAtPoint(
233
+ point: Vec,
234
+ options?: ShapeAtPointOptions,
235
+ ): ShapeRecord[] {
236
+ const candidates = this.getPageIndex(options?.pageId).search(
237
+ createPointQueryBounds(point),
238
+ );
239
+ const hits: ShapeRecord[] = [];
240
+
241
+ for (let index = candidates.length - 1; index >= 0; index -= 1) {
242
+ const candidate = candidates[index];
243
+ if (!candidate?.bounds.containsPoint(point)) {
244
+ continue;
245
+ }
246
+
247
+ const shape = this.editor.getShape(candidate.shapeId);
248
+ if (!shape) {
249
+ continue;
250
+ }
251
+ if (options?.filter && !options.filter(shape)) {
252
+ continue;
253
+ }
254
+
255
+ const util = this.editor.getShapeUtil(shape);
256
+ const positionedPoint = pagePointToShapePositionedPoint(
257
+ shape,
258
+ point,
259
+ util,
260
+ );
261
+ if (util.hitTest(shape, positionedPoint)) {
262
+ hits.push(shape);
263
+ }
264
+ }
265
+
266
+ return hits;
267
+ }
268
+
269
+ getShapesHitByLineSegment(
270
+ start: Vec,
271
+ end: Vec,
272
+ options?: ShapeLineSegmentHitTestOptions,
273
+ ): ShapeRecord[] {
274
+ const margin = options?.margin ?? 0;
275
+ const queryBounds = createLineSegmentQueryBounds(start, end, margin);
276
+ const candidates = this.getPageIndex(options?.pageId).search(queryBounds);
277
+ const hits: ShapeRecord[] = [];
278
+
279
+ for (let index = candidates.length - 1; index >= 0; index -= 1) {
280
+ const candidate = candidates[index];
281
+ if (!candidate) {
282
+ continue;
283
+ }
284
+
285
+ const clippedBounds = this.queries.getShapeClippedPageBounds(
286
+ candidate.shapeId,
287
+ );
288
+ if (
289
+ !clippedBounds ||
290
+ !doesLineSegmentIntersectBox(start, end, clippedBounds.expand(margin))
291
+ ) {
292
+ continue;
293
+ }
294
+
295
+ const shape = this.editor.getShape(candidate.shapeId);
296
+ if (!shape) {
297
+ continue;
298
+ }
299
+ if (options?.filter && !options.filter(shape)) {
300
+ continue;
301
+ }
302
+
303
+ const util = this.editor.getShapeUtil(shape);
304
+ const positionedStart = pagePointToShapePositionedPoint(shape, start, util);
305
+ const positionedEnd = pagePointToShapePositionedPoint(shape, end, util);
306
+ if (
307
+ util.hitTestLineSegment(shape, {
308
+ pageStart: start,
309
+ pageEnd: end,
310
+ positionedStart,
311
+ positionedEnd,
312
+ margin,
313
+ })
314
+ ) {
315
+ hits.push(shape);
316
+ }
317
+ }
318
+
319
+ return hits;
320
+ }
321
+
322
+ private getPageIndexSignal(
323
+ pageId: PageId,
324
+ ): Signal<SpatialIndexBackend<SpatialIndexEntry>> {
325
+ let signal = this.pageIndexSignals.get(pageId);
326
+ if (!signal) {
327
+ signal = computed(`editor.spatialIndex.page.${pageId}`, () => {
328
+ const shapeIds =
329
+ this.queries.stateSignal.get().pageShapeIdsByPage.get(pageId) ?? [];
330
+ const entries: SpatialIndexEntry[] = [];
331
+
332
+ for (const shapeId of shapeIds) {
333
+ const bounds = this.queries.getShapePageBoundsSignal(shapeId).get();
334
+ if (!bounds) {
335
+ continue;
336
+ }
337
+
338
+ entries.push({ shapeId, bounds });
339
+ }
340
+
341
+ return new UniformGridSpatialIndex(entries);
342
+ });
343
+ this.pageIndexSignals.set(pageId, signal);
344
+ }
345
+
346
+ return signal;
347
+ }
348
+
349
+ private getPageIndex(
350
+ pageId: PageId = this.editor.getCurrentPageId(),
351
+ ): SpatialIndexBackend<SpatialIndexEntry> {
352
+ return this.getPageIndexSignal(pageId).get();
353
+ }
354
+ }
@@ -0,0 +1,81 @@
1
+ import { atom } from "@land/signals";
2
+ import type { Editor } from "../Editor";
3
+ import type { CancelEditorEvent, EditorEvent } from "../events";
4
+ import type { StateNode } from "../StateNode";
5
+
6
+ type ToolFactory = (editor: Editor) => StateNode;
7
+
8
+ export interface ToolRegistrationOptions {
9
+ shortcut?: string;
10
+ }
11
+
12
+ export class ToolManager {
13
+ readonly activeToolId = atom<string>("tools.activeToolId", "select");
14
+ readonly isToolLocked = atom<boolean>("tools.isToolLocked", false);
15
+
16
+ private readonly registry = new Map<string, ToolFactory>();
17
+ private readonly shortcutToolIds = new Map<string, string>();
18
+ private activeTool: StateNode | null = null;
19
+
20
+ constructor(private readonly editor: Editor) {}
21
+
22
+ register(
23
+ id: string,
24
+ factory: ToolFactory,
25
+ options: ToolRegistrationOptions = {},
26
+ ): void {
27
+ this.registry.set(id, factory);
28
+ if (options.shortcut) {
29
+ this.shortcutToolIds.set(options.shortcut.toLowerCase(), id);
30
+ }
31
+ }
32
+
33
+ hasTool(id: string): boolean {
34
+ return this.registry.has(id);
35
+ }
36
+
37
+ getToolIdForShortcut(key: string): string | null {
38
+ return this.shortcutToolIds.get(key.toLowerCase()) ?? null;
39
+ }
40
+
41
+ setActiveTool(
42
+ id: string,
43
+ reason: CancelEditorEvent["reason"] = "tool_switch",
44
+ ): void {
45
+ const factory = this.registry.get(id);
46
+ if (!factory) {
47
+ throw new Error(`Unknown tool \"${id}\"`);
48
+ }
49
+
50
+ if (this.activeTool && this.activeToolId.get() === id) {
51
+ return;
52
+ }
53
+
54
+ if (this.activeTool) {
55
+ this.activeTool.dispatch({ type: "cancel", reason });
56
+ this.activeTool.exit({ reason });
57
+ this.editor.inputs.resetPointer();
58
+ this.editor.clearSelectionTransients();
59
+ }
60
+
61
+ this.activeToolId.set(id);
62
+ this.activeTool = factory(this.editor);
63
+ this.activeTool.enter();
64
+ }
65
+
66
+ dispatch(event: EditorEvent): void {
67
+ this.activeTool?.dispatch(event);
68
+ }
69
+
70
+ cancel(reason: CancelEditorEvent["reason"]): void {
71
+ this.activeTool?.dispatch({ type: "cancel", reason });
72
+ }
73
+
74
+ setToolLocked(locked: boolean): void {
75
+ this.isToolLocked.set(locked);
76
+ }
77
+
78
+ isActiveToolInInitialState(): boolean {
79
+ return this.activeTool?.isInInitialState() ?? true;
80
+ }
81
+ }