@sketch-ruler/canvas 3.0.0-beta.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.
- package/AGENTS.md +155 -0
- package/README.md +224 -0
- package/lib/index.cjs +1 -0
- package/lib/index.d.ts +13 -0
- package/lib/index.d.ts.map +1 -0
- package/lib/index.iife.js +1 -0
- package/lib/index.js +348 -0
- package/lib/index.umd.cjs +1 -0
- package/lib/input/index.d.ts +5 -0
- package/lib/input/index.d.ts.map +1 -0
- package/lib/input/input-manager.d.ts +74 -0
- package/lib/input/input-manager.d.ts.map +1 -0
- package/lib/input/keyboard-adapter.d.ts +19 -0
- package/lib/input/keyboard-adapter.d.ts.map +1 -0
- package/lib/input/mouse-adapter.d.ts +32 -0
- package/lib/input/mouse-adapter.d.ts.map +1 -0
- package/lib/input/wheel-normalizer.d.ts +30 -0
- package/lib/input/wheel-normalizer.d.ts.map +1 -0
- package/lib/renderers/canvas-2d-renderer.d.ts +10 -0
- package/lib/renderers/canvas-2d-renderer.d.ts.map +1 -0
- package/lib/renderers/index.d.ts +3 -0
- package/lib/renderers/index.d.ts.map +1 -0
- package/lib/renderers/label-cache.d.ts +36 -0
- package/lib/renderers/label-cache.d.ts.map +1 -0
- package/lib/renderers/offscreen-ruler-cache.d.ts +17 -0
- package/lib/renderers/offscreen-ruler-cache.d.ts.map +1 -0
- package/lib/renderers/types.d.ts +34 -0
- package/lib/renderers/types.d.ts.map +1 -0
- package/package.json +59 -0
package/AGENTS.md
ADDED
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
# @sketch-ruler/canvas 项目指南
|
|
2
|
+
|
|
3
|
+
> 本文件面向 AI Coding Agent,用于快速理解 `@sketch-ruler/canvas` 的结构、API 与开发约定。
|
|
4
|
+
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
## 包概述
|
|
8
|
+
|
|
9
|
+
`@sketch-ruler/canvas` 是 **框架无关** 的 Canvas 2D 渲染器与 DOM 输入管理器。职责:
|
|
10
|
+
|
|
11
|
+
- Canvas 2D 标尺渲染(Canvas2DRenderer)
|
|
12
|
+
- 离屏缓存(OffscreenRulerCache)
|
|
13
|
+
- 标签缓存(LabelCache)
|
|
14
|
+
- 鼠标/键盘/滚轮事件适配(InputManager、MouseAdapter、KeyboardAdapter)
|
|
15
|
+
- 滚轮标准化(WheelNormalizer)
|
|
16
|
+
|
|
17
|
+
**唯一外部依赖**:`@sketch-ruler/core`
|
|
18
|
+
|
|
19
|
+
---
|
|
20
|
+
|
|
21
|
+
## 目录结构
|
|
22
|
+
|
|
23
|
+
```
|
|
24
|
+
src/
|
|
25
|
+
├── renderers/ # Canvas 2D 渲染器与缓存
|
|
26
|
+
│ ├── canvas-2d-renderer.ts
|
|
27
|
+
│ ├── offscreen-ruler-cache.ts
|
|
28
|
+
│ ├── label-cache.ts
|
|
29
|
+
│ ├── types.ts
|
|
30
|
+
│ └── index.ts
|
|
31
|
+
├── input/ # DOM 输入管理
|
|
32
|
+
│ ├── input-manager.ts # 统一输入入口
|
|
33
|
+
│ ├── mouse-adapter.ts # 鼠标事件封装
|
|
34
|
+
│ ├── keyboard-adapter.ts # 键盘快捷键
|
|
35
|
+
│ ├── wheel-normalizer.ts # 滚轮标准化
|
|
36
|
+
│ └── index.ts
|
|
37
|
+
└── index.ts # 统一导出入口
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
---
|
|
41
|
+
|
|
42
|
+
## 构建与测试
|
|
43
|
+
|
|
44
|
+
```bash
|
|
45
|
+
# 安装依赖(在根目录执行)
|
|
46
|
+
pnpm i
|
|
47
|
+
|
|
48
|
+
# 构建本包
|
|
49
|
+
cd packages/canvas && pnpm build
|
|
50
|
+
|
|
51
|
+
# 运行测试
|
|
52
|
+
cd packages/canvas && pnpm test
|
|
53
|
+
|
|
54
|
+
# 测试监听模式
|
|
55
|
+
cd packages/canvas && pnpm test:watch
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
---
|
|
59
|
+
|
|
60
|
+
## 编码约定
|
|
61
|
+
|
|
62
|
+
- **语言**:TypeScript 5.9+,strict 模式
|
|
63
|
+
- **模块**:ESM(`"type": "module"`)
|
|
64
|
+
- **DOM 依赖**:本包是唯一允许直接操作 DOM 和 Canvas 2D API 的包
|
|
65
|
+
- **事件绑定**:所有 DOM 事件监听器必须在 `destroy()` / `unbind()` 中清理,避免内存泄漏
|
|
66
|
+
- **命名**:
|
|
67
|
+
- 类名:PascalCase(`InputManager`、`Canvas2DRenderer`)
|
|
68
|
+
- 回调接口:以 `Callbacks` 结尾(`MouseAdapterCallbacks`)
|
|
69
|
+
|
|
70
|
+
---
|
|
71
|
+
|
|
72
|
+
## 关键 API 速查
|
|
73
|
+
|
|
74
|
+
### InputManager
|
|
75
|
+
|
|
76
|
+
```ts
|
|
77
|
+
const input = new InputManager(engine, {
|
|
78
|
+
zoomStep: 0.25,
|
|
79
|
+
zoomMode: 'pointer', // 'pointer' | 'viewport-center' | 'content-center'
|
|
80
|
+
viewportSize: { width, height },
|
|
81
|
+
contentSize: { width, height },
|
|
82
|
+
onCursorChange: (cls) => { } // 'default' | 'grab' | 'grabbing'
|
|
83
|
+
})
|
|
84
|
+
input.bind(containerElement) // 事件绑定到 container.parentElement
|
|
85
|
+
input.setZoomMode(mode)
|
|
86
|
+
input.getCursorClass()
|
|
87
|
+
input.destroy() // 清理所有事件监听
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
**内置交互**:
|
|
91
|
+
- `Ctrl/Cmd + 滚轮`:缩放
|
|
92
|
+
- `Space + 鼠标左键拖拽`:平移
|
|
93
|
+
- `Ctrl+0`:100% 缩放
|
|
94
|
+
- `Ctrl++` / `Ctrl+-`:放大/缩小
|
|
95
|
+
- `Ctrl+1`:适配视口
|
|
96
|
+
|
|
97
|
+
### Canvas2DRenderer
|
|
98
|
+
|
|
99
|
+
```ts
|
|
100
|
+
const renderer = new Canvas2DRenderer(canvasElement)
|
|
101
|
+
renderer.render({
|
|
102
|
+
width, height, scale, offset,
|
|
103
|
+
vertical: false, // false=水平, true=垂直
|
|
104
|
+
thick, marks, config, palette
|
|
105
|
+
})
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
### 底层适配器(自行组合)
|
|
109
|
+
|
|
110
|
+
```ts
|
|
111
|
+
const mouse = new MouseAdapter(parent, callbacks)
|
|
112
|
+
mouse.bind()
|
|
113
|
+
mouse.unbind()
|
|
114
|
+
|
|
115
|
+
const kb = new KeyboardAdapter({ onShortcut: (combo, e) => { } })
|
|
116
|
+
kb.bind()
|
|
117
|
+
kb.unbind()
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
---
|
|
121
|
+
|
|
122
|
+
## 子路径导出
|
|
123
|
+
|
|
124
|
+
| 路径 | 用途 |
|
|
125
|
+
|------|------|
|
|
126
|
+
| `@sketch-ruler/canvas` | 完整导出 |
|
|
127
|
+
| `@sketch-ruler/canvas/renderers` | 仅渲染器与缓存 |
|
|
128
|
+
| `@sketch-ruler/canvas/input` | 仅输入管理层 |
|
|
129
|
+
|
|
130
|
+
---
|
|
131
|
+
|
|
132
|
+
## 与 @sketch-ruler/core 的协作
|
|
133
|
+
|
|
134
|
+
```ts
|
|
135
|
+
import { TransformEngine, computeScaleMarks, getTickConfig } from '@sketch-ruler/core'
|
|
136
|
+
import { InputManager, Canvas2DRenderer } from '@sketch-ruler/canvas'
|
|
137
|
+
|
|
138
|
+
const engine = new TransformEngine({ x: 0, y: 0, scale: 1 })
|
|
139
|
+
const input = new InputManager(engine, { zoomMode: 'pointer' })
|
|
140
|
+
input.bind(canvasElement)
|
|
141
|
+
|
|
142
|
+
engine.onUpdate((state) => {
|
|
143
|
+
content.style.transform = `matrix(${state.scale}, 0, 0, ${state.scale}, ${state.x}, ${state.y})`
|
|
144
|
+
const marks = computeScaleMarks({ scale: state.scale, offset: state.x, length, thick, config })
|
|
145
|
+
renderer.render({ ... })
|
|
146
|
+
})
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
---
|
|
150
|
+
|
|
151
|
+
## 注意事项
|
|
152
|
+
|
|
153
|
+
- `InputManager.bind(container)` 实际将滚轮/鼠标事件绑定到 `container.parentElement`,因此 `parentElement` 必须存在
|
|
154
|
+
- `MouseAdapter` 的 `onWheel` 回调接收 `(WheelEvent, NormalizedWheel)`,滚轮标准化逻辑在 `WheelNormalizer` 中
|
|
155
|
+
- 新增 DOM 事件相关逻辑时,请务必提供对应的解绑方法,确保 `destroy()` 能完整清理
|
package/README.md
ADDED
|
@@ -0,0 +1,224 @@
|
|
|
1
|
+
# @sketch-ruler/canvas
|
|
2
|
+
|
|
3
|
+
> 框架无关的 Canvas 2D 渲染器与 DOM 输入管理器。负责标尺绘制、离屏缓存、鼠标/键盘/滚轮事件适配。依赖 `@sketch-ruler/core`。
|
|
4
|
+
|
|
5
|
+
## 安装
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
npm install @sketch-ruler/canvas
|
|
9
|
+
# 或
|
|
10
|
+
pnpm add @sketch-ruler/canvas
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
## 快速上手
|
|
14
|
+
|
|
15
|
+
### InputManager — 统一输入管理
|
|
16
|
+
|
|
17
|
+
绑定到画布容器,自动处理 **Ctrl+滚轮缩放**、**空格+拖拽平移**、**键盘快捷键**(Ctrl+0 / Ctrl++/Ctrl+-)。
|
|
18
|
+
|
|
19
|
+
```ts
|
|
20
|
+
import { InputManager } from '@sketch-ruler/canvas'
|
|
21
|
+
import { TransformEngine } from '@sketch-ruler/core'
|
|
22
|
+
|
|
23
|
+
const engine = new TransformEngine({ x: 0, y: 0, scale: 1 })
|
|
24
|
+
|
|
25
|
+
const inputManager = new InputManager(engine, {
|
|
26
|
+
zoomStep: 0.25, // 滚轮缩放步长
|
|
27
|
+
zoomMode: 'pointer', // 'pointer' | 'viewport-center' | 'content-center'
|
|
28
|
+
viewportSize: { width: 1470, height: 700 },
|
|
29
|
+
contentSize: { width: 1920, height: 1080 },
|
|
30
|
+
onCursorChange: (cls) => {
|
|
31
|
+
// cls: 'default' | 'grab' | 'grabbing'
|
|
32
|
+
container.className = cls
|
|
33
|
+
}
|
|
34
|
+
})
|
|
35
|
+
|
|
36
|
+
// 绑定到画布 DOM(事件实际绑定到 parentElement)
|
|
37
|
+
inputManager.bind(canvasElement)
|
|
38
|
+
|
|
39
|
+
// 动态切换缩放模式
|
|
40
|
+
inputManager.setZoomMode('viewport-center')
|
|
41
|
+
|
|
42
|
+
// 获取当前光标类名
|
|
43
|
+
const cursorClass = inputManager.getCursorClass()
|
|
44
|
+
|
|
45
|
+
// 清理
|
|
46
|
+
inputManager.destroy()
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
**事件处理细节:**
|
|
50
|
+
|
|
51
|
+
- **滚轮缩放**:按住 `Ctrl` / `Cmd` + 滚轮,以 `zoomMode` 指定的原点进行缩放
|
|
52
|
+
- **空格拖拽**:按住 `Space` + 鼠标左键拖拽平移
|
|
53
|
+
- **键盘快捷键**:
|
|
54
|
+
- `Ctrl+0`:缩放到 100%
|
|
55
|
+
- `Ctrl++` / `Ctrl+=`:放大
|
|
56
|
+
- `Ctrl+-`:缩小
|
|
57
|
+
- `Ctrl+1`:适配视口(留 5% 边距)
|
|
58
|
+
|
|
59
|
+
### Canvas2DRenderer — Canvas 2D 标尺渲染器
|
|
60
|
+
|
|
61
|
+
```ts
|
|
62
|
+
import { Canvas2DRenderer } from '@sketch-ruler/canvas'
|
|
63
|
+
|
|
64
|
+
const canvas = document.getElementById('ruler') as HTMLCanvasElement
|
|
65
|
+
const renderer = new Canvas2DRenderer(canvas)
|
|
66
|
+
|
|
67
|
+
renderer.render({
|
|
68
|
+
width: 1400,
|
|
69
|
+
height: 20,
|
|
70
|
+
scale: 1,
|
|
71
|
+
offset: 0,
|
|
72
|
+
vertical: false, // false=水平标尺, true=垂直标尺
|
|
73
|
+
thick: 20,
|
|
74
|
+
marks: scaleMarks, // 由 core 的 computeScaleMarks 生成
|
|
75
|
+
config: tickConfig,
|
|
76
|
+
palette: {
|
|
77
|
+
bgColor: '#f6f7f9',
|
|
78
|
+
tickColor: '#BABBBC',
|
|
79
|
+
labelColor: '#7D8694',
|
|
80
|
+
borderColor: '#eeeeef'
|
|
81
|
+
}
|
|
82
|
+
})
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
### 离屏缓存 — OffscreenRulerCache
|
|
86
|
+
|
|
87
|
+
用于频繁重绘场景,将标尺渲染到 OffscreenCanvas 再 blit 到主 Canvas。
|
|
88
|
+
|
|
89
|
+
```ts
|
|
90
|
+
import { OffscreenRulerCache } from '@sketch-ruler/canvas'
|
|
91
|
+
|
|
92
|
+
const cache = new OffscreenRulerCache({ width: 1400, height: 20 })
|
|
93
|
+
|
|
94
|
+
cache.draw((ctx) => {
|
|
95
|
+
// 使用 Canvas 2D 上下文绘制标尺
|
|
96
|
+
ctx.fillStyle = '#f6f7f9'
|
|
97
|
+
ctx.fillRect(0, 0, 1400, 20)
|
|
98
|
+
// ... 绘制刻度
|
|
99
|
+
})
|
|
100
|
+
|
|
101
|
+
// 将缓存绘制到主 Canvas
|
|
102
|
+
ctx.drawImage(cache.canvas, 0, 0)
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
### 标签缓存 — LabelCache
|
|
106
|
+
|
|
107
|
+
缓存刻度文字标签,避免每帧重复测量文本。
|
|
108
|
+
|
|
109
|
+
```ts
|
|
110
|
+
import { LabelCache } from '@sketch-ruler/canvas'
|
|
111
|
+
|
|
112
|
+
const labelCache = new LabelCache()
|
|
113
|
+
|
|
114
|
+
// 获取或创建标签
|
|
115
|
+
const label = labelCache.get('100px', '12px Arial')
|
|
116
|
+
// label: { text: '100px', width: number, bitmap: ImageBitmap }
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
### 滚轮标准化 — WheelNormalizer
|
|
120
|
+
|
|
121
|
+
将不同浏览器/操作系统的滚轮事件统一为标准格式。
|
|
122
|
+
|
|
123
|
+
```ts
|
|
124
|
+
import { normalizeWheel, getZoomDelta } from '@sketch-ruler/canvas'
|
|
125
|
+
|
|
126
|
+
canvas.addEventListener('wheel', (e) => {
|
|
127
|
+
const normalized = normalizeWheel(e)
|
|
128
|
+
// normalized: { pixelX, pixelY, spinX, spinY }
|
|
129
|
+
|
|
130
|
+
const delta = getZoomDelta(normalized, { zoomSpeed: 0.25 })
|
|
131
|
+
engine.zoomBy(delta, e.clientX, e.clientY)
|
|
132
|
+
})
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
### 底层适配器(自行组合事件)
|
|
136
|
+
|
|
137
|
+
如果你不想用 `InputManager`,可以直接使用底层适配器:
|
|
138
|
+
|
|
139
|
+
```ts
|
|
140
|
+
import { MouseAdapter } from '@sketch-ruler/canvas'
|
|
141
|
+
import type { MouseAdapterCallbacks } from '@sketch-ruler/canvas'
|
|
142
|
+
|
|
143
|
+
const adapter = new MouseAdapter(parentElement, {
|
|
144
|
+
onWheel: (e, normalized) => {
|
|
145
|
+
// 自定义滚轮处理
|
|
146
|
+
},
|
|
147
|
+
onMouseDown: (e) => {
|
|
148
|
+
// 自定义鼠标按下
|
|
149
|
+
},
|
|
150
|
+
onMouseMove: (e) => {
|
|
151
|
+
// 自定义鼠标移动
|
|
152
|
+
},
|
|
153
|
+
onMouseUp: (e) => {
|
|
154
|
+
// 自定义鼠标抬起
|
|
155
|
+
},
|
|
156
|
+
onMouseEnter: () => {},
|
|
157
|
+
onMouseLeave: () => {}
|
|
158
|
+
} as MouseAdapterCallbacks)
|
|
159
|
+
|
|
160
|
+
adapter.bind()
|
|
161
|
+
// adapter.unbind()
|
|
162
|
+
```
|
|
163
|
+
|
|
164
|
+
```ts
|
|
165
|
+
import { KeyboardAdapter } from '@sketch-ruler/canvas'
|
|
166
|
+
import type { KeyCombo } from '@sketch-ruler/canvas'
|
|
167
|
+
|
|
168
|
+
const kb = new KeyboardAdapter({
|
|
169
|
+
onShortcut: (combo: KeyCombo, e: KeyboardEvent) => {
|
|
170
|
+
// combo: 'ctrl+0' | 'ctrl+plus' | 'ctrl+minus' | 'space' | ...
|
|
171
|
+
console.log('shortcut', combo)
|
|
172
|
+
}
|
|
173
|
+
})
|
|
174
|
+
|
|
175
|
+
kb.bind()
|
|
176
|
+
// kb.unbind()
|
|
177
|
+
```
|
|
178
|
+
|
|
179
|
+
## API 概览
|
|
180
|
+
|
|
181
|
+
| 导出 | 类型 | 说明 |
|
|
182
|
+
|------|------|------|
|
|
183
|
+
| `InputManager` | 类 | 统一输入管理(滚轮/拖拽/键盘) |
|
|
184
|
+
| `MouseAdapter` | 类 | 鼠标事件封装(wheel/mousedown/mousemove/mouseup) |
|
|
185
|
+
| `KeyboardAdapter` | 类 | 键盘快捷键封装 |
|
|
186
|
+
| `normalizeWheel` | 函数 | 滚轮事件标准化 |
|
|
187
|
+
| `getZoomDelta` | 函数 | 从标准化滚轮计算缩放增量 |
|
|
188
|
+
| `Canvas2DRenderer` | 类 | Canvas 2D 标尺渲染器 |
|
|
189
|
+
| `OffscreenRulerCache` | 类 | 离屏标尺缓存 |
|
|
190
|
+
| `LabelCache` | 类 | 刻度标签缓存 |
|
|
191
|
+
|
|
192
|
+
## 与 @sketch-ruler/core 的配合
|
|
193
|
+
|
|
194
|
+
```ts
|
|
195
|
+
import { TransformEngine, computeScaleMarks, getTickConfig } from '@sketch-ruler/core'
|
|
196
|
+
import { InputManager, Canvas2DRenderer } from '@sketch-ruler/canvas'
|
|
197
|
+
|
|
198
|
+
// 1. 创建引擎
|
|
199
|
+
const engine = new TransformEngine({ x: 0, y: 0, scale: 1 })
|
|
200
|
+
|
|
201
|
+
// 2. 绑定输入
|
|
202
|
+
const input = new InputManager(engine, { zoomMode: 'pointer' })
|
|
203
|
+
input.bind(canvasElement)
|
|
204
|
+
|
|
205
|
+
// 3. 监听状态并渲染
|
|
206
|
+
engine.onUpdate((state) => {
|
|
207
|
+
// 更新 DOM transform
|
|
208
|
+
content.style.transform = `matrix(${state.scale}, 0, 0, ${state.scale}, ${state.x}, ${state.y})`
|
|
209
|
+
|
|
210
|
+
// 渲染标尺
|
|
211
|
+
const marks = computeScaleMarks({
|
|
212
|
+
scale: state.scale,
|
|
213
|
+
offset: state.x,
|
|
214
|
+
length: 1400,
|
|
215
|
+
thick: 20,
|
|
216
|
+
config: getTickConfig(state.scale)
|
|
217
|
+
})
|
|
218
|
+
renderer.render({ width: 1400, height: 20, scale: state.scale, offset: state.x, marks })
|
|
219
|
+
})
|
|
220
|
+
```
|
|
221
|
+
|
|
222
|
+
## License
|
|
223
|
+
|
|
224
|
+
MIT
|
package/lib/index.cjs
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
Object.defineProperty(exports,Symbol.toStringTag,{value:`Module`});var e=class{canvas=null;ctx=null;lastFingerprint=``;drawStatic(e,t,n){let r=this.buildFingerprint(t,n);return(r!==this.lastFingerprint||!this.canvas)&&(this.rebuild(t,n),this.lastFingerprint=r),this.canvas?(e.drawImage(this.canvas,0,0),!0):!1}rebuild(e,t){let{width:n,height:r,ratio:i,palette:a,thick:o,vertical:s}=t;this.canvas||=document.createElement(`canvas`),this.canvas.width=Math.round(n*i),this.canvas.height=Math.round(r*i);let c=this.canvas.getContext(`2d`);this.ctx=c,c.setTransform(1,0,0,1,0,0),c.scale(i,i),c.clearRect(0,0,n,r),c.fillStyle=a.bgColor,c.fillRect(0,0,n,r),c.strokeStyle=a.tickColor,c.lineWidth=1,c.beginPath();for(let i of e){let e=i.position;i.isMajor?i.value===0||i.value===t.canvasSize?s?(c.moveTo(0,e),c.lineTo(n,e)):(c.moveTo(e,0),c.lineTo(e,r)):s?(c.moveTo(n,e),c.lineTo(n*.65,e)):(c.moveTo(e,r),c.lineTo(e,r*.65)):s?(c.moveTo(n,e),c.lineTo(n*.8,e)):(c.moveTo(e,r),c.lineTo(e,r*.8))}c.stroke(),c.closePath()}buildFingerprint(e,t){let{thick:n,palette:r,vertical:i,width:a,height:o}=t,s=e.length>1?e[1].value-e[0].value:0,c=1,l=0;if(e.length>=2){let t=e[0],n=e.find(e=>e.value!==t.value)??e[1];n&&n.value!==t.value&&(c=(n.position-t.position)/(n.value-t.value),l=t.position-t.value*c)}return`${n}:${r.bgColor}:${r.tickColor}:${r.labelColor}:${i}:${a}:${o}:${s}:${c.toFixed(4)}:${l.toFixed(2)}`}clear(){this.canvas=null,this.ctx=null,this.lastFingerprint=``}},t=class{cache=new Map;maxSize;constructor(e=500){this.maxSize=e}get(e,t){let n=this.hashKey(t),r=this.cache.get(n);if(r)return this.cache.delete(n),this.cache.set(n,r),r;let i=this.createEntry(e,t);return this.cache.set(n,i),this.evictIfNeeded(),i}createEntry(e,t){let{text:n,font:r,color:i}=t;e.font=r;let a=e.measureText(n),o=Math.ceil(a.width),s=Math.ceil(a.actualBoundingBoxAscent+a.actualBoundingBoxDescent)||12;if(o===0||s===0)return{canvas:document.createElement(`canvas`),width:0,height:0};let c=document.createElement(`canvas`);c.width=o+2,c.height=s+2;let l=c.getContext(`2d`);return l.font=r,l.fillStyle=i,l.textBaseline=`alphabetic`,l.fillText(n,1,1+a.actualBoundingBoxAscent),{canvas:c,width:o,height:s}}evictIfNeeded(){if(this.cache.size<=this.maxSize)return;let e=this.cache.keys().next().value;e!==void 0&&this.cache.delete(e)}hashKey(e){return`${e.text}:${e.font}:${e.color}`}clear(){this.cache.clear()}size(){return this.cache.size}},n=`#e9f7fe`,r=class{offscreenCache=new e;labelCache=new t;render(e,t,n){for(let r of t)r.type===`ruler`&&this.renderRuler(e,r,n)}renderRuler(e,t,r){let{marks:i,vertical:a,thick:o,width:s,height:c,ratio:l,palette:u,shadowStart:d,shadowLength:f,showShadowText:p}=t;e.setTransform(1,0,0,1,0,0),e.scale(l,l),e.clearRect(0,0,s,c),this.offscreenCache.drawStatic(e,i,t);let m=[];if(f&&i.length>0){let t=d??0,r=i[0],l=i.find(e=>e.value!==r.value)??r,h=l&&l.value!==r.value?(l.position-r.position)/(l.value-r.value):1,g=r.position-r.value*h,_=t*h+g,v=f*h;if(v>0&&(e.fillStyle=u.shadowColor??n,a?e.fillRect(0,_,s,v):e.fillRect(_,0,v,c),p)){this.renderShadowText(e,t,_,o,a,u,!1);let n=t+f,r=n*h+g;this.renderShadowText(e,n,r,o,a,u,!0),m.push(_,r)}}for(let n of i)if(n.isMajor&&n.label){if(m.some(e=>Math.abs(n.position-e)<o*1.5))continue;e.save(),e.fillStyle=u.labelColor,e.font=`${Math.max(11,Math.floor(o*.5)+2)}px -apple-system, "Helvetica Neue", ".SFNSDisplay-Regular", "Helvetica Neue", Helvetica, "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei", "WenQuanYi Micro Hei", sans-serif`,a?(n.value===0?e.translate(s*.4,n.position-3):n.value===t.canvasSize?e.translate(s*.2,n.position+32):e.translate(s*.15,n.position+14),e.rotate(-90*Math.PI/180),e.fillText(n.label,4,9)):(n.value===0?e.translate(n.position-15,c*.13):n.value===t.canvasSize?e.translate(n.position+5,c*.3):e.translate(n.position-12,c*.18),e.fillText(n.label,4,9)),e.restore()}}renderShadowText(e,t,n,r,i,a,o){e.save(),e.fillStyle=a.labelColor,e.font=`bold 12px sans-serif`,i?(e.translate(r*.6,n+(o?-8:8)),e.rotate(-Math.PI/2)):e.translate(n+(o?-8:8),r*.6),e.fillText(Math.round(t).toString(),0,0),e.restore()}destroy(){this.offscreenCache.clear(),this.labelCache.clear()}};function i(e){let t=e.deltaX,n=e.deltaY,r=e.deltaZ||0,i=e.deltaMode;return i===WheelEvent.DOM_DELTA_LINE?(t*=40,n*=40,r*=40):i===WheelEvent.DOM_DELTA_PAGE&&(t*=800,n*=800,r*=800),{deltaX:t,deltaY:n,deltaZ:r,deltaMode:i}}function a(e,t=.001){let n=i(e);return-(n.deltaY===0?n.deltaX:n.deltaY)*t}var o=class{container;callbacks;boundWheel;boundMouseDown;boundMouseMove;boundMouseUp;boundMouseEnter;boundMouseLeave;isBound=!1;constructor(e,t){this.container=e,this.callbacks=t,this.boundWheel=this.handleWheel.bind(this),this.boundMouseDown=this.handleMouseDown.bind(this),this.boundMouseMove=this.handleMouseMove.bind(this),this.boundMouseUp=this.handleMouseUp.bind(this),this.boundMouseEnter=()=>this.callbacks.onMouseEnter?.(),this.boundMouseLeave=()=>this.callbacks.onMouseLeave?.()}bind(){this.isBound||(this.isBound=!0,this.container.addEventListener(`wheel`,this.boundWheel,{passive:!1}),this.container.addEventListener(`mousedown`,this.boundMouseDown),document.addEventListener(`mousemove`,this.boundMouseMove),document.addEventListener(`mouseup`,this.boundMouseUp),this.container.addEventListener(`mouseenter`,this.boundMouseEnter),this.container.addEventListener(`mouseleave`,this.boundMouseLeave))}unbind(){this.isBound&&(this.isBound=!1,this.container.removeEventListener(`wheel`,this.boundWheel),this.container.removeEventListener(`mousedown`,this.boundMouseDown),document.removeEventListener(`mousemove`,this.boundMouseMove),document.removeEventListener(`mouseup`,this.boundMouseUp),this.container.removeEventListener(`mouseenter`,this.boundMouseEnter),this.container.removeEventListener(`mouseleave`,this.boundMouseLeave))}handleWheel(e){let t=i(e);this.callbacks.onWheel?.(e,t)}handleMouseDown(e){this.callbacks.onMouseDown?.(e)}handleMouseMove(e){this.callbacks.onMouseMove?.(e)}handleMouseUp(e){this.callbacks.onMouseUp?.(e)}},s=class{callbacks;boundKeyDown;isBound=!1;constructor(e){this.callbacks=e,this.boundKeyDown=this.handleKeyDown.bind(this)}bind(){this.isBound||(this.isBound=!0,document.addEventListener(`keydown`,this.boundKeyDown))}unbind(){this.isBound&&(this.isBound=!1,document.removeEventListener(`keydown`,this.boundKeyDown))}handleKeyDown(e){if(e.repeat)return;let t=document.activeElement;if(t?.closest(`.monaco-editor`)||t?.tagName===`INPUT`||t?.tagName===`TEXTAREA`||t?.getAttribute(`contenteditable`)===`true`)return;let n=this.parseCombo(e);this.callbacks.onShortcut?.(n,e)}parseCombo(e){let t=[];(e.ctrlKey||e.metaKey)&&t.push(`ctrl`),e.altKey&&t.push(`alt`),e.shiftKey&&t.push(`shift`);let n=e.key.toLowerCase();return n===` `&&(n=`space`),n===`+`&&(n=`plus`),n===`-`&&(n=`minus`),n===`=`&&(n=`equal`),t.push(n),t.join(`+`)}},c=class{engine;zoomStep;selfHandle;zoomMode;viewportSize;contentSize;container=null;mouseAdapter=null;isSpacePressed=!1;isDragging=!1;dragStart={x:0,y:0};lastMouse={x:0,y:0};isHovered=!1;boundKeyUp;keyboardAdapter=null;pendingWheelDelta=0;wheelRafId=null;onCursorChange=null;zoomInterceptor=null;panInterceptor=null;constructor(e,t={}){this.engine=e,this.zoomStep=t.zoomStep??.25,this.selfHandle=t.selfHandle??!1,this.zoomMode=t.zoomMode??`pointer`,this.viewportSize=t.viewportSize??{width:0,height:0},this.contentSize=t.contentSize??{width:0,height:0},this.onCursorChange=t.onCursorChange??null,this.zoomInterceptor=t.zoomInterceptor??null,this.panInterceptor=t.panInterceptor??null,this.boundKeyUp=this.handleKeyUp.bind(this)}bind(e){if(this.selfHandle)return;this.unbind(),this.container=e;let t=e.parentElement;if(!t)return;document.addEventListener(`keyup`,this.boundKeyUp);let n={onWheel:this.handleWheel.bind(this),onMouseDown:this.handleMouseDown.bind(this),onMouseMove:this.handleMouseMove.bind(this),onMouseUp:this.handleMouseUp.bind(this),onMouseEnter:()=>{this.isHovered=!0},onMouseLeave:()=>{this.isHovered=!1}};this.mouseAdapter=new o(t,n),this.mouseAdapter.bind(),this.keyboardAdapter=new s({onShortcut:this.handleShortcut.bind(this)}),this.keyboardAdapter.bind()}unbind(){this.mouseAdapter?.unbind(),this.mouseAdapter=null,this.keyboardAdapter?.unbind(),this.keyboardAdapter=null,document.removeEventListener(`keyup`,this.boundKeyUp),this.container=null}destroy(){this.wheelRafId!==null&&(cancelAnimationFrame(this.wheelRafId),this.wheelRafId=null),this.unbind()}setZoomMode(e){this.zoomMode=e}handleWheel(e){if(e.ctrlKey||e.metaKey){e.preventDefault();let t=this.container?.parentElement,n=t?t.getBoundingClientRect():new DOMRect(0,0,0,0),r,i;switch(this.zoomMode){case`viewport-center`:r=this.viewportSize.width/2,i=this.viewportSize.height/2;break;case`content-center`:{let e=this.engine.getState();r=e.x+this.contentSize.width*e.scale/2,i=e.y+this.contentSize.height*e.scale/2;break}default:r=e.clientX-n.left,i=e.clientY-n.top;break}let a=e.deltaY===0?e.deltaX:e.deltaY;this.pendingWheelDelta+=a<0?1:-1,this.wheelRafId===null&&(this.wheelRafId=requestAnimationFrame(()=>{if(this.wheelRafId=null,this.pendingWheelDelta===0)return;let e=this.engine.getState().scale,t=e*Math.exp(this.pendingWheelDelta*this.zoomStep/3);this.pendingWheelDelta=0,this.executeZoom(()=>this.engine.zoomTo(t,r,i),e,t,r,i)}))}}handleShortcut(e,t){if(!this.isHovered)return;let n=this.viewportSize.width/2,r=this.viewportSize.height/2,i=e=>{e()};switch(e){case`ctrl+0`:t.preventDefault(),i(()=>this.executeZoom(()=>this.engine.zoomTo(1,n,r),this.engine.getState().scale,1,n,r));break;case`ctrl+minus`:{t.preventDefault();let e=this.engine.getState().scale,a=e-this.zoomStep;i(()=>this.executeZoom(()=>this.engine.zoomBy(-this.zoomStep,n,r),e,a,n,r));break}case`ctrl+equal`:case`ctrl+plus`:{t.preventDefault();let e=this.engine.getState().scale,a=e+this.zoomStep;i(()=>this.executeZoom(()=>this.engine.zoomBy(this.zoomStep,n,r),e,a,n,r));break}case`ctrl+1`:{t.preventDefault();let e=this.viewportSize.width,a=this.viewportSize.height,o=this.contentSize.width,s=this.contentSize.height;if(e>0&&a>0&&o>0&&s>0){let t=e*.9/o,c=a*.9/s,l=Math.min(t,c),u=(e-o*l)/2,d=(a-s*l)/2,f=this.engine.getState().scale;i(()=>this.executeZoom(()=>this.engine.setTransform({scale:l,x:u,y:d}),f,l,n,r))}break}case`space`:this.isSpacePressed||(this.isSpacePressed=!0,t.preventDefault(),this.notifyCursorChange());break;default:break}}handleKeyUp(e){e.key===` `&&(this.isSpacePressed=!1,this.isDragging=!1,this.notifyCursorChange())}handleMouseDown(e){this.isSpacePressed&&e.button===0&&(this.isDragging=!0,this.dragStart={x:e.clientX,y:e.clientY},this.lastMouse={x:e.clientX,y:e.clientY},e.preventDefault(),this.notifyCursorChange())}async handleMouseMove(e){if(this.isDragging&&this.isSpacePressed){let t=e.clientX-this.lastMouse.x,n=e.clientY-this.lastMouse.y;(!this.panInterceptor?.beforePan||await this.panInterceptor.beforePan(t,n))&&(this.engine.panBy(t,n),this.panInterceptor?.afterPan?.(t,n)),this.lastMouse={x:e.clientX,y:e.clientY}}}handleMouseUp(){this.isDragging=!1,this.notifyCursorChange()}async executeZoom(e,t,n,r,i){(!this.zoomInterceptor?.beforeZoom||await this.zoomInterceptor.beforeZoom(t,n,r,i))&&(e(),this.zoomInterceptor?.afterZoom?.(t,this.engine.getState().scale,r,i))}getCursorClass(){return this.isSpacePressed?this.isDragging?`grabbing`:`grab`:`default`}notifyCursorChange(){this.onCursorChange?.(this.getCursorClass())}};exports.Canvas2DRenderer=r,exports.InputManager=c,exports.KeyboardAdapter=s,exports.LabelCache=t,exports.MouseAdapter=o,exports.OffscreenRulerCache=e,exports.getZoomDelta=a,exports.normalizeWheel=i;
|
package/lib/index.d.ts
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
export type { Renderer, Rect, RulerRenderPayload, RenderItem } from './renderers/types';
|
|
2
|
+
export { Canvas2DRenderer } from './renderers/canvas-2d-renderer';
|
|
3
|
+
export { OffscreenRulerCache } from './renderers/offscreen-ruler-cache';
|
|
4
|
+
export { LabelCache } from './renderers/label-cache';
|
|
5
|
+
export { InputManager } from './input/input-manager';
|
|
6
|
+
export { MouseAdapter } from './input/mouse-adapter';
|
|
7
|
+
export { KeyboardAdapter } from './input/keyboard-adapter';
|
|
8
|
+
export { normalizeWheel, getZoomDelta } from './input/wheel-normalizer';
|
|
9
|
+
export type { NormalizedWheel } from './input/wheel-normalizer';
|
|
10
|
+
export type { InputManagerOptions, ZoomMode } from './input/input-manager';
|
|
11
|
+
export type { MouseAdapterCallbacks } from './input/mouse-adapter';
|
|
12
|
+
export type { KeyCombo } from './input/keyboard-adapter';
|
|
13
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AACA,YAAY,EAAE,QAAQ,EAAE,IAAI,EAAE,kBAAkB,EAAE,UAAU,EAAE,MAAM,mBAAmB,CAAA;AACvF,OAAO,EAAE,gBAAgB,EAAE,MAAM,gCAAgC,CAAA;AACjE,OAAO,EAAE,mBAAmB,EAAE,MAAM,mCAAmC,CAAA;AACvE,OAAO,EAAE,UAAU,EAAE,MAAM,yBAAyB,CAAA;AAGpD,OAAO,EAAE,YAAY,EAAE,MAAM,uBAAuB,CAAA;AACpD,OAAO,EAAE,YAAY,EAAE,MAAM,uBAAuB,CAAA;AACpD,OAAO,EAAE,eAAe,EAAE,MAAM,0BAA0B,CAAA;AAC1D,OAAO,EAAE,cAAc,EAAE,YAAY,EAAE,MAAM,0BAA0B,CAAA;AACvE,YAAY,EAAE,eAAe,EAAE,MAAM,0BAA0B,CAAA;AAC/D,YAAY,EAAE,mBAAmB,EAAE,QAAQ,EAAE,MAAM,uBAAuB,CAAA;AAC1E,YAAY,EAAE,qBAAqB,EAAE,MAAM,uBAAuB,CAAA;AAClE,YAAY,EAAE,QAAQ,EAAE,MAAM,0BAA0B,CAAA"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
var SketchRulerCanvas=(function(e){Object.defineProperty(e,Symbol.toStringTag,{value:`Module`});var t=class{canvas=null;ctx=null;lastFingerprint=``;drawStatic(e,t,n){let r=this.buildFingerprint(t,n);return(r!==this.lastFingerprint||!this.canvas)&&(this.rebuild(t,n),this.lastFingerprint=r),this.canvas?(e.drawImage(this.canvas,0,0),!0):!1}rebuild(e,t){let{width:n,height:r,ratio:i,palette:a,thick:o,vertical:s}=t;this.canvas||=document.createElement(`canvas`),this.canvas.width=Math.round(n*i),this.canvas.height=Math.round(r*i);let c=this.canvas.getContext(`2d`);this.ctx=c,c.setTransform(1,0,0,1,0,0),c.scale(i,i),c.clearRect(0,0,n,r),c.fillStyle=a.bgColor,c.fillRect(0,0,n,r),c.strokeStyle=a.tickColor,c.lineWidth=1,c.beginPath();for(let i of e){let e=i.position;i.isMajor?i.value===0||i.value===t.canvasSize?s?(c.moveTo(0,e),c.lineTo(n,e)):(c.moveTo(e,0),c.lineTo(e,r)):s?(c.moveTo(n,e),c.lineTo(n*.65,e)):(c.moveTo(e,r),c.lineTo(e,r*.65)):s?(c.moveTo(n,e),c.lineTo(n*.8,e)):(c.moveTo(e,r),c.lineTo(e,r*.8))}c.stroke(),c.closePath()}buildFingerprint(e,t){let{thick:n,palette:r,vertical:i,width:a,height:o}=t,s=e.length>1?e[1].value-e[0].value:0,c=1,l=0;if(e.length>=2){let t=e[0],n=e.find(e=>e.value!==t.value)??e[1];n&&n.value!==t.value&&(c=(n.position-t.position)/(n.value-t.value),l=t.position-t.value*c)}return`${n}:${r.bgColor}:${r.tickColor}:${r.labelColor}:${i}:${a}:${o}:${s}:${c.toFixed(4)}:${l.toFixed(2)}`}clear(){this.canvas=null,this.ctx=null,this.lastFingerprint=``}},n=class{cache=new Map;maxSize;constructor(e=500){this.maxSize=e}get(e,t){let n=this.hashKey(t),r=this.cache.get(n);if(r)return this.cache.delete(n),this.cache.set(n,r),r;let i=this.createEntry(e,t);return this.cache.set(n,i),this.evictIfNeeded(),i}createEntry(e,t){let{text:n,font:r,color:i}=t;e.font=r;let a=e.measureText(n),o=Math.ceil(a.width),s=Math.ceil(a.actualBoundingBoxAscent+a.actualBoundingBoxDescent)||12;if(o===0||s===0)return{canvas:document.createElement(`canvas`),width:0,height:0};let c=document.createElement(`canvas`);c.width=o+2,c.height=s+2;let l=c.getContext(`2d`);return l.font=r,l.fillStyle=i,l.textBaseline=`alphabetic`,l.fillText(n,1,1+a.actualBoundingBoxAscent),{canvas:c,width:o,height:s}}evictIfNeeded(){if(this.cache.size<=this.maxSize)return;let e=this.cache.keys().next().value;e!==void 0&&this.cache.delete(e)}hashKey(e){return`${e.text}:${e.font}:${e.color}`}clear(){this.cache.clear()}size(){return this.cache.size}},r=`#e9f7fe`,i=class{offscreenCache=new t;labelCache=new n;render(e,t,n){for(let r of t)r.type===`ruler`&&this.renderRuler(e,r,n)}renderRuler(e,t,n){let{marks:i,vertical:a,thick:o,width:s,height:c,ratio:l,palette:u,shadowStart:d,shadowLength:f,showShadowText:p}=t;e.setTransform(1,0,0,1,0,0),e.scale(l,l),e.clearRect(0,0,s,c),this.offscreenCache.drawStatic(e,i,t);let m=[];if(f&&i.length>0){let t=d??0,n=i[0],l=i.find(e=>e.value!==n.value)??n,h=l&&l.value!==n.value?(l.position-n.position)/(l.value-n.value):1,g=n.position-n.value*h,_=t*h+g,v=f*h;if(v>0&&(e.fillStyle=u.shadowColor??r,a?e.fillRect(0,_,s,v):e.fillRect(_,0,v,c),p)){this.renderShadowText(e,t,_,o,a,u,!1);let n=t+f,r=n*h+g;this.renderShadowText(e,n,r,o,a,u,!0),m.push(_,r)}}for(let n of i)if(n.isMajor&&n.label){if(m.some(e=>Math.abs(n.position-e)<o*1.5))continue;e.save(),e.fillStyle=u.labelColor,e.font=`${Math.max(11,Math.floor(o*.5)+2)}px -apple-system, "Helvetica Neue", ".SFNSDisplay-Regular", "Helvetica Neue", Helvetica, "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei", "WenQuanYi Micro Hei", sans-serif`,a?(n.value===0?e.translate(s*.4,n.position-3):n.value===t.canvasSize?e.translate(s*.2,n.position+32):e.translate(s*.15,n.position+14),e.rotate(-90*Math.PI/180),e.fillText(n.label,4,9)):(n.value===0?e.translate(n.position-15,c*.13):n.value===t.canvasSize?e.translate(n.position+5,c*.3):e.translate(n.position-12,c*.18),e.fillText(n.label,4,9)),e.restore()}}renderShadowText(e,t,n,r,i,a,o){e.save(),e.fillStyle=a.labelColor,e.font=`bold 12px sans-serif`,i?(e.translate(r*.6,n+(o?-8:8)),e.rotate(-Math.PI/2)):e.translate(n+(o?-8:8),r*.6),e.fillText(Math.round(t).toString(),0,0),e.restore()}destroy(){this.offscreenCache.clear(),this.labelCache.clear()}};function a(e){let t=e.deltaX,n=e.deltaY,r=e.deltaZ||0,i=e.deltaMode;return i===WheelEvent.DOM_DELTA_LINE?(t*=40,n*=40,r*=40):i===WheelEvent.DOM_DELTA_PAGE&&(t*=800,n*=800,r*=800),{deltaX:t,deltaY:n,deltaZ:r,deltaMode:i}}function o(e,t=.001){let n=a(e);return-(n.deltaY===0?n.deltaX:n.deltaY)*t}var s=class{container;callbacks;boundWheel;boundMouseDown;boundMouseMove;boundMouseUp;boundMouseEnter;boundMouseLeave;isBound=!1;constructor(e,t){this.container=e,this.callbacks=t,this.boundWheel=this.handleWheel.bind(this),this.boundMouseDown=this.handleMouseDown.bind(this),this.boundMouseMove=this.handleMouseMove.bind(this),this.boundMouseUp=this.handleMouseUp.bind(this),this.boundMouseEnter=()=>this.callbacks.onMouseEnter?.(),this.boundMouseLeave=()=>this.callbacks.onMouseLeave?.()}bind(){this.isBound||(this.isBound=!0,this.container.addEventListener(`wheel`,this.boundWheel,{passive:!1}),this.container.addEventListener(`mousedown`,this.boundMouseDown),document.addEventListener(`mousemove`,this.boundMouseMove),document.addEventListener(`mouseup`,this.boundMouseUp),this.container.addEventListener(`mouseenter`,this.boundMouseEnter),this.container.addEventListener(`mouseleave`,this.boundMouseLeave))}unbind(){this.isBound&&(this.isBound=!1,this.container.removeEventListener(`wheel`,this.boundWheel),this.container.removeEventListener(`mousedown`,this.boundMouseDown),document.removeEventListener(`mousemove`,this.boundMouseMove),document.removeEventListener(`mouseup`,this.boundMouseUp),this.container.removeEventListener(`mouseenter`,this.boundMouseEnter),this.container.removeEventListener(`mouseleave`,this.boundMouseLeave))}handleWheel(e){let t=a(e);this.callbacks.onWheel?.(e,t)}handleMouseDown(e){this.callbacks.onMouseDown?.(e)}handleMouseMove(e){this.callbacks.onMouseMove?.(e)}handleMouseUp(e){this.callbacks.onMouseUp?.(e)}},c=class{callbacks;boundKeyDown;isBound=!1;constructor(e){this.callbacks=e,this.boundKeyDown=this.handleKeyDown.bind(this)}bind(){this.isBound||(this.isBound=!0,document.addEventListener(`keydown`,this.boundKeyDown))}unbind(){this.isBound&&(this.isBound=!1,document.removeEventListener(`keydown`,this.boundKeyDown))}handleKeyDown(e){if(e.repeat)return;let t=document.activeElement;if(t?.closest(`.monaco-editor`)||t?.tagName===`INPUT`||t?.tagName===`TEXTAREA`||t?.getAttribute(`contenteditable`)===`true`)return;let n=this.parseCombo(e);this.callbacks.onShortcut?.(n,e)}parseCombo(e){let t=[];(e.ctrlKey||e.metaKey)&&t.push(`ctrl`),e.altKey&&t.push(`alt`),e.shiftKey&&t.push(`shift`);let n=e.key.toLowerCase();return n===` `&&(n=`space`),n===`+`&&(n=`plus`),n===`-`&&(n=`minus`),n===`=`&&(n=`equal`),t.push(n),t.join(`+`)}},l=class{engine;zoomStep;selfHandle;zoomMode;viewportSize;contentSize;container=null;mouseAdapter=null;isSpacePressed=!1;isDragging=!1;dragStart={x:0,y:0};lastMouse={x:0,y:0};isHovered=!1;boundKeyUp;keyboardAdapter=null;pendingWheelDelta=0;wheelRafId=null;onCursorChange=null;zoomInterceptor=null;panInterceptor=null;constructor(e,t={}){this.engine=e,this.zoomStep=t.zoomStep??.25,this.selfHandle=t.selfHandle??!1,this.zoomMode=t.zoomMode??`pointer`,this.viewportSize=t.viewportSize??{width:0,height:0},this.contentSize=t.contentSize??{width:0,height:0},this.onCursorChange=t.onCursorChange??null,this.zoomInterceptor=t.zoomInterceptor??null,this.panInterceptor=t.panInterceptor??null,this.boundKeyUp=this.handleKeyUp.bind(this)}bind(e){if(this.selfHandle)return;this.unbind(),this.container=e;let t=e.parentElement;if(!t)return;document.addEventListener(`keyup`,this.boundKeyUp);let n={onWheel:this.handleWheel.bind(this),onMouseDown:this.handleMouseDown.bind(this),onMouseMove:this.handleMouseMove.bind(this),onMouseUp:this.handleMouseUp.bind(this),onMouseEnter:()=>{this.isHovered=!0},onMouseLeave:()=>{this.isHovered=!1}};this.mouseAdapter=new s(t,n),this.mouseAdapter.bind(),this.keyboardAdapter=new c({onShortcut:this.handleShortcut.bind(this)}),this.keyboardAdapter.bind()}unbind(){this.mouseAdapter?.unbind(),this.mouseAdapter=null,this.keyboardAdapter?.unbind(),this.keyboardAdapter=null,document.removeEventListener(`keyup`,this.boundKeyUp),this.container=null}destroy(){this.wheelRafId!==null&&(cancelAnimationFrame(this.wheelRafId),this.wheelRafId=null),this.unbind()}setZoomMode(e){this.zoomMode=e}handleWheel(e){if(e.ctrlKey||e.metaKey){e.preventDefault();let t=this.container?.parentElement,n=t?t.getBoundingClientRect():new DOMRect(0,0,0,0),r,i;switch(this.zoomMode){case`viewport-center`:r=this.viewportSize.width/2,i=this.viewportSize.height/2;break;case`content-center`:{let e=this.engine.getState();r=e.x+this.contentSize.width*e.scale/2,i=e.y+this.contentSize.height*e.scale/2;break}default:r=e.clientX-n.left,i=e.clientY-n.top;break}let a=e.deltaY===0?e.deltaX:e.deltaY;this.pendingWheelDelta+=a<0?1:-1,this.wheelRafId===null&&(this.wheelRafId=requestAnimationFrame(()=>{if(this.wheelRafId=null,this.pendingWheelDelta===0)return;let e=this.engine.getState().scale,t=e*Math.exp(this.pendingWheelDelta*this.zoomStep/3);this.pendingWheelDelta=0,this.executeZoom(()=>this.engine.zoomTo(t,r,i),e,t,r,i)}))}}handleShortcut(e,t){if(!this.isHovered)return;let n=this.viewportSize.width/2,r=this.viewportSize.height/2,i=e=>{e()};switch(e){case`ctrl+0`:t.preventDefault(),i(()=>this.executeZoom(()=>this.engine.zoomTo(1,n,r),this.engine.getState().scale,1,n,r));break;case`ctrl+minus`:{t.preventDefault();let e=this.engine.getState().scale,a=e-this.zoomStep;i(()=>this.executeZoom(()=>this.engine.zoomBy(-this.zoomStep,n,r),e,a,n,r));break}case`ctrl+equal`:case`ctrl+plus`:{t.preventDefault();let e=this.engine.getState().scale,a=e+this.zoomStep;i(()=>this.executeZoom(()=>this.engine.zoomBy(this.zoomStep,n,r),e,a,n,r));break}case`ctrl+1`:{t.preventDefault();let e=this.viewportSize.width,a=this.viewportSize.height,o=this.contentSize.width,s=this.contentSize.height;if(e>0&&a>0&&o>0&&s>0){let t=e*.9/o,c=a*.9/s,l=Math.min(t,c),u=(e-o*l)/2,d=(a-s*l)/2,f=this.engine.getState().scale;i(()=>this.executeZoom(()=>this.engine.setTransform({scale:l,x:u,y:d}),f,l,n,r))}break}case`space`:this.isSpacePressed||(this.isSpacePressed=!0,t.preventDefault(),this.notifyCursorChange());break;default:break}}handleKeyUp(e){e.key===` `&&(this.isSpacePressed=!1,this.isDragging=!1,this.notifyCursorChange())}handleMouseDown(e){this.isSpacePressed&&e.button===0&&(this.isDragging=!0,this.dragStart={x:e.clientX,y:e.clientY},this.lastMouse={x:e.clientX,y:e.clientY},e.preventDefault(),this.notifyCursorChange())}async handleMouseMove(e){if(this.isDragging&&this.isSpacePressed){let t=e.clientX-this.lastMouse.x,n=e.clientY-this.lastMouse.y;(!this.panInterceptor?.beforePan||await this.panInterceptor.beforePan(t,n))&&(this.engine.panBy(t,n),this.panInterceptor?.afterPan?.(t,n)),this.lastMouse={x:e.clientX,y:e.clientY}}}handleMouseUp(){this.isDragging=!1,this.notifyCursorChange()}async executeZoom(e,t,n,r,i){(!this.zoomInterceptor?.beforeZoom||await this.zoomInterceptor.beforeZoom(t,n,r,i))&&(e(),this.zoomInterceptor?.afterZoom?.(t,this.engine.getState().scale,r,i))}getCursorClass(){return this.isSpacePressed?this.isDragging?`grabbing`:`grab`:`default`}notifyCursorChange(){this.onCursorChange?.(this.getCursorClass())}};return e.Canvas2DRenderer=i,e.InputManager=l,e.KeyboardAdapter=c,e.LabelCache=n,e.MouseAdapter=s,e.OffscreenRulerCache=t,e.getZoomDelta=o,e.normalizeWheel=a,e})({});
|
package/lib/index.js
ADDED
|
@@ -0,0 +1,348 @@
|
|
|
1
|
+
/*!@sketch-ruler/canvas v3.0.0-beta.0 2026-5-21*/
|
|
2
|
+
//#region src/renderers/offscreen-ruler-cache.ts
|
|
3
|
+
var e = class {
|
|
4
|
+
canvas = null;
|
|
5
|
+
ctx = null;
|
|
6
|
+
lastFingerprint = "";
|
|
7
|
+
drawStatic(e, t, n) {
|
|
8
|
+
let r = this.buildFingerprint(t, n);
|
|
9
|
+
return (r !== this.lastFingerprint || !this.canvas) && (this.rebuild(t, n), this.lastFingerprint = r), this.canvas ? (e.drawImage(this.canvas, 0, 0), !0) : !1;
|
|
10
|
+
}
|
|
11
|
+
rebuild(e, t) {
|
|
12
|
+
let { width: n, height: r, ratio: i, palette: a, thick: o, vertical: s } = t;
|
|
13
|
+
this.canvas ||= document.createElement("canvas"), this.canvas.width = Math.round(n * i), this.canvas.height = Math.round(r * i);
|
|
14
|
+
let c = this.canvas.getContext("2d");
|
|
15
|
+
this.ctx = c, c.setTransform(1, 0, 0, 1, 0, 0), c.scale(i, i), c.clearRect(0, 0, n, r), c.fillStyle = a.bgColor, c.fillRect(0, 0, n, r), c.strokeStyle = a.tickColor, c.lineWidth = 1, c.beginPath();
|
|
16
|
+
for (let i of e) {
|
|
17
|
+
let e = i.position;
|
|
18
|
+
i.isMajor ? i.value === 0 || i.value === t.canvasSize ? s ? (c.moveTo(0, e), c.lineTo(n, e)) : (c.moveTo(e, 0), c.lineTo(e, r)) : s ? (c.moveTo(n, e), c.lineTo(n * .65, e)) : (c.moveTo(e, r), c.lineTo(e, r * .65)) : s ? (c.moveTo(n, e), c.lineTo(n * .8, e)) : (c.moveTo(e, r), c.lineTo(e, r * .8));
|
|
19
|
+
}
|
|
20
|
+
c.stroke(), c.closePath();
|
|
21
|
+
}
|
|
22
|
+
buildFingerprint(e, t) {
|
|
23
|
+
let { thick: n, palette: r, vertical: i, width: a, height: o } = t, s = e.length > 1 ? e[1].value - e[0].value : 0, c = 1, l = 0;
|
|
24
|
+
if (e.length >= 2) {
|
|
25
|
+
let t = e[0], n = e.find((e) => e.value !== t.value) ?? e[1];
|
|
26
|
+
n && n.value !== t.value && (c = (n.position - t.position) / (n.value - t.value), l = t.position - t.value * c);
|
|
27
|
+
}
|
|
28
|
+
return `${n}:${r.bgColor}:${r.tickColor}:${r.labelColor}:${i}:${a}:${o}:${s}:${c.toFixed(4)}:${l.toFixed(2)}`;
|
|
29
|
+
}
|
|
30
|
+
clear() {
|
|
31
|
+
this.canvas = null, this.ctx = null, this.lastFingerprint = "";
|
|
32
|
+
}
|
|
33
|
+
}, t = class {
|
|
34
|
+
cache = /* @__PURE__ */ new Map();
|
|
35
|
+
maxSize;
|
|
36
|
+
constructor(e = 500) {
|
|
37
|
+
this.maxSize = e;
|
|
38
|
+
}
|
|
39
|
+
get(e, t) {
|
|
40
|
+
let n = this.hashKey(t), r = this.cache.get(n);
|
|
41
|
+
if (r) return this.cache.delete(n), this.cache.set(n, r), r;
|
|
42
|
+
let i = this.createEntry(e, t);
|
|
43
|
+
return this.cache.set(n, i), this.evictIfNeeded(), i;
|
|
44
|
+
}
|
|
45
|
+
createEntry(e, t) {
|
|
46
|
+
let { text: n, font: r, color: i } = t;
|
|
47
|
+
e.font = r;
|
|
48
|
+
let a = e.measureText(n), o = Math.ceil(a.width), s = Math.ceil(a.actualBoundingBoxAscent + a.actualBoundingBoxDescent) || 12;
|
|
49
|
+
if (o === 0 || s === 0) return {
|
|
50
|
+
canvas: document.createElement("canvas"),
|
|
51
|
+
width: 0,
|
|
52
|
+
height: 0
|
|
53
|
+
};
|
|
54
|
+
let c = document.createElement("canvas");
|
|
55
|
+
c.width = o + 2, c.height = s + 2;
|
|
56
|
+
let l = c.getContext("2d");
|
|
57
|
+
return l.font = r, l.fillStyle = i, l.textBaseline = "alphabetic", l.fillText(n, 1, 1 + a.actualBoundingBoxAscent), {
|
|
58
|
+
canvas: c,
|
|
59
|
+
width: o,
|
|
60
|
+
height: s
|
|
61
|
+
};
|
|
62
|
+
}
|
|
63
|
+
evictIfNeeded() {
|
|
64
|
+
if (this.cache.size <= this.maxSize) return;
|
|
65
|
+
let e = this.cache.keys().next().value;
|
|
66
|
+
e !== void 0 && this.cache.delete(e);
|
|
67
|
+
}
|
|
68
|
+
hashKey(e) {
|
|
69
|
+
return `${e.text}:${e.font}:${e.color}`;
|
|
70
|
+
}
|
|
71
|
+
clear() {
|
|
72
|
+
this.cache.clear();
|
|
73
|
+
}
|
|
74
|
+
size() {
|
|
75
|
+
return this.cache.size;
|
|
76
|
+
}
|
|
77
|
+
}, n = "#e9f7fe", r = class {
|
|
78
|
+
offscreenCache = new e();
|
|
79
|
+
labelCache = new t();
|
|
80
|
+
render(e, t, n) {
|
|
81
|
+
for (let r of t) r.type === "ruler" && this.renderRuler(e, r, n);
|
|
82
|
+
}
|
|
83
|
+
renderRuler(e, t, r) {
|
|
84
|
+
let { marks: i, vertical: a, thick: o, width: s, height: c, ratio: l, palette: u, shadowStart: d, shadowLength: f, showShadowText: p } = t;
|
|
85
|
+
e.setTransform(1, 0, 0, 1, 0, 0), e.scale(l, l), e.clearRect(0, 0, s, c), this.offscreenCache.drawStatic(e, i, t);
|
|
86
|
+
let m = [];
|
|
87
|
+
if (f && i.length > 0) {
|
|
88
|
+
let t = d ?? 0, r = i[0], l = i.find((e) => e.value !== r.value) ?? r, h = l && l.value !== r.value ? (l.position - r.position) / (l.value - r.value) : 1, g = r.position - r.value * h, _ = t * h + g, v = f * h;
|
|
89
|
+
if (v > 0 && (e.fillStyle = u.shadowColor ?? n, a ? e.fillRect(0, _, s, v) : e.fillRect(_, 0, v, c), p)) {
|
|
90
|
+
this.renderShadowText(e, t, _, o, a, u, !1);
|
|
91
|
+
let n = t + f, r = n * h + g;
|
|
92
|
+
this.renderShadowText(e, n, r, o, a, u, !0), m.push(_, r);
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
for (let n of i) if (n.isMajor && n.label) {
|
|
96
|
+
if (m.some((e) => Math.abs(n.position - e) < o * 1.5)) continue;
|
|
97
|
+
e.save(), e.fillStyle = u.labelColor, e.font = `${Math.max(11, Math.floor(o * .5) + 2)}px -apple-system, "Helvetica Neue", ".SFNSDisplay-Regular", "Helvetica Neue", Helvetica, "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei", "WenQuanYi Micro Hei", sans-serif`, a ? (n.value === 0 ? e.translate(s * .4, n.position - 3) : n.value === t.canvasSize ? e.translate(s * .2, n.position + 32) : e.translate(s * .15, n.position + 14), e.rotate(-90 * Math.PI / 180), e.fillText(n.label, 4, 9)) : (n.value === 0 ? e.translate(n.position - 15, c * .13) : n.value === t.canvasSize ? e.translate(n.position + 5, c * .3) : e.translate(n.position - 12, c * .18), e.fillText(n.label, 4, 9)), e.restore();
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
renderShadowText(e, t, n, r, i, a, o) {
|
|
101
|
+
e.save(), e.fillStyle = a.labelColor, e.font = "bold 12px sans-serif", i ? (e.translate(r * .6, n + (o ? -8 : 8)), e.rotate(-Math.PI / 2)) : e.translate(n + (o ? -8 : 8), r * .6), e.fillText(Math.round(t).toString(), 0, 0), e.restore();
|
|
102
|
+
}
|
|
103
|
+
destroy() {
|
|
104
|
+
this.offscreenCache.clear(), this.labelCache.clear();
|
|
105
|
+
}
|
|
106
|
+
};
|
|
107
|
+
//#endregion
|
|
108
|
+
//#region src/input/wheel-normalizer.ts
|
|
109
|
+
function i(e) {
|
|
110
|
+
let t = e.deltaX, n = e.deltaY, r = e.deltaZ || 0, i = e.deltaMode;
|
|
111
|
+
return i === WheelEvent.DOM_DELTA_LINE ? (t *= 40, n *= 40, r *= 40) : i === WheelEvent.DOM_DELTA_PAGE && (t *= 800, n *= 800, r *= 800), {
|
|
112
|
+
deltaX: t,
|
|
113
|
+
deltaY: n,
|
|
114
|
+
deltaZ: r,
|
|
115
|
+
deltaMode: i
|
|
116
|
+
};
|
|
117
|
+
}
|
|
118
|
+
function a(e, t = .001) {
|
|
119
|
+
let n = i(e);
|
|
120
|
+
return -(n.deltaY === 0 ? n.deltaX : n.deltaY) * t;
|
|
121
|
+
}
|
|
122
|
+
//#endregion
|
|
123
|
+
//#region src/input/mouse-adapter.ts
|
|
124
|
+
var o = class {
|
|
125
|
+
container;
|
|
126
|
+
callbacks;
|
|
127
|
+
boundWheel;
|
|
128
|
+
boundMouseDown;
|
|
129
|
+
boundMouseMove;
|
|
130
|
+
boundMouseUp;
|
|
131
|
+
boundMouseEnter;
|
|
132
|
+
boundMouseLeave;
|
|
133
|
+
isBound = !1;
|
|
134
|
+
constructor(e, t) {
|
|
135
|
+
this.container = e, this.callbacks = t, this.boundWheel = this.handleWheel.bind(this), this.boundMouseDown = this.handleMouseDown.bind(this), this.boundMouseMove = this.handleMouseMove.bind(this), this.boundMouseUp = this.handleMouseUp.bind(this), this.boundMouseEnter = () => this.callbacks.onMouseEnter?.(), this.boundMouseLeave = () => this.callbacks.onMouseLeave?.();
|
|
136
|
+
}
|
|
137
|
+
bind() {
|
|
138
|
+
this.isBound || (this.isBound = !0, this.container.addEventListener("wheel", this.boundWheel, { passive: !1 }), this.container.addEventListener("mousedown", this.boundMouseDown), document.addEventListener("mousemove", this.boundMouseMove), document.addEventListener("mouseup", this.boundMouseUp), this.container.addEventListener("mouseenter", this.boundMouseEnter), this.container.addEventListener("mouseleave", this.boundMouseLeave));
|
|
139
|
+
}
|
|
140
|
+
unbind() {
|
|
141
|
+
this.isBound && (this.isBound = !1, this.container.removeEventListener("wheel", this.boundWheel), this.container.removeEventListener("mousedown", this.boundMouseDown), document.removeEventListener("mousemove", this.boundMouseMove), document.removeEventListener("mouseup", this.boundMouseUp), this.container.removeEventListener("mouseenter", this.boundMouseEnter), this.container.removeEventListener("mouseleave", this.boundMouseLeave));
|
|
142
|
+
}
|
|
143
|
+
handleWheel(e) {
|
|
144
|
+
let t = i(e);
|
|
145
|
+
this.callbacks.onWheel?.(e, t);
|
|
146
|
+
}
|
|
147
|
+
handleMouseDown(e) {
|
|
148
|
+
this.callbacks.onMouseDown?.(e);
|
|
149
|
+
}
|
|
150
|
+
handleMouseMove(e) {
|
|
151
|
+
this.callbacks.onMouseMove?.(e);
|
|
152
|
+
}
|
|
153
|
+
handleMouseUp(e) {
|
|
154
|
+
this.callbacks.onMouseUp?.(e);
|
|
155
|
+
}
|
|
156
|
+
}, s = class {
|
|
157
|
+
callbacks;
|
|
158
|
+
boundKeyDown;
|
|
159
|
+
isBound = !1;
|
|
160
|
+
constructor(e) {
|
|
161
|
+
this.callbacks = e, this.boundKeyDown = this.handleKeyDown.bind(this);
|
|
162
|
+
}
|
|
163
|
+
bind() {
|
|
164
|
+
this.isBound || (this.isBound = !0, document.addEventListener("keydown", this.boundKeyDown));
|
|
165
|
+
}
|
|
166
|
+
unbind() {
|
|
167
|
+
this.isBound && (this.isBound = !1, document.removeEventListener("keydown", this.boundKeyDown));
|
|
168
|
+
}
|
|
169
|
+
handleKeyDown(e) {
|
|
170
|
+
if (e.repeat) return;
|
|
171
|
+
let t = document.activeElement;
|
|
172
|
+
if (t?.closest(".monaco-editor") || t?.tagName === "INPUT" || t?.tagName === "TEXTAREA" || t?.getAttribute("contenteditable") === "true") return;
|
|
173
|
+
let n = this.parseCombo(e);
|
|
174
|
+
this.callbacks.onShortcut?.(n, e);
|
|
175
|
+
}
|
|
176
|
+
parseCombo(e) {
|
|
177
|
+
let t = [];
|
|
178
|
+
(e.ctrlKey || e.metaKey) && t.push("ctrl"), e.altKey && t.push("alt"), e.shiftKey && t.push("shift");
|
|
179
|
+
let n = e.key.toLowerCase();
|
|
180
|
+
return n === " " && (n = "space"), n === "+" && (n = "plus"), n === "-" && (n = "minus"), n === "=" && (n = "equal"), t.push(n), t.join("+");
|
|
181
|
+
}
|
|
182
|
+
}, c = class {
|
|
183
|
+
engine;
|
|
184
|
+
zoomStep;
|
|
185
|
+
selfHandle;
|
|
186
|
+
zoomMode;
|
|
187
|
+
viewportSize;
|
|
188
|
+
contentSize;
|
|
189
|
+
container = null;
|
|
190
|
+
mouseAdapter = null;
|
|
191
|
+
isSpacePressed = !1;
|
|
192
|
+
isDragging = !1;
|
|
193
|
+
dragStart = {
|
|
194
|
+
x: 0,
|
|
195
|
+
y: 0
|
|
196
|
+
};
|
|
197
|
+
lastMouse = {
|
|
198
|
+
x: 0,
|
|
199
|
+
y: 0
|
|
200
|
+
};
|
|
201
|
+
isHovered = !1;
|
|
202
|
+
boundKeyUp;
|
|
203
|
+
keyboardAdapter = null;
|
|
204
|
+
pendingWheelDelta = 0;
|
|
205
|
+
wheelRafId = null;
|
|
206
|
+
onCursorChange = null;
|
|
207
|
+
zoomInterceptor = null;
|
|
208
|
+
panInterceptor = null;
|
|
209
|
+
constructor(e, t = {}) {
|
|
210
|
+
this.engine = e, this.zoomStep = t.zoomStep ?? .25, this.selfHandle = t.selfHandle ?? !1, this.zoomMode = t.zoomMode ?? "pointer", this.viewportSize = t.viewportSize ?? {
|
|
211
|
+
width: 0,
|
|
212
|
+
height: 0
|
|
213
|
+
}, this.contentSize = t.contentSize ?? {
|
|
214
|
+
width: 0,
|
|
215
|
+
height: 0
|
|
216
|
+
}, this.onCursorChange = t.onCursorChange ?? null, this.zoomInterceptor = t.zoomInterceptor ?? null, this.panInterceptor = t.panInterceptor ?? null, this.boundKeyUp = this.handleKeyUp.bind(this);
|
|
217
|
+
}
|
|
218
|
+
bind(e) {
|
|
219
|
+
if (this.selfHandle) return;
|
|
220
|
+
this.unbind(), this.container = e;
|
|
221
|
+
let t = e.parentElement;
|
|
222
|
+
if (!t) return;
|
|
223
|
+
document.addEventListener("keyup", this.boundKeyUp);
|
|
224
|
+
let n = {
|
|
225
|
+
onWheel: this.handleWheel.bind(this),
|
|
226
|
+
onMouseDown: this.handleMouseDown.bind(this),
|
|
227
|
+
onMouseMove: this.handleMouseMove.bind(this),
|
|
228
|
+
onMouseUp: this.handleMouseUp.bind(this),
|
|
229
|
+
onMouseEnter: () => {
|
|
230
|
+
this.isHovered = !0;
|
|
231
|
+
},
|
|
232
|
+
onMouseLeave: () => {
|
|
233
|
+
this.isHovered = !1;
|
|
234
|
+
}
|
|
235
|
+
};
|
|
236
|
+
this.mouseAdapter = new o(t, n), this.mouseAdapter.bind(), this.keyboardAdapter = new s({ onShortcut: this.handleShortcut.bind(this) }), this.keyboardAdapter.bind();
|
|
237
|
+
}
|
|
238
|
+
unbind() {
|
|
239
|
+
this.mouseAdapter?.unbind(), this.mouseAdapter = null, this.keyboardAdapter?.unbind(), this.keyboardAdapter = null, document.removeEventListener("keyup", this.boundKeyUp), this.container = null;
|
|
240
|
+
}
|
|
241
|
+
destroy() {
|
|
242
|
+
this.wheelRafId !== null && (cancelAnimationFrame(this.wheelRafId), this.wheelRafId = null), this.unbind();
|
|
243
|
+
}
|
|
244
|
+
setZoomMode(e) {
|
|
245
|
+
this.zoomMode = e;
|
|
246
|
+
}
|
|
247
|
+
handleWheel(e) {
|
|
248
|
+
if (e.ctrlKey || e.metaKey) {
|
|
249
|
+
e.preventDefault();
|
|
250
|
+
let t = this.container?.parentElement, n = t ? t.getBoundingClientRect() : new DOMRect(0, 0, 0, 0), r, i;
|
|
251
|
+
switch (this.zoomMode) {
|
|
252
|
+
case "viewport-center":
|
|
253
|
+
r = this.viewportSize.width / 2, i = this.viewportSize.height / 2;
|
|
254
|
+
break;
|
|
255
|
+
case "content-center": {
|
|
256
|
+
let e = this.engine.getState();
|
|
257
|
+
r = e.x + this.contentSize.width * e.scale / 2, i = e.y + this.contentSize.height * e.scale / 2;
|
|
258
|
+
break;
|
|
259
|
+
}
|
|
260
|
+
default:
|
|
261
|
+
r = e.clientX - n.left, i = e.clientY - n.top;
|
|
262
|
+
break;
|
|
263
|
+
}
|
|
264
|
+
let a = e.deltaY === 0 ? e.deltaX : e.deltaY;
|
|
265
|
+
this.pendingWheelDelta += a < 0 ? 1 : -1, this.wheelRafId === null && (this.wheelRafId = requestAnimationFrame(() => {
|
|
266
|
+
if (this.wheelRafId = null, this.pendingWheelDelta === 0) return;
|
|
267
|
+
let e = this.engine.getState().scale, t = e * Math.exp(this.pendingWheelDelta * this.zoomStep / 3);
|
|
268
|
+
this.pendingWheelDelta = 0, this.executeZoom(() => this.engine.zoomTo(t, r, i), e, t, r, i);
|
|
269
|
+
}));
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
handleShortcut(e, t) {
|
|
273
|
+
if (!this.isHovered) return;
|
|
274
|
+
let n = this.viewportSize.width / 2, r = this.viewportSize.height / 2, i = (e) => {
|
|
275
|
+
e();
|
|
276
|
+
};
|
|
277
|
+
switch (e) {
|
|
278
|
+
case "ctrl+0":
|
|
279
|
+
t.preventDefault(), i(() => this.executeZoom(() => this.engine.zoomTo(1, n, r), this.engine.getState().scale, 1, n, r));
|
|
280
|
+
break;
|
|
281
|
+
case "ctrl+minus": {
|
|
282
|
+
t.preventDefault();
|
|
283
|
+
let e = this.engine.getState().scale, a = e - this.zoomStep;
|
|
284
|
+
i(() => this.executeZoom(() => this.engine.zoomBy(-this.zoomStep, n, r), e, a, n, r));
|
|
285
|
+
break;
|
|
286
|
+
}
|
|
287
|
+
case "ctrl+equal":
|
|
288
|
+
case "ctrl+plus": {
|
|
289
|
+
t.preventDefault();
|
|
290
|
+
let e = this.engine.getState().scale, a = e + this.zoomStep;
|
|
291
|
+
i(() => this.executeZoom(() => this.engine.zoomBy(this.zoomStep, n, r), e, a, n, r));
|
|
292
|
+
break;
|
|
293
|
+
}
|
|
294
|
+
case "ctrl+1": {
|
|
295
|
+
t.preventDefault();
|
|
296
|
+
let e = this.viewportSize.width, a = this.viewportSize.height, o = this.contentSize.width, s = this.contentSize.height;
|
|
297
|
+
if (e > 0 && a > 0 && o > 0 && s > 0) {
|
|
298
|
+
let t = e * .9 / o, c = a * .9 / s, l = Math.min(t, c), u = (e - o * l) / 2, d = (a - s * l) / 2, f = this.engine.getState().scale;
|
|
299
|
+
i(() => this.executeZoom(() => this.engine.setTransform({
|
|
300
|
+
scale: l,
|
|
301
|
+
x: u,
|
|
302
|
+
y: d
|
|
303
|
+
}), f, l, n, r));
|
|
304
|
+
}
|
|
305
|
+
break;
|
|
306
|
+
}
|
|
307
|
+
case "space":
|
|
308
|
+
this.isSpacePressed || (this.isSpacePressed = !0, t.preventDefault(), this.notifyCursorChange());
|
|
309
|
+
break;
|
|
310
|
+
default: break;
|
|
311
|
+
}
|
|
312
|
+
}
|
|
313
|
+
handleKeyUp(e) {
|
|
314
|
+
e.key === " " && (this.isSpacePressed = !1, this.isDragging = !1, this.notifyCursorChange());
|
|
315
|
+
}
|
|
316
|
+
handleMouseDown(e) {
|
|
317
|
+
this.isSpacePressed && e.button === 0 && (this.isDragging = !0, this.dragStart = {
|
|
318
|
+
x: e.clientX,
|
|
319
|
+
y: e.clientY
|
|
320
|
+
}, this.lastMouse = {
|
|
321
|
+
x: e.clientX,
|
|
322
|
+
y: e.clientY
|
|
323
|
+
}, e.preventDefault(), this.notifyCursorChange());
|
|
324
|
+
}
|
|
325
|
+
async handleMouseMove(e) {
|
|
326
|
+
if (this.isDragging && this.isSpacePressed) {
|
|
327
|
+
let t = e.clientX - this.lastMouse.x, n = e.clientY - this.lastMouse.y;
|
|
328
|
+
(!this.panInterceptor?.beforePan || await this.panInterceptor.beforePan(t, n)) && (this.engine.panBy(t, n), this.panInterceptor?.afterPan?.(t, n)), this.lastMouse = {
|
|
329
|
+
x: e.clientX,
|
|
330
|
+
y: e.clientY
|
|
331
|
+
};
|
|
332
|
+
}
|
|
333
|
+
}
|
|
334
|
+
handleMouseUp() {
|
|
335
|
+
this.isDragging = !1, this.notifyCursorChange();
|
|
336
|
+
}
|
|
337
|
+
async executeZoom(e, t, n, r, i) {
|
|
338
|
+
(!this.zoomInterceptor?.beforeZoom || await this.zoomInterceptor.beforeZoom(t, n, r, i)) && (e(), this.zoomInterceptor?.afterZoom?.(t, this.engine.getState().scale, r, i));
|
|
339
|
+
}
|
|
340
|
+
getCursorClass() {
|
|
341
|
+
return this.isSpacePressed ? this.isDragging ? "grabbing" : "grab" : "default";
|
|
342
|
+
}
|
|
343
|
+
notifyCursorChange() {
|
|
344
|
+
this.onCursorChange?.(this.getCursorClass());
|
|
345
|
+
}
|
|
346
|
+
};
|
|
347
|
+
//#endregion
|
|
348
|
+
export { r as Canvas2DRenderer, c as InputManager, s as KeyboardAdapter, t as LabelCache, o as MouseAdapter, e as OffscreenRulerCache, a as getZoomDelta, i as normalizeWheel };
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
(function(e,t){typeof exports==`object`&&typeof module<`u`?t(exports):typeof define==`function`&&define.amd?define([`exports`],t):(e=typeof globalThis<`u`?globalThis:e||self,t(e.SketchRulerCanvas={}))})(this,function(e){Object.defineProperty(e,Symbol.toStringTag,{value:`Module`});var t=class{canvas=null;ctx=null;lastFingerprint=``;drawStatic(e,t,n){let r=this.buildFingerprint(t,n);return(r!==this.lastFingerprint||!this.canvas)&&(this.rebuild(t,n),this.lastFingerprint=r),this.canvas?(e.drawImage(this.canvas,0,0),!0):!1}rebuild(e,t){let{width:n,height:r,ratio:i,palette:a,thick:o,vertical:s}=t;this.canvas||=document.createElement(`canvas`),this.canvas.width=Math.round(n*i),this.canvas.height=Math.round(r*i);let c=this.canvas.getContext(`2d`);this.ctx=c,c.setTransform(1,0,0,1,0,0),c.scale(i,i),c.clearRect(0,0,n,r),c.fillStyle=a.bgColor,c.fillRect(0,0,n,r),c.strokeStyle=a.tickColor,c.lineWidth=1,c.beginPath();for(let i of e){let e=i.position;i.isMajor?i.value===0||i.value===t.canvasSize?s?(c.moveTo(0,e),c.lineTo(n,e)):(c.moveTo(e,0),c.lineTo(e,r)):s?(c.moveTo(n,e),c.lineTo(n*.65,e)):(c.moveTo(e,r),c.lineTo(e,r*.65)):s?(c.moveTo(n,e),c.lineTo(n*.8,e)):(c.moveTo(e,r),c.lineTo(e,r*.8))}c.stroke(),c.closePath()}buildFingerprint(e,t){let{thick:n,palette:r,vertical:i,width:a,height:o}=t,s=e.length>1?e[1].value-e[0].value:0,c=1,l=0;if(e.length>=2){let t=e[0],n=e.find(e=>e.value!==t.value)??e[1];n&&n.value!==t.value&&(c=(n.position-t.position)/(n.value-t.value),l=t.position-t.value*c)}return`${n}:${r.bgColor}:${r.tickColor}:${r.labelColor}:${i}:${a}:${o}:${s}:${c.toFixed(4)}:${l.toFixed(2)}`}clear(){this.canvas=null,this.ctx=null,this.lastFingerprint=``}},n=class{cache=new Map;maxSize;constructor(e=500){this.maxSize=e}get(e,t){let n=this.hashKey(t),r=this.cache.get(n);if(r)return this.cache.delete(n),this.cache.set(n,r),r;let i=this.createEntry(e,t);return this.cache.set(n,i),this.evictIfNeeded(),i}createEntry(e,t){let{text:n,font:r,color:i}=t;e.font=r;let a=e.measureText(n),o=Math.ceil(a.width),s=Math.ceil(a.actualBoundingBoxAscent+a.actualBoundingBoxDescent)||12;if(o===0||s===0)return{canvas:document.createElement(`canvas`),width:0,height:0};let c=document.createElement(`canvas`);c.width=o+2,c.height=s+2;let l=c.getContext(`2d`);return l.font=r,l.fillStyle=i,l.textBaseline=`alphabetic`,l.fillText(n,1,1+a.actualBoundingBoxAscent),{canvas:c,width:o,height:s}}evictIfNeeded(){if(this.cache.size<=this.maxSize)return;let e=this.cache.keys().next().value;e!==void 0&&this.cache.delete(e)}hashKey(e){return`${e.text}:${e.font}:${e.color}`}clear(){this.cache.clear()}size(){return this.cache.size}},r=`#e9f7fe`,i=class{offscreenCache=new t;labelCache=new n;render(e,t,n){for(let r of t)r.type===`ruler`&&this.renderRuler(e,r,n)}renderRuler(e,t,n){let{marks:i,vertical:a,thick:o,width:s,height:c,ratio:l,palette:u,shadowStart:d,shadowLength:f,showShadowText:p}=t;e.setTransform(1,0,0,1,0,0),e.scale(l,l),e.clearRect(0,0,s,c),this.offscreenCache.drawStatic(e,i,t);let m=[];if(f&&i.length>0){let t=d??0,n=i[0],l=i.find(e=>e.value!==n.value)??n,h=l&&l.value!==n.value?(l.position-n.position)/(l.value-n.value):1,g=n.position-n.value*h,_=t*h+g,v=f*h;if(v>0&&(e.fillStyle=u.shadowColor??r,a?e.fillRect(0,_,s,v):e.fillRect(_,0,v,c),p)){this.renderShadowText(e,t,_,o,a,u,!1);let n=t+f,r=n*h+g;this.renderShadowText(e,n,r,o,a,u,!0),m.push(_,r)}}for(let n of i)if(n.isMajor&&n.label){if(m.some(e=>Math.abs(n.position-e)<o*1.5))continue;e.save(),e.fillStyle=u.labelColor,e.font=`${Math.max(11,Math.floor(o*.5)+2)}px -apple-system, "Helvetica Neue", ".SFNSDisplay-Regular", "Helvetica Neue", Helvetica, "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei", "WenQuanYi Micro Hei", sans-serif`,a?(n.value===0?e.translate(s*.4,n.position-3):n.value===t.canvasSize?e.translate(s*.2,n.position+32):e.translate(s*.15,n.position+14),e.rotate(-90*Math.PI/180),e.fillText(n.label,4,9)):(n.value===0?e.translate(n.position-15,c*.13):n.value===t.canvasSize?e.translate(n.position+5,c*.3):e.translate(n.position-12,c*.18),e.fillText(n.label,4,9)),e.restore()}}renderShadowText(e,t,n,r,i,a,o){e.save(),e.fillStyle=a.labelColor,e.font=`bold 12px sans-serif`,i?(e.translate(r*.6,n+(o?-8:8)),e.rotate(-Math.PI/2)):e.translate(n+(o?-8:8),r*.6),e.fillText(Math.round(t).toString(),0,0),e.restore()}destroy(){this.offscreenCache.clear(),this.labelCache.clear()}};function a(e){let t=e.deltaX,n=e.deltaY,r=e.deltaZ||0,i=e.deltaMode;return i===WheelEvent.DOM_DELTA_LINE?(t*=40,n*=40,r*=40):i===WheelEvent.DOM_DELTA_PAGE&&(t*=800,n*=800,r*=800),{deltaX:t,deltaY:n,deltaZ:r,deltaMode:i}}function o(e,t=.001){let n=a(e);return-(n.deltaY===0?n.deltaX:n.deltaY)*t}var s=class{container;callbacks;boundWheel;boundMouseDown;boundMouseMove;boundMouseUp;boundMouseEnter;boundMouseLeave;isBound=!1;constructor(e,t){this.container=e,this.callbacks=t,this.boundWheel=this.handleWheel.bind(this),this.boundMouseDown=this.handleMouseDown.bind(this),this.boundMouseMove=this.handleMouseMove.bind(this),this.boundMouseUp=this.handleMouseUp.bind(this),this.boundMouseEnter=()=>this.callbacks.onMouseEnter?.(),this.boundMouseLeave=()=>this.callbacks.onMouseLeave?.()}bind(){this.isBound||(this.isBound=!0,this.container.addEventListener(`wheel`,this.boundWheel,{passive:!1}),this.container.addEventListener(`mousedown`,this.boundMouseDown),document.addEventListener(`mousemove`,this.boundMouseMove),document.addEventListener(`mouseup`,this.boundMouseUp),this.container.addEventListener(`mouseenter`,this.boundMouseEnter),this.container.addEventListener(`mouseleave`,this.boundMouseLeave))}unbind(){this.isBound&&(this.isBound=!1,this.container.removeEventListener(`wheel`,this.boundWheel),this.container.removeEventListener(`mousedown`,this.boundMouseDown),document.removeEventListener(`mousemove`,this.boundMouseMove),document.removeEventListener(`mouseup`,this.boundMouseUp),this.container.removeEventListener(`mouseenter`,this.boundMouseEnter),this.container.removeEventListener(`mouseleave`,this.boundMouseLeave))}handleWheel(e){let t=a(e);this.callbacks.onWheel?.(e,t)}handleMouseDown(e){this.callbacks.onMouseDown?.(e)}handleMouseMove(e){this.callbacks.onMouseMove?.(e)}handleMouseUp(e){this.callbacks.onMouseUp?.(e)}},c=class{callbacks;boundKeyDown;isBound=!1;constructor(e){this.callbacks=e,this.boundKeyDown=this.handleKeyDown.bind(this)}bind(){this.isBound||(this.isBound=!0,document.addEventListener(`keydown`,this.boundKeyDown))}unbind(){this.isBound&&(this.isBound=!1,document.removeEventListener(`keydown`,this.boundKeyDown))}handleKeyDown(e){if(e.repeat)return;let t=document.activeElement;if(t?.closest(`.monaco-editor`)||t?.tagName===`INPUT`||t?.tagName===`TEXTAREA`||t?.getAttribute(`contenteditable`)===`true`)return;let n=this.parseCombo(e);this.callbacks.onShortcut?.(n,e)}parseCombo(e){let t=[];(e.ctrlKey||e.metaKey)&&t.push(`ctrl`),e.altKey&&t.push(`alt`),e.shiftKey&&t.push(`shift`);let n=e.key.toLowerCase();return n===` `&&(n=`space`),n===`+`&&(n=`plus`),n===`-`&&(n=`minus`),n===`=`&&(n=`equal`),t.push(n),t.join(`+`)}},l=class{engine;zoomStep;selfHandle;zoomMode;viewportSize;contentSize;container=null;mouseAdapter=null;isSpacePressed=!1;isDragging=!1;dragStart={x:0,y:0};lastMouse={x:0,y:0};isHovered=!1;boundKeyUp;keyboardAdapter=null;pendingWheelDelta=0;wheelRafId=null;onCursorChange=null;zoomInterceptor=null;panInterceptor=null;constructor(e,t={}){this.engine=e,this.zoomStep=t.zoomStep??.25,this.selfHandle=t.selfHandle??!1,this.zoomMode=t.zoomMode??`pointer`,this.viewportSize=t.viewportSize??{width:0,height:0},this.contentSize=t.contentSize??{width:0,height:0},this.onCursorChange=t.onCursorChange??null,this.zoomInterceptor=t.zoomInterceptor??null,this.panInterceptor=t.panInterceptor??null,this.boundKeyUp=this.handleKeyUp.bind(this)}bind(e){if(this.selfHandle)return;this.unbind(),this.container=e;let t=e.parentElement;if(!t)return;document.addEventListener(`keyup`,this.boundKeyUp);let n={onWheel:this.handleWheel.bind(this),onMouseDown:this.handleMouseDown.bind(this),onMouseMove:this.handleMouseMove.bind(this),onMouseUp:this.handleMouseUp.bind(this),onMouseEnter:()=>{this.isHovered=!0},onMouseLeave:()=>{this.isHovered=!1}};this.mouseAdapter=new s(t,n),this.mouseAdapter.bind(),this.keyboardAdapter=new c({onShortcut:this.handleShortcut.bind(this)}),this.keyboardAdapter.bind()}unbind(){this.mouseAdapter?.unbind(),this.mouseAdapter=null,this.keyboardAdapter?.unbind(),this.keyboardAdapter=null,document.removeEventListener(`keyup`,this.boundKeyUp),this.container=null}destroy(){this.wheelRafId!==null&&(cancelAnimationFrame(this.wheelRafId),this.wheelRafId=null),this.unbind()}setZoomMode(e){this.zoomMode=e}handleWheel(e){if(e.ctrlKey||e.metaKey){e.preventDefault();let t=this.container?.parentElement,n=t?t.getBoundingClientRect():new DOMRect(0,0,0,0),r,i;switch(this.zoomMode){case`viewport-center`:r=this.viewportSize.width/2,i=this.viewportSize.height/2;break;case`content-center`:{let e=this.engine.getState();r=e.x+this.contentSize.width*e.scale/2,i=e.y+this.contentSize.height*e.scale/2;break}default:r=e.clientX-n.left,i=e.clientY-n.top;break}let a=e.deltaY===0?e.deltaX:e.deltaY;this.pendingWheelDelta+=a<0?1:-1,this.wheelRafId===null&&(this.wheelRafId=requestAnimationFrame(()=>{if(this.wheelRafId=null,this.pendingWheelDelta===0)return;let e=this.engine.getState().scale,t=e*Math.exp(this.pendingWheelDelta*this.zoomStep/3);this.pendingWheelDelta=0,this.executeZoom(()=>this.engine.zoomTo(t,r,i),e,t,r,i)}))}}handleShortcut(e,t){if(!this.isHovered)return;let n=this.viewportSize.width/2,r=this.viewportSize.height/2,i=e=>{e()};switch(e){case`ctrl+0`:t.preventDefault(),i(()=>this.executeZoom(()=>this.engine.zoomTo(1,n,r),this.engine.getState().scale,1,n,r));break;case`ctrl+minus`:{t.preventDefault();let e=this.engine.getState().scale,a=e-this.zoomStep;i(()=>this.executeZoom(()=>this.engine.zoomBy(-this.zoomStep,n,r),e,a,n,r));break}case`ctrl+equal`:case`ctrl+plus`:{t.preventDefault();let e=this.engine.getState().scale,a=e+this.zoomStep;i(()=>this.executeZoom(()=>this.engine.zoomBy(this.zoomStep,n,r),e,a,n,r));break}case`ctrl+1`:{t.preventDefault();let e=this.viewportSize.width,a=this.viewportSize.height,o=this.contentSize.width,s=this.contentSize.height;if(e>0&&a>0&&o>0&&s>0){let t=e*.9/o,c=a*.9/s,l=Math.min(t,c),u=(e-o*l)/2,d=(a-s*l)/2,f=this.engine.getState().scale;i(()=>this.executeZoom(()=>this.engine.setTransform({scale:l,x:u,y:d}),f,l,n,r))}break}case`space`:this.isSpacePressed||(this.isSpacePressed=!0,t.preventDefault(),this.notifyCursorChange());break;default:break}}handleKeyUp(e){e.key===` `&&(this.isSpacePressed=!1,this.isDragging=!1,this.notifyCursorChange())}handleMouseDown(e){this.isSpacePressed&&e.button===0&&(this.isDragging=!0,this.dragStart={x:e.clientX,y:e.clientY},this.lastMouse={x:e.clientX,y:e.clientY},e.preventDefault(),this.notifyCursorChange())}async handleMouseMove(e){if(this.isDragging&&this.isSpacePressed){let t=e.clientX-this.lastMouse.x,n=e.clientY-this.lastMouse.y;(!this.panInterceptor?.beforePan||await this.panInterceptor.beforePan(t,n))&&(this.engine.panBy(t,n),this.panInterceptor?.afterPan?.(t,n)),this.lastMouse={x:e.clientX,y:e.clientY}}}handleMouseUp(){this.isDragging=!1,this.notifyCursorChange()}async executeZoom(e,t,n,r,i){(!this.zoomInterceptor?.beforeZoom||await this.zoomInterceptor.beforeZoom(t,n,r,i))&&(e(),this.zoomInterceptor?.afterZoom?.(t,this.engine.getState().scale,r,i))}getCursorClass(){return this.isSpacePressed?this.isDragging?`grabbing`:`grab`:`default`}notifyCursorChange(){this.onCursorChange?.(this.getCursorClass())}};e.Canvas2DRenderer=i,e.InputManager=l,e.KeyboardAdapter=c,e.LabelCache=n,e.MouseAdapter=s,e.OffscreenRulerCache=t,e.getZoomDelta=o,e.normalizeWheel=a});
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
export { InputManager, type InputManagerOptions } from './input-manager';
|
|
2
|
+
export { MouseAdapter, type MouseAdapterCallbacks } from './mouse-adapter';
|
|
3
|
+
export { KeyboardAdapter, type KeyboardAdapterCallbacks } from './keyboard-adapter';
|
|
4
|
+
export { normalizeWheel, getZoomDelta, type NormalizedWheel } from './wheel-normalizer';
|
|
5
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/input/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,KAAK,mBAAmB,EAAE,MAAM,iBAAiB,CAAA;AACxE,OAAO,EAAE,YAAY,EAAE,KAAK,qBAAqB,EAAE,MAAM,iBAAiB,CAAA;AAC1E,OAAO,EAAE,eAAe,EAAE,KAAK,wBAAwB,EAAE,MAAM,oBAAoB,CAAA;AACnF,OAAO,EAAE,cAAc,EAAE,YAAY,EAAE,KAAK,eAAe,EAAE,MAAM,oBAAoB,CAAA"}
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import { TransformEngine } from '@sketch-ruler/core';
|
|
2
|
+
export type ZoomMode = 'pointer' | 'viewport-center' | 'content-center';
|
|
3
|
+
export interface ZoomInterceptor {
|
|
4
|
+
beforeZoom?: (from: number, to: number, originX: number, originY: number) => boolean | Promise<boolean>;
|
|
5
|
+
afterZoom?: (from: number, to: number, originX: number, originY: number) => void;
|
|
6
|
+
}
|
|
7
|
+
export interface PanInterceptor {
|
|
8
|
+
beforePan?: (dx: number, dy: number) => boolean | Promise<boolean>;
|
|
9
|
+
afterPan?: (dx: number, dy: number) => void;
|
|
10
|
+
}
|
|
11
|
+
export interface InputManagerOptions {
|
|
12
|
+
/** 缩放步长 */
|
|
13
|
+
zoomStep?: number;
|
|
14
|
+
/** 是否由外部自行处理事件 */
|
|
15
|
+
selfHandle?: boolean;
|
|
16
|
+
/** 缩放原点模式 */
|
|
17
|
+
zoomMode?: ZoomMode;
|
|
18
|
+
/** 视口尺寸(viewport-center / content-center 模式需要) */
|
|
19
|
+
viewportSize?: {
|
|
20
|
+
width: number;
|
|
21
|
+
height: number;
|
|
22
|
+
};
|
|
23
|
+
/** 内容尺寸(content-center 模式需要) */
|
|
24
|
+
contentSize?: {
|
|
25
|
+
width: number;
|
|
26
|
+
height: number;
|
|
27
|
+
};
|
|
28
|
+
/** 光标状态变化回调 */
|
|
29
|
+
onCursorChange?: (cursorClass: string) => void;
|
|
30
|
+
/** 缩放拦截器(用于插件 beforeZoom / afterZoom) */
|
|
31
|
+
zoomInterceptor?: ZoomInterceptor;
|
|
32
|
+
/** 平移拦截器(用于插件 beforePan / afterPan) */
|
|
33
|
+
panInterceptor?: PanInterceptor;
|
|
34
|
+
}
|
|
35
|
+
export declare class InputManager {
|
|
36
|
+
private engine;
|
|
37
|
+
private zoomStep;
|
|
38
|
+
private selfHandle;
|
|
39
|
+
private zoomMode;
|
|
40
|
+
private viewportSize;
|
|
41
|
+
private contentSize;
|
|
42
|
+
private container;
|
|
43
|
+
private mouseAdapter;
|
|
44
|
+
private isSpacePressed;
|
|
45
|
+
private isDragging;
|
|
46
|
+
private dragStart;
|
|
47
|
+
private lastMouse;
|
|
48
|
+
private isHovered;
|
|
49
|
+
private boundKeyUp;
|
|
50
|
+
private keyboardAdapter;
|
|
51
|
+
private pendingWheelDelta;
|
|
52
|
+
private wheelRafId;
|
|
53
|
+
private onCursorChange;
|
|
54
|
+
private zoomInterceptor;
|
|
55
|
+
private panInterceptor;
|
|
56
|
+
constructor(engine: TransformEngine, options?: InputManagerOptions);
|
|
57
|
+
/** 绑定到容器元素 */
|
|
58
|
+
bind(container: HTMLElement): void;
|
|
59
|
+
/** 解绑所有事件 */
|
|
60
|
+
unbind(): void;
|
|
61
|
+
/** 销毁 */
|
|
62
|
+
destroy(): void;
|
|
63
|
+
setZoomMode(mode: ZoomMode): void;
|
|
64
|
+
private handleWheel;
|
|
65
|
+
private handleShortcut;
|
|
66
|
+
private handleKeyUp;
|
|
67
|
+
private handleMouseDown;
|
|
68
|
+
private handleMouseMove;
|
|
69
|
+
private handleMouseUp;
|
|
70
|
+
private executeZoom;
|
|
71
|
+
getCursorClass(): string;
|
|
72
|
+
private notifyCursorChange;
|
|
73
|
+
}
|
|
74
|
+
//# sourceMappingURL=input-manager.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"input-manager.d.ts","sourceRoot":"","sources":["../../src/input/input-manager.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,oBAAoB,CAAA;AAKzD,MAAM,MAAM,QAAQ,GAAG,SAAS,GAAG,iBAAiB,GAAG,gBAAgB,CAAA;AAEvE,MAAM,WAAW,eAAe;IAC9B,UAAU,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,KAAK,OAAO,GAAG,OAAO,CAAC,OAAO,CAAC,CAAA;IACvG,SAAS,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,KAAK,IAAI,CAAA;CACjF;AAED,MAAM,WAAW,cAAc;IAC7B,SAAS,CAAC,EAAE,CAAC,EAAE,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,KAAK,OAAO,GAAG,OAAO,CAAC,OAAO,CAAC,CAAA;IAClE,QAAQ,CAAC,EAAE,CAAC,EAAE,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,KAAK,IAAI,CAAA;CAC5C;AAED,MAAM,WAAW,mBAAmB;IAClC,WAAW;IACX,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,kBAAkB;IAClB,UAAU,CAAC,EAAE,OAAO,CAAA;IACpB,aAAa;IACb,QAAQ,CAAC,EAAE,QAAQ,CAAA;IACnB,kDAAkD;IAClD,YAAY,CAAC,EAAE;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAA;KAAE,CAAA;IAChD,gCAAgC;IAChC,WAAW,CAAC,EAAE;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAA;KAAE,CAAA;IAC/C,eAAe;IACf,cAAc,CAAC,EAAE,CAAC,WAAW,EAAE,MAAM,KAAK,IAAI,CAAA;IAC9C,yCAAyC;IACzC,eAAe,CAAC,EAAE,eAAe,CAAA;IACjC,uCAAuC;IACvC,cAAc,CAAC,EAAE,cAAc,CAAA;CAChC;AAED,qBAAa,YAAY;IACvB,OAAO,CAAC,MAAM,CAAiB;IAC/B,OAAO,CAAC,QAAQ,CAAQ;IACxB,OAAO,CAAC,UAAU,CAAS;IAC3B,OAAO,CAAC,QAAQ,CAAU;IAC1B,OAAO,CAAC,YAAY,CAAmC;IACvD,OAAO,CAAC,WAAW,CAAmC;IAEtD,OAAO,CAAC,SAAS,CAA2B;IAC5C,OAAO,CAAC,YAAY,CAA4B;IAEhD,OAAO,CAAC,cAAc,CAAQ;IAC9B,OAAO,CAAC,UAAU,CAAQ;IAC1B,OAAO,CAAC,SAAS,CAAiB;IAClC,OAAO,CAAC,SAAS,CAAiB;IAClC,OAAO,CAAC,SAAS,CAAQ;IAEzB,OAAO,CAAC,UAAU,CAA4B;IAC9C,OAAO,CAAC,eAAe,CAA+B;IACtD,OAAO,CAAC,iBAAiB,CAAI;IAC7B,OAAO,CAAC,UAAU,CAAsB;IACxC,OAAO,CAAC,cAAc,CAA+C;IACrE,OAAO,CAAC,eAAe,CAA+B;IACtD,OAAO,CAAC,cAAc,CAA8B;gBAExC,MAAM,EAAE,eAAe,EAAE,OAAO,GAAE,mBAAwB;IActE,cAAc;IACd,IAAI,CAAC,SAAS,EAAE,WAAW,GAAG,IAAI;IAmClC,aAAa;IACb,MAAM,IAAI,IAAI;IAYd,SAAS;IACT,OAAO,IAAI,IAAI;IAQf,WAAW,CAAC,IAAI,EAAE,QAAQ,GAAG,IAAI;IAIjC,OAAO,CAAC,WAAW;IAiDnB,OAAO,CAAC,cAAc;IA+FtB,OAAO,CAAC,WAAW;IAQnB,OAAO,CAAC,eAAe;YAUT,eAAe;IAkB7B,OAAO,CAAC,aAAa;YAKP,WAAW;IAiBzB,cAAc,IAAI,MAAM;IAOxB,OAAO,CAAC,kBAAkB;CAG3B"}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* KeyboardAdapter - 键盘事件适配器
|
|
3
|
+
* M3 W11:修饰键状态追踪 + 快捷键映射
|
|
4
|
+
*/
|
|
5
|
+
export type KeyCombo = string;
|
|
6
|
+
export interface KeyboardAdapterCallbacks {
|
|
7
|
+
onShortcut?: (combo: KeyCombo, e: KeyboardEvent) => void;
|
|
8
|
+
}
|
|
9
|
+
export declare class KeyboardAdapter {
|
|
10
|
+
private callbacks;
|
|
11
|
+
private boundKeyDown;
|
|
12
|
+
private isBound;
|
|
13
|
+
constructor(callbacks: KeyboardAdapterCallbacks);
|
|
14
|
+
bind(): void;
|
|
15
|
+
unbind(): void;
|
|
16
|
+
private handleKeyDown;
|
|
17
|
+
private parseCombo;
|
|
18
|
+
}
|
|
19
|
+
//# sourceMappingURL=keyboard-adapter.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"keyboard-adapter.d.ts","sourceRoot":"","sources":["../../src/input/keyboard-adapter.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,MAAM,MAAM,QAAQ,GAAG,MAAM,CAAA;AAE7B,MAAM,WAAW,wBAAwB;IACvC,UAAU,CAAC,EAAE,CAAC,KAAK,EAAE,QAAQ,EAAE,CAAC,EAAE,aAAa,KAAK,IAAI,CAAA;CACzD;AAED,qBAAa,eAAe;IAC1B,OAAO,CAAC,SAAS,CAA0B;IAC3C,OAAO,CAAC,YAAY,CAA4B;IAChD,OAAO,CAAC,OAAO,CAAQ;gBAEX,SAAS,EAAE,wBAAwB;IAK/C,IAAI,IAAI,IAAI;IAMZ,MAAM,IAAI,IAAI;IAMd,OAAO,CAAC,aAAa;IAkBrB,OAAO,CAAC,UAAU;CAgBnB"}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { NormalizedWheel } from './wheel-normalizer';
|
|
2
|
+
export interface MouseAdapterCallbacks {
|
|
3
|
+
onWheel?: (e: WheelEvent, normalized: NormalizedWheel) => void;
|
|
4
|
+
onMouseDown?: (e: MouseEvent) => void;
|
|
5
|
+
onMouseMove?: (e: MouseEvent) => void;
|
|
6
|
+
onMouseUp?: (e: MouseEvent) => void;
|
|
7
|
+
onMouseEnter?: () => void;
|
|
8
|
+
onMouseLeave?: () => void;
|
|
9
|
+
}
|
|
10
|
+
/**
|
|
11
|
+
* MouseAdapter - 鼠标事件封装适配器
|
|
12
|
+
* 统一绑定/解绑生命周期,自动标准化滚轮事件
|
|
13
|
+
*/
|
|
14
|
+
export declare class MouseAdapter {
|
|
15
|
+
private container;
|
|
16
|
+
private callbacks;
|
|
17
|
+
private boundWheel;
|
|
18
|
+
private boundMouseDown;
|
|
19
|
+
private boundMouseMove;
|
|
20
|
+
private boundMouseUp;
|
|
21
|
+
private boundMouseEnter;
|
|
22
|
+
private boundMouseLeave;
|
|
23
|
+
private isBound;
|
|
24
|
+
constructor(container: HTMLElement, callbacks: MouseAdapterCallbacks);
|
|
25
|
+
bind(): void;
|
|
26
|
+
unbind(): void;
|
|
27
|
+
private handleWheel;
|
|
28
|
+
private handleMouseDown;
|
|
29
|
+
private handleMouseMove;
|
|
30
|
+
private handleMouseUp;
|
|
31
|
+
}
|
|
32
|
+
//# sourceMappingURL=mouse-adapter.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"mouse-adapter.d.ts","sourceRoot":"","sources":["../../src/input/mouse-adapter.ts"],"names":[],"mappings":"AAAA,OAAO,EAAkB,KAAK,eAAe,EAAE,MAAM,oBAAoB,CAAA;AAEzE,MAAM,WAAW,qBAAqB;IACpC,OAAO,CAAC,EAAE,CAAC,CAAC,EAAE,UAAU,EAAE,UAAU,EAAE,eAAe,KAAK,IAAI,CAAA;IAC9D,WAAW,CAAC,EAAE,CAAC,CAAC,EAAE,UAAU,KAAK,IAAI,CAAA;IACrC,WAAW,CAAC,EAAE,CAAC,CAAC,EAAE,UAAU,KAAK,IAAI,CAAA;IACrC,SAAS,CAAC,EAAE,CAAC,CAAC,EAAE,UAAU,KAAK,IAAI,CAAA;IACnC,YAAY,CAAC,EAAE,MAAM,IAAI,CAAA;IACzB,YAAY,CAAC,EAAE,MAAM,IAAI,CAAA;CAC1B;AAED;;;GAGG;AACH,qBAAa,YAAY;IACvB,OAAO,CAAC,SAAS,CAAa;IAC9B,OAAO,CAAC,SAAS,CAAuB;IAExC,OAAO,CAAC,UAAU,CAAyB;IAC3C,OAAO,CAAC,cAAc,CAAyB;IAC/C,OAAO,CAAC,cAAc,CAAyB;IAC/C,OAAO,CAAC,YAAY,CAAyB;IAC7C,OAAO,CAAC,eAAe,CAAY;IACnC,OAAO,CAAC,eAAe,CAAY;IAEnC,OAAO,CAAC,OAAO,CAAQ;gBAEX,SAAS,EAAE,WAAW,EAAE,SAAS,EAAE,qBAAqB;IAYpE,IAAI,IAAI,IAAI;IAYZ,MAAM,IAAI,IAAI;IAYd,OAAO,CAAC,WAAW;IAKnB,OAAO,CAAC,eAAe;IAIvB,OAAO,CAAC,eAAe;IAIvB,OAAO,CAAC,aAAa;CAGtB"}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* WheelNormalizer - 统一不同浏览器滚轮事件 delta 值
|
|
3
|
+
* Chrome/Firefox/Safari 的 WheelEvent.deltaY 差异很大,
|
|
4
|
+
* 统一转换为像素级增量,便于上层消费。
|
|
5
|
+
*/
|
|
6
|
+
export interface NormalizedWheel {
|
|
7
|
+
/** 水平方向增量(像素) */
|
|
8
|
+
deltaX: number;
|
|
9
|
+
/** 垂直方向增量(像素) */
|
|
10
|
+
deltaY: number;
|
|
11
|
+
/** Z 轴增量(像素) */
|
|
12
|
+
deltaZ: number;
|
|
13
|
+
/** 原始 deltaMode */
|
|
14
|
+
deltaMode: number;
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* 将浏览器原生 WheelEvent 标准化为像素单位
|
|
18
|
+
*
|
|
19
|
+
* 各浏览器典型值:
|
|
20
|
+
* - Chrome (line mode): deltaY ≈ 100
|
|
21
|
+
* - Firefox (line mode): deltaY ≈ 3
|
|
22
|
+
* - Safari (pixel mode): deltaY ≈ 1
|
|
23
|
+
*/
|
|
24
|
+
export declare function normalizeWheel(event: WheelEvent): NormalizedWheel;
|
|
25
|
+
/**
|
|
26
|
+
* 获取滚轮事件的标准化缩放增量
|
|
27
|
+
* 用于 Ctrl+滚轮缩放场景,返回值已做方向归一化
|
|
28
|
+
*/
|
|
29
|
+
export declare function getZoomDelta(event: WheelEvent, sensitivity?: number): number;
|
|
30
|
+
//# sourceMappingURL=wheel-normalizer.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"wheel-normalizer.d.ts","sourceRoot":"","sources":["../../src/input/wheel-normalizer.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,MAAM,WAAW,eAAe;IAC9B,iBAAiB;IACjB,MAAM,EAAE,MAAM,CAAA;IACd,iBAAiB;IACjB,MAAM,EAAE,MAAM,CAAA;IACd,gBAAgB;IAChB,MAAM,EAAE,MAAM,CAAA;IACd,mBAAmB;IACnB,SAAS,EAAE,MAAM,CAAA;CAClB;AAED;;;;;;;GAOG;AACH,wBAAgB,cAAc,CAAC,KAAK,EAAE,UAAU,GAAG,eAAe,CAoBjE;AAED;;;GAGG;AACH,wBAAgB,YAAY,CAAC,KAAK,EAAE,UAAU,EAAE,WAAW,SAAQ,GAAG,MAAM,CAK3E"}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { Renderer, Rect, RulerRenderPayload } from './types';
|
|
2
|
+
export declare class Canvas2DRenderer implements Renderer {
|
|
3
|
+
private offscreenCache;
|
|
4
|
+
private labelCache;
|
|
5
|
+
render(ctx: CanvasRenderingContext2D, items: RulerRenderPayload[], viewportRect: Rect): void;
|
|
6
|
+
private renderRuler;
|
|
7
|
+
private renderShadowText;
|
|
8
|
+
destroy(): void;
|
|
9
|
+
}
|
|
10
|
+
//# sourceMappingURL=canvas-2d-renderer.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"canvas-2d-renderer.d.ts","sourceRoot":"","sources":["../../src/renderers/canvas-2d-renderer.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,QAAQ,EAAE,IAAI,EAAE,kBAAkB,EAAE,MAAM,SAAS,CAAA;AAQjE,qBAAa,gBAAiB,YAAW,QAAQ;IAC/C,OAAO,CAAC,cAAc,CAA4B;IAClD,OAAO,CAAC,UAAU,CAAmB;IAErC,MAAM,CAAC,GAAG,EAAE,wBAAwB,EAAE,KAAK,EAAE,kBAAkB,EAAE,EAAE,YAAY,EAAE,IAAI,GAAG,IAAI;IAQ5F,OAAO,CAAC,WAAW;IAkGnB,OAAO,CAAC,gBAAgB;IAsBxB,OAAO,IAAI,IAAI;CAIhB"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/renderers/index.ts"],"names":[],"mappings":"AAAA,YAAY,EAAE,QAAQ,EAAE,IAAI,EAAE,kBAAkB,EAAE,UAAU,EAAE,MAAM,SAAS,CAAA;AAC7E,OAAO,EAAE,gBAAgB,EAAE,MAAM,sBAAsB,CAAA"}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* LabelCache - 刻度标签 LRU 缓存池
|
|
3
|
+
* 将 fillText() 栅格化为离屏 Canvas,后续直接 drawImage() 贴图
|
|
4
|
+
* M2 性能优化核心:单标签绘制从 0.5-2ms 降至 0.01-0.05ms
|
|
5
|
+
*/
|
|
6
|
+
export interface LabelCacheKey {
|
|
7
|
+
text: string;
|
|
8
|
+
font: string;
|
|
9
|
+
color: string;
|
|
10
|
+
}
|
|
11
|
+
/** 缓存条目 */
|
|
12
|
+
interface CacheEntry {
|
|
13
|
+
canvas: HTMLCanvasElement;
|
|
14
|
+
width: number;
|
|
15
|
+
height: number;
|
|
16
|
+
}
|
|
17
|
+
export declare class LabelCache {
|
|
18
|
+
private cache;
|
|
19
|
+
private maxSize;
|
|
20
|
+
constructor(maxSize?: number);
|
|
21
|
+
/**
|
|
22
|
+
* 获取缓存的标签 Canvas;未命中则创建并缓存
|
|
23
|
+
*/
|
|
24
|
+
get(ctx: CanvasRenderingContext2D, key: LabelCacheKey): CacheEntry;
|
|
25
|
+
/** 预计算文本尺寸并栅格化 */
|
|
26
|
+
private createEntry;
|
|
27
|
+
/** 淘汰最久未使用的条目 */
|
|
28
|
+
private evictIfNeeded;
|
|
29
|
+
private hashKey;
|
|
30
|
+
/** 清空缓存 */
|
|
31
|
+
clear(): void;
|
|
32
|
+
/** 当前缓存数量 */
|
|
33
|
+
size(): number;
|
|
34
|
+
}
|
|
35
|
+
export {};
|
|
36
|
+
//# sourceMappingURL=label-cache.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"label-cache.d.ts","sourceRoot":"","sources":["../../src/renderers/label-cache.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,MAAM,WAAW,aAAa;IAC5B,IAAI,EAAE,MAAM,CAAA;IACZ,IAAI,EAAE,MAAM,CAAA;IACZ,KAAK,EAAE,MAAM,CAAA;CACd;AAED,WAAW;AACX,UAAU,UAAU;IAClB,MAAM,EAAE,iBAAiB,CAAA;IACzB,KAAK,EAAE,MAAM,CAAA;IACb,MAAM,EAAE,MAAM,CAAA;CACf;AAED,qBAAa,UAAU;IACrB,OAAO,CAAC,KAAK,CAAgC;IAC7C,OAAO,CAAC,OAAO,CAAQ;gBAEX,OAAO,SAAM;IAIzB;;OAEG;IACH,GAAG,CAAC,GAAG,EAAE,wBAAwB,EAAE,GAAG,EAAE,aAAa,GAAG,UAAU;IAgBlE,kBAAkB;IAClB,OAAO,CAAC,WAAW;IA2BnB,iBAAiB;IACjB,OAAO,CAAC,aAAa;IAQrB,OAAO,CAAC,OAAO;IAIf,WAAW;IACX,KAAK,IAAI,IAAI;IAIb,aAAa;IACb,IAAI,IAAI,MAAM;CAGf"}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { ScaleMark } from '@sketch-ruler/core';
|
|
2
|
+
import { RulerRenderPayload } from './types';
|
|
3
|
+
export declare class OffscreenRulerCache {
|
|
4
|
+
private canvas;
|
|
5
|
+
private ctx;
|
|
6
|
+
private lastFingerprint;
|
|
7
|
+
/**
|
|
8
|
+
* 尝试使用离屏缓存绘制静态部分
|
|
9
|
+
* @returns true 表示使用了缓存,false 表示缓存未命中/未初始化
|
|
10
|
+
*/
|
|
11
|
+
drawStatic(targetCtx: CanvasRenderingContext2D, marks: ScaleMark[], payload: RulerRenderPayload): boolean;
|
|
12
|
+
private rebuild;
|
|
13
|
+
private buildFingerprint;
|
|
14
|
+
/** 清空缓存 */
|
|
15
|
+
clear(): void;
|
|
16
|
+
}
|
|
17
|
+
//# sourceMappingURL=offscreen-ruler-cache.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"offscreen-ruler-cache.d.ts","sourceRoot":"","sources":["../../src/renderers/offscreen-ruler-cache.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,oBAAoB,CAAA;AACnD,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,SAAS,CAAA;AAEjD,qBAAa,mBAAmB;IAC9B,OAAO,CAAC,MAAM,CAAiC;IAC/C,OAAO,CAAC,GAAG,CAAwC;IACnD,OAAO,CAAC,eAAe,CAAK;IAE5B;;;OAGG;IACH,UAAU,CACR,SAAS,EAAE,wBAAwB,EACnC,KAAK,EAAE,SAAS,EAAE,EAClB,OAAO,EAAE,kBAAkB,GAC1B,OAAO;IAeV,OAAO,CAAC,OAAO;IAoEf,OAAO,CAAC,gBAAgB;IAoBxB,WAAW;IACX,KAAK,IAAI,IAAI;CAKd"}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { ScaleMark, RulerPalette } from '@sketch-ruler/core';
|
|
2
|
+
/** 视口矩形 */
|
|
3
|
+
export interface Rect {
|
|
4
|
+
x: number;
|
|
5
|
+
y: number;
|
|
6
|
+
width: number;
|
|
7
|
+
height: number;
|
|
8
|
+
}
|
|
9
|
+
/** 标尺渲染载荷 */
|
|
10
|
+
export interface RulerRenderPayload {
|
|
11
|
+
type: 'ruler';
|
|
12
|
+
marks: ScaleMark[];
|
|
13
|
+
vertical: boolean;
|
|
14
|
+
thick: number;
|
|
15
|
+
width: number;
|
|
16
|
+
height: number;
|
|
17
|
+
ratio: number;
|
|
18
|
+
palette: RulerPalette;
|
|
19
|
+
/** 阴影起始位置(世界坐标) */
|
|
20
|
+
shadowStart?: number;
|
|
21
|
+
/** 阴影长度(世界坐标) */
|
|
22
|
+
shadowLength?: number;
|
|
23
|
+
/** 是否显示阴影文字 */
|
|
24
|
+
showShadowText?: boolean;
|
|
25
|
+
/** 画布尺寸(用于阴影文字和边界标注过滤) */
|
|
26
|
+
canvasSize?: number;
|
|
27
|
+
}
|
|
28
|
+
export type RenderItem = RulerRenderPayload;
|
|
29
|
+
/** 渲染器抽象接口 */
|
|
30
|
+
export interface Renderer {
|
|
31
|
+
render(ctx: CanvasRenderingContext2D, items: RenderItem[], viewportRect: Rect): void;
|
|
32
|
+
destroy(): void;
|
|
33
|
+
}
|
|
34
|
+
//# sourceMappingURL=types.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/renderers/types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAA;AAEjE,WAAW;AACX,MAAM,WAAW,IAAI;IACnB,CAAC,EAAE,MAAM,CAAA;IACT,CAAC,EAAE,MAAM,CAAA;IACT,KAAK,EAAE,MAAM,CAAA;IACb,MAAM,EAAE,MAAM,CAAA;CACf;AAED,aAAa;AACb,MAAM,WAAW,kBAAkB;IACjC,IAAI,EAAE,OAAO,CAAA;IACb,KAAK,EAAE,SAAS,EAAE,CAAA;IAClB,QAAQ,EAAE,OAAO,CAAA;IACjB,KAAK,EAAE,MAAM,CAAA;IACb,KAAK,EAAE,MAAM,CAAA;IACb,MAAM,EAAE,MAAM,CAAA;IACd,KAAK,EAAE,MAAM,CAAA;IACb,OAAO,EAAE,YAAY,CAAA;IACrB,mBAAmB;IACnB,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB,iBAAiB;IACjB,YAAY,CAAC,EAAE,MAAM,CAAA;IACrB,eAAe;IACf,cAAc,CAAC,EAAE,OAAO,CAAA;IACxB,0BAA0B;IAC1B,UAAU,CAAC,EAAE,MAAM,CAAA;CACpB;AAED,MAAM,MAAM,UAAU,GAAG,kBAAkB,CAAA;AAE3C,cAAc;AACd,MAAM,WAAW,QAAQ;IACvB,MAAM,CAAC,GAAG,EAAE,wBAAwB,EAAE,KAAK,EAAE,UAAU,EAAE,EAAE,YAAY,EAAE,IAAI,GAAG,IAAI,CAAA;IACpF,OAAO,IAAI,IAAI,CAAA;CAChB"}
|
package/package.json
ADDED
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@sketch-ruler/canvas",
|
|
3
|
+
"version": "3.0.0-beta.0",
|
|
4
|
+
"private": false,
|
|
5
|
+
"description": "Canvas rendering and DOM input management for sketch-ruler. Framework-agnostic, depends on Canvas 2D API and DOM Events.",
|
|
6
|
+
"keywords": [
|
|
7
|
+
"sketch-ruler",
|
|
8
|
+
"ruler",
|
|
9
|
+
"canvas",
|
|
10
|
+
"renderer",
|
|
11
|
+
"input",
|
|
12
|
+
"framework-agnostic"
|
|
13
|
+
],
|
|
14
|
+
"license": "MIT",
|
|
15
|
+
"author": "kakajun <253495832@qq.com>",
|
|
16
|
+
"repository": {
|
|
17
|
+
"type": "git",
|
|
18
|
+
"url": "git+https://github.com/kakajun/vue3-sketch-ruler.git"
|
|
19
|
+
},
|
|
20
|
+
"files": [
|
|
21
|
+
"lib",
|
|
22
|
+
"AGENTS.md"
|
|
23
|
+
],
|
|
24
|
+
"type": "module",
|
|
25
|
+
"sideEffects": false,
|
|
26
|
+
"main": "lib/index.js",
|
|
27
|
+
"module": "lib/index.js",
|
|
28
|
+
"types": "lib/index.d.ts",
|
|
29
|
+
"exports": {
|
|
30
|
+
".": {
|
|
31
|
+
"types": "./lib/index.d.ts",
|
|
32
|
+
"import": "./lib/index.js",
|
|
33
|
+
"require": "./lib/index.cjs"
|
|
34
|
+
},
|
|
35
|
+
"./renderers": {
|
|
36
|
+
"types": "./lib/renderers/index.d.ts",
|
|
37
|
+
"import": "./lib/renderers/index.js",
|
|
38
|
+
"require": "./lib/renderers/index.cjs"
|
|
39
|
+
},
|
|
40
|
+
"./input": {
|
|
41
|
+
"types": "./lib/input/index.d.ts",
|
|
42
|
+
"import": "./lib/input/index.js",
|
|
43
|
+
"require": "./lib/input/index.cjs"
|
|
44
|
+
}
|
|
45
|
+
},
|
|
46
|
+
"dependencies": {
|
|
47
|
+
"@sketch-ruler/core": "workspace:*"
|
|
48
|
+
},
|
|
49
|
+
"scripts": {
|
|
50
|
+
"build": "vite build",
|
|
51
|
+
"test": "vitest run",
|
|
52
|
+
"test:watch": "vitest"
|
|
53
|
+
},
|
|
54
|
+
"devDependencies": {
|
|
55
|
+
"vite": "^8.0.13",
|
|
56
|
+
"vite-plugin-dts": "^5.0.0",
|
|
57
|
+
"vitest": "^4.1.6"
|
|
58
|
+
}
|
|
59
|
+
}
|