@plait/draw 0.1.0-next.8 → 0.28.0

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 (107) hide show
  1. package/constants/image.d.ts +1 -0
  2. package/constants/index.d.ts +1 -0
  3. package/constants/pointer.d.ts +4 -10
  4. package/engines/comment.d.ts +4 -0
  5. package/engines/cross.d.ts +4 -0
  6. package/{utils/engine → engines}/diamond.d.ts +1 -1
  7. package/{utils/engine → engines}/ellipse.d.ts +1 -1
  8. package/engines/hexagon.d.ts +4 -0
  9. package/{utils/engine → engines}/index.d.ts +1 -1
  10. package/engines/left-arrow.d.ts +4 -0
  11. package/engines/octagon.d.ts +4 -0
  12. package/engines/parallelogram.d.ts +4 -0
  13. package/engines/pentagon-arrow.d.ts +4 -0
  14. package/engines/pentagon.d.ts +4 -0
  15. package/engines/process-arrow.d.ts +4 -0
  16. package/{utils/engine → engines}/rectangle.d.ts +1 -1
  17. package/engines/right-arrow.d.ts +4 -0
  18. package/engines/round-comment.d.ts +4 -0
  19. package/{utils/engine → engines}/round-rectangle.d.ts +1 -1
  20. package/engines/star.d.ts +4 -0
  21. package/engines/trapezoid.d.ts +4 -0
  22. package/engines/triangle.d.ts +4 -0
  23. package/engines/two-way-arrow.d.ts +4 -0
  24. package/esm2022/constants/geometry.mjs +3 -3
  25. package/esm2022/constants/image.mjs +2 -0
  26. package/esm2022/constants/index.mjs +2 -1
  27. package/esm2022/constants/pointer.mjs +8 -19
  28. package/esm2022/engines/comment.mjs +57 -0
  29. package/esm2022/engines/cross.mjs +46 -0
  30. package/esm2022/engines/diamond.mjs +30 -0
  31. package/esm2022/engines/ellipse.mjs +92 -0
  32. package/esm2022/engines/hexagon.mjs +40 -0
  33. package/esm2022/engines/index.mjs +46 -0
  34. package/esm2022/engines/left-arrow.mjs +45 -0
  35. package/esm2022/engines/octagon.mjs +42 -0
  36. package/esm2022/engines/parallelogram.mjs +39 -0
  37. package/esm2022/engines/pentagon-arrow.mjs +39 -0
  38. package/esm2022/engines/pentagon.mjs +39 -0
  39. package/esm2022/engines/process-arrow.mjs +41 -0
  40. package/esm2022/engines/rectangle.mjs +26 -0
  41. package/esm2022/engines/right-arrow.mjs +45 -0
  42. package/esm2022/engines/round-comment.mjs +81 -0
  43. package/esm2022/engines/round-rectangle.mjs +59 -0
  44. package/esm2022/engines/star.mjs +45 -0
  45. package/esm2022/engines/trapezoid.mjs +40 -0
  46. package/esm2022/engines/triangle.mjs +40 -0
  47. package/esm2022/engines/two-way-arrow.mjs +48 -0
  48. package/esm2022/generators/geometry-shape.generator.mjs +7 -2
  49. package/esm2022/generators/line-active.generator.mjs +71 -22
  50. package/esm2022/generators/line.generator.mjs +2 -11
  51. package/esm2022/geometry.component.mjs +22 -7
  52. package/esm2022/image.component.mjs +70 -0
  53. package/esm2022/interfaces/geometry.mjs +15 -1
  54. package/esm2022/interfaces/image.mjs +2 -0
  55. package/esm2022/interfaces/index.mjs +8 -2
  56. package/esm2022/interfaces/line.mjs +27 -4
  57. package/esm2022/line.component.mjs +15 -6
  58. package/esm2022/plugins/with-draw-fragment.mjs +37 -7
  59. package/esm2022/plugins/with-draw.mjs +38 -10
  60. package/esm2022/plugins/with-geometry-create.mjs +15 -12
  61. package/esm2022/plugins/with-geometry-resize.mjs +28 -20
  62. package/esm2022/plugins/with-line-bound-reaction.mjs +10 -5
  63. package/esm2022/plugins/with-line-create.mjs +7 -5
  64. package/esm2022/plugins/with-line-resize.mjs +12 -4
  65. package/esm2022/transforms/geometry-text.mjs +1 -1
  66. package/esm2022/transforms/image.mjs +23 -0
  67. package/esm2022/transforms/index.mjs +4 -2
  68. package/esm2022/utils/clipboard.mjs +4 -3
  69. package/esm2022/utils/geometry.mjs +20 -6
  70. package/esm2022/utils/index.mjs +1 -1
  71. package/esm2022/utils/line-arrow.mjs +43 -18
  72. package/esm2022/utils/line.mjs +203 -52
  73. package/esm2022/utils/position/geometry.mjs +5 -4
  74. package/esm2022/utils/position/line.mjs +32 -22
  75. package/esm2022/utils/selected.mjs +5 -1
  76. package/esm2022/utils/shape.mjs +8 -0
  77. package/esm2022/utils/style/stroke.mjs +5 -2
  78. package/fesm2022/plait-draw.mjs +1450 -279
  79. package/fesm2022/plait-draw.mjs.map +1 -1
  80. package/generators/line-active.generator.d.ts +3 -0
  81. package/generators/line.generator.d.ts +1 -1
  82. package/geometry.component.d.ts +1 -1
  83. package/image.component.d.ts +20 -0
  84. package/interfaces/geometry.d.ts +20 -2
  85. package/interfaces/image.d.ts +7 -0
  86. package/interfaces/index.d.ts +5 -1
  87. package/interfaces/line.d.ts +19 -5
  88. package/line.component.d.ts +2 -1
  89. package/package.json +3 -2
  90. package/plugins/with-draw-fragment.d.ts +2 -0
  91. package/plugins/with-geometry-create.d.ts +1 -1
  92. package/styles/styles.scss +2 -2
  93. package/transforms/image.d.ts +3 -0
  94. package/transforms/index.d.ts +1 -0
  95. package/utils/geometry.d.ts +1 -0
  96. package/utils/line.d.ts +14 -6
  97. package/utils/position/geometry.d.ts +2 -1
  98. package/utils/position/line.d.ts +7 -3
  99. package/utils/selected.d.ts +2 -0
  100. package/utils/shape.d.ts +2 -0
  101. package/esm2022/utils/engine/diamond.mjs +0 -22
  102. package/esm2022/utils/engine/ellipse.mjs +0 -55
  103. package/esm2022/utils/engine/index.mjs +0 -18
  104. package/esm2022/utils/engine/parallelogram.mjs +0 -32
  105. package/esm2022/utils/engine/rectangle.mjs +0 -18
  106. package/esm2022/utils/engine/round-rectangle.mjs +0 -49
  107. package/utils/engine/parallelogram.d.ts +0 -4
@@ -1,9 +1,10 @@
1
- import { PlaitElement, ACTIVE_STROKE_WIDTH, RectangleClient, PlaitBoard, setStrokeLinecap, isPointInPolygon, getNearestPointBetweenPointAndSegments, isPointInEllipse, drawRectangle, drawRoundRectangle, isPointInRoundRectangle, idCreator, createG, SELECTION_BORDER_COLOR, SELECTION_FILL_COLOR, drawCircle, arrowPoints, createPath, drawLinearPath, distanceBetweenPointAndSegments, createMask, createRect, getElementById, findElements, getSelectedElements, Transforms, clearSelectedElement, addSelectedElement, PlaitNode, Point, isSelectionMoving, PlaitPluginElementComponent, transformPoint, toPoint, BoardTransforms, PlaitPointerType, preventTouchMove, createForeignObject, setClipboardData, getDataFromClipboard, depthFirstRecursion, getIsRecursionFunc, getHitElements, isPolylineHitRectangle } from '@plait/core';
1
+ import { PlaitElement, ACTIVE_STROKE_WIDTH, PlaitBoard, setStrokeLinecap, isPointInPolygon, getNearestPointBetweenPointAndSegments, RectangleClient, isPointInEllipse, drawRectangle, drawRoundRectangle, isPointInRoundRectangle, idCreator, createG, SELECTION_BORDER_COLOR, SELECTION_FILL_COLOR, drawCircle, distanceBetweenPointAndSegment, arrowPoints, createPath, drawLinearPath, rotate, getElementById, distanceBetweenPointAndPoint, distanceBetweenPointAndSegments, createMask, createRect, findElements, getSelectedElements, Transforms, clearSelectedElement, addSelectedElement, PlaitNode, Point, BOARD_TO_HOST, transformPoint, toPoint, isSelectionMoving, PlaitPluginElementComponent, BoardTransforms, PlaitPointerType, preventTouchMove, createForeignObject, setClipboardData, getDataFromClipboard, depthFirstRecursion, getIsRecursionFunc, getHitElements, isPolylineHitRectangle } from '@plait/core';
2
+ import { getRectangleByPoints, getFactorByPoints, getDirectionByVector, getOppositeDirection, getDirectionFactor, getPoints, getPointByVector, getPointOnPolyline, getDirectionByPointOfRectangle, rotateVectorAnti90, Generator, normalizeShapePoints, CommonPluginElement, ActiveGenerator, WithTextPluginKey, RESIZE_HANDLE_DIAMETER, PRIMARY_COLOR, isVirtualKey, isDelete, isSpaceHotkey, isDndMode, isDrawingMode, acceptImageTypes, buildImage, getRectangleResizeHandleRefs, ResizeHandle, withResize, isResizingByCondition, getRatioByPoint, ImageGenerator } from '@plait/common';
3
+ import { Alignment, buildText, AlignEditor, TextManage, DEFAULT_FONT_SIZE, getTextFromClipboard, getTextSize } from '@plait/text';
4
+ import { pointsOnBezierCurves } from 'points-on-curve';
2
5
  import * as i0 from '@angular/core';
3
6
  import { Component, ChangeDetectionStrategy } from '@angular/core';
4
7
  import { Subject } from 'rxjs';
5
- import { getRectangleByPoints, getFactorByPoints, Direction, getDirectionByPoint, getPoints, getPointOnPolyline, getDirectionFactor, Generator, normalizeShapePoints, CommonPluginElement, ActiveGenerator, WithTextPluginKey, RESIZE_HANDLE_DIAMETER, isVirtualKey, isDelete, isSpaceHotkey, isDndMode, isDrawingMode, getRectangleResizeHandleRefs, ResizeHandle, withResize, isResizingByCondition, getRatioByPoint } from '@plait/common';
6
- import { Alignment, buildText, AlignEditor, TextManage, DEFAULT_FONT_SIZE, getTextFromClipboard, getTextSize } from '@plait/text';
7
8
  import { isKeyHotkey } from 'is-hotkey';
8
9
  import { Node } from 'slate';
9
10
 
@@ -15,6 +16,20 @@ var GeometryShape;
15
16
  GeometryShape["roundRectangle"] = "roundRectangle";
16
17
  GeometryShape["parallelogram"] = "parallelogram";
17
18
  GeometryShape["text"] = "text";
19
+ GeometryShape["triangle"] = "triangle";
20
+ GeometryShape["leftArrow"] = "leftArrow";
21
+ GeometryShape["trapezoid"] = "trapezoid";
22
+ GeometryShape["rightArrow"] = "rightArrow";
23
+ GeometryShape["cross"] = "cross";
24
+ GeometryShape["star"] = "star";
25
+ GeometryShape["pentagon"] = "pentagon";
26
+ GeometryShape["hexagon"] = "hexagon";
27
+ GeometryShape["octagon"] = "octagon";
28
+ GeometryShape["pentagonArrow"] = "pentagonArrow";
29
+ GeometryShape["processArrow"] = "processArrow";
30
+ GeometryShape["twoWayArrow"] = "twoWayArrow";
31
+ GeometryShape["comment"] = "comment";
32
+ GeometryShape["roundComment"] = "roundComment";
18
33
  })(GeometryShape || (GeometryShape = {}));
