kviewer 0.0.5 → 0.0.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/module.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "kviewer",
3
3
  "configKey": "kviewer",
4
- "version": "0.0.5",
4
+ "version": "0.0.7",
5
5
  "builder": {
6
6
  "@nuxt/module-builder": "1.0.2",
7
7
  "unbuild": "3.6.1"
@@ -50,7 +50,7 @@ export class Editor {
50
50
  id,
51
51
  pageNumber,
52
52
  konvaString: konvaGroup.toJSON(),
53
- konvaClientRect: konvaGroup.getClientRect(),
53
+ konvaClientRect: Konva.Node.create(konvaGroup.toJSON()).getClientRect(),
54
54
  title: this.userName,
55
55
  type: annotation.type,
56
56
  pdfjsType: annotation.pdfjsAnnotationType,
@@ -1,16 +1,19 @@
1
1
  import { PDFName, PDFString } from "pdf-lib";
2
2
  import { convertKonvaRectToPdfRect, rgbToPdfColor, stringToPDFHexString } from "../engine/utils.js";
3
3
  import { AnnotationParser } from "./parse.js";
4
+ import { computeBoundsFromKonvaString } from "./parse_utils.js";
4
5
  const UNKNOWN_USER = "Unknown user";
5
6
  export class CircleParser extends AnnotationParser {
6
7
  async parse() {
7
8
  const { annotation, page, pdfDoc } = this;
8
9
  const context = pdfDoc.context;
9
10
  const pageHeight = page.getHeight();
11
+ const bounds = computeBoundsFromKonvaString(annotation.konvaString);
12
+ const rect = convertKonvaRectToPdfRect(bounds ?? annotation.konvaClientRect, pageHeight);
10
13
  const mainAnn = context.obj({
11
14
  Type: PDFName.of("Annot"),
12
15
  Subtype: PDFName.of("Circle"),
13
- Rect: convertKonvaRectToPdfRect(annotation.konvaClientRect, pageHeight),
16
+ Rect: rect,
14
17
  C: rgbToPdfColor(annotation.color || void 0),
15
18
  T: stringToPDFHexString(annotation.title || UNKNOWN_USER),
16
19
  Contents: stringToPDFHexString(annotation.contentsObj?.text || ""),
@@ -24,7 +27,7 @@ export class CircleParser extends AnnotationParser {
24
27
  const replyAnn = context.obj({
25
28
  Type: PDFName.of("Annot"),
26
29
  Subtype: PDFName.of("Text"),
27
- Rect: convertKonvaRectToPdfRect(annotation.konvaClientRect, pageHeight),
30
+ Rect: rect,
28
31
  Contents: stringToPDFHexString(comment.content),
29
32
  T: stringToPDFHexString(comment.title || UNKNOWN_USER),
30
33
  M: PDFString.of(comment.date || ""),
@@ -1,6 +1,7 @@
1
1
  import { PDFName, PDFNumber, PDFString } from "pdf-lib";
2
2
  import { convertKonvaRectToPdfRect, rgbToPdfColor, stringToPDFHexString } from "../engine/utils.js";
3
3
  import { AnnotationParser } from "./parse.js";
4
+ import { computeBoundsFromKonvaString } from "./parse_utils.js";
4
5
  const UNKNOWN_USER = "Unknown user";
5
6
  function parseSvgPathToPoints(data) {
6
7
  const commands = data.match(/[a-z][^a-z]*/gi) || [];
@@ -59,7 +60,9 @@ export class PolylineParser extends AnnotationParser {
59
60
  const mainAnn = context.obj({
60
61
  Type: PDFName.of("Annot"),
61
62
  Subtype: PDFName.of("Ink"),
62
- Rect: convertKonvaRectToPdfRect(annotation.konvaClientRect, pageHeight),
63
+ // Compute Rect from konvaString so it reflects the current position
64
+ // even when konvaClientRect is stale after a move/resize.
65
+ Rect: convertKonvaRectToPdfRect(computeBoundsFromKonvaString(annotation.konvaString) ?? annotation.konvaClientRect, pageHeight),
63
66
  InkList: inkList,
64
67
  C: context.obj([PDFNumber.of(r), PDFNumber.of(g), PDFNumber.of(b)]),
65
68
  T: stringToPDFHexString(annotation.title || UNKNOWN_USER),
@@ -76,7 +79,7 @@ export class PolylineParser extends AnnotationParser {
76
79
  const replyAnn = context.obj({
77
80
  Type: PDFName.of("Annot"),
78
81
  Subtype: PDFName.of("Text"),
79
- Rect: convertKonvaRectToPdfRect(annotation.konvaClientRect, pageHeight),
82
+ Rect: convertKonvaRectToPdfRect(computeBoundsFromKonvaString(annotation.konvaString) ?? annotation.konvaClientRect, pageHeight),
80
83
  Contents: stringToPDFHexString(comment.content),
81
84
  T: stringToPDFHexString(comment.title || UNKNOWN_USER),
82
85
  M: PDFString.of(comment.date || ""),
@@ -1,16 +1,19 @@
1
1
  import { PDFName, PDFString } from "pdf-lib";
2
2
  import { convertKonvaRectToPdfRect, rgbToPdfColor, stringToPDFHexString } from "../engine/utils.js";
3
3
  import { AnnotationParser } from "./parse.js";
4
+ import { computeBoundsFromKonvaString } from "./parse_utils.js";
4
5
  const UNKNOWN_USER = "Unknown user";
5
6
  export class SquareParser extends AnnotationParser {
6
7
  async parse() {
7
8
  const { annotation, page, pdfDoc } = this;
8
9
  const context = pdfDoc.context;
9
10
  const pageHeight = page.getHeight();
11
+ const bounds = computeBoundsFromKonvaString(annotation.konvaString);
12
+ const rect = convertKonvaRectToPdfRect(bounds ?? annotation.konvaClientRect, pageHeight);
10
13
  const mainAnn = context.obj({
11
14
  Type: PDFName.of("Annot"),
12
15
  Subtype: PDFName.of("Square"),
13
- Rect: convertKonvaRectToPdfRect(annotation.konvaClientRect, pageHeight),
16
+ Rect: rect,
14
17
  C: rgbToPdfColor(annotation.color || void 0),
15
18
  T: stringToPDFHexString(annotation.title || UNKNOWN_USER),
16
19
  Contents: stringToPDFHexString(annotation.contentsObj?.text || ""),
@@ -24,7 +27,7 @@ export class SquareParser extends AnnotationParser {
24
27
  const replyAnn = context.obj({
25
28
  Type: PDFName.of("Annot"),
26
29
  Subtype: PDFName.of("Text"),
27
- Rect: convertKonvaRectToPdfRect(annotation.konvaClientRect, pageHeight),
30
+ Rect: rect,
28
31
  Contents: stringToPDFHexString(comment.content),
29
32
  T: stringToPDFHexString(comment.title || UNKNOWN_USER),
30
33
  M: PDFString.of(comment.date || ""),
@@ -1,6 +1,7 @@
1
1
  import { PDFName, PDFNumber, PDFRawStream, PDFString } from "pdf-lib";
2
2
  import { convertKonvaRectToPdfRect, stringToPDFHexString } from "../engine/utils.js";
3
3
  import { AnnotationParser } from "./parse.js";
4
+ import { computeBoundsFromKonvaString } from "./parse_utils.js";
4
5
  const UNKNOWN_USER = "Unknown user";
5
6
  const SVG_RASTER_SCALE = 4;
6
7
  const MAX_RASTER_EDGE = 4096;
@@ -122,7 +123,8 @@ export class StampParser extends AnnotationParser {
122
123
  async parse() {
123
124
  const { annotation, page, pdfDoc } = this;
124
125
  const context = pdfDoc.context;
125
- const [x1, y1, x2, y2] = convertKonvaRectToPdfRect(annotation.konvaClientRect, page.getHeight());
126
+ const bounds = computeBoundsFromKonvaString(annotation.konvaString);
127
+ const [x1, y1, x2, y2] = convertKonvaRectToPdfRect(bounds ?? annotation.konvaClientRect, page.getHeight());
126
128
  const pageWidth = page.getWidth();
127
129
  const pageHeight = page.getHeight();
128
130
  const left = Math.max(0, Math.min(x1, pageWidth));
@@ -179,7 +181,7 @@ export class StampParser extends AnnotationParser {
179
181
  const replyAnn = context.obj({
180
182
  Type: PDFName.of("Annot"),
181
183
  Subtype: PDFName.of("Text"),
182
- Rect: convertKonvaRectToPdfRect(annotation.konvaClientRect, pageHeight),
184
+ Rect: convertKonvaRectToPdfRect(bounds ?? annotation.konvaClientRect, pageHeight),
183
185
  Contents: stringToPDFHexString(comment.content),
184
186
  T: stringToPDFHexString(comment.title || UNKNOWN_USER),
185
187
  M: PDFString.of(comment.date || ""),
@@ -1,11 +1,13 @@
1
1
  import { PDFName, PDFString } from "pdf-lib";
2
2
  import { convertKonvaRectToPdfRect, rgbToPdfColor, stringToPDFHexString } from "../engine/utils.js";
3
3
  import { AnnotationParser } from "./parse.js";
4
+ import { computeBoundsFromKonvaString } from "./parse_utils.js";
4
5
  const UNKNOWN_USER = "Unknown user";
5
6
  export class TextParser extends AnnotationParser {
6
7
  async parse() {
7
8
  const { annotation, page, pdfDoc } = this;
8
- const rect = convertKonvaRectToPdfRect(annotation.konvaClientRect, page.getHeight());
9
+ const bounds = computeBoundsFromKonvaString(annotation.konvaString);
10
+ const rect = convertKonvaRectToPdfRect(bounds ?? annotation.konvaClientRect, page.getHeight());
9
11
  const context = pdfDoc.context;
10
12
  const mainAnn = context.obj({
11
13
  Type: PDFName.of("Annot"),
@@ -0,0 +1,26 @@
1
+ /**
2
+ * Shared utilities for PDF export parsers.
3
+ *
4
+ * The main purpose is to compute annotation bounding boxes from the
5
+ * serialised Konva JSON (`konvaString`) rather than relying on
6
+ * `konvaClientRect`, which can become stale after the annotation is
7
+ * moved or resized.
8
+ */
9
+ /**
10
+ * Compute the bounding box of a Konva group from its serialised JSON,
11
+ * applying group-level transforms (x, y, scaleX, scaleY) to every child.
12
+ *
13
+ * Handles:
14
+ * - Rect / Image / Text children → x, y, width, height
15
+ * - Ellipse children → x (centre), y (centre), radiusX, radiusY
16
+ *
17
+ * Returns `null` when the string cannot be parsed or contains no children
18
+ * with computable dimensions. Callers should fall back to
19
+ * `annotation.konvaClientRect` in that case.
20
+ */
21
+ export declare function computeBoundsFromKonvaString(konvaString: string): {
22
+ x: number;
23
+ y: number;
24
+ width: number;
25
+ height: number;
26
+ } | null;
@@ -0,0 +1,54 @@
1
+ export function computeBoundsFromKonvaString(konvaString) {
2
+ let konvaGroup;
3
+ try {
4
+ konvaGroup = JSON.parse(konvaString);
5
+ } catch {
6
+ return null;
7
+ }
8
+ const groupX = konvaGroup.attrs?.x ?? 0;
9
+ const groupY = konvaGroup.attrs?.y ?? 0;
10
+ const scaleX = konvaGroup.attrs?.scaleX ?? 1;
11
+ const scaleY = konvaGroup.attrs?.scaleY ?? 1;
12
+ let minX = Infinity;
13
+ let minY = Infinity;
14
+ let maxX = -Infinity;
15
+ let maxY = -Infinity;
16
+ for (const child of konvaGroup.children ?? []) {
17
+ const attrs = child.attrs;
18
+ if (!attrs) continue;
19
+ let cx;
20
+ let cy;
21
+ let cw;
22
+ let ch;
23
+ if (child.className === "Ellipse") {
24
+ const rx = attrs.radiusX ?? 0;
25
+ const ry = attrs.radiusY ?? 0;
26
+ if (rx <= 0 && ry <= 0) continue;
27
+ cx = (attrs.x ?? 0) - rx;
28
+ cy = (attrs.y ?? 0) - ry;
29
+ cw = rx * 2;
30
+ ch = ry * 2;
31
+ } else {
32
+ cx = attrs.x ?? 0;
33
+ cy = attrs.y ?? 0;
34
+ cw = attrs.width ?? 0;
35
+ ch = attrs.height ?? 0;
36
+ if (cw <= 0 && ch <= 0) continue;
37
+ }
38
+ const x = groupX + cx * scaleX;
39
+ const y = groupY + cy * scaleY;
40
+ const w = Math.abs(cw * scaleX);
41
+ const h = Math.abs(ch * scaleY);
42
+ minX = Math.min(minX, x);
43
+ minY = Math.min(minY, y);
44
+ maxX = Math.max(maxX, x + w);
45
+ maxY = Math.max(maxY, y + h);
46
+ }
47
+ if (!Number.isFinite(minX) || !Number.isFinite(minY)) return null;
48
+ return {
49
+ x: minX,
50
+ y: minY,
51
+ width: maxX - minX,
52
+ height: maxY - minY
53
+ };
54
+ }
@@ -189,7 +189,7 @@ const props = defineProps({
189
189
  });
190
190
  const viewerRoot = ref(null);
191
191
  const scrollContainer = ref(null);
192
- const { state: viewerState, setScrollToPageFn } = provideViewerState();
192
+ const { state: viewerState, setScrollToPageFn, setDownloadPdfFn } = provideViewerState();
193
193
  watchEffect(() => {
194
194
  viewerState.readonly.value = props.readonly ?? false;
195
195
  });
@@ -640,6 +640,7 @@ onMounted(() => {
640
640
  if (isIPad()) {
641
641
  viewerState.setStylusMode(true);
642
642
  }
643
+ setDownloadPdfFn(() => exportPdf({ download: true }));
643
644
  loadDocument();
644
645
  window.addEventListener("keydown", onGlobalKeydown);
645
646
  });
@@ -58,9 +58,6 @@
58
58
  <script setup>
59
59
  import { ref } from "vue";
60
60
  import { useViewerState } from "../composables/useViewerState";
61
- import { getTimestampString } from "../annotation/engine/utils";
62
- import { downloadPdfBytes } from "../annotation/pdf-export/download";
63
- import { exportAnnotationsToPdf } from "../annotation/pdf-export/export";
64
61
  import PageSettings from "./tools/PageSettings.vue";
65
62
  import ZoomControls from "./tools/ZoomControls.vue";
66
63
  import HandTool from "./tools/HandTool.vue";
@@ -77,20 +74,7 @@ function bumpStyleVersion() {
77
74
  }
78
75
  async function onDownload() {
79
76
  try {
80
- if (!state.doc.value || !state.painter.value) return;
81
- const exportContext = state.painter.value.getExportContext();
82
- const bytes = await exportAnnotationsToPdf({
83
- pdfData: await state.doc.value.getData(),
84
- annotations: exportContext.annotations,
85
- options: {
86
- flatten: false,
87
- preserveOriginalAnnotations: false
88
- },
89
- originalAnnotationIds: exportContext.originalIds,
90
- modifiedAnnotationIds: exportContext.modifiedIds,
91
- deletedAnnotationIds: exportContext.deletedIds
92
- });
93
- downloadPdfBytes(bytes, `annotated_${getTimestampString()}.pdf`);
77
+ await state.downloadPdf();
94
78
  } catch (error) {
95
79
  console.error("Failed to export PDF from menu action", error);
96
80
  }
@@ -53,6 +53,7 @@ export interface ViewerState {
53
53
  stylusMode: Ref<boolean>;
54
54
  setStylusMode: (enabled: boolean) => void;
55
55
  scrollToPage: (pageNumber: number) => void;
56
+ downloadPdf: () => Promise<void>;
56
57
  history: AnnotationHistory;
57
58
  readonly: Ref<boolean>;
58
59
  shapeDetection: Ref<boolean>;
@@ -60,6 +61,7 @@ export interface ViewerState {
60
61
  export interface ViewerStateProvider {
61
62
  state: ViewerState;
62
63
  setScrollToPageFn: (fn: (pageNumber: number) => void) => void;
64
+ setDownloadPdfFn: (fn: () => Promise<void>) => void;
63
65
  }
64
66
  export declare function provideViewerState(): ViewerStateProvider;
65
67
  export declare function useViewerState(): ViewerState;
@@ -130,9 +130,13 @@ export function provideViewerState() {
130
130
  return painter.value?.getData() ?? [];
131
131
  }
132
132
  let _scrollToPageFn = null;
133
+ let _downloadPdfFn = null;
133
134
  function scrollToPage(pageNumber) {
134
135
  _scrollToPageFn?.(pageNumber);
135
136
  }
137
+ async function downloadPdf() {
138
+ await _downloadPdfFn?.();
139
+ }
136
140
  const history = createAnnotationHistory(annotations, painter);
137
141
  const state = {
138
142
  scale,
@@ -174,6 +178,7 @@ export function provideViewerState() {
174
178
  stylusMode,
175
179
  setStylusMode,
176
180
  scrollToPage,
181
+ downloadPdf,
177
182
  history,
178
183
  readonly,
179
184
  shapeDetection: shapeDetectionEnabled
@@ -183,6 +188,9 @@ export function provideViewerState() {
183
188
  state,
184
189
  setScrollToPageFn(fn) {
185
190
  _scrollToPageFn = fn;
191
+ },
192
+ setDownloadPdfFn(fn) {
193
+ _downloadPdfFn = fn;
186
194
  }
187
195
  };
188
196
  }
@@ -1,2 +1,2 @@
1
- declare const _default: any;
1
+ declare const _default: import("nuxt/app").Plugin<Record<string, unknown>> & import("nuxt/app").ObjectPlugin<Record<string, unknown>>;
2
2
  export default _default;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "kviewer",
3
- "version": "0.0.5",
3
+ "version": "0.0.7",
4
4
  "description": "Kabema PDF Editor",
5
5
  "repository": "kabema/kviewer",
6
6
  "license": "MIT",