med-viewer-sdk 0.1.21 → 0.1.23
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/README.md +624 -346
- package/dist/med-viewer-sdk.d.ts +5 -2
- package/dist/med-viewer-sdk.mjs +358 -19
- package/dist/med-viewer-sdk.umd.js +2 -2
- package/package.json +2 -2
- package/dist/adapters/vue/MedViewer.d.ts +0 -17
- package/dist/adapters/vue/index.d.ts +0 -2
- package/dist/core/AnnoAnnotator.d.ts +0 -16
- package/dist/core/BaseAnnotator.d.ts +0 -33
- package/dist/core/ColorAdjustPlugin.d.ts +0 -46
- package/dist/core/Coords.d.ts +0 -6
- package/dist/core/Engine.d.ts +0 -104
- package/dist/core/Magnification.d.ts +0 -54
- package/dist/core/Scalebar.d.ts +0 -42
- package/dist/core/SelectionPlugin.d.ts +0 -106
- package/dist/core/Toolbar.d.ts +0 -57
- package/dist/i18n/i18n.d.ts +0 -6
package/README.md
CHANGED
|
@@ -1,429 +1,707 @@
|
|
|
1
1
|
# MedViewer SDK
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
##
|
|
6
|
-
|
|
7
|
-
-
|
|
8
|
-
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
- 与工具栏集成,方便访问和控制。
|
|
18
|
-
- **比例尺插件:**
|
|
19
|
-
- 在查看器上显示动态比例尺。
|
|
20
|
-
- 支持不同类型的比例尺(例如,显微镜、地图)。
|
|
21
|
-
- 可配置位置、颜色、字体和大小。
|
|
22
|
-
- 自动初始化并与查看器的缩放级别同步。
|
|
23
|
-
- **标注工具:**
|
|
24
|
-
- **Annotorious 集成 (AnnoAnnotator):**
|
|
25
|
-
- 集成 Annotorious 库以提供高级标注功能。
|
|
26
|
-
- 支持各种标注工具(例如,徒手绘制、多边形)。
|
|
27
|
-
- 提供启用/禁用、设置工具、获取/设置标注和清除标注的方法。
|
|
28
|
-
- 动态注入自定义标注样式。
|
|
29
|
-
- **Konva.js 集成 (KonvaAnnotator):**
|
|
30
|
-
- 利用 Konva.js 实现自定义绘图覆盖。
|
|
31
|
-
- 当前支持绘制矩形和可能的箭头(注释掉的同步逻辑表明可能计划了更复杂的交互)。
|
|
32
|
-
- 允许拖动形状。
|
|
33
|
-
- 无论缩放级别如何,都保持恒定的描边宽度 (`strokeScaleEnabled: false`)。
|
|
34
|
-
- 提供启用/禁用、设置工具、获取/设置标注和清除标注的方法。
|
|
35
|
-
- **颜色调整插件:**
|
|
36
|
-
- 启用对图像属性(如亮度、对比度、饱和度、伽马、锐化、边缘增强、伪彩色和反色)的实时调整。
|
|
37
|
-
- **利用 WebGL 着色器:** 直接在 GPU 上实现调整以提高性能。
|
|
38
|
-
- 尝试强制 OpenSeadragon 使用其 WebGL 渲染器以获得最佳功能。
|
|
39
|
-
- 提供自定义 WebGL 着色器用于高级颜色操作。
|
|
40
|
-
- **基于插件的架构:**
|
|
41
|
-
- SDK 采用模块化、基于插件的架构设计,以 `MedViewerEngine` 作为核心协调器。
|
|
42
|
-
- 易于扩展以添加新功能。
|
|
43
|
-
- 管理各种插件的生命周期(初始化、销毁)。
|
|
3
|
+
> 面向医学图像分析的高性能查看与标注 SDK,基于 OpenSeadragon 深度缩放引擎,提供插件化架构。
|
|
4
|
+
|
|
5
|
+
## 特性
|
|
6
|
+
|
|
7
|
+
- **深度缩放** — 基于 OpenSeadragon 的高性能瓦片渲染,支持超大病理切片无缝浏览
|
|
8
|
+
- **插件化架构** — 标注、截图、颜色调整、比例尺、放大镜、工具栏等插件按需启用
|
|
9
|
+
- **标注能力** — 集成 Annotorious,支持矩形/多边形/圆形/椭圆/线段/手绘 六种标注工具
|
|
10
|
+
- **颜色调整** — WebGL 加速的亮度/对比度/饱和度/伽马/色相/反色/灰度/怀旧 实时调节
|
|
11
|
+
- **区域截图** — 框选区域并导出为 Blob,支持旋转和像素坐标
|
|
12
|
+
- **工具栏** — 内置深色风格工具栏,支持自定义按钮、下拉面板、激活态
|
|
13
|
+
- **Vue 集成** — 提供 Vue 2/3 通用组件 `MedViewer`,自动管理生命周期
|
|
14
|
+
- **国际化** — 内置中文/英文,支持动态切换
|
|
15
|
+
|
|
16
|
+
---
|
|
44
17
|
|
|
45
18
|
## 安装
|
|
46
19
|
|
|
47
20
|
```bash
|
|
48
|
-
|
|
49
|
-
npm install openseadragon # 根据需要添加其他依赖
|
|
21
|
+
npm install med-viewer-sdk
|
|
50
22
|
# 或
|
|
51
|
-
|
|
23
|
+
pnpm add med-viewer-sdk
|
|
52
24
|
```
|
|
53
25
|
|
|
54
|
-
|
|
26
|
+
**Peer Dependencies**(需自行安装):
|
|
55
27
|
|
|
56
|
-
```
|
|
57
|
-
|
|
58
|
-
// 如果有任何 CSS,请导入
|
|
28
|
+
```bash
|
|
29
|
+
npm install openseadragon vue
|
|
59
30
|
```
|
|
60
31
|
|
|
61
|
-
|
|
32
|
+
---
|
|
62
33
|
|
|
63
|
-
|
|
34
|
+
## 快速开始
|
|
64
35
|
|
|
65
|
-
|
|
66
|
-
import { MedViewerEngine } from './core/Engine' // 以本地路径为例
|
|
67
|
-
import OpenSeadragon from 'openseadragon'
|
|
36
|
+
### 原生 HTML / UMD
|
|
68
37
|
|
|
69
|
-
|
|
38
|
+
```html
|
|
39
|
+
<!DOCTYPE html>
|
|
40
|
+
<html>
|
|
41
|
+
<head>
|
|
42
|
+
<link rel="stylesheet" href="https://unpkg.com/med-viewer-sdk/dist/style.css">
|
|
43
|
+
</head>
|
|
44
|
+
<body>
|
|
45
|
+
<div id="viewer" style="width:100vw;height:100vh;"></div>
|
|
70
46
|
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
}
|
|
82
|
-
// ... 其他 OSD 选项
|
|
83
|
-
},
|
|
84
|
-
plugins: {
|
|
85
|
-
toolbar: true, // 启用默认工具栏
|
|
86
|
-
selection: true, // 启用选择插件
|
|
87
|
-
scalebar: {
|
|
88
|
-
// 启用比例尺插件并带自定义选项
|
|
89
|
-
type: 'MICROSCOPY',
|
|
90
|
-
location: 'BOTTOM_LEFT',
|
|
91
|
-
color: 'rgb(255, 255, 255)',
|
|
92
|
-
fontColor: 'rgb(255, 255, 255)',
|
|
93
|
-
backgroundColor: 'rgba(0, 0, 0, 0.5)'
|
|
94
|
-
},
|
|
95
|
-
anno: true, // 启用 Annotorious
|
|
96
|
-
konva: true, // 启用 Konva.js 绘图
|
|
97
|
-
colorAdjust: {
|
|
98
|
-
// 启用颜色调整并带初始设置
|
|
99
|
-
initial: {
|
|
100
|
-
brightness: 1.2,
|
|
101
|
-
contrast: 0.8,
|
|
102
|
-
invert: true
|
|
47
|
+
<script src="https://unpkg.com/openseadragon"></script>
|
|
48
|
+
<script src="https://unpkg.com/med-viewer-sdk"></script>
|
|
49
|
+
<script>
|
|
50
|
+
const engine = new MedViewerSDK.MedViewerEngine({
|
|
51
|
+
osdOptions: {
|
|
52
|
+
id: 'viewer',
|
|
53
|
+
prefixUrl: 'https://openseadragon.github.io/openseadragon/images/',
|
|
54
|
+
tileSources: {
|
|
55
|
+
type: 'image',
|
|
56
|
+
url: './slide.dzi'
|
|
103
57
|
}
|
|
58
|
+
},
|
|
59
|
+
locale: 'zh-CN',
|
|
60
|
+
plugins: {
|
|
61
|
+
toolbar: true,
|
|
62
|
+
annotorious: {
|
|
63
|
+
onCreateAnnotation: (anno) => console.log('新建标注', anno)
|
|
64
|
+
},
|
|
65
|
+
selection: {
|
|
66
|
+
onSelection: (rect, blob) => {
|
|
67
|
+
if (blob) console.log('截图完成', rect, blob)
|
|
68
|
+
}
|
|
69
|
+
},
|
|
70
|
+
colorAdjust: true,
|
|
71
|
+
scalebar: { pixelsPerMeter: 100000 },
|
|
72
|
+
magnification: true
|
|
104
73
|
}
|
|
105
|
-
}
|
|
106
|
-
|
|
74
|
+
})
|
|
75
|
+
</script>
|
|
76
|
+
</body>
|
|
77
|
+
</html>
|
|
78
|
+
```
|
|
107
79
|
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
80
|
+
### ES Module
|
|
81
|
+
|
|
82
|
+
```ts
|
|
83
|
+
import { MedViewerEngine, ToolbarPosition } from 'med-viewer-sdk'
|
|
84
|
+
import 'med-viewer-sdk/dist/style.css'
|
|
85
|
+
|
|
86
|
+
const engine = new MedViewerEngine({
|
|
87
|
+
osdOptions: {
|
|
88
|
+
id: 'viewer',
|
|
89
|
+
tileSources: 'https://example.com/slide.dzi'
|
|
90
|
+
},
|
|
91
|
+
plugins: {
|
|
92
|
+
toolbar: { position: ToolbarPosition.BOTTOM_CENTER },
|
|
93
|
+
annotorious: true,
|
|
94
|
+
colorAdjust: true
|
|
115
95
|
}
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
96
|
+
})
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
### Vue 2 / Vue 3
|
|
100
|
+
|
|
101
|
+
```vue
|
|
102
|
+
<template>
|
|
103
|
+
<MedViewer :options="viewerOptions" @ready="onReady" />
|
|
104
|
+
</template>
|
|
105
|
+
|
|
106
|
+
<script>
|
|
107
|
+
import MedViewer from 'med-viewer-sdk/dist/vue'
|
|
108
|
+
import 'med-viewer-sdk/dist/style.css'
|
|
109
|
+
|
|
110
|
+
export default {
|
|
111
|
+
components: { MedViewer },
|
|
112
|
+
data() {
|
|
113
|
+
return {
|
|
114
|
+
viewerOptions: {
|
|
115
|
+
osdOptions: {
|
|
116
|
+
id: 'my-viewer',
|
|
117
|
+
tileSources: '/slide.dzi'
|
|
118
|
+
},
|
|
119
|
+
locale: 'zh-CN',
|
|
120
|
+
plugins: {
|
|
121
|
+
toolbar: true,
|
|
122
|
+
annotorious: true,
|
|
123
|
+
colorAdjust: true,
|
|
124
|
+
selection: true,
|
|
125
|
+
scalebar: { pixelsPerMeter: 100000 },
|
|
126
|
+
magnification: true
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
},
|
|
131
|
+
methods: {
|
|
132
|
+
onReady(engine) {
|
|
133
|
+
// engine 即为 MedViewerEngine 实例
|
|
134
|
+
console.log('引擎就绪', engine)
|
|
135
|
+
}
|
|
119
136
|
}
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
137
|
+
}
|
|
138
|
+
</script>
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
> Vue 组件在 `onMounted` 时创建引擎,`onBeforeUnmount` 时自动销毁,无需手动管理。
|
|
142
|
+
|
|
143
|
+
---
|
|
144
|
+
|
|
145
|
+
## 引擎配置
|
|
146
|
+
|
|
147
|
+
### MedEngineOptions
|
|
148
|
+
|
|
149
|
+
```ts
|
|
150
|
+
interface MedEngineOptions {
|
|
151
|
+
osdOptions: OpenSeadragon.Options // 必填,OpenSeadragon 配置
|
|
152
|
+
locale?: 'zh-CN' | 'en-US' // 国际化语言,默认 'zh-CN'
|
|
153
|
+
plugins?: {
|
|
154
|
+
annotorious?: boolean | AnnotoriousOptions
|
|
155
|
+
toolbar?: boolean | ToolbarOptions
|
|
156
|
+
colorAdjust?: boolean | ColorAdjustOptions
|
|
157
|
+
selection?: boolean | SelectionOptions
|
|
158
|
+
scalebar?: boolean | ScalebarOptions
|
|
159
|
+
magnification?: boolean | MagnificationOptions
|
|
123
160
|
}
|
|
124
|
-
|
|
125
|
-
|
|
161
|
+
}
|
|
162
|
+
```
|
|
163
|
+
|
|
164
|
+
> 传 `true` 等同于传 `{}`(使用默认配置)。
|
|
165
|
+
|
|
166
|
+
### Engine 公开属性
|
|
167
|
+
|
|
168
|
+
创建后可通过 `engine.xxx` 访问各插件实例:
|
|
169
|
+
|
|
170
|
+
| 属性 | 类型 | 说明 |
|
|
171
|
+
|---|---|---|
|
|
172
|
+
| `engine.viewer` | `OpenSeadragon.Viewer` | 底层 OSD 查看器 |
|
|
173
|
+
| `engine.anno` | `AnnoAnnotator \| null` | 标注插件 |
|
|
174
|
+
| `engine.toolbar` | `MedToolbar \| null` | 工具栏插件 |
|
|
175
|
+
| `engine.colorAdjust` | `ColorAdjustPlugin \| null` | 颜色调整插件 |
|
|
176
|
+
| `engine.selection` | `SelectionPlugin \| null` | 截图插件 |
|
|
177
|
+
| `engine.scalebar` | `ScalebarPlugin \| null` | 比例尺插件 |
|
|
178
|
+
| `engine.magnification` | `MagnificationPlugin \| null` | 放大镜插件 |
|
|
179
|
+
|
|
180
|
+
### Engine 事件
|
|
181
|
+
|
|
182
|
+
```ts
|
|
183
|
+
engine.addHandler('ready', () => {
|
|
184
|
+
console.log('图像首次加载完成')
|
|
185
|
+
})
|
|
186
|
+
```
|
|
187
|
+
|
|
188
|
+
---
|
|
189
|
+
|
|
190
|
+
## 插件详解
|
|
191
|
+
|
|
192
|
+
### 1. 标注插件 (Annotorious)
|
|
193
|
+
|
|
194
|
+
提供六种标注工具:矩形、多边形、圆形、椭圆、线段、手绘。
|
|
195
|
+
|
|
196
|
+
#### 配置
|
|
197
|
+
|
|
198
|
+
```ts
|
|
199
|
+
interface AnnotoriousOptions {
|
|
200
|
+
onCreateAnnotation?: (annotation: any) => void
|
|
201
|
+
onUpdateAnnotation?: (annotation: any, previous: any) => void
|
|
202
|
+
onDeleteAnnotation?: (annotation: any) => void
|
|
203
|
+
onCancelAnnotation?: (annotation: any) => void
|
|
204
|
+
locale?: string // 默认跟随引擎 locale
|
|
205
|
+
options?: {
|
|
206
|
+
formatter?: any // 标注格式化器
|
|
126
207
|
}
|
|
208
|
+
}
|
|
209
|
+
```
|
|
210
|
+
|
|
211
|
+
#### API
|
|
212
|
+
|
|
213
|
+
```ts
|
|
214
|
+
const anno = engine.anno
|
|
215
|
+
|
|
216
|
+
// 设置工具
|
|
217
|
+
anno.setTool('rect', '#ff0000') // 启用矩形标注,颜色红色
|
|
218
|
+
anno.setTool('polygon', '#00ff00') // 启用多边形标注
|
|
219
|
+
anno.setTool(null) // 关闭标注模式
|
|
220
|
+
|
|
221
|
+
// 启用/禁用
|
|
222
|
+
anno.setEnabled(true)
|
|
223
|
+
anno.setEnabled(false)
|
|
224
|
+
|
|
225
|
+
// 数据操作
|
|
226
|
+
anno.getAnnotations() // 获取所有标注
|
|
227
|
+
anno.setAnnotations([...]) // 加载标注数据
|
|
228
|
+
anno.clear() // 清除所有标注
|
|
229
|
+
|
|
230
|
+
// 事件监听
|
|
231
|
+
anno.on('modeChange', (state) => {
|
|
232
|
+
console.log(state.enabled, state.tool)
|
|
233
|
+
})
|
|
234
|
+
```
|
|
235
|
+
|
|
236
|
+
#### 事件
|
|
237
|
+
|
|
238
|
+
| 事件名 | 参数 | 说明 |
|
|
239
|
+
|---|---|---|
|
|
240
|
+
| `modeChange` | `{ enabled: boolean, tool: string \| null }` | 标注模式变化 |
|
|
241
|
+
|
|
242
|
+
---
|
|
127
243
|
|
|
128
|
-
|
|
129
|
-
|
|
244
|
+
### 2. 截图插件 (Selection)
|
|
245
|
+
|
|
246
|
+
框选图像区域并导出为 Blob,支持旋转和像素坐标。
|
|
247
|
+
|
|
248
|
+
#### 配置
|
|
249
|
+
|
|
250
|
+
```ts
|
|
251
|
+
interface SelectionOptions {
|
|
252
|
+
showSelectionControl?: boolean // 是否显示控件按钮,默认 true
|
|
253
|
+
showConfirmDenyButtons?: boolean // 是否显示确认/取消按钮,默认 true
|
|
254
|
+
returnPixelCoordinates?: boolean // 返回像素坐标,默认 true
|
|
255
|
+
keyboardShortcut?: string // 快捷键,默认 'c'
|
|
256
|
+
allowRotation?: boolean // 允许旋转选区,默认 true
|
|
257
|
+
restrictToImage?: boolean // 限制在图像内,默认 false
|
|
258
|
+
onSelection?: (rect: Rect, blob: Blob \| null) => void // 确认选区回调
|
|
259
|
+
onSelectionCanceled?: () => void // 取消选区回调
|
|
130
260
|
}
|
|
131
261
|
```
|
|
132
262
|
|
|
133
|
-
|
|
263
|
+
#### API
|
|
134
264
|
|
|
135
|
-
|
|
265
|
+
```ts
|
|
266
|
+
const sel = engine.selection
|
|
136
267
|
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
268
|
+
sel.enable() // 启用选择模式
|
|
269
|
+
sel.disable() // 禁用选择模式
|
|
270
|
+
sel.toggleState() // 切换选择模式
|
|
271
|
+
sel.isEnabled() // 查询是否启用
|
|
272
|
+
sel.getSelection() // 获取当前选区
|
|
273
|
+
sel.clearSelection() // 清除选区
|
|
140
274
|
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
// 保留原始的 dropdownContent 或定义新的
|
|
147
|
-
},
|
|
148
|
-
{
|
|
149
|
-
id: 'customTool', // 添加新的自定义按钮
|
|
150
|
-
label: '新工具',
|
|
151
|
-
onClick: (engine) => {
|
|
152
|
-
alert('自定义工具已点击!')
|
|
153
|
-
// 在此处实现自定义逻辑
|
|
154
|
-
}
|
|
155
|
-
}
|
|
156
|
-
]
|
|
275
|
+
// 事件监听
|
|
276
|
+
sel.on('selectionEnabled', (enabled: boolean) => {
|
|
277
|
+
console.log('选择模式', enabled ? '开启' : '关闭')
|
|
278
|
+
})
|
|
279
|
+
```
|
|
157
280
|
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
281
|
+
#### 事件
|
|
282
|
+
|
|
283
|
+
| 事件名 | 参数 | 说明 |
|
|
284
|
+
|---|---|---|
|
|
285
|
+
| `selectionEnabled` | `boolean` | 选择模式启用/禁用 |
|
|
286
|
+
| `selectionStateToggled` | `boolean` | 选择模式切换 |
|
|
287
|
+
|
|
288
|
+
---
|
|
289
|
+
|
|
290
|
+
### 3. 颜色调整插件 (ColorAdjust)
|
|
291
|
+
|
|
292
|
+
基于 WebGL 着色器的实时图像处理,内置防抖优化。
|
|
293
|
+
|
|
294
|
+
#### 配置
|
|
295
|
+
|
|
296
|
+
```ts
|
|
297
|
+
interface ColorAdjustOptions {
|
|
298
|
+
adjustments?: ColorAdjustments // 初始调整值
|
|
299
|
+
debounceMs?: number // 防抖延迟,默认 60
|
|
300
|
+
loadMode?: 'async' | 'sync' // 加载模式,默认 'async'
|
|
301
|
+
onAdjustmentsChange?: (adj: ColorAdjustments) => void
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
interface ColorAdjustments {
|
|
305
|
+
brightness?: number // 亮度 0~2,默认 1
|
|
306
|
+
contrast?: number // 对比度 0~2,默认 1
|
|
307
|
+
saturation?: number // 饱和度 0~3,默认 1
|
|
308
|
+
gamma?: number // 伽马 0.1~3,默认 1
|
|
309
|
+
hue?: number // 色相 0~360,默认 0
|
|
310
|
+
invert?: boolean // 反色
|
|
311
|
+
sepia?: boolean // 怀旧
|
|
312
|
+
greyscale?: boolean // 灰度
|
|
313
|
+
}
|
|
314
|
+
```
|
|
315
|
+
|
|
316
|
+
#### API
|
|
317
|
+
|
|
318
|
+
```ts
|
|
319
|
+
const ca = engine.colorAdjust
|
|
320
|
+
|
|
321
|
+
ca.setAdjustments({ brightness: 1.5, contrast: 1.2 })
|
|
322
|
+
ca.setAdjustments({ greyscale: true })
|
|
323
|
+
ca.adjustments // 获取当前调整值
|
|
324
|
+
ca.reset() // 重置为默认值
|
|
325
|
+
```
|
|
326
|
+
|
|
327
|
+
---
|
|
328
|
+
|
|
329
|
+
### 4. 比例尺插件 (Scalebar)
|
|
330
|
+
|
|
331
|
+
在图像上叠加比例尺标注。
|
|
332
|
+
|
|
333
|
+
#### 配置
|
|
334
|
+
|
|
335
|
+
```ts
|
|
336
|
+
interface ScalebarOptions {
|
|
337
|
+
type?: ScalebarType // NONE=0, MICROSCOPY=1, MAP=2
|
|
338
|
+
pixelsPerMeter?: number // 每米像素数
|
|
339
|
+
location?: ScalebarLocation // NONE=0, TOP_LEFT=1, TOP_RIGHT=2, BOTTOM_RIGHT=3, BOTTOM_LEFT=4
|
|
340
|
+
color?: string
|
|
341
|
+
fontColor?: string
|
|
342
|
+
backgroundColor?: string
|
|
343
|
+
fontSize?: string
|
|
344
|
+
barThickness?: number
|
|
345
|
+
minWidth?: string
|
|
346
|
+
}
|
|
347
|
+
```
|
|
348
|
+
|
|
349
|
+
#### 示例
|
|
350
|
+
|
|
351
|
+
```ts
|
|
352
|
+
plugins: {
|
|
353
|
+
scalebar: {
|
|
354
|
+
type: ScalebarType.MICROSCOPY,
|
|
355
|
+
pixelsPerMeter: 100000,
|
|
356
|
+
location: ScalebarLocation.BOTTOM_LEFT
|
|
166
357
|
}
|
|
167
|
-
}
|
|
358
|
+
}
|
|
168
359
|
```
|
|
169
360
|
|
|
170
|
-
|
|
361
|
+
---
|
|
171
362
|
|
|
172
|
-
|
|
363
|
+
### 5. 放大镜插件 (Magnification)
|
|
173
364
|
|
|
174
|
-
|
|
365
|
+
显示当前放大倍率并提供快捷缩放按钮。
|
|
175
366
|
|
|
176
|
-
|
|
367
|
+
#### 配置
|
|
177
368
|
|
|
178
|
-
```
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
369
|
+
```ts
|
|
370
|
+
interface MagnificationOptions {
|
|
371
|
+
type?: MagnificationType // 'LD' 或 'OSD',默认 'OSD'
|
|
372
|
+
position?: MagnificationPosition // 位置,默认 'MIDDLE_LEFT'
|
|
373
|
+
offsetX?: number
|
|
374
|
+
offsetY?: number
|
|
375
|
+
pixelsPerMeter?: number // LD 模式需要
|
|
376
|
+
}
|
|
377
|
+
```
|
|
378
|
+
|
|
379
|
+
#### API
|
|
380
|
+
|
|
381
|
+
```ts
|
|
382
|
+
const mag = engine.magnification
|
|
383
|
+
mag.getMagnification() // 获取当前倍率
|
|
384
|
+
mag.show() // 显示
|
|
385
|
+
mag.hide() // 隐藏
|
|
386
|
+
mag.toggle() // 切换可见性
|
|
387
|
+
mag.refresh() // 刷新显示
|
|
182
388
|
```
|
|
183
389
|
|
|
184
|
-
|
|
390
|
+
---
|
|
185
391
|
|
|
186
|
-
|
|
392
|
+
## 工具栏
|
|
187
393
|
|
|
188
|
-
|
|
394
|
+
### 内置工具栏
|
|
189
395
|
|
|
190
|
-
```
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
396
|
+
```ts
|
|
397
|
+
plugins: {
|
|
398
|
+
toolbar: true // 使用默认工具栏(重置、标注、颜色调整、截图)
|
|
399
|
+
}
|
|
400
|
+
```
|
|
194
401
|
|
|
195
|
-
|
|
196
|
-
import { ref, onMounted, onBeforeUnmount } from 'vue'
|
|
197
|
-
import { MedViewerEngine } from 'med-viewer-sdk'
|
|
198
|
-
import OpenSeadragon from 'openseadragon' // 如果需要 OpenSeadragon 类型
|
|
402
|
+
### 自定义位置
|
|
199
403
|
|
|
200
|
-
|
|
201
|
-
|
|
404
|
+
```ts
|
|
405
|
+
import { ToolbarPosition } from 'med-viewer-sdk'
|
|
202
406
|
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
407
|
+
plugins: {
|
|
408
|
+
toolbar: {
|
|
409
|
+
position: ToolbarPosition.BOTTOM_CENTER
|
|
410
|
+
}
|
|
411
|
+
}
|
|
412
|
+
```
|
|
413
|
+
|
|
414
|
+
可选位置:`TOP_LEFT` `TOP_CENTER` `TOP_RIGHT` `BOTTOM_LEFT` `BOTTOM_CENTER` `BOTTOM_RIGHT` `MIDDLE_LEFT` `MIDDLE_RIGHT`
|
|
415
|
+
|
|
416
|
+
### 自定义按钮
|
|
417
|
+
|
|
418
|
+
通过 `buttons` 配置完全控制工具栏按钮,ID 匹配内置按钮时会合并默认配置:
|
|
419
|
+
|
|
420
|
+
```ts
|
|
421
|
+
plugins: {
|
|
422
|
+
toolbar: {
|
|
423
|
+
position: ToolbarPosition.BOTTOM_RIGHT,
|
|
424
|
+
buttons: [
|
|
425
|
+
{
|
|
426
|
+
id: 'reset', // 匹配内置 reset 按钮,覆盖默认配置
|
|
427
|
+
icon: myResetIcon,
|
|
428
|
+
label: '回到原点',
|
|
429
|
+
onClick: (engine, hide) => {
|
|
430
|
+
engine.viewer.viewport.goHome()
|
|
431
|
+
hide()
|
|
213
432
|
}
|
|
214
433
|
},
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
}
|
|
434
|
+
{
|
|
435
|
+
id: 'anno' // 匹配内置 anno 按钮,使用默认 dropdownContent
|
|
436
|
+
},
|
|
437
|
+
{
|
|
438
|
+
id: 'colorAdjust' // 匹配内置 colorAdjust 按钮
|
|
439
|
+
},
|
|
440
|
+
{
|
|
441
|
+
id: 'selection' // 匹配内置 selection 按钮
|
|
442
|
+
},
|
|
443
|
+
// 添加自定义按钮
|
|
444
|
+
{
|
|
445
|
+
id: 'myTool',
|
|
446
|
+
icon: myToolIcon,
|
|
447
|
+
label: '我的工具',
|
|
448
|
+
onClick: (engine, hide) => {
|
|
449
|
+
console.log('自定义工具被点击')
|
|
450
|
+
hide()
|
|
233
451
|
}
|
|
234
452
|
}
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
// 可以访问插件并进行操作
|
|
238
|
-
if (medViewer.selection) {
|
|
239
|
-
medViewer.selection.enable()
|
|
240
|
-
medViewer.selection.setSelectionMode('RECTANGLE')
|
|
241
|
-
}
|
|
453
|
+
]
|
|
242
454
|
}
|
|
243
|
-
}
|
|
455
|
+
}
|
|
456
|
+
```
|
|
244
457
|
|
|
245
|
-
|
|
246
|
-
if (medViewer) {
|
|
247
|
-
medViewer.destroy()
|
|
248
|
-
medViewer = null
|
|
249
|
-
}
|
|
250
|
-
})
|
|
251
|
-
</script>
|
|
458
|
+
### 按钮激活态
|
|
252
459
|
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
460
|
+
按钮激活时显示与 hover 相同的高亮效果(绿色背景),支持三种方式:
|
|
461
|
+
|
|
462
|
+
#### 方式一:命令式 — `setButtonActive`
|
|
463
|
+
|
|
464
|
+
最灵活,外部代码随时调用:
|
|
465
|
+
|
|
466
|
+
```ts
|
|
467
|
+
// 激活
|
|
468
|
+
engine.toolbar.setButtonActive('myTool', true)
|
|
469
|
+
// 取消激活
|
|
470
|
+
engine.toolbar.setButtonActive('myTool', false)
|
|
260
471
|
```
|
|
261
472
|
|
|
262
|
-
|
|
473
|
+
#### 方式二:声明式 — `activeEvent`
|
|
263
474
|
|
|
264
|
-
|
|
265
|
-
<template>
|
|
266
|
-
<div ref="viewerContainer" class="med-viewer-container"></div>
|
|
267
|
-
</template>
|
|
475
|
+
在按钮配置中声明事件源,Toolbar 自动绑定监听,事件触发时自动更新激活态:
|
|
268
476
|
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
477
|
+
```ts
|
|
478
|
+
{
|
|
479
|
+
id: 'myTool',
|
|
480
|
+
icon: myToolIcon,
|
|
481
|
+
label: '我的工具',
|
|
482
|
+
onClick: (engine, hide) => { /* ... */ },
|
|
483
|
+
activeEvent: {
|
|
484
|
+
emitter: engine.anno, // 任何有 .on() 方法的对象
|
|
485
|
+
event: 'modeChange', // 监听的事件名
|
|
486
|
+
mapToActive: (data) => data.enabled // 将事件数据映射为 boolean
|
|
487
|
+
}
|
|
488
|
+
}
|
|
489
|
+
```
|
|
273
490
|
|
|
274
|
-
|
|
275
|
-
data() {
|
|
276
|
-
return {
|
|
277
|
-
medViewer: null as MedViewerEngine | null
|
|
278
|
-
}
|
|
279
|
-
},
|
|
280
|
-
mounted() {
|
|
281
|
-
if (this.$refs.viewerContainer) {
|
|
282
|
-
this.medViewer = new MedViewerEngine({
|
|
283
|
-
element: this.$refs.viewerContainer as HTMLElement,
|
|
284
|
-
viewerOptions: {
|
|
285
|
-
id: 'openseadragon-viewer',
|
|
286
|
-
prefixUrl: 'https://openseadragon.github.io/openseadragon/images/',
|
|
287
|
-
tileSources: {
|
|
288
|
-
type: 'image',
|
|
289
|
-
url: 'https://openseadragon.github.io/example-images/duomo/duomo.dzi'
|
|
290
|
-
}
|
|
291
|
-
},
|
|
292
|
-
plugins: {
|
|
293
|
-
toolbar: true,
|
|
294
|
-
selection: true,
|
|
295
|
-
scalebar: {
|
|
296
|
-
type: 'MICROSCOPY',
|
|
297
|
-
location: 'BOTTOM_LEFT',
|
|
298
|
-
color: 'rgb(255, 255, 255)',
|
|
299
|
-
fontColor: 'rgb(255, 255, 255)',
|
|
300
|
-
backgroundColor: 'rgba(0, 0, 0, 0.5)'
|
|
301
|
-
},
|
|
302
|
-
anno: true,
|
|
303
|
-
konva: true,
|
|
304
|
-
colorAdjust: {
|
|
305
|
-
initial: {
|
|
306
|
-
brightness: 1.2,
|
|
307
|
-
contrast: 0.8,
|
|
308
|
-
invert: true
|
|
309
|
-
}
|
|
310
|
-
}
|
|
311
|
-
}
|
|
312
|
-
})
|
|
491
|
+
#### 方式三:下拉框联动 — `activeOnDropdown`
|
|
313
492
|
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
493
|
+
下拉框按钮设为 `true`,下拉框打开自动激活、关闭自动取消:
|
|
494
|
+
|
|
495
|
+
```ts
|
|
496
|
+
{
|
|
497
|
+
id: 'myPanel',
|
|
498
|
+
icon: myPanelIcon,
|
|
499
|
+
label: '我的面板',
|
|
500
|
+
dropdownContent: (engine, hide) => {
|
|
501
|
+
const el = document.createElement('div')
|
|
502
|
+
el.textContent = '面板内容'
|
|
503
|
+
return el
|
|
319
504
|
},
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
505
|
+
activeOnDropdown: true
|
|
506
|
+
}
|
|
507
|
+
```
|
|
508
|
+
|
|
509
|
+
#### 内置按钮激活机制
|
|
510
|
+
|
|
511
|
+
| 按钮 | 激活来源 | 机制 |
|
|
512
|
+
|---|---|---|
|
|
513
|
+
| `anno` | `engine.anno` 的 `modeChange` 事件 | 自动监听 |
|
|
514
|
+
| `selection` | `engine.selection` 的 `selectionEnabled` 事件 | 自动监听 |
|
|
515
|
+
| `colorAdjust` | 下拉框开闭 | `activeOnDropdown: true` |
|
|
516
|
+
| `reset` | 无 | 不激活 |
|
|
517
|
+
|
|
518
|
+
### 自定义下拉面板
|
|
519
|
+
|
|
520
|
+
任何按钮都可以配置 `dropdownContent`,返回一个 DOM 元素:
|
|
521
|
+
|
|
522
|
+
```ts
|
|
523
|
+
{
|
|
524
|
+
id: 'filters',
|
|
525
|
+
icon: filterIcon,
|
|
526
|
+
label: '滤镜',
|
|
527
|
+
activeOnDropdown: true,
|
|
528
|
+
dropdownContent: (engine, hide) => {
|
|
529
|
+
const container = document.createElement('div')
|
|
530
|
+
container.className = 'med-toolbar-dropdown-inner'
|
|
531
|
+
|
|
532
|
+
const btn = document.createElement('button')
|
|
533
|
+
btn.className = 'med-tool-item'
|
|
534
|
+
btn.textContent = '模糊'
|
|
535
|
+
btn.onclick = () => {
|
|
536
|
+
// 应用滤镜...
|
|
537
|
+
hide() // 关闭下拉面板
|
|
325
538
|
}
|
|
326
|
-
}
|
|
327
|
-
})
|
|
328
|
-
</script>
|
|
329
539
|
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
height: 80vh; /* 根据需要调整高度 */
|
|
334
|
-
background-color: #000;
|
|
540
|
+
container.appendChild(btn)
|
|
541
|
+
return container
|
|
542
|
+
}
|
|
335
543
|
}
|
|
336
|
-
</style>
|
|
337
544
|
```
|
|
338
545
|
|
|
339
|
-
|
|
546
|
+
> `hide()` 用于关闭当前下拉面板,务必在操作完成后调用。
|
|
340
547
|
|
|
341
|
-
|
|
342
|
-
- **全局样式**:确保 `med-viewer-sdk` 的容器元素有足够的宽高,以便查看器能够正确渲染。
|
|
343
|
-
- **响应式数据**:`MedViewerEngine` 实例本身不是响应式的。如果您需要其内部状态在 Vue 中响应式,您可能需要手动将其属性或方法包装在 Vue 的响应式系统中。
|
|
548
|
+
---
|
|
344
549
|
|
|
345
|
-
##
|
|
550
|
+
## Engine 常用 API
|
|
346
551
|
|
|
347
|
-
|
|
552
|
+
### 图像导航
|
|
348
553
|
|
|
349
|
-
|
|
554
|
+
```ts
|
|
555
|
+
engine.viewer.viewport.goHome() // 回到初始视图
|
|
556
|
+
engine.viewer.viewport.zoomTo(2) // 缩放到指定级别
|
|
557
|
+
engine.viewer.viewport.panTo(new OpenSeadragon.Point(0.5, 0.5)) // 平移到指定位置
|
|
558
|
+
```
|
|
350
559
|
|
|
351
|
-
|
|
560
|
+
### 跳转到指定位置
|
|
352
561
|
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
npm link med-viewer-sdk
|
|
360
|
-
```
|
|
361
|
-
现在,您就可以像从 npm 安装一样导入 `med-viewer-sdk` 了。
|
|
562
|
+
```ts
|
|
563
|
+
// 以指定倍率跳转到图像坐标 (x, y)
|
|
564
|
+
await engine.goToPosition({ x: 1000, y: 2000 }, true, 20)
|
|
565
|
+
// 参数: target坐标, 是否动画, 目标倍率(mag)
|
|
566
|
+
// mag=null 时仅平移,不缩放
|
|
567
|
+
```
|
|
362
568
|
|
|
363
|
-
###
|
|
569
|
+
### AI 标注框
|
|
364
570
|
|
|
365
|
-
|
|
571
|
+
```ts
|
|
572
|
+
// 加载 AI 检测框
|
|
573
|
+
engine.loadAIMarks([
|
|
574
|
+
{
|
|
575
|
+
x: 100, y: 200, width: 50, height: 50,
|
|
576
|
+
style: { border: '2px solid red' },
|
|
577
|
+
labels: [
|
|
578
|
+
{ label: '类型', value: '肿瘤', style: { color: '#333', fontSize: 12 } }
|
|
579
|
+
]
|
|
580
|
+
}
|
|
581
|
+
], true) // true = 显示标签
|
|
366
582
|
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
583
|
+
// 更新
|
|
584
|
+
engine.updateAIMarks(newBoxes, showLabel)
|
|
585
|
+
|
|
586
|
+
// 清除
|
|
587
|
+
engine.clearAIMarks()
|
|
588
|
+
```
|
|
589
|
+
|
|
590
|
+
### 选区框
|
|
591
|
+
|
|
592
|
+
```ts
|
|
593
|
+
engine.loadSelectionBox([
|
|
594
|
+
{
|
|
595
|
+
x: 100, y: 200, width: 50, height: 50,
|
|
596
|
+
style: { border: '2px solid #4CAF50' }
|
|
372
597
|
}
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
###
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
598
|
+
])
|
|
599
|
+
```
|
|
600
|
+
|
|
601
|
+
### 语言切换
|
|
602
|
+
|
|
603
|
+
```ts
|
|
604
|
+
engine.setLocale('en-US') // 动态切换为英文
|
|
605
|
+
engine.setLocale('zh-CN') // 切换为中文
|
|
606
|
+
```
|
|
607
|
+
|
|
608
|
+
### 销毁
|
|
609
|
+
|
|
610
|
+
```ts
|
|
611
|
+
engine.destroy() // 销毁所有插件和 viewer
|
|
612
|
+
```
|
|
613
|
+
|
|
614
|
+
---
|
|
615
|
+
|
|
616
|
+
## 国际化
|
|
617
|
+
|
|
618
|
+
内置中文 (`zh-CN`) 和英文 (`en-US`) 两种语言:
|
|
619
|
+
|
|
620
|
+
| 键 | 中文 | 英文 |
|
|
621
|
+
|---|---|---|
|
|
622
|
+
| toolbar.annoColor | 标注颜色 | Annotation Color |
|
|
623
|
+
| toolbar.annoShape | 标注形状 | Annotation Shape |
|
|
624
|
+
| toolbar.rect | 矩形 | Rectangle |
|
|
625
|
+
| toolbar.polygon | 多边形 | Polygon |
|
|
626
|
+
| toolbar.circle | 圆形 | Circle |
|
|
627
|
+
| toolbar.ellipse | 椭圆 | Ellipse |
|
|
628
|
+
| toolbar.line | 线段 | Line |
|
|
629
|
+
| toolbar.freehand | 手绘 | Freehand |
|
|
630
|
+
| toolbar.reset | 重置 | Reset |
|
|
631
|
+
| toolbar.screenshot | 截图 | Screenshot |
|
|
632
|
+
| toolbar.brightness | 亮度 | Brightness |
|
|
633
|
+
| toolbar.contrast | 对比度 | Contrast |
|
|
634
|
+
| toolbar.saturation | 饱和度 | Saturation |
|
|
635
|
+
| toolbar.gamma | 伽马 | Gamma |
|
|
636
|
+
| toolbar.hue | 色相 | Hue |
|
|
637
|
+
| toolbar.invert | 反色 | Invert |
|
|
638
|
+
| toolbar.sepia | 怀旧 | Sepia |
|
|
639
|
+
| toolbar.greyscale | 灰度 | Greyscale |
|
|
640
|
+
|
|
641
|
+
---
|
|
642
|
+
|
|
643
|
+
## ToolbarButton 接口
|
|
644
|
+
|
|
645
|
+
```ts
|
|
646
|
+
interface ToolbarButton {
|
|
647
|
+
id: string // 按钮唯一标识
|
|
648
|
+
label?: string // 提示文字
|
|
649
|
+
icon?: string // 图标 URL
|
|
650
|
+
dropdownContent?: (engine, hide) => HTMLElement // 下拉面板内容
|
|
651
|
+
onClick?: (engine, hide) => void // 点击回调
|
|
652
|
+
activeEvent?: { // 声明式激活状态
|
|
653
|
+
emitter: any // 事件发射器(需有 .on())
|
|
654
|
+
event: string // 事件名
|
|
655
|
+
mapToActive: (data: any) => boolean // 事件数据映射
|
|
656
|
+
}
|
|
657
|
+
activeOnDropdown?: boolean // 下拉框开闭时自动激活
|
|
658
|
+
}
|
|
659
|
+
```
|
|
660
|
+
|
|
661
|
+
---
|
|
662
|
+
|
|
663
|
+
## 构建与开发
|
|
664
|
+
|
|
665
|
+
```bash
|
|
666
|
+
# 安装依赖
|
|
667
|
+
npm install
|
|
668
|
+
|
|
669
|
+
# 构建(生成 dist/ 目录)
|
|
670
|
+
npm run build
|
|
671
|
+
|
|
672
|
+
# 输出格式
|
|
673
|
+
# dist/med-viewer-sdk.umd.js — UMD 格式(<script> 标签引入)
|
|
674
|
+
# dist/med-viewer-sdk.mjs — ESM 格式(import 引入)
|
|
675
|
+
# dist/med-viewer-sdk.d.ts — TypeScript 类型声明
|
|
676
|
+
# dist/style.css — 样式文件
|
|
677
|
+
```
|
|
678
|
+
|
|
679
|
+
---
|
|
680
|
+
|
|
681
|
+
## 浏览器兼容性
|
|
414
682
|
|
|
415
|
-
|
|
683
|
+
| 浏览器 | 版本 |
|
|
684
|
+
|---|---|
|
|
685
|
+
| Chrome | 80+ |
|
|
686
|
+
| Firefox | 80+ |
|
|
687
|
+
| Safari | 14+ |
|
|
688
|
+
| Edge | 80+ |
|
|
416
689
|
|
|
417
|
-
|
|
690
|
+
> 需要 WebGL 支持以使用颜色调整功能。
|
|
418
691
|
|
|
419
|
-
|
|
692
|
+
---
|
|
420
693
|
|
|
421
|
-
|
|
694
|
+
## 依赖说明
|
|
422
695
|
|
|
423
|
-
|
|
696
|
+
| 依赖 | 用途 |
|
|
697
|
+
|---|---|
|
|
698
|
+
| [openseadragon](https://openseadragon.github.io/) | 深度缩放图像渲染引擎 |
|
|
699
|
+
| [annotorious-openseadragon-ld](https://www.npmjs.com/package/annotorious-openseadragon-ld) | Annotorious 标注库(定制版) |
|
|
700
|
+
| [openseadragon-filtering](https://www.npmjs.com/package/openseadragon-filtering) | OSD 瓦片滤镜处理器 |
|
|
701
|
+
| [svg-path-properties](https://www.npmjs.com/package/svg-path-properties) | SVG 路径计算(标注测量) |
|
|
424
702
|
|
|
425
|
-
|
|
703
|
+
---
|
|
426
704
|
|
|
427
|
-
##
|
|
705
|
+
## License
|
|
428
706
|
|
|
429
|
-
|
|
707
|
+
MIT
|