@plait/draw 0.75.0-next.8 → 0.75.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.
@@ -1,5 +1,5 @@
1
- import { ACTIVE_STROKE_WIDTH, DEFAULT_COLOR, ThemeColorMode, PlaitElement, RectangleClient, getSelectedElements, idCreator, catmullRomFitting, PlaitBoard, createG, drawLinearPath, setStrokeLinecap, setPathStrokeLinecap, getNearestPointBetweenPointAndArc, distanceBetweenPointAndPoint, distanceBetweenPointAndSegments, HIT_DISTANCE_BUFFER, isPointInPolygon, isLineHitRectangle, rotatePointsByAngle, createDebugGenerator, rotateAntiPointsByElement, getEllipseArcCenter, Transforms, clearSelectedElement, addSelectedElement, BoardTransforms, PlaitPointerType, depthFirstRecursion, getIsRecursionFunc, SNAPPING_STROKE_WIDTH, SELECTION_BORDER_COLOR, SELECTION_FILL_COLOR, drawCircle, Point, arrowPoints, createPath, rotate, findElements, createMask, createRect, getElementById, rotatePointsByElement, PlaitNode, hasValidAngle, toViewBoxPoint, toHostPoint, Direction, rotatePoints, getRectangleByElements, getSelectionAngle, rotatedDataPoints, isAxisChangedByAngle, isSelectionMoving, drawRectangle, getRectangleByAngle, getSnapRectangles, getTripleAxis, getMinPointDelta, SNAP_TOLERANCE, drawPointSnapLines, drawSolidLines, getNearestPointBetweenPointAndSegments, isPointInEllipse, getNearestPointBetweenPointAndEllipse, getEllipseTangentSlope, getVectorFromPointAndSlope, drawRoundRectangle, isPointInRoundRectangle, getCrossingPointsBetweenEllipseAndSegment, drawLine, Path, RgbaToHEX, SELECTION_RECTANGLE_CLASS_NAME, getHitElementByPoint, WritableClipboardOperationType, WritableClipboardType, addOrCreateClipboardContext, setAngleForG, CursorClass, temporaryDisableSelection, PRESS_AND_MOVE_BUFFER, isMainPointer, throttleRAF, getAngleBetweenPoints, normalizeAngle, degreesToRadians, rotateElements, MERGING, ROTATE_HANDLE_CLASS_NAME, isSelectedElement, isDragging } from '@plait/core';
2
- import { DEFAULT_FILL, Alignment, WithTextPluginKey, TextManage, getMemorizedLatest, memorizeLatest, getPointOnPolyline, buildText, Generator, getStrokeLineDash, StrokeStyle, getCrossingPointsBetweenPointAndSegment, isPointOnSegment, getFirstTextEditor, sortElementsByArea, isFilled, getTextEditorsByElement, removeDuplicatePoints, generateElbowLineRoute, simplifyOrthogonalPoints, getExtendPoint, getUnitVectorByPointAndPoint, getPointByVectorComponent, RESIZE_HANDLE_DIAMETER, measureElement, DEFAULT_FONT_FAMILY, getFirstTextManage, ActiveGenerator, isSourceAndTargetIntersect, getPoints, DEFAULT_ROUTE_MARGIN, normalizeShapePoints, resetPointsAfterResize, getDirectionByVector, getRectangleResizeHandleRefs, getRotatedResizeCursorClassByAngle, ROTATE_HANDLE_DISTANCE_TO_ELEMENT, ROTATE_HANDLE_SIZE, isCornerHandle, getIndexByResizeHandle, withResize, drawHandle, getSymmetricHandleIndex, getResizeHandlePointByIndex, getDirectionFactorByDirectionComponent, buildClipboardData as buildClipboardData$1, insertClipboardData as insertClipboardData$1, getDirectionByPointOfRectangle, getDirectionFactor, rotateVector, getOppositeDirection, rotateVectorAnti90, getSourceAndTargetOuterRectangle, getNextPoint, PRIMARY_COLOR, CommonElementFlavour, canResize, drawPrimaryHandle, drawFillPrimaryHandle, isVirtualKey, isDelete, isSpaceHotkey, isDndMode, isDrawingMode, getElementsText, acceptImageTypes, getElementOfFocusedImage, buildImage, isResizingByCondition, getRatioByPoint, getTextManages, ImageGenerator, ResizeHandle, addRotating, removeRotating, drawRotateHandle } from '@plait/common';
1
+ import { ACTIVE_STROKE_WIDTH, DEFAULT_COLOR, ThemeColorMode, PlaitElement, RectangleClient, getSelectedElements, idCreator, catmullRomFitting, PlaitBoard, createG, drawLinearPath, setStrokeLinecap, setPathStrokeLinecap, getNearestPointBetweenPointAndArc, distanceBetweenPointAndPoint, distanceBetweenPointAndSegments, HIT_DISTANCE_BUFFER, isPointInPolygon, isLineHitRectangle, rotatePointsByAngle, createDebugGenerator, rotateAntiPointsByElement, getEllipseArcCenter, Transforms, clearSelectedElement, addSelectedElement, BoardTransforms, PlaitPointerType, depthFirstRecursion, getIsRecursionFunc, SNAPPING_STROKE_WIDTH, SELECTION_BORDER_COLOR, SELECTION_FILL_COLOR, drawCircle, Point, arrowPoints, createPath, rotate, findElements, createMask, createRect, getElementById, rotatePointsByElement, PlaitNode, hasValidAngle, toViewBoxPoint, toHostPoint, Direction, rotatePoints, getRectangleByElements, getSelectionAngle, rotatedDataPoints, isAxisChangedByAngle, isSelectionMoving, drawRectangle, getRectangleByAngle, getSnapRectangles, getTripleAxis, getMinPointDelta, SNAP_TOLERANCE, drawPointSnapLines, drawSolidLines, getNearestPointBetweenPointAndSegments, isPointInEllipse, getNearestPointBetweenPointAndEllipse, getEllipseTangentSlope, getVectorFromPointAndSlope, drawRoundRectangle, isPointInRoundRectangle, getCrossingPointsBetweenEllipseAndSegment, drawLine, getNearestPointBetweenPointAndDiscreteSegments, getNearestPointBetweenPointAndSegment, Path, RgbaToHEX, SELECTION_RECTANGLE_CLASS_NAME, getHitElementByPoint, WritableClipboardOperationType, WritableClipboardType, addOrCreateClipboardContext, setAngleForG, CursorClass, temporaryDisableSelection, PRESS_AND_MOVE_BUFFER, isMainPointer, throttleRAF, getAngleBetweenPoints, normalizeAngle, degreesToRadians, rotateElements, MERGING, ROTATE_HANDLE_CLASS_NAME, isSelectedElement, isDragging } from '@plait/core';
2
+ import { DEFAULT_FILL, Alignment, WithTextPluginKey, TextManage, getMemorizedLatest, memorizeLatest, getPointOnPolyline, buildText, Generator, getStrokeLineDash, StrokeStyle, getCrossingPointsBetweenPointAndSegment, isPointOnSegment, getFirstTextEditor, sortElementsByArea, isFilled, getTextEditorsByElement, removeDuplicatePoints, generateElbowLineRoute, simplifyOrthogonalPoints, getExtendPoint, getUnitVectorByPointAndPoint, getPointByVectorComponent, RESIZE_HANDLE_DIAMETER, measureElement, DEFAULT_FONT_FAMILY, getFirstTextManage, ActiveGenerator, isSourceAndTargetIntersect, getPoints, DEFAULT_ROUTE_MARGIN, normalizeShapePoints, resetPointsAfterResize, getDirectionByVector, getRectangleResizeHandleRefs, getRotatedResizeCursorClassByAngle, ROTATE_HANDLE_DISTANCE_TO_ELEMENT, ROTATE_HANDLE_SIZE, isCornerHandle, getIndexByResizeHandle, withResize, drawHandle, getSymmetricHandleIndex, getResizeHandlePointByIndex, getDirectionFactorByDirectionComponent, buildClipboardData as buildClipboardData$1, insertClipboardData as insertClipboardData$1, getDirectionByPointOfRectangle, getDirectionFactor, rotateVector, getOppositeDirection, rotateVectorAnti90, getSourceAndTargetOuterRectangle, getNextPoint, PRIMARY_COLOR, CommonElementFlavour, hasResizeHandle, drawPrimaryHandle, drawFillPrimaryHandle, isVirtualKey, isDelete, isSpaceHotkey, isDndMode, isDrawingMode, getElementsText, acceptImageTypes, getElementOfFocusedImage, buildImage, isResizingByCondition, getRatioByPoint, getTextManages, ImageGenerator, ResizeHandle, addRotating, removeRotating, drawRotateHandle } from '@plait/common';
3
3
  import { pointsOnBezierCurves } from 'points-on-curve';
