kviewer 0.3.0 → 0.3.2
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/module.mjs +13 -0
- package/dist/runtime/annotation/engine/config.d.ts +1 -0
- package/dist/runtime/annotation/engine/config.js +4 -0
- package/dist/runtime/annotation/engine/cursor-preview.d.ts +14 -1
- package/dist/runtime/annotation/engine/cursor-preview.js +32 -6
- package/dist/runtime/annotation/engine/editor/editor.js +10 -12
- package/dist/runtime/annotation/engine/editor/selector.js +3 -1
- package/dist/runtime/annotation/engine/tools/arrow.js +24 -14
- package/dist/runtime/annotation/engine/tools/circle.js +24 -13
- package/dist/runtime/annotation/engine/tools/cloud.js +27 -18
- package/dist/runtime/annotation/engine/tools/free-highlight.js +24 -15
- package/dist/runtime/annotation/engine/tools/freehand.js +21 -12
- package/dist/runtime/annotation/engine/tools/highlight.d.ts +2 -0
- package/dist/runtime/annotation/engine/tools/highlight.js +20 -10
- package/dist/runtime/annotation/engine/tools/note.js +3 -1
- package/dist/runtime/annotation/engine/tools/rectangle.js +19 -8
- package/dist/runtime/annotation/engine/tools/signature.js +31 -5
- package/dist/runtime/annotation/engine/tools/stamp.d.ts +1 -0
- package/dist/runtime/annotation/engine/tools/stamp.js +43 -15
- package/dist/runtime/annotation/engine/utils.d.ts +1 -4
- package/dist/runtime/annotation/engine/utils.js +3 -11
- package/dist/runtime/annotation/pdf-export/download.js +1 -1
- package/dist/runtime/annotation/pdf-export/export.js +1 -1
- package/dist/runtime/annotation/pdf-export/parse_polyline.js +5 -1
- package/dist/runtime/annotation/pdf-export/parse_stamp.js +4 -4
- package/dist/runtime/annotation/pdf-export/unicode-font.js +1 -4
- package/dist/runtime/annotation/pdf-import/decode_stamp.js +3 -2
- package/dist/runtime/annotation/pdf-import/extract_stamp_appearance.js +1 -1
- package/dist/runtime/annotation/pdf-import/utils.js +1 -1
- package/dist/runtime/annotation/utils/placement_size.d.ts +27 -0
- package/dist/runtime/annotation/utils/placement_size.js +26 -0
- package/dist/runtime/components/FormFieldLayer.d.vue.ts +1 -1
- package/dist/runtime/components/FormFieldLayer.vue.d.ts +1 -1
- package/dist/runtime/components/PdfPage.vue +3 -3
- package/dist/runtime/components/Viewer.vue +3 -1
- package/dist/runtime/components/ViewerTabs.vue +1 -1
- package/dist/runtime/components/panels/SignaturePicker.vue +8 -2
- package/dist/runtime/composables/useSearchIndex.js +1 -1
- package/dist/runtime/embed/bridge-host.d.ts +1 -1
- package/dist/runtime/embed/bridge-host.js +3 -3
- package/dist/runtime/imports.d.ts +19 -0
- package/dist/types.d.mts +1 -1
- package/package.json +2 -2
|
@@ -2,6 +2,15 @@ import Konva from "konva";
|
|
|
2
2
|
import { AnnotationType } from "../types.js";
|
|
3
3
|
import { Editor } from "../editor/editor.js";
|
|
4
4
|
export class EditorHighLight extends Editor {
|
|
5
|
+
get activeAnnotation() {
|
|
6
|
+
if (!this.currentAnnotation) throw new Error("Highlight editor is not active");
|
|
7
|
+
return this.currentAnnotation;
|
|
8
|
+
}
|
|
9
|
+
get activeStyle() {
|
|
10
|
+
const style = this.activeAnnotation.style;
|
|
11
|
+
if (!style) throw new Error("Highlight annotation has no style");
|
|
12
|
+
return style;
|
|
13
|
+
}
|
|
5
14
|
/**
|
|
6
15
|
* Creates an EditorHighLight instance.
|
|
7
16
|
* @param EditorOptions Options used to initialize the editor
|
|
@@ -16,8 +25,9 @@ export class EditorHighLight extends Editor {
|
|
|
16
25
|
* @param fixElement Element used as the positioning reference
|
|
17
26
|
*/
|
|
18
27
|
convertTextSelection(elements, fixElement) {
|
|
19
|
-
|
|
20
|
-
this.
|
|
28
|
+
const shapeGroup = this.createShapeGroup();
|
|
29
|
+
this.currentShapeGroup = shapeGroup;
|
|
30
|
+
this.getBgLayer().add(shapeGroup.konvaGroup);
|
|
21
31
|
const fixBounding = fixElement.getBoundingClientRect();
|
|
22
32
|
elements.forEach((spanEl) => {
|
|
23
33
|
const bounding = spanEl.getBoundingClientRect();
|
|
@@ -26,14 +36,14 @@ export class EditorHighLight extends Editor {
|
|
|
26
36
|
fixBounding
|
|
27
37
|
);
|
|
28
38
|
const shape = this.createShape(x, y, width, height);
|
|
29
|
-
|
|
39
|
+
shapeGroup.konvaGroup.add(shape);
|
|
30
40
|
});
|
|
31
41
|
this.setShapeGroupDone({
|
|
32
|
-
id:
|
|
42
|
+
id: shapeGroup.id,
|
|
33
43
|
contentsObj: {
|
|
34
44
|
text: this.getElementOuterText(elements)
|
|
35
45
|
},
|
|
36
|
-
color: this.
|
|
46
|
+
color: this.activeStyle.color
|
|
37
47
|
});
|
|
38
48
|
}
|
|
39
49
|
/**
|
|
@@ -67,7 +77,7 @@ export class EditorHighLight extends Editor {
|
|
|
67
77
|
* @returns A concrete Konva.Shape instance
|
|
68
78
|
*/
|
|
69
79
|
createShape(x, y, width, height) {
|
|
70
|
-
switch (this.
|
|
80
|
+
switch (this.activeAnnotation.type) {
|
|
71
81
|
case AnnotationType.HIGHLIGHT:
|
|
72
82
|
return this.createHighlightShape(x, y, width, height);
|
|
73
83
|
case AnnotationType.UNDERLINE:
|
|
@@ -76,7 +86,7 @@ export class EditorHighLight extends Editor {
|
|
|
76
86
|
return this.createStrikeoutShape(x, y, width, height);
|
|
77
87
|
default:
|
|
78
88
|
throw new Error(
|
|
79
|
-
`Unsupported annotation type: ${this.
|
|
89
|
+
`Unsupported annotation type: ${this.activeAnnotation.type}`
|
|
80
90
|
);
|
|
81
91
|
}
|
|
82
92
|
}
|
|
@@ -95,7 +105,7 @@ export class EditorHighLight extends Editor {
|
|
|
95
105
|
width,
|
|
96
106
|
height,
|
|
97
107
|
opacity: 0.5,
|
|
98
|
-
fill: this.
|
|
108
|
+
fill: this.activeStyle.color
|
|
99
109
|
});
|
|
100
110
|
}
|
|
101
111
|
/**
|
|
@@ -111,7 +121,7 @@ export class EditorHighLight extends Editor {
|
|
|
111
121
|
x,
|
|
112
122
|
y: height + y - 2,
|
|
113
123
|
width,
|
|
114
|
-
stroke: this.
|
|
124
|
+
stroke: this.activeStyle.color,
|
|
115
125
|
opacity: 1,
|
|
116
126
|
strokeWidth: 1,
|
|
117
127
|
hitStrokeWidth: 10,
|
|
@@ -131,7 +141,7 @@ export class EditorHighLight extends Editor {
|
|
|
131
141
|
x,
|
|
132
142
|
y: y + height / 2,
|
|
133
143
|
width,
|
|
134
|
-
stroke: this.
|
|
144
|
+
stroke: this.activeStyle.color,
|
|
135
145
|
opacity: 1,
|
|
136
146
|
strokeWidth: 1,
|
|
137
147
|
hitStrokeWidth: 10,
|
|
@@ -11,10 +11,12 @@ export class EditorNote extends Editor {
|
|
|
11
11
|
}
|
|
12
12
|
async mouseUpHandler(e) {
|
|
13
13
|
const color = "rgb(255, 222, 33)";
|
|
14
|
-
const { x, y } = this.konvaStage.getRelativePointerPosition();
|
|
15
14
|
if (e.currentTarget !== this.konvaStage) {
|
|
16
15
|
return;
|
|
17
16
|
}
|
|
17
|
+
const pos = this.konvaStage.getRelativePointerPosition();
|
|
18
|
+
if (!pos) return;
|
|
19
|
+
const { x, y } = pos;
|
|
18
20
|
this.isPainting = true;
|
|
19
21
|
this.currentShapeGroup = this.createShapeGroup();
|
|
20
22
|
this.getBgLayer().add(this.currentShapeGroup.konvaGroup);
|
|
@@ -23,11 +23,15 @@ export class EditorRectangle extends Editor {
|
|
|
23
23
|
if (e.currentTarget !== this.konvaStage) {
|
|
24
24
|
return;
|
|
25
25
|
}
|
|
26
|
+
const pos = this.konvaStage.getRelativePointerPosition();
|
|
27
|
+
const annotation = this.currentAnnotation;
|
|
28
|
+
const style = annotation?.style;
|
|
29
|
+
if (!pos || !annotation || !style) return;
|
|
26
30
|
this.rect = null;
|
|
27
31
|
this.isPainting = true;
|
|
28
|
-
|
|
29
|
-
this.
|
|
30
|
-
|
|
32
|
+
const shapeGroup = this.createShapeGroup();
|
|
33
|
+
this.currentShapeGroup = shapeGroup;
|
|
34
|
+
this.getBgLayer().add(shapeGroup.konvaGroup);
|
|
31
35
|
this.vertex = { x: pos.x, y: pos.y };
|
|
32
36
|
this.rect = new Konva.Rect({
|
|
33
37
|
x: pos.x,
|
|
@@ -35,11 +39,11 @@ export class EditorRectangle extends Editor {
|
|
|
35
39
|
width: 0,
|
|
36
40
|
height: 0,
|
|
37
41
|
visible: false,
|
|
38
|
-
stroke:
|
|
39
|
-
strokeWidth:
|
|
40
|
-
opacity:
|
|
42
|
+
stroke: style.color,
|
|
43
|
+
strokeWidth: style.strokeWidth || 2,
|
|
44
|
+
opacity: style.opacity
|
|
41
45
|
});
|
|
42
|
-
|
|
46
|
+
shapeGroup.konvaGroup.add(this.rect);
|
|
43
47
|
window.addEventListener("mouseup", this.globalPointerUpHandler);
|
|
44
48
|
}
|
|
45
49
|
/**
|
|
@@ -51,8 +55,10 @@ export class EditorRectangle extends Editor {
|
|
|
51
55
|
return;
|
|
52
56
|
}
|
|
53
57
|
e.evt.preventDefault();
|
|
58
|
+
if (!this.rect) return;
|
|
54
59
|
this.rect.show();
|
|
55
60
|
const pos = this.konvaStage.getRelativePointerPosition();
|
|
61
|
+
if (!pos) return;
|
|
56
62
|
const areaAttr = {
|
|
57
63
|
x: Math.min(this.vertex.x, pos.x),
|
|
58
64
|
y: Math.min(this.vertex.y, pos.y),
|
|
@@ -69,7 +75,9 @@ export class EditorRectangle extends Editor {
|
|
|
69
75
|
return;
|
|
70
76
|
}
|
|
71
77
|
this.isPainting = false;
|
|
78
|
+
if (!this.rect) return;
|
|
72
79
|
const group = this.rect.getParent();
|
|
80
|
+
if (!group) return;
|
|
73
81
|
if (!this.rect.isVisible() && group.getType() === "Group") {
|
|
74
82
|
this.delShapeGroup(group.id());
|
|
75
83
|
return;
|
|
@@ -80,9 +88,11 @@ export class EditorRectangle extends Editor {
|
|
|
80
88
|
this.rect = null;
|
|
81
89
|
return;
|
|
82
90
|
}
|
|
91
|
+
const style = this.currentAnnotation?.style;
|
|
92
|
+
if (!style) return;
|
|
83
93
|
this.setShapeGroupDone({
|
|
84
94
|
id: group.id(),
|
|
85
|
-
color:
|
|
95
|
+
color: style.color,
|
|
86
96
|
contentsObj: {
|
|
87
97
|
text: ""
|
|
88
98
|
}
|
|
@@ -103,6 +113,7 @@ export class EditorRectangle extends Editor {
|
|
|
103
113
|
* @returns true if the rectangle is too small, false otherwise
|
|
104
114
|
*/
|
|
105
115
|
isTooSmall() {
|
|
116
|
+
if (!this.rect) return true;
|
|
106
117
|
const { width, height } = this.rect.size();
|
|
107
118
|
return Math.max(width, height) < Editor.MinSize;
|
|
108
119
|
}
|
|
@@ -1,12 +1,17 @@
|
|
|
1
1
|
import Konva from "konva";
|
|
2
2
|
import { AnnotationType } from "../types.js";
|
|
3
|
-
import { resizeImage } from "../utils.js";
|
|
4
3
|
import { Editor } from "../editor/editor.js";
|
|
5
4
|
import { defaultOptions } from "../config.js";
|
|
6
|
-
import {
|
|
5
|
+
import {
|
|
6
|
+
cursorPreviewToken,
|
|
7
|
+
measureImage,
|
|
8
|
+
peekImageSize,
|
|
9
|
+
showCursorPreview
|
|
10
|
+
} from "../cursor-preview.js";
|
|
11
|
+
import { cursorPreviewSize, placementSize } from "../../utils/placement_size.js";
|
|
7
12
|
export class EditorSignature extends Editor {
|
|
8
13
|
signatureUrl;
|
|
9
|
-
signatureImage;
|
|
14
|
+
signatureImage = null;
|
|
10
15
|
constructor(editorOptions, defaultSignatureUrl) {
|
|
11
16
|
super({ ...editorOptions, editorType: AnnotationType.SIGNATURE });
|
|
12
17
|
this.signatureUrl = defaultSignatureUrl;
|
|
@@ -15,7 +20,23 @@ export class EditorSignature extends Editor {
|
|
|
15
20
|
}
|
|
16
21
|
}
|
|
17
22
|
createCursorImg() {
|
|
18
|
-
|
|
23
|
+
const scale = this.konvaStage?.scale()?.x ?? 1;
|
|
24
|
+
const max = defaultOptions.setting.MAX_PLACEMENT_SIZE;
|
|
25
|
+
const cached = cursorPreviewSize(null, peekImageSize(this.signatureUrl), max, scale);
|
|
26
|
+
if (cached) {
|
|
27
|
+
showCursorPreview(this.signatureUrl, cached.width, cached.height);
|
|
28
|
+
return;
|
|
29
|
+
}
|
|
30
|
+
const token = cursorPreviewToken();
|
|
31
|
+
void measureImage(this.signatureUrl).then((natural) => {
|
|
32
|
+
if (cursorPreviewToken() !== token) return;
|
|
33
|
+
const size = cursorPreviewSize(null, natural, max, scale);
|
|
34
|
+
if (size) {
|
|
35
|
+
showCursorPreview(this.signatureUrl, size.width, size.height);
|
|
36
|
+
} else {
|
|
37
|
+
showCursorPreview(this.signatureUrl, defaultOptions.setting.MAX_CURSOR_SIZE);
|
|
38
|
+
}
|
|
39
|
+
});
|
|
19
40
|
}
|
|
20
41
|
mouseDownHandler(e) {
|
|
21
42
|
if (e.currentTarget !== this.konvaStage) return;
|
|
@@ -25,10 +46,15 @@ export class EditorSignature extends Editor {
|
|
|
25
46
|
const pos = this.konvaStage.getRelativePointerPosition();
|
|
26
47
|
Konva.Image.fromURL(this.signatureUrl, async (image) => {
|
|
27
48
|
const { width: width_rec, height: height_rec } = image.getClientRect();
|
|
28
|
-
const { newWidth, newHeight } =
|
|
49
|
+
const { width: newWidth, height: newHeight } = placementSize(
|
|
50
|
+
null,
|
|
51
|
+
{ width: width_rec, height: height_rec },
|
|
52
|
+
defaultOptions.setting.MAX_PLACEMENT_SIZE
|
|
53
|
+
) ?? { width: width_rec, height: height_rec };
|
|
29
54
|
const crosshair = { x: newWidth / 2, y: newHeight / 2 };
|
|
30
55
|
this.signatureImage = image;
|
|
31
56
|
this.signatureImage.setAttrs({
|
|
57
|
+
image: image.image(),
|
|
32
58
|
x: pos.x - crosshair.x,
|
|
33
59
|
y: pos.y - crosshair.y,
|
|
34
60
|
width: newWidth,
|
|
@@ -8,6 +8,7 @@ export declare class EditorStamp extends Editor {
|
|
|
8
8
|
private stampWidth;
|
|
9
9
|
private stampHeight;
|
|
10
10
|
constructor(editorOptions: IEditorOptions, defaultStampUrl: string | null);
|
|
11
|
+
private get explicitSize();
|
|
11
12
|
private createCursorImg;
|
|
12
13
|
/**
|
|
13
14
|
* True when an identical stamp (same source image) already sits under the
|
|
@@ -1,12 +1,20 @@
|
|
|
1
1
|
import Konva from "konva";
|
|
2
2
|
import { AnnotationType } from "../types.js";
|
|
3
|
-
import { resizeImage } from "../utils.js";
|
|
4
3
|
import { Editor } from "../editor/editor.js";
|
|
5
4
|
import { defaultOptions } from "../config.js";
|
|
6
|
-
import {
|
|
5
|
+
import {
|
|
6
|
+
cursorPreviewToken,
|
|
7
|
+
measureImage,
|
|
8
|
+
peekImageSize,
|
|
9
|
+
showCursorPreview
|
|
10
|
+
} from "../cursor-preview.js";
|
|
11
|
+
import {
|
|
12
|
+
cursorPreviewSize,
|
|
13
|
+
placementSize
|
|
14
|
+
} from "../../utils/placement_size.js";
|
|
7
15
|
export class EditorStamp extends Editor {
|
|
8
16
|
stampUrl;
|
|
9
|
-
stampImage;
|
|
17
|
+
stampImage = null;
|
|
10
18
|
stampWidth = null;
|
|
11
19
|
stampHeight = null;
|
|
12
20
|
constructor(editorOptions, defaultStampUrl) {
|
|
@@ -16,10 +24,32 @@ export class EditorStamp extends Editor {
|
|
|
16
24
|
this.createCursorImg();
|
|
17
25
|
}
|
|
18
26
|
}
|
|
27
|
+
get explicitSize() {
|
|
28
|
+
return { width: this.stampWidth ?? 0, height: this.stampHeight ?? 0 };
|
|
29
|
+
}
|
|
19
30
|
createCursorImg() {
|
|
20
|
-
const baseSize = this.stampWidth ?? this.stampHeight ?? defaultOptions.setting.MAX_CURSOR_SIZE;
|
|
21
31
|
const scale = this.konvaStage?.scale()?.x ?? 1;
|
|
22
|
-
|
|
32
|
+
const max = defaultOptions.setting.MAX_PLACEMENT_SIZE;
|
|
33
|
+
const immediate = cursorPreviewSize(
|
|
34
|
+
this.explicitSize,
|
|
35
|
+
peekImageSize(this.stampUrl),
|
|
36
|
+
max,
|
|
37
|
+
scale
|
|
38
|
+
);
|
|
39
|
+
if (immediate) {
|
|
40
|
+
showCursorPreview(this.stampUrl, immediate.width, immediate.height);
|
|
41
|
+
return;
|
|
42
|
+
}
|
|
43
|
+
const token = cursorPreviewToken();
|
|
44
|
+
void measureImage(this.stampUrl).then((natural) => {
|
|
45
|
+
if (cursorPreviewToken() !== token) return;
|
|
46
|
+
const size = cursorPreviewSize(this.explicitSize, natural, max, scale);
|
|
47
|
+
if (size) {
|
|
48
|
+
showCursorPreview(this.stampUrl, size.width, size.height);
|
|
49
|
+
} else {
|
|
50
|
+
showCursorPreview(this.stampUrl, defaultOptions.setting.MAX_CURSOR_SIZE);
|
|
51
|
+
}
|
|
52
|
+
});
|
|
23
53
|
}
|
|
24
54
|
/**
|
|
25
55
|
* True when an identical stamp (same source image) already sits under the
|
|
@@ -47,24 +77,22 @@ export class EditorStamp extends Editor {
|
|
|
47
77
|
if (e.currentTarget !== this.konvaStage) return;
|
|
48
78
|
const pos = this.konvaStage.getRelativePointerPosition();
|
|
49
79
|
if (!pos) return;
|
|
50
|
-
const estimatedSize = this.stampWidth ?? this.stampHeight ??
|
|
80
|
+
const estimatedSize = this.stampWidth ?? this.stampHeight ?? defaultOptions.setting.MAX_PLACEMENT_SIZE;
|
|
51
81
|
if (this.hasIdenticalStampNear(pos, estimatedSize)) return;
|
|
52
82
|
this.stampImage = null;
|
|
53
83
|
this.currentShapeGroup = this.createShapeGroup();
|
|
54
84
|
this.getBgLayer().add(this.currentShapeGroup.konvaGroup);
|
|
55
85
|
Konva.Image.fromURL(this.stampUrl, async (image) => {
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
const { width: width_rec, height: height_rec } = image.getClientRect();
|
|
63
|
-
({ newWidth, newHeight } = resizeImage(width_rec, height_rec, 120));
|
|
64
|
-
}
|
|
86
|
+
const { width: width_rec, height: height_rec } = image.getClientRect();
|
|
87
|
+
const { width: newWidth, height: newHeight } = placementSize(
|
|
88
|
+
this.explicitSize,
|
|
89
|
+
{ width: width_rec, height: height_rec },
|
|
90
|
+
defaultOptions.setting.MAX_PLACEMENT_SIZE
|
|
91
|
+
) ?? { width: width_rec, height: height_rec };
|
|
65
92
|
const crosshair = { x: newWidth / 2, y: newHeight / 2 };
|
|
66
93
|
this.stampImage = image;
|
|
67
94
|
this.stampImage.setAttrs({
|
|
95
|
+
image: image.image(),
|
|
68
96
|
x: pos.x - crosshair.x,
|
|
69
97
|
y: pos.y - crosshair.y,
|
|
70
98
|
width: newWidth,
|
|
@@ -7,10 +7,7 @@ export declare function generateUUID(): string;
|
|
|
7
7
|
export declare function setCssCustomProperty(propertyName: string, value: string): void;
|
|
8
8
|
export declare function removeCssCustomProperty(propertyName: string): void;
|
|
9
9
|
export declare function base64ToImageBitmap(base64: string): Promise<ImageBitmap>;
|
|
10
|
-
export
|
|
11
|
-
newWidth: number;
|
|
12
|
-
newHeight: number;
|
|
13
|
-
};
|
|
10
|
+
export { resizeImage } from '../utils/placement_size.js';
|
|
14
11
|
export declare function convertToRGB(array: number[], index?: number): string;
|
|
15
12
|
export declare function formatTimestamp(timestamp: number): string;
|
|
16
13
|
export declare function formatPDFDate(dateString: string | null, full?: boolean): string;
|
|
@@ -58,6 +58,7 @@ export function removeCssCustomProperty(propertyName) {
|
|
|
58
58
|
}
|
|
59
59
|
export async function base64ToImageBitmap(base64) {
|
|
60
60
|
const base64Data = base64.split(",")[1];
|
|
61
|
+
if (!base64Data) throw new Error("Invalid base64 image data");
|
|
61
62
|
const binaryString = atob(base64Data);
|
|
62
63
|
const length = binaryString.length;
|
|
63
64
|
const bytes = new Uint8Array(length);
|
|
@@ -67,16 +68,7 @@ export async function base64ToImageBitmap(base64) {
|
|
|
67
68
|
const blob = new Blob([bytes], { type: "image/png" });
|
|
68
69
|
return await createImageBitmap(blob);
|
|
69
70
|
}
|
|
70
|
-
export
|
|
71
|
-
if (width <= max && height <= max) {
|
|
72
|
-
return { newWidth: width, newHeight: height };
|
|
73
|
-
}
|
|
74
|
-
const scaleFactor = Math.min(max / width, max / height);
|
|
75
|
-
return {
|
|
76
|
-
newWidth: width * scaleFactor,
|
|
77
|
-
newHeight: height * scaleFactor
|
|
78
|
-
};
|
|
79
|
-
}
|
|
71
|
+
export { resizeImage } from "../utils/placement_size.js";
|
|
80
72
|
export function convertToRGB(array, index = 0) {
|
|
81
73
|
if (index < 0 || index * 3 + 2 >= array.length) {
|
|
82
74
|
throw new Error("Index out of bounds");
|
|
@@ -143,7 +135,7 @@ export function getPDFDateTimestamp(dateString) {
|
|
|
143
135
|
let tzOffset = 0;
|
|
144
136
|
if (tzMatch) {
|
|
145
137
|
const sign = tzMatch[1] === "+" ? 1 : -1;
|
|
146
|
-
const hours = Number.parseInt(tzMatch[2], 10) || 0;
|
|
138
|
+
const hours = Number.parseInt(tzMatch[2] ?? "0", 10) || 0;
|
|
147
139
|
const minutes = Number.parseInt(tzMatch[3] || "0", 10) || 0;
|
|
148
140
|
tzOffset = sign * (hours * 60 + minutes);
|
|
149
141
|
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
export function downloadPdfBytes(data, fileName) {
|
|
2
|
-
const arrayBuffer =
|
|
2
|
+
const arrayBuffer = Uint8Array.from(data).buffer;
|
|
3
3
|
const blob = new Blob([arrayBuffer], { type: "application/pdf" });
|
|
4
4
|
const link = document.createElement("a");
|
|
5
5
|
const url = URL.createObjectURL(blob);
|
|
@@ -192,7 +192,7 @@ async function flattenPageAnnotations(pdfDoc, page, annotations) {
|
|
|
192
192
|
for (const annotation of annotations) {
|
|
193
193
|
try {
|
|
194
194
|
const node = Konva.Node.create(annotation.konvaString);
|
|
195
|
-
if (node instanceof Konva.
|
|
195
|
+
if (node instanceof Konva.Group || node instanceof Konva.Shape) {
|
|
196
196
|
layer.add(node);
|
|
197
197
|
imageLoaders.push(...loadImagesForNode(node));
|
|
198
198
|
}
|
|
@@ -12,7 +12,11 @@ function parseSvgPathToPoints(data) {
|
|
|
12
12
|
const nums = cmd.slice(1).trim().split(/[\s,]+/).map((value) => Number.parseFloat(value));
|
|
13
13
|
if (type === "M" || type === "L") {
|
|
14
14
|
for (let i = 0; i < nums.length; i += 2) {
|
|
15
|
-
|
|
15
|
+
const x = nums[i];
|
|
16
|
+
const y = nums[i + 1];
|
|
17
|
+
if (x !== void 0 && y !== void 0) {
|
|
18
|
+
points.push(x, y);
|
|
19
|
+
}
|
|
16
20
|
}
|
|
17
21
|
} else if (type === "Q") {
|
|
18
22
|
if (nums.length >= 4) {
|
|
@@ -177,10 +177,10 @@ export class StampParser extends AnnotationParser {
|
|
|
177
177
|
...this.ownerDateEntries(annotation.title, annotation.date),
|
|
178
178
|
Open: false
|
|
179
179
|
};
|
|
180
|
-
|
|
181
|
-
stampAnnDict
|
|
182
|
-
|
|
183
|
-
|
|
180
|
+
const stampAnn = context.obj({
|
|
181
|
+
...stampAnnDict,
|
|
182
|
+
...apDict ? { AP: apDict } : {}
|
|
183
|
+
});
|
|
184
184
|
const stampAnnRef = context.register(stampAnn);
|
|
185
185
|
this.addAnnotationToPage(page, stampAnnRef);
|
|
186
186
|
for (const comment of annotation.comments || []) {
|
|
@@ -1,10 +1,7 @@
|
|
|
1
1
|
import fontkit from "@pdf-lib/fontkit";
|
|
2
2
|
let fontBytesPromise = null;
|
|
3
3
|
let loadFontBytes = async () => {
|
|
4
|
-
const { default: fontUrl } = await import(
|
|
5
|
-
// @ts-expect-error -- Vite asset import, no type declaration
|
|
6
|
-
"../../assets/fonts/LiberationSans-Regular.ttf?url"
|
|
7
|
-
);
|
|
4
|
+
const { default: fontUrl } = await import("../../assets/fonts/LiberationSans-Regular.ttf?url");
|
|
8
5
|
const response = await fetch(fontUrl);
|
|
9
6
|
if (!response.ok) {
|
|
10
7
|
throw new Error(`Failed to load Unicode fallback font (${response.status})`);
|
|
@@ -16,13 +16,14 @@ export function decodeStampAnnotation(annotation, context) {
|
|
|
16
16
|
return null;
|
|
17
17
|
}
|
|
18
18
|
const group = createGhostGroup(annotation.id);
|
|
19
|
-
|
|
19
|
+
const imageConfig = {
|
|
20
20
|
x: rect.x,
|
|
21
21
|
y: rect.y,
|
|
22
22
|
width: rect.width > 0 ? rect.width : appearance.width,
|
|
23
23
|
height: rect.height > 0 ? rect.height : appearance.height,
|
|
24
24
|
base64: appearance.dataUrl
|
|
25
|
-
}
|
|
25
|
+
};
|
|
26
|
+
group.add(new Konva.Image(imageConfig));
|
|
26
27
|
const store = createAnnotationStore({
|
|
27
28
|
annotation,
|
|
28
29
|
allAnnotations: context.allAnnotations,
|
|
@@ -88,7 +88,7 @@ async function renderPageToCanvas(page, canvas, annotationMode, scale) {
|
|
|
88
88
|
if (!context) throw new Error("Unable to create canvas context for stamp extraction");
|
|
89
89
|
const viewport = page.getViewport({ scale });
|
|
90
90
|
const task = page.render({
|
|
91
|
-
|
|
91
|
+
canvas,
|
|
92
92
|
viewport,
|
|
93
93
|
annotationMode
|
|
94
94
|
});
|
|
@@ -104,7 +104,7 @@ export function getComments(annotation, allAnnotations) {
|
|
|
104
104
|
if (candidate.annotationType !== 1) continue;
|
|
105
105
|
if (candidate.inReplyTo !== annotation.id) continue;
|
|
106
106
|
comments.push({
|
|
107
|
-
id: candidate.id,
|
|
107
|
+
id: typeof candidate.id === "string" ? candidate.id : "",
|
|
108
108
|
title: getAnnotationTitle(candidate),
|
|
109
109
|
date: getAnnotationDate(candidate),
|
|
110
110
|
content: getAnnotationText(candidate)
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Sizing rules shared by stamp/signature placement and the drag cursor
|
|
3
|
+
* preview. They live apart from the engine so both paths derive their size
|
|
4
|
+
* from one implementation — the ghost drifting from what actually lands on
|
|
5
|
+
* the page was a real bug, not a hypothetical one.
|
|
6
|
+
*/
|
|
7
|
+
export interface ImageSize {
|
|
8
|
+
width: number;
|
|
9
|
+
height: number;
|
|
10
|
+
}
|
|
11
|
+
/** Aspect-preserving downscale. Never upscales. */
|
|
12
|
+
export declare function resizeImage(width: number, height: number, max: number): {
|
|
13
|
+
newWidth: number;
|
|
14
|
+
newHeight: number;
|
|
15
|
+
};
|
|
16
|
+
/**
|
|
17
|
+
* The size an annotation will occupy in stage-content units once placed.
|
|
18
|
+
* Explicit dimensions (a stamp carrying width/height metadata) win outright;
|
|
19
|
+
* otherwise the image's own aspect ratio is bounded by `max`.
|
|
20
|
+
*/
|
|
21
|
+
export declare function placementSize(explicit: Partial<ImageSize> | null, natural: ImageSize | null, max: number): ImageSize | null;
|
|
22
|
+
/**
|
|
23
|
+
* The same size expressed in CSS pixels, for the cursor ghost. The Konva stage
|
|
24
|
+
* scale is what converts content units to what the user actually sees, so a
|
|
25
|
+
* ghost that ignores it is wrong at every zoom level but 100%.
|
|
26
|
+
*/
|
|
27
|
+
export declare function cursorPreviewSize(explicit: Partial<ImageSize> | null, natural: ImageSize | null, max: number, scale: number): ImageSize | null;
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
export function resizeImage(width, height, max) {
|
|
2
|
+
if (width <= max && height <= max) {
|
|
3
|
+
return { newWidth: width, newHeight: height };
|
|
4
|
+
}
|
|
5
|
+
const scaleFactor = Math.min(max / width, max / height);
|
|
6
|
+
return {
|
|
7
|
+
newWidth: width * scaleFactor,
|
|
8
|
+
newHeight: height * scaleFactor
|
|
9
|
+
};
|
|
10
|
+
}
|
|
11
|
+
export function placementSize(explicit, natural, max) {
|
|
12
|
+
if (explicit?.width && explicit?.height) {
|
|
13
|
+
return { width: explicit.width, height: explicit.height };
|
|
14
|
+
}
|
|
15
|
+
if (!natural?.width || !natural?.height) return null;
|
|
16
|
+
const { newWidth, newHeight } = resizeImage(natural.width, natural.height, max);
|
|
17
|
+
return { width: newWidth, height: newHeight };
|
|
18
|
+
}
|
|
19
|
+
export function cursorPreviewSize(explicit, natural, max, scale) {
|
|
20
|
+
const size = placementSize(explicit, natural, max);
|
|
21
|
+
if (!size) return null;
|
|
22
|
+
return {
|
|
23
|
+
width: Math.round(size.width * scale),
|
|
24
|
+
height: Math.round(size.height * scale)
|
|
25
|
+
};
|
|
26
|
+
}
|
|
@@ -5,7 +5,7 @@ type __VLS_Props = {
|
|
|
5
5
|
/** Carries displayed width/height plus the page's `/Rotate` and MediaBox
|
|
6
6
|
* origin — the single source of truth for mapping `/Rect` to canvas. */
|
|
7
7
|
pageTransform: PageTransform;
|
|
8
|
-
pointerEvents:
|
|
8
|
+
pointerEvents: 'auto' | 'none';
|
|
9
9
|
zIndex: number;
|
|
10
10
|
};
|
|
11
11
|
declare const __VLS_export: import("vue").DefineComponent<__VLS_Props, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<__VLS_Props> & Readonly<{}>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
|
|
@@ -5,7 +5,7 @@ type __VLS_Props = {
|
|
|
5
5
|
/** Carries displayed width/height plus the page's `/Rotate` and MediaBox
|
|
6
6
|
* origin — the single source of truth for mapping `/Rect` to canvas. */
|
|
7
7
|
pageTransform: PageTransform;
|
|
8
|
-
pointerEvents:
|
|
8
|
+
pointerEvents: 'auto' | 'none';
|
|
9
9
|
zIndex: number;
|
|
10
10
|
};
|
|
11
11
|
declare const __VLS_export: import("vue").DefineComponent<__VLS_Props, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<__VLS_Props> & Readonly<{}>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
|
|
@@ -272,7 +272,7 @@ async function renderCanvas(opts) {
|
|
|
272
272
|
tmp.height = viewport.height;
|
|
273
273
|
try {
|
|
274
274
|
currentRenderTask = proxy.render({
|
|
275
|
-
|
|
275
|
+
canvas: tmp,
|
|
276
276
|
viewport,
|
|
277
277
|
annotationMode: PDFJS_ANNOTATION_MODE_DISABLE
|
|
278
278
|
});
|
|
@@ -293,7 +293,7 @@ async function renderCanvas(opts) {
|
|
|
293
293
|
canvas.height = viewport.height;
|
|
294
294
|
try {
|
|
295
295
|
currentRenderTask = proxy.render({
|
|
296
|
-
|
|
296
|
+
canvas,
|
|
297
297
|
viewport,
|
|
298
298
|
annotationMode: PDFJS_ANNOTATION_MODE_DISABLE
|
|
299
299
|
});
|
|
@@ -349,7 +349,7 @@ async function renderWindowCanvas() {
|
|
|
349
349
|
tmp.height = Math.floor(h * k);
|
|
350
350
|
try {
|
|
351
351
|
windowRenderTask = proxy.render({
|
|
352
|
-
|
|
352
|
+
canvas: tmp,
|
|
353
353
|
viewport,
|
|
354
354
|
transform: [1, 0, 0, 1, -x * k, -y * k],
|
|
355
355
|
annotationMode: PDFJS_ANNOTATION_MODE_DISABLE
|
|
@@ -1022,7 +1022,9 @@ onMounted(() => {
|
|
|
1022
1022
|
if (isIPad()) {
|
|
1023
1023
|
viewerState.setStylusMode(true);
|
|
1024
1024
|
}
|
|
1025
|
-
setDownloadPdfFn(() =>
|
|
1025
|
+
setDownloadPdfFn(async () => {
|
|
1026
|
+
await exportPdf({ download: true });
|
|
1027
|
+
});
|
|
1026
1028
|
loadDocument();
|
|
1027
1029
|
window.addEventListener("keydown", onGlobalKeydown);
|
|
1028
1030
|
});
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
<template>
|
|
2
|
-
<UPopover>
|
|
2
|
+
<UPopover v-model:open="popoverOpen">
|
|
3
3
|
<button
|
|
4
4
|
class="relative flex items-center justify-center w-8 h-8 rounded-md transition-colors cursor-pointer"
|
|
5
5
|
:class="isActive ? 'bg-default text-highlighted' : 'text-muted hover:bg-default/50 hover:text-highlighted'"
|
|
@@ -43,7 +43,7 @@
|
|
|
43
43
|
color="neutral"
|
|
44
44
|
size="xs"
|
|
45
45
|
block
|
|
46
|
-
@click="
|
|
46
|
+
@click="openDrawModal"
|
|
47
47
|
>
|
|
48
48
|
{{ t("addSignature") }}
|
|
49
49
|
</UButton>
|
|
@@ -69,10 +69,16 @@ import SignatureDrawModal from "../modals/SignatureDrawModal.vue";
|
|
|
69
69
|
const state = useViewerState();
|
|
70
70
|
const { t } = useKviewerI18n();
|
|
71
71
|
const drawModalOpen = ref(false);
|
|
72
|
+
const popoverOpen = ref(false);
|
|
72
73
|
const isActive = computed(() => state.activeTool.value === AnnotationType.SIGNATURE);
|
|
73
74
|
function selectSignature(sig) {
|
|
74
75
|
state.activeSignature.value = sig;
|
|
75
76
|
state.selectTool(AnnotationType.SIGNATURE, sig.imageUrl);
|
|
77
|
+
popoverOpen.value = false;
|
|
78
|
+
}
|
|
79
|
+
function openDrawModal() {
|
|
80
|
+
popoverOpen.value = false;
|
|
81
|
+
drawModalOpen.value = true;
|
|
76
82
|
}
|
|
77
83
|
async function onDrawSubmit(imageUrl) {
|
|
78
84
|
drawModalOpen.value = false;
|
|
@@ -15,7 +15,7 @@ export function createSearchIndex() {
|
|
|
15
15
|
includeMarkedContent: false,
|
|
16
16
|
disableNormalization: false
|
|
17
17
|
});
|
|
18
|
-
return content.items.
|
|
18
|
+
return content.items.map((item) => "str" in item ? item.str : "").join("");
|
|
19
19
|
}
|
|
20
20
|
async function buildIndex(doc) {
|
|
21
21
|
destroyed = false;
|