kviewer 0.0.1
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/README.md +134 -0
- package/dist/module.d.mts +15 -0
- package/dist/module.json +9 -0
- package/dist/module.mjs +26 -0
- package/dist/runtime/annotation/engine/config.d.ts +52 -0
- package/dist/runtime/annotation/engine/config.js +283 -0
- package/dist/runtime/annotation/engine/const.d.ts +6 -0
- package/dist/runtime/annotation/engine/const.js +7 -0
- package/dist/runtime/annotation/engine/cursor-preview.d.ts +2 -0
- package/dist/runtime/annotation/engine/cursor-preview.js +88 -0
- package/dist/runtime/annotation/engine/editor/editor.d.ts +69 -0
- package/dist/runtime/annotation/engine/editor/editor.js +233 -0
- package/dist/runtime/annotation/engine/editor/selector.d.ts +74 -0
- package/dist/runtime/annotation/engine/editor/selector.js +594 -0
- package/dist/runtime/annotation/engine/import-normalize.d.ts +5 -0
- package/dist/runtime/annotation/engine/import-normalize.js +99 -0
- package/dist/runtime/annotation/engine/input-device.d.ts +53 -0
- package/dist/runtime/annotation/engine/input-device.js +64 -0
- package/dist/runtime/annotation/engine/painter.d.ts +97 -0
- package/dist/runtime/annotation/engine/painter.js +591 -0
- package/dist/runtime/annotation/engine/store.d.ts +11 -0
- package/dist/runtime/annotation/engine/store.js +47 -0
- package/dist/runtime/annotation/engine/tools/arrow.d.ts +22 -0
- package/dist/runtime/annotation/engine/tools/arrow.js +126 -0
- package/dist/runtime/annotation/engine/tools/circle.d.ts +45 -0
- package/dist/runtime/annotation/engine/tools/circle.js +148 -0
- package/dist/runtime/annotation/engine/tools/cloud.d.ts +50 -0
- package/dist/runtime/annotation/engine/tools/cloud.js +244 -0
- package/dist/runtime/annotation/engine/tools/free-highlight.d.ts +43 -0
- package/dist/runtime/annotation/engine/tools/free-highlight.js +165 -0
- package/dist/runtime/annotation/engine/tools/free-text.d.ts +27 -0
- package/dist/runtime/annotation/engine/tools/free-text.js +114 -0
- package/dist/runtime/annotation/engine/tools/freehand.d.ts +44 -0
- package/dist/runtime/annotation/engine/tools/freehand.js +151 -0
- package/dist/runtime/annotation/engine/tools/highlight.d.ts +87 -0
- package/dist/runtime/annotation/engine/tools/highlight.js +215 -0
- package/dist/runtime/annotation/engine/tools/note.d.ts +9 -0
- package/dist/runtime/annotation/engine/tools/note.js +34 -0
- package/dist/runtime/annotation/engine/tools/rectangle.d.ts +45 -0
- package/dist/runtime/annotation/engine/tools/rectangle.js +142 -0
- package/dist/runtime/annotation/engine/tools/signature.d.ts +16 -0
- package/dist/runtime/annotation/engine/tools/signature.js +74 -0
- package/dist/runtime/annotation/engine/tools/stamp.d.ts +18 -0
- package/dist/runtime/annotation/engine/tools/stamp.js +94 -0
- package/dist/runtime/annotation/engine/types.d.ts +170 -0
- package/dist/runtime/annotation/engine/types.js +67 -0
- package/dist/runtime/annotation/engine/utils.d.ts +40 -0
- package/dist/runtime/annotation/engine/utils.js +257 -0
- package/dist/runtime/annotation/parsers/parseFormFields.d.ts +9 -0
- package/dist/runtime/annotation/parsers/parseFormFields.js +101 -0
- package/dist/runtime/annotation/pdf-export/download.d.ts +1 -0
- package/dist/runtime/annotation/pdf-export/download.js +10 -0
- package/dist/runtime/annotation/pdf-export/export-form-fields.d.ts +9 -0
- package/dist/runtime/annotation/pdf-export/export-form-fields.js +90 -0
- package/dist/runtime/annotation/pdf-export/export.d.ts +15 -0
- package/dist/runtime/annotation/pdf-export/export.js +145 -0
- package/dist/runtime/annotation/pdf-export/parse.d.ts +10 -0
- package/dist/runtime/annotation/pdf-export/parse.js +19 -0
- package/dist/runtime/annotation/pdf-export/parse_circle.d.ts +4 -0
- package/dist/runtime/annotation/pdf-export/parse_circle.js +41 -0
- package/dist/runtime/annotation/pdf-export/parse_freetext.d.ts +4 -0
- package/dist/runtime/annotation/pdf-export/parse_freetext.js +54 -0
- package/dist/runtime/annotation/pdf-export/parse_highlight.d.ts +4 -0
- package/dist/runtime/annotation/pdf-export/parse_highlight.js +134 -0
- package/dist/runtime/annotation/pdf-export/parse_ink.d.ts +4 -0
- package/dist/runtime/annotation/pdf-export/parse_ink.js +124 -0
- package/dist/runtime/annotation/pdf-export/parse_line.d.ts +4 -0
- package/dist/runtime/annotation/pdf-export/parse_line.js +71 -0
- package/dist/runtime/annotation/pdf-export/parse_polyline.d.ts +4 -0
- package/dist/runtime/annotation/pdf-export/parse_polyline.js +93 -0
- package/dist/runtime/annotation/pdf-export/parse_square.d.ts +4 -0
- package/dist/runtime/annotation/pdf-export/parse_square.js +41 -0
- package/dist/runtime/annotation/pdf-export/parse_stamp.d.ts +4 -0
- package/dist/runtime/annotation/pdf-export/parse_stamp.js +195 -0
- package/dist/runtime/annotation/pdf-export/parse_strikeout.d.ts +4 -0
- package/dist/runtime/annotation/pdf-export/parse_strikeout.js +59 -0
- package/dist/runtime/annotation/pdf-export/parse_text.d.ts +4 -0
- package/dist/runtime/annotation/pdf-export/parse_text.js +42 -0
- package/dist/runtime/annotation/pdf-export/parse_underline.d.ts +4 -0
- package/dist/runtime/annotation/pdf-export/parse_underline.js +59 -0
- package/dist/runtime/assets/kviewer.css +1 -0
- package/dist/runtime/components/AnnotationToolbar.d.vue.ts +3 -0
- package/dist/runtime/components/AnnotationToolbar.vue +125 -0
- package/dist/runtime/components/AnnotationToolbar.vue.d.ts +3 -0
- package/dist/runtime/components/FloatingPageIndicator.d.vue.ts +6 -0
- package/dist/runtime/components/FloatingPageIndicator.vue +93 -0
- package/dist/runtime/components/FloatingPageIndicator.vue.d.ts +6 -0
- package/dist/runtime/components/FormFieldLayer.d.vue.ts +11 -0
- package/dist/runtime/components/FormFieldLayer.vue +40 -0
- package/dist/runtime/components/FormFieldLayer.vue.d.ts +11 -0
- package/dist/runtime/components/PdfPage.d.vue.ts +9 -0
- package/dist/runtime/components/PdfPage.vue +199 -0
- package/dist/runtime/components/PdfPage.vue.d.ts +9 -0
- package/dist/runtime/components/ToolButton.d.vue.ts +13 -0
- package/dist/runtime/components/ToolButton.vue +26 -0
- package/dist/runtime/components/ToolButton.vue.d.ts +13 -0
- package/dist/runtime/components/Toolbar.d.vue.ts +3 -0
- package/dist/runtime/components/Toolbar.vue +11 -0
- package/dist/runtime/components/Toolbar.vue.d.ts +3 -0
- package/dist/runtime/components/Viewer.d.vue.ts +45 -0
- package/dist/runtime/components/Viewer.vue +617 -0
- package/dist/runtime/components/Viewer.vue.d.ts +45 -0
- package/dist/runtime/components/ViewerBar.d.vue.ts +3 -0
- package/dist/runtime/components/ViewerBar.vue +91 -0
- package/dist/runtime/components/ViewerBar.vue.d.ts +3 -0
- package/dist/runtime/components/ViewerTabs.d.vue.ts +381 -0
- package/dist/runtime/components/ViewerTabs.vue +171 -0
- package/dist/runtime/components/ViewerTabs.vue.d.ts +381 -0
- package/dist/runtime/components/form-fields/FormButton.d.vue.ts +7 -0
- package/dist/runtime/components/form-fields/FormButton.vue +39 -0
- package/dist/runtime/components/form-fields/FormButton.vue.d.ts +7 -0
- package/dist/runtime/components/form-fields/FormCheckbox.d.vue.ts +7 -0
- package/dist/runtime/components/form-fields/FormCheckbox.vue +28 -0
- package/dist/runtime/components/form-fields/FormCheckbox.vue.d.ts +7 -0
- package/dist/runtime/components/form-fields/FormDropdown.d.vue.ts +7 -0
- package/dist/runtime/components/form-fields/FormDropdown.vue +112 -0
- package/dist/runtime/components/form-fields/FormDropdown.vue.d.ts +7 -0
- package/dist/runtime/components/form-fields/FormFieldWrapper.d.vue.ts +8 -0
- package/dist/runtime/components/form-fields/FormFieldWrapper.vue +41 -0
- package/dist/runtime/components/form-fields/FormFieldWrapper.vue.d.ts +8 -0
- package/dist/runtime/components/form-fields/FormRadioButton.d.vue.ts +7 -0
- package/dist/runtime/components/form-fields/FormRadioButton.vue +30 -0
- package/dist/runtime/components/form-fields/FormRadioButton.vue.d.ts +7 -0
- package/dist/runtime/components/form-fields/FormSignatureField.d.vue.ts +7 -0
- package/dist/runtime/components/form-fields/FormSignatureField.vue +54 -0
- package/dist/runtime/components/form-fields/FormSignatureField.vue.d.ts +7 -0
- package/dist/runtime/components/form-fields/FormTextField.d.vue.ts +7 -0
- package/dist/runtime/components/form-fields/FormTextField.vue +66 -0
- package/dist/runtime/components/form-fields/FormTextField.vue.d.ts +7 -0
- package/dist/runtime/components/modals/FreeTextModal.d.vue.ts +25 -0
- package/dist/runtime/components/modals/FreeTextModal.vue +89 -0
- package/dist/runtime/components/modals/FreeTextModal.vue.d.ts +25 -0
- package/dist/runtime/components/modals/SignatureDrawModal.d.vue.ts +14 -0
- package/dist/runtime/components/modals/SignatureDrawModal.vue +120 -0
- package/dist/runtime/components/modals/SignatureDrawModal.vue.d.ts +14 -0
- package/dist/runtime/components/panels/SignaturePicker.d.vue.ts +3 -0
- package/dist/runtime/components/panels/SignaturePicker.vue +85 -0
- package/dist/runtime/components/panels/SignaturePicker.vue.d.ts +3 -0
- package/dist/runtime/components/panels/StampPicker.d.vue.ts +3 -0
- package/dist/runtime/components/panels/StampPicker.vue +46 -0
- package/dist/runtime/components/panels/StampPicker.vue.d.ts +3 -0
- package/dist/runtime/components/tools/ActionTools.d.vue.ts +3 -0
- package/dist/runtime/components/tools/ActionTools.vue +32 -0
- package/dist/runtime/components/tools/ActionTools.vue.d.ts +3 -0
- package/dist/runtime/components/tools/DrawingTools.d.vue.ts +6 -0
- package/dist/runtime/components/tools/DrawingTools.vue +57 -0
- package/dist/runtime/components/tools/DrawingTools.vue.d.ts +6 -0
- package/dist/runtime/components/tools/HandTool.d.vue.ts +3 -0
- package/dist/runtime/components/tools/HandTool.vue +14 -0
- package/dist/runtime/components/tools/HandTool.vue.d.ts +3 -0
- package/dist/runtime/components/tools/MarqueeTool.d.vue.ts +3 -0
- package/dist/runtime/components/tools/MarqueeTool.vue +15 -0
- package/dist/runtime/components/tools/MarqueeTool.vue.d.ts +3 -0
- package/dist/runtime/components/tools/PageInfo.d.vue.ts +3 -0
- package/dist/runtime/components/tools/PageInfo.vue +10 -0
- package/dist/runtime/components/tools/PageInfo.vue.d.ts +3 -0
- package/dist/runtime/components/tools/PageSettings.d.vue.ts +3 -0
- package/dist/runtime/components/tools/PageSettings.vue +92 -0
- package/dist/runtime/components/tools/PageSettings.vue.d.ts +3 -0
- package/dist/runtime/components/tools/SearchTool.d.vue.ts +3 -0
- package/dist/runtime/components/tools/SearchTool.vue +149 -0
- package/dist/runtime/components/tools/SearchTool.vue.d.ts +3 -0
- package/dist/runtime/components/tools/ToolProperties.d.vue.ts +7 -0
- package/dist/runtime/components/tools/ToolProperties.vue +174 -0
- package/dist/runtime/components/tools/ToolProperties.vue.d.ts +7 -0
- package/dist/runtime/components/tools/ZoomControls.d.vue.ts +3 -0
- package/dist/runtime/components/tools/ZoomControls.vue +59 -0
- package/dist/runtime/components/tools/ZoomControls.vue.d.ts +3 -0
- package/dist/runtime/composables/search-utils.d.ts +20 -0
- package/dist/runtime/composables/search-utils.js +55 -0
- package/dist/runtime/composables/useAnnotationEngine.d.ts +7 -0
- package/dist/runtime/composables/useAnnotationEngine.js +70 -0
- package/dist/runtime/composables/useAnnotationHistory.d.ts +12 -0
- package/dist/runtime/composables/useAnnotationHistory.js +69 -0
- package/dist/runtime/composables/useFormFields.d.ts +26 -0
- package/dist/runtime/composables/useFormFields.js +112 -0
- package/dist/runtime/composables/usePageProxyCache.d.ts +8 -0
- package/dist/runtime/composables/usePageProxyCache.js +73 -0
- package/dist/runtime/composables/usePageSettings.d.ts +16 -0
- package/dist/runtime/composables/usePageSettings.js +66 -0
- package/dist/runtime/composables/usePageVirtualization.d.ts +19 -0
- package/dist/runtime/composables/usePageVirtualization.js +203 -0
- package/dist/runtime/composables/useSearchIndex.d.ts +11 -0
- package/dist/runtime/composables/useSearchIndex.js +71 -0
- package/dist/runtime/composables/useViewerSearch.d.ts +32 -0
- package/dist/runtime/composables/useViewerSearch.js +418 -0
- package/dist/runtime/composables/useViewerState.d.ts +62 -0
- package/dist/runtime/composables/useViewerState.js +189 -0
- package/dist/runtime/composables/viewMode.d.ts +11 -0
- package/dist/runtime/composables/viewMode.js +19 -0
- package/dist/runtime/plugin.d.ts +2 -0
- package/dist/runtime/plugin.js +3 -0
- package/dist/runtime/public-types.d.ts +2 -0
- package/dist/runtime/public-types.js +0 -0
- package/dist/runtime/server/tsconfig.json +3 -0
- package/dist/types.d.mts +5 -0
- package/package.json +64 -0
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
export async function writeFormFieldsToPdf(pdfDoc, fieldValues) {
|
|
2
|
+
const form = pdfDoc.getForm();
|
|
3
|
+
for (const field of fieldValues) {
|
|
4
|
+
try {
|
|
5
|
+
switch (field.fieldType) {
|
|
6
|
+
case "text": {
|
|
7
|
+
const textField = form.getTextField(field.fieldName);
|
|
8
|
+
textField.setText(typeof field.value === "string" ? field.value : String(field.value));
|
|
9
|
+
break;
|
|
10
|
+
}
|
|
11
|
+
case "checkbox": {
|
|
12
|
+
const checkBox = form.getCheckBox(field.fieldName);
|
|
13
|
+
if (field.value === true) {
|
|
14
|
+
checkBox.check();
|
|
15
|
+
} else {
|
|
16
|
+
checkBox.uncheck();
|
|
17
|
+
}
|
|
18
|
+
break;
|
|
19
|
+
}
|
|
20
|
+
case "radio": {
|
|
21
|
+
if (typeof field.value === "string" && field.value !== "") {
|
|
22
|
+
const radioGroup = form.getRadioGroup(field.fieldName);
|
|
23
|
+
radioGroup.select(field.value);
|
|
24
|
+
}
|
|
25
|
+
break;
|
|
26
|
+
}
|
|
27
|
+
case "dropdown": {
|
|
28
|
+
const dropdown = form.getDropdown(field.fieldName);
|
|
29
|
+
if (Array.isArray(field.value)) {
|
|
30
|
+
for (const v of field.value) {
|
|
31
|
+
dropdown.select(v);
|
|
32
|
+
}
|
|
33
|
+
} else if (typeof field.value === "string" && field.value !== "") {
|
|
34
|
+
dropdown.select(field.value);
|
|
35
|
+
}
|
|
36
|
+
break;
|
|
37
|
+
}
|
|
38
|
+
case "signature": {
|
|
39
|
+
if (typeof field.value === "string" && field.value.startsWith("data:image")) {
|
|
40
|
+
await embedSignatureImage(pdfDoc, field);
|
|
41
|
+
}
|
|
42
|
+
break;
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
} catch (err) {
|
|
46
|
+
console.warn(
|
|
47
|
+
`Failed to write form field "${field.fieldName}" (${field.fieldType}):`,
|
|
48
|
+
err
|
|
49
|
+
);
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
async function embedSignatureImage(pdfDoc, field) {
|
|
54
|
+
const dataUrl = field.value;
|
|
55
|
+
const match = dataUrl.match(/^data:(image\/[a-zA-Z0-9.+-]+);base64,(.+)$/);
|
|
56
|
+
if (!match) return;
|
|
57
|
+
const mimeType = match[1] ?? "";
|
|
58
|
+
const base64 = match[2] ?? "";
|
|
59
|
+
let image;
|
|
60
|
+
if (mimeType.includes("jpeg") || mimeType.includes("jpg")) {
|
|
61
|
+
image = await pdfDoc.embedJpg(base64);
|
|
62
|
+
} else {
|
|
63
|
+
image = await pdfDoc.embedPng(base64);
|
|
64
|
+
}
|
|
65
|
+
const form = pdfDoc.getForm();
|
|
66
|
+
try {
|
|
67
|
+
const sigField = form.getSignature(field.fieldName);
|
|
68
|
+
const widgets = sigField.acroField.getWidgets();
|
|
69
|
+
if (widgets.length > 0) {
|
|
70
|
+
const widget = widgets[0];
|
|
71
|
+
const rect = widget.getRectangle();
|
|
72
|
+
const pages = pdfDoc.getPages();
|
|
73
|
+
for (const page of pages) {
|
|
74
|
+
const pageRef = page.ref;
|
|
75
|
+
const widgetPage = widget.P();
|
|
76
|
+
if (widgetPage === pageRef || !widgetPage) {
|
|
77
|
+
page.drawImage(image, {
|
|
78
|
+
x: rect.x,
|
|
79
|
+
y: rect.y,
|
|
80
|
+
width: rect.width,
|
|
81
|
+
height: rect.height
|
|
82
|
+
});
|
|
83
|
+
break;
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
} catch {
|
|
88
|
+
console.warn(`Could not embed signature for field "${field.fieldName}"`);
|
|
89
|
+
}
|
|
90
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { type IAnnotationStore, type FormFieldValue } from '../engine/types.js';
|
|
2
|
+
export interface ExportPdfOptions {
|
|
3
|
+
flatten?: boolean;
|
|
4
|
+
download?: boolean;
|
|
5
|
+
fileName?: string;
|
|
6
|
+
preserveOriginalAnnotations?: boolean;
|
|
7
|
+
}
|
|
8
|
+
interface ExportPdfParams {
|
|
9
|
+
pdfData: Uint8Array | ArrayBuffer;
|
|
10
|
+
annotations: IAnnotationStore[];
|
|
11
|
+
options?: ExportPdfOptions;
|
|
12
|
+
formFieldValues?: FormFieldValue[];
|
|
13
|
+
}
|
|
14
|
+
export declare function exportAnnotationsToPdf({ pdfData, annotations, options, formFieldValues, }: ExportPdfParams): Promise<Uint8Array>;
|
|
15
|
+
export {};
|
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
import { PDFDocument, PDFName } from "pdf-lib";
|
|
2
|
+
import { PdfjsAnnotationType } from "../engine/types.js";
|
|
3
|
+
import { writeFormFieldsToPdf } from "./export-form-fields.js";
|
|
4
|
+
import { CircleParser } from "./parse_circle.js";
|
|
5
|
+
import { FreeTextParser } from "./parse_freetext.js";
|
|
6
|
+
import { HighlightParser } from "./parse_highlight.js";
|
|
7
|
+
import { InkParser } from "./parse_ink.js";
|
|
8
|
+
import { LineParser } from "./parse_line.js";
|
|
9
|
+
import { PolylineParser } from "./parse_polyline.js";
|
|
10
|
+
import { SquareParser } from "./parse_square.js";
|
|
11
|
+
import { StampParser } from "./parse_stamp.js";
|
|
12
|
+
import { StrikeOutParser } from "./parse_strikeout.js";
|
|
13
|
+
import { TextParser } from "./parse_text.js";
|
|
14
|
+
import { UnderlineParser } from "./parse_underline.js";
|
|
15
|
+
const parserMap = {
|
|
16
|
+
[PdfjsAnnotationType.TEXT]: TextParser,
|
|
17
|
+
[PdfjsAnnotationType.HIGHLIGHT]: HighlightParser,
|
|
18
|
+
[PdfjsAnnotationType.UNDERLINE]: UnderlineParser,
|
|
19
|
+
[PdfjsAnnotationType.STRIKEOUT]: StrikeOutParser,
|
|
20
|
+
[PdfjsAnnotationType.SQUARE]: SquareParser,
|
|
21
|
+
[PdfjsAnnotationType.CIRCLE]: CircleParser,
|
|
22
|
+
[PdfjsAnnotationType.INK]: InkParser,
|
|
23
|
+
[PdfjsAnnotationType.POLYLINE]: PolylineParser,
|
|
24
|
+
[PdfjsAnnotationType.FREETEXT]: FreeTextParser,
|
|
25
|
+
[PdfjsAnnotationType.STAMP]: StampParser,
|
|
26
|
+
[PdfjsAnnotationType.LINE]: LineParser
|
|
27
|
+
};
|
|
28
|
+
function toUint8Array(data) {
|
|
29
|
+
if (data instanceof Uint8Array) return data;
|
|
30
|
+
return new Uint8Array(data);
|
|
31
|
+
}
|
|
32
|
+
function clearAllAnnotations(pdfDoc) {
|
|
33
|
+
for (const page of pdfDoc.getPages()) {
|
|
34
|
+
const annotsKey = PDFName.of("Annots");
|
|
35
|
+
if (page.node.has(annotsKey)) {
|
|
36
|
+
page.node.set(annotsKey, pdfDoc.context.obj([]));
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
async function parseAnnotationToPdf(annotation, page, pdfDoc) {
|
|
41
|
+
const ParserClass = parserMap[annotation.pdfjsType];
|
|
42
|
+
if (!ParserClass) {
|
|
43
|
+
console.warn(`Unsupported annotation type for PDF export: ${annotation.pdfjsType}`);
|
|
44
|
+
return;
|
|
45
|
+
}
|
|
46
|
+
const parser = new ParserClass(pdfDoc, page, annotation);
|
|
47
|
+
await parser.parse();
|
|
48
|
+
}
|
|
49
|
+
async function embedDataUrlImage(pdfDoc, dataUrl) {
|
|
50
|
+
const match = dataUrl.match(/^data:(image\/[a-zA-Z0-9.+-]+);base64,(.+)$/);
|
|
51
|
+
if (!match) {
|
|
52
|
+
throw new TypeError("Invalid data URL for flatten export image");
|
|
53
|
+
}
|
|
54
|
+
const mimeType = match[1] || "";
|
|
55
|
+
const base64 = match[2] || "";
|
|
56
|
+
if (mimeType.includes("jpeg") || mimeType.includes("jpg")) {
|
|
57
|
+
return pdfDoc.embedJpg(base64);
|
|
58
|
+
}
|
|
59
|
+
return pdfDoc.embedPng(base64);
|
|
60
|
+
}
|
|
61
|
+
async function flattenPageAnnotations(pdfDoc, page, annotations) {
|
|
62
|
+
if (annotations.length === 0) return;
|
|
63
|
+
if (typeof window === "undefined" || typeof document === "undefined") {
|
|
64
|
+
throw new TypeError("Flatten export requires a browser environment");
|
|
65
|
+
}
|
|
66
|
+
const Konva = (await import("konva")).default;
|
|
67
|
+
const width = Math.floor(page.getWidth());
|
|
68
|
+
const height = Math.floor(page.getHeight());
|
|
69
|
+
const stage = new Konva.Stage({
|
|
70
|
+
container: document.createElement("div"),
|
|
71
|
+
width,
|
|
72
|
+
height,
|
|
73
|
+
listening: false
|
|
74
|
+
});
|
|
75
|
+
const layer = new Konva.Layer({ listening: false });
|
|
76
|
+
stage.add(layer);
|
|
77
|
+
for (const annotation of annotations) {
|
|
78
|
+
try {
|
|
79
|
+
const node = Konva.Node.create(annotation.konvaString);
|
|
80
|
+
if (node instanceof Konva.Node) {
|
|
81
|
+
layer.add(node);
|
|
82
|
+
}
|
|
83
|
+
} catch {
|
|
84
|
+
console.warn(`Unable to flatten annotation ${annotation.id}; skipping`);
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
layer.draw();
|
|
88
|
+
const dataUrl = stage.toDataURL({ pixelRatio: 2 });
|
|
89
|
+
stage.destroy();
|
|
90
|
+
const image = await embedDataUrlImage(pdfDoc, dataUrl);
|
|
91
|
+
page.drawImage(image, {
|
|
92
|
+
x: 0,
|
|
93
|
+
y: 0,
|
|
94
|
+
width: page.getWidth(),
|
|
95
|
+
height: page.getHeight()
|
|
96
|
+
});
|
|
97
|
+
}
|
|
98
|
+
function groupByPage(annotations) {
|
|
99
|
+
const grouped = /* @__PURE__ */ new Map();
|
|
100
|
+
for (const annotation of annotations) {
|
|
101
|
+
const list = grouped.get(annotation.pageNumber);
|
|
102
|
+
if (list) {
|
|
103
|
+
list.push(annotation);
|
|
104
|
+
} else {
|
|
105
|
+
grouped.set(annotation.pageNumber, [annotation]);
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
return grouped;
|
|
109
|
+
}
|
|
110
|
+
export async function exportAnnotationsToPdf({
|
|
111
|
+
pdfData,
|
|
112
|
+
annotations,
|
|
113
|
+
options,
|
|
114
|
+
formFieldValues
|
|
115
|
+
}) {
|
|
116
|
+
const flatten = options?.flatten ?? false;
|
|
117
|
+
const preserveOriginalAnnotations = options?.preserveOriginalAnnotations ?? false;
|
|
118
|
+
const pdfDoc = await PDFDocument.load(toUint8Array(pdfData));
|
|
119
|
+
if (formFieldValues && formFieldValues.length > 0) {
|
|
120
|
+
await writeFormFieldsToPdf(pdfDoc, formFieldValues);
|
|
121
|
+
}
|
|
122
|
+
if (!preserveOriginalAnnotations) {
|
|
123
|
+
clearAllAnnotations(pdfDoc);
|
|
124
|
+
}
|
|
125
|
+
if (flatten) {
|
|
126
|
+
const pages = pdfDoc.getPages();
|
|
127
|
+
const grouped = groupByPage(annotations);
|
|
128
|
+
for (const [pageNumber, pageAnnotations] of grouped.entries()) {
|
|
129
|
+
const page = pages[pageNumber - 1];
|
|
130
|
+
if (!page) continue;
|
|
131
|
+
await flattenPageAnnotations(pdfDoc, page, pageAnnotations);
|
|
132
|
+
}
|
|
133
|
+
} else {
|
|
134
|
+
const pages = pdfDoc.getPages();
|
|
135
|
+
for (const annotation of annotations) {
|
|
136
|
+
const page = pages[annotation.pageNumber - 1];
|
|
137
|
+
if (!page) {
|
|
138
|
+
console.warn(`Skipping annotation ${annotation.id}; page ${annotation.pageNumber} missing`);
|
|
139
|
+
continue;
|
|
140
|
+
}
|
|
141
|
+
await parseAnnotationToPdf(annotation, page, pdfDoc);
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
return pdfDoc.save();
|
|
145
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type { PDFDocument, PDFPage, PDFRef } from 'pdf-lib';
|
|
2
|
+
import type { IAnnotationStore } from '../engine/types.js';
|
|
3
|
+
export declare abstract class AnnotationParser {
|
|
4
|
+
protected annotation: IAnnotationStore;
|
|
5
|
+
protected page: PDFPage;
|
|
6
|
+
protected pdfDoc: PDFDocument;
|
|
7
|
+
constructor(pdfDoc: PDFDocument, page: PDFPage, annotation: IAnnotationStore);
|
|
8
|
+
protected addAnnotationToPage(page: PDFPage, annotRef: PDFRef): void;
|
|
9
|
+
abstract parse(): Promise<void>;
|
|
10
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { PDFName } from "pdf-lib";
|
|
2
|
+
export class AnnotationParser {
|
|
3
|
+
annotation;
|
|
4
|
+
page;
|
|
5
|
+
pdfDoc;
|
|
6
|
+
constructor(pdfDoc, page, annotation) {
|
|
7
|
+
this.pdfDoc = pdfDoc;
|
|
8
|
+
this.page = page;
|
|
9
|
+
this.annotation = annotation;
|
|
10
|
+
}
|
|
11
|
+
addAnnotationToPage(page, annotRef) {
|
|
12
|
+
const annots = page.node.lookup(PDFName.of("Annots"));
|
|
13
|
+
if (annots) {
|
|
14
|
+
annots.push(annotRef);
|
|
15
|
+
} else {
|
|
16
|
+
page.node.set(PDFName.of("Annots"), page.doc.context.obj([annotRef]));
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { PDFName, PDFString } from "pdf-lib";
|
|
2
|
+
import { convertKonvaRectToPdfRect, rgbToPdfColor, stringToPDFHexString } from "../engine/utils.js";
|
|
3
|
+
import { AnnotationParser } from "./parse.js";
|
|
4
|
+
const UNKNOWN_USER = "Unknown user";
|
|
5
|
+
export class CircleParser extends AnnotationParser {
|
|
6
|
+
async parse() {
|
|
7
|
+
const { annotation, page, pdfDoc } = this;
|
|
8
|
+
const context = pdfDoc.context;
|
|
9
|
+
const pageHeight = page.getHeight();
|
|
10
|
+
const mainAnn = context.obj({
|
|
11
|
+
Type: PDFName.of("Annot"),
|
|
12
|
+
Subtype: PDFName.of("Circle"),
|
|
13
|
+
Rect: convertKonvaRectToPdfRect(annotation.konvaClientRect, pageHeight),
|
|
14
|
+
C: rgbToPdfColor(annotation.color || void 0),
|
|
15
|
+
T: stringToPDFHexString(annotation.title || UNKNOWN_USER),
|
|
16
|
+
Contents: stringToPDFHexString(annotation.contentsObj?.text || ""),
|
|
17
|
+
M: PDFString.of(annotation.date || ""),
|
|
18
|
+
NM: PDFString.of(annotation.id),
|
|
19
|
+
Border: [0, 0, 1]
|
|
20
|
+
});
|
|
21
|
+
const mainAnnRef = context.register(mainAnn);
|
|
22
|
+
this.addAnnotationToPage(page, mainAnnRef);
|
|
23
|
+
for (const comment of annotation.comments || []) {
|
|
24
|
+
const replyAnn = context.obj({
|
|
25
|
+
Type: PDFName.of("Annot"),
|
|
26
|
+
Subtype: PDFName.of("Text"),
|
|
27
|
+
Rect: convertKonvaRectToPdfRect(annotation.konvaClientRect, pageHeight),
|
|
28
|
+
Contents: stringToPDFHexString(comment.content),
|
|
29
|
+
T: stringToPDFHexString(comment.title || UNKNOWN_USER),
|
|
30
|
+
M: PDFString.of(comment.date || ""),
|
|
31
|
+
C: rgbToPdfColor(annotation.color || void 0),
|
|
32
|
+
IRT: mainAnnRef,
|
|
33
|
+
RT: PDFName.of("R"),
|
|
34
|
+
NM: PDFString.of(comment.id),
|
|
35
|
+
Open: false
|
|
36
|
+
});
|
|
37
|
+
const replyAnnRef = context.register(replyAnn);
|
|
38
|
+
this.addAnnotationToPage(page, replyAnnRef);
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import { PDFName, PDFNumber, PDFString } from "pdf-lib";
|
|
2
|
+
import { convertKonvaRectToPdfRect, rgbToPdfColor, stringToPDFHexString } from "../engine/utils.js";
|
|
3
|
+
import { AnnotationParser } from "./parse.js";
|
|
4
|
+
const UNKNOWN_USER = "Unknown user";
|
|
5
|
+
export class FreeTextParser extends AnnotationParser {
|
|
6
|
+
async parse() {
|
|
7
|
+
const { annotation, page, pdfDoc } = this;
|
|
8
|
+
const [x1, , , y2] = convertKonvaRectToPdfRect(annotation.konvaClientRect, page.getHeight());
|
|
9
|
+
const context = pdfDoc.context;
|
|
10
|
+
const pageWidth = page.getWidth();
|
|
11
|
+
const pageHeight = page.getHeight();
|
|
12
|
+
const iconSize = 20;
|
|
13
|
+
const xLeft = Math.max(0, Math.min(x1, pageWidth - iconSize));
|
|
14
|
+
const yTop = Math.min(pageHeight, Math.max(y2, iconSize));
|
|
15
|
+
const yBottom = yTop - iconSize;
|
|
16
|
+
const rect = [
|
|
17
|
+
PDFNumber.of(xLeft),
|
|
18
|
+
PDFNumber.of(yBottom),
|
|
19
|
+
PDFNumber.of(xLeft + iconSize),
|
|
20
|
+
PDFNumber.of(yTop)
|
|
21
|
+
];
|
|
22
|
+
const mainAnn = context.obj({
|
|
23
|
+
Type: PDFName.of("Annot"),
|
|
24
|
+
Subtype: PDFName.of("Text"),
|
|
25
|
+
Rect: rect,
|
|
26
|
+
NM: PDFString.of(annotation.id),
|
|
27
|
+
Contents: stringToPDFHexString(annotation.contentsObj?.text || ""),
|
|
28
|
+
Name: PDFName.of("Comment"),
|
|
29
|
+
T: stringToPDFHexString(annotation.title || UNKNOWN_USER),
|
|
30
|
+
M: PDFString.of(annotation.date || ""),
|
|
31
|
+
C: rgbToPdfColor(annotation.color || void 0),
|
|
32
|
+
Open: false
|
|
33
|
+
});
|
|
34
|
+
const mainAnnRef = context.register(mainAnn);
|
|
35
|
+
this.addAnnotationToPage(page, mainAnnRef);
|
|
36
|
+
for (const comment of annotation.comments || []) {
|
|
37
|
+
const replyAnn = context.obj({
|
|
38
|
+
Type: PDFName.of("Annot"),
|
|
39
|
+
Subtype: PDFName.of("Text"),
|
|
40
|
+
Rect: rect,
|
|
41
|
+
Contents: stringToPDFHexString(comment.content),
|
|
42
|
+
T: stringToPDFHexString(comment.title || UNKNOWN_USER),
|
|
43
|
+
M: PDFString.of(comment.date || ""),
|
|
44
|
+
C: rgbToPdfColor(annotation.color || void 0),
|
|
45
|
+
IRT: mainAnnRef,
|
|
46
|
+
RT: PDFName.of("R"),
|
|
47
|
+
NM: PDFString.of(comment.id),
|
|
48
|
+
Open: false
|
|
49
|
+
});
|
|
50
|
+
const replyAnnRef = context.register(replyAnn);
|
|
51
|
+
this.addAnnotationToPage(page, replyAnnRef);
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
}
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
import { PDFName, PDFNumber, PDFRawStream, PDFString } from "pdf-lib";
|
|
2
|
+
import { convertKonvaRectToPdfRect, rgbToPdfColor, stringToPDFHexString } from "../engine/utils.js";
|
|
3
|
+
import { AnnotationParser } from "./parse.js";
|
|
4
|
+
const UNKNOWN_USER = "Unknown user";
|
|
5
|
+
function formatPdfNumber(value) {
|
|
6
|
+
if (!Number.isFinite(value)) return "0";
|
|
7
|
+
return Number(value.toFixed(3)).toString();
|
|
8
|
+
}
|
|
9
|
+
function parseHighlightRects(konvaString) {
|
|
10
|
+
const konvaGroup = JSON.parse(konvaString);
|
|
11
|
+
const groupX = konvaGroup.attrs?.x ?? 0;
|
|
12
|
+
const groupY = konvaGroup.attrs?.y ?? 0;
|
|
13
|
+
const scaleX = konvaGroup.attrs?.scaleX ?? 1;
|
|
14
|
+
const scaleY = konvaGroup.attrs?.scaleY ?? 1;
|
|
15
|
+
const rects = (konvaGroup.children ?? []).filter((item) => item.className === "Rect");
|
|
16
|
+
return rects.map((rect) => {
|
|
17
|
+
const x = groupX + (rect.attrs?.x ?? 0) * scaleX;
|
|
18
|
+
const y = groupY + (rect.attrs?.y ?? 0) * scaleY;
|
|
19
|
+
const width = (rect.attrs?.width ?? 0) * scaleX;
|
|
20
|
+
const height = (rect.attrs?.height ?? 0) * scaleY;
|
|
21
|
+
return { x, y, width, height };
|
|
22
|
+
}).filter((rect) => rect.width > 0 && rect.height > 0);
|
|
23
|
+
}
|
|
24
|
+
function buildQuadPoints(rects, pageHeight) {
|
|
25
|
+
const quadPoints = [];
|
|
26
|
+
for (const rect of rects) {
|
|
27
|
+
const x = rect.x;
|
|
28
|
+
const y = rect.y;
|
|
29
|
+
const width = rect.width;
|
|
30
|
+
const height = rect.height;
|
|
31
|
+
const x1 = x;
|
|
32
|
+
const y1 = pageHeight - y;
|
|
33
|
+
const x2 = x + width;
|
|
34
|
+
const y2 = pageHeight - (y + height);
|
|
35
|
+
quadPoints.push(x1, y1, x2, y1, x1, y2, x2, y2);
|
|
36
|
+
}
|
|
37
|
+
return quadPoints;
|
|
38
|
+
}
|
|
39
|
+
export class HighlightParser extends AnnotationParser {
|
|
40
|
+
async parse() {
|
|
41
|
+
const { annotation, page, pdfDoc } = this;
|
|
42
|
+
const context = pdfDoc.context;
|
|
43
|
+
const pageHeight = page.getHeight();
|
|
44
|
+
let highlightRects = [];
|
|
45
|
+
try {
|
|
46
|
+
highlightRects = parseHighlightRects(annotation.konvaString);
|
|
47
|
+
} catch {
|
|
48
|
+
console.warn(`Invalid konva JSON for Highlight annotation ${annotation.id}`);
|
|
49
|
+
}
|
|
50
|
+
if (highlightRects.length === 0) {
|
|
51
|
+
highlightRects = [{
|
|
52
|
+
x: annotation.konvaClientRect.x,
|
|
53
|
+
y: annotation.konvaClientRect.y,
|
|
54
|
+
width: annotation.konvaClientRect.width,
|
|
55
|
+
height: annotation.konvaClientRect.height
|
|
56
|
+
}];
|
|
57
|
+
}
|
|
58
|
+
const quadPoints = buildQuadPoints(highlightRects, pageHeight);
|
|
59
|
+
const rect = convertKonvaRectToPdfRect(annotation.konvaClientRect, pageHeight);
|
|
60
|
+
const color = rgbToPdfColor(annotation.color || void 0);
|
|
61
|
+
const [x1, y1, x2, y2] = rect;
|
|
62
|
+
const appearanceWidth = Math.max(1, x2 - x1);
|
|
63
|
+
const appearanceHeight = Math.max(1, y2 - y1);
|
|
64
|
+
const [r, g, b] = color;
|
|
65
|
+
const commands = [
|
|
66
|
+
"q",
|
|
67
|
+
"/GS1 gs",
|
|
68
|
+
`${formatPdfNumber(r)} ${formatPdfNumber(g)} ${formatPdfNumber(b)} rg`
|
|
69
|
+
];
|
|
70
|
+
for (const highlightRect of highlightRects) {
|
|
71
|
+
const pageX = highlightRect.x;
|
|
72
|
+
const pageY = pageHeight - highlightRect.y - highlightRect.height;
|
|
73
|
+
const localX = pageX - x1;
|
|
74
|
+
const localY = pageY - y1;
|
|
75
|
+
commands.push(
|
|
76
|
+
`${formatPdfNumber(localX)} ${formatPdfNumber(localY)} ${formatPdfNumber(highlightRect.width)} ${formatPdfNumber(highlightRect.height)} re`,
|
|
77
|
+
"f"
|
|
78
|
+
);
|
|
79
|
+
}
|
|
80
|
+
commands.push("Q");
|
|
81
|
+
const appearanceStream = PDFRawStream.of(
|
|
82
|
+
context.obj({
|
|
83
|
+
Type: PDFName.of("XObject"),
|
|
84
|
+
Subtype: PDFName.of("Form"),
|
|
85
|
+
BBox: context.obj([0, 0, appearanceWidth, appearanceHeight]),
|
|
86
|
+
Resources: context.obj({
|
|
87
|
+
ExtGState: context.obj({
|
|
88
|
+
GS1: context.obj({
|
|
89
|
+
Type: PDFName.of("ExtGState"),
|
|
90
|
+
ca: PDFNumber.of(0.35),
|
|
91
|
+
CA: PDFNumber.of(0.35),
|
|
92
|
+
BM: PDFName.of("Multiply")
|
|
93
|
+
})
|
|
94
|
+
})
|
|
95
|
+
})
|
|
96
|
+
}),
|
|
97
|
+
new TextEncoder().encode(commands.join("\n"))
|
|
98
|
+
);
|
|
99
|
+
const appearanceRef = context.register(appearanceStream);
|
|
100
|
+
const ap = context.obj({ N: appearanceRef });
|
|
101
|
+
const mainAnn = context.obj({
|
|
102
|
+
Type: PDFName.of("Annot"),
|
|
103
|
+
Subtype: PDFName.of("Highlight"),
|
|
104
|
+
Rect: rect,
|
|
105
|
+
QuadPoints: context.obj(quadPoints),
|
|
106
|
+
C: context.obj(color),
|
|
107
|
+
T: stringToPDFHexString(annotation.title || UNKNOWN_USER),
|
|
108
|
+
Contents: stringToPDFHexString(annotation.contentsObj?.text || ""),
|
|
109
|
+
M: PDFString.of(annotation.date || ""),
|
|
110
|
+
NM: PDFString.of(annotation.id),
|
|
111
|
+
CA: PDFNumber.of(0.35),
|
|
112
|
+
AP: ap
|
|
113
|
+
});
|
|
114
|
+
const mainAnnRef = context.register(mainAnn);
|
|
115
|
+
this.addAnnotationToPage(page, mainAnnRef);
|
|
116
|
+
for (const comment of annotation.comments || []) {
|
|
117
|
+
const replyAnn = context.obj({
|
|
118
|
+
Type: PDFName.of("Annot"),
|
|
119
|
+
Subtype: PDFName.of("Text"),
|
|
120
|
+
Rect: convertKonvaRectToPdfRect(annotation.konvaClientRect, pageHeight),
|
|
121
|
+
Contents: stringToPDFHexString(comment.content),
|
|
122
|
+
T: stringToPDFHexString(comment.title || UNKNOWN_USER),
|
|
123
|
+
M: PDFString.of(comment.date || ""),
|
|
124
|
+
C: rgbToPdfColor(annotation.color || void 0),
|
|
125
|
+
IRT: mainAnnRef,
|
|
126
|
+
RT: PDFName.of("R"),
|
|
127
|
+
NM: PDFString.of(comment.id),
|
|
128
|
+
Open: false
|
|
129
|
+
});
|
|
130
|
+
const replyAnnRef = context.register(replyAnn);
|
|
131
|
+
this.addAnnotationToPage(page, replyAnnRef);
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
}
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
import { PDFName, PDFNumber, PDFRawStream, PDFString } from "pdf-lib";
|
|
2
|
+
import { convertKonvaRectToPdfRect, rgbToPdfColor, stringToPDFHexString } from "../engine/utils.js";
|
|
3
|
+
import { AnnotationParser } from "./parse.js";
|
|
4
|
+
const UNKNOWN_USER = "Unknown user";
|
|
5
|
+
function formatPdfNumber(value) {
|
|
6
|
+
if (!Number.isFinite(value)) return "0";
|
|
7
|
+
return Number(value.toFixed(3)).toString();
|
|
8
|
+
}
|
|
9
|
+
export class InkParser extends AnnotationParser {
|
|
10
|
+
async parse() {
|
|
11
|
+
const { annotation, page, pdfDoc } = this;
|
|
12
|
+
const context = pdfDoc.context;
|
|
13
|
+
const pageHeight = page.getHeight();
|
|
14
|
+
let konvaGroup;
|
|
15
|
+
try {
|
|
16
|
+
konvaGroup = JSON.parse(annotation.konvaString);
|
|
17
|
+
} catch {
|
|
18
|
+
console.warn(`Invalid konva JSON for Ink annotation ${annotation.id}`);
|
|
19
|
+
return;
|
|
20
|
+
}
|
|
21
|
+
const lines = (konvaGroup.children ?? []).filter((item) => item.className === "Line");
|
|
22
|
+
const groupX = konvaGroup.attrs?.x || 0;
|
|
23
|
+
const groupY = konvaGroup.attrs?.y || 0;
|
|
24
|
+
const scaleX = konvaGroup.attrs?.scaleX || 1;
|
|
25
|
+
const scaleY = konvaGroup.attrs?.scaleY || 1;
|
|
26
|
+
const transformedPaths = [];
|
|
27
|
+
for (const line of lines) {
|
|
28
|
+
const points = line.attrs?.points ?? [];
|
|
29
|
+
if (points.length < 4) continue;
|
|
30
|
+
const transformedPoints = [];
|
|
31
|
+
for (let i = 0; i < points.length; i += 2) {
|
|
32
|
+
const x = groupX + points[i] * scaleX;
|
|
33
|
+
const y = groupY + points[i + 1] * scaleY;
|
|
34
|
+
transformedPoints.push(x, pageHeight - y);
|
|
35
|
+
}
|
|
36
|
+
transformedPaths.push(transformedPoints);
|
|
37
|
+
}
|
|
38
|
+
if (transformedPaths.length === 0) {
|
|
39
|
+
console.warn(`Ink annotation ${annotation.id} has no drawable paths`);
|
|
40
|
+
return;
|
|
41
|
+
}
|
|
42
|
+
const inkList = context.obj(
|
|
43
|
+
transformedPaths.map((path) => context.obj(path))
|
|
44
|
+
);
|
|
45
|
+
const firstLine = lines[0]?.attrs || {};
|
|
46
|
+
const strokeWidth = firstLine.strokeWidth ?? 1;
|
|
47
|
+
const opacity = firstLine.opacity ?? 1;
|
|
48
|
+
const color = firstLine.stroke ?? annotation.color ?? "rgb(255, 0, 0)";
|
|
49
|
+
const [r, g, b] = rgbToPdfColor(color);
|
|
50
|
+
const bs = context.obj({
|
|
51
|
+
W: PDFNumber.of(strokeWidth),
|
|
52
|
+
S: PDFName.of("S")
|
|
53
|
+
});
|
|
54
|
+
const rect = convertKonvaRectToPdfRect(annotation.konvaClientRect, pageHeight);
|
|
55
|
+
const [x1, y1, x2, y2] = rect;
|
|
56
|
+
const appearanceWidth = Math.max(1, x2 - x1);
|
|
57
|
+
const appearanceHeight = Math.max(1, y2 - y1);
|
|
58
|
+
const commands = [
|
|
59
|
+
"q",
|
|
60
|
+
`${formatPdfNumber(r)} ${formatPdfNumber(g)} ${formatPdfNumber(b)} RG`,
|
|
61
|
+
`${formatPdfNumber(Math.max(0.1, strokeWidth))} w`,
|
|
62
|
+
"1 J",
|
|
63
|
+
"1 j"
|
|
64
|
+
];
|
|
65
|
+
for (const path of transformedPaths) {
|
|
66
|
+
if (path.length < 4) continue;
|
|
67
|
+
commands.push(
|
|
68
|
+
`${formatPdfNumber(path[0] - x1)} ${formatPdfNumber(path[1] - y1)} m`
|
|
69
|
+
);
|
|
70
|
+
for (let i = 2; i < path.length; i += 2) {
|
|
71
|
+
commands.push(
|
|
72
|
+
`${formatPdfNumber(path[i] - x1)} ${formatPdfNumber(path[i + 1] - y1)} l`
|
|
73
|
+
);
|
|
74
|
+
}
|
|
75
|
+
commands.push("S");
|
|
76
|
+
}
|
|
77
|
+
commands.push("Q");
|
|
78
|
+
const appearanceStream = PDFRawStream.of(
|
|
79
|
+
context.obj({
|
|
80
|
+
Type: PDFName.of("XObject"),
|
|
81
|
+
Subtype: PDFName.of("Form"),
|
|
82
|
+
BBox: context.obj([0, 0, appearanceWidth, appearanceHeight]),
|
|
83
|
+
Resources: context.obj({})
|
|
84
|
+
}),
|
|
85
|
+
new TextEncoder().encode(commands.join("\n"))
|
|
86
|
+
);
|
|
87
|
+
const appearanceRef = context.register(appearanceStream);
|
|
88
|
+
const ap = context.obj({ N: appearanceRef });
|
|
89
|
+
const mainAnn = context.obj({
|
|
90
|
+
Type: PDFName.of("Annot"),
|
|
91
|
+
Subtype: PDFName.of("Ink"),
|
|
92
|
+
Rect: rect,
|
|
93
|
+
InkList: inkList,
|
|
94
|
+
C: context.obj([PDFNumber.of(r), PDFNumber.of(g), PDFNumber.of(b)]),
|
|
95
|
+
T: stringToPDFHexString(annotation.title || UNKNOWN_USER),
|
|
96
|
+
Contents: stringToPDFHexString(annotation.contentsObj?.text || ""),
|
|
97
|
+
M: PDFString.of(annotation.date || ""),
|
|
98
|
+
NM: PDFString.of(annotation.id),
|
|
99
|
+
Border: context.obj([0, 0, 0]),
|
|
100
|
+
BS: bs,
|
|
101
|
+
CA: PDFNumber.of(opacity),
|
|
102
|
+
AP: ap
|
|
103
|
+
});
|
|
104
|
+
const mainAnnRef = context.register(mainAnn);
|
|
105
|
+
this.addAnnotationToPage(page, mainAnnRef);
|
|
106
|
+
for (const comment of annotation.comments || []) {
|
|
107
|
+
const replyAnn = context.obj({
|
|
108
|
+
Type: PDFName.of("Annot"),
|
|
109
|
+
Subtype: PDFName.of("Text"),
|
|
110
|
+
Rect: convertKonvaRectToPdfRect(annotation.konvaClientRect, pageHeight),
|
|
111
|
+
Contents: stringToPDFHexString(comment.content),
|
|
112
|
+
T: stringToPDFHexString(comment.title || UNKNOWN_USER),
|
|
113
|
+
M: PDFString.of(comment.date || ""),
|
|
114
|
+
C: context.obj([PDFNumber.of(r), PDFNumber.of(g), PDFNumber.of(b)]),
|
|
115
|
+
IRT: mainAnnRef,
|
|
116
|
+
RT: PDFName.of("R"),
|
|
117
|
+
NM: PDFString.of(comment.id),
|
|
118
|
+
Open: false
|
|
119
|
+
});
|
|
120
|
+
const replyAnnRef = context.register(replyAnn);
|
|
121
|
+
this.addAnnotationToPage(page, replyAnnRef);
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
}
|