kviewer 0.0.6 → 0.0.8

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.6",
4
+ "version": "0.0.8",
5
5
  "builder": {
6
6
  "@nuxt/module-builder": "1.0.2",
7
7
  "unbuild": "3.6.1"
@@ -50,7 +50,19 @@ export class Editor {
50
50
  id,
51
51
  pageNumber,
52
52
  konvaString: konvaGroup.toJSON(),
53
- konvaClientRect: konvaGroup.getClientRect(),
53
+ // Do NOT use konvaGroup.getClientRect() directly here — it includes the
54
+ // stage's zoom scale, which would inflate annotation coordinates in the
55
+ // exported PDF. We divide by scale to get page-space coordinates.
56
+ konvaClientRect: (() => {
57
+ const scale = konvaGroup.getStage()?.scaleX() ?? 1;
58
+ const raw = konvaGroup.getClientRect();
59
+ return {
60
+ x: raw.x / scale,
61
+ y: raw.y / scale,
62
+ width: raw.width / scale,
63
+ height: raw.height / scale
64
+ };
65
+ })(),
54
66
  title: this.userName,
55
67
  type: annotation.type,
56
68
  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
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "kviewer",
3
- "version": "0.0.6",
3
+ "version": "0.0.8",
4
4
  "description": "Kabema PDF Editor",
5
5
  "repository": "kabema/kviewer",
6
6
  "license": "MIT",