drawui-core 0.1.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.
@@ -0,0 +1,948 @@
1
+ interface Vec2 {
2
+ x: number;
3
+ y: number;
4
+ }
5
+ interface Rect {
6
+ x: number;
7
+ y: number;
8
+ width: number;
9
+ height: number;
10
+ }
11
+ interface ShapeStyle {
12
+ strokeColor: string;
13
+ fillColor: string;
14
+ strokeWidth: number;
15
+ strokeDash: number[] | null;
16
+ }
17
+ type ShapeType = 'rectangle' | 'rounded-rectangle' | 'circle' | 'ellipse' | 'diamond' | 'triangle' | 'hexagon' | 'parallelogram' | 'trapezoid' | 'pentagon' | 'octagon' | 'star' | 'uml-class' | 'uml-interface' | 'line' | 'arrow' | 'text' | 'draw';
18
+ interface BaseShape {
19
+ id: string;
20
+ type: ShapeType;
21
+ x: number;
22
+ y: number;
23
+ rotation: number;
24
+ style: ShapeStyle;
25
+ text?: string;
26
+ textColor?: string;
27
+ fontSize?: number;
28
+ highlightColor?: string;
29
+ }
30
+ interface RectangleShape extends BaseShape {
31
+ type: 'rectangle';
32
+ width: number;
33
+ height: number;
34
+ }
35
+ interface RoundedRectangleShape extends BaseShape {
36
+ type: 'rounded-rectangle';
37
+ width: number;
38
+ height: number;
39
+ borderRadius: number;
40
+ }
41
+ interface CircleShape extends BaseShape {
42
+ type: 'circle';
43
+ radius: number;
44
+ }
45
+ interface EllipseShape extends BaseShape {
46
+ type: 'ellipse';
47
+ width: number;
48
+ height: number;
49
+ }
50
+ interface DiamondShape extends BaseShape {
51
+ type: 'diamond';
52
+ width: number;
53
+ height: number;
54
+ }
55
+ interface TriangleShape extends BaseShape {
56
+ type: 'triangle';
57
+ width: number;
58
+ height: number;
59
+ }
60
+ interface HexagonShape extends BaseShape {
61
+ type: 'hexagon';
62
+ width: number;
63
+ height: number;
64
+ }
65
+ interface ParallelogramShape extends BaseShape {
66
+ type: 'parallelogram';
67
+ width: number;
68
+ height: number;
69
+ }
70
+ interface TrapezoidShape extends BaseShape {
71
+ type: 'trapezoid';
72
+ width: number;
73
+ height: number;
74
+ }
75
+ interface PentagonShape extends BaseShape {
76
+ type: 'pentagon';
77
+ width: number;
78
+ height: number;
79
+ }
80
+ interface OctagonShape extends BaseShape {
81
+ type: 'octagon';
82
+ width: number;
83
+ height: number;
84
+ }
85
+ interface StarShape extends BaseShape {
86
+ type: 'star';
87
+ width: number;
88
+ height: number;
89
+ }
90
+ type UmlVisibility = '+' | '-' | '#' | '~';
91
+ interface UmlAttribute {
92
+ id: string;
93
+ visibility: UmlVisibility;
94
+ name: string;
95
+ type: string;
96
+ /** 每行可选格式:缺省时回退到 shape 的 textColor/fontSize */
97
+ textColor?: string;
98
+ fontSize?: number;
99
+ /** 是否加删除线 */
100
+ strikethrough?: boolean;
101
+ }
102
+ interface UmlMethod {
103
+ id: string;
104
+ visibility: UmlVisibility;
105
+ name: string;
106
+ params: string;
107
+ returnType: string;
108
+ /** 每行可选格式:缺省时回退到 shape 的 textColor/fontSize */
109
+ textColor?: string;
110
+ fontSize?: number;
111
+ /** 是否加删除线 */
112
+ strikethrough?: boolean;
113
+ }
114
+ interface UmlClassShape extends BaseShape {
115
+ type: 'uml-class';
116
+ className: string;
117
+ attributes: UmlAttribute[];
118
+ methods: UmlMethod[];
119
+ width: number;
120
+ height: number;
121
+ }
122
+ interface UmlInterfaceShape extends BaseShape {
123
+ type: 'uml-interface';
124
+ className: string;
125
+ methods: UmlMethod[];
126
+ width: number;
127
+ height: number;
128
+ }
129
+ interface LineShape extends BaseShape {
130
+ type: 'line';
131
+ points: Vec2[];
132
+ connectionType?: ConnectionType;
133
+ }
134
+ type ConnectionType = 'straight' | 'polyline' | 'orthogonal' | 'curve';
135
+ /** 生成点位创建上下文:from 为源点(生成点位),to 为目标落点 */
136
+ interface GenerationContext {
137
+ from: Vec2;
138
+ to: Vec2;
139
+ sourceShapeId?: string;
140
+ edge?: 'n' | 'e' | 's' | 'w';
141
+ }
142
+ /** 生成点位预览:从源点拖出的虚线线条 + 待放置图形轮廓 */
143
+ interface GenerationPreview {
144
+ from: Vec2;
145
+ to: Vec2;
146
+ /** 待放置图形的类型;不提供(指向已有图形)时只画线条 */
147
+ type?: ShapeType;
148
+ }
149
+ /** 生成点位 → UI 菜单载荷:拖动到空白处后触发的"选择新图形"菜单 */
150
+ interface GenerationMenuPayload {
151
+ context: GenerationContext;
152
+ /** 拖动时已生成的浮动线条 id;选择图形后把线条末端接到该图形 */
153
+ existingArrowId?: string;
154
+ /** 默认高亮的图形类型(= 当前选中图形类型) */
155
+ defaultShapeType: ShapeType;
156
+ }
157
+ interface ArrowBinding {
158
+ toShapeId: string;
159
+ handle?: 'top' | 'bottom' | 'left' | 'right';
160
+ isPrecise?: boolean;
161
+ normalizedAnchor?: Vec2;
162
+ }
163
+ type ArrowHeadType = 'none' | 'start' | 'end' | 'both' | 'inheritance' | 'aggregation' | 'composition';
164
+ interface ArrowShape extends BaseShape {
165
+ type: 'arrow';
166
+ points: Vec2[];
167
+ startBinding?: ArrowBinding;
168
+ endBinding?: ArrowBinding;
169
+ connectionType?: ConnectionType;
170
+ arrowHead?: ArrowHeadType;
171
+ dashed?: boolean;
172
+ }
173
+ interface TextShape extends BaseShape {
174
+ type: 'text';
175
+ text: string;
176
+ fontSize: number;
177
+ width: number;
178
+ height: number;
179
+ }
180
+ interface DrawShape extends BaseShape {
181
+ type: 'draw';
182
+ points: Vec2[];
183
+ }
184
+ type Shape = RectangleShape | RoundedRectangleShape | CircleShape | EllipseShape | DiamondShape | TriangleShape | HexagonShape | ParallelogramShape | TrapezoidShape | PentagonShape | OctagonShape | StarShape | UmlClassShape | UmlInterfaceShape | LineShape | ArrowShape | TextShape | DrawShape;
185
+ type ToolType = 'select' | 'hand' | 'rectangle' | 'rounded-rectangle' | 'circle' | 'ellipse' | 'diamond' | 'triangle' | 'hexagon' | 'parallelogram' | 'trapezoid' | 'pentagon' | 'octagon' | 'star' | 'uml-class' | 'uml-interface' | 'line' | 'arrow' | 'text' | 'draw' | 'pen' | 'highlighter';
186
+ interface CameraState {
187
+ x: number;
188
+ y: number;
189
+ zoom: number;
190
+ }
191
+ interface EditorState {
192
+ shapes: Shape[];
193
+ selectedShapeIds: string[];
194
+ activeTool: ToolType;
195
+ camera: CameraState;
196
+ }
197
+
198
+ interface Mat2d {
199
+ a: number;
200
+ b: number;
201
+ c: number;
202
+ d: number;
203
+ e: number;
204
+ f: number;
205
+ }
206
+ declare const Mat2d: {
207
+ identity(): Mat2d;
208
+ translation(x: number, y: number): Mat2d;
209
+ rotation(angle: number): Mat2d;
210
+ scale(sx: number, sy: number): Mat2d;
211
+ multiply(a: Mat2d, b: Mat2d): Mat2d;
212
+ apply(m: Mat2d, v: Vec2): Vec2;
213
+ invert(m: Mat2d): Mat2d | null;
214
+ createCameraMatrix(camera: {
215
+ x: number;
216
+ y: number;
217
+ zoom: number;
218
+ }): Mat2d;
219
+ };
220
+
221
+ declare class Camera {
222
+ x: number;
223
+ y: number;
224
+ zoom: number;
225
+ private minZoom;
226
+ private maxZoom;
227
+ getMatrix(): Mat2d;
228
+ getInverseMatrix(): Mat2d | null;
229
+ screenToPage(screenPoint: Vec2): Vec2;
230
+ pageToScreen(pagePoint: Vec2): Vec2;
231
+ translate(dx: number, dy: number): void;
232
+ zoomAt(point: Vec2, factor: number): void;
233
+ reset(): void;
234
+ getZoom(): number;
235
+ fitBounds(bounds: {
236
+ x: number;
237
+ y: number;
238
+ width: number;
239
+ height: number;
240
+ }, padding?: number): void;
241
+ }
242
+
243
+ type Listener = () => void;
244
+ type NotificationListener = (message: string) => void;
245
+ /** 核心 → React 的 UI 指令事件 */
246
+ type UiEvent = {
247
+ kind: 'shapeTypeMenu';
248
+ screenX: number;
249
+ screenY: number;
250
+ context: GenerationContext;
251
+ existingArrowId?: string;
252
+ defaultShapeType: ShapeType;
253
+ };
254
+ declare class Editor {
255
+ private canvas;
256
+ private renderer;
257
+ private camera;
258
+ private tools;
259
+ private activeTool;
260
+ private shapes;
261
+ private selectedIds;
262
+ private listeners;
263
+ private notificationListeners;
264
+ private uiListeners;
265
+ private isPanning;
266
+ private panStart;
267
+ private dragStartPositions;
268
+ private lastClickTime;
269
+ private lastClickShapeId;
270
+ private editingShapeId;
271
+ private textInput;
272
+ private textOverlay;
273
+ private isNewTextShape;
274
+ private originalTextValue;
275
+ private undoStack;
276
+ private redoStack;
277
+ private isUndoing;
278
+ private brushRect;
279
+ private snapGuides;
280
+ private connectingTargetId;
281
+ private genPreview;
282
+ /** 拖动松开后待确认图形的上下文(配合"选择新图形"菜单) */
283
+ private pendingGen;
284
+ private renderScheduled;
285
+ private pointerIsDown;
286
+ private notifyScheduled;
287
+ constructor(canvas: HTMLCanvasElement);
288
+ private setupEventListeners;
289
+ private handlePointerDown;
290
+ private handlePointerMove;
291
+ private handlePointerUp;
292
+ /** 根据当前工具的命中结果更新鼠标样式(伸缩手柄↔/↕、拖拽 grabbing 等) */
293
+ private updateCursor;
294
+ private handleWheel;
295
+ private handleKeyDown;
296
+ private handleKeyUp;
297
+ private handleResize;
298
+ private screenToPage;
299
+ private addShape;
300
+ private updateShape;
301
+ private updateBoundArrows;
302
+ private getBindingPosition;
303
+ private deleteShape;
304
+ private selectShape;
305
+ private selectShapes;
306
+ private setBrushRect;
307
+ /** 设置对齐参考线(由工具在拖拽过程中调用) */
308
+ private setSnapGuides;
309
+ private setConnectingTarget;
310
+ /** 设置生成点位预览(拖动/悬浮中显示的线条 + 待放置图形) */
311
+ private setGenerationPreview;
312
+ /** 拖动到空白处松开 → 弹出"选择新图形"菜单(转发给 React UI) */
313
+ private openGenerationMenu;
314
+ private lineStyle;
315
+ /** 根据生成源(图形+边)计算连线起点绑定 */
316
+ private computeSourceBinding;
317
+ /** 连线到达目标图形时,应吸附到目标面向来线的那个边 */
318
+ private connectHandle;
319
+ /** 仅创建一条从源点引出、末端悬空的线条并选中它 */
320
+ createFloatingLine(context: GenerationContext): string | undefined;
321
+ /**
322
+ * 创建"新图形 + 连线":在 to 处放置指定类型图形;若提供了 existingArrowId,
323
+ * 则把那条(已存在的浮动)线条末端接到新图形,否则新建一条连线。
324
+ * 新图形样式继承源图形(若有)。
325
+ */
326
+ createGeneratedConnection(context: GenerationContext, options: {
327
+ type: ShapeType;
328
+ existingArrowId?: string;
329
+ }): void;
330
+ /** 从源点连到已有图形:仅新建一条连线,不新造图形 */
331
+ connectToExisting(context: GenerationContext, targetShapeId: string, binding?: ArrowBinding): void;
332
+ subscribeUi(listener: (payload: UiEvent) => void): () => boolean;
333
+ private emitUi;
334
+ /** 批量更新选中图形的样式(边框色、填充色等),提交历史记录 */
335
+ updateSelectedShapesStyle(updates: Partial<ShapeStyle>): void;
336
+ /** 批量更新选中图形的文字颜色和高亮背景色,提交历史记录 */
337
+ updateSelectedShapesText(updates: {
338
+ textColor?: string;
339
+ highlightColor?: string;
340
+ }): void;
341
+ /** 批量更新选中线条/箭头的连线类型 */
342
+ updateConnectionType(connectionType: ConnectionType): void;
343
+ /** 批量更新选中箭头的箭头类型 */
344
+ updateArrowHeadType(arrowHead: ArrowHeadType): void;
345
+ /** 批量更新选中箭头的虚线样式 */
346
+ updateArrowDashed(dashed: boolean): void;
347
+ /** 更新 UML 类图的类名、属性、方法,自动重算尺寸 */
348
+ updateUmlClass(id: string, updates: {
349
+ className?: string;
350
+ attributes?: UmlAttribute[];
351
+ methods?: UmlMethod[];
352
+ }): void;
353
+ /** 更新 UML 接口(仅含方法,无属性段;size 由内容自动重算) */
354
+ updateUmlInterface(id: string, updates: {
355
+ className?: string;
356
+ methods?: UmlMethod[];
357
+ }): void;
358
+ /** 获取选中的 UML(类图或接口,如果有) */
359
+ getSelectedUml(): UmlClassShape | UmlInterfaceShape | null;
360
+ /** 按 id 查找 UML(类图或接口) */
361
+ private getUmlById;
362
+ /** 给指定 UML 类追加一条属性 */
363
+ addUmlAttribute(shapeId: string): void;
364
+ /** 给指定 UML(类图或接口)追加一条方法 */
365
+ addUmlMethod(shapeId: string): void;
366
+ /** 删除指定 UML 类的某条属性 */
367
+ removeUmlAttribute(shapeId: string, attrId: string): void;
368
+ /** 删除指定 UML(类图或接口)的某条方法 */
369
+ removeUmlMethod(shapeId: string, methodId: string): void;
370
+ /** 重排属性行 */
371
+ reorderUmlAttribute(shapeId: string, fromIdx: number, toIdx: number): void;
372
+ /** 重排方法行 */
373
+ reorderUmlMethod(shapeId: string, fromIdx: number, toIdx: number): void;
374
+ /** 更新单行的可选格式(颜色/字号/删除线) */
375
+ updateUmlRowStyle(shapeId: string, section: 'attribute' | 'method', rowId: string, style: {
376
+ textColor?: string | null;
377
+ fontSize?: number | null;
378
+ strikethrough?: boolean | null;
379
+ }): void;
380
+ /**
381
+ * 屏幕坐标(画布左上角为原点)下的 UML 行命中检测。
382
+ * 命中返回:所在形状、区域(className/attribute/method)、行索引、完整文本。
383
+ */
384
+ getUmlRowAtScreenPoint(screenPoint: {
385
+ x: number;
386
+ y: number;
387
+ }): {
388
+ shape: UmlClassShape | UmlInterfaceShape;
389
+ section: 'className' | 'attribute' | 'method';
390
+ rowIndex: number;
391
+ text: string;
392
+ } | null;
393
+ setActiveTool(tool: ToolType): void;
394
+ getActiveTool(): ToolType;
395
+ getShapes(): Shape[];
396
+ getSelectedIds(): string[];
397
+ getCamera(): Camera;
398
+ /** 指针是否处于按下状态(拖拽/画线/框选中) */
399
+ isPointerDown(): boolean;
400
+ zoomIn(): void;
401
+ zoomOut(): void;
402
+ resetZoom(): void;
403
+ /**
404
+ * 通过 panBy 偏移相机位置(像素单位)。
405
+ * 用于 minimap 拖拽等需要程序化移动视口的场景。
406
+ */
407
+ panBy(dx: number, dy: number): void;
408
+ /**
409
+ * 直接设置相机的屏幕偏移(绝对位移)。用于 minimap / hand tool 拖拽等
410
+ * 已经能算出目标 camera 的场景,避免 panBy 多次累加带来的漂移。
411
+ */
412
+ setCameraPosition(x: number, y: number): void;
413
+ /**
414
+ * 主动触发一次主画布重绘(合帧队列)。
415
+ * 当外部代码只读地变更了 camera 状态后调用此方法。
416
+ */
417
+ requestRender(): void;
418
+ setShowGrid(show: boolean): void;
419
+ render(): void;
420
+ /** 合帧渲染:同一帧内多次调用只会触发一次实际重绘 */
421
+ private scheduleRender;
422
+ subscribe(listener: Listener): () => boolean;
423
+ subscribeNotifications(listener: NotificationListener): () => boolean;
424
+ private notify;
425
+ private notifyListeners;
426
+ private cloneShapes;
427
+ /**
428
+ * 创建历史标记(参考 tldraw 的 mark 机制)。
429
+ * 在用户操作之前调用,捕获当前状态作为撤销点。
430
+ */
431
+ private commitHistory;
432
+ /** tldraw 风格的 mark 别名 */
433
+ mark(): void;
434
+ undo(): void;
435
+ redo(): void;
436
+ canUndo(): boolean;
437
+ canRedo(): boolean;
438
+ saveToStorage(): boolean;
439
+ loadFromStorage(): boolean;
440
+ clearStorage(): void;
441
+ exportJSON(): string;
442
+ importJSON(json: string): boolean;
443
+ getEditingShapeId(): string | null;
444
+ canEditShape(shape: Shape): boolean;
445
+ enterEditing(shapeId: string): void;
446
+ exitEditing(): void;
447
+ private removeTextInput;
448
+ private createTextInput;
449
+ private getShapeScreenBounds;
450
+ getShapeAtScreenPoint(screenPoint: Vec2): Shape | null;
451
+ getShapeAtPoint(point: Vec2, excludeId?: string): Shape | null;
452
+ private isPointInShape;
453
+ updateArrowBinding(arrowId: string, terminal: 'start' | 'end', targetShapeId: string | null, binding?: ArrowBinding): void;
454
+ private getClosestHandle;
455
+ dispose(): void;
456
+ }
457
+
458
+ interface SnapGuide {
459
+ type: 'horizontal' | 'vertical';
460
+ position: number;
461
+ start: number;
462
+ end: number;
463
+ }
464
+ declare class Renderer {
465
+ private ctx;
466
+ private width;
467
+ private height;
468
+ private dpr;
469
+ private showGrid;
470
+ /** 文本排版缓存:避免每帧对每个字符做 measureText */
471
+ private textLayoutCache;
472
+ private static readonly TEXT_CACHE_LIMIT;
473
+ constructor(canvas: HTMLCanvasElement);
474
+ resize(width: number, height: number): void;
475
+ setShowGrid(show: boolean): void;
476
+ render(shapes: Shape[], camera: Camera, selectedIds: Set<string>, brushRect?: {
477
+ x: number;
478
+ y: number;
479
+ width: number;
480
+ height: number;
481
+ } | null, snapGuides?: SnapGuide[], connectingTarget?: string | null, generationPreview?: GenerationPreview | null): void;
482
+ /** 连接进行中,高亮目标图形四边中点的可连锚点 */
483
+ private drawConnectingTargetHighlight;
484
+ /** 世界坐标下的轴对齐包围盒(未考虑旋转) */
485
+ private getShapeWorldBounds;
486
+ private drawBrushRect;
487
+ /**
488
+ * 绘制网格:只绘制当前视口内的网格线,且合并为单条路径一次 stroke,
489
+ * 避免大量独立 stroke() 调用导致的拖拽卡顿。
490
+ */
491
+ private drawGrid;
492
+ private drawShape;
493
+ private applyStyle;
494
+ private drawRectangle;
495
+ private drawRoundedRectangle;
496
+ private drawCircle;
497
+ private drawEllipse;
498
+ private drawDiamond;
499
+ private drawTriangle;
500
+ private drawHexagon;
501
+ private drawParallelogram;
502
+ private drawTrapezoid;
503
+ private drawPentagon;
504
+ private drawOctagon;
505
+ private drawStar;
506
+ private drawUmlClass;
507
+ /**
508
+ * UML 接口:标题显示为 `<<${className}>>`,仅方法段,无属性段,只有一条分隔线。
509
+ */
510
+ private drawUmlInterface;
511
+ /** 默认拐点圆角半径(页面坐标) */
512
+ private static readonly DEFAULT_CORNER_RADIUS;
513
+ /** 连线与图形之间保留的最小避障间距,避免线条贴边/粘在图形上 */
514
+ private static readonly ROUTE_CLEARANCE;
515
+ /** 连接点处的垂直出线桩长度:第一段垂直脱离图形边缘,避免贴角/贴边 */
516
+ private static readonly CONNECTION_LEAD;
517
+ /** 连接的源/目标图形作为障碍时使用的避让间距(足够阻挡中段折回穿过,又允许紧邻节点直连) */
518
+ private static readonly CONNECTED_PAD;
519
+ private drawLine;
520
+ private drawArrow;
521
+ /** 选中连线 → 在线条上叠加一条高亮辅助线 */
522
+ private drawLinearHighlight;
523
+ /** 生成点位预览:源点圆点 + 虚线线条 + 待放置图形轮廓 */
524
+ private drawGenerationPreview;
525
+ /** 待放置图形的虚线轮廓(按默认尺寸绘制对应类型) */
526
+ private drawPreviewShapeOutline;
527
+ private static readonly ALIGN_TOLERANCE;
528
+ /**
529
+ * 判断连线两端是否水平或垂直对齐(在容差内)。
530
+ * 若在容差内,强制将坐标对齐(避免 grid snapping 导致无法形成直线)。
531
+ */
532
+ private getAlignedPoints;
533
+ /** 生成路径点(对齐容差已在上游处理)。 */
534
+ private buildPathPoints;
535
+ /**
536
+ * 使用 arcTo 绘制带圆角拐点的折线。
537
+ * 对首尾两点不做圆角处理,中间拐点按 radius 切割相邻线段做切线圆角。
538
+ * radius 会根据前后两段长度自动缩小以避免退化。
539
+ */
540
+ private drawRoundedPolyline;
541
+ /** 直角折线:直接用直线段连接所有拐点(无圆角) */
542
+ private drawSharpPolyline;
543
+ /** 绘制对齐参考线(飞书风格:浅蓝色虚线) */
544
+ private drawSnapGuides;
545
+ /** 折线:与orthogonal使用相同的H-V-H路由,差异仅在渲染(sharp vs rounded)。dock 法向在首/末段加一段垂直出线桩,垂直脱离图形边缘。 */
546
+ private generatePolylinePoints;
547
+ /** 直角折线:与 polyline 使用相同的 H-V-H 正交路由,仅渲染方式(sharp)不同。 */
548
+ private generateOrthogonalPoints;
549
+ /** 去除连续重合点与共线点,精简冗余拐点 */
550
+ private collapseLinePoints;
551
+ /** 计算单段直角路由:优先无碰撞的 H-V-H / V-H-V 折线,必要时绕开障碍 */
552
+ private computeOrthogonalRoute;
553
+ /** 收集连线绕行所需的障碍包围盒(转为 shape 本地坐标)。
554
+ * 将本连线之外的所有图形(含其它连线/箭头/手绘)都作为障碍;
555
+ * 连接端点所在图形使用小间距(CONNECTED_PAD),其余图形使用大间距(ROUTE_CLEARANCE)。 */
556
+ private collectRoutingObstacles;
557
+ private pointInRect;
558
+ /** 整个折线路径是否与任一障碍相交 */
559
+ private routeHitsObstacles;
560
+ /** 轴对齐线段是否与任一障碍(向外扩其 pad)相交 */
561
+ private segmentHitsObstacle;
562
+ /**
563
+ * 正交避障路由器(类飞书)。
564
+ * 用「起终点坐标 + 每个障碍物扩展边界」构造一条布线网格,再用 A* 在网格上求
565
+ * 最短避障折线(半径外扩 pad 保证不贴边)。返回不含 from、含 to 的拐点序列。
566
+ */
567
+ private routeOrthogonalAvoidance;
568
+ /** 曲线:通过中点生成平滑曲线路径(离散化贝塞尔曲线) */
569
+ private generateCurvePoints;
570
+ /** 计算贝塞尔曲线控制点 */
571
+ private computeCurveControlPoint;
572
+ /**
573
+ * 获取文本的显示行(换行 + 截断),结果按 shape/文本/尺寸缓存,
574
+ * 避免每帧渲染都对文本逐字符 measureText。
575
+ */
576
+ private getDisplayLines;
577
+ private drawShapeText;
578
+ private getTextBounds;
579
+ private wrapLine;
580
+ private truncateLine;
581
+ private getBindingPoint;
582
+ /**
583
+ * 计算端点所在图形的外法向(垂直于该侧边缘,页面坐标单位向量)。
584
+ * 用于连线垂直脱离/接入图形边缘,端点不在任何图形上时返回 undefined。
585
+ */
586
+ private getDockNormal;
587
+ /**
588
+ * 获取 shape 在页面坐标系中的 bounds(x/y 为左上角)
589
+ */
590
+ private getShapePageBounds;
591
+ /**
592
+ * 若某个世界坐标端点落在某图形内部,则把它吸附到该图形外轮廓的最近点,
593
+ * 避免连线端点/线段穿进图形内部。多个图形重叠时取距另一端更近的那个。
594
+ */
595
+ private clampToShapeOutline;
596
+ /**
597
+ * 从 shape 中心向 p 方向发射一条射线,返回与 shape 外轮廓的最近交点
598
+ * 用于 normalizedAnchor 计算精确绑定的落点
599
+ */
600
+ private nearestPointOnShapeOutline;
601
+ /** 线段 vs 形状轮廓的所有交点(页面坐标系) */
602
+ private intersectShapeOutlineWithSegment;
603
+ /** 线段 vs shape 本地坐标的轮廓交点 */
604
+ private intersectLocalShapeOutlineWithSegment;
605
+ /** 线段 vs 轴对齐矩形 4 条边的所有交点 */
606
+ private intersectSegmentRect;
607
+ /** 两条线段的交点(不存在返回 null) */
608
+ private segmentIntersection;
609
+ /** 线段 vs 圆 的所有交点 */
610
+ private intersectSegmentCircle;
611
+ private drawArrowHead;
612
+ private drawInheritanceHead;
613
+ private drawDiamondHead;
614
+ /** 根据箭头类型绘制起始/末端箭头 */
615
+ private drawArrowHeads;
616
+ private drawText;
617
+ private drawFreehand;
618
+ private drawSelectionOutline;
619
+ private drawSelectionHandles;
620
+ /** 画旋转图标:顺时针短弧 + 箭头 */
621
+ private drawRotateIcon;
622
+ private getShapeBounds;
623
+ }
624
+
625
+ declare const Vec: {
626
+ create(x?: number, y?: number): Vec2;
627
+ add(a: Vec2, b: Vec2): Vec2;
628
+ subtract(a: Vec2, b: Vec2): Vec2;
629
+ multiply(v: Vec2, scalar: number): Vec2;
630
+ divide(v: Vec2, scalar: number): Vec2;
631
+ dot(a: Vec2, b: Vec2): number;
632
+ length(v: Vec2): number;
633
+ normalize(v: Vec2): Vec2;
634
+ distance(a: Vec2, b: Vec2): number;
635
+ rotate(v: Vec2, angle: number): Vec2;
636
+ clone(v: Vec2): Vec2;
637
+ equals(a: Vec2, b: Vec2): boolean;
638
+ };
639
+
640
+ interface ToolContext {
641
+ addShape(shape: Shape): void;
642
+ updateShape(id: string, updates: Partial<Shape>): void;
643
+ deleteShape(id: string): void;
644
+ selectShape(id: string, additive: boolean): void;
645
+ selectShapes(ids: string[]): void;
646
+ getShapes(): Shape[];
647
+ getSelectedIds(): string[];
648
+ setActiveTool(tool: ToolType): void;
649
+ screenToPage(point: Vec2): Vec2;
650
+ getCamera(): Camera;
651
+ panBy(dx: number, dy: number): void;
652
+ setCameraPosition(x: number, y: number): void;
653
+ getShapeAtPoint(point: Vec2, excludeId?: string): Shape | null;
654
+ updateArrowBinding(arrowId: string, terminal: 'start' | 'end', targetShapeId: string | null, binding?: ArrowBinding): void;
655
+ enterEditing(shapeId: string): void;
656
+ exitEditing(): void;
657
+ commitHistory(): void;
658
+ setBrushRect(rect: {
659
+ x: number;
660
+ y: number;
661
+ width: number;
662
+ height: number;
663
+ } | null): void;
664
+ setSnapGuides(guides: SnapGuide[]): void;
665
+ setConnectingTarget(id: string | null): void;
666
+ setGenerationPreview(preview: GenerationPreview | null): void;
667
+ /** 拖动到空白处:创建一条浮动线条并弹出"选择新图形"菜单 */
668
+ openGenerationMenu(payload: GenerationMenuPayload): void;
669
+ /** 创建"新图形 + 连线";existingArrowId 存在时把线条末端接到新图形 */
670
+ createGeneratedConnection(context: GenerationContext, options: {
671
+ type: ShapeType;
672
+ existingArrowId?: string;
673
+ }): void;
674
+ /** 仅创建一条从源点引出、末端悬空的线条,返回线条 id */
675
+ createFloatingLine(context: GenerationContext): string | undefined;
676
+ /** 从源点连到已有图形 */
677
+ connectToExisting(context: GenerationContext, targetShapeId: string, binding?: ArrowBinding): void;
678
+ updateSelectedShapesStyle(updates: Partial<ShapeStyle>): void;
679
+ updateSelectedShapesText(updates: {
680
+ textColor?: string;
681
+ highlightColor?: string;
682
+ }): void;
683
+ updateConnectionType(connectionType: ConnectionType): void;
684
+ }
685
+ declare abstract class Tool {
686
+ abstract type: ToolType;
687
+ protected context: ToolContext;
688
+ constructor(context: ToolContext);
689
+ abstract onPointerDown(point: Vec2, event: PointerEvent): void;
690
+ abstract onPointerMove(point: Vec2, event: PointerEvent): void;
691
+ abstract onPointerUp(point: Vec2, event: PointerEvent): void;
692
+ abstract onKeyDown(event: KeyboardEvent): void;
693
+ abstract onKeyUp(event: KeyboardEvent): void;
694
+ /** 返回当前指针位置应显示的鼠标样式,null 表示使用默认 */
695
+ getCursor(_point: Vec2): string | null;
696
+ dispose(): void;
697
+ }
698
+
699
+ declare class SelectTool extends Tool {
700
+ type: ToolType;
701
+ private state;
702
+ private translatingState;
703
+ private resizingState;
704
+ private generatingState;
705
+ private hoverGenPoint;
706
+ private rotatingState;
707
+ private draggingArrowId;
708
+ private draggingTerminal;
709
+ private pendingHistoryCommit;
710
+ private brushStart;
711
+ private brushShiftKey;
712
+ /** 生成点位默认创建的图形类型:源图形若为可创建类型则同型,否则用矩形 */
713
+ private defaultGenType;
714
+ private genPreviewOf;
715
+ /** 把生成点位打包成创建上下文(from 为源点,to 为目标落点) */
716
+ private genContext;
717
+ onPointerDown(point: Vec2, event: PointerEvent): void;
718
+ onPointerMove(point: Vec2, event: PointerEvent): void;
719
+ onPointerUp(point: Vec2, _event: PointerEvent): void;
720
+ onKeyDown(event: KeyboardEvent): void;
721
+ onKeyUp(_event: KeyboardEvent): void;
722
+ /** 鼠标样式:伸缩手柄 → 对应方向的双向箭头;拖拽中 → 抓取 */
723
+ getCursor(point: Vec2): string | null;
724
+ /** 手柄光标:根据手柄方位 + 图形旋转角映射到标准 resize 光标 */
725
+ private getHandleCursor;
726
+ private static readonly ALIGN_SNAP_THRESHOLD;
727
+ /**
728
+ * 拖拽移动:平滑移动 + 边缘/中心对齐吸附。
729
+ * 对齐规则:移动图形的 上边/中心/下边(及左边/中心/右边)与其他图形对应边比较,
730
+ * 在容差内即视为对齐 → 吸附并显示对应方向的辅助线:
731
+ * - 上下边同时对齐(同尺寸) → 两条平行线
732
+ * - 仅上边或仅下边对齐 → 一条平行线
733
+ * - 仅中心对齐 → 中间一条平行线
734
+ */
735
+ private handleTranslating;
736
+ private handleResizing;
737
+ /** 合并同位置辅助线(同类型同位置合并跨度) */
738
+ private mergeGuides;
739
+ private handleRotation;
740
+ private handleNudge;
741
+ /** 框选:更新选区矩形并选中相交的图形 */
742
+ private handleBrushing;
743
+ /** 获取图形在世界坐标系中的包围盒 */
744
+ private getShapeWorldBounds;
745
+ /** AABB 相交检测 */
746
+ private rectsIntersect;
747
+ private hitTest;
748
+ private getHitHandle;
749
+ /** 生成点位命中阈值 / 单击 vs 拖动判定阈值 */
750
+ private static readonly GEN_HIT_THRESHOLD;
751
+ private static readonly CLICK_MOVE_THRESHOLD;
752
+ /** 收集选中图形上的全部生成点位(图形四边中点 + 连线中点) */
753
+ private computeGenPoints;
754
+ /** 命中检测:指针是否落在某个生成点位附近(取最近的) */
755
+ private hitGenPoint;
756
+ /** 生成点位预览的落点:拖动时跟随光标;静止/悬浮时向外延伸一个默认位置的框 */
757
+ private genPreviewTarget;
758
+ /** 生成拖动:更新预览(线条 + 框)与目标高亮 */
759
+ private handleGenerating;
760
+ private handleRotating;
761
+ private edgeMidpointWorld;
762
+ /** 根据绑定信息返回目标图形的实际吸附点 */
763
+ private bindingPosition;
764
+ private isPointInShape;
765
+ private isPointInHexagon;
766
+ private pointToLineDistance;
767
+ private getShapeBounds;
768
+ private hitTestArrowTerminal;
769
+ private findShapeNearPoint;
770
+ private pointToRectDistance;
771
+ private static readonly SNAP_THRESHOLD;
772
+ private static readonly HANDLE_SNAP_DISTANCE;
773
+ private static readonly ANCHOR_EPS;
774
+ /**
775
+ * 拖拽箭头端点时计算辅助线(与 ArrowTool 规则一致):
776
+ * 另一端绑定的图形 vs 拖拽端点附近的候选图形,若存在边/中心对齐,
777
+ * 按连线主方向出对应方向的辅助线(上下两条平行线 / 上或下一条 / 中间一条)。
778
+ */
779
+ private computeSnapGuidesForArrow;
780
+ /**
781
+ * 在 point 附近找可吸附的图形,并返回 binding(精确或非精确)。
782
+ * 优先级:最近的 handle 吸附(非精确) > 最近的边缘点(精确 normalizedAnchor)
783
+ */
784
+ private findBindingNearPoint;
785
+ private getPageBounds;
786
+ private computeNormalizedAnchor;
787
+ private nearestPointOnShapeOutline;
788
+ private nearestLocalPointOnShapeOutline;
789
+ private nearestPointOnRectOutlineLocal;
790
+ private nearestPointOnCircleOutlineLocal;
791
+ private nearestPointOnSegment;
792
+ }
793
+
794
+ /**
795
+ * 手形(拖拽视口)工具。
796
+ *
797
+ * 设计参考 tldraw 的 HandTool / childStates.Dragging:
798
+ * - 默认光标显示 grab(张开的小手)
799
+ * - 按下:进入 pointing 状态,光标变 grabbing(握紧的小手)
800
+ * - 鼠标位移超过 DRAG_THRESHOLD:进入 dragging 状态
801
+ * - dragging 中:相机 = initialCamera + 屏幕位移
802
+ * (屏幕位移直接加到相机偏移量;与 tldraw 略不同:tldraw 用
803
+ * initialCamera + (delta / zoom) 把屏幕位移除回去再叠加,我们
804
+ * 因为 camera.x/y 本身就是屏幕像素坐标,直接加更直观)
805
+ * - 松开:回到 idle,但保留当前相机和上次鼠标位置,
806
+ * 便于下一段拖拽继续连续移动
807
+ * - Esc:立刻取消正在进行的拖拽
808
+ */
809
+ declare class HandTool extends Tool {
810
+ type: ToolType;
811
+ private state;
812
+ private startScreenPoint;
813
+ private initialCameraPos;
814
+ /** 进入 dragging 状态的最小屏幕位移 */
815
+ private static readonly DRAG_THRESHOLD;
816
+ onPointerDown(_point: Vec2, event: PointerEvent): void;
817
+ onPointerMove(_point: Vec2, event: PointerEvent): void;
818
+ onPointerUp(_point: Vec2, event: PointerEvent): void;
819
+ onKeyDown(event: KeyboardEvent): void;
820
+ onKeyUp(_event: KeyboardEvent): void;
821
+ getCursor(_point: Vec2): string;
822
+ private applyCameraDelta;
823
+ }
824
+
825
+ declare const UML_FONT_SIZE = 14;
826
+ declare const UML_LINE_HEIGHT = 22;
827
+ declare const UML_HEADER_HEIGHT = 36;
828
+ declare const UML_PADDING_X = 14;
829
+ declare const UML_PADDING_Y = 8;
830
+ declare const UML_MIN_WIDTH = 160;
831
+ declare function calcUmlClassSize(className: string, attributes: UmlAttribute[], methods: UmlMethod[]): {
832
+ width: number;
833
+ height: number;
834
+ };
835
+ declare class ShapeTool extends Tool {
836
+ type: ShapeType;
837
+ constructor(context: ToolContext, shapeType: ShapeType);
838
+ onPointerDown(point: Vec2, _event: PointerEvent): void;
839
+ onPointerMove(_point: Vec2, _event: PointerEvent): void;
840
+ onPointerUp(_point: Vec2, _event: PointerEvent): void;
841
+ onKeyDown(_event: KeyboardEvent): void;
842
+ onKeyUp(_event: KeyboardEvent): void;
843
+ getCursor(): string | null;
844
+ }
845
+
846
+ declare class LineTool extends Tool {
847
+ type: "line";
848
+ private isDrawing;
849
+ private startPoint;
850
+ private currentShape;
851
+ onPointerDown(point: Vec2, _event: PointerEvent): void;
852
+ onPointerMove(point: Vec2, _event: PointerEvent): void;
853
+ onPointerUp(_point: Vec2, _event: PointerEvent): void;
854
+ onKeyDown(_event: KeyboardEvent): void;
855
+ onKeyUp(_event: KeyboardEvent): void;
856
+ getCursor(): string | null;
857
+ }
858
+
859
+ type FreehandMode = 'pen' | 'highlighter';
860
+ /**
861
+ * 自由绘制工具(画笔 / 荧光笔)。
862
+ *
863
+ * - pen 模式:生成持久笔迹,除非手动删除(选中后按 Delete)否则一直保留。
864
+ * - highlighter 模式:生成半透明粗笔迹,绘制完成若干秒后自动消失。
865
+ *
866
+ * 两种模式底层都生成 `type:'draw'` 形状,从而复用已有的渲染 / 命中选中 / 删除链路。
867
+ */
868
+ declare class FreehandTool extends Tool {
869
+ type: ToolType;
870
+ private mode;
871
+ private isDrawing;
872
+ private points;
873
+ private currentShapeId;
874
+ private committed;
875
+ private fadeTimer;
876
+ constructor(context: ToolContext, mode: FreehandMode);
877
+ onPointerDown(point: Vec2, _event: PointerEvent): void;
878
+ onPointerMove(point: Vec2, _event: PointerEvent): void;
879
+ onPointerUp(_point: Vec2, _event: PointerEvent): void;
880
+ onKeyDown(_event: KeyboardEvent): void;
881
+ onKeyUp(_event: KeyboardEvent): void;
882
+ getCursor(): string | null;
883
+ dispose(): void;
884
+ }
885
+
886
+ declare class ArrowTool extends Tool {
887
+ type: "arrow";
888
+ private isDrawing;
889
+ private startPoint;
890
+ private currentShapeId;
891
+ private startBindingInfo;
892
+ onPointerDown(point: Vec2, _event: PointerEvent): void;
893
+ onPointerMove(point: Vec2, _event: PointerEvent): void;
894
+ onPointerUp(_point: Vec2, _event: PointerEvent): void;
895
+ onKeyDown(_event: KeyboardEvent): void;
896
+ onKeyUp(_event: KeyboardEvent): void;
897
+ getCursor(): string | null;
898
+ private computeSnapGuides;
899
+ private getShapeBounds;
900
+ private findShapeBinding;
901
+ private pointToRectDistance;
902
+ private getClosestHandle;
903
+ private getBindingPosition;
904
+ private getRectBindingPosition;
905
+ private getCircleBindingPosition;
906
+ }
907
+
908
+ /**
909
+ * 主题颜色常量 - 统一管理所有 UI 颜色
910
+ * 修改此处即可全局生效
911
+ */
912
+ declare const THEME: {
913
+ readonly selection: {
914
+ /** 选中边框颜色 */
915
+ readonly outline: "#3b82f6";
916
+ /** 选中控制点边框颜色 */
917
+ readonly handleStroke: "#3b82f6";
918
+ /** 选中控制点填充颜色 */
919
+ readonly handleFill: "#ffffff";
920
+ /** 选中区域填充颜色 (半透明) */
921
+ readonly outlineFill: "rgba(59, 130, 246, 0.1)";
922
+ /** 选中阴影颜色 */
923
+ readonly shadow: "#3b82f6";
924
+ };
925
+ readonly grid: "#e5e7eb";
926
+ readonly text: "#1f2937";
927
+ readonly editBorder: "#3b82f6";
928
+ };
929
+ /** 获取当前主题(方便未来扩展为动态主题切换) */
930
+ declare function getTheme(): {
931
+ readonly selection: {
932
+ /** 选中边框颜色 */
933
+ readonly outline: "#3b82f6";
934
+ /** 选中控制点边框颜色 */
935
+ readonly handleStroke: "#3b82f6";
936
+ /** 选中控制点填充颜色 */
937
+ readonly handleFill: "#ffffff";
938
+ /** 选中区域填充颜色 (半透明) */
939
+ readonly outlineFill: "rgba(59, 130, 246, 0.1)";
940
+ /** 选中阴影颜色 */
941
+ readonly shadow: "#3b82f6";
942
+ };
943
+ readonly grid: "#e5e7eb";
944
+ readonly text: "#1f2937";
945
+ readonly editBorder: "#3b82f6";
946
+ };
947
+
948
+ export { type ArrowBinding, type ArrowHeadType, type ArrowShape, ArrowTool, Camera, type CameraState, type CircleShape, type ConnectionType, type DiamondShape, type DrawShape, Editor, type EditorState, type EllipseShape, type FreehandMode, FreehandTool, type GenerationContext, HandTool, type HexagonShape, type LineShape, LineTool, Mat2d, type OctagonShape, type ParallelogramShape, type PentagonShape, type Rect, type RectangleShape, Renderer, type RoundedRectangleShape, SelectTool, type Shape, type ShapeStyle, ShapeTool, type ShapeType, type SnapGuide, type StarShape, THEME, type TextShape, type ToolType, type TrapezoidShape, type TriangleShape, UML_FONT_SIZE, UML_HEADER_HEIGHT, UML_LINE_HEIGHT, UML_MIN_WIDTH, UML_PADDING_X, UML_PADDING_Y, type UmlAttribute, type UmlClassShape, type UmlInterfaceShape, type UmlMethod, type UmlVisibility, Vec, type Vec2 as Vec2Type, calcUmlClassSize, getTheme };