kviewer 0.3.2 → 0.3.4

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 (51) hide show
  1. package/dist/module.d.mts +1 -1
  2. package/dist/module.json +1 -1
  3. package/dist/module.mjs +15 -15
  4. package/dist/runtime/annotation/engine/editor/selector.d.ts +1 -0
  5. package/dist/runtime/annotation/engine/editor/selector.js +17 -5
  6. package/dist/runtime/annotation/engine/painter.d.ts +2 -0
  7. package/dist/runtime/annotation/engine/painter.js +4 -0
  8. package/dist/runtime/annotation/pdf-export/export-form-fields.js +26 -41
  9. package/dist/runtime/annotation/pdf-export/print.d.ts +12 -0
  10. package/dist/runtime/annotation/pdf-export/print.js +63 -0
  11. package/dist/runtime/assets/kviewer.css +1 -1
  12. package/dist/runtime/components/AnnotationToolbar.vue +12 -1
  13. package/dist/runtime/components/FormFieldLayer.vue +7 -2
  14. package/dist/runtime/components/PdfPage.vue +24 -1
  15. package/dist/runtime/components/SelectionToolbarItems.d.vue.ts +8 -0
  16. package/dist/runtime/components/SelectionToolbarItems.vue +43 -0
  17. package/dist/runtime/components/SelectionToolbarItems.vue.d.ts +8 -0
  18. package/dist/runtime/components/Viewer.d.vue.ts +17 -1
  19. package/dist/runtime/components/Viewer.vue +45 -2
  20. package/dist/runtime/components/Viewer.vue.d.ts +17 -1
  21. package/dist/runtime/components/ViewerBar.d.vue.ts +4 -4
  22. package/dist/runtime/components/ViewerBar.vue +2 -1
  23. package/dist/runtime/components/ViewerBar.vue.d.ts +4 -4
  24. package/dist/runtime/components/ViewerTabs.d.vue.ts +4 -0
  25. package/dist/runtime/components/ViewerTabs.vue +2 -0
  26. package/dist/runtime/components/ViewerTabs.vue.d.ts +4 -0
  27. package/dist/runtime/components/form-fields/FormFieldWrapper.vue +15 -2
  28. package/dist/runtime/components/form-fields/PlacedFieldChrome.vue +9 -9
  29. package/dist/runtime/components/form-fields/PlacedFieldToolbar.d.vue.ts +7 -0
  30. package/dist/runtime/components/form-fields/PlacedFieldToolbar.vue +51 -0
  31. package/dist/runtime/components/form-fields/PlacedFieldToolbar.vue.d.ts +7 -0
  32. package/dist/runtime/components/tools/ToolbarMenu.vue +17 -0
  33. package/dist/runtime/composables/useScriptingManager.d.ts +5 -0
  34. package/dist/runtime/composables/useScriptingManager.js +6 -0
  35. package/dist/runtime/composables/useViewerState.d.ts +2 -0
  36. package/dist/runtime/composables/useViewerState.js +8 -0
  37. package/dist/runtime/embed/bridge-client.d.ts +4 -0
  38. package/dist/runtime/embed/bridge-client.js +6 -0
  39. package/dist/runtime/embed/bridge-host.d.ts +10 -1
  40. package/dist/runtime/embed/bridge-host.js +102 -28
  41. package/dist/runtime/embed/protocol.d.ts +17 -0
  42. package/dist/runtime/embed/protocol.js +1 -0
  43. package/dist/runtime/i18n/messages.d.ts +2 -0
  44. package/dist/runtime/i18n/messages.js +2 -0
  45. package/dist/runtime/public-types.d.ts +4 -3
  46. package/dist/runtime/public-types.js +4 -1
  47. package/dist/runtime/selection-items.d.ts +61 -0
  48. package/dist/runtime/selection-items.js +1 -0
  49. package/dist/runtime/toolbar-tools.d.ts +4 -1
  50. package/dist/types.d.mts +1 -1
  51. package/package.json +1 -1
package/dist/module.d.mts CHANGED
@@ -1,5 +1,5 @@
1
1
  import * as _nuxt_schema from '@nuxt/schema';
2
- export { ActiveTool, AddFormFieldPayload, CheckboxStyle, ExportPdfOptions, FormFieldDefinition, FormFieldOrigin, FormFieldType, FormFieldValue, KVIEWER_EMBED_PROTOCOL_VERSION, KViewerApi, KViewerEmbedBridgeOptions, KViewerEmbedClient, KViewerEmbedClientOptions, KViewerEmbedEventName, KViewerEmbedEventPayloads, KViewerEmbedMethodName, PlacedFieldDefaults, SignatureData, SignatureFieldStatus, SignatureHandlers, ViewMode, ViewerBottomToolId, ViewerLayoutToolId, ViewerMenuButtonItem, ViewerMenuCheckboxItem, ViewerMenuItem, ViewerMenuSeparatorItem, ViewerState, ViewerToolEntry, ViewerToolId, ViewerToolSlot, ViewerTopToolId, useKViewerEmbedBridge } from '../dist/runtime/public-types.js';
2
+ export { ActiveTool, AddFormFieldPayload, CheckboxStyle, ExportPdfOptions, FormFieldDefinition, FormFieldOrigin, FormFieldType, FormFieldValue, IAnnotationStore, KVIEWER_EMBED_PROTOCOL_VERSION, KVIEWER_EMBED_UNSUPPORTED_METHOD_ERROR_NAME, KViewerApi, KViewerEmbedBridgeOptions, KViewerEmbedClient, KViewerEmbedClientOptions, KViewerEmbedEventName, KViewerEmbedEventPayloads, KViewerEmbedMethodName, KViewerHostApi, PlacedFieldDefaults, SignatureData, SignatureFieldStatus, SignatureHandlers, ViewMode, ViewerBottomToolId, ViewerLayoutToolId, ViewerMenuButtonItem, ViewerMenuCheckboxItem, ViewerMenuItem, ViewerMenuSeparatorItem, ViewerSelectionButtonItem, ViewerSelectionContext, ViewerSelectionItem, ViewerSelectionSelectItem, ViewerState, ViewerToolEntry, ViewerToolId, ViewerToolSlot, ViewerTopToolId, useKViewerEmbedBridge } from '../dist/runtime/public-types.js';
3
3
 
