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 +1 -1
- package/dist/runtime/annotation/engine/editor/editor.js +1 -1
- package/dist/runtime/annotation/pdf-export/parse_circle.js +5 -2
- package/dist/runtime/annotation/pdf-export/parse_polyline.js +5 -2
- package/dist/runtime/annotation/pdf-export/parse_square.js +5 -2
- package/dist/runtime/annotation/pdf-export/parse_stamp.js +4 -2
- package/dist/runtime/annotation/pdf-export/parse_text.js +3 -1
- package/dist/runtime/annotation/pdf-export/parse_utils.d.ts +26 -0
- package/dist/runtime/annotation/pdf-export/parse_utils.js +54 -0
- package/dist/runtime/components/Viewer.vue +2 -1
- package/dist/runtime/components/ViewerBar.vue +1 -17
- package/dist/runtime/composables/useViewerState.d.ts +2 -0
- package/dist/runtime/composables/useViewerState.js +8 -0
- package/dist/runtime/plugin.d.ts +1 -1
- package/package.json +1 -1
package/dist/module.json
CHANGED
|
@@ -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:
|
|
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:
|
|
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
|
|
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:
|
|
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:
|
|
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
|
|
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
|
|
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
|
-
|
|
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
|
}
|
package/dist/runtime/plugin.d.ts
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
declare const _default:
|
|
1
|
+
declare const _default: import("nuxt/app").Plugin<Record<string, unknown>> & import("nuxt/app").ObjectPlugin<Record<string, unknown>>;
|
|
2
2
|
export default _default;
|