kviewer 0.2.1 → 0.2.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 (111) hide show
  1. package/README.md +19 -0
  2. package/dist/module.d.mts +8 -1
  3. package/dist/module.json +1 -1
  4. package/dist/module.mjs +25 -3
  5. package/dist/runtime/annotation/engine/canvas-resolution.d.ts +7 -0
  6. package/dist/runtime/annotation/engine/canvas-resolution.js +21 -0
  7. package/dist/runtime/annotation/engine/editor/editor.d.ts +7 -2
  8. package/dist/runtime/annotation/engine/editor/editor.js +14 -0
  9. package/dist/runtime/annotation/engine/editor/selector.d.ts +13 -1
  10. package/dist/runtime/annotation/engine/editor/selector.js +60 -8
  11. package/dist/runtime/annotation/engine/import-normalize.js +3 -3
  12. package/dist/runtime/annotation/engine/painter.d.ts +33 -2
  13. package/dist/runtime/annotation/engine/painter.js +88 -2
  14. package/dist/runtime/annotation/engine/store.js +3 -0
  15. package/dist/runtime/annotation/engine/tools/arrow.js +0 -1
  16. package/dist/runtime/annotation/engine/tools/circle.js +0 -2
  17. package/dist/runtime/annotation/engine/tools/free-highlight.js +0 -2
  18. package/dist/runtime/annotation/engine/tools/freehand.js +0 -2
  19. package/dist/runtime/annotation/engine/tools/rectangle.js +0 -2
  20. package/dist/runtime/annotation/engine/types.d.ts +9 -2
  21. package/dist/runtime/annotation/font-style.d.ts +15 -0
  22. package/dist/runtime/annotation/font-style.js +19 -0
  23. package/dist/runtime/annotation/parsers/extractCheckboxStyles.d.ts +8 -0
  24. package/dist/runtime/annotation/parsers/extractCheckboxStyles.js +24 -1
  25. package/dist/runtime/annotation/pdf-export/annotation-flags.d.ts +8 -0
  26. package/dist/runtime/annotation/pdf-export/annotation-flags.js +5 -0
  27. package/dist/runtime/annotation/pdf-export/export-form-fields.js +3 -1
  28. package/dist/runtime/annotation/pdf-export/export.js +17 -4
  29. package/dist/runtime/annotation/pdf-export/parse.d.ts +22 -1
  30. package/dist/runtime/annotation/pdf-export/parse.js +32 -2
  31. package/dist/runtime/annotation/pdf-export/parse_circle.js +5 -5
  32. package/dist/runtime/annotation/pdf-export/parse_freetext.js +33 -24
  33. package/dist/runtime/annotation/pdf-export/parse_highlight.js +5 -5
  34. package/dist/runtime/annotation/pdf-export/parse_ink.js +7 -6
  35. package/dist/runtime/annotation/pdf-export/parse_line.js +7 -6
  36. package/dist/runtime/annotation/pdf-export/parse_polyline.js +7 -6
  37. package/dist/runtime/annotation/pdf-export/parse_square.js +5 -5
  38. package/dist/runtime/annotation/pdf-export/parse_stamp.js +6 -7
  39. package/dist/runtime/annotation/pdf-export/parse_strikeout.js +5 -5
  40. package/dist/runtime/annotation/pdf-export/parse_text.js +5 -5
  41. package/dist/runtime/annotation/pdf-export/parse_underline.js +5 -5
  42. package/dist/runtime/annotation/pdf-export/unicode-font.d.ts +5 -0
  43. package/dist/runtime/annotation/pdf-export/unicode-font.js +31 -0
  44. package/dist/runtime/annotation/pdf-export/winansi.d.ts +17 -0
  45. package/dist/runtime/annotation/pdf-export/winansi.js +77 -0
  46. package/dist/runtime/annotation/pdf-import/decode_circle.js +0 -1
  47. package/dist/runtime/annotation/pdf-import/decode_ink.js +0 -1
  48. package/dist/runtime/annotation/pdf-import/decode_line.js +0 -1
  49. package/dist/runtime/annotation/pdf-import/decode_square.js +0 -1
  50. package/dist/runtime/annotation/pdf-import/extract_stamp_appearance.d.ts +12 -0
  51. package/dist/runtime/annotation/pdf-import/extract_stamp_appearance.js +17 -4
  52. package/dist/runtime/annotation/pdf-import/types.d.ts +2 -0
  53. package/dist/runtime/assets/fonts/LICENSE-LiberationSans +102 -0
  54. package/dist/runtime/assets/fonts/LiberationSans-Regular.ttf +0 -0
  55. package/dist/runtime/assets/kviewer.css +1 -1
  56. package/dist/runtime/components/AnnotationToolbar.vue +6 -4
  57. package/dist/runtime/components/PdfPage.d.vue.ts +16 -0
  58. package/dist/runtime/components/PdfPage.vue +170 -9
  59. package/dist/runtime/components/PdfPage.vue.d.ts +16 -0
  60. package/dist/runtime/components/PerfHud.d.vue.ts +3 -0
  61. package/dist/runtime/components/PerfHud.vue +97 -0
  62. package/dist/runtime/components/PerfHud.vue.d.ts +3 -0
  63. package/dist/runtime/components/ToolButton.vue +4 -1
  64. package/dist/runtime/components/Viewer.d.vue.ts +25 -2
  65. package/dist/runtime/components/Viewer.vue +174 -12
  66. package/dist/runtime/components/Viewer.vue.d.ts +25 -2
  67. package/dist/runtime/components/ViewerBar.d.vue.ts +21 -1
  68. package/dist/runtime/components/ViewerBar.vue +183 -101
  69. package/dist/runtime/components/ViewerBar.vue.d.ts +21 -1
  70. package/dist/runtime/components/ViewerTabs.d.vue.ts +10 -2
  71. package/dist/runtime/components/ViewerTabs.vue +13 -2
  72. package/dist/runtime/components/ViewerTabs.vue.d.ts +10 -2
  73. package/dist/runtime/components/form-fields/FormDropdown.vue +50 -19
  74. package/dist/runtime/components/form-fields/FormSignatureField.vue +4 -2
  75. package/dist/runtime/components/form-fields/FormTextField.vue +7 -18
  76. package/dist/runtime/components/modals/FreeTextModal.vue +6 -4
  77. package/dist/runtime/components/modals/SignatureDrawModal.vue +6 -4
  78. package/dist/runtime/components/panels/PlacedFieldSidebar.vue +67 -50
  79. package/dist/runtime/components/panels/SignaturePicker.vue +6 -4
  80. package/dist/runtime/components/panels/StampPicker.vue +3 -1
  81. package/dist/runtime/components/tools/AnnotationToolButton.d.vue.ts +10 -0
  82. package/dist/runtime/components/tools/AnnotationToolButton.vue +33 -0
  83. package/dist/runtime/components/tools/AnnotationToolButton.vue.d.ts +10 -0
  84. package/dist/runtime/components/tools/MarqueeTool.vue +3 -1
  85. package/dist/runtime/components/tools/PageSettings.vue +13 -11
  86. package/dist/runtime/components/tools/SearchTool.vue +12 -10
  87. package/dist/runtime/components/tools/ToolProperties.vue +7 -5
  88. package/dist/runtime/components/tools/ToolbarMenu.d.vue.ts +9 -0
  89. package/dist/runtime/components/tools/ToolbarMenu.vue +79 -0
  90. package/dist/runtime/components/tools/ToolbarMenu.vue.d.ts +9 -0
  91. package/dist/runtime/components/tools/ZoomControls.vue +17 -9
  92. package/dist/runtime/composables/canvas-limits.d.ts +22 -0
  93. package/dist/runtime/composables/canvas-limits.js +10 -0
  94. package/dist/runtime/composables/useAnnotationEngine.js +3 -1
  95. package/dist/runtime/composables/useFormFields.d.ts +6 -0
  96. package/dist/runtime/composables/useFormFields.js +28 -0
  97. package/dist/runtime/composables/useInertiaPanzoom.d.ts +10 -0
  98. package/dist/runtime/composables/useInertiaPanzoom.js +57 -10
  99. package/dist/runtime/composables/useKviewerI18n.d.ts +18 -0
  100. package/dist/runtime/composables/useKviewerI18n.js +29 -0
  101. package/dist/runtime/composables/useScriptingManager.d.ts +8 -4
  102. package/dist/runtime/composables/useScriptingManager.js +2 -0
  103. package/dist/runtime/composables/useViewerState.d.ts +4 -0
  104. package/dist/runtime/composables/useViewerState.js +8 -0
  105. package/dist/runtime/i18n/messages.d.ts +252 -0
  106. package/dist/runtime/i18n/messages.js +249 -0
  107. package/dist/runtime/public-types.d.ts +2 -0
  108. package/dist/runtime/toolbar-tools.d.ts +54 -0
  109. package/dist/runtime/toolbar-tools.js +79 -0
  110. package/dist/types.d.mts +1 -1
  111. package/package.json +14 -31