4
4
  import { TEXT_DEFAULT_HEIGHT, DEFAULT_FONT_SIZE, AlignEditor } from '@plait/text-plugins';
5
5
  import { Editor, Node } from 'slate';
@@ -1069,7 +1069,7 @@ function generateCloudPath(rectangle) {
1069
1069
  const xRadius = divisionWidth / 8.5;
1070
1070
  const yRadius = divisionHeight / 20;
1071
1071
  const startPoint = [rectangle.x + divisionWidth, rectangle.y + divisionHeight];
1072
- const arcs = [
1072
+ const arcCommands = [
1073
1073
  {
1074
1074
  rx: xRadius,
1075
1075
  ry: yRadius * 1.2,
@@ -1143,15 +1143,15 @@ function generateCloudPath(rectangle) {
1143
1143
  endY: rectangle.y + divisionHeight
1144
1144
  }
1145
1145
  ];
1146
- return { startPoint, arcs };
1146
+ return { startPoint, arcCommands };
1147
1147
  }
1148
1148
  const CloudEngine = {
1149
1149
  draw(board, rectangle, options) {
1150
1150
  const rs = PlaitBoard.getRoughSVG(board);
1151
- const { startPoint, arcs } = generateCloudPath(rectangle);
1151
+ const { startPoint, arcCommands } = generateCloudPath(rectangle);
1152
1152
  const pathData = `M ${startPoint[0]} ${startPoint[1]} ` +
1153
- arcs
1154
- .map((arc) => `A ${arc.rx} ${arc.ry} ${arc.xAxisRotation} ${arc.largeArcFlag} ${arc.sweepFlag} ${arc.endX} ${arc.endY}`)
1153
+ arcCommands
1154
+ .map((command) => `A ${command.rx} ${command.ry} ${command.xAxisRotation} ${command.largeArcFlag} ${command.sweepFlag} ${command.endX} ${command.endY}`)
1155
1155
  .join('\n') +
1156
1156
  ' Z';
1157
1157
  const svgElement = rs.path(pathData, { ...options, fillStyle: 'solid' });
@@ -1166,19 +1166,18 @@ const CloudEngine = {
1166
1166
  return RectangleClient.getCornerPoints(rectangle);
1167
1167
  },
1168
1168
  getNearestPoint(rectangle, point) {
1169
- const { startPoint, arcs } = generateCloudPath(rectangle);
1169
+ const { startPoint, arcCommands } = generateCloudPath(rectangle);
1170
1170
  let minDistance = Infinity;
1171
1171
  let nearestPoint = point;
1172
- // 检查每个弧段
1173
1172
  let currentStart = startPoint;
1174
- for (const arc of arcs) {
1175
- const arcNearestPoint = getNearestPointBetweenPointAndArc(point, currentStart, arc);
1173
+ for (const arcCommand of arcCommands) {
1174
+ const arcNearestPoint = getNearestPointBetweenPointAndArc(point, currentStart, arcCommand);
1176
1175
  const distance = distanceBetweenPointAndPoint(point[0], point[1], arcNearestPoint[0], arcNearestPoint[1]);
1177
1176
  if (distance < minDistance) {
1178
1177
  minDistance = distance;
1179
1178
  nearestPoint = arcNearestPoint;
1180
1179
  }
1181
- currentStart = [arc.endX, arc.endY];
1180
+ currentStart = [arcCommand.endX, arcCommand.endY];
1182
1181
  }
1183
1182
  return nearestPoint;
1184
1183
  },
@@ -1216,7 +1215,7 @@ const isHitArrowLineText = (board, element, point) => {
1216
1215
  return getHitArrowLineTextIndex(board, element, point) !== -1;
1217
1216
  };
1218
1217
  const isHitPolyLine = (pathPoints, point) => {
1219
- const distance = distanceBetweenPointAndSegments(pathPoints, point);
1218
+ const distance = distanceBetweenPointAndSegments(point, pathPoints);
1220
1219
  return distance <= HIT_DISTANCE_BUFFER;
1221
1220
  };
1222
1221
  const isHitArrowLine = (board, element, point) => {
@@ -1349,13 +1348,13 @@ const isHitDrawElement = (board, element, point, isStrict = true) => {
1349
1348
  if (PlaitDrawElement.isGeometry(element) && rectangle) {
1350
1349
  if (debugGenerator$4.isDebug() && shapes.includes(element.shape)) {
1351
1350
  debugGenerator$4.clear();
1352
- const { startPoint, arcs } = generateCloudPath(rectangle);
1353
- const points = [startPoint, ...arcs.map((arc) => [arc.endX, arc.endY])];
1351
+ const { startPoint, arcCommands } = generateCloudPath(rectangle);
1352
+ const points = [startPoint, ...arcCommands.map((arc) => [arc.endX, arc.endY])];
1354
1353
  debugGenerator$4.drawCircles(board, points, 5, false);
1355
1354
  let minDistance = Infinity;
1356
1355
  let nearestPoint = point;
1357
1356
  let currentStart = startPoint;
1358
- for (const arc of arcs) {
1357
+ for (const arc of arcCommands) {
1359
1358
  const arcNearestPoint = getNearestPointBetweenPointAndArc(point, currentStart, arc);
1360
1359
  const distance = distanceBetweenPointAndPoint(point[0], point[1], arcNearestPoint[0], arcNearestPoint[1]);
1361
1360
  const { center } = getEllipseArcCenter(currentStart, arc);
@@ -5358,19 +5357,57 @@ const InternalStorageEngine = {
5358
5357
  }
5359
5358
  };
5360
5359
 
5360
+ function generateNoteCurlyLeftPath(rectangle) {
5361
+ const curlyWidth = rectangle.width * 0.09;
5362
+ const rightX = rectangle.x + rectangle.width;
5363
+ const centerY = rectangle.y + rectangle.height / 2;
5364
+ return {
5365
+ startPoint: [rightX, rectangle.y],
5366
+ upperCurve: {
5367
+ controlPoint1: [rightX - curlyWidth, rectangle.y],
5368
+ controlPoint2: [rightX, centerY],
5369
+ endPoint: [rightX - curlyWidth, centerY]
5370
+ },
5371
+ lowerCurve: {
5372
+ controlPoint1: [rightX, centerY],
5373
+ controlPoint2: [rightX - curlyWidth, rectangle.y + rectangle.height],
5374
+ endPoint: [rightX, rectangle.y + rectangle.height]
5375
+ }
5376
+ };
5377
+ }
5361
5378
  const NoteCurlyLeftEngine = {
5362
5379
  draw(board, rectangle, options) {
5363
5380
  const rs = PlaitBoard.getRoughSVG(board);
5364
- const shape = rs.path(`M${rectangle.x + rectangle.width} ${rectangle.y}
5365
- C${rectangle.x + rectangle.width - rectangle.width * 0.09} ${rectangle.y},
5366
- ${rectangle.x + rectangle.width} ${rectangle.y + rectangle.height / 2},
5367
- ${rectangle.x + rectangle.width - rectangle.width * 0.09} ${rectangle.y + rectangle.height / 2}
5368
- C${rectangle.x + rectangle.width} ${rectangle.y + rectangle.height / 2},
5369
- ${rectangle.x + rectangle.width - rectangle.width * 0.09} ${rectangle.y + rectangle.height},
5370
- ${rectangle.x + rectangle.width} ${rectangle.y + rectangle.height}`, { ...options, fillStyle: 'solid', fill: 'transparent' });
5381
+ const { startPoint, upperCurve, lowerCurve } = generateNoteCurlyLeftPath(rectangle);
5382
+ const pathData = [
5383
+ `M${startPoint[0]} ${startPoint[1]}`,
5384
+ `C${upperCurve.controlPoint1[0]} ${upperCurve.controlPoint1[1]},
5385
+ ${upperCurve.controlPoint2[0]} ${upperCurve.controlPoint2[1]},
5386
+ ${upperCurve.endPoint[0]} ${upperCurve.endPoint[1]}`,
5387
+ `C${lowerCurve.controlPoint1[0]} ${lowerCurve.controlPoint1[1]},
5388
+ ${lowerCurve.controlPoint2[0]} ${lowerCurve.controlPoint2[1]},
5389
+ ${lowerCurve.endPoint[0]} ${lowerCurve.endPoint[1]}`
5390
+ ].join(' ');
5391
+ const shape = rs.path(pathData, { ...options, fillStyle: 'solid', fill: 'transparent' });
5371
5392
  setStrokeLinecap(shape, 'round');
5372
5393
  return shape;
5373
5394
  },
5395
+ getNearestPoint(rectangle, point) {
5396
+ const { startPoint, upperCurve, lowerCurve } = generateNoteCurlyLeftPath(rectangle);
5397
+ const upperBezierPoints = pointsOnBezierCurves([startPoint, upperCurve.controlPoint1, upperCurve.controlPoint2, upperCurve.endPoint], 0.001);
5398
+ const lowerBezierPoints = pointsOnBezierCurves([upperCurve.endPoint, lowerCurve.controlPoint1, lowerCurve.controlPoint2, lowerCurve.endPoint], 0.001);
5399
+ const allPoints = [...upperBezierPoints, ...lowerBezierPoints];
5400
+ let minDistance = Infinity;
5401
+ let nearestPoint = point;
5402
+ for (const curvePoint of allPoints) {
5403
+ const distance = distanceBetweenPointAndPoint(point[0], point[1], curvePoint[0], curvePoint[1]);
5404
+ if (distance < minDistance) {
5405
+ minDistance = distance;
5406
+ nearestPoint = [...curvePoint];
5407
+ }
5408
+ }
5409
+ return nearestPoint;
5410
+ },
5374
5411
  isInsidePoint(rectangle, point) {
5375
5412
  const rangeRectangle = RectangleClient.getRectangleByPoints([point, point]);
5376
5413
  return RectangleClient.isHit(rectangle, rangeRectangle);
@@ -5378,9 +5415,6 @@ const NoteCurlyLeftEngine = {
5378
5415
  getCornerPoints(rectangle) {
5379
5416
  return RectangleClient.getCornerPoints(rectangle);
5380
5417
  },
5381
- getNearestPoint(rectangle, point) {
5382
- return getNearestPointBetweenPointAndSegments(point, RectangleEngine.getCornerPoints(rectangle));
5383
- },
5384
5418
  getEdgeByConnectionPoint(rectangle, pointOfRectangle) {
5385
5419
  const corners = RectangleEngine.getCornerPoints(rectangle);
5386
5420
  const point = RectangleClient.getConnectionPoint(rectangle, pointOfRectangle);
@@ -5403,16 +5437,37 @@ const NoteCurlyLeftEngine = {
5403
5437
  }
5404
5438
  };
5405
5439
 
5440
+ function generateNoteCurlyRightPath(rectangle) {
5441
+ const curlyWidth = rectangle.width * 0.09;
5442
+ const centerY = rectangle.y + rectangle.height / 2;
5443
+ return {
5444
+ startPoint: [rectangle.x, rectangle.y],
5445
+ upperCurve: {
5446
+ controlPoint1: [rectangle.x + curlyWidth, rectangle.y],
5447
+ controlPoint2: [rectangle.x, centerY],
5448
+ endPoint: [rectangle.x + curlyWidth, centerY]
5449
+ },
5450
+ lowerCurve: {
5451
+ controlPoint1: [rectangle.x, centerY],
5452
+ controlPoint2: [rectangle.x + curlyWidth, rectangle.y + rectangle.height],
5453
+ endPoint: [rectangle.x, rectangle.y + rectangle.height]
5454
+ }
5455
+ };
5456
+ }
5406
5457
  const NoteCurlyRightEngine = {
5407
5458
  draw(board, rectangle, options) {
5408
5459
  const rs = PlaitBoard.getRoughSVG(board);
5409
- const shape = rs.path(`M${rectangle.x} ${rectangle.y}
5410
- C${rectangle.x + rectangle.width * 0.09} ${rectangle.y},
5411
- ${rectangle.x} ${rectangle.y + rectangle.height / 2},
5412
- ${rectangle.x + rectangle.width * 0.09} ${rectangle.y + rectangle.height / 2}
5413
- C${rectangle.x} ${rectangle.y + rectangle.height / 2},
5414
- ${rectangle.x + rectangle.width * 0.09} ${rectangle.y + rectangle.height},
5415
- ${rectangle.x} ${rectangle.y + rectangle.height}`, { ...options, fillStyle: 'solid', fill: 'transparent' });
5460
+ const { startPoint, upperCurve, lowerCurve } = generateNoteCurlyRightPath(rectangle);
5461
+ const pathData = [
5462
+ `M${startPoint[0]} ${startPoint[1]}`,
5463
+ `C${upperCurve.controlPoint1[0]} ${upperCurve.controlPoint1[1]},
5464
+ ${upperCurve.controlPoint2[0]} ${upperCurve.controlPoint2[1]},
5465
+ ${upperCurve.endPoint[0]} ${upperCurve.endPoint[1]}`,
5466
+ `C${lowerCurve.controlPoint1[0]} ${lowerCurve.controlPoint1[1]},
5467
+ ${lowerCurve.controlPoint2[0]} ${lowerCurve.controlPoint2[1]},
5468
+ ${lowerCurve.endPoint[0]} ${lowerCurve.endPoint[1]}`
5469
+ ].join(' ');
5470
+ const shape = rs.path(pathData, { ...options, fillStyle: 'solid', fill: 'transparent' });
5416
5471
  setStrokeLinecap(shape, 'round');
5417
5472
  return shape;
5418
5473
  },
@@ -5424,7 +5479,24 @@ const NoteCurlyRightEngine = {
5424
5479
  return RectangleClient.getCornerPoints(rectangle);
5425
5480
  },
5426
5481
  getNearestPoint(rectangle, point) {
5427
- return getNearestPointBetweenPointAndSegments(point, RectangleEngine.getCornerPoints(rectangle));
5482
+ const { startPoint, upperCurve, lowerCurve } = generateNoteCurlyRightPath(rectangle);
5483
+ // 生成上部贝塞尔曲线的点
5484
+ const upperBezierPoints = pointsOnBezierCurves([startPoint, upperCurve.controlPoint1, upperCurve.controlPoint2, upperCurve.endPoint], 0.001);
5485
+ // 生成下部贝塞尔曲线的点
5486
+ const lowerBezierPoints = pointsOnBezierCurves([upperCurve.endPoint, lowerCurve.controlPoint1, lowerCurve.controlPoint2, lowerCurve.endPoint], 0.001);
5487
+ // 合并所有点
5488
+ const allPoints = [...upperBezierPoints, ...lowerBezierPoints];
5489
+ // 找到最近的点
5490
+ let minDistance = Infinity;
5491
+ let nearestPoint = [...point];
5492
+ for (const curvePoint of allPoints) {
5493
+ const distance = distanceBetweenPointAndPoint(point[0], point[1], curvePoint[0], curvePoint[1]);
5494
+ if (distance < minDistance) {
5495
+ minDistance = distance;
5496
+ nearestPoint = [...curvePoint];
5497
+ }
5498
+ }
5499
+ return nearestPoint;
5428
5500
  },
5429
5501
  getEdgeByConnectionPoint(rectangle, pointOfRectangle) {
5430
5502
  const corners = RectangleEngine.getCornerPoints(rectangle);
@@ -5656,24 +5728,71 @@ function getHorizontalTextRectangle(cell) {
5656
5728
  };
5657
5729
  }
5658
5730
 
5731
+ function generateActorPath(rectangle) {
5732
+ const centerX = rectangle.x + rectangle.width / 2;
5733
+ const headRadius = { width: rectangle.width / 3 / 2, height: rectangle.height / 4 / 2 };
5734
+ const centerY = rectangle.y + rectangle.height / 4 / 2;
5735
+ return {
5736
+ headArcCommand: {
5737
+ rx: headRadius.width,
5738
+ ry: headRadius.height,
5739
+ xAxisRotation: 0,
5740
+ largeArcFlag: 0,
5741
+ sweepFlag: 1,
5742
+ endX: centerX,
5743
+ endY: rectangle.y
5744
+ },
5745
+ bodyLine: [
5746
+ [centerX, rectangle.y + rectangle.height / 4],
5747
+ [centerX, rectangle.y + (rectangle.height / 4) * 3]
5748
+ ],
5749
+ armsLine: [
5750
+ [rectangle.x, rectangle.y + rectangle.height / 2],
5751
+ [rectangle.x + rectangle.width, rectangle.y + rectangle.height / 2]
5752
+ ],
5753
+ leftLegLine: [
5754
+ [centerX, rectangle.y + (rectangle.height / 4) * 3],
5755
+ [rectangle.x + rectangle.width / 12, rectangle.y + rectangle.height]
5756
+ ],
5757
+ rightLegLine: [
5758
+ [centerX, rectangle.y + (rectangle.height / 4) * 3],
5759
+ [rectangle.x + (rectangle.width / 12) * 11, rectangle.y + rectangle.height]
5760
+ ]
5761
+ };
5762
+ }
5659
5763
  const ActorEngine = {
5660
5764
  draw(board, rectangle, options) {
5661
5765
  const rs = PlaitBoard.getRoughSVG(board);
5662
- const shape = rs.path(`M${rectangle.x + rectangle.width / 2} ${rectangle.y + rectangle.height / 4}
5663
- A${rectangle.width / 3 / 2} ${rectangle.height / 4 / 2}, 0, 0, 1, ${rectangle.x + rectangle.width / 2} ${rectangle.y}
5664
- A${rectangle.width / 3 / 2} ${rectangle.height / 4 / 2}, 0, 0, 1, ${rectangle.x + rectangle.width / 2} ${rectangle.y +
5665
- rectangle.height / 4}
5666
- V${rectangle.y + (rectangle.height / 4) * 3}
5667
- M${rectangle.x + rectangle.width / 2} ${rectangle.y + rectangle.height / 2} H${rectangle.x}
5668
- M${rectangle.x + rectangle.width / 2} ${rectangle.y + rectangle.height / 2} H${rectangle.x + rectangle.width}
5669
- M${rectangle.x + rectangle.width / 2} ${rectangle.y + (rectangle.height / 4) * 3}
5670
- L${rectangle.x + rectangle.width / 12} ${rectangle.y + rectangle.height}
5671
- M${rectangle.x + rectangle.width / 2} ${rectangle.y + (rectangle.height / 4) * 3}
5672
- L${rectangle.x + (rectangle.width / 12) * 11} ${rectangle.y + rectangle.height}
5673
- `, { ...options, fillStyle: 'solid' });
5766
+ const { headArcCommand, bodyLine, armsLine, leftLegLine, rightLegLine } = generateActorPath(rectangle);
5767
+ const pathData = [
5768
+ // 头部(从中间开始画)
5769
+ `M${bodyLine[0][0]} ${bodyLine[0][1]}`,
5770
+ `A${headArcCommand.rx} ${headArcCommand.ry} ${headArcCommand.xAxisRotation} ${headArcCommand.largeArcFlag} ${headArcCommand.sweepFlag} ${headArcCommand.endX} ${headArcCommand.endY}`,
5771
+ `A${headArcCommand.rx} ${headArcCommand.ry} ${headArcCommand.xAxisRotation} ${headArcCommand.largeArcFlag} ${headArcCommand.sweepFlag} ${bodyLine[0][0]} ${bodyLine[0][1]}`,
5772
+ // 身体
5773
+ `V${bodyLine[1][1]}`,
5774
+ // 手臂
5775
+ `M${armsLine[0][0]} ${armsLine[0][1]} H${armsLine[1][0]}`,
5776
+ //
5777
+ `M${leftLegLine[0][0]} ${leftLegLine[0][1]} L${leftLegLine[1][0]} ${leftLegLine[1][1]}`,
5778
+ `M${rightLegLine[0][0]} ${rightLegLine[0][1]} L${rightLegLine[1][0]} ${rightLegLine[1][1]}`
5779
+ ].join(' ');
5780
+ const shape = rs.path(pathData, { ...options, fillStyle: 'solid' });
5674
5781
  setStrokeLinecap(shape, 'round');
5675
5782
  return shape;
5676
5783
  },
5784
+ getNearestPoint(rectangle, point) {
5785
+ const { headArcCommand, bodyLine, armsLine, leftLegLine, rightLegLine } = generateActorPath(rectangle);
5786
+ // 检查头部椭圆
5787
+ const headCenter = [rectangle.x + rectangle.width / 2, rectangle.y + rectangle.height / 4 / 2];
5788
+ const nearestPointForHead = getNearestPointBetweenPointAndEllipse(point, headCenter, headArcCommand.rx, headArcCommand.ry);
5789
+ const distanceForHead = distanceBetweenPointAndPoint(...point, ...nearestPointForHead);
5790
+ // 检查所有线段
5791
+ const allSegments = [bodyLine, armsLine, leftLegLine, rightLegLine];
5792
+ const nearestPointForLines = getNearestPointBetweenPointAndDiscreteSegments(point, allSegments);
5793
+ const distanceForLines = distanceBetweenPointAndPoint(...point, ...nearestPointForLines);
5794
+ return distanceForHead < distanceForLines ? nearestPointForHead : nearestPointForLines;
5795
+ },
5677
5796
  isInsidePoint(rectangle, point) {
5678
5797
  const rangeRectangle = RectangleClient.getRectangleByPoints([point, point]);
5679
5798
  return RectangleClient.isHit(rectangle, rangeRectangle);
@@ -5681,35 +5800,6 @@ const ActorEngine = {
5681
5800
  getCornerPoints(rectangle) {
5682
5801
  return RectangleClient.getCornerPoints(rectangle);
5683
5802
  },
5684
- getNearestPoint(rectangle, point) {
5685
- let nearestPoint = getNearestPointBetweenPointAndSegments(point, RectangleEngine.getCornerPoints(rectangle));
5686
- if (nearestPoint[1] >= rectangle.y && nearestPoint[1] <= rectangle.y + rectangle.height / 4) {
5687
- const centerPoint = [rectangle.x + rectangle.width / 2, rectangle.y + rectangle.height / 4 / 2];
5688
- nearestPoint = getNearestPointBetweenPointAndEllipse(point, centerPoint, rectangle.width / 3 / 2, rectangle.height / 4 / 2);
5689
- return nearestPoint;
5690
- }
5691
- if (nearestPoint[1] >= rectangle.y + rectangle.height / 4 && nearestPoint[1] < rectangle.y + (rectangle.height / 4) * 3) {
5692
- if (nearestPoint[1] === rectangle.x + rectangle.width / 2) {
5693
- nearestPoint = getNearestPointBetweenPointAndSegments(point, [
5694
- [rectangle.x + rectangle.width / 2, rectangle.y + rectangle.height / 4],
5695
- [rectangle.x + rectangle.width / 2, rectangle.y + (rectangle.height / 4) * 3]
5696
- ]);
5697
- }
5698
- else {
5699
- nearestPoint = getNearestPointBetweenPointAndSegments(point, [
5700
- [rectangle.x, rectangle.y + rectangle.height / 2],
5701
- [rectangle.x + rectangle.width, rectangle.y + rectangle.height / 2]
5702
- ]);
5703
- }
5704
- return nearestPoint;
5705
- }
5706
- nearestPoint = getNearestPointBetweenPointAndSegments(point, [
5707
- [rectangle.x + rectangle.width / 12, rectangle.y + rectangle.height],
5708
- [rectangle.x + rectangle.width / 2, rectangle.y + (rectangle.height / 4) * 3],
5709
- [rectangle.x + (rectangle.width / 12) * 11, rectangle.y + rectangle.height]
5710
- ]);
5711
- return nearestPoint;
5712
- },
5713
5803
  getConnectorPoints(rectangle) {
5714
5804
  return RectangleClient.getEdgeCenterPoints(rectangle);
5715
5805
  },
@@ -5794,18 +5884,41 @@ const ContainerEngine = {
5794
5884
  }
5795
5885
  };
5796
5886
 
5887
+ function generatePackagePath(rectangle) {
5888
+ const headerHeight = 25;
5889
+ const topWidth = rectangle.width * 0.7;
5890
+ const cornerX = rectangle.x + rectangle.width * 0.8;
5891
+ return {
5892
+ headerHeight,
5893
+ points: {
5894
+ leftTop: [rectangle.x, rectangle.y + headerHeight],
5895
+ topStart: [rectangle.x, rectangle.y],
5896
+ topEnd: [rectangle.x + topWidth, rectangle.y],
5897
+ cornerPoint: [cornerX, rectangle.y + headerHeight],
5898
+ rightTop: [rectangle.x + rectangle.width, rectangle.y + headerHeight],
5899
+ rightBottom: [rectangle.x + rectangle.width, rectangle.y + rectangle.height],
5900
+ leftBottom: [rectangle.x, rectangle.y + rectangle.height],
5901
+ leftMiddle: [rectangle.x, rectangle.y + headerHeight],
5902
+ middlePoint: [cornerX, rectangle.y + headerHeight]
5903
+ }
5904
+ };
5905
+ }
5797
5906
  const PackageEngine = {
5798
5907
  draw(board, rectangle, options) {
5799
5908
  const rs = PlaitBoard.getRoughSVG(board);
5800
- const shape = rs.path(`M${rectangle.x} ${rectangle.y + 25}
5801
- V${rectangle.y}
5802
- H${rectangle.x + rectangle.width * 0.7}
5803
- L${rectangle.x + rectangle.width * 0.8} ${rectangle.y + 25}
5804
- H${rectangle.x + rectangle.width}
5805
- V${rectangle.y + rectangle.height}
5806
- H${rectangle.x}
5807
- V${rectangle.y + 25}
5808
- H${rectangle.x + rectangle.width * 0.8}`, { ...options, fillStyle: 'solid' });
5909
+ const { points } = generatePackagePath(rectangle);
5910
+ const pathData = [
5911
+ `M${points.leftTop[0]} ${points.leftTop[1]}`,
5912
+ `V${points.topStart[1]}`,
5913
+ `H${points.topEnd[0]}`,
5914
+ `L${points.cornerPoint[0]} ${points.cornerPoint[1]}`,
5915
+ `H${points.rightTop[0]}`,
5916
+ `V${points.rightBottom[1]}`,
5917
+ `H${points.leftBottom[0]}`,
5918
+ `V${points.leftMiddle[1]}`,
5919
+ `H${points.middlePoint[0]}`
5920
+ ].join(' ');
5921
+ const shape = rs.path(pathData, { ...options, fillStyle: 'solid' });
5809
5922
  setStrokeLinecap(shape, 'round');
5810
5923
  return shape;
5811
5924
  },
@@ -5817,15 +5930,23 @@ const PackageEngine = {
5817
5930
  return RectangleClient.getCornerPoints(rectangle);
5818
5931
  },
5819
5932
  getNearestPoint(rectangle, point) {
5820
- let nearestPoint = getNearestPointBetweenPointAndSegments(point, RectangleEngine.getCornerPoints(rectangle));
5821
- if (nearestPoint[0] > rectangle.x + rectangle.width * 0.7 && nearestPoint[1] <= rectangle.y + 25) {
5822
- nearestPoint = getNearestPointBetweenPointAndSegments(point, [
5823
- [rectangle.x + rectangle.width * 0.7, rectangle.y],
5824
- [rectangle.x + rectangle.width * 0.8, rectangle.y + 25],
5825
- [rectangle.x + rectangle.width, rectangle.y + 25]
5826
- ], false);
5827
- }
5828
- return nearestPoint;
5933
+ const { points } = generatePackagePath(rectangle);
5934
+ const segments = [
5935
+ // 左边竖线
5936
+ [points.topStart, points.leftTop],
5937
+ [points.leftTop, points.leftBottom],
5938
+ // 底边
5939
+ [points.leftBottom, points.rightBottom],
5940
+ // 右边竖线
5941
+ [points.rightBottom, points.rightTop],
5942
+ // 顶部折线
5943
+ [points.topStart, points.topEnd],
5944
+ [points.topEnd, points.cornerPoint],
5945
+ [points.cornerPoint, points.rightTop],
5946
+ // 中间横线
5947
+ [points.leftMiddle, points.middlePoint]
5948
+ ];
5949
+ return getNearestPointBetweenPointAndDiscreteSegments(point, segments);
5829
5950
  },
5830
5951
  getConnectorPoints(rectangle) {
5831
5952
  return RectangleClient.getEdgeCenterPoints(rectangle);
@@ -5923,12 +6044,23 @@ const CombinedFragmentEngine = {
5923
6044
  }
5924
6045
  };
5925
6046
 
6047
+ function getDeletionLines(rectangle) {
6048
+ return [
6049
+ [
6050
+ [rectangle.x, rectangle.y],
6051
+ [rectangle.x + rectangle.width, rectangle.y + rectangle.height]
6052
+ ],
6053
+ [
6054
+ [rectangle.x + rectangle.width, rectangle.y],
6055
+ [rectangle.x, rectangle.y + rectangle.height]
6056
+ ]
6057
+ ];
6058
+ }
5926
6059
  const DeletionEngine = {
5927
6060
  draw(board, rectangle, options) {
5928
6061
  const rs = PlaitBoard.getRoughSVG(board);
5929
- const shape = rs.path(`M${rectangle.x} ${rectangle.y} L${rectangle.x + rectangle.width} ${rectangle.y + rectangle.height}
5930
- M${rectangle.x + rectangle.width} ${rectangle.y} L${rectangle.x} ${rectangle.y + rectangle.height}
5931
- `, { ...options, fillStyle: 'solid', strokeWidth: 4 });
6062
+ const lines = getDeletionLines(rectangle);
6063
+ const shape = rs.path(lines.map(([from, to]) => `M${from[0]} ${from[1]} L${to[0]} ${to[1]}`).join(' '), { ...options, fillStyle: 'solid', strokeWidth: 4 });
5932
6064
  setStrokeLinecap(shape, 'round');
5933
6065
  return shape;
5934
6066
  },
@@ -5940,7 +6072,18 @@ const DeletionEngine = {
5940
6072
  return RectangleClient.getCornerPoints(rectangle);
5941
6073
  },
5942
6074
  getNearestPoint(rectangle, point) {
5943
- return getNearestPointBetweenPointAndSegments(point, RectangleEngine.getCornerPoints(rectangle));
6075
+ const lines = getDeletionLines(rectangle);
6076
+ let minDistance = Infinity;
6077
+ let nearestPoint = point;
6078
+ lines.forEach(line => {
6079
+ const currentPoint = getNearestPointBetweenPointAndSegment(point, line);
6080
+ const distance = distanceBetweenPointAndPoint(point[0], point[1], currentPoint[0], currentPoint[1]);
6081
+ if (distance < minDistance) {
6082
+ minDistance = distance;
6083
+ nearestPoint = currentPoint;
6084
+ }
6085
+ });
6086
+ return nearestPoint;
5944
6087
  },
5945
6088
  getEdgeByConnectionPoint(rectangle, pointOfRectangle) {
5946
6089
  const corners = RectangleEngine.getCornerPoints(rectangle);
@@ -6048,24 +6191,60 @@ const NoteEngine = {
6048
6191
  }
6049
6192
  };
6050
6193
 
6194
+ function generateAssemblyPath(rectangle) {
6195
+ const centerY = rectangle.y + rectangle.height / 2;
6196
+ const firstLineEndX = rectangle.x + rectangle.width * 0.3;
6197
+ const circleWidth = rectangle.width * 0.13;
6198
+ const circleHeight = rectangle.height * 0.285;
6199
+ const verticalX = firstLineEndX + circleWidth;
6200
+ const verticalRadius = rectangle.width * 0.233;
6201
+ return {
6202
+ startPoint: [rectangle.x, centerY],
6203
+ line1: [
6204
+ [rectangle.x, centerY],
6205
+ [firstLineEndX, centerY]
6206
+ ],
6207
+ circleArcCommand: {
6208
+ rx: circleWidth,
6209
+ ry: circleHeight,
6210
+ xAxisRotation: 0,
6211
+ largeArcFlag: 1,
6212
+ sweepFlag: 1,
6213
+ endX: firstLineEndX,
6214
+ endY: centerY
6215
+ },
6216
+ verticalArcCommand: {
6217
+ rx: verticalRadius,
6218
+ ry: rectangle.height / 2,
6219
+ xAxisRotation: 0,
6220
+ largeArcFlag: 0,
6221
+ sweepFlag: 1,
6222
+ endX: verticalX,
6223
+ endY: rectangle.y + rectangle.height
6224
+ },
6225
+ line2: [
6226
+ [verticalX + verticalRadius, centerY],
6227
+ [rectangle.x + rectangle.width, centerY]
6228
+ ]
6229
+ };
6230
+ }
6051
6231
  const AssemblyEngine = {
6052
6232
  draw(board, rectangle, options) {
6053
6233
  const rs = PlaitBoard.getRoughSVG(board);
6054
- const shape = rs.path(`
6055
- M${rectangle.x} ${rectangle.y + rectangle.height / 2}
6056
- H${rectangle.x + rectangle.width * 0.3}
6057
- A${rectangle.width * 0.13} ${rectangle.height * 0.285}, 0, 1, 1 ${rectangle.x +
6058
- rectangle.width * 0.3 +
6059
- rectangle.width * 0.26} ${rectangle.y + rectangle.height / 2}
6060
- A${rectangle.width * 0.13} ${rectangle.height * 0.285}, 0, 1, 1 ${rectangle.x + rectangle.width * 0.3} ${rectangle.y +
6061
- rectangle.height / 2}
6062
- M${rectangle.x + rectangle.width * 0.3 + rectangle.width * 0.13} ${rectangle.y}
6063
- A${rectangle.width * 0.233} ${rectangle.height / 2}, 0, 0, 1 ${rectangle.x +
6064
- rectangle.width * 0.3 +
6065
- rectangle.width * 0.13} ${rectangle.y + rectangle.height}
6066
- M${rectangle.x + rectangle.width * 0.3 + rectangle.width * 0.13 + rectangle.width * 0.233} ${rectangle.y +
6067
- rectangle.height / 2} H${rectangle.x + rectangle.width}
6068
- `, {
6234
+ const { startPoint, line1, circleArcCommand, verticalArcCommand, line2 } = generateAssemblyPath(rectangle);
6235
+ const pathData = [
6236
+ `M${startPoint[0]} ${startPoint[1]}`,
6237
+ `H${line1[1][0]}`,
6238
+ // 画完整的圆形:先画一个半圆,再画另一个半圆
6239
+ `A${circleArcCommand.rx} ${circleArcCommand.ry} ${circleArcCommand.xAxisRotation} ${circleArcCommand.largeArcFlag} ${circleArcCommand.sweepFlag} ${line1[1][0] + circleArcCommand.rx * 2} ${circleArcCommand.endY}`,
6240
+ `A${circleArcCommand.rx} ${circleArcCommand.ry} ${circleArcCommand.xAxisRotation} ${circleArcCommand.largeArcFlag} ${circleArcCommand.sweepFlag} ${circleArcCommand.endX} ${circleArcCommand.endY}`,
6241
+ // 垂直椭圆
6242
+ `M${verticalArcCommand.endX} ${rectangle.y}`,
6243
+ `A${verticalArcCommand.rx} ${verticalArcCommand.ry} ${verticalArcCommand.xAxisRotation} ${verticalArcCommand.largeArcFlag} ${verticalArcCommand.sweepFlag} ${verticalArcCommand.endX} ${verticalArcCommand.endY}`,
6244
+ // 最后一条线
6245
+ `M${line2[0][0]} ${line2[0][1]} H${line2[1][0]}`
6246
+ ].join(' ');
6247
+ const shape = rs.path(pathData, {
6069
6248
  ...options,
6070
6249
  fillStyle: 'solid'
6071
6250
  });
@@ -6083,11 +6262,25 @@ const AssemblyEngine = {
6083
6262
  return RectangleClient.getEdgeCenterPoints(rectangle);
6084
6263
  },
6085
6264
  getNearestPoint(rectangle, point) {
6086
- const nearestPoint = getNearestPointBetweenPointAndSegments(point, RectangleEngine.getCornerPoints(rectangle));
6087
- if (nearestPoint[0] === rectangle.x + rectangle.width / 2) {
6088
- return getNearestPointBetweenPointAndEllipse(point, [rectangle.x + rectangle.width * 0.43, rectangle.y + rectangle.height / 2], rectangle.width * 0.223, rectangle.height / 2);
6089
- }
6090
- return nearestPoint;
6265
+ const { line1, line2, circleArcCommand, verticalArcCommand } = generateAssemblyPath(rectangle);
6266
+ // 检查直线段
6267
+ const nearestPointForLines = getNearestPointBetweenPointAndSegments(point, [...line1, ...line2]);
6268
+ const distanceForLines = distanceBetweenPointAndPoint(...point, ...nearestPointForLines);
6269
+ // 检查中间圆形
6270
+ const circleCenter = [line1[1][0] + circleArcCommand.rx, line1[1][1]];
6271
+ const nearestPointForCircle = getNearestPointBetweenPointAndEllipse(point, circleCenter, circleArcCommand.rx, circleArcCommand.ry);
6272
+ const distanceForCircle = distanceBetweenPointAndPoint(...point, ...nearestPointForCircle);
6273
+ // 检查垂直椭圆(使用 getNearestPointBetweenPointAndArc 处理半圆弧)
6274
+ const arcStartPoint = [verticalArcCommand.endX, rectangle.y];
6275
+ const nearestPointForEllipse = getNearestPointBetweenPointAndArc(point, arcStartPoint, verticalArcCommand);
6276
+ const distanceForEllipse = distanceBetweenPointAndPoint(...point, ...nearestPointForEllipse);
6277
+ // 返回最近的点
6278
+ const minDistance = Math.min(distanceForLines, distanceForCircle, distanceForEllipse);
6279
+ if (minDistance === distanceForLines)
6280
+ return nearestPointForLines;
6281
+ if (minDistance === distanceForCircle)
6282
+ return nearestPointForCircle;
6283
+ return nearestPointForEllipse;
6091
6284
  },
6092
6285
  getTangentVectorByConnectionPoint(rectangle, pointOfRectangle) {
6093
6286
  const connectionPoint = RectangleClient.getConnectionPoint(rectangle, pointOfRectangle);
@@ -6101,13 +6294,38 @@ const AssemblyEngine = {
6101
6294
  }
6102
6295
  };
6103
6296
 
6297
+ function generateRequiredInterfacePath(rectangle) {
6298
+ const arcWidth = rectangle.width * 0.39;
6299
+ const arcHeight = rectangle.height / 2;
6300
+ return {
6301
+ startPoint: [rectangle.x, rectangle.y],
6302
+ leftArcCommand: {
6303
+ rx: arcWidth,
6304
+ ry: arcHeight,
6305
+ xAxisRotation: 0,
6306
+ largeArcFlag: 0,
6307
+ sweepFlag: 1,
6308
+ endX: rectangle.x,
6309
+ endY: rectangle.y + rectangle.height
6310
+ },
6311
+ line: {
6312
+ startX: rectangle.x + rectangle.width * 0.41,
6313
+ startY: rectangle.y + rectangle.height / 2,
6314
+ endX: rectangle.x + rectangle.width,
6315
+ endY: rectangle.y + rectangle.height / 2
6316
+ }
6317
+ };
6318
+ }
6104
6319
  const RequiredInterfaceEngine = {
6105
6320
  draw(board, rectangle, options) {
6106
6321
  const rs = PlaitBoard.getRoughSVG(board);
6107
- const shape = rs.path(`M${rectangle.x} ${rectangle.y}
6108
- A${rectangle.width * 0.39} ${rectangle.height / 2}, 0, 0, 1 ${rectangle.x} ${rectangle.y + rectangle.height}
6109
- M${rectangle.x + rectangle.width * 0.41} ${rectangle.y + rectangle.height / 2} H${rectangle.x + rectangle.width}
6110
- `, {
6322
+ const { startPoint, leftArcCommand, line } = generateRequiredInterfacePath(rectangle);
6323
+ const pathData = [
6324
+ `M${startPoint[0]} ${startPoint[1]}`,
6325
+ `A${leftArcCommand.rx} ${leftArcCommand.ry} ${leftArcCommand.xAxisRotation} ${leftArcCommand.largeArcFlag} ${leftArcCommand.sweepFlag} ${leftArcCommand.endX} ${leftArcCommand.endY}`,
6326
+ `M${line.startX} ${line.startY} H${line.endX}`
6327
+ ].join(' ');
6328
+ const shape = rs.path(pathData, {
6111
6329
  ...options,
6112
6330
  fillStyle: 'solid',
6113
6331
  fill: 'transparent'
@@ -6123,7 +6341,26 @@ const RequiredInterfaceEngine = {
6123
6341
  return RectangleClient.getCornerPoints(rectangle);
6124
6342
  },
6125
6343
  getNearestPoint(rectangle, point) {
6126
- return getNearestPointBetweenPointAndSegments(point, RectangleEngine.getCornerPoints(rectangle));
6344
+ const { startPoint, leftArcCommand, line } = generateRequiredInterfacePath(rectangle);
6345
+ let minDistance = Infinity;
6346
+ let nearestPoint = point;
6347
+ // 检查圆弧段
6348
+ const arcNearestPoint = getNearestPointBetweenPointAndArc(point, startPoint, leftArcCommand);
6349
+ const arcDistance = distanceBetweenPointAndPoint(point[0], point[1], arcNearestPoint[0], arcNearestPoint[1]);
6350
+ if (arcDistance < minDistance) {
6351
+ minDistance = arcDistance;
6352
+ nearestPoint = arcNearestPoint;
6353
+ }
6354
+ // 检查直线段
6355
+ const lineStart = [line.startX, line.startY];
6356
+ const lineEnd = [line.endX, line.endY];
6357
+ const lineNearestPoint = getNearestPointBetweenPointAndSegment(point, [lineStart, lineEnd]);
6358
+ const lineDistance = distanceBetweenPointAndPoint(point[0], point[1], lineNearestPoint[0], lineNearestPoint[1]);
6359
+ if (lineDistance < minDistance) {
6360
+ minDistance = lineDistance;
6361
+ nearestPoint = lineNearestPoint;
6362
+ }
6363
+ return nearestPoint;
6127
6364
  },
6128
6365
  getEdgeByConnectionPoint(rectangle, pointOfRectangle) {
6129
6366
  const corners = RectangleEngine.getCornerPoints(rectangle);
@@ -6136,25 +6373,52 @@ const RequiredInterfaceEngine = {
6136
6373
  };
6137
6374
 
6138
6375
  const percentage = 0.54;
6139
- const getStartPoint = (rectangle) => {
6140
- return [rectangle.x, rectangle.y + rectangle.height / 2];
6141
- };
6142
- const getEndPoint = (rectangle) => {
6143
- return [rectangle.x + rectangle.width * percentage, rectangle.y + rectangle.height / 2];
6144
- };
6145
- const arcPercentage = percentage + (1 - percentage) / 2;
6146
- const getArcCenter = (rectangle) => {
6147
- return [rectangle.x + arcPercentage * rectangle.width, rectangle.y + rectangle.height / 2];
6148
- };
6376
+ function generateProvidedInterfacePath(rectangle) {
6377
+ const centerY = rectangle.y + rectangle.height / 2;
6378
+ const rx = (rectangle.width * (1 - percentage)) / 2;
6379
+ const ry = rectangle.height / 2;
6380
+ const startPoint = [rectangle.x, centerY];
6381
+ const lineEndX = rectangle.x + rectangle.width * percentage;
6382
+ return {
6383
+ startPoint,
6384
+ line: {
6385
+ startX: startPoint[0],
6386
+ startY: centerY,
6387
+ endX: lineEndX,
6388
+ endY: centerY
6389
+ },
6390
+ arcCommands: [
6391
+ {
6392
+ rx,
6393
+ ry,
6394
+ xAxisRotation: 0,
6395
+ largeArcFlag: 1,
6396
+ sweepFlag: 1,
6397
+ endX: rectangle.x + rectangle.width,
6398
+ endY: centerY
6399
+ },
6400
+ {
6401
+ rx,
6402
+ ry,
6403
+ xAxisRotation: 0,
6404
+ largeArcFlag: 1,
6405
+ sweepFlag: 1,
6406
+ endX: lineEndX,
6407
+ endY: centerY
6408
+ }
6409
+ ]
6410
+ };
6411
+ }
6149
6412
  const ProvidedInterfaceEngine = {
6150
6413
  draw(board, rectangle, options) {
6151
6414
  const rs = PlaitBoard.getRoughSVG(board);
6152
- const startPoint = getStartPoint(rectangle);
6153
- const endPoint = getEndPoint(rectangle);
6154
- const shape = rs.path(`M${startPoint[0]} ${startPoint[1]}
6155
- H${endPoint[0]}
6156
- A${(rectangle.width * (1 - percentage)) / 2} ${rectangle.height / 2}, 0, 1, 1 ${rectangle.x + rectangle.width} ${rectangle.y + rectangle.height / 2}
6157
- A${(rectangle.width * (1 - percentage)) / 2} ${rectangle.height / 2}, 0, 1, 1 ${rectangle.x + rectangle.width * percentage} ${rectangle.y + rectangle.height / 2}`, {
6415
+ const { startPoint, line, arcCommands } = generateProvidedInterfacePath(rectangle);
6416
+ const pathData = [
6417
+ `M${startPoint[0]} ${startPoint[1]}`,
6418
+ `H${line.endX}`,
6419
+ ...arcCommands.map((command) => `A${command.rx} ${command.ry} ${command.xAxisRotation} ${command.largeArcFlag} ${command.sweepFlag} ${command.endX} ${command.endY}`)
6420
+ ].join(' ');
6421
+ const shape = rs.path(pathData, {
6158
6422
  ...options,
6159
6423
  fillStyle: 'solid'
6160
6424
  });
@@ -6172,17 +6436,17 @@ const ProvidedInterfaceEngine = {
6172
6436
  return RectangleClient.getEdgeCenterPoints(rectangle);
6173
6437
  },
6174
6438
  getNearestPoint(rectangle, point) {
6175
- const startPoint = getStartPoint(rectangle);
6176
- const endPoint = getEndPoint(rectangle);
6177
- const nearestPointForLine = getNearestPointBetweenPointAndSegments(point, [startPoint, endPoint]);
6439
+ const { startPoint, line, arcCommands } = generateProvidedInterfacePath(rectangle);
6440
+ // 检查直线段
6441
+ const lineStart = [line.startX, line.startY];
6442
+ const lineEnd = [line.endX, line.endY];
6443
+ const nearestPointForLine = getNearestPointBetweenPointAndSegments(point, [lineStart, lineEnd]);
6178
6444
  const distanceForLine = distanceBetweenPointAndPoint(...point, ...nearestPointForLine);
6179
- const arcCenter = getArcCenter(rectangle);
6180
- const nearestPointForEllipse = getNearestPointBetweenPointAndEllipse(point, arcCenter, (rectangle.width * (1 - percentage)) / 2, rectangle.height / 2);
6445
+ // 检查圆弧段
6446
+ const arcCenter = [rectangle.x + (3 * rectangle.width) / 4, line.startY];
6447
+ const nearestPointForEllipse = getNearestPointBetweenPointAndEllipse(point, arcCenter, arcCommands[0].rx, arcCommands[0].ry);
6181
6448
  const distanceForEllipse = distanceBetweenPointAndPoint(...point, ...nearestPointForEllipse);
6182
- if (distanceForLine < distanceForEllipse) {
6183
- return nearestPointForLine;
6184
- }
6185
- return nearestPointForEllipse;
6449
+ return distanceForLine < distanceForEllipse ? nearestPointForLine : nearestPointForEllipse;
6186
6450
  },
6187
6451
  getTangentVectorByConnectionPoint(rectangle, pointOfRectangle) {
6188
6452
  const connectionPoint = RectangleClient.getConnectionPoint(rectangle, pointOfRectangle);
@@ -6198,30 +6462,84 @@ const ProvidedInterfaceEngine = {
6198
6462
  }
6199
6463
  };
6200
6464
 
6465
+ function generateComponentPath(rectangle) {
6466
+ const mainLineX = rectangle.x + 12;
6467
+ const boxWidth = rectangle.width > 70 ? 24 : rectangle.width * 0.2;
6468
+ const boxHeight = rectangle.height - 28 - rectangle.height * 0.35 > 1 ? 14 : rectangle.height * 0.175;
6469
+ const topBoxY = rectangle.y + rectangle.height * 0.175;
6470
+ const bottomBoxY = rectangle.y + rectangle.height - rectangle.height * 0.175 - boxHeight;
6471
+ return {
6472
+ boxSize: {
6473
+ width: boxWidth,
6474
+ height: boxHeight
6475
+ },
6476
+ points: {
6477
+ mainStart: [mainLineX, rectangle.y],
6478
+ topBoxStart: [mainLineX, topBoxY],
6479
+ topBoxEnd: [mainLineX, topBoxY + boxHeight],
6480
+ bottomBoxStart: [mainLineX, bottomBoxY],
6481
+ bottomBoxEnd: [mainLineX, bottomBoxY + boxHeight],
6482
+ mainEnd: [mainLineX, rectangle.y + rectangle.height],
6483
+ rightTop: [rectangle.x + rectangle.width, rectangle.y],
6484
+ rightBottom: [rectangle.x + rectangle.width, rectangle.y + rectangle.height]
6485
+ }
6486
+ };
6487
+ }
6201
6488
  const ComponentEngine = {
6202
6489
  draw(board, rectangle, options) {
6203
6490
  const rs = PlaitBoard.getRoughSVG(board);
6204
- const boxSize = {
6205
- with: rectangle.width > 70 ? 24 : rectangle.width * 0.2,
6206
- height: rectangle.height - 28 - rectangle.height * 0.35 > 1 ? 14 : rectangle.height * 0.175
6207
- };
6208
- const shape = rs.path(`M${rectangle.x + 12} ${rectangle.y}
6209
- v${rectangle.height * 0.175}
6210
- h${boxSize.with / 2} v${boxSize.height} h${-boxSize.with} v${-boxSize.height} h${boxSize.with / 2}
6211
-
6212
- M${rectangle.x + 12} ${rectangle.y + rectangle.height * 0.175 + boxSize.height}
6213
-
6214
- v${rectangle.height - rectangle.height * 0.35 - boxSize.height * 2}
6215
- h${boxSize.with / 2} v${boxSize.height} h${-boxSize.with} v${-boxSize.height} h${boxSize.with / 2}
6216
- M${rectangle.x + 12} ${rectangle.y + rectangle.height - rectangle.height * 0.175}
6217
- V${rectangle.y + rectangle.height}
6218
- H${rectangle.x + rectangle.width}
6219
- v${-rectangle.height}
6220
- h${-(rectangle.width - 12)}
6221
- `, { ...options, fillStyle: 'solid' });
6491
+ const { boxSize, points } = generateComponentPath(rectangle);
6492
+ const pathData = [
6493
+ // 主矩形轮廓
6494
+ `M${points.mainStart[0]} ${points.mainStart[1]}`,
6495
+ `H${points.rightTop[0]}`,
6496
+ `V${points.rightBottom[1]}`,
6497
+ `H${points.mainEnd[0]}`,
6498
+ // 上方小矩形
6499
+ `M${points.topBoxStart[0]} ${points.topBoxStart[1]}`,
6500
+ `h${boxSize.width / 2} v${boxSize.height} h${-boxSize.width} v${-boxSize.height} h${boxSize.width / 2}`,
6501
+ // 下方小矩形
6502
+ `M${points.bottomBoxStart[0]} ${points.bottomBoxStart[1]}`,
6503
+ `h${boxSize.width / 2} v${boxSize.height} h${-boxSize.width} v${-boxSize.height} h${boxSize.width / 2}`,
6504
+ // 连接线
6505
+ `M${points.mainStart[0]} ${points.mainStart[1]}`,
6506
+ `V${points.topBoxStart[1]}`,
6507
+ `M${points.topBoxEnd[0]} ${points.topBoxEnd[1]}`,
6508
+ `V${points.bottomBoxStart[1]}`,
6509
+ `M${points.bottomBoxEnd[0]} ${points.bottomBoxEnd[1]}`,
6510
+ `V${points.mainEnd[1]}`
6511
+ ].join(' ');
6512
+ const shape = rs.path(pathData, { ...options, fillStyle: 'solid' });
6222
6513
  setStrokeLinecap(shape, 'round');
6223
6514
  return shape;
6224
6515
  },
6516
+ getNearestPoint(rectangle, point) {
6517
+ const { boxSize, points } = generateComponentPath(rectangle);
6518
+ const segments = [
6519
+ // 主矩形轮廓
6520
+ [points.mainStart, [points.rightTop[0], points.mainStart[1]]],
6521
+ [[points.rightTop[0], points.mainStart[1]], points.rightBottom],
6522
+ [points.rightBottom, [points.mainEnd[0], points.rightBottom[1]]],
6523
+ [[points.mainEnd[0], points.rightBottom[1]], points.mainStart],
6524
+ // 上方小矩形
6525
+ [points.topBoxStart, [points.topBoxStart[0] + boxSize.width / 2, points.topBoxStart[1]]],
6526
+ [[points.topBoxStart[0] + boxSize.width / 2, points.topBoxStart[1]], [points.topBoxStart[0] + boxSize.width / 2, points.topBoxEnd[1]]],
6527
+ [[points.topBoxStart[0] + boxSize.width / 2, points.topBoxEnd[1]], [points.topBoxStart[0] - boxSize.width / 2, points.topBoxEnd[1]]],
6528
+ [[points.topBoxStart[0] - boxSize.width / 2, points.topBoxEnd[1]], [points.topBoxStart[0] - boxSize.width / 2, points.topBoxStart[1]]],
6529
+ [[points.topBoxStart[0] - boxSize.width / 2, points.topBoxStart[1]], points.topBoxStart],
6530
+ // 下方小矩形
6531
+ [points.bottomBoxStart, [points.bottomBoxStart[0] + boxSize.width / 2, points.bottomBoxStart[1]]],
6532
+ [[points.bottomBoxStart[0] + boxSize.width / 2, points.bottomBoxStart[1]], [points.bottomBoxStart[0] + boxSize.width / 2, points.bottomBoxEnd[1]]],
6533
+ [[points.bottomBoxStart[0] + boxSize.width / 2, points.bottomBoxEnd[1]], [points.bottomBoxStart[0] - boxSize.width / 2, points.bottomBoxEnd[1]]],
6534
+ [[points.bottomBoxStart[0] - boxSize.width / 2, points.bottomBoxEnd[1]], [points.bottomBoxStart[0] - boxSize.width / 2, points.bottomBoxStart[1]]],
6535
+ [[points.bottomBoxStart[0] - boxSize.width / 2, points.bottomBoxStart[1]], points.bottomBoxStart],
6536
+ // 连接线
6537
+ [points.mainStart, points.topBoxStart],
6538
+ [points.topBoxEnd, points.bottomBoxStart],
6539
+ [points.bottomBoxEnd, points.mainEnd]
6540
+ ];
6541
+ return getNearestPointBetweenPointAndDiscreteSegments(point, segments);
6542
+ },
6225
6543
  isInsidePoint(rectangle, point) {
6226
6544
  const rangeRectangle = RectangleClient.getRectangleByPoints([point, point]);
6227
6545
  return RectangleClient.isHit(rectangle, rangeRectangle);
@@ -6229,26 +6547,18 @@ const ComponentEngine = {
6229
6547
  getCornerPoints(rectangle) {
6230
6548
  return RectangleClient.getCornerPoints(rectangle);
6231
6549
  },
6232
- getNearestPoint(rectangle, point) {
6233
- let nearestPoint = getNearestPointBetweenPointAndSegments(point, RectangleEngine.getCornerPoints(rectangle));
6234
- if (nearestPoint[1] === rectangle.y + rectangle.height / 2) {
6235
- nearestPoint = getNearestPointBetweenPointAndSegments(point, [
6236
- [rectangle.x + 12, rectangle.y + rectangle.height * 0.175 + 14],
6237
- [rectangle.x + 12, rectangle.y + rectangle.height - rectangle.height * 0.175 - 14]
6238
- ], false);
6239
- }
6240
- return nearestPoint;
6241
- },
6242
6550
  getTangentVectorByConnectionPoint(rectangle, pointOfRectangle) {
6551
+ const { points } = generateComponentPath(rectangle);
6243
6552
  const connectionPoint = RectangleClient.getConnectionPoint(rectangle, pointOfRectangle);
6244
- return getUnitVectorByPointAndPoint([rectangle.x + 12, rectangle.y + rectangle.height - rectangle.height * 0.175 - 14], connectionPoint);
6553
+ return getUnitVectorByPointAndPoint(points.mainStart, connectionPoint);
6245
6554
  },
6246
6555
  getConnectorPoints(rectangle) {
6556
+ const { points } = generateComponentPath(rectangle);
6247
6557
  return [
6248
6558
  [rectangle.x + rectangle.width / 2, rectangle.y],
6249
6559
  [rectangle.x + rectangle.width, rectangle.y + rectangle.height / 2],
6250
6560
  [rectangle.x + rectangle.width / 2, rectangle.y + rectangle.height],
6251
- [rectangle.x + 12, rectangle.y + rectangle.height / 2]
6561
+ [points.mainStart[0], rectangle.y + rectangle.height / 2]
6252
6562
  ];
6253
6563
  },
6254
6564
  getTextRectangle(element) {
@@ -6865,7 +7175,7 @@ class GeometryComponent extends CommonElementFlavour {
6865
7175
  return RectangleClient.getRectangleByPoints(element.points);
6866
7176
  },
6867
7177
  hasResizeHandle: () => {
6868
- return canResize(this.board, this.element);
7178
+ return hasResizeHandle(this.board, this.element);
6869
7179
  }
6870
7180
  });
6871
7181
  this.lineAutoCompleteGenerator = new ArrowLineAutoCompleteGenerator(this.board);
@@ -8027,6 +8337,7 @@ const withArrowLineAutoComplete = (board) => {
8027
8337
  // source point must be click point
8028
8338
  const rotatedSourcePoint = rotatePointsByElement(sourcePoint, sourceElement) || sourcePoint;
8029
8339
  temporaryElement = handleArrowLineCreating(board, ArrowLineShape.elbow, rotatedSourcePoint, movingPoint, sourceElement, lineShapeG);
8340
+ Transforms.addSelectionWithTemporaryElements(board, []);
8030
8341
  }
8031
8342
  }
8032
8343
  pointerMove(event);
@@ -8081,7 +8392,7 @@ const withArrowLineTextMove = (board) => {
8081
8392
  if (element) {
8082
8393
  const movingPoint = resizeState.endPoint;
8083
8394
  const points = getArrowLinePoints(board, element);
8084
- const distance = distanceBetweenPointAndSegments(points, movingPoint);
8395
+ const distance = distanceBetweenPointAndSegments(movingPoint, points);
8085
8396
  if (distance <= movableBuffer) {
8086
8397
  const point = getNearestPointBetweenPointAndSegments(movingPoint, points, false);
8087
8398
  const position = getRatioByPoint(points, point);
@@ -8246,7 +8557,7 @@ class TableComponent extends CommonElementFlavour {
8246
8557
  if (cells?.length) {
8247
8558
  return false;
8248
8559
  }
8249
- return canResize(this.board, this.element);
8560
+ return hasResizeHandle(this.board, this.element);
8250
8561
  }
8251
8562
  });
8252
8563
  this.tableGenerator = new TableGenerator(this.board);
@@ -8518,7 +8829,8 @@ const withTable = (board) => {
8518
8829
  tableBoard.isRectangleHit = (element, selection) => {
8519
8830
  if (PlaitDrawElement.isElementByTable(element)) {
8520
8831
  const rangeRectangle = RectangleClient.getRectangleByPoints([selection.anchor, selection.focus]);
8521
- return isLineHitRectangle(element.points, rangeRectangle);
8832
+ const client = RectangleClient.getRectangleByPoints(element.points);
8833
+ return isLineHitRectangle(RectangleClient.getCornerPoints(client), rangeRectangle);
8522
8834
  }
8523
8835
  return isRectangleHit(element, selection);
8524
8836
  };