4
4
  interface ModuleOptions {
5
5
  /**
package/dist/module.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "kviewer",
3
3
  "configKey": "kviewer",
4
- "version": "0.3.2",
4
+ "version": "0.3.4",
5
5
  "builder": {
6
6
  "@nuxt/module-builder": "1.0.2",
7
7
  "unbuild": "3.6.1"
package/dist/module.mjs CHANGED
@@ -1,7 +1,7 @@
1
1
  import { createRequire } from 'node:module';
2
2
  import { dirname, join } from 'node:path';
3
3
  import { defineNuxtModule, createResolver, extendViteConfig, addPlugin, addComponentsDir, addImports } from '@nuxt/kit';
4
- export { KVIEWER_EMBED_PROTOCOL_VERSION, KViewerEmbedClient, useKViewerEmbedBridge } from '../dist/runtime/public-types.js';
4
+ export { KVIEWER_EMBED_PROTOCOL_VERSION, KVIEWER_EMBED_UNSUPPORTED_METHOD_ERROR_NAME, KViewerEmbedClient, useKViewerEmbedBridge } from '../dist/runtime/public-types.js';
5
5
 
6
6
  const module$1 = defineNuxtModule({
7
7
  meta: {
@@ -34,19 +34,14 @@ const module$1 = defineNuxtModule({
34
34
  config.optimizeDeps ||= {};
35
35
  config.optimizeDeps.include ||= [];
36
36
  config.optimizeDeps.include.push(
37
- "konva",
38
- "pdf-lib",
39
- "pdfjs-dist/legacy/build/pdf.mjs",
40
- "pdfjs-dist/web/pdf_viewer.mjs",
41
- // unicode-font.ts registers @pdf-lib/fontkit for Unicode font
42
- // embedding. Its ESM build does `import pako from 'pako'`, but pako 1.x
43
- // is CJS with no `default` export so unless fontkit is pre-bundled
44
- // (where esbuild applies CJS interop and synthesizes the default), the
45
- // browser receives the raw pako index.js and throws "does not provide
46
- // an export named 'default'". The `kviewer >` prefix anchors resolution
47
- // through this module, so it works even when pnpm leaves fontkit
48
- // un-hoisted in the consumer (a bare '@pdf-lib/fontkit' entry would not
49
- // resolve from the consumer root and Vite would silently skip it).
37
+ "kviewer > konva",
38
+ "kviewer > pdf-lib",
39
+ "kviewer > pdfjs-dist/legacy/build/pdf.mjs",
40
+ "kviewer > pdfjs-dist/web/pdf_viewer.mjs",
41
+ // fontkit's ESM build does `import pako from 'pako'`, but pako 1.x is
42
+ // CJS with no `default` export without pre-bundling (where esbuild
43
+ // synthesizes it) the browser throws "does not provide an export named
44
+ // 'default'".
50
45
  "kviewer > @pdf-lib/fontkit"
51
46
  );
52
47
  });
@@ -71,7 +66,12 @@ const module$1 = defineNuxtModule({
71
66
  addImports([
72
67
  { name: "useKViewerEmbedBridge", from: resolve("./runtime/embed/bridge-host") },
73
68
  { name: "KViewerEmbedClient", from: resolve("./runtime/embed/bridge-client") },
74
- { name: "KVIEWER_EMBED_PROTOCOL_VERSION", from: resolve("./runtime/embed/protocol") }
69
+ { name: "KVIEWER_EMBED_PROTOCOL_VERSION", from: resolve("./runtime/embed/protocol") },
70
+ {
71
+ name: "UNSUPPORTED_METHOD_ERROR_NAME",
72
+ as: "KVIEWER_EMBED_UNSUPPORTED_METHOD_ERROR_NAME",
73
+ from: resolve("./runtime/embed/protocol")
74
+ }
75
75
  ]);
76
76
  const require_ = createRequire(import.meta.url);
77
77
  const pdfjsDir = dirname(require_.resolve("pdfjs-dist/package.json"));
@@ -52,6 +52,7 @@ export declare class Selector {
52
52
  private marqueeRect;
53
53
  private marqueeStartPos;
54
54
  private marqueeStage;
55
+ private suppressStageClick;
55
56
  constructor({ konvaCanvasStore, getAnnotationStore, onDelete, onSelected, onMultiSelected, onMultiChanged, onCancel, onChanged, onRequestDeleteConfirm, onFreeTextDoubleClick, getStylusModeEnabled, setExternalGesturesEnabled, }: ISelectorOptions);
56
57
  /** Returns true if this event should be rejected (finger touch in stylus mode). */
57
58
  private isStylusRejected;
@@ -24,6 +24,7 @@ export class Selector {
24
24
  marqueeRect = null;
25
25
  marqueeStartPos = null;
26
26
  marqueeStage = null;
27
+ suppressStageClick = false;
27
28
  constructor({
28
29
  konvaCanvasStore,
29
30
  getAnnotationStore,
@@ -88,13 +89,17 @@ export class Selector {
88
89
  }
89
90
  disableStageEvents(konvaStage) {
90
91
  konvaStage.off(
91
- "click mousedown mousemove mouseup touchstart touchmove touchend pointerdown pointermove pointerup"
92
+ "click tap mousedown mousemove mouseup touchstart touchmove touchend pointerdown pointermove pointerup"
92
93
  );
93
94
  }
94
95
  bindStageEvents(konvaStage) {
95
96
  konvaStage.on("click tap", (e) => {
96
97
  if (e.target !== konvaStage) return;
97
98
  if (this.isStylusRejected(e.evt)) return;
99
+ if (this.suppressStageClick) {
100
+ this.suppressStageClick = false;
101
+ return;
102
+ }
98
103
  this.clearTransformers();
99
104
  });
100
105
  }
@@ -474,6 +479,7 @@ export class Selector {
474
479
  this.handleShapeClick(shape, konvaStage, false);
475
480
  }
476
481
  clear() {
482
+ this.suppressStageClick = false;
477
483
  this.clearTransformers();
478
484
  this.konvaCanvasStore.forEach((konvaCanvas) => {
479
485
  const { konvaStage } = konvaCanvas;
@@ -489,6 +495,7 @@ export class Selector {
489
495
  const pageGroups = this.getPageShapeGroups(konvaStage);
490
496
  this.disableStageEvents(konvaStage);
491
497
  this.bindStageEvents(konvaStage);
498
+ this.bindMarqueeEvents(konvaStage, { mouseOnly: true });
492
499
  this.enableShapeGroups(pageGroups, konvaStage);
493
500
  if (this.selectedId) {
494
501
  this.selectedShape(this.selectedId, konvaStage);
@@ -519,13 +526,17 @@ export class Selector {
519
526
  this.enableShapeGroups(pageGroups, konvaStage);
520
527
  this.bindMarqueeEvents(konvaStage);
521
528
  }
522
- bindMarqueeEvents(konvaStage) {
523
- konvaStage.on("mousedown touchstart", (e) => {
529
+ bindMarqueeEvents(konvaStage, opts = {}) {
530
+ const { mouseOnly = false } = opts;
531
+ konvaStage.on(mouseOnly ? "mousedown" : "mousedown touchstart", (e) => {
524
532
  if (e.target !== konvaStage) return;
525
533
  if (this.isStylusRejected(e.evt)) return;
534
+ const button = e.evt?.button;
535
+ if (mouseOnly && button !== void 0 && button !== 0) return;
526
536
  if (this.getStylusModeEnabled?.() && e.evt) e.evt.preventDefault();
527
537
  this.clearTransformers();
528
538
  this.selectedIds = [];
539
+ this.marqueeRect?.destroy();
529
540
  const pos = konvaStage.getRelativePointerPosition();
530
541
  if (!pos) return;
531
542
  this.marqueeStartPos = pos;
@@ -543,7 +554,7 @@ export class Selector {
543
554
  });
544
555
  this.getBackgroundLayer(konvaStage).add(this.marqueeRect);
545
556
  });
546
- konvaStage.on("mousemove touchmove", (e) => {
557
+ konvaStage.on(mouseOnly ? "mousemove" : "mousemove touchmove", (e) => {
547
558
  if (!this.marqueeRect || !this.marqueeStartPos || !this.marqueeStage) return;
548
559
  if (this.isStylusRejected(e.evt)) return;
549
560
  if (this.getStylusModeEnabled?.() && e.evt) e.evt.preventDefault();
@@ -556,7 +567,7 @@ export class Selector {
556
567
  this.marqueeRect.setAttrs({ x, y, width, height });
557
568
  this.marqueeStage.batchDraw();
558
569
  });
559
- konvaStage.on("mouseup touchend", (e) => {
570
+ konvaStage.on(mouseOnly ? "mouseup" : "mouseup touchend", (e) => {
560
571
  if (!this.marqueeRect || !this.marqueeStage) return;
561
572
  if (this.isStylusRejected(e.evt)) return;
562
573
  const selectionBox = this.marqueeRect.getClientRect();
@@ -567,6 +578,7 @@ export class Selector {
567
578
  this.marqueeStage = null;
568
579
  return;
569
580
  }
581
+ if (mouseOnly) this.suppressStageClick = true;
570
582
  const groups = this.getPageShapeGroups(this.marqueeStage);
571
583
  const matchedIds = [];
572
584
  groups.forEach((group) => {
@@ -128,6 +128,8 @@ export declare class Painter {
128
128
  activateEraser(): void;
129
129
  highlightRange(range: Range, annotation: IAnnotationType): void;
130
130
  selectAnnotation(id: string): void;
131
+ /** Clears the current annotation selection without touching the store. */
132
+ clearSelection(): void;
131
133
  initAnnotations(annotations: IAnnotationStore[]): Promise<void>;
132
134
  update(id: string, updates: Partial<IAnnotationStore>): void;
133
135
  delete(id: string, emit?: boolean): void;
@@ -624,6 +624,10 @@ export class Painter {
624
624
  this.selector.select(id);
625
625
  this.callbacks.setDefaultMode();
626
626
  }
627
+ /** Clears the current annotation selection without touching the store. */
628
+ clearSelection() {
629
+ this.selector.delete();
630
+ }
627
631
  async initAnnotations(annotations) {
628
632
  annotations.forEach((annotation) => {
629
633
  this.saveToStore(annotation, true);
@@ -6,8 +6,7 @@ import {
6
6
  PDFDict,
7
7
  PDFName,
8
8
  PDFRawStream,
9
- PDFString,
10
- StandardFonts
9
+ PDFString
11
10
  } from "pdf-lib";
12
11
  import { CHECKBOX_STYLE_TABLE } from "../checkbox-styles.js";
13
12
  export async function writeFormFieldsToPdf(pdfDoc, fieldValues, placedDefinitions = []) {
@@ -265,7 +264,7 @@ async function createSignatureField(pdfDoc, page, def, fv) {
265
264
  if (def.required) sig.setFlagTo(AcroFieldFlags.Required, true);
266
265
  const v = fv?.value;
267
266
  const signed = typeof v === "string" && v.startsWith("data:image");
268
- const appearanceRef = signed ? await buildSignedSignatureAppearance(pdfDoc, v, width, height) : await buildSignaturePlaceholderAppearance(pdfDoc, width, height);
267
+ const appearanceRef = signed ? await buildSignedSignatureAppearance(pdfDoc, v, width, height) : buildEmptySignatureAppearance(pdfDoc, width, height);
269
268
  const widgetDict = context.obj({
270
269
  Type: "Annot",
271
270
  Subtype: "Widget",
@@ -282,54 +281,25 @@ async function createSignatureField(pdfDoc, page, def, fv) {
282
281
  form.acroForm.addField(fieldRef);
283
282
  page.node.addAnnot(widgetRef);
284
283
  }
285
- async function buildSignaturePlaceholderAppearance(pdfDoc, width, height) {
284
+ function buildEmptySignatureAppearance(pdfDoc, width, height) {
286
285
  const { context } = pdfDoc;
287
- const font = await pdfDoc.embedFont(StandardFonts.Helvetica);
288
- const label = "Sign here";
289
- const fontSize = Math.max(6, Math.min(12, height * 0.4));
290
- const textWidth = font.widthOfTextAtSize(label, fontSize);
291
- const textHeight = font.heightAtSize(fontSize);
292
- const textX = (width - textWidth) / 2;
293
- const textY = (height - textHeight) / 2;
294
- const ops = [
295
- "q",
296
- "0.55 0.55 0.55 RG",
297
- "0.75 w",
298
- "[3 2] 0 d",
299
- `0.5 0.5 ${(width - 1).toFixed(3)} ${(height - 1).toFixed(3)} re`,
300
- "S",
301
- "0.55 0.55 0.55 rg",
302
- "BT",
303
- `/F1 ${fontSize.toFixed(3)} Tf`,
304
- `${textX.toFixed(3)} ${textY.toFixed(3)} Td`,
305
- `(${label}) Tj`,
306
- "ET",
307
- "Q"
308
- ].join("\n");
309
286
  const dict = context.obj({
310
287
  Type: "XObject",
311
288
  Subtype: "Form",
312
289
  BBox: [0, 0, width, height],
313
290
  Matrix: [1, 0, 0, 1, 0, 0],
314
- Resources: { Font: { F1: font.ref } }
291
+ Resources: {}
315
292
  });
316
- const stream = PDFRawStream.of(dict, new TextEncoder().encode(ops));
293
+ const stream = PDFRawStream.of(dict, new Uint8Array(0));
317
294
  return context.register(stream);
318
295
  }
319
296
  async function buildSignedSignatureAppearance(pdfDoc, dataUrl, width, height) {
320
297
  const match = dataUrl.match(/^data:(image\/[a-zA-Z0-9.+-]+);base64,(.+)$/);
321
- if (!match) return buildSignaturePlaceholderAppearance(pdfDoc, width, height);
298
+ if (!match) return buildEmptySignatureAppearance(pdfDoc, width, height);
322
299
  const mimeType = match[1] ?? "";
323
300
  const base64 = match[2] ?? "";
324
301
  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;
302
+ const { drawX, drawY, drawW, drawH } = fitImageInBox(image, width, height);
333
303
  const ops = [
334
304
  "q",
335
305
  `${drawW.toFixed(3)} 0 0 ${drawH.toFixed(3)} ${drawX.toFixed(3)} ${drawY.toFixed(3)} cm`,
@@ -346,6 +316,20 @@ async function buildSignedSignatureAppearance(pdfDoc, dataUrl, width, height) {
346
316
  const stream = PDFRawStream.of(dict, new TextEncoder().encode(ops));
347
317
  return pdfDoc.context.register(stream);
348
318
  }
319
+ function fitImageInBox(image, boxW, boxH) {
320
+ const imgAspect = image.width / image.height;
321
+ const boxAspect = boxW / boxH;
322
+ let drawW = boxW;
323
+ let drawH = boxH;
324
+ if (imgAspect > boxAspect) drawH = boxW / imgAspect;
325
+ else drawW = boxH * imgAspect;
326
+ return {
327
+ drawX: (boxW - drawW) / 2,
328
+ drawY: (boxH - drawH) / 2,
329
+ drawW,
330
+ drawH
331
+ };
332
+ }
349
333
  async function embedSignatureImage(pdfDoc, field) {
350
334
  const dataUrl = field.value;
351
335
  const match = dataUrl.match(/^data:(image\/[a-zA-Z0-9.+-]+);base64,(.+)$/);
@@ -370,11 +354,12 @@ async function embedSignatureImage(pdfDoc, field) {
370
354
  const pageRef = page.ref;
371
355
  const widgetPage = widget.P();
372
356
  if (widgetPage === pageRef || !widgetPage) {
357
+ const { drawX, drawY, drawW, drawH } = fitImageInBox(image, rect.width, rect.height);
373
358
  page.drawImage(image, {
374
- x: rect.x,
375
- y: rect.y,
376
- width: rect.width,
377
- height: rect.height
359
+ x: rect.x + drawX,
360
+ y: rect.y + drawY,
361
+ width: drawW,
362
+ height: drawH
378
363
  });
379
364
  break;
380
365
  }
@@ -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 +1 @@
1
- .kviewer-text-layer{left:0;line-height:1;overflow:hidden;position:absolute;top:0;-webkit-user-select:none;-moz-user-select:none;user-select:none;z-index:1}.kviewer-text-layer--interactive{cursor:text;-webkit-user-select:text;-moz-user-select:text;user-select:text}.KViewer_is_painting.KViewer_painting_type_12 .kviewer-annotation-layer,.KViewer_is_painting.KViewer_painting_type_12 .kviewer-annotation-layer *,.KViewer_is_painting.KViewer_painting_type_13 .kviewer-annotation-layer,.KViewer_is_painting.KViewer_painting_type_13 .kviewer-annotation-layer *,.KViewer_is_painting.KViewer_painting_type_5 .kviewer-annotation-layer,.KViewer_is_painting.KViewer_painting_type_5 .kviewer-annotation-layer *,.KViewer_is_painting.KViewer_painting_type_6 .kviewer-annotation-layer,.KViewer_is_painting.KViewer_painting_type_6 .kviewer-annotation-layer *,.KViewer_is_painting.KViewer_painting_type_7 .kviewer-annotation-layer,.KViewer_is_painting.KViewer_painting_type_7 .kviewer-annotation-layer *,.KViewer_is_painting.KViewer_painting_type_8 .kviewer-annotation-layer,.KViewer_is_painting.KViewer_painting_type_8 .kviewer-annotation-layer *{cursor:crosshair!important}.KViewer_is_painting.KViewer_painting_type_4 .kviewer-annotation-layer,.KViewer_is_painting.KViewer_painting_type_4 .kviewer-annotation-layer *{cursor:text!important}.KViewer_is_painting.KViewer_painting_type_11 .kviewer-annotation-layer,.KViewer_is_painting.KViewer_painting_type_11 .kviewer-annotation-layer *{cursor:crosshair!important}.KViewer_selector_hover{cursor:pointer!important}.kviewer-form-layer{left:0;overflow:hidden;position:absolute;top:0}.kviewer-form-field{box-sizing:border-box;position:absolute}.kviewer-form-input{background-color:color-mix(in srgb,var(--kvw-field-color,#1677ff) 14%,transparent);border:1px solid transparent;box-sizing:border-box;color:#000;display:block;font-family:-apple-system,BlinkMacSystemFont,Helvetica Neue,Helvetica,Arial,sans-serif;font-weight:500;height:100%;line-height:normal;margin:0;outline:none;padding:1px 2px;resize:none;width:100%;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale;text-rendering:optimizeLegibility;vertical-align:top}.kviewer-form-input:hover{background-color:color-mix(in srgb,var(--kvw-field-color,#1677ff) 22%,transparent);border-color:rgba(0,0,0,.2)}.kviewer-form-input:focus{background-color:color-mix(in srgb,var(--kvw-field-color,#1677ff) 28%,transparent);border-color:var(--kvw-field-color,var(--color-primary,#3b82f6));outline:2px solid var(--kvw-field-color,var(--color-primary,#3b82f6))}.kviewer-form-uselect{align-items:center;background-color:color-mix(in srgb,var(--kvw-field-color,#1677ff) 14%,transparent);border:1px solid transparent;border-radius:0;box-sizing:border-box;color:#000;cursor:pointer;display:flex;height:100%;line-height:1;padding:0 11px 0 2px;width:100%}.kviewer-form-uselect:hover{background-color:color-mix(in srgb,var(--kvw-field-color,#1677ff) 22%,transparent);border-color:rgba(0,0,0,.2)}.kviewer-form-uselect:focus-visible{background-color:color-mix(in srgb,var(--kvw-field-color,#1677ff) 28%,transparent);outline:2px solid var(--kvw-field-color,var(--color-primary,#3b82f6));outline-offset:-2px}.kviewer-form-uselect:disabled{cursor:default}.kviewer-form-listbox{overflow-y:auto;padding:0}.kviewer-form-listbox option{padding:1px 3px}.kviewer-form-editable-combo{height:100%;position:relative;width:100%}.kviewer-form-editable-combo input{height:100%;width:100%}.kviewer-form-checkbox,.kviewer-form-radio{align-items:center;background:#fff;box-sizing:border-box;cursor:pointer;display:flex;height:100%;justify-content:center;width:100%}.kviewer-form-checkbox input,.kviewer-form-radio input{accent-color:var(--kvw-field-color,var(--color-primary,#3b82f6));cursor:pointer;height:80%;margin:0;max-height:18px;max-width:18px;width:80%}.kviewer-form-checkbox--detected{background:transparent}.kviewer-form-checkbox--circle{border-radius:50%}.kviewer-form-checkbox__icon{color:#1a1a1a;height:65%;width:65%}.kviewer-form-checkbox--circle .kviewer-form-checkbox__icon{height:50%;width:50%}.kviewer-form-radio input{accent-color:var(--kvw-field-color,var(--color-primary,#3b82f6));cursor:pointer;height:80%;margin:0;max-height:18px;max-width:18px;width:80%}.kviewer-form-button{align-items:center;background:#c8c8c8;border:1px solid rgba(0,0,0,.35);box-sizing:border-box;color:#000;cursor:pointer;display:flex;font-family:inherit;font-weight:700;height:100%;justify-content:center;line-height:1;padding:0 6px;text-align:center;width:100%;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}.kviewer-form-button:hover{background:#bdbdbd}.kviewer-form-button:active{background:#b0b0b0}.kviewer-form-signature{align-items:center;background:rgba(255,255,200,.15);border:1px dashed rgba(0,0,0,.2);cursor:pointer;display:flex;height:100%;justify-content:center;width:100%}.kviewer-form-signature:hover{background:color-mix(in srgb,var(--kvw-field-color,#3b82f6) 8%,transparent);border-color:var(--kvw-field-color,var(--color-primary,#3b82f6))}.kviewer-form-signature--filled{background:transparent;border-color:transparent;border-style:solid}.kviewer-form-signature__preview{max-height:100%;max-width:100%;-o-object-fit:contain;object-fit:contain}.kviewer-form-signature__placeholder{color:rgba(0,0,0,.4);font-size:10px;pointer-events:none;-webkit-user-select:none;-moz-user-select:none;user-select:none}.kviewer-form-signature--static{cursor:default}.kviewer-form-signature--static:hover{background:rgba(255,255,200,.15);border-color:rgba(0,0,0,.2)}.kviewer-form-signature__marker{align-items:center;display:inline-flex;gap:4px}.kviewer-form-signature__marker-icon{flex:0 0 auto;height:12px;width:12px}
1
+ .kviewer-text-layer{left:0;line-height:1;overflow:hidden;position:absolute;top:0;-webkit-user-select:none;-moz-user-select:none;user-select:none;z-index:1}.kviewer-text-layer--interactive{cursor:text;-webkit-user-select:text;-moz-user-select:text;user-select:text}.KViewer_is_painting.KViewer_painting_type_12 .kviewer-annotation-layer,.KViewer_is_painting.KViewer_painting_type_12 .kviewer-annotation-layer *,.KViewer_is_painting.KViewer_painting_type_13 .kviewer-annotation-layer,.KViewer_is_painting.KViewer_painting_type_13 .kviewer-annotation-layer *,.KViewer_is_painting.KViewer_painting_type_5 .kviewer-annotation-layer,.KViewer_is_painting.KViewer_painting_type_5 .kviewer-annotation-layer *,.KViewer_is_painting.KViewer_painting_type_6 .kviewer-annotation-layer,.KViewer_is_painting.KViewer_painting_type_6 .kviewer-annotation-layer *,.KViewer_is_painting.KViewer_painting_type_7 .kviewer-annotation-layer,.KViewer_is_painting.KViewer_painting_type_7 .kviewer-annotation-layer *,.KViewer_is_painting.KViewer_painting_type_8 .kviewer-annotation-layer,.KViewer_is_painting.KViewer_painting_type_8 .kviewer-annotation-layer *{cursor:crosshair!important}.KViewer_is_painting.KViewer_painting_type_4 .kviewer-annotation-layer,.KViewer_is_painting.KViewer_painting_type_4 .kviewer-annotation-layer *{cursor:text!important}.KViewer_is_painting.KViewer_painting_type_11 .kviewer-annotation-layer,.KViewer_is_painting.KViewer_painting_type_11 .kviewer-annotation-layer *{cursor:crosshair!important}.KViewer_selector_hover{cursor:pointer!important}.kviewer-form-layer{left:0;overflow:hidden;position:absolute;top:0}.kviewer-form-field{box-sizing:border-box;position:absolute}.kviewer-form-input{background-color:color-mix(in srgb,var(--kvw-field-color,#1677ff) 14%,transparent);border:1px solid transparent;box-sizing:border-box;color:#000;display:block;font-family:-apple-system,BlinkMacSystemFont,Helvetica Neue,Helvetica,Arial,sans-serif;font-weight:500;height:100%;line-height:normal;margin:0;outline:none;padding:1px 2px;resize:none;width:100%;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale;text-rendering:optimizeLegibility;vertical-align:top}.kviewer-form-input:hover{background-color:color-mix(in srgb,var(--kvw-field-color,#1677ff) 22%,transparent);border-color:rgba(0,0,0,.2)}.kviewer-form-input:focus{background-color:color-mix(in srgb,var(--kvw-field-color,#1677ff) 28%,transparent);border-color:var(--kvw-field-color,var(--color-primary,#3b82f6));outline:2px solid var(--kvw-field-color,var(--color-primary,#3b82f6))}.kviewer-form-uselect{align-items:center;background-color:color-mix(in srgb,var(--kvw-field-color,#1677ff) 14%,transparent);border:1px solid transparent;border-radius:0;box-sizing:border-box;color:#000;cursor:pointer;display:flex;height:100%;line-height:1;padding:0 11px 0 2px;width:100%}.kviewer-form-uselect:hover{background-color:color-mix(in srgb,var(--kvw-field-color,#1677ff) 22%,transparent);border-color:rgba(0,0,0,.2)}.kviewer-form-uselect:focus-visible{background-color:color-mix(in srgb,var(--kvw-field-color,#1677ff) 28%,transparent);outline:2px solid var(--kvw-field-color,var(--color-primary,#3b82f6));outline-offset:-2px}.kviewer-form-uselect:disabled{cursor:default}.kviewer-form-listbox{overflow-y:auto;padding:0}.kviewer-form-listbox option{padding:1px 3px}.kviewer-form-editable-combo{height:100%;position:relative;width:100%}.kviewer-form-editable-combo input{height:100%;width:100%}.kviewer-form-checkbox,.kviewer-form-radio{align-items:center;background:#fff;box-sizing:border-box;cursor:pointer;display:flex;height:100%;justify-content:center;width:100%}.kviewer-form-checkbox input,.kviewer-form-radio input{accent-color:var(--kvw-field-color,var(--color-primary,#3b82f6));cursor:pointer;height:80%;margin:0;max-height:18px;max-width:18px;width:80%}.kviewer-form-checkbox--detected{background:transparent}.kviewer-form-checkbox--circle{border-radius:50%}.kviewer-form-checkbox__icon{color:#1a1a1a;height:65%;width:65%}.kviewer-form-checkbox--circle .kviewer-form-checkbox__icon{height:50%;width:50%}.kviewer-form-radio input{accent-color:var(--kvw-field-color,var(--color-primary,#3b82f6));cursor:pointer;height:80%;margin:0;max-height:18px;max-width:18px;width:80%}.kviewer-form-button{align-items:center;background:#c8c8c8;border:1px solid rgba(0,0,0,.35);box-sizing:border-box;color:#000;cursor:pointer;display:flex;font-family:inherit;font-weight:700;height:100%;justify-content:center;line-height:1;padding:0 6px;text-align:center;width:100%;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}.kviewer-form-button:hover{background:#bdbdbd}.kviewer-form-button:active{background:#b0b0b0}.kviewer-form-signature{align-items:center;background:rgba(255,255,200,.15);border:1px dashed rgba(0,0,0,.2);cursor:pointer;display:flex;height:100%;justify-content:center;width:100%}.kviewer-form-signature:hover{background:color-mix(in srgb,var(--kvw-field-color,#3b82f6) 8%,transparent);border-color:var(--kvw-field-color,var(--color-primary,#3b82f6))}.kviewer-form-signature--filled{background:transparent;border-color:transparent;border-style:solid}.kviewer-form-signature__preview{max-height:100%;max-width:100%;-o-object-fit:contain;object-fit:contain}.kviewer-form-signature__placeholder{color:rgba(0,0,0,.4);font-size:10px;pointer-events:none;-webkit-user-select:none;-moz-user-select:none;user-select:none}.kviewer-form-field__body--locked .kviewer-form-signature,.kviewer-form-field__body--locked .kviewer-form-signature:hover{border-color:transparent}.kviewer-form-signature--static{cursor:default}.kviewer-form-signature--static:hover{background:rgba(255,255,200,.15);border-color:rgba(0,0,0,.2)}.kviewer-form-signature__marker{align-items:center;display:inline-flex;gap:4px}.kviewer-form-signature__marker-icon{flex:0 0 auto;height:12px;width:12px}
@@ -55,6 +55,9 @@
55
55
  <UIcon name="i-lucide-pencil" class="text-sm text-muted" />
56
56
  </button>
57
57
 
58
+ <!-- Host-supplied custom items (selectionItems prop) -->
59
+ <SelectionToolbarItems :items="customItems" :ctx="selectionCtx" />
60
+
58
61
  <!-- Delete -->
59
62
  <button
60
63
  class="flex items-center justify-center w-7 h-7 rounded-md hover:bg-default cursor-pointer"
@@ -69,12 +72,20 @@
69
72
  </template>
70
73
 
71
74
  <script setup>
72
- import { computed } from "vue";
75
+ import { computed, inject } from "vue";
73
76
  import { useViewerState } from "../composables/useViewerState";
74
77
  import { useKviewerI18n } from "../composables/useKviewerI18n";
75
78
  import { defaultOptions, annotationDefinitions } from "../annotation/engine/config";
76
79
  import { AnnotationType } from "../annotation/engine/types";
80
+ import { SELECTION_ITEMS_KEY } from "../selection-items";
81
+ import SelectionToolbarItems from "./SelectionToolbarItems.vue";
77
82
  const state = useViewerState();
83
+ const customItems = inject(SELECTION_ITEMS_KEY, void 0);
84
+ const selectionCtx = computed(() => ({
85
+ kind: "annotation",
86
+ annotations: state.selectedAnnotations.value.length > 0 ? state.selectedAnnotations.value : state.selectedAnnotation.value ? [state.selectedAnnotation.value] : [],
87
+ field: null
88
+ }));
78
89
  const { t } = useKviewerI18n();
79
90
  const colors = defaultOptions.colors;
80
91
  const isMultiSelect = computed(() => state.selectedAnnotations.value.length > 1);
@@ -2,6 +2,7 @@
2
2
  <div
3
3
  v-if="fields.length > 0"
4
4
  class="kviewer-form-layer"
5
+ :class="{ 'kviewer-form-layer--interactive': props.pointerEvents === 'auto' }"
5
6
  :style="{
6
7
  position: 'absolute',
7
8
  top: 0,
@@ -11,7 +12,6 @@
11
12
  transform: `scale(${props.scale})`,
12
13
  transformOrigin: '0 0',
13
14
  zIndex: props.zIndex,
14
- pointerEvents: props.pointerEvents,
15
15
  // Force native form controls (inputs, checkboxes, radios, scrollbars)
16
16
  // to render in light mode regardless of the user\'s OS theme — PDF
17
17
  // pages have a white background, so dark-mode controls clash.
@@ -57,7 +57,8 @@ watch(() => state.formEditMode.value, (enabled) => {
57
57
  });
58
58
  watch(() => state.activeTool.value, (tool) => {
59
59
  const isFormFieldTool = typeof tool === "number" && (tool === AnnotationType.FORM_TEXT || tool === AnnotationType.FORM_CHECKBOX || tool === AnnotationType.FORM_RADIO || tool === AnnotationType.FORM_SIGNATURE);
60
- if (tool !== "hand" && !isFormFieldTool) formFields.selectPlacedField(null);
60
+ const keepsSelection = tool === "hand" || tool === AnnotationType.SELECT || isFormFieldTool;
61
+ if (!keepsSelection) formFields.selectPlacedField(null);
61
62
  });
62
63
  function onKeyDown(e) {
63
64
  if (!selectedFieldId.value) return;
@@ -75,3 +76,7 @@ onBeforeUnmount(() => {
75
76
  window.removeEventListener("keydown", onKeyDown);
76
77
  });
77
78
  </script>
79
+
80
+ <style>
81
+ .kviewer-form-layer{pointer-events:none}.kviewer-form-layer--interactive .kviewer-form-field{pointer-events:auto}.kviewer-form-layer:not(.kviewer-form-layer--interactive) .kviewer-form-field,.kviewer-form-layer:not(.kviewer-form-layer--interactive) .kviewer-placed-chrome,.kviewer-form-layer:not(.kviewer-form-layer--interactive) .kviewer-placed-toolbar{pointer-events:none}
82
+ </style>
@@ -63,6 +63,7 @@
63
63
  height: cssHeight + 'px',
64
64
  zIndex: 3,
65
65
  pointerEvents: annotationPointerEvents,
66
+ cursor: annotationCursor,
66
67
  transform: 'translateZ(0)',
67
68
  overflow: 'hidden'
68
69
  }"
@@ -134,9 +135,31 @@ const annotationPointerEvents = computed(() => {
134
135
  if (state.readonly.value) return "none";
135
136
  return isSearchTextInteractive.value ? "none" : isAnnotating.value ? "auto" : "none";
136
137
  });
138
+ const annotationCursor = computed(() => {
139
+ const tool = state.activeTool.value;
140
+ if (tool === "marquee" || tool === "eraser") return "crosshair";
141
+ if (typeof tool !== "number") return "";
142
+ switch (tool) {
143
+ case AnnotationType.NONE:
144
+ case AnnotationType.SELECT:
145
+ return "";
146
+ // Stamp/signature hide the native cursor behind a floating image
147
+ // preview (body cursor 'none'); an element cursor here would
148
+ // override that and bring the arrow back on top of the preview.
149
+ case AnnotationType.STAMP:
150
+ case AnnotationType.SIGNATURE:
151
+ return "";
152
+ case AnnotationType.HIGHLIGHT:
153
+ case AnnotationType.STRIKEOUT:
154
+ case AnnotationType.UNDERLINE:
155
+ return "text";
156
+ default:
157
+ return "crosshair";
158
+ }
159
+ });
137
160
  const isUsingAnnotationTool = computed(() => {
138
161
  const tool = state.activeTool.value;
139
- return tool === "marquee" || tool === "eraser" || typeof tool === "number" && tool !== AnnotationType.NONE;
162
+ return tool === "marquee" || tool === "eraser" || typeof tool === "number" && tool !== AnnotationType.NONE && tool !== AnnotationType.SELECT;
140
163
  });
141
164
  const formFieldPointerEvents = computed(() => {
142
165
  if (state.readonly.value) return "none";
@@ -0,0 +1,8 @@
1
+ import type { ViewerSelectionContext, ViewerSelectionItem } from '../selection-items.js';
2
+ type __VLS_Props = {
3
+ items: ViewerSelectionItem[] | undefined;
4
+ ctx: ViewerSelectionContext;
5
+ };
6
+ 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>;
7
+ declare const _default: typeof __VLS_export;
8
+ export default _default;
@@ -0,0 +1,43 @@
1
+ <template>
2
+ <template v-for="item in visibleItems" :key="item.key">
3
+ <button
4
+ v-if="item.type === 'button'"
5
+ class="flex items-center justify-center h-7 rounded-md hover:bg-default cursor-pointer disabled:opacity-50 disabled:cursor-not-allowed"
6
+ :class="item.icon ? 'w-7' : 'px-2'"
7
+ :title="item.label"
8
+ :disabled="item.disabled"
9
+ :data-testid="`selection-item-${item.key}`"
10
+ @click="item.onSelect(props.ctx)"
11
+ >
12
+ <UIcon v-if="item.icon" :name="item.icon" class="text-sm text-muted" />
13
+ <span v-else class="text-xs text-muted whitespace-nowrap">{{ item.label }}</span>
14
+ </button>
15
+ <USelect
16
+ v-else-if="item.type === 'select'"
17
+ :model-value="selectValue(item)"
18
+ :items="item.items"
19
+ :placeholder="item.placeholder"
20
+ :disabled="item.disabled"
21
+ :aria-label="item.label"
22
+ :title="item.label"
23
+ class="w-24"
24
+ size="sm"
25
+ :data-testid="`selection-item-${item.key}`"
26
+ @update:model-value="(v) => item.onUpdate(String(v), props.ctx)"
27
+ />
28
+ </template>
29
+ </template>
30
+
31
+ <script setup>
32
+ import { computed } from "vue";
33
+ const props = defineProps({
34
+ items: { type: null, required: true },
35
+ ctx: { type: Object, required: true }
36
+ });
37
+ const visibleItems = computed(
38
+ () => (props.items ?? []).filter((item) => item.visible?.(props.ctx) ?? true)
39
+ );
40
+ function selectValue(item) {
41
+ return typeof item.value === "function" ? item.value(props.ctx) : item.value;
42
+ }
43
+ </script>
@@ -0,0 +1,8 @@
1
+ import type { ViewerSelectionContext, ViewerSelectionItem } from '../selection-items.js';
2
+ type __VLS_Props = {
3
+ items: ViewerSelectionItem[] | undefined;
4
+ ctx: ViewerSelectionContext;
5
+ };
6
+ 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>;
7
+ declare const _default: typeof __VLS_export;
8
+ export default _default;
@@ -3,6 +3,7 @@ import type { AddFormFieldPayload, FormFieldDefinition, IAnnotationStore, Placed
3
3
  import { type ExportPdfOptions } from '../annotation/pdf-export/export.js';
4
4
  import type { ViewerMenuItem } from '../menu-items.js';
5
5
  import type { ViewerToolEntry } from '../toolbar-tools.js';
6
+ import { type ViewerSelectionItem } from '../selection-items.js';
6
7
  import { type KviewerMessageOverrides } from '../composables/useKviewerI18n.js';
7
8
  import { type KviewerLocale } from '../i18n/messages.js';
8
9
  type __VLS_Props = {
@@ -63,6 +64,11 @@ type __VLS_Props = {
63
64
  * toggles or actions without replacing the entire header. Ignored
64
65
  * when the host supplies a custom `#header` slot. */
65
66
  menuItems?: ViewerMenuItem[];
67
+ /** Custom controls appended to the floating selection popover — shown
68
+ * when an annotation or a placed form field (e.g. a signature field)
69
+ * is selected. Buttons and selects; every callback receives the
70
+ * current selection. See {@link ViewerSelectionItem}. */
71
+ selectionItems?: ViewerSelectionItem[];
66
72
  /** Configures which toolbar tools render and in what order. Entries are
67
73
  * built-in tool IDs (strings) or `{ type: 'slot', ... }` objects that
68
74
  * inject a custom button via a `#tool-<name>` slot. When omitted, the
@@ -78,6 +84,11 @@ type __VLS_Props = {
78
84
  messages?: KviewerMessageOverrides;
79
85
  };
80
86
  declare function exportPdf(options?: ExportPdfOptions): Promise<Uint8Array>;
87
+ /** Print via the real PDF instead of the on-screen canvases: export the
88
+ * document (annotations + form values baked in) and hand the vector bytes
89
+ * to the browser's native PDF print pipeline. Screen canvases are sized
90
+ * for the monitor and would come out blurry on paper. */
91
+ declare function printPdf(options?: ExportPdfOptions): Promise<void>;
81
92
  type ImportMode = 'replace' | 'merge';
82
93
  declare function importAnnotations(annotations: IAnnotationStore[], options?: {
83
94
  mode?: ImportMode;
@@ -100,6 +111,10 @@ declare const __VLS_base: import("vue").DefineComponent<__VLS_Props, {
100
111
  getAnnotations: () => IAnnotationStore[];
101
112
  importAnnotations: typeof importAnnotations;
102
113
  exportPdf: typeof exportPdf;
114
+ /** Export the document (same options as `exportPdf`, minus `download`)
115
+ * and open the browser's print dialog on the resulting vector PDF —
116
+ * full print quality, unlike printing the screen canvases. */
117
+ printPdf: typeof printPdf;
103
118
  getKonvaCanvasState: typeof getKonvaCanvasState;
104
119
  getFormFieldValues: () => import("../annotation/engine/types.js").FormFieldValue[];
105
120
  setFormFieldValue: (fieldName: string, value: string | boolean | string[]) => void;
@@ -180,6 +195,7 @@ declare const __VLS_base: import("vue").DefineComponent<__VLS_Props, {
180
195
  clickToSign: boolean;
181
196
  readonly: boolean;
182
197
  active: boolean;
198
+ formEditMode: boolean;
183
199
  locale: KviewerLocale;
184
200
  menuItems: ViewerMenuItem[];
185
201
  tools: ViewerToolEntry[];
@@ -188,11 +204,11 @@ declare const __VLS_base: import("vue").DefineComponent<__VLS_Props, {
188
204
  signatureHandlers: SignatureHandlers;
189
205
  viewMode: ViewMode;
190
206
  shapeDetection: boolean;
191
- formEditMode: boolean;
192
207
  roleColors: Record<string, string>;
193
208
  activeRoleId: string | null;
194
209
  editablePlacedFields: boolean;
195
210
  fieldDefaults: PlacedFieldDefaults;
211
+ selectionItems: ViewerSelectionItem[];
196
212
  messages: KviewerMessageOverrides;
197
213
  }, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
198
214
  declare const __VLS_export: __VLS_WithSlots<typeof __VLS_base, __VLS_Slots>;