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.
Files changed (55) hide show
  1. package/dist/module.d.mts +1 -1
  2. package/dist/module.json +1 -1
  3. package/dist/module.mjs +19 -15
  4. package/dist/runtime/annotation/engine/editor/editor.js +10 -12
  5. package/dist/runtime/annotation/engine/editor/selector.js +3 -1
  6. package/dist/runtime/annotation/engine/tools/arrow.js +24 -14
  7. package/dist/runtime/annotation/engine/tools/circle.js +24 -13
  8. package/dist/runtime/annotation/engine/tools/cloud.js +27 -18
  9. package/dist/runtime/annotation/engine/tools/free-highlight.js +24 -15
  10. package/dist/runtime/annotation/engine/tools/freehand.js +21 -12
  11. package/dist/runtime/annotation/engine/tools/highlight.d.ts +2 -0
  12. package/dist/runtime/annotation/engine/tools/highlight.js +20 -10
  13. package/dist/runtime/annotation/engine/tools/note.js +3 -1
  14. package/dist/runtime/annotation/engine/tools/rectangle.js +19 -8
  15. package/dist/runtime/annotation/engine/tools/signature.js +2 -1
  16. package/dist/runtime/annotation/engine/tools/stamp.js +2 -1
  17. package/dist/runtime/annotation/engine/utils.js +2 -1
  18. package/dist/runtime/annotation/pdf-export/download.js +1 -1
  19. package/dist/runtime/annotation/pdf-export/export-form-fields.js +20 -12
  20. package/dist/runtime/annotation/pdf-export/export.js +1 -1
  21. package/dist/runtime/annotation/pdf-export/parse_polyline.js +5 -1
  22. package/dist/runtime/annotation/pdf-export/parse_stamp.js +4 -4
  23. package/dist/runtime/annotation/pdf-export/print.d.ts +12 -0
  24. package/dist/runtime/annotation/pdf-export/print.js +63 -0
  25. package/dist/runtime/annotation/pdf-export/unicode-font.js +1 -4
  26. package/dist/runtime/annotation/pdf-import/decode_stamp.js +3 -2
  27. package/dist/runtime/annotation/pdf-import/extract_stamp_appearance.js +1 -1
  28. package/dist/runtime/annotation/pdf-import/utils.js +1 -1
  29. package/dist/runtime/components/FormFieldLayer.d.vue.ts +1 -1
  30. package/dist/runtime/components/FormFieldLayer.vue.d.ts +1 -1
  31. package/dist/runtime/components/PdfPage.vue +3 -3
  32. package/dist/runtime/components/Viewer.d.vue.ts +10 -1
  33. package/dist/runtime/components/Viewer.vue +24 -2
  34. package/dist/runtime/components/Viewer.vue.d.ts +10 -1
  35. package/dist/runtime/components/ViewerTabs.vue +1 -1
  36. package/dist/runtime/components/panels/SignaturePicker.vue +8 -2
  37. package/dist/runtime/components/tools/ToolbarMenu.vue +17 -0
  38. package/dist/runtime/composables/useScriptingManager.d.ts +5 -0
  39. package/dist/runtime/composables/useScriptingManager.js +6 -0
  40. package/dist/runtime/composables/useSearchIndex.js +1 -1
  41. package/dist/runtime/composables/useViewerState.d.ts +2 -0
  42. package/dist/runtime/composables/useViewerState.js +8 -0
  43. package/dist/runtime/embed/bridge-client.d.ts +4 -0
  44. package/dist/runtime/embed/bridge-client.js +6 -0
  45. package/dist/runtime/embed/bridge-host.d.ts +11 -2
  46. package/dist/runtime/embed/bridge-host.js +104 -30
  47. package/dist/runtime/embed/protocol.d.ts +17 -0
  48. package/dist/runtime/embed/protocol.js +1 -0
  49. package/dist/runtime/i18n/messages.d.ts +2 -0
  50. package/dist/runtime/i18n/messages.js +2 -0
  51. package/dist/runtime/imports.d.ts +19 -0
  52. package/dist/runtime/public-types.d.ts +2 -2
  53. package/dist/runtime/public-types.js +4 -1
  54. package/dist/types.d.mts +1 -1
  55. 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
- this.currentShapeGroup = this.createShapeGroup();
20
- this.getBgLayer().add(this.currentShapeGroup.konvaGroup);
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
- this.currentShapeGroup.konvaGroup.add(shape);
39
+ shapeGroup.konvaGroup.add(shape);
30
40
  });
31
41
  this.setShapeGroupDone({
32
- id: this.currentShapeGroup.id,
42
+ id: shapeGroup.id,
33
43
  contentsObj: {
34
44
  text: this.getElementOuterText(elements)
35
45
  },
36
- color: this.currentAnnotation.style.color
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.currentAnnotation.type) {
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.currentAnnotation.type}`
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.currentAnnotation.style.color
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.currentAnnotation.style.color,
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.currentAnnotation.style.color,
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
- this.currentShapeGroup = this.createShapeGroup();
29
- this.getBgLayer().add(this.currentShapeGroup.konvaGroup);
30
- const pos = this.konvaStage.getRelativePointerPosition();
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: this.currentAnnotation.style.color,
39
- strokeWidth: this.currentAnnotation.style.strokeWidth || 2,
40
- opacity: this.currentAnnotation.style.opacity
42
+ stroke: style.color,
43
+ strokeWidth: style.strokeWidth || 2,
44
+ opacity: style.opacity
41
45
  });
42
- this.currentShapeGroup.konvaGroup.add(this.rect);
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: this.currentAnnotation.style.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 = data.buffer.slice(data.byteOffset, data.byteOffset + data.byteLength);
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 imgAspect = image.width / image.height;
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: rect.width,
377
- height: rect.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.Node) {
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
- points.push(nums[i], nums[i + 1]);
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
- if (apDict) {
181
- stampAnnDict.AP = apDict;
182
- }
183
- const stampAnn = context.obj(stampAnnDict);
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
- group.add(new Konva.Image({
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
- canvasContext: context,
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: string;
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: string;
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
- canvasContext: tmp.getContext("2d"),
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
- canvasContext: canvas.getContext("2d"),
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
- canvasContext: tmp.getContext("2d"),
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(() => exportPdf({ download: true }));
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;
@@ -48,7 +48,7 @@
48
48
  class="absolute inset-0"
49
49
  >
50
50
  <Viewer
51
- :ref="(el) => setViewerRef(activeTab.id, el)"
51
+ :ref="(el) => setViewerRef(activeTab?.id ?? '', el)"
52
52
  :source="activeTab.source"
53
53
  :active="true"
54
54
  :stamps="stamps"