@@ -2,7 +2,11 @@
2
2
  <div ref="viewerRoot" class="flex flex-col h-full">
3
3
  <!-- Header slot: defaults to ViewerBar -->
4
4
  <slot name="header">
5
- <ViewerBar :menu-items="props.menuItems" />
5
+ <ViewerBar :menu-items="props.menuItems" :tools="props.tools">
6
+ <template v-for="name in toolSlotNames" :key="name" #[name]="scope">
7
+ <slot :name="name" v-bind="scope" />
8
+ </template>
9
+ </ViewerBar>
6
10
  </slot>
7
11
 
8
12
  <div class="flex flex-1 min-h-0">
@@ -61,6 +65,7 @@
61
65
  :page-number="singlePageMeta.pageNumber"
62
66
  :page-meta="singlePageMeta"
63
67
  :scale="pageScale"
68
+ :view-window="pageViewWindow(singlePageMeta.pageNumber)"
64
69
  />
65
70
  </div>
66
71
  </div>
@@ -89,6 +94,7 @@
89
94
  :page-number="meta.pageNumber"
90
95
  :page-meta="meta"
91
96
  :scale="pageScale"
97
+ :view-window="pageViewWindow(meta.pageNumber)"
92
98
  />
93
99
  </div>
94
100
  </div>
