image-annotation-drawer 1.3.1 → 1.3.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.d.ts CHANGED
@@ -1,3 +1,1075 @@
1
+ /**
2
+ * 类型定义模块
3
+ * 包含所有标注库的类型定义
4
+ */
5
+ /** 点坐标 */
6
+ type Point = {
7
+ x: number;
8
+ y: number;
9
+ };
10
+ /** 矩形标注 */
11
+ type Rect = {
12
+ start: Point;
13
+ width: number;
14
+ height: number;
15
+ };
16
+ /** 多边形顶点 */
17
+ type Polygon = {
18
+ point: Point;
19
+ };
20
+ /** 文本标注 */
21
+ type TextAnnotation = {
22
+ /** 稳定唯一 ID(入库时生成) */
23
+ id?: string;
24
+ position: {
25
+ x: number;
26
+ y: number;
27
+ };
28
+ text: string;
29
+ width: number;
30
+ height: number;
31
+ /** 文本标注的独立样式 */
32
+ style?: Pick<TextStyle, 'font' | 'color' | 'backgroundColor'>;
33
+ };
34
+ /** 标注类型联合 */
35
+ type Shape = Rect | Polygon | TextAnnotation;
36
+ /** 边线样式 */
37
+ type LineStyle = 'solid' | 'dashed' | 'dotted';
38
+ /** 顶点样式 */
39
+ type VertexStyle = {
40
+ size: number;
41
+ fillColor: string;
42
+ strokeColor: string;
43
+ strokeWidth: number;
44
+ shape: 'circle' | 'square' | 'diamond';
45
+ };
46
+ /** 标题样式 */
47
+ type TitleStyle = {
48
+ /** 字体样式,如 "bold 12px Arial" */
49
+ font?: string;
50
+ /** 文字颜色 */
51
+ color?: string;
52
+ /** 背景颜色 */
53
+ backgroundColor?: string;
54
+ /** 水平内边距 */
55
+ paddingX?: number;
56
+ /** 垂直内边距 */
57
+ paddingY?: number;
58
+ /** 圆角半径 */
59
+ borderRadius?: number;
60
+ /** 标题输入框的占位提示文本(随标题样式快照存入标注) */
61
+ placeholder?: string;
62
+ };
63
+ /** 标题位置 */
64
+ type TitlePosition = {
65
+ /** 垂直位置:top=标注上方, bottom=标注下方, inside-top=标注内部上方 */
66
+ vertical?: 'top' | 'bottom' | 'inside-top';
67
+ /** 水平对齐:left/center/right */
68
+ align?: 'left' | 'center' | 'right';
69
+ /** 水平偏移(像素,图像坐标) */
70
+ offsetX?: number;
71
+ /** 垂直偏移(像素,图像坐标) */
72
+ offsetY?: number;
73
+ };
74
+ /** 标注样式 */
75
+ type AnnotationStyle = {
76
+ strokeColor: string;
77
+ lineWidth: number;
78
+ lineStyle?: LineStyle;
79
+ vertexStyle?: VertexStyle;
80
+ /** 标注标题样式(覆盖全局默认) */
81
+ titleStyle?: TitleStyle;
82
+ /** 标注标题位置(覆盖全局默认) */
83
+ titlePosition?: TitlePosition;
84
+ /** 标题位置是否由用户显式设置(true 时不参与设置标题时的自动边缘判断) */
85
+ titlePositionExplicit?: boolean;
86
+ };
87
+ /** 操作记录 */
88
+ type Operate<T extends Shape> = {
89
+ /** 稳定唯一 ID(入库时生成;绘制中的临时对象无 id) */
90
+ id?: string;
91
+ type: "rect" | "polygon" | "drag" | "text" | "";
92
+ data: T[];
93
+ status: "fullfilled" | "pending";
94
+ style?: AnnotationStyle;
95
+ /** 标注标题 */
96
+ title?: string;
97
+ };
98
+ /** 绘制类型 */
99
+ type DrawType = "rect" | "polygon" | "drag" | "text" | "";
100
+ /** 抽屉选项 */
101
+ type DrawerOptions = {
102
+ id: string;
103
+ drawType?: DrawType;
104
+ useEvents?: boolean;
105
+ /** 标注颜色配置 */
106
+ annotationColor?: string | ColorConfig;
107
+ /** 边线样式 */
108
+ lineStyle?: LineStyle;
109
+ /** 顶点样式 */
110
+ vertexStyle?: Partial<VertexStyle>;
111
+ /** 文本样式 */
112
+ textStyle?: Partial<TextStyle>;
113
+ /** 是否启用标注标题功能(默认 false,需显式开启) */
114
+ enableTitle?: boolean;
115
+ /** 是否将绘制坐标约束在图片边界内(默认 true,图片未加载时自动跳过) */
116
+ clampToImageBounds?: boolean;
117
+ };
118
+ /** 选中标注信息 */
119
+ type SelectedAnnotation = {
120
+ index: number;
121
+ type: "rect" | "polygon" | "text";
122
+ /** 锁定选中态:true 时无控制点(不可 resize);可选字段,缺省视为 false */
123
+ locked?: boolean;
124
+ };
125
+ /** 控制点信息 */
126
+ type ActiveHandle = {
127
+ type: "rect-corner" | "polygon-vertex";
128
+ index: number;
129
+ };
130
+ /** 事件处理器类型 */
131
+ type WheelEventListener = (event: WheelEvent) => void;
132
+ type MouseEventListener = (event: MouseEvent) => void;
133
+ type KeyboardEventListener = (event: KeyboardEvent) => void;
134
+ /** 抽屉事件处理器集合 */
135
+ type DrawerEventHandlers = {
136
+ wheel: WheelEventListener;
137
+ mousedown: MouseEventListener;
138
+ mousemove: MouseEventListener;
139
+ mouseup: MouseEventListener;
140
+ mouseleave: MouseEventListener;
141
+ dblclick: MouseEventListener;
142
+ click: MouseEventListener;
143
+ keydown: KeyboardEventListener;
144
+ };
145
+ /** 文本输入框样式 */
146
+ type TextInputStyle = {
147
+ border: string;
148
+ borderRadius: string;
149
+ padding: string;
150
+ fontSize: string;
151
+ fontFamily: string;
152
+ backgroundColor: string;
153
+ color: string;
154
+ boxShadow: string;
155
+ minWidth: string;
156
+ maxWidth: string;
157
+ };
158
+ /** 文本样式配置 */
159
+ type TextStyle = {
160
+ font: string;
161
+ color: string;
162
+ padding: number;
163
+ backgroundColor: string;
164
+ borderRadius: number;
165
+ selectedBorderColor: string;
166
+ selectedBackgroundColor: string;
167
+ /** 文本输入框样式 */
168
+ inputStyle?: Partial<TextInputStyle>;
169
+ };
170
+ /** 选中样式配置 */
171
+ type SelectionStyle = {
172
+ strokeColor: string;
173
+ fillColor: string;
174
+ handleSize: number;
175
+ handleColor: string;
176
+ };
177
+ /** 颜色配置 */
178
+ type ColorConfig = {
179
+ rect?: string;
180
+ polygon?: string;
181
+ text?: string;
182
+ default?: string;
183
+ };
184
+ /** 视口尺寸 */
185
+ type ViewportSize = {
186
+ width: number;
187
+ height: number;
188
+ };
189
+ /** 视图偏移 */
190
+ type ViewportOffset = {
191
+ x: number;
192
+ y: number;
193
+ };
194
+ /** 标注变更事件载荷(create/delete/update 共用) */
195
+ type ShapeChangePayload = {
196
+ id: string;
197
+ type: 'rect' | 'polygon';
198
+ index: number;
199
+ data: Operate<Rect | Polygon>;
200
+ } | {
201
+ id: string;
202
+ type: 'text';
203
+ index: number;
204
+ data: TextAnnotation;
205
+ };
206
+ /** Drawer 事件表 */
207
+ type DrawerEventMap = {
208
+ /** 新标注画完入库 */
209
+ create: ShapeChangePayload;
210
+ /** 标注被删除 */
211
+ delete: ShapeChangePayload;
212
+ /** 标注数据变化(移动/缩放结束、标题/文本/样式修改) */
213
+ update: ShapeChangePayload;
214
+ /** 清空所有标注 */
215
+ clear: undefined;
216
+ /** 撤销成功 */
217
+ undo: undefined;
218
+ };
219
+ type DrawerEventName = keyof DrawerEventMap;
220
+ type DrawerListener<K extends DrawerEventName> = (payload: DrawerEventMap[K]) => void;
221
+ /** 标注变更通知回调(manager 内部上报给 Drawer 使用) */
222
+ type ChangeNotify = (event: 'create' | 'delete' | 'update', payload: ShapeChangePayload) => void;
223
+
224
+ /**
225
+ * 工具函数模块
226
+ * 包含几何计算、坐标转换等通用工具函数
227
+ */
228
+
229
+ /**
230
+ * 检测点是否在矩形内
231
+ * 支持负宽高的情况(从右下往左上画)
232
+ */
233
+ declare function isPointInRect(point: Point, rect: Rect): boolean;
234
+ /**
235
+ * 检测点是否在多边形内(射线法)
236
+ */
237
+ declare function isPointInPolygon(point: Point, polygon: Polygon[]): boolean;
238
+ /**
239
+ * 从 URL 解析图片类型
240
+ * 支持 png, jpg, jpeg, webp, gif, bmp, svg 等常见格式
241
+ */
242
+ declare function getImageTypeFromUrl(urlString: string): string;
243
+ /**
244
+ * 将 base64 字符串转换为 File 对象
245
+ */
246
+ declare function base64ToFile(base64Data: string, filename: string): File;
247
+ /**
248
+ * 将 base64 字符串转换为 Blob 对象(用于上传)
249
+ */
250
+ declare function base64ToBlob(base64: string): Promise<Blob>;
251
+ /**
252
+ * 计算缩放增量
253
+ */
254
+ declare function getZoomDelta(currentScale: number, zoomIn: boolean): number;
255
+
256
+ /**
257
+ * 视图管理模块
258
+ * 负责视口尺寸、缩放、偏移、边界约束等视图相关功能
259
+ */
260
+
261
+ declare class ViewportManager {
262
+ width: number;
263
+ height: number;
264
+ originalWidth: number;
265
+ originalHeight: number;
266
+ scale: number;
267
+ minScale: number;
268
+ maxScale: number;
269
+ offset: ViewportOffset;
270
+ initialScale: number;
271
+ initialOffset: ViewportOffset;
272
+ isInitialScale: boolean;
273
+ minOffsetX: number;
274
+ minOffsetY: number;
275
+ maxOffsetX: number;
276
+ maxOffsetY: number;
277
+ /**
278
+ * 设置视口尺寸
279
+ */
280
+ setSize(width: number, height: number): void;
281
+ /**
282
+ * 设置原始图片尺寸
283
+ */
284
+ setOriginalSize(width: number, height: number): void;
285
+ /**
286
+ * 计算初始视图参数
287
+ */
288
+ calculateInitialView(): void;
289
+ /**
290
+ * 计算边界约束
291
+ */
292
+ calculateBoundaries(): void;
293
+ /**
294
+ * 确保视图在合理范围内
295
+ */
296
+ constrainViewport(): void;
297
+ /**
298
+ * 将画布坐标转换为图像坐标
299
+ */
300
+ toImageCoordinates(canvasX: number, canvasY: number): {
301
+ x: number;
302
+ y: number;
303
+ };
304
+ /**
305
+ * 将图像坐标转换为画布坐标
306
+ */
307
+ toCanvasCoordinates(imgX: number, imgY: number): {
308
+ x: number;
309
+ y: number;
310
+ };
311
+ /**
312
+ * 更新缩放比例
313
+ */
314
+ updateScale(newScale: number, centerX: number, centerY: number): void;
315
+ /**
316
+ * 重置视图到初始状态
317
+ */
318
+ resetToInitial(): void;
319
+ /**
320
+ * 更新偏移量(用于拖拽)
321
+ */
322
+ updateOffset(dx: number, dy: number): void;
323
+ /**
324
+ * 重置所有状态
325
+ */
326
+ reset(): void;
327
+ }
328
+
329
+ /**
330
+ * 形状策略接口:每种标注形状(rect/polygon/未来星型)实现一个策略
331
+ * 策略是无状态的纯逻辑单元,所有共享状态存于 AnnotationStore
332
+ */
333
+
334
+ /** resize 所需的原始数据快照(rect 用 Rect 副本,polygon 用 Polygon[] 副本) */
335
+ type ResizeSnapshot = Rect | Polygon[];
336
+ interface ShapeStrategy<T extends Rect | Polygon = Rect | Polygon> {
337
+ readonly type: 'rect' | 'polygon';
338
+ /** 开始绘制(point 已由 store 完成 clamp) */
339
+ startDrawing(store: StrategyStore, point: Point): void;
340
+ /** 更新绘制中状态(rect: 更新宽高;polygon: 更新临时点) */
341
+ updateDrawing(store: StrategyStore, point: Point): void;
342
+ /** 尝试加点(仅 polygon 有意义;rect 空实现返回 false)。返回是否实际添加 */
343
+ addPoint(store: StrategyStore, point: Point): boolean;
344
+ /** 完成绘制;满足入库条件则推入 recordList 并 notifyChange('create')。返回是否结束了绘制 */
345
+ finishDrawing(store: StrategyStore): boolean;
346
+ /** 取消绘制(polygon ≥2 点保存入库的现有行为在策略内实现) */
347
+ cancelDrawing(store: StrategyStore): void;
348
+ /** 撤销绘制中状态(polygon 逐点 pop;rect 整体清)。返回是否有动作 */
349
+ withdrawInProgress(store: StrategyStore): boolean;
350
+ hitTest(record: Operate<T>, imgCoords: Point): boolean;
351
+ /** 返回画布坐标系控制点列表 */
352
+ getHandles(record: Operate<T>, viewport: ViewportManager, handleSize: number): {
353
+ x: number;
354
+ y: number;
355
+ index: number;
356
+ }[];
357
+ move(record: Operate<T>, dx: number, dy: number): void;
358
+ /** 拍摄 resize 前快照 */
359
+ snapshotForResize(record: Operate<T>): ResizeSnapshot;
360
+ /** 执行 resize;如需交换控制点(rect 越过交换)返回新 handle index,否则返回 undefined */
361
+ resize(record: Operate<T>, handleIndex: number, currentPoint: Point, snapshot: ResizeSnapshot): number | undefined;
362
+ /** 标题/导出参考边界(图像坐标):rect 用自身,polygon 用顶点 bbox */
363
+ getBounds(record: Operate<T>): {
364
+ x: number;
365
+ y: number;
366
+ width: number;
367
+ height: number;
368
+ };
369
+ }
370
+ /** 策略视角的 Store 最小契约(AnnotationStore 实现它) */
371
+ interface StrategyStore {
372
+ operate: Operate<Rect | Polygon>;
373
+ isDrawing: boolean;
374
+ drawStartPoint: Point;
375
+ tempPolygonPoint: Point | null;
376
+ recordList: Operate<Rect | Polygon>[];
377
+ strokeStyle: string;
378
+ lineWidth: number;
379
+ getCurrentStyle(): AnnotationStyle;
380
+ clamp(point: Point): Point;
381
+ pushRecord(record: Operate<Rect | Polygon>): void;
382
+ }
383
+
384
+ /**
385
+ * 标注存储基类
386
+ * 持有标注的全部共享状态与共享逻辑(选中/删除/撤销编排/样式/颜色/标题/clamp/通知/清空),
387
+ * 形状特异逻辑通过 ShapeStrategy 委托(见 shapes/ 目录),由子类 AnnotationManager 分发。
388
+ *
389
+ * 实现 StrategyStore 契约,供无状态策略读写绘制中状态。
390
+ */
391
+
392
+ declare class AnnotationStore implements StrategyStore {
393
+ protected viewport: ViewportManager;
394
+ protected container?: HTMLElement | undefined;
395
+ protected renderCallback?: (() => void) | undefined;
396
+ protected changeCallback?: ChangeNotify | undefined;
397
+ recordList: Operate<Rect | Polygon>[];
398
+ operate: Operate<Rect | Polygon>;
399
+ isDrawing: boolean;
400
+ drawStartPoint: Point;
401
+ tempPolygonPoint: Point | null;
402
+ selectedAnnotation: SelectedAnnotation | null;
403
+ isMovingAnnotation: boolean;
404
+ isResizing: boolean;
405
+ activeHandle: ActiveHandle | null;
406
+ annotationMoveStart: Point;
407
+ originalRect: Rect | null;
408
+ originalPolygon: Polygon[] | null;
409
+ protected deleteHistory: {
410
+ annotation: Operate<Rect | Polygon>;
411
+ index: number;
412
+ }[];
413
+ editingTitleIndex: number | null;
414
+ titleInput: HTMLInputElement | null;
415
+ strokeStyle: string;
416
+ lineWidth: number;
417
+ lineStyle: LineStyle;
418
+ vertexStyle: VertexStyle;
419
+ selectionStyle: SelectionStyle;
420
+ private colorConfig;
421
+ titleStyle: TitleStyle;
422
+ titlePosition: TitlePosition;
423
+ enableTitle: boolean;
424
+ measureTitleWidth?: (text: string, font: string) => number;
425
+ clampEnabled: boolean;
426
+ constructor(viewport: ViewportManager, container?: HTMLElement | undefined, renderCallback?: (() => void) | undefined, changeCallback?: ChangeNotify | undefined);
427
+ /**
428
+ * 按形状类型查找策略(委托默认注册表)
429
+ */
430
+ protected strategyOf(type: 'rect' | 'polygon'): ShapeStrategy;
431
+ /**
432
+ * 将点约束到图片边界内(StrategyStore 契约;委托 shapes/shared.clampPoint)
433
+ */
434
+ clamp(point: Point): Point;
435
+ /**
436
+ * 将点约束到图片边界内(原图像素坐标系)
437
+ * clampEnabled 为 false 或图片未加载(originalWidth/originalHeight 为 0)时跳过 clamp,原样返回
438
+ */
439
+ protected clampToImageBounds(point: Point): Point;
440
+ /**
441
+ * 把**整个标注**约束到图片边界内 —— 供「移动 / 缩放已有标注」使用。
442
+ *
443
+ * 为什么需要单独一个:`strategy.move()` 只是把坐标整体加 dx/dy,`resize()` 直接用鼠标点,
444
+ * 两者原先都没有边界判断,所以已有标注能被拖到/缩到图外;
445
+ * 而 `clampToImageBounds(point)` 只作用于绘制入口(startRectDrawing / updateRectDrawing 等)。
446
+ *
447
+ * - `clampEnabled` 为 false 或图片未加载时原样返回
448
+ * - 矩形:按包围盒先把尺寸限制到不超过图片,再把左上角夹到 `[0, w-width] × [0, h-height]`
449
+ * - 多边形:按包围盒整体平移;比图片还大时贴左上角
450
+ * - **完全在图内时不做任何改动**(保留调用方原有的坐标表示,例如负宽高)
451
+ */
452
+ protected clampRecordToImageBounds(record: Operate<Rect | Polygon>): void;
453
+ /**
454
+ * 新标注入库:generateId + 当前样式快照 + push + 清删除历史 + notifyChange('create')
455
+ * (统一自原 finishRectDrawing / finishPolygonDrawing / cancelDrawing 的入库尾段;
456
+ * 记录 status 由策略在调用前自行设定,store 不统一改写)
457
+ */
458
+ pushRecord(record: Operate<Rect | Polygon>): void;
459
+ /**
460
+ * 初始化标题功能(由 Drawer 在设置 enableTitle 后调用)
461
+ */
462
+ initTitleSupport(): void;
463
+ /**
464
+ * 设置颜色配置
465
+ */
466
+ setColorConfig(config: string | ColorConfig): void;
467
+ /**
468
+ * 获取颜色配置
469
+ */
470
+ getColorConfig(): ColorConfig;
471
+ /**
472
+ * 应用颜色配置到当前样式
473
+ */
474
+ private applyColorConfig;
475
+ /**
476
+ * 获取指定类型的颜色
477
+ */
478
+ getColorForType(type: 'rect' | 'polygon' | 'default'): string;
479
+ /**
480
+ * 获取当前样式(用于创建新标注时保存)
481
+ */
482
+ getCurrentStyle(): AnnotationStyle;
483
+ /**
484
+ * 上报标注变更(载荷携带深拷贝快照;委托 shapes/shared.notifyShapeChange)
485
+ */
486
+ protected notifyChange(event: 'create' | 'delete' | 'update', index: number, annotation: Operate<Rect | Polygon>): void;
487
+ /**
488
+ * 供 Drawer 在程序化移动后触发 update(拖拽路径由 finishMovingAnnotation 触发)
489
+ */
490
+ notifyUpdate(index: number): void;
491
+ /**
492
+ * 解析标注定位参数:number 按索引,string 按 id
493
+ */
494
+ resolveIndex(ref: number | string): number;
495
+ /**
496
+ * 按索引或 id 获取单个标注(深拷贝快照)
497
+ */
498
+ getAnnotation(ref: number | string): Operate<Rect | Polygon> | undefined;
499
+ /**
500
+ * 将当前全局样式应用到指定标注(按索引或 id),保存并通知 update
501
+ */
502
+ updateAnnotationStyle(ref: number | string): boolean;
503
+ /**
504
+ * 设置边线样式
505
+ */
506
+ setLineStyle(style: LineStyle): void;
507
+ /**
508
+ * 设置顶点样式
509
+ */
510
+ setVertexStyle(style: Partial<VertexStyle>): void;
511
+ /**
512
+ * 获取边线样式
513
+ */
514
+ getLineStyle(): LineStyle;
515
+ /**
516
+ * 获取顶点样式
517
+ */
518
+ getVertexStyle(): VertexStyle;
519
+ /**
520
+ * 获取标注的样式(如果不存在则返回当前样式)
521
+ */
522
+ getAnnotationStyle(annotation: Operate<Rect | Polygon>): AnnotationStyle;
523
+ /**
524
+ * 撤销操作(编排顺序:先策略撤销绘制中状态,再 deleteHistory 恢复,再 recordList.pop)
525
+ */
526
+ withdraw(): boolean;
527
+ /**
528
+ * 选中指定索引或 id 的标注
529
+ * @param ref - 标注索引或 id
530
+ * @param options.lock - true 时进入锁定选中态(无控制点,不可 resize);缺省/false 与现状一致
531
+ */
532
+ selectAnnotation(ref: number | string, options?: {
533
+ lock?: boolean;
534
+ }): boolean;
535
+ /**
536
+ * 取消选中
537
+ */
538
+ deselectAnnotation(): void;
539
+ /**
540
+ * 删除选中的标注
541
+ */
542
+ deleteSelectedAnnotation(): boolean;
543
+ /**
544
+ * 开始移动标注(锁定选中态不可交互移动;程序化 moveSelectedAnnotation 不受此限制)
545
+ */
546
+ startMovingAnnotation(e: MouseEvent): boolean;
547
+ /**
548
+ * 更新移动起始点(供事件层在移动过程中同步鼠标位置,替代越权直写 annotationMoveStart)
549
+ */
550
+ updateMoveStart(p: Point): void;
551
+ /**
552
+ * 完成标注移动
553
+ */
554
+ finishMovingAnnotation(): void;
555
+ /**
556
+ * 清除所有标注
557
+ */
558
+ clear(): void;
559
+ /**
560
+ * 获取所有标注数据
561
+ */
562
+ getAnnotations(): Operate<Rect | Polygon>[];
563
+ /**
564
+ * 获取当前选中的标注信息
565
+ */
566
+ getSelectedAnnotation(): {
567
+ index: number;
568
+ type: "rect" | "polygon" | "text";
569
+ data: Operate<Rect | Polygon>;
570
+ } | null;
571
+ /**
572
+ * 设置标题样式(全局默认)
573
+ * @param style - 标题样式配置
574
+ */
575
+ setTitleStyle(style: Partial<TitleStyle>): void;
576
+ /**
577
+ * 获取当前标题样式
578
+ */
579
+ getTitleStyle(): TitleStyle;
580
+ /**
581
+ * 设置标题位置(全局默认)
582
+ * @param position - 标题位置配置
583
+ */
584
+ setTitlePosition(position: Partial<TitlePosition>): void;
585
+ /**
586
+ * 获取当前标题位置
587
+ */
588
+ getTitlePosition(): TitlePosition;
589
+ /**
590
+ * 获取标注实际使用的标题样式(标注自定义样式优先于全局默认)
591
+ */
592
+ getEffectiveTitleStyle(annotation: Operate<Rect | Polygon>): TitleStyle;
593
+ /**
594
+ * 获取标注实际使用的标题位置(标注自定义位置优先于全局默认)
595
+ */
596
+ getEffectiveTitlePosition(annotation: Operate<Rect | Polygon>): TitlePosition;
597
+ /**
598
+ * 将当前全局标题样式/位置快照存入标注(设置标题时自动调用)
599
+ */
600
+ private captureTitleStyleSnapshot;
601
+ /**
602
+ * 设置标题时按图片初始显示尺寸自动判断标题位置(翻转优先 + 贴边),并写回标注。
603
+ * 规则:显式设置过位置、图片未加载、空多边形时跳过。
604
+ */
605
+ private autoAdjustTitlePosition;
606
+ /**
607
+ * 图片加载完成后,对所有「未显式设置位置」的标题补做一次边缘判断。
608
+ * 用于图片尚未加载时就通过 API 设置了标题的场景。
609
+ */
610
+ adjustTitlePositions(): void;
611
+ /**
612
+ * 单独修改某个标注的标题样式(不影响全局默认)
613
+ * @param ref - 标注索引或 id
614
+ * @param style - 标题样式(部分覆盖)
615
+ */
616
+ setAnnotationTitleStyle(ref: number | string, style: Partial<TitleStyle>): boolean;
617
+ /**
618
+ * 单独修改某个标注的标题位置(不影响全局默认)
619
+ * @param ref - 标注索引或 id
620
+ * @param position - 标题位置(部分覆盖)
621
+ */
622
+ setAnnotationTitlePosition(ref: number | string, position: Partial<TitlePosition>): boolean;
623
+ /**
624
+ * 创建标题输入框
625
+ */
626
+ private createTitleInput;
627
+ /**
628
+ * 设置标注标题(API调用)
629
+ * @param ref - 标注索引或 id
630
+ * @param title - 标题文本
631
+ */
632
+ setTitle(ref: number | string, title: string): boolean;
633
+ /**
634
+ * 获取标注标题
635
+ * @param ref - 标注索引或 id
636
+ */
637
+ getTitle(ref: number | string): string | undefined;
638
+ /**
639
+ * 开始编辑标题(双击或API触发)
640
+ * @param index - 标注索引
641
+ */
642
+ startTitleEditing(index: number): boolean;
643
+ /**
644
+ * 完成标题编辑
645
+ */
646
+ finishTitleEditing(): boolean;
647
+ /**
648
+ * 取消标题编辑
649
+ */
650
+ cancelTitleEditing(): boolean;
651
+ /**
652
+ * 重置标题编辑状态
653
+ */
654
+ private resetTitleEditingState;
655
+ /**
656
+ * 清理标题输入框
657
+ */
658
+ destroyTitleInput(): void;
659
+ }
660
+
661
+ /**
662
+ * 标注管理模块(兼容层)
663
+ * 继承 AnnotationStore 获得全部共享状态与共享方法,
664
+ * 形状特异方法在此按类型委托给 ShapeStrategy(见 shapes/ 目录与 registry.ts)。
665
+ * 公共 API(构造签名、方法签名、字段名、通知时机)与原实现完全一致。
666
+ */
667
+
668
+ declare class AnnotationManager extends AnnotationStore {
669
+ /**
670
+ * 开始绘制矩形
671
+ */
672
+ startRectDrawing(startPoint: Point): void;
673
+ /**
674
+ * 更新矩形绘制
675
+ */
676
+ updateRectDrawing(currentPoint: Point): void;
677
+ /**
678
+ * 完成矩形绘制
679
+ */
680
+ finishRectDrawing(): boolean;
681
+ /**
682
+ * 开始绘制多边形
683
+ */
684
+ startPolygonDrawing(startPoint: Point): void;
685
+ /**
686
+ * 添加多边形点
687
+ */
688
+ addPolygonPoint(point: Point): boolean;
689
+ /**
690
+ * 更新多边形临时点
691
+ */
692
+ updatePolygonTempPoint(point: Point): void;
693
+ /**
694
+ * 完成多边形绘制
695
+ */
696
+ finishPolygonDrawing(): boolean;
697
+ /**
698
+ * 取消当前绘制(按 operate.type 分发;polygon ≥2 点保存入库的行为在策略内;
699
+ * 非 polygon 一律按 rect 语义复位,与原实现的尾部三行一致)
700
+ */
701
+ cancelDrawing(): void;
702
+ /**
703
+ * 移动选中的标注
704
+ */
705
+ moveSelectedAnnotation(dx: number, dy: number): boolean;
706
+ /**
707
+ * 开始调整大小(拍摄一次 resize 快照;拖拽期间策略原地同步快照,store 不重拍)
708
+ */
709
+ startResizing(handle: ActiveHandle, _startPoint: Point): boolean;
710
+ /**
711
+ * 调整矩形大小(策略返回越过交换后的新 handle index 时写回 activeHandle.index)
712
+ */
713
+ resizeRect(currentPoint: Point): boolean;
714
+ /**
715
+ * 调整多边形顶点位置
716
+ */
717
+ resizePolygon(currentPoint: Point): boolean;
718
+ /**
719
+ * 获取点击位置对应的标注(倒序遍历,优先上层)
720
+ */
721
+ getAnnotationAtPoint(imgCoords: Point): SelectedAnnotation | null;
722
+ /**
723
+ * 获取点击位置的控制点(命中判定在 store:控制点画布坐标 ± handleSize/2)
724
+ */
725
+ getHandleAtPoint(offsetX: number, offsetY: number): ActiveHandle | null;
726
+ }
727
+
728
+ /**
729
+ * 文本标注模块
730
+ * 负责文本标注的添加、编辑、移动、删除等功能
731
+ */
732
+
733
+ declare class TextAnnotationManager {
734
+ private viewport;
735
+ private container;
736
+ private ctx;
737
+ private renderCallback?;
738
+ private changeCallback?;
739
+ textAnnotations: TextAnnotation[];
740
+ editingTextIndex: number | null;
741
+ currentEditingIndex: number | null;
742
+ movingTextIndex: number | null;
743
+ isTextMoving: boolean;
744
+ textMoveStart: Point;
745
+ textBeforeEditing: string;
746
+ selectedTextIndex: number | null;
747
+ clampEnabled: boolean;
748
+ textInput: HTMLInputElement | null;
749
+ private deleteHistory;
750
+ private defaultInputStyle;
751
+ textStyle: TextStyle;
752
+ constructor(viewport: ViewportManager, container: HTMLElement, ctx: CanvasRenderingContext2D, renderCallback?: (() => void) | undefined, changeCallback?: ChangeNotify | undefined);
753
+ /**
754
+ * 创建文本输入框
755
+ */
756
+ private createTextInput;
757
+ /**
758
+ * 应用输入框样式
759
+ */
760
+ private applyInputStyle;
761
+ /**
762
+ * 设置文本样式
763
+ */
764
+ setTextStyle(style: Partial<TextStyle>): void;
765
+ /**
766
+ * 设置输入框样式
767
+ */
768
+ setInputStyle(style: Partial<TextInputStyle>): void;
769
+ /**
770
+ * 设置选中态样式
771
+ */
772
+ setSelectionStyle(style: {
773
+ selectedBorderColor?: string;
774
+ selectedBackgroundColor?: string;
775
+ }): void;
776
+ /**
777
+ * 获取当前文本样式(用于创建新标注时保存)
778
+ */
779
+ getCurrentStyle(): Pick<TextStyle, 'font' | 'color' | 'backgroundColor'>;
780
+ /**
781
+ * 上报文本标注变更(载荷携带深拷贝快照)
782
+ */
783
+ private notifyChange;
784
+ /**
785
+ * 解析文本标注定位参数:number 按索引,string 按 id
786
+ */
787
+ resolveIndex(ref: number | string): number;
788
+ /**
789
+ * 按索引或 id 获取单个文本标注(深拷贝快照)
790
+ */
791
+ getTextAnnotation(ref: number | string): TextAnnotation | undefined;
792
+ /**
793
+ * 更新指定文本标注的样式(部分覆盖,不影响全局默认和其他标注)
794
+ * @param ref - 文本标注索引或 id
795
+ * @param style - 样式(部分覆盖)
796
+ */
797
+ updateTextAnnotationStyle(ref: number | string, style: Partial<Pick<TextStyle, 'font' | 'color' | 'backgroundColor'>>): boolean;
798
+ /**
799
+ * 将点约束到图片边界内(原图像素坐标系)
800
+ * clampEnabled 为 false 或图片未加载(originalWidth/originalHeight 为 0)时跳过 clamp,原样返回
801
+ */
802
+ private clampToImageBounds;
803
+ /**
804
+ * 添加文本标注
805
+ * @param x - 图像坐标 X(标注背景框左上角)
806
+ * @param y - 图像坐标 Y(标注背景框左上角)
807
+ * @param text - 初始文本(可选,默认为空字符串)
808
+ */
809
+ addTextAnnotation(x: number, y: number, text?: string): number;
810
+ /**
811
+ * 更新文本标注内容
812
+ */
813
+ updateTextAnnotation(ref: number | string, text: string): boolean;
814
+ /**
815
+ * 移动文本标注位置
816
+ */
817
+ moveTextAnnotation(ref: number | string, x: number, y: number): boolean;
818
+ /**
819
+ * 删除文本标注
820
+ */
821
+ removeTextAnnotation(ref: number | string): boolean;
822
+ /**
823
+ * 清除所有文本标注
824
+ */
825
+ clearTextAnnotations(): void;
826
+ /**
827
+ * 开始编辑文本标注
828
+ */
829
+ startEditing(index: number): boolean;
830
+ /**
831
+ * 完成编辑
832
+ */
833
+ finishEditing(): boolean;
834
+ /**
835
+ * 取消编辑
836
+ */
837
+ cancelEditing(): boolean;
838
+ /**
839
+ * 删除正在编辑的文本标注
840
+ */
841
+ deleteEditingAnnotation(): boolean;
842
+ /**
843
+ * 重置编辑状态
844
+ */
845
+ private resetEditingState;
846
+ /**
847
+ * 调整输入框宽度
848
+ */
849
+ private adjustInputWidth;
850
+ /**
851
+ * 开始移动文本标注
852
+ */
853
+ startMoving(e: MouseEvent, index: number): boolean;
854
+ /**
855
+ * 移动文本标注
856
+ */
857
+ moveAnnotation(e: MouseEvent): boolean;
858
+ /**
859
+ * 完成移动
860
+ */
861
+ finishMoving(): void;
862
+ /**
863
+ * 结束文本移动(不触碰编辑态)
864
+ */
865
+ endTextMoving(): void;
866
+ /**
867
+ * 当前是否处于 DOM 编辑可见状态(输入框显示中)
868
+ */
869
+ isEditingVisible(): boolean;
870
+ /**
871
+ * 检查是否点击了文本标注
872
+ */
873
+ checkTextClick(offsetX: number, offsetY: number): number | null;
874
+ /**
875
+ * 检查是否点击了文本标注(用于移动)
876
+ */
877
+ checkTextClickForMove(e: MouseEvent): {
878
+ index: number;
879
+ handled: boolean;
880
+ };
881
+ /**
882
+ * 获取所有文本标注
883
+ */
884
+ getTextAnnotations(): TextAnnotation[];
885
+ /**
886
+ * 撤销文本标注
887
+ */
888
+ withdraw(): boolean;
889
+ /**
890
+ * 选中文本标注
891
+ */
892
+ selectTextAnnotation(index: number): boolean;
893
+ /**
894
+ * 取消选中文本标注
895
+ */
896
+ deselectTextAnnotation(): void;
897
+ /**
898
+ * 删除选中的文本标注
899
+ */
900
+ deleteSelectedTextAnnotation(): boolean;
901
+ /**
902
+ * 获取选中的文本标注信息
903
+ */
904
+ getSelectedTextAnnotation(): {
905
+ index: number;
906
+ data: TextAnnotation;
907
+ } | null;
908
+ /**
909
+ * 销毁(清理 DOM)
910
+ */
911
+ destroy(): void;
912
+ }
913
+
914
+ /**
915
+ * 渲染模块
916
+ * 渲染管线:背景图片 → 形状标注(策略表分发) → 选中高亮 → 文本标注
917
+ * 形状特异绘制逻辑已拆至 renderers/ 目录(Task 7)
918
+ */
919
+
920
+ declare class Renderer {
921
+ private ctx;
922
+ private viewport;
923
+ private annotationManager;
924
+ private textManager;
925
+ /** 形状渲染策略表 */
926
+ private shapeRenderers;
927
+ constructor(ctx: CanvasRenderingContext2D, viewport: ViewportManager, annotationManager: AnnotationManager, textManager: TextAnnotationManager);
928
+ /**
929
+ * 主渲染方法
930
+ */
931
+ render(bgImage: HTMLImageElement | null): void;
932
+ /**
933
+ * 绘制背景图片
934
+ */
935
+ private drawBackgroundImage;
936
+ /**
937
+ * 绘制标注(矩形、多边形)——ctx 状态由管线统一设置,几何绘制委托策略表
938
+ */
939
+ private drawAnnotations;
940
+ /**
941
+ * 绘制选中状态的标注高亮(读取锁定态:locked 时策略不画控制点)
942
+ */
943
+ private drawSelectionHighlight;
944
+ /**
945
+ * 绘制标注标题
946
+ * @param annotation - 标注操作记录
947
+ */
948
+ private drawAnnotationTitle;
949
+ /**
950
+ * 导出时绘制所有内容到指定上下文
951
+ */
952
+ drawForExport(ctx: CanvasRenderingContext2D, bgImage: HTMLImageElement, originalWidth: number, originalHeight: number): void;
953
+ }
954
+
955
+ /**
956
+ * 事件处理模块
957
+ * 负责所有用户交互事件的处理
958
+ */
959
+
960
+ declare class EventHandler {
961
+ private canvas;
962
+ private viewport;
963
+ private annotationManager;
964
+ private textManager;
965
+ private renderer;
966
+ private getDrawType;
967
+ private getBgImage;
968
+ private renderCallback;
969
+ private setDrawTypeCallback;
970
+ private notifyUndo;
971
+ isDragging: boolean;
972
+ dragStart: Point;
973
+ justFinishedMove: boolean;
974
+ justDeselected: boolean;
975
+ constructor(canvas: HTMLCanvasElement, viewport: ViewportManager, annotationManager: AnnotationManager, textManager: TextAnnotationManager, renderer: Renderer, getDrawType: () => DrawType, getBgImage: () => HTMLImageElement | null, renderCallback: () => void, setDrawTypeCallback: (type: DrawType) => void, notifyUndo: () => void);
976
+ /**
977
+ * 处理滚轮缩放
978
+ */
979
+ handleWheel(e: WheelEvent): void;
980
+ /**
981
+ * 处理鼠标按下
982
+ */
983
+ handleMouseDown(e: MouseEvent): void;
984
+ /**
985
+ * 文本模式下的点击处理
986
+ */
987
+ private handleTextModeClick;
988
+ /**
989
+ * 矩形模式下的点击处理
990
+ */
991
+ private handleRectModeClick;
992
+ /**
993
+ * 多边形模式下的点击处理
994
+ */
995
+ private handlePolygonModeClick;
996
+ /**
997
+ * 无模式下的点击处理
998
+ */
999
+ private handleNoModeClick;
1000
+ /**
1001
+ * 检查多边形点是否有效(避免重复点)
1002
+ */
1003
+ private checkPolygonPointValid;
1004
+ /**
1005
+ * 处理鼠标移动
1006
+ */
1007
+ handleMouseMove(e: MouseEvent): void;
1008
+ /**
1009
+ * 处理标注移动或调整大小
1010
+ */
1011
+ private handleAnnotationMove;
1012
+ /**
1013
+ * 悬停点是否为锁定选中的标注(锁定态不显示 move 光标)
1014
+ */
1015
+ private isHoverLockedAnnotation;
1016
+ /**
1017
+ * 更新鼠标光标样式
1018
+ */
1019
+ private updateCursor;
1020
+ /**
1021
+ * 更新矩形模式光标
1022
+ */
1023
+ private updateRectModeCursor;
1024
+ /**
1025
+ * 更新多边形模式光标
1026
+ */
1027
+ private updatePolygonModeCursor;
1028
+ /**
1029
+ * 更新拖拽模式光标
1030
+ */
1031
+ private updateDragModeCursor;
1032
+ /**
1033
+ * 更新无模式光标
1034
+ */
1035
+ private updateNoModeCursor;
1036
+ /**
1037
+ * 处理鼠标释放
1038
+ */
1039
+ handleMouseUp(_e: MouseEvent): void;
1040
+ /**
1041
+ * 处理鼠标离开
1042
+ */
1043
+ handleMouseLeave(e: MouseEvent): void;
1044
+ /**
1045
+ * 处理鼠标双击
1046
+ */
1047
+ handleDoubleClick(e: MouseEvent): void;
1048
+ /**
1049
+ * 处理鼠标单击(用于多边形绘制;加点已收敛到 mousedown 通道,
1050
+ * 此处仅保留防误触发的守卫与 justDeselected 消费)
1051
+ */
1052
+ handleClick(_e: MouseEvent): void;
1053
+ /**
1054
+ * 处理键盘按下
1055
+ */
1056
+ handleKeyDown(e: KeyboardEvent): void;
1057
+ }
1058
+
1059
+ /**
1060
+ * 迷你事件发射器(零依赖)
1061
+ * 供 Drawer 对外提供 on/off 订阅能力
1062
+ */
1063
+
1064
+ declare class Emitter {
1065
+ private listeners;
1066
+ on<K extends DrawerEventName>(event: K, listener: DrawerListener<K>): void;
1067
+ off<K extends DrawerEventName>(event: K, listener: DrawerListener<K>): void;
1068
+ emit<K extends DrawerEventName>(event: K, payload: DrawerEventMap[K]): void;
1069
+ /** 清空所有监听器(destroy 时调用) */
1070
+ clear(): void;
1071
+ }
1072
+
1
1073
  /**
2
1074
  * Image Annotation Drawer
3
1075
  * 一个轻量级的图片标注库,基于 HTML5 Canvas
@@ -10,16 +1082,7 @@
10
1082
  * - 图片导出:支持导出标注后的图片
11
1083
  * - TypeScript 支持:完整的类型定义
12
1084
  */
