kviewer 0.0.9 → 0.0.11
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 +29 -0
- package/dist/module.json +1 -1
- package/dist/module.mjs +19 -0
- package/dist/runtime/annotation/checkbox-styles.d.ts +19 -0
- package/dist/runtime/annotation/checkbox-styles.js +27 -0
- package/dist/runtime/annotation/engine/config.js +44 -0
- package/dist/runtime/annotation/engine/editor/editor.d.ts +6 -3
- package/dist/runtime/annotation/engine/editor/editor.js +22 -9
- package/dist/runtime/annotation/engine/editor/selector.d.ts +9 -1
- package/dist/runtime/annotation/engine/editor/selector.js +72 -30
- package/dist/runtime/annotation/engine/painter.d.ts +6 -2
- package/dist/runtime/annotation/engine/painter.js +42 -5
- package/dist/runtime/annotation/engine/tools/form-field.d.ts +24 -0
- package/dist/runtime/annotation/engine/tools/form-field.js +117 -0
- package/dist/runtime/annotation/engine/types.d.ts +54 -1
- package/dist/runtime/annotation/engine/types.js +4 -0
- package/dist/runtime/annotation/font-style.d.ts +23 -0
- package/dist/runtime/annotation/font-style.js +57 -0
- package/dist/runtime/annotation/parsers/extractCheckboxStyles.d.ts +21 -0
- package/dist/runtime/annotation/parsers/extractCheckboxStyles.js +75 -0
- package/dist/runtime/annotation/parsers/parseFormFields.js +16 -3
- package/dist/runtime/annotation/pdf-export/export-form-fields.d.ts +8 -5
- package/dist/runtime/annotation/pdf-export/export-form-fields.js +245 -1
- package/dist/runtime/annotation/pdf-export/export.d.ts +3 -2
- package/dist/runtime/annotation/pdf-export/export.js +9 -2
- package/dist/runtime/assets/kviewer.css +1 -1
- package/dist/runtime/components/FormFieldLayer.d.vue.ts +5 -0
- package/dist/runtime/components/FormFieldLayer.vue +40 -3
- package/dist/runtime/components/FormFieldLayer.vue.d.ts +5 -0
- package/dist/runtime/components/PdfPage.d.vue.ts +6 -1
- package/dist/runtime/components/PdfPage.vue +75 -18
- package/dist/runtime/components/PdfPage.vue.d.ts +6 -1
- package/dist/runtime/components/ToolButton.d.vue.ts +4 -2
- package/dist/runtime/components/ToolButton.vue.d.ts +4 -2
- package/dist/runtime/components/Viewer.d.vue.ts +18 -3
- package/dist/runtime/components/Viewer.vue +278 -170
- package/dist/runtime/components/Viewer.vue.d.ts +18 -3
- package/dist/runtime/components/ViewerBar.vue +24 -2
- package/dist/runtime/components/form-fields/FormButton.vue +16 -4
- package/dist/runtime/components/form-fields/FormCheckbox.vue +31 -15
- package/dist/runtime/components/form-fields/FormDropdown.vue +3 -1
- package/dist/runtime/components/form-fields/FormFieldWrapper.d.vue.ts +12 -1
- package/dist/runtime/components/form-fields/FormFieldWrapper.vue +45 -9
- package/dist/runtime/components/form-fields/FormFieldWrapper.vue.d.ts +12 -1
- package/dist/runtime/components/form-fields/FormRadioButton.vue +3 -1
- package/dist/runtime/components/form-fields/FormSignatureField.vue +2 -1
- package/dist/runtime/components/form-fields/FormTextField.vue +47 -7
- package/dist/runtime/components/form-fields/PlacedFieldChrome.d.vue.ts +16 -0
- package/dist/runtime/components/form-fields/PlacedFieldChrome.vue +131 -0
- package/dist/runtime/components/form-fields/PlacedFieldChrome.vue.d.ts +16 -0
- package/dist/runtime/components/modals/FreeTextModal.vue +27 -3
- package/dist/runtime/components/modals/SignatureDrawModal.vue +70 -15
- package/dist/runtime/components/panels/PlacedFieldSidebar.d.vue.ts +3 -0
- package/dist/runtime/components/panels/PlacedFieldSidebar.vue +505 -0
- package/dist/runtime/components/panels/PlacedFieldSidebar.vue.d.ts +3 -0
- package/dist/runtime/components/tools/ActionTools.vue +4 -5
- package/dist/runtime/components/tools/FormFieldTools.d.vue.ts +3 -0
- package/dist/runtime/components/tools/FormFieldTools.vue +35 -0
- package/dist/runtime/components/tools/FormFieldTools.vue.d.ts +3 -0
- package/dist/runtime/composables/useAnnotationEngine.d.ts +2 -0
- package/dist/runtime/composables/useAnnotationEngine.js +6 -2
- package/dist/runtime/composables/useAnnotationHistory.d.ts +1 -0
- package/dist/runtime/composables/useAnnotationHistory.js +10 -3
- package/dist/runtime/composables/useFormFields.d.ts +29 -2
- package/dist/runtime/composables/useFormFields.js +259 -5
- package/dist/runtime/composables/useInertiaPanzoom.d.ts +40 -0
- package/dist/runtime/composables/useInertiaPanzoom.js +475 -0
- package/dist/runtime/composables/usePageVirtualization.d.ts +9 -0
- package/dist/runtime/composables/usePageVirtualization.js +34 -3
- package/dist/runtime/composables/useViewerSearch.d.ts +1 -0
- package/dist/runtime/composables/useViewerSearch.js +15 -5
- package/dist/runtime/composables/useViewerState.d.ts +3 -0
- package/dist/runtime/composables/useViewerState.js +22 -1
- package/package.json +2 -2
|
@@ -0,0 +1,475 @@
|
|
|
1
|
+
import { onBeforeUnmount } from "vue";
|
|
2
|
+
const DECELERATION = 0.998;
|
|
3
|
+
const RUBBER_BAND_C = 0.55;
|
|
4
|
+
const BOUNCE_BACK_RATE = 0.04;
|
|
5
|
+
const BOUNCE_VELOCITY_DECAY = 0.04;
|
|
6
|
+
const MIN_VELOCITY = 0.01;
|
|
7
|
+
export function useInertiaPanzoom(options = {}) {
|
|
8
|
+
const {
|
|
9
|
+
minScale = 0.5,
|
|
10
|
+
maxScale = 4,
|
|
11
|
+
axisLockThreshold = 10,
|
|
12
|
+
axisLockRatio = 2
|
|
13
|
+
} = options;
|
|
14
|
+
let controller = null;
|
|
15
|
+
function attach(target) {
|
|
16
|
+
detach();
|
|
17
|
+
const parent = target.parentElement;
|
|
18
|
+
if (!parent) return;
|
|
19
|
+
let scale = 1;
|
|
20
|
+
let tx = 0;
|
|
21
|
+
let ty = 0;
|
|
22
|
+
let gesturesEnabled = true;
|
|
23
|
+
const pointers = /* @__PURE__ */ new Map();
|
|
24
|
+
let pinchStart = null;
|
|
25
|
+
let panStart = null;
|
|
26
|
+
let lastMoveTime = 0;
|
|
27
|
+
let lastMoveX = 0;
|
|
28
|
+
let lastMoveY = 0;
|
|
29
|
+
let vx = 0;
|
|
30
|
+
let vy = 0;
|
|
31
|
+
let momentumRaf = 0;
|
|
32
|
+
let lastStepTime = 0;
|
|
33
|
+
const subscribers = /* @__PURE__ */ new Set();
|
|
34
|
+
target.style.transformOrigin = "0 0";
|
|
35
|
+
target.style.touchAction = "none";
|
|
36
|
+
parent.style.touchAction = "none";
|
|
37
|
+
target.style.userSelect = "none";
|
|
38
|
+
let willChangeActive = false;
|
|
39
|
+
function setWillChangeActive(active) {
|
|
40
|
+
if (willChangeActive === active) return;
|
|
41
|
+
willChangeActive = active;
|
|
42
|
+
target.style.willChange = active ? "transform" : "";
|
|
43
|
+
}
|
|
44
|
+
const BAR_SIZE = 10;
|
|
45
|
+
const vBar = document.createElement("div");
|
|
46
|
+
vBar.setAttribute("data-inertia-scrollbar", "y");
|
|
47
|
+
vBar.style.cssText = `position:absolute;right:2px;top:0;width:${BAR_SIZE}px;background:rgba(0,0,0,0.35);border-radius:${BAR_SIZE / 2}px;opacity:0;transition:opacity 300ms,background-color 150ms;z-index:10;touch-action:none;cursor:grab;`;
|
|
48
|
+
const hBar = document.createElement("div");
|
|
49
|
+
hBar.setAttribute("data-inertia-scrollbar", "x");
|
|
50
|
+
hBar.style.cssText = `position:absolute;bottom:2px;left:0;height:${BAR_SIZE}px;background:rgba(0,0,0,0.35);border-radius:${BAR_SIZE / 2}px;opacity:0;transition:opacity 300ms,background-color 150ms;z-index:10;touch-action:none;cursor:grab;`;
|
|
51
|
+
parent.appendChild(vBar);
|
|
52
|
+
parent.appendChild(hBar);
|
|
53
|
+
let hideTimer = 0;
|
|
54
|
+
let barDrag = null;
|
|
55
|
+
function updateIndicators() {
|
|
56
|
+
const pw = parent.clientWidth;
|
|
57
|
+
const ph = parent.clientHeight;
|
|
58
|
+
const cw = target.scrollWidth * scale;
|
|
59
|
+
const ch = target.scrollHeight * scale;
|
|
60
|
+
if (ch > ph) {
|
|
61
|
+
const thumbH = Math.max(30, ph / ch * ph);
|
|
62
|
+
const scrollable = ch - ph;
|
|
63
|
+
const st = Math.max(0, Math.min(scrollable, -ty));
|
|
64
|
+
vBar.style.height = `${thumbH}px`;
|
|
65
|
+
vBar.style.top = `${st / scrollable * (ph - thumbH)}px`;
|
|
66
|
+
vBar.style.opacity = "1";
|
|
67
|
+
vBar.style.display = "";
|
|
68
|
+
} else {
|
|
69
|
+
vBar.style.opacity = "0";
|
|
70
|
+
vBar.style.display = "none";
|
|
71
|
+
}
|
|
72
|
+
if (cw > pw) {
|
|
73
|
+
const thumbW = Math.max(30, pw / cw * pw);
|
|
74
|
+
const scrollable = cw - pw;
|
|
75
|
+
const sl = Math.max(0, Math.min(scrollable, -tx));
|
|
76
|
+
hBar.style.width = `${thumbW}px`;
|
|
77
|
+
hBar.style.left = `${sl / scrollable * (pw - thumbW)}px`;
|
|
78
|
+
hBar.style.opacity = "1";
|
|
79
|
+
hBar.style.display = "";
|
|
80
|
+
} else {
|
|
81
|
+
hBar.style.opacity = "0";
|
|
82
|
+
hBar.style.display = "none";
|
|
83
|
+
}
|
|
84
|
+
if (barDrag) return;
|
|
85
|
+
clearTimeout(hideTimer);
|
|
86
|
+
hideTimer = window.setTimeout(() => {
|
|
87
|
+
vBar.style.opacity = "0";
|
|
88
|
+
hBar.style.opacity = "0";
|
|
89
|
+
}, 800);
|
|
90
|
+
}
|
|
91
|
+
function onBarDown(axis) {
|
|
92
|
+
return (e) => {
|
|
93
|
+
e.stopPropagation();
|
|
94
|
+
e.preventDefault();
|
|
95
|
+
stopMomentum();
|
|
96
|
+
const pw = parent.clientWidth;
|
|
97
|
+
const ph = parent.clientHeight;
|
|
98
|
+
const cw = target.scrollWidth * scale;
|
|
99
|
+
const ch = target.scrollHeight * scale;
|
|
100
|
+
if (axis === "y") {
|
|
101
|
+
if (ch <= ph) return;
|
|
102
|
+
const thumbH = Math.max(30, ph / ch * ph);
|
|
103
|
+
const trackH = ph - thumbH;
|
|
104
|
+
const scrollable = ch - ph;
|
|
105
|
+
barDrag = {
|
|
106
|
+
axis: "y",
|
|
107
|
+
startPointer: e.clientY,
|
|
108
|
+
startT: ty,
|
|
109
|
+
ratio: scrollable / trackH,
|
|
110
|
+
pointerId: e.pointerId
|
|
111
|
+
};
|
|
112
|
+
vBar.style.background = "rgba(0,0,0,0.55)";
|
|
113
|
+
} else {
|
|
114
|
+
if (cw <= pw) return;
|
|
115
|
+
const thumbW = Math.max(30, pw / cw * pw);
|
|
116
|
+
const trackW = pw - thumbW;
|
|
117
|
+
const scrollable = cw - pw;
|
|
118
|
+
barDrag = {
|
|
119
|
+
axis: "x",
|
|
120
|
+
startPointer: e.clientX,
|
|
121
|
+
startT: tx,
|
|
122
|
+
ratio: scrollable / trackW,
|
|
123
|
+
pointerId: e.pointerId
|
|
124
|
+
};
|
|
125
|
+
hBar.style.background = "rgba(0,0,0,0.55)";
|
|
126
|
+
}
|
|
127
|
+
e.target.setPointerCapture?.(e.pointerId);
|
|
128
|
+
};
|
|
129
|
+
}
|
|
130
|
+
function onBarMove(e) {
|
|
131
|
+
if (!barDrag || e.pointerId !== barDrag.pointerId) return;
|
|
132
|
+
e.stopPropagation();
|
|
133
|
+
if (barDrag.axis === "y") {
|
|
134
|
+
const dy = e.clientY - barDrag.startPointer;
|
|
135
|
+
const b = getBounds();
|
|
136
|
+
ty = Math.max(b.minY, Math.min(b.maxY, barDrag.startT - dy * barDrag.ratio));
|
|
137
|
+
} else {
|
|
138
|
+
const dx = e.clientX - barDrag.startPointer;
|
|
139
|
+
const b = getBounds();
|
|
140
|
+
tx = Math.max(b.minX, Math.min(b.maxX, barDrag.startT - dx * barDrag.ratio));
|
|
141
|
+
}
|
|
142
|
+
apply();
|
|
143
|
+
}
|
|
144
|
+
function onBarUp(e) {
|
|
145
|
+
if (!barDrag || e.pointerId !== barDrag.pointerId) return;
|
|
146
|
+
e.stopPropagation();
|
|
147
|
+
vBar.style.background = "rgba(0,0,0,0.35)";
|
|
148
|
+
hBar.style.background = "rgba(0,0,0,0.35)";
|
|
149
|
+
barDrag = null;
|
|
150
|
+
updateIndicators();
|
|
151
|
+
}
|
|
152
|
+
vBar.addEventListener("pointerdown", onBarDown("y"));
|
|
153
|
+
hBar.addEventListener("pointerdown", onBarDown("x"));
|
|
154
|
+
vBar.addEventListener("pointermove", onBarMove);
|
|
155
|
+
hBar.addEventListener("pointermove", onBarMove);
|
|
156
|
+
vBar.addEventListener("pointerup", onBarUp);
|
|
157
|
+
hBar.addEventListener("pointerup", onBarUp);
|
|
158
|
+
vBar.addEventListener("pointercancel", onBarUp);
|
|
159
|
+
hBar.addEventListener("pointercancel", onBarUp);
|
|
160
|
+
function apply() {
|
|
161
|
+
target.style.transform = `translate(${tx}px, ${ty}px) scale(${scale})`;
|
|
162
|
+
updateIndicators();
|
|
163
|
+
if (subscribers.size > 0) {
|
|
164
|
+
const t = { scale, tx, ty };
|
|
165
|
+
subscribers.forEach((cb) => cb(t));
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
function scrollToElement(elem, opts) {
|
|
169
|
+
const block = opts?.block ?? "start";
|
|
170
|
+
const animate = opts?.animate ?? true;
|
|
171
|
+
const elemRect = elem.getBoundingClientRect();
|
|
172
|
+
const parentRect = parent.getBoundingClientRect();
|
|
173
|
+
const targetOffset = block === "center" ? (parentRect.height - elemRect.height) / 2 : 0;
|
|
174
|
+
const delta = elemRect.top - parentRect.top - targetOffset;
|
|
175
|
+
let newTy = ty - delta;
|
|
176
|
+
const b = getBounds();
|
|
177
|
+
newTy = Math.max(b.minY, Math.min(b.maxY, newTy));
|
|
178
|
+
stopMomentum();
|
|
179
|
+
if (!animate) {
|
|
180
|
+
ty = newTy;
|
|
181
|
+
apply();
|
|
182
|
+
return;
|
|
183
|
+
}
|
|
184
|
+
const startTy = ty;
|
|
185
|
+
const startTime = performance.now();
|
|
186
|
+
const duration = Math.min(600, 200 + Math.abs(newTy - startTy) * 0.5);
|
|
187
|
+
setWillChangeActive(true);
|
|
188
|
+
function frame() {
|
|
189
|
+
const t = Math.min(1, (performance.now() - startTime) / duration);
|
|
190
|
+
const eased = 1 - Math.pow(1 - t, 3);
|
|
191
|
+
ty = startTy + (newTy - startTy) * eased;
|
|
192
|
+
apply();
|
|
193
|
+
if (t < 1) {
|
|
194
|
+
momentumRaf = requestAnimationFrame(frame);
|
|
195
|
+
} else {
|
|
196
|
+
momentumRaf = 0;
|
|
197
|
+
setWillChangeActive(false);
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
momentumRaf = requestAnimationFrame(frame);
|
|
201
|
+
}
|
|
202
|
+
function getBounds() {
|
|
203
|
+
const pw = parent.clientWidth;
|
|
204
|
+
const ph = parent.clientHeight;
|
|
205
|
+
const cw = target.scrollWidth * scale;
|
|
206
|
+
const ch = target.scrollHeight * scale;
|
|
207
|
+
let minX, maxX, minY, maxY;
|
|
208
|
+
if (cw <= pw) {
|
|
209
|
+
minX = maxX = (pw - cw) / 2;
|
|
210
|
+
} else {
|
|
211
|
+
minX = pw - cw;
|
|
212
|
+
maxX = 0;
|
|
213
|
+
}
|
|
214
|
+
if (ch <= ph) {
|
|
215
|
+
minY = maxY = (ph - ch) / 2;
|
|
216
|
+
} else {
|
|
217
|
+
minY = ph - ch;
|
|
218
|
+
maxY = 0;
|
|
219
|
+
}
|
|
220
|
+
return { minX, maxX, minY, maxY };
|
|
221
|
+
}
|
|
222
|
+
function rubberBandAxis(v, min, max, dim) {
|
|
223
|
+
if (v < min) return min - (1 - 1 / ((min - v) * RUBBER_BAND_C / dim + 1)) * dim;
|
|
224
|
+
if (v > max) return max + (1 - 1 / ((v - max) * RUBBER_BAND_C / dim + 1)) * dim;
|
|
225
|
+
return v;
|
|
226
|
+
}
|
|
227
|
+
function stopMomentum() {
|
|
228
|
+
if (momentumRaf) {
|
|
229
|
+
cancelAnimationFrame(momentumRaf);
|
|
230
|
+
momentumRaf = 0;
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
function startMomentum() {
|
|
234
|
+
stopMomentum();
|
|
235
|
+
lastStepTime = performance.now();
|
|
236
|
+
function step() {
|
|
237
|
+
const now = performance.now();
|
|
238
|
+
const dt = Math.min(32, now - lastStepTime);
|
|
239
|
+
lastStepTime = now;
|
|
240
|
+
const b = getBounds();
|
|
241
|
+
if (tx < b.minX) {
|
|
242
|
+
tx += (b.minX - tx) * (1 - Math.exp(-BOUNCE_BACK_RATE * dt));
|
|
243
|
+
vx *= Math.exp(-BOUNCE_VELOCITY_DECAY * dt);
|
|
244
|
+
} else if (tx > b.maxX) {
|
|
245
|
+
tx += (b.maxX - tx) * (1 - Math.exp(-BOUNCE_BACK_RATE * dt));
|
|
246
|
+
vx *= Math.exp(-BOUNCE_VELOCITY_DECAY * dt);
|
|
247
|
+
} else {
|
|
248
|
+
vx *= Math.pow(DECELERATION, dt);
|
|
249
|
+
tx += vx * dt;
|
|
250
|
+
}
|
|
251
|
+
if (ty < b.minY) {
|
|
252
|
+
ty += (b.minY - ty) * (1 - Math.exp(-BOUNCE_BACK_RATE * dt));
|
|
253
|
+
vy *= Math.exp(-BOUNCE_VELOCITY_DECAY * dt);
|
|
254
|
+
} else if (ty > b.maxY) {
|
|
255
|
+
ty += (b.maxY - ty) * (1 - Math.exp(-BOUNCE_BACK_RATE * dt));
|
|
256
|
+
vy *= Math.exp(-BOUNCE_VELOCITY_DECAY * dt);
|
|
257
|
+
} else {
|
|
258
|
+
vy *= Math.pow(DECELERATION, dt);
|
|
259
|
+
ty += vy * dt;
|
|
260
|
+
}
|
|
261
|
+
apply();
|
|
262
|
+
const stopped = Math.abs(vx) < MIN_VELOCITY && Math.abs(vy) < MIN_VELOCITY;
|
|
263
|
+
const inBounds = tx >= b.minX - 0.5 && tx <= b.maxX + 0.5 && ty >= b.minY - 0.5 && ty <= b.maxY + 0.5;
|
|
264
|
+
if (stopped && inBounds) {
|
|
265
|
+
tx = Math.max(b.minX, Math.min(b.maxX, tx));
|
|
266
|
+
ty = Math.max(b.minY, Math.min(b.maxY, ty));
|
|
267
|
+
vx = vy = 0;
|
|
268
|
+
apply();
|
|
269
|
+
momentumRaf = 0;
|
|
270
|
+
setWillChangeActive(false);
|
|
271
|
+
return;
|
|
272
|
+
}
|
|
273
|
+
momentumRaf = requestAnimationFrame(step);
|
|
274
|
+
}
|
|
275
|
+
momentumRaf = requestAnimationFrame(step);
|
|
276
|
+
}
|
|
277
|
+
function onPenFilter(e) {
|
|
278
|
+
if (e instanceof PointerEvent && e.pointerType === "pen") {
|
|
279
|
+
e.stopPropagation();
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
function onDown(e) {
|
|
283
|
+
if (e.pointerType === "pen") return;
|
|
284
|
+
if (!gesturesEnabled) return;
|
|
285
|
+
stopMomentum();
|
|
286
|
+
setWillChangeActive(true);
|
|
287
|
+
pointers.set(e.pointerId, { x: e.clientX, y: e.clientY });
|
|
288
|
+
if (pointers.size === 2) {
|
|
289
|
+
const pts = [...pointers.values()];
|
|
290
|
+
pinchStart = {
|
|
291
|
+
dist: Math.hypot(pts[0].x - pts[1].x, pts[0].y - pts[1].y),
|
|
292
|
+
midX: (pts[0].x + pts[1].x) / 2,
|
|
293
|
+
midY: (pts[0].y + pts[1].y) / 2,
|
|
294
|
+
s: scale,
|
|
295
|
+
tx,
|
|
296
|
+
ty
|
|
297
|
+
};
|
|
298
|
+
panStart = null;
|
|
299
|
+
} else if (pointers.size === 1) {
|
|
300
|
+
panStart = { x: e.clientX, y: e.clientY, tx, ty, axisLock: "none" };
|
|
301
|
+
lastMoveTime = performance.now();
|
|
302
|
+
lastMoveX = e.clientX;
|
|
303
|
+
lastMoveY = e.clientY;
|
|
304
|
+
vx = vy = 0;
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
function onMove(e) {
|
|
308
|
+
if (!pointers.has(e.pointerId)) return;
|
|
309
|
+
pointers.set(e.pointerId, { x: e.clientX, y: e.clientY });
|
|
310
|
+
if (pointers.size === 2 && pinchStart) {
|
|
311
|
+
e.preventDefault();
|
|
312
|
+
const pts = [...pointers.values()];
|
|
313
|
+
const dist = Math.hypot(pts[0].x - pts[1].x, pts[0].y - pts[1].y);
|
|
314
|
+
const midX = (pts[0].x + pts[1].x) / 2;
|
|
315
|
+
const midY = (pts[0].y + pts[1].y) / 2;
|
|
316
|
+
const newScale = Math.max(minScale, Math.min(maxScale, pinchStart.s * (dist / pinchStart.dist)));
|
|
317
|
+
const rect = parent.getBoundingClientRect();
|
|
318
|
+
const focalX = (pinchStart.midX - rect.left - pinchStart.tx) / pinchStart.s;
|
|
319
|
+
const focalY = (pinchStart.midY - rect.top - pinchStart.ty) / pinchStart.s;
|
|
320
|
+
tx = midX - rect.left - newScale * focalX;
|
|
321
|
+
ty = midY - rect.top - newScale * focalY;
|
|
322
|
+
scale = newScale;
|
|
323
|
+
apply();
|
|
324
|
+
} else if (pointers.size === 1 && panStart) {
|
|
325
|
+
e.preventDefault();
|
|
326
|
+
const dx = e.clientX - panStart.x;
|
|
327
|
+
const dy = e.clientY - panStart.y;
|
|
328
|
+
if (panStart.axisLock === "none") {
|
|
329
|
+
const absDx = Math.abs(dx);
|
|
330
|
+
const absDy = Math.abs(dy);
|
|
331
|
+
if (Math.max(absDx, absDy) >= axisLockThreshold) {
|
|
332
|
+
panStart.axisLock = absDy > absDx * axisLockRatio ? "y" : "free";
|
|
333
|
+
}
|
|
334
|
+
}
|
|
335
|
+
const rawTx = panStart.axisLock === "y" ? panStart.tx : panStart.tx + dx;
|
|
336
|
+
const rawTy = panStart.ty + dy;
|
|
337
|
+
const b = getBounds();
|
|
338
|
+
tx = rubberBandAxis(rawTx, b.minX, b.maxX, parent.clientWidth);
|
|
339
|
+
ty = rubberBandAxis(rawTy, b.minY, b.maxY, parent.clientHeight);
|
|
340
|
+
apply();
|
|
341
|
+
const now = performance.now();
|
|
342
|
+
const dt = Math.max(1, now - lastMoveTime);
|
|
343
|
+
const instVx = (e.clientX - lastMoveX) / dt;
|
|
344
|
+
const instVy = (e.clientY - lastMoveY) / dt;
|
|
345
|
+
const alpha = 0.85;
|
|
346
|
+
vx = vx * (1 - alpha) + instVx * alpha;
|
|
347
|
+
vy = vy * (1 - alpha) + instVy * alpha;
|
|
348
|
+
lastMoveTime = now;
|
|
349
|
+
lastMoveX = e.clientX;
|
|
350
|
+
lastMoveY = e.clientY;
|
|
351
|
+
}
|
|
352
|
+
}
|
|
353
|
+
function onUp(e) {
|
|
354
|
+
pointers.delete(e.pointerId);
|
|
355
|
+
if (pointers.size < 2) pinchStart = null;
|
|
356
|
+
if (pointers.size === 0) {
|
|
357
|
+
const hadPan = panStart !== null;
|
|
358
|
+
const lockedAxis = panStart?.axisLock;
|
|
359
|
+
panStart = null;
|
|
360
|
+
const idleMs = performance.now() - lastMoveTime;
|
|
361
|
+
if (idleMs > 60) {
|
|
362
|
+
vx = 0;
|
|
363
|
+
vy = 0;
|
|
364
|
+
}
|
|
365
|
+
if (hadPan && lockedAxis === "y") vx = 0;
|
|
366
|
+
startMomentum();
|
|
367
|
+
}
|
|
368
|
+
}
|
|
369
|
+
function onWheelZoom(e) {
|
|
370
|
+
if (!e.ctrlKey && !e.metaKey) return;
|
|
371
|
+
e.preventDefault();
|
|
372
|
+
stopMomentum();
|
|
373
|
+
const rect = parent.getBoundingClientRect();
|
|
374
|
+
const mx = e.clientX - rect.left;
|
|
375
|
+
const my = e.clientY - rect.top;
|
|
376
|
+
const delta = e.deltaY < 0 ? 1 : -1;
|
|
377
|
+
const newScale = Math.max(minScale, Math.min(maxScale, scale * Math.exp(delta * 0.1)));
|
|
378
|
+
const focalX = (mx - tx) / scale;
|
|
379
|
+
const focalY = (my - ty) / scale;
|
|
380
|
+
tx = mx - newScale * focalX;
|
|
381
|
+
ty = my - newScale * focalY;
|
|
382
|
+
scale = newScale;
|
|
383
|
+
apply();
|
|
384
|
+
}
|
|
385
|
+
function scrollBy(dx, dy) {
|
|
386
|
+
stopMomentum();
|
|
387
|
+
const b = getBounds();
|
|
388
|
+
tx = Math.max(b.minX, Math.min(b.maxX, tx - dx));
|
|
389
|
+
ty = Math.max(b.minY, Math.min(b.maxY, ty - dy));
|
|
390
|
+
apply();
|
|
391
|
+
}
|
|
392
|
+
parent.addEventListener("pointerdown", onDown);
|
|
393
|
+
parent.addEventListener("pointermove", onMove);
|
|
394
|
+
parent.addEventListener("pointerup", onUp);
|
|
395
|
+
parent.addEventListener("pointercancel", onUp);
|
|
396
|
+
parent.addEventListener("wheel", onWheelZoom, { passive: false });
|
|
397
|
+
target.addEventListener("pointerdown", onPenFilter);
|
|
398
|
+
apply();
|
|
399
|
+
controller = {
|
|
400
|
+
cleanup: () => {
|
|
401
|
+
stopMomentum();
|
|
402
|
+
clearTimeout(hideTimer);
|
|
403
|
+
parent.removeEventListener("pointerdown", onDown);
|
|
404
|
+
parent.removeEventListener("pointermove", onMove);
|
|
405
|
+
parent.removeEventListener("pointerup", onUp);
|
|
406
|
+
parent.removeEventListener("pointercancel", onUp);
|
|
407
|
+
parent.removeEventListener("wheel", onWheelZoom);
|
|
408
|
+
target.removeEventListener("pointerdown", onPenFilter);
|
|
409
|
+
vBar.remove();
|
|
410
|
+
hBar.remove();
|
|
411
|
+
target.style.transform = "";
|
|
412
|
+
target.style.transformOrigin = "";
|
|
413
|
+
target.style.willChange = "";
|
|
414
|
+
target.style.touchAction = "";
|
|
415
|
+
target.style.userSelect = "";
|
|
416
|
+
parent.style.touchAction = "";
|
|
417
|
+
},
|
|
418
|
+
reset: () => {
|
|
419
|
+
stopMomentum();
|
|
420
|
+
scale = 1;
|
|
421
|
+
tx = 0;
|
|
422
|
+
ty = 0;
|
|
423
|
+
vx = vy = 0;
|
|
424
|
+
pointers.clear();
|
|
425
|
+
pinchStart = null;
|
|
426
|
+
panStart = null;
|
|
427
|
+
setWillChangeActive(false);
|
|
428
|
+
const b = getBounds();
|
|
429
|
+
tx = Math.max(b.minX, Math.min(b.maxX, tx));
|
|
430
|
+
ty = Math.max(b.minY, Math.min(b.maxY, ty));
|
|
431
|
+
apply();
|
|
432
|
+
},
|
|
433
|
+
stop: () => {
|
|
434
|
+
stopMomentum();
|
|
435
|
+
setWillChangeActive(false);
|
|
436
|
+
},
|
|
437
|
+
getScale: () => scale,
|
|
438
|
+
getTransform: () => ({ scale, tx, ty }),
|
|
439
|
+
scrollToElement,
|
|
440
|
+
scrollBy,
|
|
441
|
+
setGesturesEnabled: (enabled) => {
|
|
442
|
+
gesturesEnabled = enabled;
|
|
443
|
+
if (!enabled) {
|
|
444
|
+
pointers.clear();
|
|
445
|
+
panStart = null;
|
|
446
|
+
pinchStart = null;
|
|
447
|
+
stopMomentum();
|
|
448
|
+
setWillChangeActive(false);
|
|
449
|
+
}
|
|
450
|
+
},
|
|
451
|
+
onChange: (cb) => {
|
|
452
|
+
subscribers.add(cb);
|
|
453
|
+
return () => subscribers.delete(cb);
|
|
454
|
+
}
|
|
455
|
+
};
|
|
456
|
+
}
|
|
457
|
+
function detach() {
|
|
458
|
+
controller?.cleanup();
|
|
459
|
+
controller = null;
|
|
460
|
+
}
|
|
461
|
+
onBeforeUnmount(detach);
|
|
462
|
+
return {
|
|
463
|
+
attach,
|
|
464
|
+
detach,
|
|
465
|
+
reset: () => controller?.reset(),
|
|
466
|
+
stop: () => controller?.stop(),
|
|
467
|
+
getScale: () => controller?.getScale() ?? 1,
|
|
468
|
+
getTransform: () => controller?.getTransform() ?? { scale: 1, tx: 0, ty: 0 },
|
|
469
|
+
scrollToElement: (elem, opts) => controller?.scrollToElement(elem, opts),
|
|
470
|
+
scrollBy: (dx, dy) => controller?.scrollBy(dx, dy),
|
|
471
|
+
setGesturesEnabled: (enabled) => controller?.setGesturesEnabled(enabled),
|
|
472
|
+
onChange: (cb) => controller?.onChange(cb) ?? (() => {
|
|
473
|
+
})
|
|
474
|
+
};
|
|
475
|
+
}
|
|
@@ -4,6 +4,12 @@ export interface PageMeta {
|
|
|
4
4
|
pageNumber: number;
|
|
5
5
|
width: number;
|
|
6
6
|
height: number;
|
|
7
|
+
/** PDF user-space origin of the page's visible area (`view[0]`, `view[1]`).
|
|
8
|
+
* Almost always (0, 0); non-zero for PDFs whose MediaBox has been
|
|
9
|
+
* shifted. Annotations in `/Rect` are in default user space, so any
|
|
10
|
+
* layer drawn at the canvas top-left must subtract this origin. */
|
|
11
|
+
viewOffsetX: number;
|
|
12
|
+
viewOffsetY: number;
|
|
7
13
|
}
|
|
8
14
|
export interface PageVirtualization {
|
|
9
15
|
pageMetas: ShallowRef<PageMeta[]>;
|
|
@@ -13,6 +19,9 @@ export interface PageVirtualization {
|
|
|
13
19
|
observePage: (pageNumber: number, element: HTMLElement) => void;
|
|
14
20
|
unobservePage: (pageNumber: number) => void;
|
|
15
21
|
scrollToPage: (pageNumber: number) => void;
|
|
22
|
+
getPageElement: (pageNumber: number) => HTMLElement | undefined;
|
|
23
|
+
/** Recompute currentPage using visual bounding rects. Works with CSS transforms. */
|
|
24
|
+
updateCurrentPageByRect: () => void;
|
|
16
25
|
init: (doc: PDFDocumentProxy, scrollRoot: HTMLElement) => Promise<void>;
|
|
17
26
|
destroy: () => void;
|
|
18
27
|
}
|
|
@@ -96,13 +96,40 @@ export function createPageVirtualization() {
|
|
|
96
96
|
currentPage.value = pageNumber;
|
|
97
97
|
}
|
|
98
98
|
}
|
|
99
|
+
function getPageElement(pageNumber) {
|
|
100
|
+
return pageToElement.get(pageNumber);
|
|
101
|
+
}
|
|
102
|
+
function updateCurrentPageByRect() {
|
|
103
|
+
if (!scrollRoot) return;
|
|
104
|
+
const rootRect = scrollRoot.getBoundingClientRect();
|
|
105
|
+
const rootMid = rootRect.top + rootRect.height / 2;
|
|
106
|
+
let bestPage = currentPage.value;
|
|
107
|
+
let bestDist = Infinity;
|
|
108
|
+
for (const [pageNum, el] of pageToElement) {
|
|
109
|
+
const rect = el.getBoundingClientRect();
|
|
110
|
+
const dist = rootMid < rect.top ? rect.top - rootMid : rootMid > rect.bottom ? rootMid - rect.bottom : 0;
|
|
111
|
+
if (dist < bestDist) {
|
|
112
|
+
bestDist = dist;
|
|
113
|
+
bestPage = pageNum;
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
if (bestPage !== currentPage.value) {
|
|
117
|
+
currentPage.value = bestPage;
|
|
118
|
+
}
|
|
119
|
+
}
|
|
99
120
|
async function fetchPageDimensions(doc, pageNumber) {
|
|
100
121
|
const proxy = await doc.getPage(pageNumber);
|
|
101
122
|
const view = proxy.view;
|
|
123
|
+
const vx = view[0] ?? 0;
|
|
124
|
+
const vy = view[1] ?? 0;
|
|
125
|
+
const vw = view[2] ?? 0;
|
|
126
|
+
const vh = view[3] ?? 0;
|
|
102
127
|
return {
|
|
103
128
|
pageNumber,
|
|
104
|
-
width:
|
|
105
|
-
height:
|
|
129
|
+
width: vw - vx,
|
|
130
|
+
height: vh - vy,
|
|
131
|
+
viewOffsetX: vx,
|
|
132
|
+
viewOffsetY: vy
|
|
106
133
|
};
|
|
107
134
|
}
|
|
108
135
|
async function init(doc, root) {
|
|
@@ -118,7 +145,9 @@ export function createPageVirtualization() {
|
|
|
118
145
|
metas.push({
|
|
119
146
|
pageNumber: i,
|
|
120
147
|
width: firstMeta.width,
|
|
121
|
-
height: firstMeta.height
|
|
148
|
+
height: firstMeta.height,
|
|
149
|
+
viewOffsetX: firstMeta.viewOffsetX,
|
|
150
|
+
viewOffsetY: firstMeta.viewOffsetY
|
|
122
151
|
});
|
|
123
152
|
}
|
|
124
153
|
}
|
|
@@ -197,6 +226,8 @@ export function createPageVirtualization() {
|
|
|
197
226
|
observePage,
|
|
198
227
|
unobservePage,
|
|
199
228
|
scrollToPage,
|
|
229
|
+
getPageElement,
|
|
230
|
+
updateCurrentPageByRect,
|
|
200
231
|
init,
|
|
201
232
|
destroy
|
|
202
233
|
};
|
|
@@ -27,6 +27,7 @@ export interface ViewerSearchState {
|
|
|
27
27
|
totalPages: number;
|
|
28
28
|
}) => void;
|
|
29
29
|
setScrollToPage: (fn: (pageNumber: number) => void) => void;
|
|
30
|
+
setScrollToElement: (fn: (el: HTMLElement) => void) => void;
|
|
30
31
|
}
|
|
31
32
|
export declare function provideViewerSearch(): ViewerSearchState;
|
|
32
33
|
export declare function useViewerSearch(): ViewerSearchState;
|
|
@@ -26,12 +26,16 @@ export function provideViewerSearch() {
|
|
|
26
26
|
let inputElement = null;
|
|
27
27
|
let _searchIndex = null;
|
|
28
28
|
let _scrollToPage = null;
|
|
29
|
+
let _scrollToElement = null;
|
|
29
30
|
function setSearchIndex(index) {
|
|
30
31
|
_searchIndex = index;
|
|
31
32
|
}
|
|
32
33
|
function setScrollToPage(fn) {
|
|
33
34
|
_scrollToPage = fn;
|
|
34
35
|
}
|
|
36
|
+
function setScrollToElement(fn) {
|
|
37
|
+
_scrollToElement = fn;
|
|
38
|
+
}
|
|
35
39
|
function getSortedPageNumbers() {
|
|
36
40
|
return [...pageLayers.keys()].sort((a, b) => a - b);
|
|
37
41
|
}
|
|
@@ -186,10 +190,14 @@ export function provideViewerSearch() {
|
|
|
186
190
|
}
|
|
187
191
|
prevEnd = end;
|
|
188
192
|
if (isSelected && shouldScrollSelected && selectedSpan) {
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
+
if (_scrollToElement) {
|
|
194
|
+
_scrollToElement(selectedSpan);
|
|
195
|
+
} else {
|
|
196
|
+
selectedSpan.scrollIntoView({
|
|
197
|
+
block: "start",
|
|
198
|
+
inline: "center"
|
|
199
|
+
});
|
|
200
|
+
}
|
|
193
201
|
}
|
|
194
202
|
}
|
|
195
203
|
if (prevEnd) {
|
|
@@ -309,6 +317,7 @@ export function provideViewerSearch() {
|
|
|
309
317
|
inputElement = null;
|
|
310
318
|
_searchIndex = null;
|
|
311
319
|
_scrollToPage = null;
|
|
320
|
+
_scrollToElement = null;
|
|
312
321
|
}
|
|
313
322
|
function setQuery(value) {
|
|
314
323
|
query.value = value;
|
|
@@ -404,7 +413,8 @@ export function provideViewerSearch() {
|
|
|
404
413
|
setInputElement,
|
|
405
414
|
focusInput,
|
|
406
415
|
setSearchIndex,
|
|
407
|
-
setScrollToPage
|
|
416
|
+
setScrollToPage,
|
|
417
|
+
setScrollToElement
|
|
408
418
|
};
|
|
409
419
|
provide(VIEWER_SEARCH_KEY, state);
|
|
410
420
|
return state;
|
|
@@ -57,6 +57,9 @@ export interface ViewerState {
|
|
|
57
57
|
history: AnnotationHistory;
|
|
58
58
|
readonly: Ref<boolean>;
|
|
59
59
|
shapeDetection: Ref<boolean>;
|
|
60
|
+
visualZoom: Ref<number>;
|
|
61
|
+
formEditMode: Ref<boolean>;
|
|
62
|
+
setFormEditMode: (enabled: boolean) => void;
|
|
60
63
|
}
|
|
61
64
|
export interface ViewerStateProvider {
|
|
62
65
|
state: ViewerState;
|
|
@@ -65,7 +65,22 @@ export function provideViewerState() {
|
|
|
65
65
|
}
|
|
66
66
|
const readonly = ref(false);
|
|
67
67
|
const shapeDetectionEnabled = ref(false);
|
|
68
|
+
const visualZoom = ref(1);
|
|
68
69
|
const stylusMode = ref(false);
|
|
70
|
+
const formEditMode = ref(false);
|
|
71
|
+
function isFormFieldTool(t) {
|
|
72
|
+
return typeof t === "number" && (t === AnnotationType.FORM_TEXT || t === AnnotationType.FORM_CHECKBOX || t === AnnotationType.FORM_RADIO || t === AnnotationType.FORM_SIGNATURE);
|
|
73
|
+
}
|
|
74
|
+
function setFormEditMode(enabled) {
|
|
75
|
+
if (formEditMode.value === enabled) return;
|
|
76
|
+
formEditMode.value = enabled;
|
|
77
|
+
const t = activeTool.value;
|
|
78
|
+
if (enabled) {
|
|
79
|
+
if (typeof t === "number" && !isFormFieldTool(t)) selectTool("hand");
|
|
80
|
+
} else {
|
|
81
|
+
if (isFormFieldTool(t)) selectTool("hand");
|
|
82
|
+
}
|
|
83
|
+
}
|
|
69
84
|
function setStylusMode(enabled) {
|
|
70
85
|
stylusMode.value = enabled;
|
|
71
86
|
if (painter.value) {
|
|
@@ -95,6 +110,9 @@ export function provideViewerState() {
|
|
|
95
110
|
}
|
|
96
111
|
function selectTool(tool, dataTransfer) {
|
|
97
112
|
if (readonly.value && tool !== "hand") return;
|
|
113
|
+
if (isFormFieldTool(tool) && !formEditMode.value) {
|
|
114
|
+
formEditMode.value = true;
|
|
115
|
+
}
|
|
98
116
|
activeTool.value = tool;
|
|
99
117
|
selectedAnnotation.value = null;
|
|
100
118
|
selectedAnnotations.value = [];
|
|
@@ -181,7 +199,10 @@ export function provideViewerState() {
|
|
|
181
199
|
downloadPdf,
|
|
182
200
|
history,
|
|
183
201
|
readonly,
|
|
184
|
-
shapeDetection: shapeDetectionEnabled
|
|
202
|
+
shapeDetection: shapeDetectionEnabled,
|
|
203
|
+
visualZoom,
|
|
204
|
+
formEditMode,
|
|
205
|
+
setFormEditMode
|
|
185
206
|
};
|
|
186
207
|
provide(VIEWER_STATE_KEY, state);
|
|
187
208
|
return {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "kviewer",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.11",
|
|
4
4
|
"description": "Kabema PDF Editor",
|
|
5
5
|
"repository": "kabema/kviewer",
|
|
6
6
|
"license": "MIT",
|
|
@@ -28,7 +28,7 @@
|
|
|
28
28
|
],
|
|
29
29
|
"scripts": {
|
|
30
30
|
"prepack": "nuxt-module-build build",
|
|
31
|
-
"dev": "npm run dev:prepare && nuxt dev playground",
|
|
31
|
+
"dev": "npm run dev:prepare && portless kviewer nuxt dev playground",
|
|
32
32
|
"dev:build": "nuxt build playground",
|
|
33
33
|
"dev:prepare": "nuxt-module-build build --stub && nuxt-module-build prepare && nuxt prepare playground",
|
|
34
34
|
"release": "npm run lint && npm run test && npm run prepack && changelogen --release && npm publish && git push --follow-tags",
|