@@ -122,6 +128,7 @@
122
128
  :page-number="meta.pageNumber"
123
129
  :page-meta="meta"
124
130
  :scale="pageScale"
131
+ :view-window="pageViewWindow(meta.pageNumber)"
125
132
  />
126
133
  </div>
127
134
  </div>
@@ -133,6 +140,10 @@
133
140
 
134
141
  <ClientOnly>
135
142
  <FloatingPageIndicator :scroll-container="scrollContainer" />
143
+ <!-- Debug overlay for on-device profiling; renders only with
144
+ ?perf in the URL. Gated on `active` so tabbed hosts show a
145
+ single instance. -->
146
+ <PerfHud v-if="props.active" />
136
147
  </ClientOnly>
137
148
  </div>
138
149
 
@@ -181,14 +192,17 @@ import {
181
192
  nextTick,
182
193
  triggerRef,
183
194
  onMounted,
184
- onBeforeUnmount
195
+ onBeforeUnmount,
196
+ useSlots
185
197
  } from "vue";
198
+ import { useRuntimeConfig } from "#imports";
186
199
  import ViewerBar from "./ViewerBar.vue";
187
200
  import PdfPage from "./PdfPage.vue";
188
201
  import FreeTextModal from "./modals/FreeTextModal.vue";
189
202
  import PlacedFieldSidebar from "./panels/PlacedFieldSidebar.vue";
190
203
  import AnnotationToolbar from "./AnnotationToolbar.vue";
191
204
  import FloatingPageIndicator from "./FloatingPageIndicator.vue";
205
+ import PerfHud from "./PerfHud.vue";
192
206
  import { providePageSettings } from "../composables/usePageSettings";
193
207
  import { mapViewModeToFitMode, normalizeZoom, provideViewerState } from "../composables/useViewerState";
194
208
  import { provideViewerSearch } from "../composables/useViewerSearch";
