kviewer 0.3.0 → 0.3.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (45) hide show
  1. package/dist/module.d.mts +1 -1
  2. package/dist/module.json +1 -1
  3. package/dist/module.mjs +13 -0
  4. package/dist/runtime/annotation/engine/config.d.ts +1 -0
  5. package/dist/runtime/annotation/engine/config.js +4 -0
  6. package/dist/runtime/annotation/engine/cursor-preview.d.ts +14 -1
  7. package/dist/runtime/annotation/engine/cursor-preview.js +32 -6
  8. package/dist/runtime/annotation/engine/editor/editor.js +10 -12
  9. package/dist/runtime/annotation/engine/editor/selector.js +3 -1
  10. package/dist/runtime/annotation/engine/tools/arrow.js +24 -14
  11. package/dist/runtime/annotation/engine/tools/circle.js +24 -13
  12. package/dist/runtime/annotation/engine/tools/cloud.js +27 -18
  13. package/dist/runtime/annotation/engine/tools/free-highlight.js +24 -15
  14. package/dist/runtime/annotation/engine/tools/freehand.js +21 -12
  15. package/dist/runtime/annotation/engine/tools/highlight.d.ts +2 -0
  16. package/dist/runtime/annotation/engine/tools/highlight.js +20 -10
  17. package/dist/runtime/annotation/engine/tools/note.js +3 -1
  18. package/dist/runtime/annotation/engine/tools/rectangle.js +19 -8
  19. package/dist/runtime/annotation/engine/tools/signature.js +31 -5
  20. package/dist/runtime/annotation/engine/tools/stamp.d.ts +1 -0
  21. package/dist/runtime/annotation/engine/tools/stamp.js +43 -15
  22. package/dist/runtime/annotation/engine/utils.d.ts +1 -4
  23. package/dist/runtime/annotation/engine/utils.js +3 -11
  24. package/dist/runtime/annotation/pdf-export/download.js +1 -1
  25. package/dist/runtime/annotation/pdf-export/export.js +1 -1
  26. package/dist/runtime/annotation/pdf-export/parse_polyline.js +5 -1
  27. package/dist/runtime/annotation/pdf-export/parse_stamp.js +4 -4
  28. package/dist/runtime/annotation/pdf-export/unicode-font.js +1 -4
  29. package/dist/runtime/annotation/pdf-import/decode_stamp.js +3 -2
  30. package/dist/runtime/annotation/pdf-import/extract_stamp_appearance.js +1 -1
  31. package/dist/runtime/annotation/pdf-import/utils.js +1 -1
  32. package/dist/runtime/annotation/utils/placement_size.d.ts +27 -0
  33. package/dist/runtime/annotation/utils/placement_size.js +26 -0
  34. package/dist/runtime/components/FormFieldLayer.d.vue.ts +1 -1
  35. package/dist/runtime/components/FormFieldLayer.vue.d.ts +1 -1
  36. package/dist/runtime/components/PdfPage.vue +3 -3
  37. package/dist/runtime/components/Viewer.vue +3 -1
  38. package/dist/runtime/components/ViewerTabs.vue +1 -1
  39. package/dist/runtime/components/panels/SignaturePicker.vue +8 -2
  40. package/dist/runtime/composables/useSearchIndex.js +1 -1
  41. package/dist/runtime/embed/bridge-host.d.ts +1 -1
  42. package/dist/runtime/embed/bridge-host.js +3 -3
  43. package/dist/runtime/imports.d.ts +19 -0
  44. package/dist/types.d.mts +1 -1
  45. 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
  }
@@ -1,12 +1,17 @@
1
1
  import Konva from "konva";
2
2
  import { AnnotationType } from "../types.js";
3
- import { resizeImage } from "../utils.js";
4
3
  import { Editor } from "../editor/editor.js";
5
4
  import { defaultOptions } from "../config.js";