13
- export type { Shape, Point, Rect, Polygon, TextAnnotation, Operate, DrawerOptions, DrawType, SelectedAnnotation, ActiveHandle, TextStyle, TextInputStyle, SelectionStyle, AnnotationStyle, LineStyle, VertexStyle, ColorConfig, ViewportSize, ViewportOffset, DrawerEventHandlers, TitleStyle, TitlePosition, DrawerEventMap, DrawerEventName, DrawerListener, ShapeChangePayload, ChangeNotify, } from './modules/types';
14
- export { isPointInRect, isPointInPolygon, getImageTypeFromUrl, base64ToFile, base64ToBlob, getZoomDelta, } from './modules/utils';
15
- export { ViewportManager } from './modules/viewport';
16
- export { AnnotationManager } from './modules/annotations';
17
- export { TextAnnotationManager } from './modules/shapes/text-manager';
18
- export { Renderer } from './modules/renderer';
19
- export { EventHandler } from './modules/events';
20
- export { Emitter } from './modules/emitter';
21
- import type { Rect, Polygon, TextAnnotation, Operate, DrawerOptions, DrawType, ColorConfig, LineStyle, VertexStyle, TextStyle, TextInputStyle, TitleStyle, TitlePosition } from './modules/types';
22
- import type { DrawerEventName, DrawerListener } from './modules/types';
1085
+
23
1086
  /**
24
1087
  * Drawer 类 - 图片标注库的主入口
25
1088
  *
@@ -32,7 +1095,7 @@ import type { DrawerEventName, DrawerListener } from './modules/types';
32
1095
  * drawer.drawImage('image.jpg');
33
1096
  * ```
34
1097
  */
35
- export declare class Drawer {
1098
+ declare class Drawer {
36
1099
  private canvas;
37
1100
  private ctx;
38
1101
  private container;
@@ -378,4 +1441,6 @@ export declare class Drawer {
378
1441
  */
379
1442
  destroy(): void;
380
1443
  }
381
- export default Drawer;
1444
+
1445
+ export { AnnotationManager, Drawer, Emitter, EventHandler, Renderer, TextAnnotationManager, ViewportManager, base64ToBlob, base64ToFile, Drawer as default, getImageTypeFromUrl, getZoomDelta, isPointInPolygon, isPointInRect };
1446
+ export type { ActiveHandle, AnnotationStyle, ChangeNotify, ColorConfig, DrawType, DrawerEventHandlers, DrawerEventMap, DrawerEventName, DrawerListener, DrawerOptions, LineStyle, Operate, Point, Polygon, Rect, SelectedAnnotation, SelectionStyle, Shape, ShapeChangePayload, TextAnnotation, TextInputStyle, TextStyle, TitlePosition, TitleStyle, VertexStyle, ViewportOffset, ViewportSize };