kviewer 0.3.1 → 0.3.3
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 +19 -15
- 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 +2 -1
- package/dist/runtime/annotation/engine/tools/stamp.js +2 -1
- package/dist/runtime/annotation/engine/utils.js +2 -1
- package/dist/runtime/annotation/pdf-export/download.js +1 -1
- package/dist/runtime/annotation/pdf-export/export-form-fields.js +20 -12
- 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/print.d.ts +12 -0
- package/dist/runtime/annotation/pdf-export/print.js +63 -0
- 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/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.d.vue.ts +10 -1
- package/dist/runtime/components/Viewer.vue +24 -2
- package/dist/runtime/components/Viewer.vue.d.ts +10 -1
- package/dist/runtime/components/ViewerTabs.vue +1 -1
- package/dist/runtime/components/panels/SignaturePicker.vue +8 -2
- package/dist/runtime/components/tools/ToolbarMenu.vue +17 -0
- package/dist/runtime/composables/useScriptingManager.d.ts +5 -0
- package/dist/runtime/composables/useScriptingManager.js +6 -0
- package/dist/runtime/composables/useSearchIndex.js +1 -1
- package/dist/runtime/composables/useViewerState.d.ts +2 -0
- package/dist/runtime/composables/useViewerState.js +8 -0
- package/dist/runtime/embed/bridge-client.d.ts +4 -0
- package/dist/runtime/embed/bridge-client.js +6 -0
- package/dist/runtime/embed/bridge-host.d.ts +11 -2
- package/dist/runtime/embed/bridge-host.js +104 -30
- package/dist/runtime/embed/protocol.d.ts +17 -0
- package/dist/runtime/embed/protocol.js +1 -0
- package/dist/runtime/i18n/messages.d.ts +2 -0
- package/dist/runtime/i18n/messages.js +2 -0
- package/dist/runtime/imports.d.ts +19 -0
- package/dist/runtime/public-types.d.ts +2 -2
- package/dist/runtime/public-types.js +4 -1
- 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
|
}
|
|
@@ -11,7 +11,7 @@ import {
|
|
|
11
11
|
import { cursorPreviewSize, placementSize } from "../../utils/placement_size.js";
|
|
12
12
|
export class EditorSignature extends Editor {
|
|
13
13
|
signatureUrl;
|
|
14
|
-
signatureImage;
|
|
14
|
+
signatureImage = null;
|
|
15
15
|
constructor(editorOptions, defaultSignatureUrl) {
|
|
16
16
|
super({ ...editorOptions, editorType: AnnotationType.SIGNATURE });
|
|
17
17
|
this.signatureUrl = defaultSignatureUrl;
|
|
@@ -54,6 +54,7 @@ export class EditorSignature extends Editor {
|
|
|
54
54
|
const crosshair = { x: newWidth / 2, y: newHeight / 2 };
|
|
55
55
|
this.signatureImage = image;
|
|
56
56
|
this.signatureImage.setAttrs({
|
|
57
|
+
image: image.image(),
|
|
57
58
|
x: pos.x - crosshair.x,
|
|
58
59
|
y: pos.y - crosshair.y,
|
|
59
60
|
width: newWidth,
|
|
@@ -14,7 +14,7 @@ import {
|
|
|
14
14
|
} from "../../utils/placement_size.js";
|
|
15
15
|
export class EditorStamp extends Editor {
|
|
16
16
|
stampUrl;
|
|
17
|
-
stampImage;
|
|
17
|
+
stampImage = null;
|
|
18
18
|
stampWidth = null;
|
|
19
19
|
stampHeight = null;
|
|
20
20
|
constructor(editorOptions, defaultStampUrl) {
|
|
@@ -92,6 +92,7 @@ export class EditorStamp extends Editor {
|
|
|
92
92
|
const crosshair = { x: newWidth / 2, y: newHeight / 2 };
|
|
93
93
|
this.stampImage = image;
|
|
94
94
|
this.stampImage.setAttrs({
|
|
95
|
+
image: image.image(),
|
|
95
96
|
x: pos.x - crosshair.x,
|
|
96
97
|
y: pos.y - crosshair.y,
|
|
97
98
|
width: newWidth,
|
|
@@ -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);
|
|
@@ -134,7 +135,7 @@ export function getPDFDateTimestamp(dateString) {
|
|
|
134
135
|
let tzOffset = 0;
|
|
135
136
|
if (tzMatch) {
|
|
136
137
|
const sign = tzMatch[1] === "+" ? 1 : -1;
|
|
137
|
-
const hours = Number.parseInt(tzMatch[2], 10) || 0;
|
|
138
|
+
const hours = Number.parseInt(tzMatch[2] ?? "0", 10) || 0;
|
|
138
139
|
const minutes = Number.parseInt(tzMatch[3] || "0", 10) || 0;
|
|
139
140
|
tzOffset = sign * (hours * 60 + minutes);
|
|
140
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);
|
|
@@ -322,14 +322,7 @@ async function buildSignedSignatureAppearance(pdfDoc, dataUrl, width, height) {
|
|
|
322
322
|
const mimeType = match[1] ?? "";
|
|
323
323
|
const base64 = match[2] ?? "";
|
|
324
324
|
const image = mimeType.includes("jpeg") || mimeType.includes("jpg") ? await pdfDoc.embedJpg(base64) : await pdfDoc.embedPng(base64);
|
|
325
|
-
const
|
|
326
|
-
const boxAspect = width / height;
|
|
327
|
-
let drawW = width;
|
|
328
|
-
let drawH = height;
|
|
329
|
-
if (imgAspect > boxAspect) drawH = width / imgAspect;
|
|
330
|
-
else drawW = height * imgAspect;
|
|
331
|
-
const drawX = (width - drawW) / 2;
|
|
332
|
-
const drawY = (height - drawH) / 2;
|
|
325
|
+
const { drawX, drawY, drawW, drawH } = fitImageInBox(image, width, height);
|
|
333
326
|
const ops = [
|
|
334
327
|
"q",
|
|
335
328
|
`${drawW.toFixed(3)} 0 0 ${drawH.toFixed(3)} ${drawX.toFixed(3)} ${drawY.toFixed(3)} cm`,
|
|
@@ -346,6 +339,20 @@ async function buildSignedSignatureAppearance(pdfDoc, dataUrl, width, height) {
|
|
|
346
339
|
const stream = PDFRawStream.of(dict, new TextEncoder().encode(ops));
|
|
347
340
|
return pdfDoc.context.register(stream);
|
|
348
341
|
}
|
|
342
|
+
function fitImageInBox(image, boxW, boxH) {
|
|
343
|
+
const imgAspect = image.width / image.height;
|
|
344
|
+
const boxAspect = boxW / boxH;
|
|
345
|
+
let drawW = boxW;
|
|
346
|
+
let drawH = boxH;
|
|
347
|
+
if (imgAspect > boxAspect) drawH = boxW / imgAspect;
|
|
348
|
+
else drawW = boxH * imgAspect;
|
|
349
|
+
return {
|
|
350
|
+
drawX: (boxW - drawW) / 2,
|
|
351
|
+
drawY: (boxH - drawH) / 2,
|
|
352
|
+
drawW,
|
|
353
|
+
drawH
|
|
354
|
+
};
|
|
355
|
+
}
|
|
349
356
|
async function embedSignatureImage(pdfDoc, field) {
|
|
350
357
|
const dataUrl = field.value;
|
|
351
358
|
const match = dataUrl.match(/^data:(image\/[a-zA-Z0-9.+-]+);base64,(.+)$/);
|
|
@@ -370,11 +377,12 @@ async function embedSignatureImage(pdfDoc, field) {
|
|
|
370
377
|
const pageRef = page.ref;
|
|
371
378
|
const widgetPage = widget.P();
|
|
372
379
|
if (widgetPage === pageRef || !widgetPage) {
|
|
380
|
+
const { drawX, drawY, drawW, drawH } = fitImageInBox(image, rect.width, rect.height);
|
|
373
381
|
page.drawImage(image, {
|
|
374
|
-
x: rect.x,
|
|
375
|
-
y: rect.y,
|
|
376
|
-
width:
|
|
377
|
-
height:
|
|
382
|
+
x: rect.x + drawX,
|
|
383
|
+
y: rect.y + drawY,
|
|
384
|
+
width: drawW,
|
|
385
|
+
height: drawH
|
|
378
386
|
});
|
|
379
387
|
break;
|
|
380
388
|
}
|
|
@@ -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 || []) {
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Print a PDF by handing the actual (vector) bytes to the browser's native
|
|
3
|
+
* PDF pipeline, instead of letting the print dialog rasterize the on-screen
|
|
4
|
+
* canvases: a hidden iframe loads the blob and `contentWindow.print()` opens
|
|
5
|
+
* the dialog with the full-resolution document.
|
|
6
|
+
*
|
|
7
|
+
* On iOS/iPadOS Safari an iframe renders only the first PDF page, so there
|
|
8
|
+
* the blob opens in a new tab and the user prints from the native preview
|
|
9
|
+
* (share sheet → Print). Same fallback if `print()` throws (some browsers
|
|
10
|
+
* refuse to script-print plugin documents).
|
|
11
|
+
*/
|
|
12
|
+
export declare function printPdfBytes(data: Uint8Array): Promise<void>;
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import { isIPad } from "../../annotation/engine/input-device.js";
|
|
2
|
+
let activeFrame = null;
|
|
3
|
+
let activeUrl = null;
|
|
4
|
+
function reclaimPrevious() {
|
|
5
|
+
activeFrame?.remove();
|
|
6
|
+
activeFrame = null;
|
|
7
|
+
if (activeUrl) {
|
|
8
|
+
URL.revokeObjectURL(activeUrl);
|
|
9
|
+
activeUrl = null;
|
|
10
|
+
}
|
|
11
|
+
}
|
|
12
|
+
function openInNewTab(url) {
|
|
13
|
+
const win = window.open(url, "_blank");
|
|
14
|
+
if (!win) {
|
|
15
|
+
throw new Error(
|
|
16
|
+
"Could not open the PDF for printing \u2014 the popup was blocked. Allow popups for this site and try again."
|
|
17
|
+
);
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
export async function printPdfBytes(data) {
|
|
21
|
+
reclaimPrevious();
|
|
22
|
+
const arrayBuffer = Uint8Array.from(data).buffer;
|
|
23
|
+
const blob = new Blob([arrayBuffer], { type: "application/pdf" });
|
|
24
|
+
const url = URL.createObjectURL(blob);
|
|
25
|
+
activeUrl = url;
|
|
26
|
+
const isAppleMobile = isIPad() || /iPhone|iPod/.test(navigator.userAgent);
|
|
27
|
+
if (isAppleMobile) {
|
|
28
|
+
openInNewTab(url);
|
|
29
|
+
return;
|
|
30
|
+
}
|
|
31
|
+
const frame = document.createElement("iframe");
|
|
32
|
+
frame.style.position = "fixed";
|
|
33
|
+
frame.style.right = "0";
|
|
34
|
+
frame.style.bottom = "0";
|
|
35
|
+
frame.style.width = "1px";
|
|
36
|
+
frame.style.height = "1px";
|
|
37
|
+
frame.style.border = "0";
|
|
38
|
+
frame.style.visibility = "hidden";
|
|
39
|
+
frame.src = url;
|
|
40
|
+
activeFrame = frame;
|
|
41
|
+
await new Promise((resolve, reject) => {
|
|
42
|
+
const timer = setTimeout(() => {
|
|
43
|
+
reject(new Error("Timed out loading the PDF for printing"));
|
|
44
|
+
}, 15e3);
|
|
45
|
+
frame.addEventListener(
|
|
46
|
+
"load",
|
|
47
|
+
() => {
|
|
48
|
+
clearTimeout(timer);
|
|
49
|
+
resolve();
|
|
50
|
+
},
|
|
51
|
+
{ once: true }
|
|
52
|
+
);
|
|
53
|
+
document.body.appendChild(frame);
|
|
54
|
+
});
|
|
55
|
+
try {
|
|
56
|
+
const win = frame.contentWindow;
|
|
57
|
+
if (!win) throw new Error("Print frame has no window");
|
|
58
|
+
win.focus();
|
|
59
|
+
win.print();
|
|
60
|
+
} catch {
|
|
61
|
+
openInNewTab(url);
|
|
62
|
+
}
|
|
63
|
+
}
|
|
@@ -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)
|
|
@@ -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
|
|
@@ -78,6 +78,11 @@ type __VLS_Props = {
|
|
|
78
78
|
messages?: KviewerMessageOverrides;
|
|
79
79
|
};
|
|
80
80
|
declare function exportPdf(options?: ExportPdfOptions): Promise<Uint8Array>;
|
|
81
|
+
/** Print via the real PDF instead of the on-screen canvases: export the
|
|
82
|
+
* document (annotations + form values baked in) and hand the vector bytes
|
|
83
|
+
* to the browser's native PDF print pipeline. Screen canvases are sized
|
|
84
|
+
* for the monitor and would come out blurry on paper. */
|
|
85
|
+
declare function printPdf(options?: ExportPdfOptions): Promise<void>;
|
|
81
86
|
type ImportMode = 'replace' | 'merge';
|
|
82
87
|
declare function importAnnotations(annotations: IAnnotationStore[], options?: {
|
|
83
88
|
mode?: ImportMode;
|
|
@@ -100,6 +105,10 @@ declare const __VLS_base: import("vue").DefineComponent<__VLS_Props, {
|
|
|
100
105
|
getAnnotations: () => IAnnotationStore[];
|
|
101
106
|
importAnnotations: typeof importAnnotations;
|
|
102
107
|
exportPdf: typeof exportPdf;
|
|
108
|
+
/** Export the document (same options as `exportPdf`, minus `download`)
|
|
109
|
+
* and open the browser's print dialog on the resulting vector PDF —
|
|
110
|
+
* full print quality, unlike printing the screen canvases. */
|
|
111
|
+
printPdf: typeof printPdf;
|
|
103
112
|
getKonvaCanvasState: typeof getKonvaCanvasState;
|
|
104
113
|
getFormFieldValues: () => import("../annotation/engine/types.js").FormFieldValue[];
|
|
105
114
|
setFormFieldValue: (fieldName: string, value: string | boolean | string[]) => void;
|
|
@@ -180,6 +189,7 @@ declare const __VLS_base: import("vue").DefineComponent<__VLS_Props, {
|
|
|
180
189
|
clickToSign: boolean;
|
|
181
190
|
readonly: boolean;
|
|
182
191
|
active: boolean;
|
|
192
|
+
formEditMode: boolean;
|
|
183
193
|
locale: KviewerLocale;
|
|
184
194
|
menuItems: ViewerMenuItem[];
|
|
185
195
|
tools: ViewerToolEntry[];
|
|
@@ -188,7 +198,6 @@ declare const __VLS_base: import("vue").DefineComponent<__VLS_Props, {
|
|
|
188
198
|
signatureHandlers: SignatureHandlers;
|
|
189
199
|
viewMode: ViewMode;
|
|
190
200
|
shapeDetection: boolean;
|
|
191
|
-
formEditMode: boolean;
|
|
192
201
|
roleColors: Record<string, string>;
|
|
193
202
|
activeRoleId: string | null;
|
|
194
203
|
editablePlacedFields: boolean;
|
|
@@ -214,6 +214,7 @@ import {
|
|
|
214
214
|
} from "../annotation/pdf-import/decode";
|
|
215
215
|
import { getTimestampString } from "../annotation/engine/utils";
|
|
216
216
|
import { downloadPdfBytes } from "../annotation/pdf-export/download";
|
|
217
|
+
import { printPdfBytes } from "../annotation/pdf-export/print";
|
|
217
218
|
import { exportAnnotationsToPdf } from "../annotation/pdf-export/export";
|
|
218
219
|
import { createPageVirtualization } from "../composables/usePageVirtualization";
|
|
219
220
|
import { createPageProxyCache } from "../composables/usePageProxyCache";
|
|
@@ -260,7 +261,7 @@ const fieldPlacedCallbacks = /* @__PURE__ */ new Set();
|
|
|
260
261
|
const signedStatusCallbacks = /* @__PURE__ */ new Set();
|
|
261
262
|
const viewerRoot = ref(null);
|
|
262
263
|
const scrollContainer = ref(null);
|
|
263
|
-
const { state: viewerState, setScrollToPageFn, setDownloadPdfFn, setApplyScaleFn } = provideViewerState();
|
|
264
|
+
const { state: viewerState, setScrollToPageFn, setDownloadPdfFn, setPrintPdfFn, setApplyScaleFn } = provideViewerState();
|
|
264
265
|
watchEffect(() => {
|
|
265
266
|
viewerState.readonly.value = props.readonly ?? false;
|
|
266
267
|
});
|
|
@@ -806,6 +807,18 @@ async function exportPdf(options = {}) {
|
|
|
806
807
|
}
|
|
807
808
|
return bytes;
|
|
808
809
|
}
|
|
810
|
+
async function printPdf(options = {}) {
|
|
811
|
+
if (scriptingManager?.isReady()) {
|
|
812
|
+
await scriptingManager.dispatchWillPrint().catch(() => {
|
|
813
|
+
});
|
|
814
|
+
}
|
|
815
|
+
const bytes = await exportPdf({ ...options, download: false });
|
|
816
|
+
await printPdfBytes(bytes);
|
|
817
|
+
if (scriptingManager?.isReady()) {
|
|
818
|
+
scriptingManager.dispatchDidPrint().catch(() => {
|
|
819
|
+
});
|
|
820
|
+
}
|
|
821
|
+
}
|
|
809
822
|
async function importAnnotations(annotations, options) {
|
|
810
823
|
const mode = options?.mode ?? "replace";
|
|
811
824
|
const normalized = normalizeImportedAnnotations(
|
|
@@ -1022,7 +1035,12 @@ onMounted(() => {
|
|
|
1022
1035
|
if (isIPad()) {
|
|
1023
1036
|
viewerState.setStylusMode(true);
|
|
1024
1037
|
}
|
|
1025
|
-
setDownloadPdfFn(() =>
|
|
1038
|
+
setDownloadPdfFn(async () => {
|
|
1039
|
+
await exportPdf({ download: true });
|
|
1040
|
+
});
|
|
1041
|
+
setPrintPdfFn(async () => {
|
|
1042
|
+
await printPdf();
|
|
1043
|
+
});
|
|
1026
1044
|
loadDocument();
|
|
1027
1045
|
window.addEventListener("keydown", onGlobalKeydown);
|
|
1028
1046
|
});
|
|
@@ -1092,6 +1110,10 @@ defineExpose({
|
|
|
1092
1110
|
getAnnotations: () => painter?.getData() ?? [],
|
|
1093
1111
|
importAnnotations,
|
|
1094
1112
|
exportPdf,
|
|
1113
|
+
/** Export the document (same options as `exportPdf`, minus `download`)
|
|
1114
|
+
* and open the browser's print dialog on the resulting vector PDF —
|
|
1115
|
+
* full print quality, unlike printing the screen canvases. */
|
|
1116
|
+
printPdf,
|
|
1095
1117
|
getKonvaCanvasState,
|
|
1096
1118
|
getFormFieldValues: () => formFieldsState.getAllFieldValues(),
|
|
1097
1119
|
setFormFieldValue: (fieldName, value) => formFieldsState.setFieldValueByName(fieldName, value),
|
|
@@ -78,6 +78,11 @@ type __VLS_Props = {
|
|
|
78
78
|
messages?: KviewerMessageOverrides;
|
|
79
79
|
};
|
|
80
80
|
declare function exportPdf(options?: ExportPdfOptions): Promise<Uint8Array>;
|
|
81
|
+
/** Print via the real PDF instead of the on-screen canvases: export the
|
|
82
|
+
* document (annotations + form values baked in) and hand the vector bytes
|
|
83
|
+
* to the browser's native PDF print pipeline. Screen canvases are sized
|
|
84
|
+
* for the monitor and would come out blurry on paper. */
|
|
85
|
+
declare function printPdf(options?: ExportPdfOptions): Promise<void>;
|
|
81
86
|
type ImportMode = 'replace' | 'merge';
|
|
82
87
|
declare function importAnnotations(annotations: IAnnotationStore[], options?: {
|
|
83
88
|
mode?: ImportMode;
|
|
@@ -100,6 +105,10 @@ declare const __VLS_base: import("vue").DefineComponent<__VLS_Props, {
|
|
|
100
105
|
getAnnotations: () => IAnnotationStore[];
|
|
101
106
|
importAnnotations: typeof importAnnotations;
|
|
102
107
|
exportPdf: typeof exportPdf;
|
|
108
|
+
/** Export the document (same options as `exportPdf`, minus `download`)
|
|
109
|
+
* and open the browser's print dialog on the resulting vector PDF —
|
|
110
|
+
* full print quality, unlike printing the screen canvases. */
|
|
111
|
+
printPdf: typeof printPdf;
|
|
103
112
|
getKonvaCanvasState: typeof getKonvaCanvasState;
|
|
104
113
|
getFormFieldValues: () => import("../annotation/engine/types.js").FormFieldValue[];
|
|
105
114
|
setFormFieldValue: (fieldName: string, value: string | boolean | string[]) => void;
|
|
@@ -180,6 +189,7 @@ declare const __VLS_base: import("vue").DefineComponent<__VLS_Props, {
|
|
|
180
189
|
clickToSign: boolean;
|
|
181
190
|
readonly: boolean;
|
|
182
191
|
active: boolean;
|
|
192
|
+
formEditMode: boolean;
|
|
183
193
|
locale: KviewerLocale;
|
|
184
194
|
menuItems: ViewerMenuItem[];
|
|
185
195
|
tools: ViewerToolEntry[];
|
|
@@ -188,7 +198,6 @@ declare const __VLS_base: import("vue").DefineComponent<__VLS_Props, {
|
|
|
188
198
|
signatureHandlers: SignatureHandlers;
|
|
189
199
|
viewMode: ViewMode;
|
|
190
200
|
shapeDetection: boolean;
|
|
191
|
-
formEditMode: boolean;
|
|
192
201
|
roleColors: Record<string, string>;
|
|
193
202
|
activeRoleId: string | null;
|
|
194
203
|
editablePlacedFields: boolean;
|