6
- import { showCursorPreview } from "../cursor-preview.js";
5
+ import {
6
+ cursorPreviewToken,
7
+ measureImage,
8
+ peekImageSize,
9
+ showCursorPreview
10
+ } from "../cursor-preview.js";
11
+ import { cursorPreviewSize, placementSize } from "../../utils/placement_size.js";
7
12
  export class EditorSignature extends Editor {
8
13
  signatureUrl;
9
- signatureImage;
14
+ signatureImage = null;
10
15
  constructor(editorOptions, defaultSignatureUrl) {
11
16
  super({ ...editorOptions, editorType: AnnotationType.SIGNATURE });
12
17
  this.signatureUrl = defaultSignatureUrl;
@@ -15,7 +20,23 @@ export class EditorSignature extends Editor {
15
20
  }
16
21
  }
17
22
  createCursorImg() {
18
- showCursorPreview(this.signatureUrl, defaultOptions.setting.MAX_CURSOR_SIZE);
23
+ const scale = this.konvaStage?.scale()?.x ?? 1;
24
+ const max = defaultOptions.setting.MAX_PLACEMENT_SIZE;
25
+ const cached = cursorPreviewSize(null, peekImageSize(this.signatureUrl), max, scale);
26
+ if (cached) {
27
+ showCursorPreview(this.signatureUrl, cached.width, cached.height);
28
+ return;
29
+ }
30
+ const token = cursorPreviewToken();
31
+ void measureImage(this.signatureUrl).then((natural) => {
32
+ if (cursorPreviewToken() !== token) return;
33
+ const size = cursorPreviewSize(null, natural, max, scale);
34
+ if (size) {
35
+ showCursorPreview(this.signatureUrl, size.width, size.height);
36
+ } else {
37
+ showCursorPreview(this.signatureUrl, defaultOptions.setting.MAX_CURSOR_SIZE);
38
+ }
39
+ });
19
40
  }
20
41
  mouseDownHandler(e) {
21
42
  if (e.currentTarget !== this.konvaStage) return;
@@ -25,10 +46,15 @@ export class EditorSignature extends Editor {
25
46
  const pos = this.konvaStage.getRelativePointerPosition();
26
47
  Konva.Image.fromURL(this.signatureUrl, async (image) => {
27
48
  const { width: width_rec, height: height_rec } = image.getClientRect();
28
- const { newWidth, newHeight } = resizeImage(width_rec, height_rec, 120);
49
+ const { width: newWidth, height: newHeight } = placementSize(
50
+ null,
51
+ { width: width_rec, height: height_rec },
52
+ defaultOptions.setting.MAX_PLACEMENT_SIZE
53
+ ) ?? { width: width_rec, height: height_rec };
29
54
  const crosshair = { x: newWidth / 2, y: newHeight / 2 };
30
55
  this.signatureImage = image;
31
56
  this.signatureImage.setAttrs({
57
+ image: image.image(),
32
58
  x: pos.x - crosshair.x,
33
59
  y: pos.y - crosshair.y,
34
60
  width: newWidth,
@@ -8,6 +8,7 @@ export declare class EditorStamp extends Editor {
8
8
  private stampWidth;
9
9
  private stampHeight;
10
10
  constructor(editorOptions: IEditorOptions, defaultStampUrl: string | null);
11
+ private get explicitSize();
11
12
  private createCursorImg;
12
13
  /**
13
14
  * True when an identical stamp (same source image) already sits under the
@@ -1,12 +1,20 @@
1
1
  import Konva from "konva";
2
2
  import { AnnotationType } from "../types.js";
3
- import { resizeImage } from "../utils.js";
4
3
  import { Editor } from "../editor/editor.js";
5
4
  import { defaultOptions } from "../config.js";
6
- import { showCursorPreview } from "../cursor-preview.js";
5
+ import {
6
+ cursorPreviewToken,
7
+ measureImage,
8
+ peekImageSize,
9
+ showCursorPreview
10
+ } from "../cursor-preview.js";
11
+ import {
12
+ cursorPreviewSize,
13
+ placementSize
14
+ } from "../../utils/placement_size.js";
7
15
  export class EditorStamp extends Editor {
8
16
  stampUrl;
9
- stampImage;
17
+ stampImage = null;
10
18
  stampWidth = null;
11
19
  stampHeight = null;
12
20
  constructor(editorOptions, defaultStampUrl) {
@@ -16,10 +24,32 @@ export class EditorStamp extends Editor {
16
24
  this.createCursorImg();
17
25
  }
18
26
  }
27
+ get explicitSize() {
28
+ return { width: this.stampWidth ?? 0, height: this.stampHeight ?? 0 };
29
+ }
19
30
  createCursorImg() {
20
- const baseSize = this.stampWidth ?? this.stampHeight ?? defaultOptions.setting.MAX_CURSOR_SIZE;
21
31
  const scale = this.konvaStage?.scale()?.x ?? 1;
22
- showCursorPreview(this.stampUrl, Math.round(baseSize * scale));
32
+ const max = defaultOptions.setting.MAX_PLACEMENT_SIZE;
33
+ const immediate = cursorPreviewSize(
34
+ this.explicitSize,
35
+ peekImageSize(this.stampUrl),
36
+ max,
37
+ scale
38
+ );
39
+ if (immediate) {
40
+ showCursorPreview(this.stampUrl, immediate.width, immediate.height);
41
+ return;
42
+ }
43
+ const token = cursorPreviewToken();
44
+ void measureImage(this.stampUrl).then((natural) => {
45
+ if (cursorPreviewToken() !== token) return;
46
+ const size = cursorPreviewSize(this.explicitSize, natural, max, scale);
47
+ if (size) {
48
+ showCursorPreview(this.stampUrl, size.width, size.height);
49
+ } else {
50
+ showCursorPreview(this.stampUrl, defaultOptions.setting.MAX_CURSOR_SIZE);
51
+ }
52
+ });
23
53
  }
24
54
  /**
25
55
  * True when an identical stamp (same source image) already sits under the
@@ -47,24 +77,22 @@ export class EditorStamp extends Editor {
47
77
  if (e.currentTarget !== this.konvaStage) return;
48
78
  const pos = this.konvaStage.getRelativePointerPosition();
49
79
  if (!pos) return;
50
- const estimatedSize = this.stampWidth ?? this.stampHeight ?? 120;
80
+ const estimatedSize = this.stampWidth ?? this.stampHeight ?? defaultOptions.setting.MAX_PLACEMENT_SIZE;
51
81
  if (this.hasIdenticalStampNear(pos, estimatedSize)) return;
52
82
  this.stampImage = null;
53
83
  this.currentShapeGroup = this.createShapeGroup();
54
84
  this.getBgLayer().add(this.currentShapeGroup.konvaGroup);
55
85
  Konva.Image.fromURL(this.stampUrl, async (image) => {
56
- let newWidth;
57
- let newHeight;
58
- if (this.stampWidth && this.stampHeight) {
59
- newWidth = this.stampWidth;
60
- newHeight = this.stampHeight;
61
- } else {
62
- const { width: width_rec, height: height_rec } = image.getClientRect();
63
- ({ newWidth, newHeight } = resizeImage(width_rec, height_rec, 120));
64
- }
86
+ const { width: width_rec, height: height_rec } = image.getClientRect();
87
+ const { width: newWidth, height: newHeight } = placementSize(
88
+ this.explicitSize,
89
+ { width: width_rec, height: height_rec },
90
+ defaultOptions.setting.MAX_PLACEMENT_SIZE
91
+ ) ?? { width: width_rec, height: height_rec };
65
92
  const crosshair = { x: newWidth / 2, y: newHeight / 2 };
66
93
  this.stampImage = image;
67
94
  this.stampImage.setAttrs({
95
+ image: image.image(),
68
96
  x: pos.x - crosshair.x,
69
97
  y: pos.y - crosshair.y,
70
98
  width: newWidth,
@@ -7,10 +7,7 @@ export declare function generateUUID(): string;
7
7
  export declare function setCssCustomProperty(propertyName: string, value: string): void;
8
8
  export declare function removeCssCustomProperty(propertyName: string): void;
9
9
  export declare function base64ToImageBitmap(base64: string): Promise<ImageBitmap>;
10
- export declare function resizeImage(width: number, height: number, max: number): {
11
- newWidth: number;
12
- newHeight: number;
13
- };
10
+ export { resizeImage } from '../utils/placement_size.js';
14
11
  export declare function convertToRGB(array: number[], index?: number): string;
15
12
  export declare function formatTimestamp(timestamp: number): string;
16
13
  export declare function formatPDFDate(dateString: string | null, full?: boolean): string;
@@ -58,6 +58,7 @@ export function removeCssCustomProperty(propertyName) {
58
58
  }
59
59
  export async function base64ToImageBitmap(base64) {
60
60
  const base64Data = base64.split(",")[1];
61
+ if (!base64Data) throw new Error("Invalid base64 image data");
61
62
  const binaryString = atob(base64Data);
62
63
  const length = binaryString.length;
63
64
  const bytes = new Uint8Array(length);
@@ -67,16 +68,7 @@ export async function base64ToImageBitmap(base64) {
67
68
  const blob = new Blob([bytes], { type: "image/png" });
68
69
  return await createImageBitmap(blob);
69
70
  }
70
- export function resizeImage(width, height, max) {
71
- if (width <= max && height <= max) {
72
- return { newWidth: width, newHeight: height };
73
- }
74
- const scaleFactor = Math.min(max / width, max / height);
75
- return {
76
- newWidth: width * scaleFactor,
77
- newHeight: height * scaleFactor
78
- };
79
- }
71
+ export { resizeImage } from "../utils/placement_size.js";
80
72
  export function convertToRGB(array, index = 0) {
81
73
  if (index < 0 || index * 3 + 2 >= array.length) {
82
74
  throw new Error("Index out of bounds");
@@ -143,7 +135,7 @@ export function getPDFDateTimestamp(dateString) {
143
135
  let tzOffset = 0;
144
136
  if (tzMatch) {
145
137
  const sign = tzMatch[1] === "+" ? 1 : -1;
146
- const hours = Number.parseInt(tzMatch[2], 10) || 0;
138
+ const hours = Number.parseInt(tzMatch[2] ?? "0", 10) || 0;
147
139
  const minutes = Number.parseInt(tzMatch[3] || "0", 10) || 0;
148
140
  tzOffset = sign * (hours * 60 + minutes);
149
141
  }
@@ -1,5 +1,5 @@
1
1
  export function downloadPdfBytes(data, fileName) {
2
- const arrayBuffer = 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);
@@ -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 || []) {
@@ -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)
@@ -0,0 +1,27 @@
1
+ /**
2
+ * Sizing rules shared by stamp/signature placement and the drag cursor
3
+ * preview. They live apart from the engine so both paths derive their size
4
+ * from one implementation — the ghost drifting from what actually lands on
5
+ * the page was a real bug, not a hypothetical one.
6
+ */
7
+ export interface ImageSize {
8
+ width: number;
9
+ height: number;
10
+ }
11
+ /** Aspect-preserving downscale. Never upscales. */
12
+ export declare function resizeImage(width: number, height: number, max: number): {
13
+ newWidth: number;
14
+ newHeight: number;
15
+ };
16
+ /**
17
+ * The size an annotation will occupy in stage-content units once placed.
18
+ * Explicit dimensions (a stamp carrying width/height metadata) win outright;
19
+ * otherwise the image's own aspect ratio is bounded by `max`.
20
+ */
21
+ export declare function placementSize(explicit: Partial<ImageSize> | null, natural: ImageSize | null, max: number): ImageSize | null;
22
+ /**
23
+ * The same size expressed in CSS pixels, for the cursor ghost. The Konva stage
24
+ * scale is what converts content units to what the user actually sees, so a
25
+ * ghost that ignores it is wrong at every zoom level but 100%.
26
+ */
27
+ export declare function cursorPreviewSize(explicit: Partial<ImageSize> | null, natural: ImageSize | null, max: number, scale: number): ImageSize | null;
@@ -0,0 +1,26 @@
1
+ export function resizeImage(width, height, max) {
2
+ if (width <= max && height <= max) {
3
+ return { newWidth: width, newHeight: height };
4
+ }
5
+ const scaleFactor = Math.min(max / width, max / height);
6
+ return {
7
+ newWidth: width * scaleFactor,
8
+ newHeight: height * scaleFactor
9
+ };
10
+ }
11
+ export function placementSize(explicit, natural, max) {
12
+ if (explicit?.width && explicit?.height) {
13
+ return { width: explicit.width, height: explicit.height };
14
+ }
15
+ if (!natural?.width || !natural?.height) return null;
16
+ const { newWidth, newHeight } = resizeImage(natural.width, natural.height, max);
17
+ return { width: newWidth, height: newHeight };
18
+ }
19
+ export function cursorPreviewSize(explicit, natural, max, scale) {
20
+ const size = placementSize(explicit, natural, max);
21
+ if (!size) return null;
22
+ return {
23
+ width: Math.round(size.width * scale),
24
+ height: Math.round(size.height * scale)
25
+ };
26
+ }
@@ -5,7 +5,7 @@ type __VLS_Props = {
5
5
  /** Carries displayed width/height plus the page's `/Rotate` and MediaBox
6
6
  * origin — the single source of truth for mapping `/Rect` to canvas. */
7
7
  pageTransform: PageTransform;
8
- pointerEvents: 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
@@ -1022,7 +1022,9 @@ onMounted(() => {
1022
1022
  if (isIPad()) {
1023
1023
  viewerState.setStylusMode(true);
1024
1024
  }
1025
- setDownloadPdfFn(() => exportPdf({ download: true }));
1025
+ setDownloadPdfFn(async () => {
1026
+ await exportPdf({ download: true });
1027
+ });
1026
1028
  loadDocument();
1027
1029
  window.addEventListener("keydown", onGlobalKeydown);
1028
1030
  });
@@ -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"
@@ -1,5 +1,5 @@
1
1
  <template>
2
- <UPopover>
2
+ <UPopover v-model:open="popoverOpen">
3
3
  <button
4
4
  class="relative flex items-center justify-center w-8 h-8 rounded-md transition-colors cursor-pointer"
5
5
  :class="isActive ? 'bg-default text-highlighted' : 'text-muted hover:bg-default/50 hover:text-highlighted'"
@@ -43,7 +43,7 @@
43
43
  color="neutral"
44
44
  size="xs"
45
45
  block
46
- @click="drawModalOpen = true"
46
+ @click="openDrawModal"
47
47
  >
48
48
  {{ t("addSignature") }}
49
49
  </UButton>
@@ -69,10 +69,16 @@ import SignatureDrawModal from "../modals/SignatureDrawModal.vue";
69
69
  const state = useViewerState();
70
70
  const { t } = useKviewerI18n();
71
71
  const drawModalOpen = ref(false);
72
+ const popoverOpen = ref(false);
72
73
  const isActive = computed(() => state.activeTool.value === AnnotationType.SIGNATURE);
73
74
  function selectSignature(sig) {
74
75
  state.activeSignature.value = sig;
75
76
  state.selectTool(AnnotationType.SIGNATURE, sig.imageUrl);
77
+ popoverOpen.value = false;
78
+ }
79
+ function openDrawModal() {
80
+ popoverOpen.value = false;
81
+ drawModalOpen.value = true;
76
82
  }
77
83
  async function onDrawSubmit(imageUrl) {
78
84
  drawModalOpen.value = false;
@@ -15,7 +15,7 @@ export function createSearchIndex() {
15
15
  includeMarkedContent: false,
16
16
  disableNormalization: false
17
17
  });
18
- return content.items.filter((item) => "str" in item).map((item) => item.str).join("");
18
+ return content.items.map((item) => "str" in item ? item.str : "").join("");
19
19
  }
20
20
  async function buildIndex(doc) {
21
21
  destroyed = false;