kviewer 0.0.8 → 0.0.10
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/module.d.mts +1 -1
- package/dist/module.json +1 -1
- package/dist/runtime/annotation/engine/editor/editor.d.ts +11 -5
- package/dist/runtime/annotation/engine/editor/editor.js +27 -11
- package/dist/runtime/annotation/engine/editor/selector.d.ts +20 -1
- package/dist/runtime/annotation/engine/editor/selector.js +132 -33
- package/dist/runtime/annotation/engine/painter.d.ts +4 -1
- package/dist/runtime/annotation/engine/painter.js +21 -5
- package/dist/runtime/annotation/engine/tools/freehand.js +2 -2
- package/dist/runtime/components/PdfPage.d.vue.ts +6 -1
- package/dist/runtime/components/PdfPage.vue +51 -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 +3 -0
- package/dist/runtime/components/Viewer.vue +225 -165
- package/dist/runtime/components/Viewer.vue.d.ts +3 -0
- package/dist/runtime/components/form-fields/FormSignatureField.vue +23 -1
- package/dist/runtime/components/modals/FreeTextModal.vue +27 -3
- package/dist/runtime/components/tools/ActionTools.vue +4 -5
- package/dist/runtime/components/tools/DrawingTools.vue +1 -1
- package/dist/runtime/composables/useAnnotationEngine.d.ts +2 -0
- package/dist/runtime/composables/useAnnotationEngine.js +5 -1
- package/dist/runtime/composables/useAnnotationHistory.d.ts +1 -0
- package/dist/runtime/composables/useAnnotationHistory.js +10 -3
- package/dist/runtime/composables/useInertiaPanzoom.d.ts +40 -0
- package/dist/runtime/composables/useInertiaPanzoom.js +472 -0
- package/dist/runtime/composables/usePageVirtualization.d.ts +3 -0
- package/dist/runtime/composables/usePageVirtualization.js +23 -0
- package/dist/runtime/composables/useViewerSearch.d.ts +1 -0
- package/dist/runtime/composables/useViewerSearch.js +15 -5
- package/dist/runtime/public-types.d.ts +2 -0
- package/dist/types.d.mts +1 -1
- package/package.json +1 -1
package/dist/module.d.mts
CHANGED
package/dist/module.json
CHANGED
|
@@ -10,6 +10,8 @@ export interface IEditorOptions {
|
|
|
10
10
|
onChange: (id: string, updates: Partial<IAnnotationStore>) => void;
|
|
11
11
|
/** When provided and returns true, only pen/mouse input can draw. */
|
|
12
12
|
getStylusModeEnabled?: () => boolean;
|
|
13
|
+
/** Delay in ms before consecutive freehand strokes are finalized as a single annotation. */
|
|
14
|
+
freehandGroupingDelay?: number;
|
|
13
15
|
}
|
|
14
16
|
export interface IShapeGroup {
|
|
15
17
|
id: string;
|
|
@@ -30,8 +32,9 @@ export declare abstract class Editor {
|
|
|
30
32
|
shapeGroupStore: Map<string, IShapeGroup>;
|
|
31
33
|
currentShapeGroup: IShapeGroup | null;
|
|
32
34
|
protected getStylusModeEnabled?: () => boolean;
|
|
35
|
+
protected freehandGroupingDelay: number;
|
|
33
36
|
static MinSize: number;
|
|
34
|
-
constructor({ userName, konvaStage, pageNumber, annotation, onAdd, editorType, onChange, getStylusModeEnabled, }: IEditorOptions & {
|
|
37
|
+
constructor({ userName, konvaStage, pageNumber, annotation, onAdd, editorType, onChange, getStylusModeEnabled, freehandGroupingDelay, }: IEditorOptions & {
|
|
35
38
|
editorType: AnnotationType;
|
|
36
39
|
});
|
|
37
40
|
private dispatchAddEvent;
|
|
@@ -61,9 +64,12 @@ export declare abstract class Editor {
|
|
|
61
64
|
addSerializedGroupToLayer(konvaStage: Konva.Stage, konvaString: string): void;
|
|
62
65
|
deleteGroup(id: string, konvaStage: Konva.Stage): void;
|
|
63
66
|
updateStyle(annotationStore: IAnnotationStore, style: IAnnotationStyle): void;
|
|
64
|
-
static Timer: {
|
|
65
|
-
|
|
66
|
-
|
|
67
|
+
static Timer: Map<number, {
|
|
68
|
+
id: number;
|
|
69
|
+
callback: (pageNumber: number) => void;
|
|
70
|
+
}>;
|
|
67
71
|
static TimerClear(pageNumber: number): void;
|
|
68
|
-
static TimerStart(pageNumber: number, callback: (pageNumber: number) => void): void;
|
|
72
|
+
static TimerStart(pageNumber: number, callback: (pageNumber: number) => void, delay?: number): void;
|
|
73
|
+
/** Immediately execute and clear all pending timers (e.g. before export). */
|
|
74
|
+
static flushAllTimers(): void;
|
|
69
75
|
}
|
|
@@ -14,6 +14,7 @@ export class Editor {
|
|
|
14
14
|
shapeGroupStore = /* @__PURE__ */ new Map();
|
|
15
15
|
currentShapeGroup;
|
|
16
16
|
getStylusModeEnabled;
|
|
17
|
+
freehandGroupingDelay;
|
|
17
18
|
static MinSize = 8;
|
|
18
19
|
constructor({
|
|
19
20
|
userName,
|
|
@@ -23,7 +24,8 @@ export class Editor {
|
|
|
23
24
|
onAdd,
|
|
24
25
|
editorType,
|
|
25
26
|
onChange,
|
|
26
|
-
getStylusModeEnabled
|
|
27
|
+
getStylusModeEnabled,
|
|
28
|
+
freehandGroupingDelay
|
|
27
29
|
}) {
|
|
28
30
|
this.userName = userName;
|
|
29
31
|
this.id = `${pageNumber}_${editorType}`;
|
|
@@ -36,6 +38,7 @@ export class Editor {
|
|
|
36
38
|
this.onChange = onChange || (() => {
|
|
37
39
|
});
|
|
38
40
|
this.getStylusModeEnabled = getStylusModeEnabled;
|
|
41
|
+
this.freehandGroupingDelay = freehandGroupingDelay ?? 1e3;
|
|
39
42
|
this.disableEditMode();
|
|
40
43
|
this.enableEditMode();
|
|
41
44
|
}
|
|
@@ -228,18 +231,31 @@ export class Editor {
|
|
|
228
231
|
updateStyle(annotationStore, style) {
|
|
229
232
|
this.changeStyle(annotationStore, style);
|
|
230
233
|
}
|
|
231
|
-
static Timer =
|
|
234
|
+
static Timer = /* @__PURE__ */ new Map();
|
|
232
235
|
static TimerClear(pageNumber) {
|
|
233
|
-
const
|
|
234
|
-
if (
|
|
235
|
-
window.clearTimeout(
|
|
236
|
+
const entry = Editor.Timer.get(pageNumber);
|
|
237
|
+
if (entry) {
|
|
238
|
+
window.clearTimeout(entry.id);
|
|
239
|
+
Editor.Timer.delete(pageNumber);
|
|
236
240
|
}
|
|
237
241
|
}
|
|
238
|
-
static TimerStart(pageNumber, callback) {
|
|
239
|
-
Editor.Timer
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
242
|
+
static TimerStart(pageNumber, callback, delay = 1e3) {
|
|
243
|
+
Editor.Timer.set(pageNumber, {
|
|
244
|
+
callback,
|
|
245
|
+
id: window.setTimeout(() => {
|
|
246
|
+
Editor.Timer.delete(pageNumber);
|
|
247
|
+
if (typeof callback === "function") {
|
|
248
|
+
callback(pageNumber);
|
|
249
|
+
}
|
|
250
|
+
}, delay)
|
|
251
|
+
});
|
|
252
|
+
}
|
|
253
|
+
/** Immediately execute and clear all pending timers (e.g. before export). */
|
|
254
|
+
static flushAllTimers() {
|
|
255
|
+
for (const [pageNumber, entry] of Editor.Timer) {
|
|
256
|
+
window.clearTimeout(entry.id);
|
|
257
|
+
Editor.Timer.delete(pageNumber);
|
|
258
|
+
entry.callback(pageNumber);
|
|
259
|
+
}
|
|
244
260
|
}
|
|
245
261
|
}
|
|
@@ -13,6 +13,13 @@ export interface ISelectorOptions {
|
|
|
13
13
|
onFreeTextDoubleClick?: (id: string) => void;
|
|
14
14
|
/** When provided and returns true, only pen/mouse input can interact. */
|
|
15
15
|
getStylusModeEnabled?: () => boolean;
|
|
16
|
+
/**
|
|
17
|
+
* Toggle external gesture handlers (e.g. panzoom) off while an annotation
|
|
18
|
+
* is being dragged/transformed. Without this, on mobile the panzoom
|
|
19
|
+
* single-finger pan runs in parallel with Konva's shape drag and both
|
|
20
|
+
* CSS transforms compete every frame, causing visible lag.
|
|
21
|
+
*/
|
|
22
|
+
setExternalGesturesEnabled?: (enabled: boolean) => void;
|
|
16
23
|
}
|
|
17
24
|
export declare class Selector {
|
|
18
25
|
readonly onSelected: (id: string, isClick: boolean, clientRect: IRect) => void;
|
|
@@ -23,6 +30,7 @@ export declare class Selector {
|
|
|
23
30
|
private onRequestDeleteConfirm?;
|
|
24
31
|
private onFreeTextDoubleClick?;
|
|
25
32
|
private getStylusModeEnabled?;
|
|
33
|
+
private setExternalGesturesEnabled?;
|
|
26
34
|
private transformerStore;
|
|
27
35
|
private getAnnotationStore;
|
|
28
36
|
private konvaCanvasStore;
|
|
@@ -32,7 +40,7 @@ export declare class Selector {
|
|
|
32
40
|
private marqueeRect;
|
|
33
41
|
private marqueeStartPos;
|
|
34
42
|
private marqueeStage;
|
|
35
|
-
constructor({ konvaCanvasStore, getAnnotationStore, onDelete, onSelected, onMultiSelected, onCancel, onChanged, onRequestDeleteConfirm, onFreeTextDoubleClick, getStylusModeEnabled, }: ISelectorOptions);
|
|
43
|
+
constructor({ konvaCanvasStore, getAnnotationStore, onDelete, onSelected, onMultiSelected, onCancel, onChanged, onRequestDeleteConfirm, onFreeTextDoubleClick, getStylusModeEnabled, setExternalGesturesEnabled, }: ISelectorOptions);
|
|
36
44
|
/** Returns true if this event should be rejected (finger touch in stylus mode). */
|
|
37
45
|
private isStylusRejected;
|
|
38
46
|
private handleKeyDown;
|
|
@@ -66,6 +74,17 @@ export declare class Selector {
|
|
|
66
74
|
activateMarquee(pageNumber: number): void;
|
|
67
75
|
private bindMarqueeEvents;
|
|
68
76
|
private rectsIntersect;
|
|
77
|
+
/**
|
|
78
|
+
* Check whether a line segment intersects an axis-aligned rectangle
|
|
79
|
+
* (expanded by `tolerance` on each side to match the visual eraser width).
|
|
80
|
+
* Uses Liang-Barsky line clipping.
|
|
81
|
+
*/
|
|
82
|
+
private segmentIntersectsRect;
|
|
83
|
+
/**
|
|
84
|
+
* Check whether any segment of an eraser path intersects a rectangle.
|
|
85
|
+
* Points is a flat array [x0, y0, x1, y1, ...] in page-space coordinates.
|
|
86
|
+
*/
|
|
87
|
+
private eraserPathIntersectsRect;
|
|
69
88
|
activateEraser(pageNumber: number): void;
|
|
70
89
|
private eraserLine;
|
|
71
90
|
private eraserStage;
|
|
@@ -12,6 +12,7 @@ export class Selector {
|
|
|
12
12
|
onRequestDeleteConfirm;
|
|
13
13
|
onFreeTextDoubleClick;
|
|
14
14
|
getStylusModeEnabled;
|
|
15
|
+
setExternalGesturesEnabled;
|
|
15
16
|
transformerStore = /* @__PURE__ */ new Map();
|
|
16
17
|
getAnnotationStore;
|
|
17
18
|
konvaCanvasStore;
|
|
@@ -31,7 +32,8 @@ export class Selector {
|
|
|
31
32
|
onChanged,
|
|
32
33
|
onRequestDeleteConfirm,
|
|
33
34
|
onFreeTextDoubleClick,
|
|
34
|
-
getStylusModeEnabled
|
|
35
|
+
getStylusModeEnabled,
|
|
36
|
+
setExternalGesturesEnabled
|
|
35
37
|
}) {
|
|
36
38
|
this.konvaCanvasStore = konvaCanvasStore;
|
|
37
39
|
this.getAnnotationStore = getAnnotationStore;
|
|
@@ -43,6 +45,7 @@ export class Selector {
|
|
|
43
45
|
this.onRequestDeleteConfirm = onRequestDeleteConfirm;
|
|
44
46
|
this.onFreeTextDoubleClick = onFreeTextDoubleClick;
|
|
45
47
|
this.getStylusModeEnabled = getStylusModeEnabled;
|
|
48
|
+
this.setExternalGesturesEnabled = setExternalGesturesEnabled;
|
|
46
49
|
this.handleKeyDown = this.handleKeyDown.bind(this);
|
|
47
50
|
window.addEventListener("keydown", this.handleKeyDown);
|
|
48
51
|
}
|
|
@@ -188,6 +191,7 @@ export class Selector {
|
|
|
188
191
|
const transformer = new Konva.Transformer({
|
|
189
192
|
resizeEnabled: rawAnnotationStore.resizable,
|
|
190
193
|
rotateEnabled: false,
|
|
194
|
+
enabledAnchors: ["top-left", "top-right", "bottom-left", "bottom-right"],
|
|
191
195
|
borderStrokeWidth: defaultOptions.chooseSetting.STROKEWIDTH,
|
|
192
196
|
borderStroke: defaultOptions.chooseSetting.COLOR,
|
|
193
197
|
anchorFill: defaultOptions.chooseSetting.COLOR,
|
|
@@ -223,7 +227,34 @@ export class Selector {
|
|
|
223
227
|
}
|
|
224
228
|
transformer.off("transformend");
|
|
225
229
|
transformer.off("transformstart");
|
|
230
|
+
let dragLayer = null;
|
|
231
|
+
const moveToDragLayer = () => {
|
|
232
|
+
if (dragLayer) return;
|
|
233
|
+
const bg = this.getBackgroundLayer(konvaStage);
|
|
234
|
+
dragLayer = new Konva.Layer();
|
|
235
|
+
konvaStage.add(dragLayer);
|
|
236
|
+
group.moveTo(dragLayer);
|
|
237
|
+
transformer.moveTo(dragLayer);
|
|
238
|
+
bg.batchDraw();
|
|
239
|
+
dragLayer.batchDraw();
|
|
240
|
+
};
|
|
241
|
+
const restoreFromDragLayer = () => {
|
|
242
|
+
if (!dragLayer) return;
|
|
243
|
+
const bg = this.getBackgroundLayer(konvaStage);
|
|
244
|
+
group.moveTo(bg);
|
|
245
|
+
transformer.moveTo(bg);
|
|
246
|
+
dragLayer.destroy();
|
|
247
|
+
dragLayer = null;
|
|
248
|
+
bg.batchDraw();
|
|
249
|
+
};
|
|
250
|
+
transformer.on("transformstart", () => {
|
|
251
|
+
this.setExternalGesturesEnabled?.(false);
|
|
252
|
+
moveToDragLayer();
|
|
253
|
+
this.onCancel();
|
|
254
|
+
});
|
|
226
255
|
transformer.on("transformend", () => {
|
|
256
|
+
this.setExternalGesturesEnabled?.(true);
|
|
257
|
+
restoreFromDragLayer();
|
|
227
258
|
this.onChanged(
|
|
228
259
|
group.id(),
|
|
229
260
|
group.toJSON(),
|
|
@@ -232,13 +263,22 @@ export class Selector {
|
|
|
232
263
|
transformer.getClientRect()
|
|
233
264
|
);
|
|
234
265
|
});
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
});
|
|
266
|
+
let dragStartRect = null;
|
|
267
|
+
let dragAnchorPos = null;
|
|
238
268
|
transformer.on("dragstart", () => {
|
|
269
|
+
this.setExternalGesturesEnabled?.(false);
|
|
270
|
+
moveToDragLayer();
|
|
239
271
|
this.onCancel();
|
|
272
|
+
const boxes = transformer.nodes().map((node) => node.getClientRect());
|
|
273
|
+
dragStartRect = this.getTotalBox(boxes);
|
|
274
|
+
const firstNode = transformer.nodes()[0];
|
|
275
|
+
dragAnchorPos = firstNode ? firstNode.getAbsolutePosition() : null;
|
|
240
276
|
});
|
|
241
277
|
transformer.on("dragend", () => {
|
|
278
|
+
this.setExternalGesturesEnabled?.(true);
|
|
279
|
+
restoreFromDragLayer();
|
|
280
|
+
dragStartRect = null;
|
|
281
|
+
dragAnchorPos = null;
|
|
242
282
|
this.onChanged(
|
|
243
283
|
group.id(),
|
|
244
284
|
group.toJSON(),
|
|
@@ -248,29 +288,32 @@ export class Selector {
|
|
|
248
288
|
);
|
|
249
289
|
});
|
|
250
290
|
transformer.on("dragmove", () => {
|
|
251
|
-
|
|
252
|
-
const
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
291
|
+
if (!dragStartRect || !dragAnchorPos) return;
|
|
292
|
+
const nodes = transformer.nodes();
|
|
293
|
+
const firstNode = nodes[0];
|
|
294
|
+
if (!firstNode) return;
|
|
295
|
+
const currentPos = firstNode.getAbsolutePosition();
|
|
296
|
+
const dx = currentPos.x - dragAnchorPos.x;
|
|
297
|
+
const dy = currentPos.y - dragAnchorPos.y;
|
|
298
|
+
const boxX = dragStartRect.x + dx;
|
|
299
|
+
const boxY = dragStartRect.y + dy;
|
|
300
|
+
const halfWidth = dragStartRect.width / 2;
|
|
301
|
+
const halfHeight = dragStartRect.height / 2;
|
|
302
|
+
const stageW = konvaStage.width();
|
|
303
|
+
const stageH = konvaStage.height();
|
|
304
|
+
let clampDx = 0;
|
|
305
|
+
let clampDy = 0;
|
|
306
|
+
if (boxX + halfWidth < 0) clampDx = -(boxX + halfWidth);
|
|
307
|
+
else if (boxX + halfWidth > stageW) clampDx = stageW - (boxX + halfWidth);
|
|
308
|
+
if (boxY + halfHeight < 0) clampDy = -(boxY + halfHeight);
|
|
309
|
+
else if (boxY + halfHeight > stageH) clampDy = stageH - (boxY + halfHeight);
|
|
310
|
+
if (clampDx !== 0 || clampDy !== 0) {
|
|
311
|
+
nodes.forEach((shape) => {
|
|
312
|
+
const pos = shape.getAbsolutePosition();
|
|
313
|
+
shape.setAbsolutePosition({ x: pos.x + clampDx, y: pos.y + clampDy });
|
|
314
|
+
});
|
|
315
|
+
dragAnchorPos = firstNode.getAbsolutePosition();
|
|
316
|
+
}
|
|
274
317
|
});
|
|
275
318
|
transformer.nodes([group]);
|
|
276
319
|
this.getBackgroundLayer(konvaStage).add(transformer);
|
|
@@ -507,12 +550,11 @@ export class Selector {
|
|
|
507
550
|
this.transformerStore.set("__multi__", transformer);
|
|
508
551
|
nodes.forEach((node) => {
|
|
509
552
|
node.on("dragstart.marquee", () => {
|
|
553
|
+
this.setExternalGesturesEnabled?.(false);
|
|
510
554
|
this.onCancel();
|
|
511
555
|
});
|
|
512
|
-
node.on("dragmove.marquee", () => {
|
|
513
|
-
this.onMultiSelected(matchedIds, transformer.getClientRect());
|
|
514
|
-
});
|
|
515
556
|
node.on("dragend.marquee", () => {
|
|
557
|
+
this.setExternalGesturesEnabled?.(true);
|
|
516
558
|
this.onMultiSelected(matchedIds, transformer.getClientRect());
|
|
517
559
|
});
|
|
518
560
|
});
|
|
@@ -526,6 +568,60 @@ export class Selector {
|
|
|
526
568
|
rectsIntersect(a, b) {
|
|
527
569
|
return a.x < b.x + b.width && a.x + a.width > b.x && a.y < b.y + b.height && a.y + a.height > b.y;
|
|
528
570
|
}
|
|
571
|
+
/**
|
|
572
|
+
* Check whether a line segment intersects an axis-aligned rectangle
|
|
573
|
+
* (expanded by `tolerance` on each side to match the visual eraser width).
|
|
574
|
+
* Uses Liang-Barsky line clipping.
|
|
575
|
+
*/
|
|
576
|
+
segmentIntersectsRect(x1, y1, x2, y2, rect, tolerance) {
|
|
577
|
+
const left = rect.x - tolerance;
|
|
578
|
+
const top = rect.y - tolerance;
|
|
579
|
+
const right = rect.x + rect.width + tolerance;
|
|
580
|
+
const bottom = rect.y + rect.height + tolerance;
|
|
581
|
+
let tMin = 0;
|
|
582
|
+
let tMax = 1;
|
|
583
|
+
const dx = x2 - x1;
|
|
584
|
+
const dy = y2 - y1;
|
|
585
|
+
const edges = [
|
|
586
|
+
{ p: -dx, q: x1 - left },
|
|
587
|
+
{ p: dx, q: right - x1 },
|
|
588
|
+
{ p: -dy, q: y1 - top },
|
|
589
|
+
{ p: dy, q: bottom - y1 }
|
|
590
|
+
];
|
|
591
|
+
for (const { p, q } of edges) {
|
|
592
|
+
if (p === 0) {
|
|
593
|
+
if (q < 0) return false;
|
|
594
|
+
} else {
|
|
595
|
+
const t = q / p;
|
|
596
|
+
if (p < 0) {
|
|
597
|
+
tMin = Math.max(tMin, t);
|
|
598
|
+
} else {
|
|
599
|
+
tMax = Math.min(tMax, t);
|
|
600
|
+
}
|
|
601
|
+
if (tMin > tMax) return false;
|
|
602
|
+
}
|
|
603
|
+
}
|
|
604
|
+
return true;
|
|
605
|
+
}
|
|
606
|
+
/**
|
|
607
|
+
* Check whether any segment of an eraser path intersects a rectangle.
|
|
608
|
+
* Points is a flat array [x0, y0, x1, y1, ...] in page-space coordinates.
|
|
609
|
+
*/
|
|
610
|
+
eraserPathIntersectsRect(points, rect, tolerance) {
|
|
611
|
+
for (let i = 0; i + 3 < points.length; i += 2) {
|
|
612
|
+
if (this.segmentIntersectsRect(
|
|
613
|
+
points[i],
|
|
614
|
+
points[i + 1],
|
|
615
|
+
points[i + 2],
|
|
616
|
+
points[i + 3],
|
|
617
|
+
rect,
|
|
618
|
+
tolerance
|
|
619
|
+
)) {
|
|
620
|
+
return true;
|
|
621
|
+
}
|
|
622
|
+
}
|
|
623
|
+
return false;
|
|
624
|
+
}
|
|
529
625
|
activateEraser(pageNumber) {
|
|
530
626
|
const konvaCanvas = this.konvaCanvasStore.get(pageNumber);
|
|
531
627
|
if (!konvaCanvas) return;
|
|
@@ -569,17 +665,20 @@ export class Selector {
|
|
|
569
665
|
konvaStage.on("mouseup touchend", (e) => {
|
|
570
666
|
if (!this.eraserLine || !this.eraserStage) return;
|
|
571
667
|
if (this.isStylusRejected(e.evt)) return;
|
|
572
|
-
const
|
|
668
|
+
const points = this.eraserLine.points();
|
|
573
669
|
this.eraserLine.destroy();
|
|
574
670
|
this.eraserLine = null;
|
|
575
|
-
if (
|
|
671
|
+
if (points.length < 4) {
|
|
576
672
|
this.eraserStage = null;
|
|
577
673
|
return;
|
|
578
674
|
}
|
|
675
|
+
const scale = this.eraserStage.scaleX() ?? 1;
|
|
676
|
+
const screenPoints = points.map((v) => v * scale);
|
|
677
|
+
const tolerance = 6;
|
|
579
678
|
const groups = this.getPageShapeGroups(this.eraserStage);
|
|
580
679
|
const toDelete = [];
|
|
581
680
|
groups.forEach((group) => {
|
|
582
|
-
if (this.
|
|
681
|
+
if (this.eraserPathIntersectsRect(screenPoints, group.getClientRect(), tolerance)) {
|
|
583
682
|
toDelete.push(group.id());
|
|
584
683
|
}
|
|
585
684
|
});
|
|
@@ -32,11 +32,14 @@ export declare class Painter {
|
|
|
32
32
|
private callbacks;
|
|
33
33
|
private getStylusModeEnabled?;
|
|
34
34
|
private getReadonly?;
|
|
35
|
-
|
|
35
|
+
private freehandGroupingDelay;
|
|
36
|
+
constructor({ userName, callbacks, getStylusModeEnabled, getReadonly, freehandGroupingDelay, setExternalGesturesEnabled, }: {
|
|
36
37
|
userName: string;
|
|
37
38
|
callbacks: PainterCallbacks;
|
|
38
39
|
getStylusModeEnabled?: () => boolean;
|
|
39
40
|
getReadonly?: () => boolean;
|
|
41
|
+
freehandGroupingDelay?: number;
|
|
42
|
+
setExternalGesturesEnabled?: (enabled: boolean) => void;
|
|
40
43
|
});
|
|
41
44
|
private editFreeText;
|
|
42
45
|
private bindGlobalEvents;
|
|
@@ -10,6 +10,7 @@ import {
|
|
|
10
10
|
PAINTER_PAINTING_TYPE
|
|
11
11
|
} from "./const.js";
|
|
12
12
|
import { hideCursorPreview } from "./cursor-preview.js";
|
|
13
|
+
import { Editor } from "./editor/editor.js";
|
|
13
14
|
import { EditorCircle } from "./tools/circle.js";
|
|
14
15
|
import { EditorFreeHand } from "./tools/freehand.js";
|
|
15
16
|
import { EditorFreeHighlight } from "./tools/free-highlight.js";
|
|
@@ -23,6 +24,10 @@ import { EditorArrow } from "./tools/arrow.js";
|
|
|
23
24
|
import { EditorCloud } from "./tools/cloud.js";
|
|
24
25
|
import { Selector } from "./editor/selector.js";
|
|
25
26
|
import { Store } from "./store.js";
|
|
27
|
+
import { isIPad } from "./input-device.js";
|
|
28
|
+
if (typeof window !== "undefined" && isIPad()) {
|
|
29
|
+
Konva.pixelRatio = 1;
|
|
30
|
+
}
|
|
26
31
|
export class Painter {
|
|
27
32
|
userName;
|
|
28
33
|
konvaCanvasStore = /* @__PURE__ */ new Map();
|
|
@@ -34,16 +39,20 @@ export class Painter {
|
|
|
34
39
|
callbacks;
|
|
35
40
|
getStylusModeEnabled;
|
|
36
41
|
getReadonly;
|
|
42
|
+
freehandGroupingDelay;
|
|
37
43
|
constructor({
|
|
38
44
|
userName,
|
|
39
45
|
callbacks,
|
|
40
46
|
getStylusModeEnabled,
|
|
41
|
-
getReadonly
|
|
47
|
+
getReadonly,
|
|
48
|
+
freehandGroupingDelay,
|
|
49
|
+
setExternalGesturesEnabled
|
|
42
50
|
}) {
|
|
43
51
|
this.userName = userName;
|
|
44
52
|
this.callbacks = callbacks;
|
|
45
53
|
this.getStylusModeEnabled = getStylusModeEnabled;
|
|
46
54
|
this.getReadonly = getReadonly;
|
|
55
|
+
this.freehandGroupingDelay = freehandGroupingDelay ?? 1e3;
|
|
47
56
|
this.store = new Store();
|
|
48
57
|
this.selector = new Selector({
|
|
49
58
|
konvaCanvasStore: this.konvaCanvasStore,
|
|
@@ -83,7 +92,8 @@ export class Painter {
|
|
|
83
92
|
onFreeTextDoubleClick: (id) => {
|
|
84
93
|
void this.editFreeText(id);
|
|
85
94
|
},
|
|
86
|
-
getStylusModeEnabled: this.getStylusModeEnabled
|
|
95
|
+
getStylusModeEnabled: this.getStylusModeEnabled,
|
|
96
|
+
setExternalGesturesEnabled
|
|
87
97
|
});
|
|
88
98
|
this.bindGlobalEvents();
|
|
89
99
|
}
|
|
@@ -135,7 +145,9 @@ export class Painter {
|
|
|
135
145
|
}
|
|
136
146
|
applyStylusTouchAction(stage) {
|
|
137
147
|
stage.preventDefault(false);
|
|
138
|
-
|
|
148
|
+
if (!isIPad()) {
|
|
149
|
+
Konva.pixelRatio = window.devicePixelRatio || 1;
|
|
150
|
+
}
|
|
139
151
|
const content = stage.content;
|
|
140
152
|
if (content) content.style.touchAction = "pan-x pan-y";
|
|
141
153
|
stage.container().querySelectorAll("canvas").forEach((c) => {
|
|
@@ -144,7 +156,9 @@ export class Painter {
|
|
|
144
156
|
}
|
|
145
157
|
removeStylusTouchAction(stage) {
|
|
146
158
|
stage.preventDefault(true);
|
|
147
|
-
|
|
159
|
+
if (!isIPad()) {
|
|
160
|
+
Konva.pixelRatio = window.devicePixelRatio || 1;
|
|
161
|
+
}
|
|
148
162
|
const content = stage.content;
|
|
149
163
|
if (content) content.style.touchAction = "";
|
|
150
164
|
stage.container().querySelectorAll("canvas").forEach((c) => {
|
|
@@ -244,7 +258,8 @@ export class Painter {
|
|
|
244
258
|
onChange: (id, updates) => {
|
|
245
259
|
this.updateStore(id, updates);
|
|
246
260
|
},
|
|
247
|
-
getStylusModeEnabled: this.getStylusModeEnabled
|
|
261
|
+
getStylusModeEnabled: this.getStylusModeEnabled,
|
|
262
|
+
freehandGroupingDelay: this.freehandGroupingDelay
|
|
248
263
|
};
|
|
249
264
|
let editor = null;
|
|
250
265
|
switch (annotation.type) {
|
|
@@ -547,6 +562,7 @@ export class Painter {
|
|
|
547
562
|
return this.store.annotations;
|
|
548
563
|
}
|
|
549
564
|
getExportContext() {
|
|
565
|
+
Editor.flushAllTimers();
|
|
550
566
|
const originalIds = this.store.originalIds;
|
|
551
567
|
const modifiedIds = this.store.modifiedOriginals;
|
|
552
568
|
const changedAnnotations = this.store.annotations.filter(
|
|
@@ -86,7 +86,7 @@ export class EditorFreeHand extends Editor {
|
|
|
86
86
|
}
|
|
87
87
|
});
|
|
88
88
|
this.currentShapeGroup = null;
|
|
89
|
-
});
|
|
89
|
+
}, this.freehandGroupingDelay);
|
|
90
90
|
return;
|
|
91
91
|
}
|
|
92
92
|
Editor.TimerStart(this.pageNumber, () => {
|
|
@@ -98,7 +98,7 @@ export class EditorFreeHand extends Editor {
|
|
|
98
98
|
}
|
|
99
99
|
});
|
|
100
100
|
this.currentShapeGroup = null;
|
|
101
|
-
});
|
|
101
|
+
}, this.freehandGroupingDelay);
|
|
102
102
|
this.line = null;
|
|
103
103
|
}
|
|
104
104
|
/**
|
|
@@ -3,7 +3,12 @@ type __VLS_Props = {
|
|
|
3
3
|
pageNumber: number;
|
|
4
4
|
pageMeta: PageMeta;
|
|
5
5
|
scale: number;
|
|
6
|
+
/** Canvas internal resolution multiplier (CSS size unchanged).
|
|
7
|
+
* Used by panzoom to sharpen the PDF when visually zoomed in. */
|
|
8
|
+
renderQuality?: number;
|
|
6
9
|
};
|
|
7
|
-
declare const __VLS_export: import("vue").DefineComponent<__VLS_Props, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<__VLS_Props> & Readonly<{}>, {
|
|
10
|
+
declare const __VLS_export: import("vue").DefineComponent<__VLS_Props, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<__VLS_Props> & Readonly<{}>, {
|
|
11
|
+
renderQuality: number;
|
|
12
|
+
}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
|
|
8
13
|
declare const _default: typeof __VLS_export;
|
|
9
14
|
export default _default;
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
<template>
|
|
2
2
|
<div
|
|
3
|
-
class="kviewer-page"
|
|
3
|
+
class="kviewer-page shadow-lg"
|
|
4
4
|
:style="{
|
|
5
5
|
position: 'relative',
|
|
6
6
|
width: cssWidth + 'px',
|
|
@@ -63,7 +63,8 @@ const PDFJS_ANNOTATION_MODE_DISABLE = 0;
|
|
|
63
63
|
const props = defineProps({
|
|
64
64
|
pageNumber: { type: Number, required: true },
|
|
65
65
|
pageMeta: { type: Object, required: true },
|
|
66
|
-
scale: { type: Number, required: true }
|
|
66
|
+
scale: { type: Number, required: true },
|
|
67
|
+
renderQuality: { type: Number, required: false, default: 1 }
|
|
67
68
|
});
|
|
68
69
|
const canvasRef = ref(null);
|
|
69
70
|
const textLayerRef = ref(null);
|
|
@@ -141,7 +142,14 @@ watch(
|
|
|
141
142
|
}
|
|
142
143
|
}
|
|
143
144
|
);
|
|
144
|
-
|
|
145
|
+
watch(
|
|
146
|
+
() => props.renderQuality,
|
|
147
|
+
async () => {
|
|
148
|
+
if (!pageProxy.value) return;
|
|
149
|
+
await renderCanvas({ doubleBuffer: true });
|
|
150
|
+
}
|
|
151
|
+
);
|
|
152
|
+
async function renderCanvas(opts) {
|
|
145
153
|
const canvas = canvasRef.value;
|
|
146
154
|
const proxy = pageProxy.value;
|
|
147
155
|
if (!canvas || !proxy) return;
|
|
@@ -150,23 +158,48 @@ async function renderCanvas() {
|
|
|
150
158
|
currentRenderTask = null;
|
|
151
159
|
}
|
|
152
160
|
const dpr = window.devicePixelRatio || 1;
|
|
161
|
+
const quality = props.renderQuality ?? 1;
|
|
153
162
|
const viewport = proxy.getViewport({
|
|
154
|
-
scale: props.scale * dpr
|
|
163
|
+
scale: props.scale * quality * dpr
|
|
155
164
|
});
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
165
|
+
if (opts?.doubleBuffer) {
|
|
166
|
+
const tmp = document.createElement("canvas");
|
|
167
|
+
tmp.width = viewport.width;
|
|
168
|
+
tmp.height = viewport.height;
|
|
169
|
+
try {
|
|
170
|
+
currentRenderTask = proxy.render({
|
|
171
|
+
canvasContext: tmp.getContext("2d"),
|
|
172
|
+
viewport,
|
|
173
|
+
annotationMode: PDFJS_ANNOTATION_MODE_DISABLE
|
|
174
|
+
});
|
|
175
|
+
await currentRenderTask.promise;
|
|
176
|
+
canvas.width = viewport.width;
|
|
177
|
+
canvas.height = viewport.height;
|
|
178
|
+
canvas.getContext("2d").drawImage(tmp, 0, 0);
|
|
179
|
+
} catch (e) {
|
|
180
|
+
if (e instanceof Error && e.message?.includes("Rendering cancelled")) return;
|
|
181
|
+
throw e;
|
|
182
|
+
} finally {
|
|
183
|
+
currentRenderTask = null;
|
|
184
|
+
tmp.width = 0;
|
|
185
|
+
tmp.height = 0;
|
|
186
|
+
}
|
|
187
|
+
} else {
|
|
188
|
+
canvas.width = viewport.width;
|
|
189
|
+
canvas.height = viewport.height;
|
|
190
|
+
try {
|
|
191
|
+
currentRenderTask = proxy.render({
|
|
192
|
+
canvasContext: canvas.getContext("2d"),
|
|
193
|
+
viewport,
|
|
194
|
+
annotationMode: PDFJS_ANNOTATION_MODE_DISABLE
|
|
195
|
+
});
|
|
196
|
+
await currentRenderTask.promise;
|
|
197
|
+
} catch (e) {
|
|
198
|
+
if (e instanceof Error && e.message?.includes("Rendering cancelled")) return;
|
|
199
|
+
throw e;
|
|
200
|
+
} finally {
|
|
201
|
+
currentRenderTask = null;
|
|
202
|
+
}
|
|
170
203
|
}
|
|
171
204
|
}
|
|
172
205
|
function initKonva() {
|
|
@@ -3,7 +3,12 @@ type __VLS_Props = {
|
|
|
3
3
|
pageNumber: number;
|
|
4
4
|
pageMeta: PageMeta;
|
|
5
5
|
scale: number;
|
|
6
|
+
/** Canvas internal resolution multiplier (CSS size unchanged).
|
|
7
|
+
* Used by panzoom to sharpen the PDF when visually zoomed in. */
|
|
8
|
+
renderQuality?: number;
|
|
6
9
|
};
|
|
7
|
-
declare const __VLS_export: import("vue").DefineComponent<__VLS_Props, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<__VLS_Props> & Readonly<{}>, {
|
|
10
|
+
declare const __VLS_export: import("vue").DefineComponent<__VLS_Props, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<__VLS_Props> & Readonly<{}>, {
|
|
11
|
+
renderQuality: number;
|
|
12
|
+
}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
|
|
8
13
|
declare const _default: typeof __VLS_export;
|
|
9
14
|
export default _default;
|