kviewer 0.0.8 → 0.0.9
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 +5 -2
- package/dist/runtime/annotation/engine/editor/editor.js +6 -3
- package/dist/runtime/annotation/engine/editor/selector.d.ts +11 -0
- package/dist/runtime/annotation/engine/editor/selector.js +60 -3
- package/dist/runtime/annotation/engine/painter.d.ts +3 -1
- package/dist/runtime/annotation/engine/painter.js +6 -2
- package/dist/runtime/annotation/engine/tools/freehand.js +2 -2
- package/dist/runtime/components/Viewer.d.vue.ts +3 -0
- package/dist/runtime/components/Viewer.vue +13 -2
- package/dist/runtime/components/Viewer.vue.d.ts +3 -0
- package/dist/runtime/components/form-fields/FormSignatureField.vue +23 -1
- package/dist/runtime/components/tools/DrawingTools.vue +1 -1
- package/dist/runtime/composables/useAnnotationEngine.d.ts +1 -0
- package/dist/runtime/composables/useAnnotationEngine.js +2 -1
- 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;
|
|
@@ -65,5 +68,5 @@ export declare abstract class Editor {
|
|
|
65
68
|
[pageNumber: number]: number;
|
|
66
69
|
};
|
|
67
70
|
static TimerClear(pageNumber: number): void;
|
|
68
|
-
static TimerStart(pageNumber: number, callback: (pageNumber: number) => void): void;
|
|
71
|
+
static TimerStart(pageNumber: number, callback: (pageNumber: number) => void, delay?: number): void;
|
|
69
72
|
}
|
|
@@ -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
|
}
|
|
@@ -235,11 +238,11 @@ export class Editor {
|
|
|
235
238
|
window.clearTimeout(timer);
|
|
236
239
|
}
|
|
237
240
|
}
|
|
238
|
-
static TimerStart(pageNumber, callback) {
|
|
241
|
+
static TimerStart(pageNumber, callback, delay = 1e3) {
|
|
239
242
|
Editor.Timer[pageNumber] = window.setTimeout(() => {
|
|
240
243
|
if (typeof callback === "function") {
|
|
241
244
|
callback(pageNumber);
|
|
242
245
|
}
|
|
243
|
-
},
|
|
246
|
+
}, delay);
|
|
244
247
|
}
|
|
245
248
|
}
|
|
@@ -66,6 +66,17 @@ export declare class Selector {
|
|
|
66
66
|
activateMarquee(pageNumber: number): void;
|
|
67
67
|
private bindMarqueeEvents;
|
|
68
68
|
private rectsIntersect;
|
|
69
|
+
/**
|
|
70
|
+
* Check whether a line segment intersects an axis-aligned rectangle
|
|
71
|
+
* (expanded by `tolerance` on each side to match the visual eraser width).
|
|
72
|
+
* Uses Liang-Barsky line clipping.
|
|
73
|
+
*/
|
|
74
|
+
private segmentIntersectsRect;
|
|
75
|
+
/**
|
|
76
|
+
* Check whether any segment of an eraser path intersects a rectangle.
|
|
77
|
+
* Points is a flat array [x0, y0, x1, y1, ...] in page-space coordinates.
|
|
78
|
+
*/
|
|
79
|
+
private eraserPathIntersectsRect;
|
|
69
80
|
activateEraser(pageNumber: number): void;
|
|
70
81
|
private eraserLine;
|
|
71
82
|
private eraserStage;
|
|
@@ -526,6 +526,60 @@ export class Selector {
|
|
|
526
526
|
rectsIntersect(a, b) {
|
|
527
527
|
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
528
|
}
|
|
529
|
+
/**
|
|
530
|
+
* Check whether a line segment intersects an axis-aligned rectangle
|
|
531
|
+
* (expanded by `tolerance` on each side to match the visual eraser width).
|
|
532
|
+
* Uses Liang-Barsky line clipping.
|
|
533
|
+
*/
|
|
534
|
+
segmentIntersectsRect(x1, y1, x2, y2, rect, tolerance) {
|
|
535
|
+
const left = rect.x - tolerance;
|
|
536
|
+
const top = rect.y - tolerance;
|
|
537
|
+
const right = rect.x + rect.width + tolerance;
|
|
538
|
+
const bottom = rect.y + rect.height + tolerance;
|
|
539
|
+
let tMin = 0;
|
|
540
|
+
let tMax = 1;
|
|
541
|
+
const dx = x2 - x1;
|
|
542
|
+
const dy = y2 - y1;
|
|
543
|
+
const edges = [
|
|
544
|
+
{ p: -dx, q: x1 - left },
|
|
545
|
+
{ p: dx, q: right - x1 },
|
|
546
|
+
{ p: -dy, q: y1 - top },
|
|
547
|
+
{ p: dy, q: bottom - y1 }
|
|
548
|
+
];
|
|
549
|
+
for (const { p, q } of edges) {
|
|
550
|
+
if (p === 0) {
|
|
551
|
+
if (q < 0) return false;
|
|
552
|
+
} else {
|
|
553
|
+
const t = q / p;
|
|
554
|
+
if (p < 0) {
|
|
555
|
+
tMin = Math.max(tMin, t);
|
|
556
|
+
} else {
|
|
557
|
+
tMax = Math.min(tMax, t);
|
|
558
|
+
}
|
|
559
|
+
if (tMin > tMax) return false;
|
|
560
|
+
}
|
|
561
|
+
}
|
|
562
|
+
return true;
|
|
563
|
+
}
|
|
564
|
+
/**
|
|
565
|
+
* Check whether any segment of an eraser path intersects a rectangle.
|
|
566
|
+
* Points is a flat array [x0, y0, x1, y1, ...] in page-space coordinates.
|
|
567
|
+
*/
|
|
568
|
+
eraserPathIntersectsRect(points, rect, tolerance) {
|
|
569
|
+
for (let i = 0; i + 3 < points.length; i += 2) {
|
|
570
|
+
if (this.segmentIntersectsRect(
|
|
571
|
+
points[i],
|
|
572
|
+
points[i + 1],
|
|
573
|
+
points[i + 2],
|
|
574
|
+
points[i + 3],
|
|
575
|
+
rect,
|
|
576
|
+
tolerance
|
|
577
|
+
)) {
|
|
578
|
+
return true;
|
|
579
|
+
}
|
|
580
|
+
}
|
|
581
|
+
return false;
|
|
582
|
+
}
|
|
529
583
|
activateEraser(pageNumber) {
|
|
530
584
|
const konvaCanvas = this.konvaCanvasStore.get(pageNumber);
|
|
531
585
|
if (!konvaCanvas) return;
|
|
@@ -569,17 +623,20 @@ export class Selector {
|
|
|
569
623
|
konvaStage.on("mouseup touchend", (e) => {
|
|
570
624
|
if (!this.eraserLine || !this.eraserStage) return;
|
|
571
625
|
if (this.isStylusRejected(e.evt)) return;
|
|
572
|
-
const
|
|
626
|
+
const points = this.eraserLine.points();
|
|
573
627
|
this.eraserLine.destroy();
|
|
574
628
|
this.eraserLine = null;
|
|
575
|
-
if (
|
|
629
|
+
if (points.length < 4) {
|
|
576
630
|
this.eraserStage = null;
|
|
577
631
|
return;
|
|
578
632
|
}
|
|
633
|
+
const scale = this.eraserStage.scaleX() ?? 1;
|
|
634
|
+
const screenPoints = points.map((v) => v * scale);
|
|
635
|
+
const tolerance = 6;
|
|
579
636
|
const groups = this.getPageShapeGroups(this.eraserStage);
|
|
580
637
|
const toDelete = [];
|
|
581
638
|
groups.forEach((group) => {
|
|
582
|
-
if (this.
|
|
639
|
+
if (this.eraserPathIntersectsRect(screenPoints, group.getClientRect(), tolerance)) {
|
|
583
640
|
toDelete.push(group.id());
|
|
584
641
|
}
|
|
585
642
|
});
|
|
@@ -32,11 +32,13 @@ 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, }: {
|
|
36
37
|
userName: string;
|
|
37
38
|
callbacks: PainterCallbacks;
|
|
38
39
|
getStylusModeEnabled?: () => boolean;
|
|
39
40
|
getReadonly?: () => boolean;
|
|
41
|
+
freehandGroupingDelay?: number;
|
|
40
42
|
});
|
|
41
43
|
private editFreeText;
|
|
42
44
|
private bindGlobalEvents;
|
|
@@ -34,16 +34,19 @@ export class Painter {
|
|
|
34
34
|
callbacks;
|
|
35
35
|
getStylusModeEnabled;
|
|
36
36
|
getReadonly;
|
|
37
|
+
freehandGroupingDelay;
|
|
37
38
|
constructor({
|
|
38
39
|
userName,
|
|
39
40
|
callbacks,
|
|
40
41
|
getStylusModeEnabled,
|
|
41
|
-
getReadonly
|
|
42
|
+
getReadonly,
|
|
43
|
+
freehandGroupingDelay
|
|
42
44
|
}) {
|
|
43
45
|
this.userName = userName;
|
|
44
46
|
this.callbacks = callbacks;
|
|
45
47
|
this.getStylusModeEnabled = getStylusModeEnabled;
|
|
46
48
|
this.getReadonly = getReadonly;
|
|
49
|
+
this.freehandGroupingDelay = freehandGroupingDelay ?? 1e3;
|
|
47
50
|
this.store = new Store();
|
|
48
51
|
this.selector = new Selector({
|
|
49
52
|
konvaCanvasStore: this.konvaCanvasStore,
|
|
@@ -244,7 +247,8 @@ export class Painter {
|
|
|
244
247
|
onChange: (id, updates) => {
|
|
245
248
|
this.updateStore(id, updates);
|
|
246
249
|
},
|
|
247
|
-
getStylusModeEnabled: this.getStylusModeEnabled
|
|
250
|
+
getStylusModeEnabled: this.getStylusModeEnabled,
|
|
251
|
+
freehandGroupingDelay: this.freehandGroupingDelay
|
|
248
252
|
};
|
|
249
253
|
let editor = null;
|
|
250
254
|
switch (annotation.type) {
|
|
@@ -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
|
/**
|
|
@@ -15,6 +15,8 @@ type __VLS_Props = {
|
|
|
15
15
|
shapeDetection?: boolean;
|
|
16
16
|
/** When false, global keyboard shortcuts (e.g. Cmd+F) are suppressed. Used by ViewerTabs to prevent hidden viewers from capturing input. */
|
|
17
17
|
active?: boolean;
|
|
18
|
+
/** Delay in ms before consecutive freehand strokes are finalized as a single annotation. Set to 0 to disable grouping. Default: 1000. */
|
|
19
|
+
freehandGroupingDelay?: number;
|
|
18
20
|
};
|
|
19
21
|
declare function exportPdf(options?: ExportPdfOptions): Promise<Uint8Array>;
|
|
20
22
|
type ImportMode = 'replace' | 'merge';
|
|
@@ -40,6 +42,7 @@ declare const __VLS_base: import("vue").DefineComponent<__VLS_Props, {
|
|
|
40
42
|
setFormFieldValue: (fieldName: string, value: string | boolean | string[]) => void;
|
|
41
43
|
}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<__VLS_Props> & Readonly<{}>, {
|
|
42
44
|
userName: string;
|
|
45
|
+
freehandGroupingDelay: number;
|
|
43
46
|
zoom: number;
|
|
44
47
|
readonly: boolean;
|
|
45
48
|
active: boolean;
|
|
@@ -185,7 +185,8 @@ const props = defineProps({
|
|
|
185
185
|
zoom: { type: Number, required: false, default: 1 },
|
|
186
186
|
readonly: { type: Boolean, required: false, default: false },
|
|
187
187
|
shapeDetection: { type: Boolean, required: false, default: false },
|
|
188
|
-
active: { type: Boolean, required: false, default: true }
|
|
188
|
+
active: { type: Boolean, required: false, default: true },
|
|
189
|
+
freehandGroupingDelay: { type: Number, required: false, default: 1e3 }
|
|
189
190
|
});
|
|
190
191
|
const viewerRoot = ref(null);
|
|
191
192
|
const scrollContainer = ref(null);
|
|
@@ -609,7 +610,8 @@ function onGlobalKeydown(event) {
|
|
|
609
610
|
onMounted(() => {
|
|
610
611
|
painter = createAnnotationEngine(viewerState, {
|
|
611
612
|
userName: props.userName,
|
|
612
|
-
onRequestTextInput
|
|
613
|
+
onRequestTextInput,
|
|
614
|
+
freehandGroupingDelay: props.freehandGroupingDelay
|
|
613
615
|
});
|
|
614
616
|
viewerState.selectTool(viewerState.activeTool.value);
|
|
615
617
|
if (props.stamps) viewerState.stamps.value = props.stamps;
|
|
@@ -674,6 +676,15 @@ watch(
|
|
|
674
676
|
viewerState.stamps.value = newStamps ?? [];
|
|
675
677
|
}
|
|
676
678
|
);
|
|
679
|
+
watch(
|
|
680
|
+
() => props.signatureHandlers,
|
|
681
|
+
(newHandlers) => {
|
|
682
|
+
viewerState.signatureHandlers = newHandlers ?? null;
|
|
683
|
+
if (newHandlers) {
|
|
684
|
+
viewerState.loadSignatures();
|
|
685
|
+
}
|
|
686
|
+
}
|
|
687
|
+
);
|
|
677
688
|
onBeforeUnmount(() => {
|
|
678
689
|
window.removeEventListener("keydown", onGlobalKeydown);
|
|
679
690
|
scrollContainer.value?.removeEventListener("wheel", onSinglePageWheel);
|
|
@@ -15,6 +15,8 @@ type __VLS_Props = {
|
|
|
15
15
|
shapeDetection?: boolean;
|
|
16
16
|
/** When false, global keyboard shortcuts (e.g. Cmd+F) are suppressed. Used by ViewerTabs to prevent hidden viewers from capturing input. */
|
|
17
17
|
active?: boolean;
|
|
18
|
+
/** Delay in ms before consecutive freehand strokes are finalized as a single annotation. Set to 0 to disable grouping. Default: 1000. */
|
|
19
|
+
freehandGroupingDelay?: number;
|
|
18
20
|
};
|
|
19
21
|
declare function exportPdf(options?: ExportPdfOptions): Promise<Uint8Array>;
|
|
20
22
|
type ImportMode = 'replace' | 'merge';
|
|
@@ -40,6 +42,7 @@ declare const __VLS_base: import("vue").DefineComponent<__VLS_Props, {
|
|
|
40
42
|
setFormFieldValue: (fieldName: string, value: string | boolean | string[]) => void;
|
|
41
43
|
}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<__VLS_Props> & Readonly<{}>, {
|
|
42
44
|
userName: string;
|
|
45
|
+
freehandGroupingDelay: number;
|
|
43
46
|
zoom: number;
|
|
44
47
|
readonly: boolean;
|
|
45
48
|
active: boolean;
|
|
@@ -14,17 +14,27 @@
|
|
|
14
14
|
Click to sign
|
|
15
15
|
</span>
|
|
16
16
|
</div>
|
|
17
|
+
|
|
18
|
+
<ClientOnly>
|
|
19
|
+
<SignatureDrawModal
|
|
20
|
+
v-model:open="drawModalOpen"
|
|
21
|
+
@submit="onDrawSubmit"
|
|
22
|
+
@cancel="drawModalOpen = false"
|
|
23
|
+
/>
|
|
24
|
+
</ClientOnly>
|
|
17
25
|
</template>
|
|
18
26
|
|
|
19
27
|
<script setup>
|
|
20
|
-
import { computed } from "vue";
|
|
28
|
+
import { ref, computed } from "vue";
|
|
21
29
|
import { useFormFields } from "../../composables/useFormFields";
|
|
22
30
|
import { useViewerState } from "../../composables/useViewerState";
|
|
31
|
+
import SignatureDrawModal from "../modals/SignatureDrawModal.vue";
|
|
23
32
|
const props = defineProps({
|
|
24
33
|
field: { type: Object, required: true }
|
|
25
34
|
});
|
|
26
35
|
const formFields = useFormFields();
|
|
27
36
|
const state = useViewerState();
|
|
37
|
+
const drawModalOpen = ref(false);
|
|
28
38
|
const signatureUrl = computed(() => {
|
|
29
39
|
const fv = formFields.getFieldValue(props.field.id);
|
|
30
40
|
if (!fv || typeof fv.value !== "string" || fv.value === "") return null;
|
|
@@ -50,5 +60,17 @@ async function onClickSign() {
|
|
|
50
60
|
}
|
|
51
61
|
}
|
|
52
62
|
}
|
|
63
|
+
drawModalOpen.value = true;
|
|
64
|
+
}
|
|
65
|
+
async function onDrawSubmit(imageUrl) {
|
|
66
|
+
drawModalOpen.value = false;
|
|
67
|
+
if (state.signatureHandlers) {
|
|
68
|
+
const saved = await state.saveSignature(imageUrl);
|
|
69
|
+
if (saved) {
|
|
70
|
+
formFields.setFieldValue(props.field.id, saved.imageUrl);
|
|
71
|
+
return;
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
formFields.setFieldValue(props.field.id, imageUrl);
|
|
53
75
|
}
|
|
54
76
|
</script>
|
|
@@ -18,7 +18,7 @@
|
|
|
18
18
|
@click="state.selectTool(AnnotationType.FREETEXT)"
|
|
19
19
|
/>
|
|
20
20
|
<StampPicker />
|
|
21
|
-
<SignaturePicker />
|
|
21
|
+
<SignaturePicker v-if="state.signatureHandlers" />
|
|
22
22
|
<ToolButton
|
|
23
23
|
:tool="getDef(AnnotationType.RECTANGLE)"
|
|
24
24
|
:active="state.activeTool.value === AnnotationType.RECTANGLE"
|
|
@@ -4,4 +4,5 @@ export declare function createAnnotationEngine(viewerState: ViewerState, options
|
|
|
4
4
|
userName?: string;
|
|
5
5
|
onRequestTextInput: PainterCallbacks['onRequestTextInput'];
|
|
6
6
|
onRequestDeleteConfirm?: PainterCallbacks['onRequestDeleteConfirm'];
|
|
7
|
+
freehandGroupingDelay?: number;
|
|
7
8
|
}): Painter;
|
|
@@ -64,7 +64,8 @@ export function createAnnotationEngine(viewerState, options) {
|
|
|
64
64
|
userName: options.userName ?? "User",
|
|
65
65
|
callbacks,
|
|
66
66
|
getStylusModeEnabled: () => viewerState.stylusMode.value,
|
|
67
|
-
getReadonly: () => viewerState.readonly.value
|
|
67
|
+
getReadonly: () => viewerState.readonly.value,
|
|
68
|
+
freehandGroupingDelay: options.freehandGroupingDelay
|
|
68
69
|
});
|
|
69
70
|
viewerState.painter.value = painter;
|
|
70
71
|
return painter;
|
|
@@ -1,2 +1,4 @@
|
|
|
1
1
|
export type { ExportPdfOptions } from './annotation/pdf-export/export.js';
|
|
2
2
|
export type { ViewerTabItem, AddTabOptions } from './components/ViewerTabs.vue.js';
|
|
3
|
+
export type { SignatureData, SignatureHandlers } from './annotation/engine/types.js';
|
|
4
|
+
export type { ViewMode } from './composables/viewMode.js';
|
package/dist/types.d.mts
CHANGED