@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,101 @@
1
+ import {
2
+ type ShapeSvgExporter,
3
+ type SvgNode,
4
+ } from "@land/canvas-core";
5
+ import type { JSONContent } from "@tiptap/core";
6
+ import type { RichTextDocument, TextShapeRecord } from "./textTypes";
7
+ import { getPlainTextFromRichText } from "./richTextUtils";
8
+ import {
9
+ getTextAlign,
10
+ getTextFontFamily,
11
+ getTextFontSize,
12
+ getTextLineHeight,
13
+ } from "./textStyle";
14
+ import {
15
+ getRichTextTextNodeStyle,
16
+ isRichTextTextNode,
17
+ } from "./richTextUtils";
18
+
19
+ export class TextShapeSvgExporter
20
+ implements ShapeSvgExporter<TextShapeRecord>
21
+ {
22
+ readonly type = "text" as const;
23
+
24
+ toSvg(shape: TextShapeRecord): SvgNode | null {
25
+ const text = getPlainTextFromRichText(shape.props.richText);
26
+ if (text.trim().length === 0) {
27
+ return null;
28
+ }
29
+
30
+ return {
31
+ tag: "foreignObject",
32
+ attrs: {
33
+ x: 0,
34
+ y: 0,
35
+ width: shape.props.w * shape.props.scale,
36
+ height: shape.props.h * shape.props.scale,
37
+ },
38
+ children: [
39
+ {
40
+ tag: "div",
41
+ attrs: {
42
+ xmlns: "http://www.w3.org/1999/xhtml",
43
+ dir: "auto",
44
+ style: getTextShapeExportStyle(shape),
45
+ },
46
+ children: richTextDocumentToSvgNodes(shape.props.richText),
47
+ },
48
+ ],
49
+ };
50
+ }
51
+ }
52
+
53
+ function richTextDocumentToSvgNodes(richText: RichTextDocument): SvgNode[] {
54
+ return richText.content.map((node) => richTextBlockToSvgNode(node));
55
+ }
56
+
57
+ function richTextBlockToSvgNode(node: JSONContent): SvgNode {
58
+ const children =
59
+ node.content?.map((child) => richTextNodeToSvgNode(child)) ?? [];
60
+ return {
61
+ tag: "p",
62
+ attrs: { style: "margin:0" },
63
+ children: children.length > 0 ? children : [{ tag: "br" }],
64
+ };
65
+ }
66
+
67
+ function richTextNodeToSvgNode(node: JSONContent): SvgNode {
68
+ if (isRichTextTextNode(node)) {
69
+ const style = getRichTextTextNodeStyle(node);
70
+ if (!style) {
71
+ return node.text;
72
+ }
73
+ return {
74
+ tag: "span",
75
+ attrs: { style },
76
+ children: [node.text],
77
+ };
78
+ }
79
+
80
+ return {
81
+ tag: "span",
82
+ children: node.content?.map((child) => richTextNodeToSvgNode(child)) ?? [],
83
+ };
84
+ }
85
+
86
+ function getTextShapeExportStyle(shape: TextShapeRecord): string {
87
+ return [
88
+ `color:${shape.props.color}`,
89
+ `font-family:${getTextFontFamily(shape.props.font)}`,
90
+ `font-size:${getTextFontSize(shape.props.size)}px`,
91
+ `line-height:${getTextLineHeight(shape.props.size)}px`,
92
+ `text-align:${getTextAlign(shape.props.textAlign)}`,
93
+ "white-space:pre-wrap",
94
+ "overflow-wrap:break-word",
95
+ "box-sizing:border-box",
96
+ `width:${shape.props.w}px`,
97
+ `height:${shape.props.h}px`,
98
+ `transform:scale(${shape.props.scale})`,
99
+ "transform-origin:top left",
100
+ ].join(";");
101
+ }
@@ -0,0 +1,284 @@
1
+ import {
2
+ Box,
3
+ ShapeUtil,
4
+ Vec,
5
+ getAnchoredSelectionResizeLocalBounds,
6
+ type ShapeId,
7
+ type ShapeEditEndResult,
8
+ type ShapeSelectionFrameInput,
9
+ type ShapeSelectionResizeAspectRatioLockInput,
10
+ type ShapeSelectionResizeInput,
11
+ type ShapeSelectionTranslateInput,
12
+ type ShapeUpdate,
13
+ } from "@land/canvas-core";
14
+ import {
15
+ type TextShapeAlign,
16
+ type TextShapeFont,
17
+ type TextShapeProps,
18
+ type TextShapeRecord,
19
+ type TextShapeSize,
20
+ } from "./textTypes";
21
+ import {
22
+ createRichTextFromPlainText,
23
+ getPlainTextFromRichText,
24
+ validateRichTextDocument,
25
+ } from "./richTextUtils";
26
+ import { DEFAULT_TEXT_FONT, getTextMinHeight } from "./textStyle";
27
+ import {
28
+ getTextLabelSelectionTargetId,
29
+ getTextLabelTranslateTargetId,
30
+ textLabelCanUseSelectionFrameHandles,
31
+ textLabelContributesToSelectionFrame,
32
+ } from "./textLabelHelpers";
33
+
34
+ const TEXT_FONTS: readonly TextShapeFont[] = [
35
+ "noto-sans-sc",
36
+ "noto-serif-sc",
37
+ "noto-sans-mono",
38
+ "ma-shan-zheng",
39
+ ];
40
+ const TEXT_SIZES: readonly TextShapeSize[] = ["s", "m", "l", "xl"];
41
+ const TEXT_ALIGNS: readonly TextShapeAlign[] = ["start", "middle", "end"];
42
+ const TEXT_MIN_WIDTH = 16;
43
+ const TEXT_MIN_SCALE = 0.01;
44
+
45
+ function clampNumber(value: unknown, fallback: number): number {
46
+ return typeof value === "number" && Number.isFinite(value) ? value : fallback;
47
+ }
48
+
49
+ function isTextFont(value: unknown): value is TextShapeFont {
50
+ return typeof value === "string" && TEXT_FONTS.includes(value as TextShapeFont);
51
+ }
52
+
53
+ function isTextSize(value: unknown): value is TextShapeSize {
54
+ return typeof value === "string" && TEXT_SIZES.includes(value as TextShapeSize);
55
+ }
56
+
57
+ function isTextAlign(value: unknown): value is TextShapeAlign {
58
+ return typeof value === "string" && TEXT_ALIGNS.includes(value as TextShapeAlign);
59
+ }
60
+
61
+ function getTextScale(shape: TextShapeRecord): number {
62
+ return Math.max(TEXT_MIN_SCALE, shape.props.scale);
63
+ }
64
+
65
+ function getTextVisualWidth(shape: TextShapeRecord): number {
66
+ return shape.props.w * getTextScale(shape);
67
+ }
68
+
69
+ function getTextVisualHeight(shape: TextShapeRecord): number {
70
+ return shape.props.h * getTextScale(shape);
71
+ }
72
+
73
+ function getTextVisualBounds(shape: TextShapeRecord): Box {
74
+ return new Box(shape.x, shape.y, getTextVisualWidth(shape), getTextVisualHeight(shape));
75
+ }
76
+
77
+ function getTextVisualLocalBounds(shape: TextShapeRecord): Box {
78
+ return new Box(0, 0, getTextVisualWidth(shape), getTextVisualHeight(shape));
79
+ }
80
+
81
+ function isTextWidthResizeHandle(
82
+ handleId: ShapeSelectionResizeInput["handleId"],
83
+ ): boolean {
84
+ return handleId === "left" || handleId === "right";
85
+ }
86
+
87
+ function getTextScaleDelta(
88
+ shape: TextShapeRecord,
89
+ input: ShapeSelectionResizeInput,
90
+ ): number {
91
+ const widthRatio =
92
+ input.selectionLocalBounds.w / Math.max(1, getTextVisualWidth(shape));
93
+ const heightRatio =
94
+ input.selectionLocalBounds.h / Math.max(1, getTextVisualHeight(shape));
95
+
96
+ switch (input.handleId) {
97
+ case "left":
98
+ case "right":
99
+ return widthRatio;
100
+ case "top":
101
+ case "bottom":
102
+ return heightRatio;
103
+ default:
104
+ return Math.max(widthRatio, heightRatio);
105
+ }
106
+ }
107
+
108
+ function selectionLocalPointToPagePoint(
109
+ point: Vec,
110
+ input: ShapeSelectionResizeInput,
111
+ ): Vec {
112
+ return input.selectionRotation === 0
113
+ ? point.clone()
114
+ : Vec.RotWith(
115
+ point,
116
+ input.selectionRotationCenter,
117
+ input.selectionRotation,
118
+ );
119
+ }
120
+
121
+ export class TextShapeUtil extends ShapeUtil<TextShapeRecord> {
122
+ readonly type = "text" as const;
123
+
124
+ getDefaultProps(): TextShapeProps {
125
+ return {
126
+ richText: createRichTextFromPlainText(""),
127
+ w: 16,
128
+ h: getTextMinHeight("m"),
129
+ autoSize: true,
130
+ scale: 1,
131
+ font: DEFAULT_TEXT_FONT,
132
+ size: "m",
133
+ color: "#0f172a",
134
+ textAlign: "start",
135
+ };
136
+ }
137
+
138
+ validateProps(props: unknown): TextShapeProps {
139
+ const candidate =
140
+ props && typeof props === "object"
141
+ ? (props as Partial<TextShapeProps>)
142
+ : {};
143
+ const fallback = this.getDefaultProps();
144
+
145
+ return {
146
+ richText: validateRichTextDocument(candidate.richText),
147
+ w: Math.max(1, clampNumber(candidate.w, fallback.w)),
148
+ h: Math.max(1, clampNumber(candidate.h, fallback.h)),
149
+ autoSize:
150
+ typeof candidate.autoSize === "boolean"
151
+ ? candidate.autoSize
152
+ : fallback.autoSize,
153
+ scale: Math.max(0.01, clampNumber(candidate.scale, fallback.scale)),
154
+ font: isTextFont(candidate.font) ? candidate.font : fallback.font,
155
+ size: isTextSize(candidate.size) ? candidate.size : fallback.size,
156
+ color:
157
+ typeof candidate.color === "string" ? candidate.color : fallback.color,
158
+ textAlign: isTextAlign(candidate.textAlign)
159
+ ? candidate.textAlign
160
+ : fallback.textAlign,
161
+ };
162
+ }
163
+
164
+ getBounds(shape: TextShapeRecord): Box {
165
+ return getTextVisualBounds(shape);
166
+ }
167
+
168
+ getLocalBounds(shape: TextShapeRecord): Box {
169
+ return getTextVisualLocalBounds(shape);
170
+ }
171
+
172
+ getRenderBounds(shape: TextShapeRecord): Box {
173
+ return getTextVisualLocalBounds(shape);
174
+ }
175
+
176
+ getExportBounds(shape: TextShapeRecord): Box {
177
+ return getTextVisualLocalBounds(shape);
178
+ }
179
+
180
+ canEdit(_shape: TextShapeRecord): boolean {
181
+ return true;
182
+ }
183
+
184
+ contributesToSelectionFrame(
185
+ shape: TextShapeRecord,
186
+ input: ShapeSelectionFrameInput,
187
+ ): boolean {
188
+ return (
189
+ textLabelContributesToSelectionFrame(input.editor, shape.id) ?? true
190
+ );
191
+ }
192
+
193
+ canUseSelectionFrameHandles(
194
+ shape: TextShapeRecord,
195
+ input: ShapeSelectionFrameInput,
196
+ ): boolean {
197
+ return textLabelCanUseSelectionFrameHandles(input.editor, shape.id) ?? true;
198
+ }
199
+
200
+ getSelectionTargetId(
201
+ shape: TextShapeRecord,
202
+ input: ShapeSelectionFrameInput,
203
+ ): ShapeId {
204
+ return getTextLabelSelectionTargetId(input.editor, shape.id) ?? shape.id;
205
+ }
206
+
207
+ getTranslateTargetId(
208
+ shape: TextShapeRecord,
209
+ input: ShapeSelectionTranslateInput,
210
+ ): ShapeId {
211
+ return getTextLabelTranslateTargetId(input.editor, shape.id) ?? shape.id;
212
+ }
213
+
214
+ resizeSelectionBounds(
215
+ shape: TextShapeRecord,
216
+ input: ShapeSelectionResizeInput,
217
+ ): ShapeUpdate {
218
+ const nextProps: Partial<TextShapeProps> = {};
219
+
220
+ if (isTextWidthResizeHandle(input.handleId)) {
221
+ const nextW = Math.max(
222
+ TEXT_MIN_WIDTH,
223
+ input.selectionLocalBounds.w / getTextScale(shape),
224
+ );
225
+ nextProps.w = nextW;
226
+ nextProps.autoSize = false;
227
+ } else {
228
+ nextProps.scale = Math.max(
229
+ TEXT_MIN_SCALE,
230
+ shape.props.scale * getTextScaleDelta(shape, input),
231
+ );
232
+ }
233
+
234
+ const nextScale = nextProps.scale ?? getTextScale(shape);
235
+ const nextW = nextProps.w ?? shape.props.w;
236
+ const visualWidth = nextW * nextScale;
237
+ const visualHeight = shape.props.h * nextScale;
238
+ const anchoredBounds = getAnchoredSelectionResizeLocalBounds({
239
+ handleId: input.handleId,
240
+ requestedBounds: input.selectionLocalBounds,
241
+ actualWidth: visualWidth,
242
+ actualHeight: visualHeight,
243
+ scaleX: input.scaleX,
244
+ scaleY: input.scaleY,
245
+ isResizingFromCenter: input.isResizingFromCenter,
246
+ });
247
+ const pageCenter = selectionLocalPointToPagePoint(
248
+ anchoredBounds.center,
249
+ input,
250
+ );
251
+ const flipsSingleAxis =
252
+ (input.scaleX < 0) !== (input.scaleY < 0);
253
+
254
+ return {
255
+ id: shape.id,
256
+ x: pageCenter.x - visualWidth / 2,
257
+ y: pageCenter.y - visualHeight / 2,
258
+ rotation: flipsSingleAxis
259
+ ? input.selectionRotation * 2 - shape.rotation
260
+ : shape.rotation,
261
+ scaleX: input.scaleX < 0 ? -shape.scaleX : shape.scaleX,
262
+ scaleY: input.scaleY < 0 ? -shape.scaleY : shape.scaleY,
263
+ props: nextProps,
264
+ };
265
+ }
266
+
267
+ getSelectionResizeAspectRatioLock(
268
+ _shape: TextShapeRecord,
269
+ input: ShapeSelectionResizeAspectRatioLockInput,
270
+ ): boolean | null {
271
+ if (!input.isOnlySelected) {
272
+ return null;
273
+ }
274
+ return !isTextWidthResizeHandle(input.handleId);
275
+ }
276
+
277
+ onEditEnd(shape: TextShapeRecord): ShapeEditEndResult | void {
278
+ if (getPlainTextFromRichText(shape.props.richText).trim().length > 0) {
279
+ return;
280
+ }
281
+
282
+ return { deleteShapeIds: [shape.id] };
283
+ }
284
+ }
@@ -0,0 +1,228 @@
1
+ import {
2
+ StateNode,
3
+ Vec,
4
+ reparentShapeToContainingParent,
5
+ type Editor,
6
+ type PointerEditorEvent,
7
+ } from "@land/canvas-core";
8
+ import {
9
+ type TextShapeProps,
10
+ type TextShapeRecord,
11
+ } from "./textTypes";
12
+ import {
13
+ cloneTextShapeProps,
14
+ createTextShape,
15
+ getDefaultTextShapeProps,
16
+ isTextShapeRecord,
17
+ } from "./textShapeHelpers";
18
+
19
+ const TEXT_CREATE_HISTORY_MARK = "text.create";
20
+ const DRAG_CREATE_DISTANCE_THRESHOLD = 4;
21
+ const FIXED_TEXT_HORIZONTAL_THRESHOLD = 8;
22
+ const MIN_FIXED_TEXT_WIDTH = 16;
23
+
24
+ interface PointingTextInfo {
25
+ originPagePoint: Vec;
26
+ shapeId: TextShapeRecord["id"] | null;
27
+ didCreateShape: boolean;
28
+ }
29
+
30
+ export class TextTool extends StateNode {
31
+ constructor(editor: Editor) {
32
+ super(editor, "text");
33
+ this.initial = "idle";
34
+ this.addChild(new TextIdleState(editor, this));
35
+ this.addChild(new TextPointingState(editor, this));
36
+ }
37
+ }
38
+
39
+ class TextIdleState extends StateNode {
40
+ constructor(editor: Editor, parent: StateNode) {
41
+ super(editor, "idle", parent);
42
+ }
43
+
44
+ protected onEnter(): void {
45
+ this.editor.clearSelectionTransients();
46
+ }
47
+
48
+ protected onPointerDown(event: PointerEditorEvent): void {
49
+ this.transition("pointing", {
50
+ originPagePoint: event.pagePoint.clone(),
51
+ shapeId: null,
52
+ didCreateShape: false,
53
+ } satisfies PointingTextInfo);
54
+ }
55
+ }
56
+
57
+ class TextPointingState extends StateNode {
58
+ private info: PointingTextInfo | null = null;
59
+
60
+ constructor(editor: Editor, parent: StateNode) {
61
+ super(editor, "pointing", parent);
62
+ }
63
+
64
+ protected onEnter(info?: unknown): void {
65
+ this.info = info as PointingTextInfo;
66
+ this.editor.history.mark(TEXT_CREATE_HISTORY_MARK);
67
+ }
68
+
69
+ protected onPointerMove(event: PointerEditorEvent): void {
70
+ this.updateFixedWidthShape(event.pagePoint);
71
+ }
72
+
73
+ protected onPointerUp(event: PointerEditorEvent): void {
74
+ if (!this.info) {
75
+ this.transition("idle");
76
+ return;
77
+ }
78
+
79
+ const created =
80
+ this.info.shapeId !== null
81
+ ? this.editor.getShape(this.info.shapeId)
82
+ : this.createTextShape(event.pagePoint);
83
+ if (!isTextShapeRecord(created)) {
84
+ this.editor.history.bailToMark(TEXT_CREATE_HISTORY_MARK);
85
+ this.transition("idle");
86
+ return;
87
+ }
88
+ this.info.didCreateShape = true;
89
+ reparentShapeToContainingParent(this.editor, created.id, {
90
+ source: "user",
91
+ });
92
+
93
+ if (!this.editor.isToolLocked()) {
94
+ this.transition("idle");
95
+ this.editor.commands.setActiveTool("select", { source: "system" });
96
+ }
97
+
98
+ this.editor.startEditingShape(
99
+ {
100
+ shapeId: created.id,
101
+ entryMode: "focusEnd",
102
+ markId: TEXT_CREATE_HISTORY_MARK,
103
+ reuseExistingMark: true,
104
+ preserveInteraction: true,
105
+ },
106
+ "user",
107
+ );
108
+
109
+ if (this.editor.isToolLocked()) {
110
+ this.transition("idle");
111
+ }
112
+ }
113
+
114
+ protected onCancel(): void {
115
+ if (!this.info?.didCreateShape) {
116
+ this.editor.history.bailToMark(TEXT_CREATE_HISTORY_MARK);
117
+ }
118
+ this.transition("idle");
119
+ }
120
+
121
+ protected onExit(): void {
122
+ this.info = null;
123
+ }
124
+
125
+ private updateFixedWidthShape(pagePoint: Vec): void {
126
+ if (!this.info) {
127
+ return;
128
+ }
129
+
130
+ const draft = getFixedWidthTextDraft(
131
+ this.info.originPagePoint,
132
+ pagePoint,
133
+ this.getDefaultTextProps(),
134
+ );
135
+ if (!draft) {
136
+ return;
137
+ }
138
+
139
+ if (!this.info.shapeId) {
140
+ const created = this.createTextShapeAt(draft);
141
+ this.info.shapeId = created.id;
142
+ return;
143
+ }
144
+
145
+ this.editor.commands.updateShapes(
146
+ [
147
+ {
148
+ id: this.info.shapeId,
149
+ x: draft.x,
150
+ y: draft.y,
151
+ props: {
152
+ w: draft.props.w,
153
+ h: draft.props.h,
154
+ autoSize: false,
155
+ },
156
+ },
157
+ ],
158
+ { source: "user" },
159
+ );
160
+ }
161
+
162
+ private createTextShape(pagePoint: Vec): TextShapeRecord {
163
+ return this.createTextShapeAt(
164
+ getAutoSizeTextDraft(pagePoint, this.getDefaultTextProps()),
165
+ );
166
+ }
167
+
168
+ private createTextShapeAt({
169
+ x,
170
+ y,
171
+ props,
172
+ }: {
173
+ x: number;
174
+ y: number;
175
+ props: TextShapeProps;
176
+ }): TextShapeRecord {
177
+ const created = createTextShape(this.editor, { x, y, props }, "user");
178
+
179
+ this.editor.commands.setSelectedShapeIds([created.id], {
180
+ source: "system",
181
+ });
182
+ return created;
183
+ }
184
+
185
+ private getDefaultTextProps(): TextShapeProps {
186
+ return getDefaultTextShapeProps(this.editor);
187
+ }
188
+ }
189
+
190
+ function getAutoSizeTextDraft(
191
+ pagePoint: Vec,
192
+ defaults: TextShapeProps,
193
+ ): { x: number; y: number; props: TextShapeProps } {
194
+ return {
195
+ x: pagePoint.x,
196
+ y: pagePoint.y - defaults.h / 2,
197
+ props: cloneTextProps(defaults),
198
+ };
199
+ }
200
+
201
+ function getFixedWidthTextDraft(
202
+ originPagePoint: Vec,
203
+ pagePoint: Vec,
204
+ defaults: TextShapeProps,
205
+ ): { x: number; y: number; props: TextShapeProps } | null {
206
+ const horizontalDistance = Math.abs(pagePoint.x - originPagePoint.x);
207
+ const dragDistance = originPagePoint.dist(pagePoint);
208
+ if (
209
+ dragDistance < DRAG_CREATE_DISTANCE_THRESHOLD ||
210
+ horizontalDistance < FIXED_TEXT_HORIZONTAL_THRESHOLD
211
+ ) {
212
+ return null;
213
+ }
214
+
215
+ return {
216
+ x: Math.min(originPagePoint.x, pagePoint.x),
217
+ y: originPagePoint.y - defaults.h / 2,
218
+ props: {
219
+ ...cloneTextProps(defaults),
220
+ w: Math.max(MIN_FIXED_TEXT_WIDTH, horizontalDistance),
221
+ autoSize: false,
222
+ },
223
+ };
224
+ }
225
+
226
+ function cloneTextProps(props: TextShapeProps): TextShapeProps {
227
+ return cloneTextShapeProps(props);
228
+ }