19
34
  const PlaitGeometry = {
20
35
  getTextEditor(element) {
@@ -29,79 +44,6 @@ const PlaitGeometry = {
29
44
  }
30
45
  };
31
46
 
32
- var LineMarkerType;
33
- (function (LineMarkerType) {
34
- LineMarkerType["arrow"] = "arrow";
35
- LineMarkerType["none"] = "none";
36
- LineMarkerType["openTriangle"] = "open-triangle";
37
- LineMarkerType["solidTriangle"] = "solid-triangle";
38
- LineMarkerType["sharpArrow"] = "sharp-arrow";
39
- })(LineMarkerType || (LineMarkerType = {}));
40
- var LineShape;
41
- (function (LineShape) {
42
- LineShape["straight"] = "straight";
43
- LineShape["curve"] = "curve";
44
- LineShape["elbow"] = "elbow";
45
- })(LineShape || (LineShape = {}));
46
- var LineHandleKey;
47
- (function (LineHandleKey) {
48
- LineHandleKey["source"] = "source";
49
- LineHandleKey["target"] = "target";
50
- })(LineHandleKey || (LineHandleKey = {}));
51
- const PlaitLine = {
52
- getTextEditors(element) {
53
- const component = PlaitElement.getComponent(element);
54
- if (component) {
55
- const manage = component.textManages.find(manage => manage.isEditing);
56
- if (manage) {
57
- return [manage.componentRef.instance.editor];
58
- }
59
- else {
60
- return component.textManages.map(manage => manage.componentRef.instance.editor);
61
- }
62
- }
63
- throw new Error('can not get correctly component in get text editor');
64
- },
65
- isSourceMark(line, markType) {
66
- return line.source.marker === markType;
67
- },
68
- isTargetMark(line, markType) {
69
- return line.target.marker === markType;
70
- },
71
- isBoundElementOfSource(line, element) {
72
- return line.source.boundId === element.id;
73
- },
74
- isBoundElementOfTarget(line, element) {
75
- return line.target.boundId === element.id;
76
- }
77
- };
78
-
79
- var StrokeStyle;
80
- (function (StrokeStyle) {
81
- StrokeStyle["solid"] = "solid";
82
- StrokeStyle["dashed"] = "dashed";
83
- })(StrokeStyle || (StrokeStyle = {}));
84
-
85
- const PlaitDrawElement = {
86
- isGeometry: (value) => {
87
- return value.type === 'geometry';
88
- },
89
- isLine: (value) => {
90
- return value.type === 'line';
91
- },
92
- isText: (value) => {
93
- return value.type === 'geometry' && value.shape === GeometryShape.text;
94
- },
95
- isDrawElement: (value) => {
96
- if (PlaitDrawElement.isGeometry(value) || PlaitDrawElement.isLine(value)) {
97
- return true;
98
- }
99
- else {
100
- return false;
101
- }
102
- }
103
- };
104
-
105
47
  const ShapeDefaultSpace = {
106
48
  rectangleAndText: 4
107
49
  };
@@ -127,29 +69,22 @@ const DefaultTextProperty = {
127
69
  text: '文本'
128
70
  };
129
71
  const GeometryThreshold = {
130
- defaultTextMaxWidth: 34 * 14,
72
+ defaultTextMaxWidth: 34 * 14
131
73
  };
132
74
 
133
- var DrawPointerType;
134
- (function (DrawPointerType) {
135
- DrawPointerType["text"] = "text";
136
- DrawPointerType["rectangle"] = "rectangle";
137
- DrawPointerType["line"] = "line";
138
- DrawPointerType["diamond"] = "diamond";
139
- DrawPointerType["roundRectangle"] = "roundRectangle";
140
- DrawPointerType["parallelogram"] = "parallelogram";
141
- DrawPointerType["ellipse"] = "ellipse";
142
- })(DrawPointerType || (DrawPointerType = {}));
143
- const GeometryPointer = [
144
- DrawPointerType.rectangle,
145
- DrawPointerType.text,
146
- DrawPointerType.diamond,
147
- DrawPointerType.ellipse,
148
- DrawPointerType.parallelogram,
149
- DrawPointerType.roundRectangle
150
- ];
75
+ const getGeometryPointers = () => {
76
+ return Object.keys(GeometryShape);
77
+ };
78
+ const getLinePointers = () => {
79
+ return Object.keys(LineShape);
80
+ };
81
+
82
+ const DEFAULT_IMAGE_WIDTH = 1000;
151
83
 
152
84
  const getStrokeWidthByElement = (element) => {
85
+ if (PlaitDrawElement.isText(element)) {
86
+ return 0;
87
+ }
153
88
  const strokeWidth = element.strokeWidth || DefaultGeometryStyle.strokeWidth;
154
89
  return strokeWidth;
155
90
  };
@@ -168,6 +103,102 @@ const getStrokeStyleByElement = (element) => {
168
103
  return element.strokeStyle || StrokeStyle.solid;
169
104
  };
170
105
 
106
+ const heightRatio$1 = 3 / 4;
107
+ const CommentEngine = {
108
+ draw(board, rectangle, options) {
109
+ const points = getCommentPoints(rectangle);
110
+ const rs = PlaitBoard.getRoughSVG(board);
111
+ const polygon = rs.polygon(points, { ...options, fillStyle: 'solid' });
112
+ setStrokeLinecap(polygon, 'round');
113
+ return polygon;
114
+ },
115
+ isHit(rectangle, point) {
116
+ const parallelogramPoints = getCommentPoints(rectangle);
117
+ return isPointInPolygon(point, parallelogramPoints);
118
+ },
119
+ getCornerPoints(rectangle) {
120
+ return getCommentPoints(rectangle);
121
+ },
122
+ getNearestPoint(rectangle, point) {
123
+ return getNearestPointBetweenPointAndSegments(point, getCommentPoints(rectangle));
124
+ },
125
+ getEdgeByConnectionPoint(rectangle, pointOfRectangle) {
126
+ const corners = getCommentPoints(rectangle);
127
+ const point = RectangleClient.getConnectionPoint(rectangle, pointOfRectangle);
128
+ return getEdgeOnPolygonByPoint(corners, point);
129
+ },
130
+ getConnectorPoints(rectangle) {
131
+ return RectangleClient.getEdgeCenterPoints(rectangle);
132
+ },
133
+ getTextRectangle(element) {
134
+ const elementRectangle = getRectangleByPoints(element.points);
135
+ const strokeWidth = getStrokeWidthByElement(element);
136
+ const height = element.textHeight;
137
+ const width = elementRectangle.width - ShapeDefaultSpace.rectangleAndText * 2 - strokeWidth * 2;
138
+ return {
139
+ height,
140
+ width: width > 0 ? width : 0,
141
+ x: elementRectangle.x + ShapeDefaultSpace.rectangleAndText + strokeWidth,
142
+ y: elementRectangle.y + (elementRectangle.height * heightRatio$1 - height) / 2
143
+ };
144
+ }
145
+ };
146
+ const getCommentPoints = (rectangle) => {
147
+ return [
148
+ [rectangle.x, rectangle.y],
149
+ [rectangle.x + rectangle.width, rectangle.y],
150
+ [rectangle.x + rectangle.width, rectangle.y + rectangle.height * heightRatio$1],
151
+ [rectangle.x + (rectangle.width * 3) / 5, rectangle.y + rectangle.height * heightRatio$1],
152
+ [rectangle.x + rectangle.width / 2, rectangle.y + rectangle.height],
153
+ [rectangle.x + (rectangle.width * 2) / 5, rectangle.y + rectangle.height * heightRatio$1],
154
+ [rectangle.x, rectangle.y + rectangle.height * heightRatio$1]
155
+ ];
156
+ };
157
+
158
+ const CrossEngine = {
159
+ draw(board, rectangle, options) {
160
+ const points = getCrossPoints(rectangle);
161
+ const rs = PlaitBoard.getRoughSVG(board);
162
+ const polygon = rs.polygon(points, { ...options, fillStyle: 'solid' });
163
+ setStrokeLinecap(polygon, 'round');
164
+ return polygon;
165
+ },
166
+ isHit(rectangle, point) {
167
+ const parallelogramPoints = getCrossPoints(rectangle);
168
+ return isPointInPolygon(point, parallelogramPoints);
169
+ },
170
+ getCornerPoints(rectangle) {
171
+ return getCrossPoints(rectangle);
172
+ },
173
+ getNearestPoint(rectangle, point) {
174
+ return getNearestPointBetweenPointAndSegments(point, getCrossPoints(rectangle));
175
+ },
176
+ getEdgeByConnectionPoint(rectangle, pointOfRectangle) {
177
+ const corners = getCrossPoints(rectangle);
178
+ const point = RectangleClient.getConnectionPoint(rectangle, pointOfRectangle);
179
+ return getEdgeOnPolygonByPoint(corners, point);
180
+ },
181
+ getConnectorPoints(rectangle) {
182
+ return RectangleClient.getEdgeCenterPoints(rectangle);
183
+ }
184
+ };
185
+ const getCrossPoints = (rectangle) => {
186
+ return [
187
+ [rectangle.x + rectangle.width / 4, rectangle.y],
188
+ [rectangle.x + (rectangle.width * 3) / 4, rectangle.y],
189
+ [rectangle.x + (rectangle.width * 3) / 4, rectangle.y + rectangle.height / 4],
190
+ [rectangle.x + rectangle.width, rectangle.y + rectangle.height / 4],
191
+ [rectangle.x + rectangle.width, rectangle.y + (rectangle.height * 3) / 4],
192
+ [rectangle.x + (rectangle.width * 3) / 4, rectangle.y + (rectangle.height * 3) / 4],
193
+ [rectangle.x + (rectangle.width * 3) / 4, rectangle.y + rectangle.height],
194
+ [rectangle.x + rectangle.width / 4, rectangle.y + rectangle.height],
195
+ [rectangle.x + rectangle.width / 4, rectangle.y + (rectangle.height * 3) / 4],
196
+ [rectangle.x, rectangle.y + (rectangle.height * 3) / 4],
197
+ [rectangle.x, rectangle.y + rectangle.height / 4],
198
+ [rectangle.x + rectangle.width / 4, rectangle.y + rectangle.height / 4]
199
+ ];
200
+ };
201
+
171
202
  const DiamondEngine = {
172
203
  draw(board, rectangle, options) {
173
204
  const points = RectangleClient.getEdgeCenterPoints(rectangle);
@@ -180,9 +211,16 @@ const DiamondEngine = {
180
211
  const controlPoints = RectangleClient.getEdgeCenterPoints(rectangle);
181
212
  return isPointInPolygon(point, controlPoints);
182
213
  },
214
+ getCornerPoints(rectangle) {
215
+ return RectangleClient.getEdgeCenterPoints(rectangle);
216
+ },
183
217
  getNearestPoint(rectangle, point) {
184
- const connectorPoints = RectangleClient.getEdgeCenterPoints(rectangle);
185
- return getNearestPointBetweenPointAndSegments(point, connectorPoints);
218
+ return getNearestPointBetweenPointAndSegments(point, DiamondEngine.getCornerPoints(rectangle));
219
+ },
220
+ getEdgeByConnectionPoint(rectangle, pointOfRectangle) {
221
+ const corners = DiamondEngine.getCornerPoints(rectangle);
222
+ const point = RectangleClient.getConnectionPoint(rectangle, pointOfRectangle);
223
+ return getEdgeOnPolygonByPoint(corners, point);
186
224
  },
187
225
  getConnectorPoints(rectangle) {
188
226
  return RectangleClient.getEdgeCenterPoints(rectangle);
@@ -199,10 +237,22 @@ const EllipseEngine = {
199
237
  const centerPoint = [rectangle.x + rectangle.width / 2, rectangle.y + rectangle.height / 2];
200
238
  return isPointInEllipse(point, centerPoint, rectangle.width / 2, rectangle.height / 2);
201
239
  },
240
+ getCornerPoints(rectangle) {
241
+ return RectangleClient.getEdgeCenterPoints(rectangle);
242
+ },
202
243
  getNearestPoint(rectangle, point) {
203
244
  const centerPoint = [rectangle.x + rectangle.width / 2, rectangle.y + rectangle.height / 2];
204
245
  return getNearestPointBetweenPointAndEllipse(point, centerPoint, rectangle.width / 2, rectangle.height / 2);
205
246
  },
247
+ getTangentVectorByConnectionPoint(rectangle, pointOfRectangle) {
248
+ const connectionPoint = RectangleClient.getConnectionPoint(rectangle, pointOfRectangle);
249
+ const centerPoint = [rectangle.x + rectangle.width / 2, rectangle.y + rectangle.height / 2];
250
+ const point = [connectionPoint[0] - centerPoint[0], -(connectionPoint[1] - centerPoint[1])];
251
+ const a = rectangle.width / 2;
252
+ const b = rectangle.height / 2;
253
+ const slope = getTangentSlope(point[0], point[1], a, b);
254
+ return getVectorBySlope(point[0], point[1], slope);
255
+ },
206
256
  getConnectorPoints(rectangle) {
207
257
  return RectangleClient.getEdgeCenterPoints(rectangle);
208
258
  }
@@ -242,29 +292,182 @@ function getNearestPointBetweenPointAndEllipse(point, center, rx, ry, rotation =
242
292
  const signY = point[1] > center[1] ? 1 : -1;
243
293
  return [center[0] + a * tx * signX, center[1] + b * ty * signY];
244
294
  }
295
+ /**
296
+ * the result of slope is based on Cartesian coordinate system
297
+ * x, y are based on the position in the Cartesian coordinate system
298
+ */
299
+ function getTangentSlope(x, y, a, b) {
300
+ const k = (-b * b * x) / (a * a * y);
301
+ return k;
302
+ }
303
+ /**
304
+ * x, y are based on the position in the Cartesian coordinate system
305
+ */
306
+ function getVectorBySlope(x, y, slope) {
307
+ const deltaX = 30;
308
+ const deltaY = -slope * deltaX;
309
+ let start = [0 - deltaX, 0 - deltaY];
310
+ let end = [0 + deltaX, 0 + deltaY];
311
+ // y < 0 acts on the lower half of the x-axis, with the starting point at the top and the end point at the bottom.
312
+ if (y < 0) {
313
+ const temp = start;
314
+ start = end;
315
+ end = temp;
316
+ }
317
+ const vector = [end[0] - start[0], end[1] - start[1]];
318
+ return vector;
319
+ }
245
320
 
246
- const ParallelogramEngine = {
321
+ const HexagonEngine = {
247
322
  draw(board, rectangle, options) {
248
- const points = getParallelogramPoints(rectangle);
323
+ const points = getHexagonPoints(rectangle);
249
324
  const rs = PlaitBoard.getRoughSVG(board);
250
325
  const polygon = rs.polygon(points, { ...options, fillStyle: 'solid' });
251
326
  setStrokeLinecap(polygon, 'round');
252
327
  return polygon;
253
328
  },
254
329
  isHit(rectangle, point) {
255
- const parallelogramPoints = getParallelogramPoints(rectangle);
330
+ const parallelogramPoints = getHexagonPoints(rectangle);
256
331
  return isPointInPolygon(point, parallelogramPoints);
257
332
  },
333
+ getCornerPoints(rectangle) {
334
+ return getHexagonPoints(rectangle);
335
+ },
258
336
  getNearestPoint(rectangle, point) {
259
- const cornerPoints = getParallelogramPoints(rectangle);
337
+ return getNearestPointBetweenPointAndSegments(point, getHexagonPoints(rectangle));
338
+ },
339
+ getEdgeByConnectionPoint(rectangle, pointOfRectangle) {
340
+ const corners = getHexagonPoints(rectangle);
341
+ const point = RectangleClient.getConnectionPoint(rectangle, pointOfRectangle);
342
+ return getEdgeOnPolygonByPoint(corners, point);
343
+ },
344
+ getConnectorPoints(rectangle) {
345
+ return RectangleClient.getEdgeCenterPoints(rectangle);
346
+ }
347
+ };
348
+ const getHexagonPoints = (rectangle) => {
349
+ return [
350
+ [rectangle.x + rectangle.width / 4, rectangle.y],
351
+ [rectangle.x + (rectangle.width * 3) / 4, rectangle.y],
352
+ [rectangle.x + rectangle.width, rectangle.y + rectangle.height / 2],
353
+ [rectangle.x + (rectangle.width * 3) / 4, rectangle.y + rectangle.height],
354
+ [rectangle.x + rectangle.width / 4, rectangle.y + rectangle.height],
355
+ [rectangle.x, rectangle.y + rectangle.height / 2]
356
+ ];
357
+ };
358
+
359
+ const LeftArrowEngine = {
360
+ draw(board, rectangle, options) {
361
+ const points = getLeftArrowPoints(rectangle);
362
+ const rs = PlaitBoard.getRoughSVG(board);
363
+ const polygon = rs.polygon(points, { ...options, fillStyle: 'solid' });
364
+ setStrokeLinecap(polygon, 'round');
365
+ return polygon;
366
+ },
367
+ getCornerPoints(rectangle) {
368
+ return getLeftArrowPoints(rectangle);
369
+ },
370
+ isHit(rectangle, point) {
371
+ const points = getLeftArrowPoints(rectangle);
372
+ return isPointInPolygon(point, points);
373
+ },
374
+ getNearestPoint(rectangle, point) {
375
+ const cornerPoints = getLeftArrowPoints(rectangle);
260
376
  return getNearestPointBetweenPointAndSegments(point, cornerPoints);
261
377
  },
378
+ getEdgeByConnectionPoint(rectangle, pointOfRectangle) {
379
+ const corners = getLeftArrowPoints(rectangle);
380
+ const point = RectangleClient.getConnectionPoint(rectangle, pointOfRectangle);
381
+ return getEdgeOnPolygonByPoint(corners, point);
382
+ },
262
383
  getConnectorPoints(rectangle) {
263
- const cornerPoints = getParallelogramPoints(rectangle);
384
+ return [
385
+ [rectangle.x, rectangle.y + rectangle.height / 2],
386
+ [rectangle.x + rectangle.width, rectangle.y + rectangle.height / 2]
387
+ ];
388
+ }
389
+ };
390
+ const getLeftArrowPoints = (rectangle) => {
391
+ return [
392
+ [rectangle.x, rectangle.y + rectangle.height / 2],
393
+ [rectangle.x + rectangle.width * 0.32, rectangle.y],
394
+ [rectangle.x + rectangle.width * 0.32, rectangle.y + rectangle.height * 0.2],
395
+ [rectangle.x + rectangle.width, rectangle.y + rectangle.height * 0.2],
396
+ [rectangle.x + rectangle.width, rectangle.y + rectangle.height * 0.8],
397
+ [rectangle.x + rectangle.width * 0.32, rectangle.y + rectangle.height * 0.8],
398
+ [rectangle.x + rectangle.width * 0.32, rectangle.y + rectangle.height]
399
+ ];
400
+ };
401
+
402
+ const OctagonEngine = {
403
+ draw(board, rectangle, options) {
404
+ const points = getOctagonPoints(rectangle);
405
+ const rs = PlaitBoard.getRoughSVG(board);
406
+ const polygon = rs.polygon(points, { ...options, fillStyle: 'solid' });
407
+ setStrokeLinecap(polygon, 'round');
408
+ return polygon;
409
+ },
410
+ isHit(rectangle, point) {
411
+ const parallelogramPoints = getOctagonPoints(rectangle);
412
+ return isPointInPolygon(point, parallelogramPoints);
413
+ },
414
+ getCornerPoints(rectangle) {
415
+ return getOctagonPoints(rectangle);
416
+ },
417
+ getNearestPoint(rectangle, point) {
418
+ return getNearestPointBetweenPointAndSegments(point, getOctagonPoints(rectangle));
419
+ },
420
+ getEdgeByConnectionPoint(rectangle, pointOfRectangle) {
421
+ const corners = getOctagonPoints(rectangle);
422
+ const point = RectangleClient.getConnectionPoint(rectangle, pointOfRectangle);
423
+ return getEdgeOnPolygonByPoint(corners, point);
424
+ },
425
+ getConnectorPoints(rectangle) {
426
+ return RectangleClient.getEdgeCenterPoints(rectangle);
427
+ }
428
+ };
429
+ const getOctagonPoints = (rectangle) => {
430
+ return [
431
+ [rectangle.x + (rectangle.width * 3) / 10, rectangle.y],
432
+ [rectangle.x + (rectangle.width * 7) / 10, rectangle.y],
433
+ [rectangle.x + rectangle.width, rectangle.y + (rectangle.height * 3) / 10],
434
+ [rectangle.x + rectangle.width, rectangle.y + (rectangle.height * 7) / 10],
435
+ [rectangle.x + (rectangle.width * 7) / 10, rectangle.y + rectangle.height],
436
+ [rectangle.x + (rectangle.width * 3) / 10, rectangle.y + rectangle.height],
437
+ [rectangle.x, rectangle.y + (rectangle.height * 7) / 10],
438
+ [rectangle.x, rectangle.y + (rectangle.height * 3) / 10]
439
+ ];
440
+ };
441
+
442
+ const ParallelogramEngine = {
443
+ draw(board, rectangle, options) {
444
+ const points = getParallelogramCornerPoints(rectangle);
445
+ const rs = PlaitBoard.getRoughSVG(board);
446
+ const polygon = rs.polygon(points, { ...options, fillStyle: 'solid' });
447
+ setStrokeLinecap(polygon, 'round');
448
+ return polygon;
449
+ },
450
+ isHit(rectangle, point) {
451
+ const parallelogramPoints = getParallelogramCornerPoints(rectangle);
452
+ return isPointInPolygon(point, parallelogramPoints);
453
+ },
454
+ getCornerPoints(rectangle) {
455
+ return getParallelogramCornerPoints(rectangle);
456
+ },
457
+ getNearestPoint(rectangle, point) {
458
+ return getNearestPointBetweenPointAndSegments(point, ParallelogramEngine.getCornerPoints(rectangle));
459
+ },
460
+ getEdgeByConnectionPoint(rectangle, pointOfRectangle) {
461
+ const corners = getParallelogramCornerPoints(rectangle);
462
+ const point = RectangleClient.getConnectionPoint(rectangle, pointOfRectangle);
463
+ return getEdgeOnPolygonByPoint(corners, point);
464
+ },
465
+ getConnectorPoints(rectangle) {
466
+ const cornerPoints = getParallelogramCornerPoints(rectangle);
264
467
  return getCenterPointsOnPolygon(cornerPoints);
265
468
  }
266
469
  };
267
- const getParallelogramPoints = (rectangle) => {
470
+ const getParallelogramCornerPoints = (rectangle) => {
268
471
  return [
269
472
  [rectangle.x + rectangle.width / 4, rectangle.y],
270
473
  [rectangle.x + rectangle.width, rectangle.y],
@@ -273,6 +476,119 @@ const getParallelogramPoints = (rectangle) => {
273
476
  ];
274
477
  };
275
478
 
479
+ const PentagonEngine = {
480
+ draw(board, rectangle, options) {
481
+ const points = getPentagonPoints(rectangle);
482
+ const rs = PlaitBoard.getRoughSVG(board);
483
+ const polygon = rs.polygon(points, { ...options, fillStyle: 'solid' });
484
+ setStrokeLinecap(polygon, 'round');
485
+ return polygon;
486
+ },
487
+ isHit(rectangle, point) {
488
+ const parallelogramPoints = getPentagonPoints(rectangle);
489
+ return isPointInPolygon(point, parallelogramPoints);
490
+ },
491
+ getCornerPoints(rectangle) {
492
+ return getPentagonPoints(rectangle);
493
+ },
494
+ getNearestPoint(rectangle, point) {
495
+ return getNearestPointBetweenPointAndSegments(point, getPentagonPoints(rectangle));
496
+ },
497
+ getEdgeByConnectionPoint(rectangle, pointOfRectangle) {
498
+ const corners = getPentagonPoints(rectangle);
499
+ const point = RectangleClient.getConnectionPoint(rectangle, pointOfRectangle);
500
+ return getEdgeOnPolygonByPoint(corners, point);
501
+ },
502
+ getConnectorPoints(rectangle) {
503
+ return getPentagonPoints(rectangle);
504
+ }
505
+ };
506
+ const getPentagonPoints = (rectangle) => {
507
+ return [
508
+ [rectangle.x + rectangle.width / 2, rectangle.y],
509
+ [rectangle.x + rectangle.width, rectangle.y + (rectangle.height * 2) / 5],
510
+ [rectangle.x + (rectangle.width * 4) / 5, rectangle.y + rectangle.height],
511
+ [rectangle.x + rectangle.width / 5, rectangle.y + rectangle.height],
512
+ [rectangle.x, rectangle.y + (rectangle.height * 2) / 5]
513
+ ];
514
+ };
515
+
516
+ const PentagonArrowEngine = {
517
+ draw(board, rectangle, options) {
518
+ const points = getPentagonArrowPoints(rectangle);
519
+ const rs = PlaitBoard.getRoughSVG(board);
520
+ const polygon = rs.polygon(points, { ...options, fillStyle: 'solid' });
521
+ setStrokeLinecap(polygon, 'round');
522
+ return polygon;
523
+ },
524
+ isHit(rectangle, point) {
525
+ const parallelogramPoints = getPentagonArrowPoints(rectangle);
526
+ return isPointInPolygon(point, parallelogramPoints);
527
+ },
528
+ getCornerPoints(rectangle) {
529
+ return getPentagonArrowPoints(rectangle);
530
+ },
531
+ getNearestPoint(rectangle, point) {
532
+ return getNearestPointBetweenPointAndSegments(point, getPentagonArrowPoints(rectangle));
533
+ },
534
+ getEdgeByConnectionPoint(rectangle, pointOfRectangle) {
535
+ const corners = getPentagonArrowPoints(rectangle);
536
+ const point = RectangleClient.getConnectionPoint(rectangle, pointOfRectangle);
537
+ return getEdgeOnPolygonByPoint(corners, point);
538
+ },
539
+ getConnectorPoints(rectangle) {
540
+ return RectangleClient.getEdgeCenterPoints(rectangle);
541
+ }
542
+ };
543
+ const getPentagonArrowPoints = (rectangle) => {
544
+ return [
545
+ [rectangle.x, rectangle.y],
546
+ [rectangle.x + (rectangle.width * 3) / 5, rectangle.y],
547
+ [rectangle.x + rectangle.width, rectangle.y + rectangle.height / 2],
548
+ [rectangle.x + (rectangle.width * 3) / 5, rectangle.y + rectangle.height],
549
+ [rectangle.x, rectangle.y + rectangle.height]
550
+ ];
551
+ };
552
+
553
+ const ProcessArrowEngine = {
554
+ draw(board, rectangle, options) {
555
+ const points = getProcessArrowPoints(rectangle);
556
+ const rs = PlaitBoard.getRoughSVG(board);
557
+ const polygon = rs.polygon(points, { ...options, fillStyle: 'solid' });
558
+ setStrokeLinecap(polygon, 'round');
559
+ return polygon;
560
+ },
561
+ isHit(rectangle, point) {
562
+ const parallelogramPoints = getProcessArrowPoints(rectangle);
563
+ return isPointInPolygon(point, parallelogramPoints);
564
+ },
565
+ getCornerPoints(rectangle) {
566
+ return getProcessArrowPoints(rectangle);
567
+ },
568
+ getNearestPoint(rectangle, point) {
569
+ return getNearestPointBetweenPointAndSegments(point, getProcessArrowPoints(rectangle));
570
+ },
571
+ getEdgeByConnectionPoint(rectangle, pointOfRectangle) {
572
+ const corners = getProcessArrowPoints(rectangle);
573
+ const point = RectangleClient.getConnectionPoint(rectangle, pointOfRectangle);
574
+ return getEdgeOnPolygonByPoint(corners, point);
575
+ },
576
+ getConnectorPoints(rectangle) {
577
+ return getProcessArrowPoints(rectangle);
578
+ }
579
+ };
580
+ const getProcessArrowPoints = (rectangle) => {
581
+ const wider = rectangle.width > rectangle.height / 2;
582
+ return [
583
+ [rectangle.x, rectangle.y],
584
+ [rectangle.x + (wider ? rectangle.width - rectangle.height / 2 : 0), rectangle.y],
585
+ [rectangle.x + rectangle.width, rectangle.y + rectangle.height / 2],
586
+ [rectangle.x + (wider ? rectangle.width - rectangle.height / 2 : 0), rectangle.y + rectangle.height],
587
+ [rectangle.x, rectangle.y + rectangle.height],
588
+ [rectangle.x + (wider ? rectangle.height / 2 : rectangle.width), rectangle.y + rectangle.height / 2]
589
+ ];
590
+ };
591
+
276
592
  const RectangleEngine = {
277
593
  draw(board, rectangle, options) {
278
594
  return drawRectangle(board, rectangle, { ...options, fillStyle: 'solid' });
@@ -281,15 +597,65 @@ const RectangleEngine = {
281
597
  const rangeRectangle = RectangleClient.toRectangleClient([point, point]);
282
598
  return RectangleClient.isHit(rectangle, rangeRectangle);
283
599
  },
600
+ getCornerPoints(rectangle) {
601
+ return RectangleClient.getCornerPoints(rectangle);
602
+ },
284
603
  getNearestPoint(rectangle, point) {
285
- const cornerPoints = RectangleClient.getCornerPoints(rectangle);
286
- return getNearestPointBetweenPointAndSegments(point, cornerPoints);
604
+ return getNearestPointBetweenPointAndSegments(point, RectangleEngine.getCornerPoints(rectangle));
605
+ },
606
+ getEdgeByConnectionPoint(rectangle, pointOfRectangle) {
607
+ const corners = RectangleEngine.getCornerPoints(rectangle);
608
+ const point = RectangleClient.getConnectionPoint(rectangle, pointOfRectangle);
609
+ return getEdgeOnPolygonByPoint(corners, point);
287
610
  },
288
611
  getConnectorPoints(rectangle) {
289
612
  return RectangleClient.getEdgeCenterPoints(rectangle);
290
613
  }
291
614
  };
292
615
 
616
+ const RightArrowEngine = {
617
+ draw(board, rectangle, options) {
618
+ const points = getRightArrowPoints(rectangle);
619
+ const rs = PlaitBoard.getRoughSVG(board);
620
+ const polygon = rs.polygon(points, { ...options, fillStyle: 'solid' });
621
+ setStrokeLinecap(polygon, 'round');
622
+ return polygon;
623
+ },
624
+ getCornerPoints(rectangle) {
625
+ return getRightArrowPoints(rectangle);
626
+ },
627
+ isHit(rectangle, point) {
628
+ const points = getRightArrowPoints(rectangle);
629
+ return isPointInPolygon(point, points);
630
+ },
631
+ getNearestPoint(rectangle, point) {
632
+ const cornerPoints = getRightArrowPoints(rectangle);
633
+ return getNearestPointBetweenPointAndSegments(point, cornerPoints);
634
+ },
635
+ getEdgeByConnectionPoint(rectangle, pointOfRectangle) {
636
+ const corners = getRightArrowPoints(rectangle);
637
+ const point = RectangleClient.getConnectionPoint(rectangle, pointOfRectangle);
638
+ return getEdgeOnPolygonByPoint(corners, point);
639
+ },
640
+ getConnectorPoints(rectangle) {
641
+ return [
642
+ [rectangle.x, rectangle.y + rectangle.height / 2],
643
+ [rectangle.x + rectangle.width, rectangle.y + rectangle.height / 2]
644
+ ];
645
+ }
646
+ };
647
+ const getRightArrowPoints = (rectangle) => {
648
+ return [
649
+ [rectangle.x, rectangle.y + rectangle.height * 0.2],
650
+ [rectangle.x + rectangle.width * 0.68, rectangle.y + rectangle.height * 0.2],
651
+ [rectangle.x + rectangle.width * 0.68, rectangle.y],
652
+ [rectangle.x + rectangle.width, rectangle.y + rectangle.height / 2],
653
+ [rectangle.x + rectangle.width * 0.68, rectangle.y + rectangle.height],
654
+ [rectangle.x + rectangle.width * 0.68, rectangle.y + rectangle.height * 0.8],
655
+ [rectangle.x, rectangle.y + rectangle.height * 0.8]
656
+ ];
657
+ };
658
+
293
659
  const RoundRectangleEngine = {
294
660
  draw(board, rectangle, options) {
295
661
  return drawRoundRectangle(PlaitBoard.getRoughSVG(board), rectangle.x, rectangle.y, rectangle.x + rectangle.width, rectangle.y + rectangle.height, { ...options, fillStyle: 'solid' }, false, getRoundRectangleRadius(rectangle));
@@ -297,9 +663,17 @@ const RoundRectangleEngine = {
297
663
  isHit(rectangle, point) {
298
664
  return isPointInRoundRectangle(point, rectangle, getRoundRectangleRadius(rectangle));
299
665
  },
666
+ getCornerPoints(rectangle) {
667
+ return RectangleClient.getCornerPoints(rectangle);
668
+ },
300
669
  getNearestPoint(rectangle, point) {
301
670
  return getNearestPointBetweenPointAndRoundRectangle(point, rectangle, getRoundRectangleRadius(rectangle));
302
671
  },
672
+ getEdgeByConnectionPoint(rectangle, pointOfRectangle) {
673
+ const corners = RectangleEngine.getCornerPoints(rectangle);
674
+ const point = RectangleClient.getConnectionPoint(rectangle, pointOfRectangle);
675
+ return getEdgeOnPolygonByPoint(corners, point);
676
+ },
303
677
  getConnectorPoints(rectangle) {
304
678
  return RectangleClient.getEdgeCenterPoints(rectangle);
305
679
  }
@@ -337,18 +711,279 @@ function getNearestPointBetweenPointAndRoundRectangle(point, rectangle, radius)
337
711
  return result;
338
712
  }
339
713
 
714
+ const heightRatio = 3 / 4;
715
+ const RoundCommentEngine = {
716
+ draw(board, rectangle, options) {
717
+ const rs = PlaitBoard.getRoughSVG(board);
718
+ const x1 = rectangle.x;
719
+ const y1 = rectangle.y;
720
+ const x2 = rectangle.x + rectangle.width;
721
+ const y2 = rectangle.y + rectangle.height * heightRatio;
722
+ const radius = getRoundRectangleRadius(rectangle);
723
+ const point1 = [x1 + radius, y1];
724
+ const point2 = [x2 - radius, y1];
725
+ const point3 = [x2, y1 + radius];
726
+ const point4 = [x2, y2 - radius];
727
+ const point5 = [x2 - radius, y2];
728
+ const point6 = [x1 + radius, y2];
729
+ const point7 = [x1, y2 - radius];
730
+ const point8 = [x1, y1 + radius];
731
+ const point9 = [x1 + rectangle.width / 4, y2];
732
+ const point10 = [x1 + rectangle.width / 4, rectangle.y + rectangle.height];
733
+ const point11 = [x1 + rectangle.width / 2, y2];
734
+ return rs.path(`M${point2[0]} ${point2[1]} A ${radius} ${radius}, 0, 0, 1, ${point3[0]} ${point3[1]} L ${point4[0]} ${point4[1]} A ${radius} ${radius}, 0, 0, 1, ${point5[0]} ${point5[1]} L ${point11[0]} ${point11[1]} ${point10[0]} ${point10[1]} ${point9[0]} ${point9[1]} ${point6[0]} ${point6[1]} A ${radius} ${radius}, 0, 0, 1, ${point7[0]} ${point7[1]} L ${point8[0]} ${point8[1]} A ${radius} ${radius}, 0, 0, 1, ${point1[0]} ${point1[1]} Z`, options);
735
+ },
736
+ isHit(rectangle, point) {
737
+ const points = [
738
+ [rectangle.x + rectangle.width / 4, rectangle.y + (rectangle.height * 3) / 4],
739
+ [rectangle.x + rectangle.width / 4, rectangle.y + rectangle.height],
740
+ [rectangle.x + rectangle.width / 2, rectangle.y + (rectangle.height * 3) / 4]
741
+ ];
742
+ rectangle.height = (rectangle.height * 3) / 4;
743
+ return isPointInPolygon(point, points) || isPointInRoundRectangle(point, rectangle, getRoundRectangleRadius(rectangle));
744
+ },
745
+ getCornerPoints(rectangle) {
746
+ return getRoundCommentPoints(rectangle);
747
+ },
748
+ getNearestPoint(rectangle, point) {
749
+ return getNearestPointBetweenPointAndSegments(point, getRoundCommentPoints(rectangle));
750
+ },
751
+ getEdgeByConnectionPoint(rectangle, pointOfRectangle) {
752
+ const corners = getRoundCommentPoints(rectangle);
753
+ const point = RectangleClient.getConnectionPoint(rectangle, pointOfRectangle);
754
+ return getEdgeOnPolygonByPoint(corners, point);
755
+ },
756
+ getConnectorPoints(rectangle) {
757
+ return [
758
+ [rectangle.x + rectangle.width / 2, rectangle.y],
759
+ [rectangle.x + rectangle.width, rectangle.y + rectangle.height / 2],
760
+ [rectangle.x + rectangle.width / 2, rectangle.y + rectangle.height * heightRatio],
761
+ [rectangle.x, rectangle.y + rectangle.height / 2]
762
+ ];
763
+ },
764
+ getTextRectangle(element) {
765
+ const elementRectangle = getRectangleByPoints(element.points);
766
+ const strokeWidth = getStrokeWidthByElement(element);
767
+ const height = element.textHeight;
768
+ const width = elementRectangle.width - ShapeDefaultSpace.rectangleAndText * 2 - strokeWidth * 2;
769
+ return {
770
+ height,
771
+ width: width > 0 ? width : 0,
772
+ x: elementRectangle.x + ShapeDefaultSpace.rectangleAndText + strokeWidth,
773
+ y: elementRectangle.y + (elementRectangle.height * heightRatio - height) / 2
774
+ };
775
+ }
776
+ };
777
+ const getRoundCommentPoints = (rectangle) => {
778
+ return [
779
+ [rectangle.x, rectangle.y],
780
+ [rectangle.x + rectangle.width, rectangle.y],
781
+ [rectangle.x + rectangle.width, rectangle.y + rectangle.height * heightRatio],
782
+ [rectangle.x + rectangle.width / 2, rectangle.y + rectangle.height * heightRatio],
783
+ [rectangle.x + rectangle.width / 4, rectangle.y + rectangle.height],
784
+ [rectangle.x + rectangle.width / 4, rectangle.y + rectangle.height * heightRatio],
785
+ [rectangle.x, rectangle.y + rectangle.height * heightRatio]
786
+ ];
787
+ };
788
+
789
+ const StarEngine = {
790
+ draw(board, rectangle, options) {
791
+ const points = getStarPoints(rectangle);
792
+ const rs = PlaitBoard.getRoughSVG(board);
793
+ const polygon = rs.polygon(points, { ...options, fillStyle: 'solid' });
794
+ setStrokeLinecap(polygon, 'round');
795
+ return polygon;
796
+ },
797
+ isHit(rectangle, point) {
798
+ const parallelogramPoints = getStarPoints(rectangle);
799
+ return isPointInPolygon(point, parallelogramPoints);
800
+ },
801
+ getCornerPoints(rectangle) {
802
+ return getStarPoints(rectangle);
803
+ },
804
+ getNearestPoint(rectangle, point) {
805
+ return getNearestPointBetweenPointAndSegments(point, getStarPoints(rectangle));
806
+ },
807
+ getEdgeByConnectionPoint(rectangle, pointOfRectangle) {
808
+ const corners = getStarPoints(rectangle);
809
+ const point = RectangleClient.getConnectionPoint(rectangle, pointOfRectangle);
810
+ return getEdgeOnPolygonByPoint(corners, point);
811
+ },
812
+ getConnectorPoints(rectangle) {
813
+ const points = getStarPoints(rectangle);
814
+ return [points[1], points[3], points[5], points[7], points[9]];
815
+ }
816
+ };
817
+ const getStarPoints = (rectangle) => {
818
+ return [
819
+ [rectangle.x + rectangle.width / 2, rectangle.y + (rectangle.height * 75) / 91],
820
+ [rectangle.x + (rectangle.width * 18.61) / 96, rectangle.y + rectangle.height],
821
+ [rectangle.x + (rectangle.width * 24.2235871) / 96, rectangle.y + (rectangle.height * 57.7254249) / 91],
822
+ [rectangle.x, rectangle.y + (rectangle.height * 34.5491503) / 91],
823
+ [rectangle.x + (rectangle.width * 33.3053687) / 96, rectangle.y + (rectangle.height * 29.7745751) / 91],
824
+ [rectangle.x + rectangle.width / 2, rectangle.y],
825
+ [rectangle.x + (rectangle.width * 62.6946313) / 96, rectangle.y + (rectangle.height * 29.7745751) / 91],
826
+ [rectangle.x + rectangle.width, rectangle.y + (rectangle.height * 34.5491503) / 91],
827
+ [rectangle.x + (rectangle.width * 71.7764129) / 96, rectangle.y + (rectangle.height * 57.7254249) / 91],
828
+ [rectangle.x + (rectangle.width * 77.3892626) / 96, rectangle.y + rectangle.height]
829
+ ];
830
+ };
831
+
832
+ const TrapezoidEngine = {
833
+ draw(board, rectangle, options) {
834
+ const points = getTrapezoidPoints(rectangle);
835
+ const rs = PlaitBoard.getRoughSVG(board);
836
+ const polygon = rs.polygon(points, { ...options, fillStyle: 'solid' });
837
+ setStrokeLinecap(polygon, 'round');
838
+ return polygon;
839
+ },
840
+ isHit(rectangle, point) {
841
+ const points = getTrapezoidPoints(rectangle);
842
+ return isPointInPolygon(point, points);
843
+ },
844
+ getNearestPoint(rectangle, point) {
845
+ const cornerPoints = getTrapezoidPoints(rectangle);
846
+ return getNearestPointBetweenPointAndSegments(point, cornerPoints);
847
+ },
848
+ getConnectorPoints(rectangle) {
849
+ const points = getTrapezoidPoints(rectangle);
850
+ return getCenterPointsOnPolygon(points);
851
+ },
852
+ getEdgeByConnectionPoint(rectangle, pointOfRectangle) {
853
+ const corners = getTrapezoidPoints(rectangle);
854
+ const point = RectangleClient.getConnectionPoint(rectangle, pointOfRectangle);
855
+ return getEdgeOnPolygonByPoint(corners, point);
856
+ },
857
+ getCornerPoints(rectangle) {
858
+ return getTrapezoidPoints(rectangle);
859
+ }
860
+ };
861
+ const getTrapezoidPoints = (rectangle) => {
862
+ return [
863
+ [rectangle.x + rectangle.width * 0.15, rectangle.y],
864
+ [rectangle.x + rectangle.width * 0.85, rectangle.y],
865
+ [rectangle.x + rectangle.width, rectangle.y + rectangle.height],
866
+ [rectangle.x, rectangle.y + rectangle.height]
867
+ ];
868
+ };
869
+
870
+ const TriangleEngine = {
871
+ draw(board, rectangle, options) {
872
+ const points = getTrianglePoints(rectangle);
873
+ const rs = PlaitBoard.getRoughSVG(board);
874
+ const polygon = rs.polygon(points, { ...options, fillStyle: 'solid' });
875
+ setStrokeLinecap(polygon, 'round');
876
+ return polygon;
877
+ },
878
+ isHit(rectangle, point) {
879
+ const points = getTrianglePoints(rectangle);
880
+ return isPointInPolygon(point, points);
881
+ },
882
+ getNearestPoint(rectangle, point) {
883
+ const cornerPoints = getTrianglePoints(rectangle);
884
+ return getNearestPointBetweenPointAndSegments(point, cornerPoints);
885
+ },
886
+ getConnectorPoints(rectangle) {
887
+ const cornerPoints = getTrianglePoints(rectangle);
888
+ const lineCenterPoints = getCenterPointsOnPolygon(cornerPoints);
889
+ return [...lineCenterPoints, ...cornerPoints];
890
+ },
891
+ getEdgeByConnectionPoint(rectangle, pointOfRectangle) {
892
+ const corners = getTrianglePoints(rectangle);
893
+ const point = RectangleClient.getConnectionPoint(rectangle, pointOfRectangle);
894
+ return getEdgeOnPolygonByPoint(corners, point);
895
+ },
896
+ getCornerPoints(rectangle) {
897
+ return getTrianglePoints(rectangle);
898
+ }
899
+ };
900
+ const getTrianglePoints = (rectangle) => {
901
+ return [
902
+ [rectangle.x + rectangle.width / 2, rectangle.y],
903
+ [rectangle.x + rectangle.width, rectangle.y + rectangle.height],
904
+ [rectangle.x, rectangle.y + rectangle.height]
905
+ ];
906
+ };
907
+
908
+ const TwoWayArrowEngine = {
909
+ draw(board, rectangle, options) {
910
+ const points = getTwoWayArrowPoints(rectangle);
911
+ const rs = PlaitBoard.getRoughSVG(board);
912
+ const polygon = rs.polygon(points, { ...options, fillStyle: 'solid' });
913
+ setStrokeLinecap(polygon, 'round');
914
+ return polygon;
915
+ },
916
+ getCornerPoints(rectangle) {
917
+ return getTwoWayArrowPoints(rectangle);
918
+ },
919
+ isHit(rectangle, point) {
920
+ const points = getTwoWayArrowPoints(rectangle);
921
+ return isPointInPolygon(point, points);
922
+ },
923
+ getNearestPoint(rectangle, point) {
924
+ const cornerPoints = getTwoWayArrowPoints(rectangle);
925
+ return getNearestPointBetweenPointAndSegments(point, cornerPoints);
926
+ },
927
+ getEdgeByConnectionPoint(rectangle, pointOfRectangle) {
928
+ const corners = getTwoWayArrowPoints(rectangle);
929
+ const point = RectangleClient.getConnectionPoint(rectangle, pointOfRectangle);
930
+ return getEdgeOnPolygonByPoint(corners, point);
931
+ },
932
+ getConnectorPoints(rectangle) {
933
+ return [
934
+ [rectangle.x, rectangle.y + rectangle.height / 2],
935
+ [rectangle.x + rectangle.width, rectangle.y + rectangle.height / 2]
936
+ ];
937
+ }
938
+ };
939
+ const getTwoWayArrowPoints = (rectangle) => {
940
+ return [
941
+ [rectangle.x, rectangle.y + rectangle.height / 2],
942
+ [rectangle.x + (rectangle.width * 8) / 25, rectangle.y],
943
+ [rectangle.x + (rectangle.width * 8) / 25, rectangle.y + rectangle.height / 5],
944
+ [rectangle.x + (rectangle.width * 17) / 25, rectangle.y + rectangle.height / 5],
945
+ [rectangle.x + (rectangle.width * 17) / 25, rectangle.y],
946
+ [rectangle.x + rectangle.width, rectangle.y + rectangle.height / 2],
947
+ [rectangle.x + (rectangle.width * 17) / 25, rectangle.y + rectangle.height],
948
+ [rectangle.x + (rectangle.width * 17) / 25, rectangle.y + (rectangle.height * 4) / 5],
949
+ [rectangle.x + (rectangle.width * 8) / 25, rectangle.y + (rectangle.height * 4) / 5],
950
+ [rectangle.x + (rectangle.width * 8) / 25, rectangle.y + rectangle.height]
951
+ ];
952
+ };
953
+
340
954
  const ShapeEngineMap = {
341
955
  [GeometryShape.rectangle]: RectangleEngine,
342
956
  [GeometryShape.diamond]: DiamondEngine,
343
957
  [GeometryShape.ellipse]: EllipseEngine,
344
958
  [GeometryShape.parallelogram]: ParallelogramEngine,
345
959
  [GeometryShape.roundRectangle]: RoundRectangleEngine,
346
- [GeometryShape.text]: RectangleEngine
960
+ [GeometryShape.text]: RectangleEngine,
961
+ [GeometryShape.triangle]: TriangleEngine,
962
+ [GeometryShape.leftArrow]: LeftArrowEngine,
963
+ [GeometryShape.trapezoid]: TrapezoidEngine,
964
+ [GeometryShape.rightArrow]: RightArrowEngine,
965
+ [GeometryShape.cross]: CrossEngine,
966
+ [GeometryShape.star]: StarEngine,
967
+ [GeometryShape.pentagon]: PentagonEngine,
968
+ [GeometryShape.hexagon]: HexagonEngine,
969
+ [GeometryShape.octagon]: OctagonEngine,
970
+ [GeometryShape.pentagonArrow]: PentagonArrowEngine,
971
+ [GeometryShape.processArrow]: ProcessArrowEngine,
972
+ [GeometryShape.twoWayArrow]: TwoWayArrowEngine,
973
+ [GeometryShape.comment]: CommentEngine,
974
+ [GeometryShape.roundComment]: RoundCommentEngine
347
975
  };
348
976
  const getEngine = (shape) => {
349
977
  return ShapeEngineMap[shape];
350
978
  };
351
979
 
980
+ const getShape = (value) => {
981
+ if (PlaitDrawElement.isImage(value)) {
982
+ return GeometryShape.rectangle;
983
+ }
984
+ return value.shape;
985
+ };
986
+
352
987
  const createGeometryElement = (shape, points, text, options) => {
353
988
  let textOptions = {};
354
989
  let alignment = Alignment.center;
@@ -390,14 +1025,15 @@ const drawBoundMask = (board, element) => {
390
1025
  const G = createG();
391
1026
  const rectangle = getRectangleByPoints(element.points);
392
1027
  const activeRectangle = RectangleClient.inflate(rectangle, ACTIVE_STROKE_WIDTH);
393
- const maskG = drawGeometry(board, activeRectangle, element.shape, {
1028
+ const shape = getShape(element);
1029
+ const maskG = drawGeometry(board, activeRectangle, shape, {
394
1030
  stroke: SELECTION_BORDER_COLOR,
395
1031
  strokeWidth: 1,
396
1032
  fill: SELECTION_FILL_COLOR,
397
1033
  fillStyle: 'solid'
398
1034
  });
399
1035
  G.appendChild(maskG);
400
- const connectorPoints = getEngine(element.shape).getConnectorPoints(activeRectangle);
1036
+ const connectorPoints = getEngine(shape).getConnectorPoints(activeRectangle);
401
1037
  connectorPoints.forEach(point => {
402
1038
  const circleG = drawCircle(PlaitBoard.getRoughSVG(board), point, 6, {
403
1039
  stroke: '#999999',
@@ -415,7 +1051,8 @@ const drawGeometry = (board, outerRectangle, shape, options) => {
415
1051
  const getNearestPoint = (element, point, inflateDelta = 0) => {
416
1052
  const rectangle = getRectangleByPoints(element.points);
417
1053
  const activeRectangle = RectangleClient.inflate(rectangle, inflateDelta);
418
- return getEngine(element.shape).getNearestPoint(activeRectangle, point);
1054
+ const shape = getShape(element);
1055
+ return getEngine(shape).getNearestPoint(activeRectangle, point);
419
1056
  };
420
1057
  const getCenterPointsOnPolygon = (points) => {
421
1058
  const centerPoint = [];
@@ -425,6 +1062,17 @@ const getCenterPointsOnPolygon = (points) => {
425
1062
  }
426
1063
  return centerPoint;
427
1064
  };
1065
+ const getEdgeOnPolygonByPoint = (corners, point) => {
1066
+ for (let index = 1; index <= corners.length; index++) {
1067
+ let start = corners[index - 1];
1068
+ let end = index === corners.length ? corners[0] : corners[index];
1069
+ const distance = distanceBetweenPointAndSegment(point[0], point[1], start[0], start[1], end[0], end[1]);
1070
+ if (distance < 1) {
1071
+ return [start, end];
1072
+ }
1073
+ }
1074
+ return null;
1075
+ };
428
1076
 
429
1077
  const drawLineArrow = (element, points, options) => {
430
1078
  const arrowG = createG();
@@ -432,16 +1080,19 @@ const drawLineArrow = (element, points, options) => {
432
1080
  return null;
433
1081
  }
434
1082
  if (!PlaitLine.isSourceMark(element, LineMarkerType.none)) {
435
- const sourceArrow = getArrow(element, element.source.marker, points[1], points[0], options);
1083
+ const source = getExtendPoint(points[0], points[1], 24);
1084
+ const sourceArrow = getArrow(element, { marker: element.source.marker, source, target: points[0], isSource: true }, options);
436
1085
  sourceArrow && arrowG.appendChild(sourceArrow);
437
1086
  }
438
1087
  if (!PlaitLine.isTargetMark(element, LineMarkerType.none)) {
439
- const arrow = getArrow(element, element.target.marker, points[points.length - 2], points[points.length - 1], options);
1088
+ const source = getExtendPoint(points[points.length - 1], points[points.length - 2], 24);
1089
+ const arrow = getArrow(element, { marker: element.target.marker, source, target: points[points.length - 1], isSource: false }, options);
440
1090
  arrow && arrowG.appendChild(arrow);
441
1091
  }
442
1092
  return arrowG;
443
1093
  };
444
- const getArrow = (element, marker, source, target, options) => {
1094
+ const getArrow = (element, arrowOptions, options) => {
1095
+ const { marker, source, target, isSource } = arrowOptions;
445
1096
  let targetArrow;
446
1097
  switch (marker) {
447
1098
  case LineMarkerType.openTriangle: {
@@ -460,16 +1111,27 @@ const getArrow = (element, marker, source, target, options) => {
460
1111
  targetArrow = drawSharpArrow(source, target, options);
461
1112
  break;
462
1113
  }
1114
+ case LineMarkerType.oneSideUp: {
1115
+ targetArrow = drawOneSideArrow(source, target, 'up', options);
1116
+ break;
1117
+ }
1118
+ case LineMarkerType.oneSideDown: {
1119
+ targetArrow = drawOneSideArrow(source, target, 'down', options);
1120
+ break;
1121
+ }
1122
+ case LineMarkerType.hollowTriangle: {
1123
+ targetArrow = drawHollowTriangleArrow(source, target, options);
1124
+ break;
1125
+ }
1126
+ case LineMarkerType.singleSlash: {
1127
+ targetArrow = drawSingleSlash(source, target, isSource, options);
1128
+ break;
1129
+ }
463
1130
  }
464
1131
  return targetArrow;
465
1132
  };
466
1133
  const drawSharpArrow = (source, target, options) => {
467
- const directionFactor = getFactorByPoints(source, target);
468
1134
  const startPoint = target;
469
- // const startPoint: Point = [
470
- // target[0],
471
- // target[1]
472
- // ];
473
1135
  const { pointLeft, pointRight } = arrowPoints(source, target, 12, 20);
474
1136
  const g = createG();
475
1137
  const path = createPath();
@@ -484,10 +1146,7 @@ const drawSharpArrow = (source, target, options) => {
484
1146
  const drawArrow = (element, source, target, options) => {
485
1147
  const directionFactor = getFactorByPoints(source, target);
486
1148
  const strokeWidth = getStrokeWidthByElement(element);
487
- const endPoint = [
488
- target[0] + strokeWidth * directionFactor.x / 2,
489
- target[1] + strokeWidth * directionFactor.y / 2
490
- ];
1149
+ const endPoint = [target[0] + (strokeWidth * directionFactor.x) / 2, target[1] + (strokeWidth * directionFactor.y) / 2];
491
1150
  const middlePoint = [endPoint[0] - 8 * directionFactor.x, endPoint[1] - 8 * directionFactor.y];
492
1151
  const { pointLeft, pointRight } = arrowPoints(source, endPoint, 12, 30);
493
1152
  const arrowG = drawLinearPath([pointLeft, endPoint, pointRight, middlePoint], { ...options, fill: options.stroke }, true);
@@ -503,13 +1162,26 @@ const drawSolidTriangle = (source, target, options) => {
503
1162
  const drawOpenTriangle = (element, source, target, options) => {
504
1163
  const directionFactor = getFactorByPoints(source, target);
505
1164
  const strokeWidth = getStrokeWidthByElement(element);
506
- const endPoint = [
507
- target[0] + strokeWidth * directionFactor.x / 2,
508
- target[1] + strokeWidth * directionFactor.y / 2
509
- ];
1165
+ const endPoint = [target[0] + (strokeWidth * directionFactor.x) / 2, target[1] + (strokeWidth * directionFactor.y) / 2];
510
1166
  const { pointLeft, pointRight } = arrowPoints(source, endPoint, 12, 40);
511
1167
  return drawLinearPath([pointLeft, endPoint, pointRight], options);
512
1168
  };
1169
+ const drawOneSideArrow = (source, target, side, options) => {
1170
+ const { pointLeft, pointRight } = arrowPoints(source, target, 12, 40);
1171
+ return drawLinearPath([side === 'up' ? pointRight : pointLeft, target], options);
1172
+ };
1173
+ const drawSingleSlash = (source, target, isSource, options) => {
1174
+ source = getExtendPoint(target, source, 12);
1175
+ const middlePoint = getExtendPoint(target, source, 6);
1176
+ const angle = isSource ? 120 : 60;
1177
+ const start = rotate(...source, ...middlePoint, (angle * Math.PI) / 180);
1178
+ const end = rotate(...target, ...middlePoint, (angle * Math.PI) / 180);
1179
+ return drawLinearPath([start, end], options);
1180
+ };
1181
+ const drawHollowTriangleArrow = (source, target, options) => {
1182
+ const { pointLeft, pointRight } = arrowPoints(source, target, 12, 30);
1183
+ return drawLinearPath([pointLeft, pointRight, target], { ...options, fill: 'white' }, true);
1184
+ };
513
1185
 
514
1186
  const createLineElement = (shape, points, source, target, options) => {
515
1187
  return {
@@ -525,28 +1197,127 @@ const createLineElement = (shape, points, source, target, options) => {
525
1197
  };
526
1198
  };
527
1199
  const getLinePoints = (board, element) => {
528
- return element.shape === LineShape.elbow ? getElbowPoints(board, element) : getStraightPoints(board, element);
1200
+ switch (element.shape) {
1201
+ case LineShape.elbow: {
1202
+ return getElbowPoints(board, element);
1203
+ }
1204
+ case LineShape.curve: {
1205
+ return getCurvePoints(board, element);
1206
+ }
1207
+ default: {
1208
+ return PlaitLine.getPoints(board, element);
1209
+ }
1210
+ }
529
1211
  };
530
- const getStraightPoints = (board, element) => {
531
- return [getSourcePoint(board, element), getTargetPoint(board, element)];
1212
+ const getLineHandleRefPair = (board, element) => {
1213
+ const strokeWidth = getStrokeWidthByElement(element);
1214
+ const sourceBoundElement = element.source.boundId ? getElementById(board, element.source.boundId) : undefined;
1215
+ const targetBoundElement = element.target.boundId ? getElementById(board, element.target.boundId) : undefined;
1216
+ let sourcePoint = sourceBoundElement ? getConnectionPoint(sourceBoundElement, element.source.connection) : element.points[0];
1217
+ let targetPoint = targetBoundElement
1218
+ ? getConnectionPoint(targetBoundElement, element.target.connection)
1219
+ : element.points[element.points.length - 1];
1220
+ let sourceDirection = getDirectionByVector([targetPoint[0] - sourcePoint[0], targetPoint[1] - sourcePoint[1]]);
1221
+ let targetDirection = getOppositeDirection(sourceDirection);
1222
+ const sourceFactor = getDirectionFactor(sourceDirection);
1223
+ const targetFactor = getDirectionFactor(targetDirection);
1224
+ const sourceHandleRef = {
1225
+ key: LineHandleKey.source,
1226
+ point: sourcePoint,
1227
+ direction: sourceDirection,
1228
+ vector: [sourceFactor.x, sourceFactor.y]
1229
+ };
1230
+ const targetHandleRef = {
1231
+ key: LineHandleKey.target,
1232
+ point: targetPoint,
1233
+ direction: targetDirection,
1234
+ vector: [targetFactor.x, targetFactor.y]
1235
+ };
1236
+ if (sourceBoundElement) {
1237
+ const connectionOffset = PlaitLine.isSourceMarkOrTargetMark(element, LineMarkerType.none, LineHandleKey.source) ? 0 : strokeWidth;
1238
+ const sourceVector = getVectorByConnection(sourceBoundElement, element.source.connection);
1239
+ const direction = getDirectionByVector(sourceVector);
1240
+ sourceDirection = direction ? direction : sourceDirection;
1241
+ sourcePoint = getConnectionPoint(sourceBoundElement, element.source.connection, sourceDirection, connectionOffset);
1242
+ sourceHandleRef.boundElement = sourceBoundElement;
1243
+ sourceHandleRef.direction = sourceDirection;
1244
+ sourceHandleRef.point = sourcePoint;
1245
+ sourceHandleRef.vector = sourceVector;
1246
+ }
1247
+ if (targetBoundElement) {
1248
+ const connectionOffset = PlaitLine.isSourceMarkOrTargetMark(element, LineMarkerType.none, LineHandleKey.target) ? 0 : strokeWidth;
1249
+ const targetVector = getVectorByConnection(targetBoundElement, element.target.connection);
1250
+ const direction = getDirectionByVector(targetVector);
1251
+ targetDirection = direction ? direction : targetDirection;
1252
+ targetPoint = getConnectionPoint(targetBoundElement, element.target.connection, targetDirection, connectionOffset);
1253
+ targetHandleRef.boundElement = targetBoundElement;
1254
+ targetHandleRef.direction = targetDirection;
1255
+ targetHandleRef.point = targetPoint;
1256
+ targetHandleRef.vector = targetVector;
1257
+ }
1258
+ return { source: sourceHandleRef, target: targetHandleRef };
532
1259
  };
533
1260
  const getElbowPoints = (board, element) => {
534
1261
  if (element.points.length === 2) {
535
- const source = getSourcePoint(board, element);
536
- const target = getTargetPoint(board, element);
537
- let sourceDirection = source[0] < target[0] ? Direction.right : Direction.left;
538
- let targetDirection = source[0] < target[0] ? Direction.left : Direction.right;
539
- if (element.source.connection) {
540
- sourceDirection = getDirectionByPoint(element.source.connection, sourceDirection);
541
- }
542
- if (element.target.connection) {
543
- targetDirection = getDirectionByPoint(element.target.connection, targetDirection);
544
- }
545
- const points = getPoints(source, sourceDirection, target, targetDirection, 30);
1262
+ const handleRefPair = getLineHandleRefPair(board, element);
1263
+ const offset = element.source.boundId || element.target.boundId ? 30 : 0;
1264
+ let points = getPoints(handleRefPair.source.point, handleRefPair.source.direction, handleRefPair.target.point, handleRefPair.target.direction, offset);
1265
+ points = removeDuplicatePoints(points);
546
1266
  return points;
547
1267
  }
548
1268
  return element.points;
549
1269
  };
1270
+ const getCurvePoints = (board, element) => {
1271
+ if (element.points.length === 2) {
1272
+ const handleRefPair = getLineHandleRefPair(board, element);
1273
+ const { source, target } = handleRefPair;
1274
+ const sourceBoundElement = handleRefPair.source.boundElement;
1275
+ const targetBoundElement = handleRefPair.target.boundElement;
1276
+ let curvePoints = [source.point];
1277
+ const sumDistance = distanceBetweenPointAndPoint(...source.point, ...target.point);
1278
+ const offset = 12 + sumDistance / 3;
1279
+ if (sourceBoundElement) {
1280
+ curvePoints.push(getPointByVector(source.point, source.vector, offset));
1281
+ }
1282
+ if (targetBoundElement) {
1283
+ curvePoints.push(getPointByVector(target.point, target.vector, offset));
1284
+ }
1285
+ const isSingleBound = (sourceBoundElement && !targetBoundElement) || (!sourceBoundElement && targetBoundElement);
1286
+ if (isSingleBound) {
1287
+ curvePoints.push(target.point);
1288
+ const points = Q2C(curvePoints);
1289
+ return pointsOnBezierCurves(points);
1290
+ }
1291
+ if (!sourceBoundElement && !targetBoundElement) {
1292
+ curvePoints.push(getPointByVector(source.point, source.vector, offset));
1293
+ curvePoints.push(getPointByVector(target.point, target.vector, offset));
1294
+ }
1295
+ curvePoints.push(target.point);
1296
+ return pointsOnBezierCurves(curvePoints);
1297
+ }
1298
+ else {
1299
+ //TODO 直接获取贝塞尔曲线上高密度点
1300
+ const points = PlaitLine.getPoints(board, element);
1301
+ const draw = PlaitBoard.getRoughSVG(board).generator.curve(points);
1302
+ let bezierPoints = transformOpsToPoints(draw.sets[0].ops);
1303
+ bezierPoints = removeDuplicatePoints(bezierPoints);
1304
+ return pointsOnBezierCurves(bezierPoints);
1305
+ }
1306
+ };
1307
+ const transformOpsToPoints = (ops) => {
1308
+ const result = [];
1309
+ for (let item of ops) {
1310
+ if (item.op === 'move') {
1311
+ result.push([item.data[0], item.data[1]]);
1312
+ }
1313
+ else {
1314
+ result.push([item.data[0], item.data[1]]);
1315
+ result.push([item.data[2], item.data[3]]);
1316
+ result.push([item.data[4], item.data[5]]);
1317
+ }
1318
+ }
1319
+ return result;
1320
+ };
550
1321
  const isHitPolyLine = (pathPoints, point, strokeWidth, expand = 0) => {
551
1322
  const distance = distanceBetweenPointAndSegments(pathPoints, point);
552
1323
  return distance <= strokeWidth + expand;
@@ -576,20 +1347,28 @@ const drawLine = (board, element) => {
576
1347
  const strokeLineDash = getLineDashByElement(element);
577
1348
  const options = { stroke: strokeColor, strokeWidth, strokeLineDash };
578
1349
  const lineG = createG();
579
- const points = getLinePoints(board, element);
580
- const line = drawLinearPath(points, options);
581
- line.setAttribute('mask', `url(#${element.id})`);
1350
+ let points = getLinePoints(board, element);
1351
+ let line;
1352
+ if (element.shape === LineShape.curve) {
1353
+ //TODO element.points 应为曲线拐点
1354
+ line = PlaitBoard.getRoughSVG(board).curve(points, options);
1355
+ }
1356
+ else {
1357
+ line = drawLinearPath(points, options);
1358
+ }
1359
+ const id = idCreator();
1360
+ line.setAttribute('mask', `url(#${id})`);
582
1361
  lineG.appendChild(line);
583
- const { mask, maskTargetFillRect } = drawMask(board, element);
1362
+ const { mask, maskTargetFillRect } = drawMask(board, element, id);
584
1363
  lineG.appendChild(mask);
585
1364
  line.appendChild(maskTargetFillRect);
586
- const arrow = drawLineArrow(element, points, options);
1365
+ const arrow = drawLineArrow(element, points, { stroke: strokeColor, strokeWidth });
587
1366
  arrow && lineG.appendChild(arrow);
588
1367
  return lineG;
589
1368
  };
590
- function drawMask(board, element) {
1369
+ function drawMask(board, element, id) {
591
1370
  const mask = createMask();
592
- mask.setAttribute('id', element.id);
1371
+ mask.setAttribute('id', id);
593
1372
  const points = getLinePoints(board, element);
594
1373
  let rectangle = getRectangleByPoints(points);
595
1374
  rectangle = RectangleClient.getOutlineRectangle(rectangle, -30);
@@ -610,33 +1389,16 @@ function drawMask(board, element) {
610
1389
  maskTargetFillRect.setAttribute('opacity', '0');
611
1390
  return { mask, maskTargetFillRect };
612
1391
  }
613
- const getSourcePoint = (board, element) => {
614
- if (element.source.boundId) {
615
- const strokeWidth = getStrokeWidthByElement(element);
616
- const connectionOffset = PlaitLine.isSourceMark(element, LineMarkerType.none) ? 0 : strokeWidth;
617
- const boundElement = getElementById(board, element.source.boundId);
618
- return boundElement ? getConnectionPoint(boundElement, element.source.connection, connectionOffset) : element.points[0];
1392
+ const getConnectionPoint = (geometry, connection, direction, delta) => {
1393
+ const rectangle = getRectangleByPoints(geometry.points);
1394
+ if (direction && delta) {
1395
+ const directionFactor = getDirectionFactor(direction);
1396
+ const point = RectangleClient.getConnectionPoint(rectangle, connection);
1397
+ return [point[0] + directionFactor.x * delta, point[1] + directionFactor.y * delta];
619
1398
  }
620
- return element.points[0];
621
- };
622
- const getTargetPoint = (board, element) => {
623
- if (element.target.boundId) {
624
- const strokeWidth = getStrokeWidthByElement(element);
625
- const connectionOffset = PlaitLine.isTargetMark(element, LineMarkerType.none) ? 0 : strokeWidth;
626
- const boundElement = getElementById(board, element.target.boundId);
627
- return boundElement
628
- ? getConnectionPoint(boundElement, element.target.connection, connectionOffset)
629
- : element.points[element.points.length - 1];
1399
+ else {
1400
+ return RectangleClient.getConnectionPoint(rectangle, connection);
630
1401
  }
631
- return element.points[element.points.length - 1];
632
- };
633
- const getConnectionPoint = (geometry, connection, offset) => {
634
- const rectangle = getRectangleByPoints(geometry.points);
635
- const directionFactor = getDirectionFactor(getDirectionByPoint(connection, Direction.bottom));
636
- return [
637
- rectangle.x + rectangle.width * connection[0] + directionFactor.x * offset,
638
- rectangle.y + rectangle.height * connection[1] + directionFactor.y * offset
639
- ];
640
1402
  };
641
1403
  const transformPointToConnection = (board, point, hitElement) => {
642
1404
  let rectangle = getRectangleByPoints(hitElement.points);
@@ -647,7 +1409,8 @@ const transformPointToConnection = (board, point, hitElement) => {
647
1409
  return [(nearestPoint[0] - rectangle.x) / rectangle.width, (nearestPoint[1] - rectangle.y) / rectangle.height];
648
1410
  };
649
1411
  const getHitConnectorPoint = (movingPoint, hitElement, rectangle) => {
650
- const connector = getEngine(hitElement.shape).getConnectorPoints(rectangle);
1412
+ const shape = getShape(hitElement);
1413
+ const connector = getEngine(shape).getConnectorPoints(rectangle);
651
1414
  const points = getPointsByCenterPoint(movingPoint, 5, 5);
652
1415
  const pointRectangle = getRectangleByPoints(points);
653
1416
  return connector.find(point => {
@@ -671,6 +1434,64 @@ const getBoardLines = (board) => {
671
1434
  recursion: (element) => PlaitDrawElement.isDrawElement(element)
672
1435
  });
673
1436
  };
1437
+ const removeDuplicatePoints = (points) => {
1438
+ const newArray = [];
1439
+ points.forEach(point => {
1440
+ const index = newArray.findIndex(otherPoint => {
1441
+ return point[0] === otherPoint[0] && point[1] === otherPoint[1];
1442
+ });
1443
+ if (index === -1)
1444
+ newArray.push(point);
1445
+ });
1446
+ return newArray;
1447
+ };
1448
+ const getExtendPoint = (source, target, extendDistance) => {
1449
+ const distance = distanceBetweenPointAndPoint(...source, ...target);
1450
+ const sin = (target[1] - source[1]) / distance;
1451
+ const cos = (target[0] - source[0]) / distance;
1452
+ return [source[0] + extendDistance * cos, source[1] + extendDistance * sin];
1453
+ };
1454
+ // quadratic Bezier to cubic Bezier
1455
+ const Q2C = (points) => {
1456
+ const result = [];
1457
+ const numSegments = points.length / 3;
1458
+ for (let i = 0; i < numSegments; i++) {
1459
+ const start = points[i];
1460
+ const qControl = points[i + 1];
1461
+ const end = points[i + 2];
1462
+ const startDistance = distanceBetweenPointAndPoint(...start, ...qControl);
1463
+ const endDistance = distanceBetweenPointAndPoint(...end, ...qControl);
1464
+ const cControl1 = getExtendPoint(start, qControl, (startDistance * 2) / 3);
1465
+ const cControl2 = getExtendPoint(end, qControl, (endDistance * 2) / 3);
1466
+ result.push(start, cControl1, cControl2, end);
1467
+ }
1468
+ return result;
1469
+ };
1470
+ const getVectorByConnection = (boundElement, connection) => {
1471
+ const rectangle = getRectangleByPoints(boundElement.points);
1472
+ const shape = getShape(boundElement);
1473
+ const engine = getEngine(shape);
1474
+ let vector = [0, 0];
1475
+ const direction = getDirectionByPointOfRectangle(connection);
1476
+ if (direction) {
1477
+ const factor = getDirectionFactor(direction);
1478
+ return [factor.x, factor.y];
1479
+ }
1480
+ if (engine.getEdgeByConnectionPoint) {
1481
+ const edge = engine.getEdgeByConnectionPoint(rectangle, connection);
1482
+ if (edge) {
1483
+ const lineVector = [edge[1][0] - edge[0][0], edge[1][1] - edge[0][1]];
1484
+ return rotateVectorAnti90(lineVector);
1485
+ }
1486
+ }
1487
+ if (engine.getTangentVectorByConnectionPoint) {
1488
+ const lineVector = engine.getTangentVectorByConnectionPoint(rectangle, connection);
1489
+ if (lineVector) {
1490
+ return rotateVectorAnti90(lineVector);
1491
+ }
1492
+ }
1493
+ return vector;
1494
+ };
674
1495
 
675
1496
  const getSelectedDrawElements = (board) => {
676
1497
  const selectedElements = getSelectedElements(board).filter(value => PlaitDrawElement.isDrawElement(value));
@@ -684,6 +1505,111 @@ const getSelectedLineElements = (board) => {
684
1505
  const selectedElements = getSelectedElements(board).filter(value => PlaitDrawElement.isLine(value));
685
1506
  return selectedElements;
686
1507
  };
1508
+ const getSelectedImageElements = (board) => {
1509
+ const selectedElements = getSelectedElements(board).filter(value => PlaitDrawElement.isImage(value));
1510
+ return selectedElements;
1511
+ };
1512
+
1513
+ var LineMarkerType;
1514
+ (function (LineMarkerType) {
1515
+ LineMarkerType["arrow"] = "arrow";
1516
+ LineMarkerType["none"] = "none";
1517
+ LineMarkerType["openTriangle"] = "open-triangle";
1518
+ LineMarkerType["solidTriangle"] = "solid-triangle";
1519
+ LineMarkerType["sharpArrow"] = "sharp-arrow";
1520
+ LineMarkerType["oneSideUp"] = "one-side-up";
1521
+ LineMarkerType["oneSideDown"] = "one-side-down";
1522
+ LineMarkerType["hollowTriangle"] = "hollow-triangle";
1523
+ LineMarkerType["singleSlash"] = "single-slash";
1524
+ })(LineMarkerType || (LineMarkerType = {}));
1525
+ var LineShape;
1526
+ (function (LineShape) {
1527
+ LineShape["straight"] = "straight";
1528
+ LineShape["curve"] = "curve";
1529
+ LineShape["elbow"] = "elbow";
1530
+ })(LineShape || (LineShape = {}));
1531
+ var LineHandleKey;
1532
+ (function (LineHandleKey) {
1533
+ LineHandleKey["source"] = "source";
1534
+ LineHandleKey["target"] = "target";
1535
+ })(LineHandleKey || (LineHandleKey = {}));
1536
+ const PlaitLine = {
1537
+ getTextEditors(element) {
1538
+ const component = PlaitElement.getComponent(element);
1539
+ if (component) {
1540
+ const manage = component.textManages.find(manage => manage.isEditing);
1541
+ if (manage) {
1542
+ return [manage.componentRef.instance.editor];
1543
+ }
1544
+ else {
1545
+ return component.textManages.map(manage => manage.componentRef.instance.editor);
1546
+ }
1547
+ }
1548
+ throw new Error('can not get correctly component in get text editor');
1549
+ },
1550
+ isSourceMarkOrTargetMark(line, markType, handleKey) {
1551
+ if (handleKey === LineHandleKey.source) {
1552
+ return line.source.marker === markType;
1553
+ }
1554
+ else {
1555
+ return line.target.marker === markType;
1556
+ }
1557
+ },
1558
+ isSourceMark(line, markType) {
1559
+ return PlaitLine.isSourceMarkOrTargetMark(line, markType, LineHandleKey.source);
1560
+ },
1561
+ isTargetMark(line, markType) {
1562
+ return PlaitLine.isSourceMarkOrTargetMark(line, markType, LineHandleKey.target);
1563
+ },
1564
+ isBoundElementOfSource(line, element) {
1565
+ return line.source.boundId === element.id;
1566
+ },
1567
+ isBoundElementOfTarget(line, element) {
1568
+ return line.target.boundId === element.id;
1569
+ },
1570
+ getPoints(board, line) {
1571
+ let sourcePoint = line.source.boundId
1572
+ ? getConnectionPoint(getElementById(board, line.source.boundId), line.source.connection)
1573
+ : line.points[0];
1574
+ let targetPoint = line.target.boundId
1575
+ ? getConnectionPoint(getElementById(board, line.target.boundId), line.target.connection)
1576
+ : line.points[line.points.length - 1];
1577
+ const restPoints = line.points.length > 2 ? line.points.slice(1, line.points.length - 1) : [];
1578
+ return [sourcePoint, ...restPoints, targetPoint];
1579
+ }
1580
+ };
1581
+
1582
+ var StrokeStyle;
1583
+ (function (StrokeStyle) {
1584
+ StrokeStyle["solid"] = "solid";
1585
+ StrokeStyle["dashed"] = "dashed";
1586
+ })(StrokeStyle || (StrokeStyle = {}));
1587
+
1588
+ const PlaitDrawElement = {
1589
+ isGeometry: (value) => {
1590
+ return value.type === 'geometry';
1591
+ },
1592
+ isLine: (value) => {
1593
+ return value.type === 'line';
1594
+ },
1595
+ isText: (value) => {
1596
+ return value.type === 'geometry' && value.shape === GeometryShape.text;
1597
+ },
1598
+ isImage: (value) => {
1599
+ return value.type === 'image';
1600
+ },
1601
+ isDrawElement: (value) => {
1602
+ if (PlaitDrawElement.isGeometry(value) || PlaitDrawElement.isLine(value) || PlaitDrawElement.isImage(value)) {
1603
+ return true;
1604
+ }
1605
+ else {
1606
+ return false;
1607
+ }
1608
+ },
1609
+ isShape: (value) => {
1610
+ return PlaitDrawElement.isImage(value) || PlaitDrawElement.isGeometry(value);
1611
+ }
1612
+ };
687
1613
 
688
1614
  class GeometryShapeGenerator extends Generator {
689
1615
  canDraw(element, data) {
@@ -699,7 +1625,12 @@ class GeometryShapeGenerator extends Generator {
699
1625
  const strokeColor = getStrokeColorByElement(element);
700
1626
  const fill = getFillByElement(element);
701
1627
  const strokeLineDash = getLineDashByElement(element);
702
- return drawGeometry(this.board, RectangleClient.inflate(rectangle, -strokeWidth), shape, { stroke: strokeColor, strokeWidth, fill, strokeLineDash });
1628
+ return drawGeometry(this.board, RectangleClient.inflate(rectangle, -strokeWidth), shape, {
1629
+ stroke: strokeColor,
1630
+ strokeWidth,
1631
+ fill,
1632
+ strokeLineDash
1633
+ });
703
1634
  }
704
1635
  }
705
1636
 
@@ -779,6 +1710,28 @@ const setTextSize = (board, element, textWidth, textHeight) => {
779
1710
  }
780
1711
  };
781
1712
 
1713
+ const insertImage = (board, imageItem, startPoint) => {
1714
+ const { width, height, url } = imageItem;
1715
+ const host = BOARD_TO_HOST.get(board);
1716
+ const viewportWidth = PlaitBoard.getComponent(board).nativeElement.clientWidth;
1717
+ const viewportHeight = PlaitBoard.getComponent(board).nativeElement.clientHeight;
1718
+ const point = transformPoint(board, toPoint(viewportWidth / 2, viewportHeight / 2, host));
1719
+ const points = startPoint
1720
+ ? [startPoint, [startPoint[0] + width, startPoint[1] + height]]
1721
+ : [
1722
+ [point[0] - width / 2, point[1] - height / 2],
1723
+ [point[0] + width / 2, point[1] + height / 2]
1724
+ ];
1725
+ const imageElement = {
1726
+ id: idCreator(),
1727
+ type: 'image',
1728
+ points,
1729
+ url
1730
+ };
1731
+ Transforms.insertNode(board, imageElement, [board.children.length]);
1732
+ Transforms.addSelectionWithTemporaryElements(board, [imageElement]);
1733
+ };
1734
+
782
1735
  const resizeLine = (board, options, path) => {
783
1736
  Transforms.setNode(board, options, path);
784
1737
  };
@@ -809,7 +1762,8 @@ const DrawTransforms = {
809
1762
  resizeLine,
810
1763
  setLineTexts,
811
1764
  removeLineText,
812
- setLineMark
1765
+ setLineMark,
1766
+ insertImage
813
1767
  };
814
1768
 
815
1769
  class GeometryComponent extends CommonPluginElement {
@@ -827,10 +1781,19 @@ class GeometryComponent extends CommonPluginElement {
827
1781
  getStrokeWidth: () => {
828
1782
  const selectedElements = getSelectedElements(this.board);
829
1783
  if (selectedElements.length === 1 && !isSelectionMoving(this.board)) {
830
- return DefaultGeometryActiveStyle.strokeWidth;
1784
+ return ACTIVE_STROKE_WIDTH;
1785
+ }
1786
+ else {
1787
+ return ACTIVE_STROKE_WIDTH;
1788
+ }
1789
+ },
1790
+ getStrokeOpacity: () => {
1791
+ const selectedElements = getSelectedElements(this.board);
1792
+ if (selectedElements.length === 1 && !isSelectionMoving(this.board)) {
1793
+ return 1;
831
1794
  }
832
1795
  else {
833
- return DefaultGeometryActiveStyle.selectionStrokeWidth;
1796
+ return 0.5;
834
1797
  }
835
1798
  },
836
1799
  getRectangle: (element) => {
@@ -884,6 +1847,10 @@ class GeometryComponent extends CommonPluginElement {
884
1847
  const plugins = this.board.getPluginOptions(WithTextPluginKey).textPlugins;
885
1848
  const manage = new TextManage(this.board, this.viewContainerRef, {
886
1849
  getRectangle: () => {
1850
+ const getRectangle = getEngine(this.element.shape).getTextRectangle;
1851
+ if (getRectangle) {
1852
+ return getRectangle(this.element);
1853
+ }
887
1854
  return getTextRectangle(this.element);
888
1855
  },
889
1856
  onValueChangeHandle: (textManageRef) => {
@@ -911,14 +1878,15 @@ class GeometryComponent extends CommonPluginElement {
911
1878
  this.destroy$.complete();
912
1879
  }
913
1880
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "16.2.3", ngImport: i0, type: GeometryComponent, deps: [{ token: i0.ViewContainerRef }, { token: i0.ChangeDetectorRef }], target: i0.ɵɵFactoryTarget.Component }); }
914
- static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "16.2.3", type: GeometryComponent, selector: "plait-draw-geometry", usesInheritance: true, ngImport: i0, template: ``, isInline: true, changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
1881
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "16.2.3", type: GeometryComponent, isStandalone: true, selector: "plait-draw-geometry", usesInheritance: true, ngImport: i0, template: ``, isInline: true, changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
915
1882
  }
916
1883
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "16.2.3", ngImport: i0, type: GeometryComponent, decorators: [{
917
1884
  type: Component,
918
1885
  args: [{
919
1886
  selector: 'plait-draw-geometry',
920
1887
  template: ``,
921
- changeDetection: ChangeDetectionStrategy.OnPush
1888
+ changeDetection: ChangeDetectionStrategy.OnPush,
1889
+ standalone: true
922
1890
  }]
923
1891
  }], ctorParameters: function () { return [{ type: i0.ViewContainerRef }, { type: i0.ChangeDetectorRef }]; } });
924
1892
 
@@ -927,21 +1895,17 @@ class LineShapeGenerator extends Generator {
927
1895
  return true;
928
1896
  }
929
1897
  baseDraw(element, data) {
930
- const shape = element.shape;
931
1898
  let lineG;
932
- switch (shape) {
933
- case LineShape.elbow:
934
- case LineShape.straight:
935
- lineG = drawLine(this.board, element);
936
- break;
937
- default:
938
- break;
939
- }
1899
+ lineG = drawLine(this.board, element);
940
1900
  return lineG;
941
1901
  }
942
1902
  }
943
1903
 
944
1904
  class LineActiveGenerator extends Generator {
1905
+ constructor() {
1906
+ super(...arguments);
1907
+ this.hasResizeHandle = false;
1908
+ }
945
1909
  canDraw(element, data) {
946
1910
  if (data.selected) {
947
1911
  return true;
@@ -952,27 +1916,70 @@ class LineActiveGenerator extends Generator {
952
1916
  }
953
1917
  baseDraw(element, data) {
954
1918
  const activeG = createG();
955
- activeG.classList.add('active');
956
- activeG.classList.add('line-handle');
957
- const sourcePoint = getSourcePoint(this.board, element);
958
- const targetPoint = getTargetPoint(this.board, element);
959
- const sourceCircle = drawCircle(PlaitBoard.getRoughSVG(this.board), sourcePoint, RESIZE_HANDLE_DIAMETER, {
960
- stroke: '#999999',
961
- strokeWidth: 1,
962
- fill: '#FFF',
963
- fillStyle: 'solid'
964
- });
965
- const targetCircle = drawCircle(PlaitBoard.getRoughSVG(this.board), targetPoint, RESIZE_HANDLE_DIAMETER, {
966
- stroke: '#999999',
967
- strokeWidth: 1,
968
- fill: '#FFF',
969
- fillStyle: 'solid'
970
- });
971
- activeG.appendChild(targetCircle);
972
- activeG.appendChild(sourceCircle);
1919
+ if (this.hasResizeHandle) {
1920
+ activeG.classList.add('active');
1921
+ activeG.classList.add('line-handle');
1922
+ const points = PlaitLine.getPoints(this.board, element);
1923
+ points.forEach(point => {
1924
+ const circle = drawCircle(PlaitBoard.getRoughSVG(this.board), point, RESIZE_HANDLE_DIAMETER, {
1925
+ stroke: '#999999',
1926
+ strokeWidth: 1,
1927
+ fill: '#FFF',
1928
+ fillStyle: 'solid'
1929
+ });
1930
+ activeG.appendChild(circle);
1931
+ });
1932
+ getMiddlePoints(this.board, element).forEach(point => {
1933
+ const circle = drawCircle(PlaitBoard.getRoughSVG(this.board), point, RESIZE_HANDLE_DIAMETER, {
1934
+ stroke: '#FFFFFF80',
1935
+ strokeWidth: 1,
1936
+ fill: `${PRIMARY_COLOR}80`,
1937
+ fillStyle: 'solid'
1938
+ });
1939
+ activeG.appendChild(circle);
1940
+ });
1941
+ }
1942
+ else {
1943
+ const points = getLinePoints(this.board, element);
1944
+ const activeRectangle = getRectangleByPoints(points);
1945
+ const strokeG = drawRectangle(this.board, activeRectangle, {
1946
+ stroke: PRIMARY_COLOR,
1947
+ strokeWidth: DefaultGeometryActiveStyle.selectionStrokeWidth
1948
+ });
1949
+ activeG.appendChild(strokeG);
1950
+ }
973
1951
  return activeG;
974
1952
  }
975
1953
  }
1954
+ function getMiddlePoints(board, element) {
1955
+ const result = [];
1956
+ const shape = element.shape;
1957
+ if (shape === LineShape.straight) {
1958
+ const points = PlaitLine.getPoints(board, element);
1959
+ for (let i = 0; i < points.length - 1; i++) {
1960
+ result.push([(points[i][0] + points[i + 1][0]) / 2, (points[i][1] + points[i + 1][1]) / 2]);
1961
+ }
1962
+ }
1963
+ if (shape === LineShape.curve) {
1964
+ const points = PlaitLine.getPoints(board, element);
1965
+ const pointsOnBezier = getCurvePoints(board, element);
1966
+ if (points.length === 2) {
1967
+ const start = 0;
1968
+ const endIndex = pointsOnBezier.length - 1;
1969
+ const middleIndex = Math.round((start + endIndex) / 2);
1970
+ result.push(pointsOnBezier[middleIndex]);
1971
+ }
1972
+ else {
1973
+ for (let i = 0; i < points.length - 1; i++) {
1974
+ const startIndex = pointsOnBezier.findIndex(point => point[0] === points[i][0] && point[1] === points[i][1]);
1975
+ const endIndex = pointsOnBezier.findIndex(point => point[0] === points[i + 1][0] && point[1] === points[i + 1][1]);
1976
+ const middleIndex = Math.round((startIndex + endIndex) / 2);
1977
+ result.push(pointsOnBezier[middleIndex]);
1978
+ }
1979
+ }
1980
+ }
1981
+ return result;
1982
+ }
976
1983
 
977
1984
  class LineComponent extends PlaitPluginElementComponent {
978
1985
  constructor(viewContainerRef, cdr) {
@@ -1028,11 +2035,18 @@ class LineComponent extends PlaitPluginElementComponent {
1028
2035
  this.updateTextRectangle();
1029
2036
  return;
1030
2037
  }
1031
- const hasSameSelected = value.selected === previous.selected;
1032
- if (!hasSameSelected) {
2038
+ if (!isSelectionMoving(this.board)) {
2039
+ this.activeGenerator.hasResizeHandle = this.hasResizeHandle();
1033
2040
  this.activeGenerator.draw(this.element, PlaitBoard.getElementActiveHost(this.board), { selected: this.selected });
1034
2041
  }
1035
2042
  }
2043
+ hasResizeHandle() {
2044
+ const selectedElements = getSelectedElements(this.board);
2045
+ if (PlaitBoard.hasBeenTextEditing(this.board) && PlaitDrawElement.isText(this.element)) {
2046
+ return false;
2047
+ }
2048
+ return selectedElements.length === 1 && !isSelectionMoving(this.board);
2049
+ }
1036
2050
  initializeTextManages() {
1037
2051
  if (this.element.texts?.length) {
1038
2052
  this.element.texts.forEach((text, index) => {
@@ -1105,14 +2119,15 @@ class LineComponent extends PlaitPluginElementComponent {
1105
2119
  this.destroy$.complete();
1106
2120
  }
1107
2121
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "16.2.3", ngImport: i0, type: LineComponent, deps: [{ token: i0.ViewContainerRef }, { token: i0.ChangeDetectorRef }], target: i0.ɵɵFactoryTarget.Component }); }
1108
- static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "16.2.3", type: LineComponent, selector: "plait-draw-line", usesInheritance: true, ngImport: i0, template: ``, isInline: true, changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
2122
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "16.2.3", type: LineComponent, isStandalone: true, selector: "plait-draw-line", usesInheritance: true, ngImport: i0, template: ``, isInline: true, changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
1109
2123
  }
1110
2124
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "16.2.3", ngImport: i0, type: LineComponent, decorators: [{
1111
2125
  type: Component,
1112
2126
  args: [{
1113
2127
  selector: 'plait-draw-line',
1114
2128
  template: ``,
1115
- changeDetection: ChangeDetectionStrategy.OnPush
2129
+ changeDetection: ChangeDetectionStrategy.OnPush,
2130
+ standalone: true
1116
2131
  }]
1117
2132
  }], ctorParameters: function () { return [{ type: i0.ViewContainerRef }, { type: i0.ChangeDetectorRef }]; } });
1118
2133
 
@@ -1152,13 +2167,14 @@ const withGeometryCreateByDrag = (board) => {
1152
2167
  geometryShapeG?.remove();
1153
2168
  geometryShapeG = createG();
1154
2169
  const geometryGenerator = new GeometryShapeGenerator(board);
1155
- const isGeometryPointer = PlaitBoard.isInPointer(board, GeometryPointer);
2170
+ const geometryPointers = getGeometryPointers();
2171
+ const isGeometryPointer = PlaitBoard.isInPointer(board, geometryPointers);
1156
2172
  const dragMode = isGeometryPointer && isDndMode(board);
1157
2173
  const movingPoint = transformPoint(board, toPoint(event.x, event.y, PlaitBoard.getHost(board)));
1158
2174
  const pointer = PlaitBoard.getPointer(board);
1159
2175
  if (dragMode) {
1160
2176
  const points = getDefaultGeometryPoints(pointer, movingPoint);
1161
- if (pointer === DrawPointerType.text) {
2177
+ if (pointer === GeometryShape.text) {
1162
2178
  const textG = getTemporaryTextG(movingPoint);
1163
2179
  geometryShapeG.appendChild(textG);
1164
2180
  PlaitBoard.getElementActiveHost(board).append(geometryShapeG);
@@ -1176,12 +2192,13 @@ const withGeometryCreateByDrag = (board) => {
1176
2192
  };
1177
2193
  board.pointerUp = (event) => {
1178
2194
  const pointer = PlaitBoard.getPointer(board);
1179
- const isGeometryPointer = PlaitBoard.isInPointer(board, GeometryPointer);
2195
+ const geometryPointers = getGeometryPointers();
2196
+ const isGeometryPointer = PlaitBoard.isInPointer(board, geometryPointers);
1180
2197
  const dragMode = isGeometryPointer && isDndMode(board);
1181
2198
  if (dragMode) {
1182
2199
  const targetPoint = transformPoint(board, toPoint(event.x, event.y, PlaitBoard.getHost(board)));
1183
2200
  const points = getDefaultGeometryPoints(pointer, targetPoint);
1184
- if (pointer === DrawPointerType.text) {
2201
+ if (pointer === GeometryShape.text) {
1185
2202
  DrawTransforms.insertText(board, points);
1186
2203
  }
1187
2204
  else {
@@ -1196,7 +2213,7 @@ const withGeometryCreateByDrag = (board) => {
1196
2213
  };
1197
2214
  return board;
1198
2215
  };
1199
- const withGeometryCreateByDraw = (board) => {
2216
+ const withGeometryCreateByDrawing = (board) => {
1200
2217
  const { pointerDown, pointerMove, pointerUp, keydown, keyup } = board;
1201
2218
  let start = null;
1202
2219
  let geometryShapeG = null;
@@ -1211,13 +2228,14 @@ const withGeometryCreateByDraw = (board) => {
1211
2228
  keyup(event);
1212
2229
  };
1213
2230
  board.pointerDown = (event) => {
1214
- const isGeometryPointer = PlaitBoard.isInPointer(board, GeometryPointer);
2231
+ const geometryPointers = getGeometryPointers();
2232
+ const isGeometryPointer = PlaitBoard.isInPointer(board, geometryPointers);
1215
2233
  if (isGeometryPointer && isDrawingMode(board)) {
1216
2234
  const point = transformPoint(board, toPoint(event.x, event.y, PlaitBoard.getHost(board)));
1217
2235
  start = point;
1218
2236
  const pointer = PlaitBoard.getPointer(board);
1219
2237
  preventTouchMove(board, event, true);
1220
- if (pointer === DrawPointerType.text) {
2238
+ if (pointer === GeometryShape.text) {
1221
2239
  const points = getDefaultGeometryPoints(pointer, point);
1222
2240
  const textElement = createGeometryElement(GeometryShape.text, points, DefaultTextProperty.text);
1223
2241
  Transforms.insertNode(board, textElement, [board.children.length]);
@@ -1236,7 +2254,7 @@ const withGeometryCreateByDraw = (board) => {
1236
2254
  const drawMode = !!start;
1237
2255
  const movingPoint = transformPoint(board, toPoint(event.x, event.y, PlaitBoard.getHost(board)));
1238
2256
  const pointer = PlaitBoard.getPointer(board);
1239
- if (drawMode && pointer !== DrawPointerType.text) {
2257
+ if (drawMode && pointer !== GeometryShape.text) {
1240
2258
  const points = normalizeShapePoints([start, movingPoint], isShift);
1241
2259
  temporaryElement = createGeometryElement(pointer, points, '', {
1242
2260
  strokeColor: DefaultGeometryProperty.strokeColor,
@@ -1255,7 +2273,7 @@ const withGeometryCreateByDraw = (board) => {
1255
2273
  if (Math.hypot(width, height) === 0) {
1256
2274
  const pointer = PlaitBoard.getPointer(board);
1257
2275
  const points = getDefaultGeometryPoints(pointer, targetPoint);
1258
- if (pointer !== DrawPointerType.text) {
2276
+ if (pointer !== GeometryShape.text) {
1259
2277
  temporaryElement = createGeometryElement(pointer, points, '', {
1260
2278
  strokeColor: DefaultGeometryProperty.strokeColor,
1261
2279
  strokeWidth: DefaultGeometryProperty.strokeWidth
@@ -1279,7 +2297,7 @@ const withGeometryCreateByDraw = (board) => {
1279
2297
  return board;
1280
2298
  };
1281
2299
  const getDefaultGeometryPoints = (pointer, targetPoint) => {
1282
- return pointer === DrawPointerType.text
2300
+ return pointer === GeometryShape.text
1283
2301
  ? getPointsByCenterPoint(targetPoint, DefaultTextProperty.width, DefaultTextProperty.height)
1284
2302
  : getPointsByCenterPoint(targetPoint, DefaultGeometryProperty.width, DefaultGeometryProperty.height);
1285
2303
  };
@@ -1298,7 +2316,7 @@ const getTemporaryTextG = (movingPoint) => {
1298
2316
 
1299
2317
  const buildClipboardData = (board, elements, startPoint) => {
1300
2318
  return elements.map(element => {
1301
- if (PlaitDrawElement.isGeometry(element)) {
2319
+ if (PlaitDrawElement.isGeometry(element) || PlaitDrawElement.isImage(element)) {
1302
2320
  const points = element.points.map(point => [point[0] - startPoint[0], point[1] - startPoint[1]]);
1303
2321
  return { ...element, points };
1304
2322
  }
@@ -1321,7 +2339,7 @@ const buildClipboardData = (board, elements, startPoint) => {
1321
2339
  };
1322
2340
  const insertClipboardData = (board, elements, startPoint) => {
1323
2341
  const lines = elements.filter(value => PlaitDrawElement.isLine(value));
1324
- const geometries = elements.filter(value => PlaitDrawElement.isGeometry(value));
2342
+ const geometries = elements.filter(value => PlaitDrawElement.isGeometry(value) || PlaitDrawElement.isImage(value));
1325
2343
  geometries.forEach(element => {
1326
2344
  const sourceLines = [];
1327
2345
  const targetLines = [];
@@ -1345,6 +2363,7 @@ const insertClipboardData = (board, elements, startPoint) => {
1345
2363
  element.points = element.points.map(point => [startPoint[0] + point[0], startPoint[1] + point[1]]);
1346
2364
  Transforms.insertNode(board, element, [board.children.length]);
1347
2365
  });
2366
+ Transforms.addSelectionWithTemporaryElements(board, elements);
1348
2367
  };
1349
2368
 
1350
2369
  const withDrawFragment = (baseBoard) => {
@@ -1353,21 +2372,35 @@ const withDrawFragment = (baseBoard) => {
1353
2372
  board.getDeletedFragment = (data) => {
1354
2373
  const drawElements = getSelectedDrawElements(board);
1355
2374
  if (drawElements.length) {
1356
- const lines = getBoardLines(board);
1357
2375
  const geometryElements = drawElements.filter(value => PlaitDrawElement.isGeometry(value));
1358
2376
  const lineElements = drawElements.filter(value => PlaitDrawElement.isLine(value));
1359
- const boundLineElements = lines.filter(line => geometryElements.find(geometry => PlaitLine.isBoundElementOfSource(line, geometry) || PlaitLine.isBoundElementOfTarget(line, geometry)));
1360
- data.push(...[...geometryElements, ...lineElements, ...boundLineElements.filter(line => !lineElements.includes(line))]);
2377
+ const imageElements = drawElements.filter(value => PlaitDrawElement.isImage(value));
2378
+ const boundLineElements = [
2379
+ ...getBoundedLineElements(board, geometryElements),
2380
+ ...getBoundedLineElements(board, imageElements)
2381
+ ].filter(line => !lineElements.includes(line));
2382
+ data.push(...[
2383
+ ...geometryElements,
2384
+ ...lineElements,
2385
+ ...imageElements,
2386
+ ...boundLineElements.filter(line => !lineElements.includes(line))
2387
+ ]);
1361
2388
  }
1362
2389
  return getDeletedFragment(data);
1363
2390
  };
1364
- board.setFragment = (data, rectangle) => {
2391
+ board.setFragment = (data, rectangle, type) => {
1365
2392
  const targetDrawElements = getSelectedDrawElements(board);
2393
+ let boundLineElements = [];
1366
2394
  if (targetDrawElements.length) {
1367
- const elements = buildClipboardData(board, targetDrawElements, rectangle ? [rectangle.x, rectangle.y] : [0, 0]);
2395
+ if (type === 'cut') {
2396
+ const geometryElements = targetDrawElements.filter(value => PlaitDrawElement.isGeometry(value));
2397
+ const lineElements = targetDrawElements.filter(value => PlaitDrawElement.isLine(value));
2398
+ boundLineElements = getBoundedLineElements(board, geometryElements).filter(line => !lineElements.includes(line));
2399
+ }
2400
+ const elements = buildClipboardData(board, [...targetDrawElements, ...boundLineElements], rectangle ? [rectangle.x, rectangle.y] : [0, 0]);
1368
2401
  setClipboardData(data, elements);
1369
2402
  }
1370
- setFragment(data, rectangle);
2403
+ setFragment(data, rectangle, type);
1371
2404
  };
1372
2405
  board.insertFragment = (data, targetPoint) => {
1373
2406
  const elements = getDataFromClipboard(data);
@@ -1387,10 +2420,24 @@ const withDrawFragment = (baseBoard) => {
1387
2420
  return;
1388
2421
  }
1389
2422
  }
2423
+ if (data?.files.length) {
2424
+ const acceptImageArray = acceptImageTypes.map(type => 'image/' + type);
2425
+ if (acceptImageArray.includes(data?.files[0].type)) {
2426
+ const imageFile = data.files[0];
2427
+ buildImage(board, imageFile, DEFAULT_IMAGE_WIDTH, imageItem => {
2428
+ DrawTransforms.insertImage(board, imageItem, targetPoint);
2429
+ });
2430
+ return;
2431
+ }
2432
+ }
1390
2433
  insertFragment(data, targetPoint);
1391
2434
  };
1392
2435
  return board;
1393
2436
  };
2437
+ const getBoundedLineElements = (board, plaitShapes) => {
2438
+ const lines = getBoardLines(board);
2439
+ return lines.filter(line => plaitShapes.find(shape => PlaitLine.isBoundElementOfSource(line, shape) || PlaitLine.isBoundElementOfTarget(line, shape)));
2440
+ };
1394
2441
 
1395
2442
  const DefaultLineStyle = {
1396
2443
  strokeWidth: 2,
@@ -1408,10 +2455,10 @@ const getHitGeometryResizeHandleRef = (board, element, point) => {
1408
2455
  const getHitOutlineGeometry = (board, point, offset = 0) => {
1409
2456
  let geometry = null;
1410
2457
  depthFirstRecursion(board, node => {
1411
- if (PlaitDrawElement.isGeometry(node)) {
1412
- const shape = node.shape;
2458
+ if (PlaitDrawElement.isGeometry(node) || PlaitDrawElement.isImage(node)) {
1413
2459
  let client = getRectangleByPoints(node.points);
1414
2460
  client = RectangleClient.getOutlineRectangle(client, offset);
2461
+ const shape = getShape(node);
1415
2462
  const isHit = getEngine(shape).isHit(client, point);
1416
2463
  if (isHit) {
1417
2464
  geometry = node;
@@ -1429,7 +2476,8 @@ const withLineCreateByDraw = (board) => {
1429
2476
  let lineShapeG = null;
1430
2477
  let temporaryElement = null;
1431
2478
  board.pointerDown = (event) => {
1432
- const isLinePointer = PlaitBoard.isPointer(board, DrawPointerType.line);
2479
+ const linePointers = getLinePointers();
2480
+ const isLinePointer = PlaitBoard.isInPointer(board, linePointers);
1433
2481
  if (isLinePointer && isDrawingMode(board)) {
1434
2482
  const point = transformPoint(board, toPoint(event.x, event.y, PlaitBoard.getHost(board)));
1435
2483
  start = point;
@@ -1451,7 +2499,8 @@ const withLineCreateByDraw = (board) => {
1451
2499
  targetRef.connection = hitElement ? transformPointToConnection(board, movingPoint, hitElement) : undefined;
1452
2500
  targetRef.boundId = hitElement ? hitElement.id : undefined;
1453
2501
  const lineGenerator = new LineShapeGenerator(board);
1454
- temporaryElement = createLineElement(LineShape.elbow, [start, movingPoint], { marker: LineMarkerType.none, connection: sourceRef.connection, boundId: sourceRef?.boundId }, { marker: LineMarkerType.arrow, connection: targetRef.connection, boundId: targetRef?.boundId }, {
2502
+ const lineShape = PlaitBoard.getPointer(board);
2503
+ temporaryElement = createLineElement(lineShape, [start, movingPoint], { marker: LineMarkerType.none, connection: sourceRef.connection, boundId: sourceRef?.boundId }, { marker: LineMarkerType.arrow, connection: targetRef.connection, boundId: targetRef?.boundId }, {
1455
2504
  strokeColor: DefaultLineStyle.strokeColor,
1456
2505
  strokeWidth: DefaultLineStyle.strokeWidth
1457
2506
  });
@@ -1496,12 +2545,12 @@ const withGeometryResize = (board) => {
1496
2545
  return true;
1497
2546
  },
1498
2547
  detect: (point) => {
1499
- const selectedGeometryElements = getSelectedGeometryElements(board);
1500
- if (selectedGeometryElements.length !== 1 || getSelectedElements(board).length !== 1) {
2548
+ const selectedElements = [...getSelectedGeometryElements(board), ...getSelectedImageElements(board)];
2549
+ if (selectedElements.length !== 1 || getSelectedElements(board).length !== 1) {
1501
2550
  return null;
1502
2551
  }
1503
- const target = selectedGeometryElements[0];
1504
- const targetComponent = PlaitElement.getComponent(selectedGeometryElements[0]);
2552
+ const target = selectedElements[0];
2553
+ const targetComponent = PlaitElement.getComponent(selectedElements[0]);
1505
2554
  if (targetComponent.activeGenerator.hasResizeHandle) {
1506
2555
  const handleRef = getHitGeometryResizeHandleRef(board, target, point);
1507
2556
  if (handleRef) {
@@ -1516,27 +2565,34 @@ const withGeometryResize = (board) => {
1516
2565
  },
1517
2566
  onResize: (resizeRef, resizeState) => {
1518
2567
  let points = [...resizeRef.element.points];
2568
+ const rectangle = getRectangleByPoints(resizeRef.element.points);
2569
+ const ratio = rectangle.height / rectangle.width;
1519
2570
  if (resizeRef.handle === ResizeHandle.nw) {
1520
2571
  points = [resizeState.endTransformPoint, resizeRef.element.points[1]];
1521
2572
  }
1522
2573
  if (resizeRef.handle === ResizeHandle.ne) {
1523
- points = [
1524
- [resizeRef.element.points[0][0], resizeState.endTransformPoint[1]],
1525
- [resizeState.endTransformPoint[0], resizeRef.element.points[1][1]]
1526
- ];
2574
+ points = [resizeState.endTransformPoint, [resizeRef.element.points[0][0], resizeRef.element.points[1][1]]];
1527
2575
  }
1528
2576
  if (resizeRef.handle === ResizeHandle.se) {
1529
- points = [resizeRef.element.points[0], resizeState.endTransformPoint];
2577
+ points = [resizeState.endTransformPoint, resizeRef.element.points[0]];
1530
2578
  }
1531
2579
  if (resizeRef.handle === ResizeHandle.sw) {
1532
- points = [
1533
- [resizeState.endTransformPoint[0], resizeRef.element.points[0][1]],
1534
- [resizeRef.element.points[1][0], resizeState.endTransformPoint[1]]
1535
- ];
2580
+ points = [resizeState.endTransformPoint, [resizeRef.element.points[1][0], resizeRef.element.points[0][1]]];
2581
+ }
2582
+ if (isShift || PlaitDrawElement.isImage(resizeRef.element)) {
2583
+ const rectangle = getRectangleByPoints(points);
2584
+ const factor = points[0][1] > points[1][1] ? 1 : -1;
2585
+ const height = rectangle.width * ratio * factor;
2586
+ points = [[resizeState.endTransformPoint[0], points[1][1] + height], points[1]];
2587
+ }
2588
+ if (PlaitDrawElement.isGeometry(resizeRef.element)) {
2589
+ const { height: textHeight } = PlaitGeometry.getTextManage(resizeRef.element).getSize();
2590
+ DrawTransforms.resizeGeometry(board, points, textHeight, resizeRef.path);
2591
+ }
2592
+ else {
2593
+ points = normalizeShapePoints(points);
2594
+ Transforms.setNode(board, { points }, resizeRef.path);
1536
2595
  }
1537
- points = normalizeShapePoints(points, isShift);
1538
- const { height: textHeight } = PlaitGeometry.getTextManage(resizeRef.element).getSize();
1539
- DrawTransforms.resizeGeometry(board, points, textHeight, resizeRef.path);
1540
2596
  }
1541
2597
  };
1542
2598
  withResize(board, options);
@@ -1547,34 +2603,44 @@ var LineResizeHandle;
1547
2603
  (function (LineResizeHandle) {
1548
2604
  LineResizeHandle["source"] = "source";
1549
2605
  LineResizeHandle["target"] = "target";
2606
+ LineResizeHandle["addHandle"] = "addHandle";
1550
2607
  })(LineResizeHandle || (LineResizeHandle = {}));
1551
2608
  const getHitLineResizeHandleRef = (board, element, point) => {
1552
- const sourcePoint = getSourcePoint(board, element);
1553
- const targetPoint = getTargetPoint(board, element);
1554
- const sourceRectangle = {
1555
- x: sourcePoint[0] - RESIZE_HANDLE_DIAMETER / 2,
1556
- y: sourcePoint[1] - RESIZE_HANDLE_DIAMETER / 2,
1557
- width: RESIZE_HANDLE_DIAMETER,
1558
- height: RESIZE_HANDLE_DIAMETER
1559
- };
1560
- const targetRectangle = {
1561
- x: targetPoint[0] - RESIZE_HANDLE_DIAMETER / 2,
1562
- y: targetPoint[1] - RESIZE_HANDLE_DIAMETER / 2,
1563
- width: RESIZE_HANDLE_DIAMETER,
1564
- height: RESIZE_HANDLE_DIAMETER
1565
- };
1566
- const isHitSourceRectangle = RectangleClient.isHit(RectangleClient.toRectangleClient([point, point]), sourceRectangle);
1567
- const isHitTargetRectangle = RectangleClient.isHit(RectangleClient.toRectangleClient([point, point]), targetRectangle);
1568
- if (isHitSourceRectangle) {
1569
- return { rectangle: sourceRectangle, handle: LineResizeHandle.source };
2609
+ const points = PlaitLine.getPoints(board, element);
2610
+ const index = getHitPointIndex(points, point);
2611
+ if (index !== -1) {
2612
+ if (index === 0) {
2613
+ return { handle: LineResizeHandle.source, index };
2614
+ }
2615
+ if (index === points.length - 1) {
2616
+ return { handle: LineResizeHandle.target, index };
2617
+ }
2618
+ return { index };
1570
2619
  }
1571
- if (isHitTargetRectangle) {
1572
- return { rectangle: targetRectangle, handle: LineResizeHandle.target };
2620
+ const middlePoints = getMiddlePoints(board, element);
2621
+ const middleIndex = getHitPointIndex(middlePoints, point);
2622
+ if (middleIndex !== -1) {
2623
+ return { handle: LineResizeHandle.addHandle, index: middleIndex };
1573
2624
  }
1574
2625
  return undefined;
1575
2626
  };
2627
+ function getHitPointIndex(points, movingPoint) {
2628
+ const rectangles = points.map(point => {
2629
+ return {
2630
+ x: point[0] - RESIZE_HANDLE_DIAMETER / 2,
2631
+ y: point[1] - RESIZE_HANDLE_DIAMETER / 2,
2632
+ width: RESIZE_HANDLE_DIAMETER,
2633
+ height: RESIZE_HANDLE_DIAMETER
2634
+ };
2635
+ });
2636
+ const rectangle = rectangles.find(rectangle => {
2637
+ return RectangleClient.isHit(RectangleClient.toRectangleClient([movingPoint, movingPoint]), rectangle);
2638
+ });
2639
+ return rectangle ? rectangles.indexOf(rectangle) : -1;
2640
+ }
1576
2641
 
1577
2642
  const withLineResize = (board) => {
2643
+ let pointIndex = 0;
1578
2644
  const options = {
1579
2645
  key: 'draw-line',
1580
2646
  canResize: () => {
@@ -1591,6 +2657,7 @@ const withLineResize = (board) => {
1591
2657
  element: value,
1592
2658
  handle: handleRef.handle
1593
2659
  };
2660
+ pointIndex = handleRef.index;
1594
2661
  }
1595
2662
  });
1596
2663
  return result;
@@ -1602,17 +2669,23 @@ const withLineResize = (board) => {
1602
2669
  let source = { ...resizeRef.element.source };
1603
2670
  let target = { ...resizeRef.element.target };
1604
2671
  if (resizeRef.handle === LineResizeHandle.source) {
1605
- points[0] = resizeState.endTransformPoint;
2672
+ points[pointIndex] = resizeState.endTransformPoint;
1606
2673
  const hitElement = getHitOutlineGeometry(board, resizeState.endTransformPoint, -4);
1607
2674
  source.connection = hitElement ? transformPointToConnection(board, resizeState.endTransformPoint, hitElement) : undefined;
1608
2675
  source.boundId = hitElement ? hitElement.id : undefined;
1609
2676
  }
1610
- if (resizeRef.handle === LineResizeHandle.target) {
1611
- points[1] = resizeState.endTransformPoint;
2677
+ else if (resizeRef.handle === LineResizeHandle.target) {
2678
+ points[pointIndex] = resizeState.endTransformPoint;
1612
2679
  const hitElement = getHitOutlineGeometry(board, resizeState.endTransformPoint, -4);
1613
2680
  target.connection = hitElement ? transformPointToConnection(board, resizeState.endTransformPoint, hitElement) : undefined;
1614
2681
  target.boundId = hitElement ? hitElement.id : undefined;
1615
2682
  }
2683
+ else if (resizeRef.handle === LineResizeHandle.addHandle) {
2684
+ points.splice(pointIndex + 1, 0, resizeState.endTransformPoint);
2685
+ }
2686
+ else {
2687
+ points[pointIndex] = resizeState.endTransformPoint;
2688
+ }
1616
2689
  DrawTransforms.resizeLine(board, { points, source, target }, resizeRef.path);
1617
2690
  }
1618
2691
  };
@@ -1625,9 +2698,14 @@ const withLineBoundReaction = (board) => {
1625
2698
  let boundShapeG = null;
1626
2699
  board.pointerMove = (event) => {
1627
2700
  boundShapeG?.remove();
1628
- const isLinePointer = PlaitBoard.isPointer(board, DrawPointerType.line);
2701
+ const linePointers = Object.keys(LineShape);
2702
+ const isLinePointer = PlaitBoard.isInPointer(board, linePointers);
1629
2703
  const movingPoint = transformPoint(board, toPoint(event.x, event.y, PlaitBoard.getHost(board)));
1630
- const isLineResizing = isResizingByCondition(board, element => PlaitDrawElement.isLine(element));
2704
+ const isLineResizing = isResizingByCondition(board, resizeRef => {
2705
+ const { element, handle } = resizeRef;
2706
+ const isSourceOrTarget = handle === LineResizeHandle.target || handle === LineResizeHandle.source;
2707
+ return PlaitDrawElement.isLine(element) && isSourceOrTarget;
2708
+ });
1631
2709
  if (isLinePointer || isLineResizing) {
1632
2710
  const hitElement = getHitOutlineGeometry(board, movingPoint, -4);
1633
2711
  if (hitElement) {
@@ -1705,8 +2783,74 @@ function editHandle(board, element, manageIndex, isFirstEdit = false) {
1705
2783
  });
1706
2784
  }
1707
2785
 
2786
+ class ImageComponent extends CommonPluginElement {
2787
+ get activeGenerator() {
2788
+ return this.imageGenerator.componentRef.instance.activeGenerator;
2789
+ }
2790
+ constructor(viewContainerRef, cdr) {
2791
+ super(cdr);
2792
+ this.viewContainerRef = viewContainerRef;
2793
+ this.cdr = cdr;
2794
+ this.destroy$ = new Subject();
2795
+ }
2796
+ initializeGenerator() {
2797
+ this.imageGenerator = new ImageGenerator(this.board, {
2798
+ getRectangle: (element) => {
2799
+ return {
2800
+ x: element.points[0][0],
2801
+ y: element.points[0][1],
2802
+ width: element.points[1][0] - element.points[0][0],
2803
+ height: element.points[1][1] - element.points[0][1]
2804
+ };
2805
+ },
2806
+ getImageItem: element => {
2807
+ return {
2808
+ url: element.url,
2809
+ width: element.points[1][0] - element.points[0][0],
2810
+ height: element.points[1][1] - element.points[0][1]
2811
+ };
2812
+ }
2813
+ });
2814
+ }
2815
+ ngOnInit() {
2816
+ super.ngOnInit();
2817
+ this.initializeGenerator();
2818
+ this.imageGenerator.draw(this.element, this.g, this.viewContainerRef);
2819
+ }
2820
+ onContextChanged(value, previous) {
2821
+ if (value.element !== previous.element) {
2822
+ this.imageGenerator.updateImage(this.g, previous.element, value.element);
2823
+ this.imageGenerator.componentRef.instance.isFocus = this.selected;
2824
+ }
2825
+ else {
2826
+ const hasSameSelected = value.selected === previous.selected;
2827
+ const hasSameHandleState = this.activeGenerator.options.hasResizeHandle() === this.activeGenerator.hasResizeHandle;
2828
+ if (!hasSameSelected || !hasSameHandleState) {
2829
+ this.imageGenerator.componentRef.instance.isFocus = this.selected;
2830
+ }
2831
+ }
2832
+ }
2833
+ ngOnDestroy() {
2834
+ super.ngOnDestroy();
2835
+ this.destroy$.next();
2836
+ this.destroy$.complete();
2837
+ this.imageGenerator.destroy();
2838
+ }
2839
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "16.2.3", ngImport: i0, type: ImageComponent, deps: [{ token: i0.ViewContainerRef }, { token: i0.ChangeDetectorRef }], target: i0.ɵɵFactoryTarget.Component }); }
2840
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "16.2.3", type: ImageComponent, isStandalone: true, selector: "plait-draw-geometry", usesInheritance: true, ngImport: i0, template: ``, isInline: true, changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
2841
+ }
2842
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "16.2.3", ngImport: i0, type: ImageComponent, decorators: [{
2843
+ type: Component,
2844
+ args: [{
2845
+ selector: 'plait-draw-geometry',
2846
+ template: ``,
2847
+ changeDetection: ChangeDetectionStrategy.OnPush,
2848
+ standalone: true
2849
+ }]
2850
+ }], ctorParameters: function () { return [{ type: i0.ViewContainerRef }, { type: i0.ChangeDetectorRef }]; } });
2851
+
1708
2852
  const withDraw = (board) => {
1709
- const { drawElement, getRectangle, isHitSelection, isMovable, dblclick } = board;
2853
+ const { drawElement, getRectangle, isHitSelection, isMovable, isAlign } = board;
1710
2854
  board.drawElement = (context) => {
1711
2855
  if (PlaitDrawElement.isGeometry(context.element)) {
1712
2856
  return GeometryComponent;
@@ -1714,6 +2858,9 @@ const withDraw = (board) => {
1714
2858
  else if (PlaitDrawElement.isLine(context.element)) {
1715
2859
  return LineComponent;
1716
2860
  }
2861
+ else if (PlaitDrawElement.isImage(context.element)) {
2862
+ return ImageComponent;
2863
+ }
1717
2864
  return drawElement(context);
1718
2865
  };
1719
2866
  board.getRectangle = (element) => {
@@ -1721,9 +2868,11 @@ const withDraw = (board) => {
1721
2868
  return getRectangleByPoints(element.points);
1722
2869
  }
1723
2870
  if (PlaitDrawElement.isLine(element)) {
1724
- const source = getSourcePoint(board, element);
1725
- const target = getTargetPoint(board, element);
1726
- return getRectangleByPoints([source, target]);
2871
+ const points = getLinePoints(board, element);
2872
+ return getRectangleByPoints(points);
2873
+ }
2874
+ if (PlaitDrawElement.isImage(element)) {
2875
+ return getRectangleByPoints(element.points);
1727
2876
  }
1728
2877
  return getRectangle(element);
1729
2878
  };
@@ -1737,6 +2886,11 @@ const withDraw = (board) => {
1737
2886
  }
1738
2887
  return RectangleClient.isHit(rangeRectangle, client);
1739
2888
  }
2889
+ if (PlaitDrawElement.isImage(element)) {
2890
+ const client = getRectangleByPoints(element.points);
2891
+ const rangeRectangle = RectangleClient.toRectangleClient([range.anchor, range.focus]);
2892
+ return RectangleClient.isHit(rangeRectangle, client);
2893
+ }
1740
2894
  if (PlaitDrawElement.isLine(element)) {
1741
2895
  const points = getLinePoints(board, element);
1742
2896
  const strokeWidth = getStrokeWidthByElement(element);
@@ -1755,17 +2909,34 @@ const withDraw = (board) => {
1755
2909
  if (PlaitDrawElement.isGeometry(element)) {
1756
2910
  return true;
1757
2911
  }
2912
+ if (PlaitDrawElement.isImage(element)) {
2913
+ return true;
2914
+ }
1758
2915
  if (PlaitDrawElement.isLine(element)) {
1759
- return !element.source.boundId && !element.source.boundId;
2916
+ const selectedElements = getSelectedElements(board);
2917
+ const isSelected = (boundId) => {
2918
+ return !!selectedElements.find(value => value.id === boundId);
2919
+ };
2920
+ if ((element.source.boundId && !isSelected(element.source.boundId)) ||
2921
+ (element.target.boundId && !isSelected(element.target.boundId))) {
2922
+ return false;
2923
+ }
2924
+ return true;
1760
2925
  }
1761
2926
  return isMovable(element);
1762
2927
  };
1763
- return withLineText(withLineBoundReaction(withLineResize(withGeometryResize(withLineCreateByDraw(withGeometryCreateByDrag(withGeometryCreateByDraw(withDrawFragment(withDrawHotkey(board)))))))));
2928
+ board.isAlign = (element) => {
2929
+ if (PlaitDrawElement.isGeometry(element) || PlaitDrawElement.isImage(element)) {
2930
+ return true;
2931
+ }
2932
+ return isAlign(element);
2933
+ };
2934
+ return withLineText(withLineBoundReaction(withLineResize(withGeometryResize(withLineCreateByDraw(withGeometryCreateByDrag(withGeometryCreateByDrawing(withDrawFragment(withDrawHotkey(board)))))))));
1764
2935
  };
1765
2936
 
1766
2937
  /**
1767
2938
  * Generated bundle index. Do not edit.
1768
2939
  */
1769
2940
 
1770
- export { DefaultGeometryActiveStyle, DefaultGeometryProperty, DefaultGeometryStyle, DefaultTextProperty, DrawPointerType, DrawTransforms, GeometryComponent, GeometryPointer, GeometryShape, GeometryThreshold, LineComponent, LineHandleKey, LineMarkerType, LineShape, PlaitDrawElement, PlaitGeometry, PlaitLine, ShapeDefaultSpace, StrokeStyle, createGeometryElement, createLineElement, drawBoundMask, drawGeometry, drawLine, getBoardLines, getCenterPointsOnPolygon, getConnectionPoint, getElbowPoints, getFillByElement, getHitConnectorPoint, getHitLineTextIndex, getLineDashByElement, getLinePoints, getLineTextRectangle, getNearestPoint, getPointsByCenterPoint, getSelectedDrawElements, getSelectedGeometryElements, getSelectedLineElements, getSourcePoint, getStraightPoints, getStrokeColorByElement, getStrokeStyleByElement, getStrokeWidthByElement, getTargetPoint, getTextRectangle, isHitLineText, isHitPolyLine, transformPointToConnection, withDraw };
2941
+ export { DEFAULT_IMAGE_WIDTH, DefaultGeometryActiveStyle, DefaultGeometryProperty, DefaultGeometryStyle, DefaultTextProperty, DrawTransforms, GeometryComponent, GeometryShape, GeometryThreshold, LineComponent, LineHandleKey, LineMarkerType, LineShape, PlaitDrawElement, PlaitGeometry, PlaitLine, Q2C, ShapeDefaultSpace, StrokeStyle, createGeometryElement, createLineElement, drawBoundMask, drawGeometry, drawLine, getBoardLines, getCenterPointsOnPolygon, getConnectionPoint, getCurvePoints, getEdgeOnPolygonByPoint, getElbowPoints, getExtendPoint, getFillByElement, getGeometryPointers, getHitConnectorPoint, getHitLineTextIndex, getLineDashByElement, getLineHandleRefPair, getLinePointers, getLinePoints, getLineTextRectangle, getNearestPoint, getPointsByCenterPoint, getSelectedDrawElements, getSelectedGeometryElements, getSelectedImageElements, getSelectedLineElements, getStrokeColorByElement, getStrokeStyleByElement, getStrokeWidthByElement, getTextRectangle, getVectorByConnection, isHitLineText, isHitPolyLine, removeDuplicatePoints, transformOpsToPoints, transformPointToConnection, withDraw };
1771
2942
  //# sourceMappingURL=plait-draw.mjs.map