@@ -206,11 +220,13 @@ import { createPageProxyCache } from "../composables/usePageProxyCache";
206
220
  import { createSearchIndex } from "../composables/useSearchIndex";
207
221
  import { createShapeDetection } from "../composables/useShapeDetection";
208
222
  import { provideFormFields } from "../composables/useFormFields";
209
- import { extractCheckboxStyles, extractButtonCaptions } from "../annotation/parsers/extractCheckboxStyles";
223
+ import { extractCheckboxStyles, extractButtonCaptions, extractSignedSignatureNames } from "../annotation/parsers/extractCheckboxStyles";
210
224
  import { isIPad } from "../annotation/engine/input-device";
211
225
  import { useInertiaPanzoom } from "../composables/useInertiaPanzoom";
212
226
  import { createScriptingManager } from "../composables/useScriptingManager";
213
227
  import { createScriptingBridge } from "../composables/useScriptingBridge";
228
+ import { provideKviewerI18n } from "../composables/useKviewerI18n";
229
+ import { KVIEWER_LOCALES } from "../i18n/messages";
214
230
  const props = defineProps({
215
231
  source: { type: null, required: true },
216
232
  textLayer: { type: Boolean, required: false },
@@ -227,12 +243,19 @@ const props = defineProps({
227
243
  roleColors: { type: Object, required: false, default: void 0 },
228
244
  activeRoleId: { type: [String, null], required: false, default: void 0 },
229
245
  scripting: { type: Boolean, required: false, default: false },
230
- menuItems: { type: Array, required: false, default: void 0 }
246
+ menuItems: { type: Array, required: false, default: void 0 },
247
+ tools: { type: Array, required: false, default: void 0 },
248
+ locale: { type: null, required: false, default: void 0 },
249
+ messages: { type: Object, required: false, default: void 0 }
231
250
  });
251
+ const slots = useSlots();
252
+ const toolSlotNames = computed(
253
+ () => Object.keys(slots).filter((name) => name.startsWith("tool-"))
254
+ );
232
255
  const emit = defineEmits(["update:formEditMode", "update:activeRoleId", "update:viewedPages", "all-pages-read"]);
233
256
  const viewerRoot = ref(null);
234
257
  const scrollContainer = ref(null);
235
- const { state: viewerState, setScrollToPageFn, setDownloadPdfFn } = provideViewerState();
258
+ const { state: viewerState, setScrollToPageFn, setDownloadPdfFn, setApplyScaleFn } = provideViewerState();
236
259
  watchEffect(() => {
237
260
  viewerState.readonly.value = props.readonly ?? false;
238
261
  });
@@ -255,6 +278,14 @@ watch(
255
278
  );
256
279
  const viewerSearch = provideViewerSearch();
257
280
  const formFieldsState = provideFormFields();
281
+ const kviewerPublicConfig = useRuntimeConfig().public.kviewer;
282
+ provideKviewerI18n(
283
+ computed(() => {
284
+ const candidate = props.locale ?? kviewerPublicConfig?.locale ?? "en";
285
+ return KVIEWER_LOCALES.includes(candidate) ? candidate : "en";
286
+ }),
287
+ computed(() => props.messages ?? {})
288
+ );
258
289
  watch(
259
290
  () => props.roleColors,
260
291
  (v) => {
@@ -295,6 +326,7 @@ const proxyCache = createPageProxyCache();
295
326
  const searchIndex = createSearchIndex();
296
327
  const shapeDetectionCache = createShapeDetection();
297
328
  const doc = shallowRef(null);
329
+ let destroyActiveDoc = null;
298
330
  let painter = null;
299
331
  let scriptingManager = null;
300
332
  let scriptingBridge = null;
@@ -353,6 +385,116 @@ const isRotatedSideways = computed(() => {
353
385
  });
354
386
  const zoomWrapper = ref(null);
355
387
  const inertiaPanzoom = useInertiaPanzoom({ minScale: 0.5, maxScale: 4 });
388
+ const MIN_ZOOM = 0.25;
389
+ const MAX_ZOOM = 4;
390
+ let suppressScaleReset = false;
391
+ async function commitZoom(target) {
392
+ const s0 = viewerState.scale.value;
393
+ target = Math.min(Math.max(MAX_ZOOM, s0), Math.max(Math.min(MIN_ZOOM, s0), target));
394
+ const viewportEl = zoomWrapper.value?.parentElement ?? null;
395
+ const anchor = virtualization.getPageElement(viewerState.currentPage.value);
396
+ const visual = inertiaPanzoom.getScale();
397
+ if (target === viewerState.scale.value && Math.abs(visual - 1) < 5e-3) return;
398
+ if (!viewportEl || !anchor || !anchor.isConnected) {
399
+ viewerState.fitMode.value = "custom";
400
+ viewerState.scale.value = target;
401
+ return;
402
+ }
403
+ const vpRect = viewportEl.getBoundingClientRect();
404
+ const fx = vpRect.width / 2;
405
+ const fy = vpRect.height / 2;
406
+ const aRect = anchor.getBoundingClientRect();
407
+ const qx = (vpRect.left + fx - aRect.left) / aRect.width;
408
+ const qy = (vpRect.top + fy - aRect.top) / aRect.height;
409
+ suppressScaleReset = target !== viewerState.scale.value;
410
+ viewerState.fitMode.value = "custom";
411
+ viewerState.scale.value = target;
412
+ await nextTick();
413
+ const t = inertiaPanzoom.getTransform();
414
+ const vp2 = viewportEl.getBoundingClientRect();
415
+ const a2 = anchor.getBoundingClientRect();
416
+ const layoutLeft = (a2.left - vp2.left - t.tx) / t.scale;
417
+ const layoutTop = (a2.top - vp2.top - t.ty) / t.scale;
418
+ const layoutW = a2.width / t.scale;
419
+ const layoutH = a2.height / t.scale;
420
+ inertiaPanzoom.setTransform({
421
+ scale: 1,
422
+ tx: fx - (layoutLeft + qx * layoutW),
423
+ ty: fy - (layoutTop + qy * layoutH)
424
+ });
425
+ viewWindows.value = {};
426
+ computeViewWindows();
427
+ }
428
+ setApplyScaleFn((value) => {
429
+ void commitZoom(value);
430
+ });
431
+ watch(
432
+ () => viewerState.scale.value,
433
+ (s) => inertiaPanzoom.setScaleLimits(
434
+ Math.min(1, MIN_ZOOM / s),
435
+ Math.max(1, MAX_ZOOM / s)
436
+ ),
437
+ { immediate: true }
438
+ );
439
+ inertiaPanzoom.onZoomEnd((visual) => {
440
+ if (Math.abs(visual - 1) < 5e-3) return;
441
+ void commitZoom(viewerState.scale.value * visual);
442
+ });
443
+ const viewWindows = shallowRef({});
444
+ const VIEW_WINDOW_MARGIN = 0.25;
445
+ function computeViewWindows() {
446
+ const viewportEl = zoomWrapper.value?.parentElement;
447
+ if (!viewportEl) return;
448
+ const vpRect = viewportEl.getBoundingClientRect();
449
+ if (vpRect.width === 0 || vpRect.height === 0) return;
450
+ const prev = viewWindows.value;
451
+ const next = {};
452
+ let changed = false;
453
+ for (const meta of pageMetas.value) {
454
+ const el = virtualization.getPageElement(meta.pageNumber);
455
+ if (!el) continue;
456
+ const r = el.getBoundingClientRect();
457
+ if (r.width === 0 || el.offsetWidth === 0) continue;
458
+ const k = r.width / el.offsetWidth;
459
+ const vx = (Math.max(vpRect.left, r.left) - r.left) / k;
460
+ const vy = (Math.max(vpRect.top, r.top) - r.top) / k;
461
+ const vr = (Math.min(vpRect.right, r.right) - r.left) / k;
462
+ const vb = (Math.min(vpRect.bottom, r.bottom) - r.top) / k;
463
+ if (vr <= vx || vb <= vy) continue;
464
+ const old = prev[meta.pageNumber];
465
+ if (old && vx >= old.x && vy >= old.y && vr <= old.x + old.width && vb <= old.y + old.height) {
466
+ next[meta.pageNumber] = old;
467
+ continue;
468
+ }
469
+ const mx = vpRect.width / k * VIEW_WINDOW_MARGIN;
470
+ const my = vpRect.height / k * VIEW_WINDOW_MARGIN;
471
+ next[meta.pageNumber] = {
472
+ x: vx - mx,
473
+ y: vy - my,
474
+ width: vr - vx + 2 * mx,
475
+ height: vb - vy + 2 * my
476
+ };
477
+ changed = true;
478
+ }
479
+ if (changed || Object.keys(next).length !== Object.keys(prev).length) {
480
+ viewWindows.value = next;
481
+ }
482
+ }
483
+ let viewWindowTimer = 0;
484
+ let lastViewWindowUpdate = 0;
485
+ inertiaPanzoom.onChange(() => {
486
+ const now = performance.now();
487
+ const wait = Math.max(0, 250 - (now - lastViewWindowUpdate));
488
+ clearTimeout(viewWindowTimer);
489
+ viewWindowTimer = window.setTimeout(() => {
490
+ lastViewWindowUpdate = performance.now();
491
+ computeViewWindows();
492
+ }, wait);
493
+ });
494
+ function pageViewWindow(pageNumber) {
495
+ if (pageSettings.rotation.value !== 0) return null;
496
+ return viewWindows.value[pageNumber] ?? null;
497
+ }
356
498
  function pageSlotStyle(meta) {
357
499
  const s = pageScale.value;
358
500
  if (isRotatedSideways.value) {
@@ -449,26 +591,36 @@ async function loadDocument() {
449
591
  }
450
592
  const source = props.source;
451
593
  const baseParams = source instanceof Uint8Array || typeof source === "string" ? { url: typeof source === "string" ? source : void 0, data: source instanceof Uint8Array ? source : void 0 } : { ...source };
594
+ if (baseParams.data instanceof Uint8Array) {
595
+ baseParams.data = baseParams.data.slice();
596
+ } else if (baseParams.data instanceof ArrayBuffer) {
597
+ baseParams.data = baseParams.data.slice(0);
598
+ }
452
599
  const docParams = {
453
600
  ...baseParams,
454
601
  standardFontDataUrl: "/_kviewer/standard_fonts/",
455
602
  cMapUrl: "/_kviewer/cmaps/",
456
603
  cMapPacked: true,
457
- useSystemFonts: true
604
+ useSystemFonts: true,
605
+ wasmUrl: "/_kviewer/wasm/",
606
+ iccUrl: "/_kviewer/iccs/"
458
607
  };
459
608
  const loadingTask = pdfjs.getDocument(docParams);
609
+ destroyActiveDoc = () => loadingTask.destroy();
460
610
  const pdfDoc = await loadingTask.promise;
461
611
  doc.value = pdfDoc;
462
612
  viewerState.doc.value = pdfDoc;
463
613
  viewerState.totalPages.value = pdfDoc.numPages;
464
614
  proxyCache.setDocument(pdfDoc);
465
615
  pdfDoc.getData().then(async (bytes) => {
466
- const [styles, captions] = await Promise.all([
616
+ const [styles, captions, signedSigs] = await Promise.all([
467
617
  extractCheckboxStyles(bytes),
468
- extractButtonCaptions(bytes)
618
+ extractButtonCaptions(bytes),
619
+ extractSignedSignatureNames(bytes)
469
620
  ]);
470
621
  if (styles.size > 0) formFieldsState.applyCheckboxStyles(styles);
471
622
  if (captions.size > 0) formFieldsState.applyButtonCaptions(captions);
623
+ if (signedSigs.size > 0) formFieldsState.applySignedSignatures(signedSigs);
472
624
  }).catch((err) => console.warn("[KViewer] Failed to read MK data from source PDF:", err));
473
625
  if (!scrollContainer.value) {
474
626
  await nextTick();
@@ -628,7 +780,7 @@ async function importAnnotations(annotations, options) {
628
780
  const mode = options?.mode ?? "replace";
629
781
  const normalized = normalizeImportedAnnotations(
630
782
  annotations,
631
- props.userName ?? "User"
783
+ props.userName
632
784
  );
633
785
  if (!painter) {
634
786
  return { loaded: 0, skipped: normalized.skipped + normalized.annotations.length };
@@ -819,10 +971,18 @@ onMounted(() => {
819
971
  { immediate: true }
820
972
  );
821
973
  watch(() => viewerState.scale.value, async () => {
974
+ if (suppressScaleReset) {
975
+ suppressScaleReset = false;
976
+ return;
977
+ }
822
978
  await nextTick();
823
979
  inertiaPanzoom.reset();
980
+ viewWindows.value = {};
981
+ computeViewWindows();
982
+ });
983
+ watch(() => viewerState.currentPage.value, () => {
984
+ if (isSinglePageMode.value) inertiaPanzoom.reset();
824
985
  });
825
- watch(() => viewerState.currentPage.value, () => inertiaPanzoom.reset());
826
986
  }
827
987
  if (isIPad()) {
828
988
  viewerState.setStylusMode(true);
@@ -843,7 +1003,7 @@ watch(
843
1003
  searchIndex.destroy();
844
1004
  virtualization.destroy();
845
1005
  proxyCache.clear();
846
- doc.value?.destroy();
1006
+ void destroyActiveDoc?.();
847
1007
  loadDocument();
848
1008
  }
849
1009
  );
@@ -875,6 +1035,7 @@ watch(
875
1035
  );
876
1036
  onBeforeUnmount(() => {
877
1037
  window.removeEventListener("keydown", onGlobalKeydown);
1038
+ clearTimeout(viewWindowTimer);
878
1039
  scrollContainer.value?.removeEventListener("wheel", onWheel);
879
1040
  inertiaPanzoom.detach();
880
1041
  resizeObserver?.disconnect();
@@ -889,7 +1050,8 @@ onBeforeUnmount(() => {
889
1050
  virtualization.destroy();
890
1051
  proxyCache.clear();
891
1052
  painter?.destroy();
892
- doc.value?.destroy();
1053
+ void destroyActiveDoc?.();
1054
+ destroyActiveDoc = null;
893
1055
  });
894
1056
  defineExpose({
895
1057
  getAnnotations: () => painter?.getData() ?? [],
@@ -2,6 +2,9 @@ import { type ViewMode } from '../composables/useViewerState.js';
2
2
  import type { AddFormFieldPayload, FormFieldDefinition, IAnnotationStore, StampDefinition, SignatureHandlers } from '../annotation/engine/types.js';
3
3
  import { type ExportPdfOptions } from '../annotation/pdf-export/export.js';
4
4
  import type { ViewerMenuItem } from '../menu-items.js';
5
+ import type { ViewerToolEntry } from '../toolbar-tools.js';
6
+ import { type KviewerMessageOverrides } from '../composables/useKviewerI18n.js';
7
+ import { type KviewerLocale } from '../i18n/messages.js';
5
8
  type __VLS_Props = {
6
9
  source: string | Uint8Array | object;
7
10
  textLayer?: boolean;
@@ -45,6 +48,19 @@ type __VLS_Props = {
45
48
  * toggles or actions without replacing the entire header. Ignored
46
49
  * when the host supplies a custom `#header` slot. */
47
50
  menuItems?: ViewerMenuItem[];
51
+ /** Configures which toolbar tools render and in what order. Entries are
52
+ * built-in tool IDs (strings) or `{ type: 'slot', ... }` objects that
53
+ * inject a custom button via a `#tool-<name>` slot. When omitted, the
54
+ * default toolbar is rendered unchanged. Ignored when the host supplies
55
+ * a custom `#header` slot. See {@link ViewerToolEntry}. */
56
+ tools?: ViewerToolEntry[];
57
+ /** UI language for built-in viewer strings (toolbar, search, modals,
58
+ * form sidebar). Defaults to the `kviewer.locale` module option, then
59
+ * 'en'. Hosts with their own i18n just bind their active locale. */
60
+ locale?: KviewerLocale;
61
+ /** Per-string overrides for built-in messages — wins over the locale
62
+ * catalog. Useful for app-specific wording without forking locales. */
63
+ messages?: KviewerMessageOverrides;
48
64
  };
49
65
  declare function exportPdf(options?: ExportPdfOptions): Promise<Uint8Array>;
50
66
  type ImportMode = 'replace' | 'merge';
@@ -55,11 +71,15 @@ declare function importAnnotations(annotations: IAnnotationStore[], options?: {
55
71
  skipped: number;
56
72
  }>;
57
73
  declare function getKonvaCanvasState(): Record<number, string>;
58
- declare var __VLS_1: {}, __VLS_51: {};
74
+ declare var __VLS_1: {}, __VLS_11: string, __VLS_12: {
75
+ state: import("../composables/useViewerState.js").ViewerState;
76
+ }, __VLS_62: {};
59
77
  type __VLS_Slots = {} & {
78
+ [K in NonNullable<typeof __VLS_11>]?: (props: typeof __VLS_12) => any;
79
+ } & {
60
80
  header?: (props: typeof __VLS_1) => any;
61
81
  } & {
62
- footer?: (props: typeof __VLS_51) => any;
82
+ footer?: (props: typeof __VLS_62) => any;
63
83
  };
64
84
  declare const __VLS_base: import("vue").DefineComponent<__VLS_Props, {
65
85
  getAnnotations: () => IAnnotationStore[];
@@ -109,6 +129,7 @@ declare const __VLS_base: import("vue").DefineComponent<__VLS_Props, {
109
129
  readonly: boolean;
110
130
  active: boolean;
111
131
  menuItems: ViewerMenuItem[];
132
+ tools: ViewerToolEntry[];
112
133
  scripting: boolean;
113
134
  stamps: StampDefinition[];
114
135
  signatureHandlers: SignatureHandlers;
@@ -117,6 +138,8 @@ declare const __VLS_base: import("vue").DefineComponent<__VLS_Props, {
117
138
  formEditMode: boolean;
118
139
  roleColors: Record<string, string>;
119
140
  activeRoleId: string | null;
141
+ locale: KviewerLocale;
142
+ messages: KviewerMessageOverrides;
120
143
  }, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
121
144
  declare const __VLS_export: __VLS_WithSlots<typeof __VLS_base, __VLS_Slots>;
122
145
  declare const _default: typeof __VLS_export;
@@ -1,9 +1,29 @@
1
1
  import type { ViewerMenuItem } from '../menu-items.js';
2
+ import { type ViewerToolEntry } from '../toolbar-tools.js';
2
3
  type __VLS_Props = {
3
4
  /** Extra items appended to the burger menu after the built-ins.
4
5
  * See {@link ViewerMenuItem}. Pure forwarding from `<KViewer>`. */
5
6
  menuItems?: ViewerMenuItem[];
7
+ /** Configures which toolbar tools render and in what order. When
8
+ * omitted, the default toolbar is rendered. See {@link ViewerToolEntry}. */
9
+ tools?: ViewerToolEntry[];
6
10
  };
7
- 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>;
11
+ declare var __VLS_109: string, __VLS_110: {
12
+ state: import("../composables/useViewerState.js").ViewerState;
13
+ }, __VLS_154: string, __VLS_155: {
14
+ state: import("../composables/useViewerState.js").ViewerState;
15
+ };
16
+ type __VLS_Slots = {} & {
17
+ [K in NonNullable<typeof __VLS_109>]?: (props: typeof __VLS_110) => any;
18
+ } & {
19
+ [K in NonNullable<typeof __VLS_154>]?: (props: typeof __VLS_155) => any;
20
+ };
21
+ declare const __VLS_base: 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>;
22
+ declare const __VLS_export: __VLS_WithSlots<typeof __VLS_base, __VLS_Slots>;
8
23
  declare const _default: typeof __VLS_export;
9
24
  export default _default;
25
+ type __VLS_WithSlots<T, S> = T & {
26
+ new (): {
27
+ $slots: S;
28
+ };
29
+ };