kviewer 0.0.4 → 0.0.5
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 +3 -0
- package/dist/module.json +2 -2
- package/dist/runtime/annotation/engine/painter.d.ts +7 -0
- package/dist/runtime/annotation/engine/painter.js +14 -0
- package/dist/runtime/annotation/engine/store.d.ts +5 -0
- package/dist/runtime/annotation/engine/store.js +17 -0
- package/dist/runtime/annotation/engine/tools/free-text.js +3 -1
- package/dist/runtime/annotation/engine/types.d.ts +20 -0
- package/dist/runtime/annotation/pdf-export/export.d.ts +4 -1
- package/dist/runtime/annotation/pdf-export/export.js +101 -4
- package/dist/runtime/annotation/pdf-export/parse_freetext.js +106 -18
- package/dist/runtime/annotation/pdf-export/parse_highlight.js +22 -6
- package/dist/runtime/annotation/pdf-export/parse_ink.js +32 -3
- package/dist/runtime/annotation/pdf-export/parse_line.js +133 -13
- package/dist/runtime/annotation/pdf-import/decode.d.ts +21 -0
- package/dist/runtime/annotation/pdf-import/decode.js +134 -0
- package/dist/runtime/annotation/pdf-import/decode_circle.d.ts +3 -0
- package/dist/runtime/annotation/pdf-import/decode_circle.js +36 -0
- package/dist/runtime/annotation/pdf-import/decode_freetext.d.ts +3 -0
- package/dist/runtime/annotation/pdf-import/decode_freetext.js +87 -0
- package/dist/runtime/annotation/pdf-import/decode_highlight.d.ts +3 -0
- package/dist/runtime/annotation/pdf-import/decode_highlight.js +96 -0
- package/dist/runtime/annotation/pdf-import/decode_ink.d.ts +3 -0
- package/dist/runtime/annotation/pdf-import/decode_ink.js +48 -0
- package/dist/runtime/annotation/pdf-import/decode_line.d.ts +3 -0
- package/dist/runtime/annotation/pdf-import/decode_line.js +48 -0
- package/dist/runtime/annotation/pdf-import/decode_square.d.ts +3 -0
- package/dist/runtime/annotation/pdf-import/decode_square.js +39 -0
- package/dist/runtime/annotation/pdf-import/decode_stamp.d.ts +3 -0
- package/dist/runtime/annotation/pdf-import/decode_stamp.js +38 -0
- package/dist/runtime/annotation/pdf-import/decode_text.d.ts +3 -0
- package/dist/runtime/annotation/pdf-import/decode_text.js +33 -0
- package/dist/runtime/annotation/pdf-import/extract_stamp_appearance.d.ts +23 -0
- package/dist/runtime/annotation/pdf-import/extract_stamp_appearance.js +168 -0
- package/dist/runtime/annotation/pdf-import/types.d.ts +57 -0
- package/dist/runtime/annotation/pdf-import/types.js +25 -0
- package/dist/runtime/annotation/pdf-import/utils.d.ts +48 -0
- package/dist/runtime/annotation/pdf-import/utils.js +250 -0
- package/dist/runtime/assets/kviewer.css +1 -1
- package/dist/runtime/components/AnnotationToolbar.vue +1 -0
- package/dist/runtime/components/FloatingPageIndicator.vue +4 -1
- package/dist/runtime/components/PdfPage.vue +27 -1
- package/dist/runtime/components/Viewer.d.vue.ts +12 -1
- package/dist/runtime/components/Viewer.vue +112 -34
- package/dist/runtime/components/Viewer.vue.d.ts +12 -1
- package/dist/runtime/components/ViewerBar.vue +8 -3
- package/dist/runtime/components/ViewerTabs.d.vue.ts +16 -1
- package/dist/runtime/components/ViewerTabs.vue +42 -12
- package/dist/runtime/components/ViewerTabs.vue.d.ts +16 -1
- package/dist/runtime/components/form-fields/FormCheckbox.vue +37 -1
- package/dist/runtime/components/tools/ActionTools.vue +3 -0
- package/dist/runtime/components/tools/PageInfo.vue +1 -1
- package/dist/runtime/components/tools/SearchTool.vue +3 -1
- package/dist/runtime/components/tools/ZoomControls.vue +3 -0
- package/dist/runtime/composables/shape-detection-utils.d.ts +38 -0
- package/dist/runtime/composables/shape-detection-utils.js +50 -0
- package/dist/runtime/composables/useFormFields.d.ts +3 -1
- package/dist/runtime/composables/useFormFields.js +47 -0
- package/dist/runtime/composables/useShapeDetection.d.ts +24 -0
- package/dist/runtime/composables/useShapeDetection.js +235 -0
- package/dist/runtime/composables/useViewerState.d.ts +2 -0
- package/dist/runtime/composables/useViewerState.js +5 -1
- package/dist/runtime/plugin.d.ts +1 -1
- package/package.json +28 -5
|
@@ -0,0 +1,250 @@
|
|
|
1
|
+
import Konva from "konva";
|
|
2
|
+
import { annotationDefinitions } from "../engine/config.js";
|
|
3
|
+
import { SHAPE_GROUP_NAME } from "../engine/const.js";
|
|
4
|
+
import {
|
|
5
|
+
PdfjsAnnotationEditorType
|
|
6
|
+
} from "../engine/types.js";
|
|
7
|
+
import { formatTimestamp } from "../engine/utils.js";
|
|
8
|
+
import {
|
|
9
|
+
asBorderStyle,
|
|
10
|
+
asPoint,
|
|
11
|
+
asTextObject,
|
|
12
|
+
isNumber,
|
|
13
|
+
isRecord,
|
|
14
|
+
isRawPdfAnnotation,
|
|
15
|
+
isString
|
|
16
|
+
} from "./types.js";
|
|
17
|
+
const UNKNOWN_USER = "Unknown user";
|
|
18
|
+
function clampColor(value) {
|
|
19
|
+
if (!Number.isFinite(value)) return 0;
|
|
20
|
+
return Math.max(0, Math.min(255, Math.round(value)));
|
|
21
|
+
}
|
|
22
|
+
function normalizeColorComponent(value) {
|
|
23
|
+
if (value >= 0 && value <= 1) return clampColor(value * 255);
|
|
24
|
+
return clampColor(value);
|
|
25
|
+
}
|
|
26
|
+
function getDefinition(type) {
|
|
27
|
+
return annotationDefinitions.find((def) => def.type === type) ?? null;
|
|
28
|
+
}
|
|
29
|
+
function getIndexedNumber(input, index) {
|
|
30
|
+
if (Array.isArray(input) || ArrayBuffer.isView(input)) {
|
|
31
|
+
const value = input[index];
|
|
32
|
+
return isNumber(value) ? value : null;
|
|
33
|
+
}
|
|
34
|
+
if (isRecord(input)) {
|
|
35
|
+
const value = input[String(index)];
|
|
36
|
+
return isNumber(value) ? value : null;
|
|
37
|
+
}
|
|
38
|
+
return null;
|
|
39
|
+
}
|
|
40
|
+
function toNumericTuple(input, size) {
|
|
41
|
+
const tuple = [];
|
|
42
|
+
for (let index = 0; index < size; index++) {
|
|
43
|
+
const value = getIndexedNumber(input, index);
|
|
44
|
+
if (!isNumber(value)) return null;
|
|
45
|
+
tuple.push(value);
|
|
46
|
+
}
|
|
47
|
+
return tuple;
|
|
48
|
+
}
|
|
49
|
+
function toCoordinateList(input) {
|
|
50
|
+
if (!Array.isArray(input) && !ArrayBuffer.isView(input)) return null;
|
|
51
|
+
const list = input;
|
|
52
|
+
const values = [];
|
|
53
|
+
for (let index = 0; index < list.length; index++) {
|
|
54
|
+
const value = list[index];
|
|
55
|
+
if (!isNumber(value)) return null;
|
|
56
|
+
values.push(value);
|
|
57
|
+
}
|
|
58
|
+
return values;
|
|
59
|
+
}
|
|
60
|
+
export function colorArrayToRgb(input) {
|
|
61
|
+
const tuple = toNumericTuple(input, 3);
|
|
62
|
+
if (!tuple) return null;
|
|
63
|
+
const [r, g, b] = tuple;
|
|
64
|
+
return `rgb(${normalizeColorComponent(r)}, ${normalizeColorComponent(g)}, ${normalizeColorComponent(b)})`;
|
|
65
|
+
}
|
|
66
|
+
export function getAnnotationColor(annotation, type) {
|
|
67
|
+
const decoded = colorArrayToRgb(annotation.color);
|
|
68
|
+
if (decoded) return decoded;
|
|
69
|
+
const fallback = getDefinition(type)?.style?.color;
|
|
70
|
+
return fallback ?? "rgb(255, 0, 0)";
|
|
71
|
+
}
|
|
72
|
+
export function getAnnotationTitle(annotation) {
|
|
73
|
+
const title = asTextObject(annotation.titleObj)?.str;
|
|
74
|
+
if (isString(title) && title.length > 0) return title;
|
|
75
|
+
return UNKNOWN_USER;
|
|
76
|
+
}
|
|
77
|
+
export function getAnnotationText(annotation) {
|
|
78
|
+
const text = asTextObject(annotation.contentsObj)?.str;
|
|
79
|
+
if (isString(text)) return text;
|
|
80
|
+
return "";
|
|
81
|
+
}
|
|
82
|
+
export function getFreeTextContent(annotation) {
|
|
83
|
+
if (isString(annotation.textContent)) return annotation.textContent;
|
|
84
|
+
if (Array.isArray(annotation.textContent)) {
|
|
85
|
+
const lines = annotation.textContent.filter((line) => isString(line));
|
|
86
|
+
if (lines.length > 0) return lines.join("\n");
|
|
87
|
+
}
|
|
88
|
+
return getAnnotationText(annotation);
|
|
89
|
+
}
|
|
90
|
+
export function getAnnotationDate(annotation) {
|
|
91
|
+
if (isString(annotation.modificationDate) && annotation.modificationDate.length > 0) {
|
|
92
|
+
return annotation.modificationDate;
|
|
93
|
+
}
|
|
94
|
+
return formatTimestamp(Date.now());
|
|
95
|
+
}
|
|
96
|
+
export function getComments(annotation, allAnnotations) {
|
|
97
|
+
const comments = [];
|
|
98
|
+
for (const candidate of allAnnotations) {
|
|
99
|
+
if (!isRawPdfAnnotation(candidate)) continue;
|
|
100
|
+
if (candidate.annotationType !== 1) continue;
|
|
101
|
+
if (candidate.inReplyTo !== annotation.id) continue;
|
|
102
|
+
comments.push({
|
|
103
|
+
id: candidate.id,
|
|
104
|
+
title: getAnnotationTitle(candidate),
|
|
105
|
+
date: getAnnotationDate(candidate),
|
|
106
|
+
content: getAnnotationText(candidate)
|
|
107
|
+
});
|
|
108
|
+
}
|
|
109
|
+
return comments;
|
|
110
|
+
}
|
|
111
|
+
export function rectFromPdfRect(input, pageHeight) {
|
|
112
|
+
if (!Array.isArray(input) || input.length < 4) return null;
|
|
113
|
+
const [x1, y1, x2, y2] = input;
|
|
114
|
+
if (!isNumber(x1) || !isNumber(y1) || !isNumber(x2) || !isNumber(y2)) return null;
|
|
115
|
+
return {
|
|
116
|
+
x: x1,
|
|
117
|
+
y: pageHeight - y2,
|
|
118
|
+
width: x2 - x1,
|
|
119
|
+
height: y2 - y1
|
|
120
|
+
};
|
|
121
|
+
}
|
|
122
|
+
export function pointFromPdfPoint(input, pageHeight) {
|
|
123
|
+
const pair = toNumericTuple(input, 2);
|
|
124
|
+
if (pair) return { x: pair[0], y: pageHeight - pair[1] };
|
|
125
|
+
const point = asPoint(input);
|
|
126
|
+
if (!point || !isNumber(point.x) || !isNumber(point.y)) return null;
|
|
127
|
+
return { x: point.x, y: pageHeight - point.y };
|
|
128
|
+
}
|
|
129
|
+
export function localPointFromPdfPoint(input) {
|
|
130
|
+
const pair = toNumericTuple(input, 2);
|
|
131
|
+
if (pair) return { x: pair[0], y: pair[1] };
|
|
132
|
+
const point = asPoint(input);
|
|
133
|
+
if (!point || !isNumber(point.x) || !isNumber(point.y)) return null;
|
|
134
|
+
return { x: point.x, y: point.y };
|
|
135
|
+
}
|
|
136
|
+
export function rectFromQuadPoints(input, pageHeight) {
|
|
137
|
+
const numeric = toCoordinateList(input);
|
|
138
|
+
if (numeric && numeric.length >= 8) {
|
|
139
|
+
const p02 = pointFromPdfPoint([numeric[0], numeric[1]], pageHeight);
|
|
140
|
+
const p12 = pointFromPdfPoint([numeric[2], numeric[3]], pageHeight);
|
|
141
|
+
const p32 = pointFromPdfPoint([numeric[6], numeric[7]], pageHeight);
|
|
142
|
+
if (!p02 || !p12 || !p32) return null;
|
|
143
|
+
return {
|
|
144
|
+
x: p02.x,
|
|
145
|
+
y: p02.y,
|
|
146
|
+
width: p12.x - p02.x,
|
|
147
|
+
height: p12.y - p32.y
|
|
148
|
+
};
|
|
149
|
+
}
|
|
150
|
+
if (!Array.isArray(input) || input.length < 4) return null;
|
|
151
|
+
const p0 = pointFromPdfPoint(input[0], pageHeight);
|
|
152
|
+
const p1 = pointFromPdfPoint(input[1], pageHeight);
|
|
153
|
+
const p3 = pointFromPdfPoint(input[3], pageHeight);
|
|
154
|
+
if (!p0 || !p1 || !p3) return null;
|
|
155
|
+
return {
|
|
156
|
+
x: p0.x,
|
|
157
|
+
y: p0.y,
|
|
158
|
+
width: p1.x - p0.x,
|
|
159
|
+
height: p1.y - p3.y
|
|
160
|
+
};
|
|
161
|
+
}
|
|
162
|
+
export function pointsFromInkList(input, pageHeight) {
|
|
163
|
+
const numeric = toCoordinateList(input);
|
|
164
|
+
if (numeric) {
|
|
165
|
+
const points2 = [];
|
|
166
|
+
for (let index = 0; index + 1 < numeric.length; index += 2) {
|
|
167
|
+
points2.push(numeric[index], pageHeight - numeric[index + 1]);
|
|
168
|
+
}
|
|
169
|
+
return points2;
|
|
170
|
+
}
|
|
171
|
+
if (!Array.isArray(input)) return [];
|
|
172
|
+
const points = [];
|
|
173
|
+
for (const item of input) {
|
|
174
|
+
const converted = pointFromPdfPoint(item, pageHeight);
|
|
175
|
+
if (!converted) continue;
|
|
176
|
+
points.push(converted.x, converted.y);
|
|
177
|
+
}
|
|
178
|
+
return points;
|
|
179
|
+
}
|
|
180
|
+
export function linePointsFromCoordinates(input, pageHeight) {
|
|
181
|
+
const tuple = toNumericTuple(input, 4);
|
|
182
|
+
if (!tuple) return null;
|
|
183
|
+
const [x, y, x1, y1] = tuple;
|
|
184
|
+
return [x, pageHeight - y, x1, pageHeight - y1];
|
|
185
|
+
}
|
|
186
|
+
export function normalizedStrokeWidth(annotation, fallback = 1, options = {}) {
|
|
187
|
+
const borderStyle = asBorderStyle(annotation.borderStyle);
|
|
188
|
+
const width = isNumber(borderStyle?.width) && borderStyle.width > 0 ? borderStyle.width : null;
|
|
189
|
+
const rawWidth = isNumber(borderStyle?.rawWidth) && borderStyle.rawWidth > 0 ? borderStyle.rawWidth : null;
|
|
190
|
+
const preferRawWidth = options.preferRawWidth === true;
|
|
191
|
+
const resolvedWidth = preferRawWidth && isNumber(rawWidth) && (!isNumber(width) || rawWidth > width) ? rawWidth : width ?? rawWidth;
|
|
192
|
+
if (!isNumber(resolvedWidth)) return fallback;
|
|
193
|
+
return resolvedWidth === 1 ? 2 : Math.max(0.1, resolvedWidth);
|
|
194
|
+
}
|
|
195
|
+
export function dashedBorder(annotation) {
|
|
196
|
+
const borderStyle = asBorderStyle(annotation.borderStyle);
|
|
197
|
+
if (!borderStyle) return [];
|
|
198
|
+
const style = borderStyle.style;
|
|
199
|
+
const dashArray = toCoordinateList(borderStyle.dashArray);
|
|
200
|
+
if (style !== 2 || !dashArray) return [];
|
|
201
|
+
return dashArray;
|
|
202
|
+
}
|
|
203
|
+
export function hasArrowEnding(value) {
|
|
204
|
+
if (!isString(value)) return false;
|
|
205
|
+
return value.toLowerCase() !== "none";
|
|
206
|
+
}
|
|
207
|
+
export function createGhostGroup(id) {
|
|
208
|
+
return new Konva.Group({
|
|
209
|
+
draggable: false,
|
|
210
|
+
name: SHAPE_GROUP_NAME,
|
|
211
|
+
id
|
|
212
|
+
});
|
|
213
|
+
}
|
|
214
|
+
export function createAnnotationStore(params) {
|
|
215
|
+
const {
|
|
216
|
+
annotation,
|
|
217
|
+
allAnnotations,
|
|
218
|
+
pageNumber,
|
|
219
|
+
type,
|
|
220
|
+
group,
|
|
221
|
+
color,
|
|
222
|
+
fontSize,
|
|
223
|
+
contentsText,
|
|
224
|
+
contentsImage,
|
|
225
|
+
overrideSubtype
|
|
226
|
+
} = params;
|
|
227
|
+
const def = getDefinition(type);
|
|
228
|
+
const pdfjsType = isNumber(annotation.annotationType) ? annotation.annotationType : def?.pdfjsAnnotationType ?? 0;
|
|
229
|
+
return {
|
|
230
|
+
id: annotation.id,
|
|
231
|
+
pageNumber,
|
|
232
|
+
konvaString: group.toJSON(),
|
|
233
|
+
konvaClientRect: group.getClientRect(),
|
|
234
|
+
title: getAnnotationTitle(annotation),
|
|
235
|
+
type,
|
|
236
|
+
color: color ?? def?.style?.color ?? null,
|
|
237
|
+
subtype: overrideSubtype ?? (isString(annotation.subtype) ? annotation.subtype : def?.subtype ?? "Text"),
|
|
238
|
+
fontSize: fontSize ?? def?.style?.fontSize ?? null,
|
|
239
|
+
pdfjsType,
|
|
240
|
+
pdfjsEditorType: def?.pdfjsEditorType ?? PdfjsAnnotationEditorType.NONE,
|
|
241
|
+
date: getAnnotationDate(annotation),
|
|
242
|
+
contentsObj: {
|
|
243
|
+
text: contentsText ?? getAnnotationText(annotation),
|
|
244
|
+
image: contentsImage
|
|
245
|
+
},
|
|
246
|
+
comments: getComments(annotation, allAnnotations),
|
|
247
|
+
resizable: def?.resizable ?? false,
|
|
248
|
+
draggable: def?.draggable ?? false
|
|
249
|
+
};
|
|
250
|
+
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
.kviewer-text-layer{left:0;line-height:1;overflow:hidden;position:absolute;top:0;-webkit-user-select:none;-moz-user-select:none;user-select:none;z-index:1}.kviewer-text-layer--interactive{cursor:text;-webkit-user-select:text;-moz-user-select:text;user-select:text}.KViewer_is_painting.KViewer_painting_type_12 .kviewer-annotation-layer,.KViewer_is_painting.KViewer_painting_type_12 .kviewer-annotation-layer *,.KViewer_is_painting.KViewer_painting_type_13 .kviewer-annotation-layer,.KViewer_is_painting.KViewer_painting_type_13 .kviewer-annotation-layer *,.KViewer_is_painting.KViewer_painting_type_5 .kviewer-annotation-layer,.KViewer_is_painting.KViewer_painting_type_5 .kviewer-annotation-layer *,.KViewer_is_painting.KViewer_painting_type_6 .kviewer-annotation-layer,.KViewer_is_painting.KViewer_painting_type_6 .kviewer-annotation-layer *,.KViewer_is_painting.KViewer_painting_type_7 .kviewer-annotation-layer,.KViewer_is_painting.KViewer_painting_type_7 .kviewer-annotation-layer *,.KViewer_is_painting.KViewer_painting_type_8 .kviewer-annotation-layer,.KViewer_is_painting.KViewer_painting_type_8 .kviewer-annotation-layer *{cursor:crosshair!important}.KViewer_is_painting.KViewer_painting_type_4 .kviewer-annotation-layer,.KViewer_is_painting.KViewer_painting_type_4 .kviewer-annotation-layer *{cursor:text!important}.KViewer_is_painting.KViewer_painting_type_11 .kviewer-annotation-layer,.KViewer_is_painting.KViewer_painting_type_11 .kviewer-annotation-layer *{cursor:crosshair!important}.KViewer_selector_hover{cursor:pointer!important}.kviewer-form-layer{left:0;overflow:hidden;position:absolute;top:0}.kviewer-form-field{box-sizing:border-box;position:absolute}.kviewer-form-input{background-color:rgba(224,232,255,.6);border:1px solid transparent;box-sizing:border-box;color:#000;font-family:inherit;height:100%;line-height:normal;margin:0;outline:none;padding:1px 2px;resize:none;width:100%}.kviewer-form-input:hover{background-color:rgba(224,232,255,.8);border-color:rgba(0,0,0,.2)}.kviewer-form-input:focus{background-color:rgba(224,232,255,.9);border-color:var(--color-primary,#3b82f6);outline:2px solid var(--color-primary,#3b82f6)}.kviewer-form-select{-webkit-appearance:auto;-moz-appearance:auto;appearance:auto;cursor:pointer}.kviewer-form-listbox{overflow-y:auto;padding:0}.kviewer-form-listbox option{padding:1px 3px}.kviewer-form-editable-combo{height:100%;position:relative;width:100%}.kviewer-form-editable-combo input{height:100%;width:100%}.kviewer-form-checkbox,.kviewer-form-radio{align-items:center;background:#fff;display:flex;height:100%;justify-content:center;width:100%}.kviewer-form-checkbox input,.kviewer-form-radio input{accent-color:var(--color-primary,#3b82f6);cursor:pointer;height:80%;margin:0;max-height:18px;max-width:18px;width:80%}.kviewer-form-button{background:linear-gradient(180deg,#f0f0f0,#d0d0d0);border:1px solid rgba(0,0,0,.3);box-sizing:border-box;cursor:pointer;font-family:inherit;font-weight:600;height:100%;padding:2px 6px;text-align:center;width:100%}.kviewer-form-button:hover{background:linear-gradient(180deg,#e8e8e8,#c8c8c8)}.kviewer-form-button:active{background:linear-gradient(180deg,#c8c8c8,#d0d0d0)}.kviewer-form-signature{align-items:center;background:rgba(255,255,200,.15);border:1px dashed rgba(0,0,0,.2);cursor:pointer;display:flex;height:100%;justify-content:center;width:100%}.kviewer-form-signature:hover{background:rgba(59,130,246,.08);border-color:var(--color-primary,#3b82f6)}.kviewer-form-signature--filled{background:transparent;border-color:transparent;border-style:solid}.kviewer-form-signature__preview{max-height:100%;max-width:100%;-o-object-fit:contain;object-fit:contain}.kviewer-form-signature__placeholder{color:rgba(0,0,0,.4);font-size:10px;pointer-events:none;-webkit-user-select:none;-moz-user-select:none;user-select:none}
|
|
1
|
+
.kviewer-text-layer{left:0;line-height:1;overflow:hidden;position:absolute;top:0;-webkit-user-select:none;-moz-user-select:none;user-select:none;z-index:1}.kviewer-text-layer--interactive{cursor:text;-webkit-user-select:text;-moz-user-select:text;user-select:text}.KViewer_is_painting.KViewer_painting_type_12 .kviewer-annotation-layer,.KViewer_is_painting.KViewer_painting_type_12 .kviewer-annotation-layer *,.KViewer_is_painting.KViewer_painting_type_13 .kviewer-annotation-layer,.KViewer_is_painting.KViewer_painting_type_13 .kviewer-annotation-layer *,.KViewer_is_painting.KViewer_painting_type_5 .kviewer-annotation-layer,.KViewer_is_painting.KViewer_painting_type_5 .kviewer-annotation-layer *,.KViewer_is_painting.KViewer_painting_type_6 .kviewer-annotation-layer,.KViewer_is_painting.KViewer_painting_type_6 .kviewer-annotation-layer *,.KViewer_is_painting.KViewer_painting_type_7 .kviewer-annotation-layer,.KViewer_is_painting.KViewer_painting_type_7 .kviewer-annotation-layer *,.KViewer_is_painting.KViewer_painting_type_8 .kviewer-annotation-layer,.KViewer_is_painting.KViewer_painting_type_8 .kviewer-annotation-layer *{cursor:crosshair!important}.KViewer_is_painting.KViewer_painting_type_4 .kviewer-annotation-layer,.KViewer_is_painting.KViewer_painting_type_4 .kviewer-annotation-layer *{cursor:text!important}.KViewer_is_painting.KViewer_painting_type_11 .kviewer-annotation-layer,.KViewer_is_painting.KViewer_painting_type_11 .kviewer-annotation-layer *{cursor:crosshair!important}.KViewer_selector_hover{cursor:pointer!important}.kviewer-form-layer{left:0;overflow:hidden;position:absolute;top:0}.kviewer-form-field{box-sizing:border-box;position:absolute}.kviewer-form-input{background-color:rgba(224,232,255,.6);border:1px solid transparent;box-sizing:border-box;color:#000;font-family:inherit;height:100%;line-height:normal;margin:0;outline:none;padding:1px 2px;resize:none;width:100%}.kviewer-form-input:hover{background-color:rgba(224,232,255,.8);border-color:rgba(0,0,0,.2)}.kviewer-form-input:focus{background-color:rgba(224,232,255,.9);border-color:var(--color-primary,#3b82f6);outline:2px solid var(--color-primary,#3b82f6)}.kviewer-form-select{-webkit-appearance:auto;-moz-appearance:auto;appearance:auto;cursor:pointer}.kviewer-form-listbox{overflow-y:auto;padding:0}.kviewer-form-listbox option{padding:1px 3px}.kviewer-form-editable-combo{height:100%;position:relative;width:100%}.kviewer-form-editable-combo input{height:100%;width:100%}.kviewer-form-checkbox,.kviewer-form-radio{align-items:center;background:#fff;box-sizing:border-box;cursor:pointer;display:flex;height:100%;justify-content:center;width:100%}.kviewer-form-checkbox input,.kviewer-form-radio input{accent-color:var(--color-primary,#3b82f6);cursor:pointer;height:80%;margin:0;max-height:18px;max-width:18px;width:80%}.kviewer-form-checkbox--detected{background:transparent}.kviewer-form-checkbox--circle{border-radius:50%}.kviewer-form-checkbox__icon{color:#1a1a1a;height:65%;width:65%}.kviewer-form-checkbox--circle .kviewer-form-checkbox__icon{height:50%;width:50%}.kviewer-form-radio input{accent-color:var(--color-primary,#3b82f6);cursor:pointer;height:80%;margin:0;max-height:18px;max-width:18px;width:80%}.kviewer-form-button{background:linear-gradient(180deg,#f0f0f0,#d0d0d0);border:1px solid rgba(0,0,0,.3);box-sizing:border-box;cursor:pointer;font-family:inherit;font-weight:600;height:100%;padding:2px 6px;text-align:center;width:100%}.kviewer-form-button:hover{background:linear-gradient(180deg,#e8e8e8,#c8c8c8)}.kviewer-form-button:active{background:linear-gradient(180deg,#c8c8c8,#d0d0d0)}.kviewer-form-signature{align-items:center;background:rgba(255,255,200,.15);border:1px dashed rgba(0,0,0,.2);cursor:pointer;display:flex;height:100%;justify-content:center;width:100%}.kviewer-form-signature:hover{background:rgba(59,130,246,.08);border-color:var(--color-primary,#3b82f6)}.kviewer-form-signature--filled{background:transparent;border-color:transparent;border-style:solid}.kviewer-form-signature__preview{max-height:100%;max-width:100%;-o-object-fit:contain;object-fit:contain}.kviewer-form-signature__placeholder{color:rgba(0,0,0,.4);font-size:10px;pointer-events:none;-webkit-user-select:none;-moz-user-select:none;user-select:none}
|
|
@@ -11,16 +11,18 @@
|
|
|
11
11
|
class="relative z-10 flex items-center gap-1 bg-elevated rounded-full px-1 py-0.5 shadow-md
|
|
12
12
|
transition-opacity duration-300"
|
|
13
13
|
:class="visible ? 'opacity-100' : 'opacity-0 pointer-events-none'"
|
|
14
|
+
data-testid="page-indicator"
|
|
14
15
|
>
|
|
15
16
|
<UButton
|
|
16
17
|
icon="i-lucide-chevron-left"
|
|
17
18
|
variant="ghost"
|
|
18
19
|
color="neutral"
|
|
19
20
|
size="xs"
|
|
21
|
+
data-testid="page-prev"
|
|
20
22
|
:disabled="state.currentPage.value <= 1"
|
|
21
23
|
@click="goToPreviousPage"
|
|
22
24
|
/>
|
|
23
|
-
<span class="text-sm text-muted tabular-nums px-1 select-none">
|
|
25
|
+
<span class="text-sm text-muted tabular-nums px-1 select-none" data-testid="page-indicator-text">
|
|
24
26
|
{{ state.currentPage.value }} / {{ state.totalPages.value }}
|
|
25
27
|
</span>
|
|
26
28
|
<UButton
|
|
@@ -28,6 +30,7 @@
|
|
|
28
30
|
variant="ghost"
|
|
29
31
|
color="neutral"
|
|
30
32
|
size="xs"
|
|
33
|
+
data-testid="page-next"
|
|
31
34
|
:disabled="state.currentPage.value >= state.totalPages.value"
|
|
32
35
|
@click="goToNextPage"
|
|
33
36
|
/>
|
|
@@ -56,8 +56,10 @@ import { useViewerState } from "../composables/useViewerState";
|
|
|
56
56
|
import { useViewerSearch } from "../composables/useViewerSearch";
|
|
57
57
|
import { usePageProxyCache } from "../composables/usePageProxyCache";
|
|
58
58
|
import { useFormFields } from "../composables/useFormFields";
|
|
59
|
+
import { useShapeDetection } from "../composables/useShapeDetection";
|
|
59
60
|
import { AnnotationType } from "../annotation/engine/types";
|
|
60
61
|
import FormFieldLayer from "./FormFieldLayer.vue";
|
|
62
|
+
const PDFJS_ANNOTATION_MODE_DISABLE = 0;
|
|
61
63
|
const props = defineProps({
|
|
62
64
|
pageNumber: { type: Number, required: true },
|
|
63
65
|
pageMeta: { type: Object, required: true },
|
|
@@ -70,6 +72,7 @@ const state = useViewerState();
|
|
|
70
72
|
const search = useViewerSearch();
|
|
71
73
|
const proxyCache = usePageProxyCache();
|
|
72
74
|
const formFields = useFormFields();
|
|
75
|
+
const shapeDetection = useShapeDetection();
|
|
73
76
|
const pageProxy = shallowRef(null);
|
|
74
77
|
let currentRenderTask = null;
|
|
75
78
|
let textLayer = null;
|
|
@@ -104,17 +107,38 @@ onMounted(async () => {
|
|
|
104
107
|
const rawAnnotations = await pageProxy.value.getAnnotations();
|
|
105
108
|
formFields.parsePageFields(props.pageNumber, rawAnnotations);
|
|
106
109
|
}
|
|
110
|
+
if (state.shapeDetection.value && pageProxy.value) {
|
|
111
|
+
const viewport = pageProxy.value.getViewport({ scale: props.scale });
|
|
112
|
+
const shapes = await shapeDetection.preprocessShapesForPage(
|
|
113
|
+
props.pageNumber,
|
|
114
|
+
pageProxy.value,
|
|
115
|
+
viewport
|
|
116
|
+
);
|
|
117
|
+
if (shapes.length > 0) {
|
|
118
|
+
formFields.registerDetectedCheckboxes(
|
|
119
|
+
props.pageNumber,
|
|
120
|
+
shapes,
|
|
121
|
+
props.scale,
|
|
122
|
+
baseHeight
|
|
123
|
+
);
|
|
124
|
+
}
|
|
125
|
+
}
|
|
107
126
|
});
|
|
108
127
|
watch(
|
|
109
128
|
() => props.scale,
|
|
110
129
|
async () => {
|
|
111
130
|
if (!pageProxy.value) return;
|
|
131
|
+
shapeDetection.clearShapeCache(props.pageNumber);
|
|
112
132
|
await renderCanvas();
|
|
113
133
|
updateTextLayer();
|
|
114
134
|
if (state.painter.value) {
|
|
115
135
|
state.painter.value.destroyCanvasForPage(props.pageNumber);
|
|
116
136
|
initKonva();
|
|
117
137
|
}
|
|
138
|
+
if (pageProxy.value) {
|
|
139
|
+
const viewport = pageProxy.value.getViewport({ scale: props.scale });
|
|
140
|
+
shapeDetection.preprocessShapesForPage(props.pageNumber, pageProxy.value, viewport);
|
|
141
|
+
}
|
|
118
142
|
}
|
|
119
143
|
);
|
|
120
144
|
async function renderCanvas() {
|
|
@@ -134,7 +158,8 @@ async function renderCanvas() {
|
|
|
134
158
|
try {
|
|
135
159
|
currentRenderTask = proxy.render({
|
|
136
160
|
canvasContext: canvas.getContext("2d"),
|
|
137
|
-
viewport
|
|
161
|
+
viewport,
|
|
162
|
+
annotationMode: PDFJS_ANNOTATION_MODE_DISABLE
|
|
138
163
|
});
|
|
139
164
|
await currentRenderTask.promise;
|
|
140
165
|
} catch (e) {
|
|
@@ -200,6 +225,7 @@ onBeforeUnmount(() => {
|
|
|
200
225
|
textLayer?.cancel();
|
|
201
226
|
textLayer = null;
|
|
202
227
|
search.unregisterPageLayer(props.pageNumber);
|
|
228
|
+
shapeDetection.clearShapeCache(props.pageNumber);
|
|
203
229
|
state.painter.value?.destroyCanvasForPage(props.pageNumber);
|
|
204
230
|
});
|
|
205
231
|
</script>
|
|
@@ -11,6 +11,8 @@ type __VLS_Props = {
|
|
|
11
11
|
zoom?: number;
|
|
12
12
|
/** When true, the viewer is in view-only mode: annotations, drawing tools, and form editing are disabled. */
|
|
13
13
|
readonly?: boolean;
|
|
14
|
+
/** When true, auto-detect checkbox-like shapes (squares, circles) and create interactive form field checkboxes. */
|
|
15
|
+
shapeDetection?: boolean;
|
|
14
16
|
/** When false, global keyboard shortcuts (e.g. Cmd+F) are suppressed. Used by ViewerTabs to prevent hidden viewers from capturing input. */
|
|
15
17
|
active?: boolean;
|
|
16
18
|
};
|
|
@@ -36,7 +38,16 @@ declare const __VLS_base: import("vue").DefineComponent<__VLS_Props, {
|
|
|
36
38
|
getKonvaCanvasState: typeof getKonvaCanvasState;
|
|
37
39
|
getFormFieldValues: () => import("../annotation/engine/types.js").FormFieldValue[];
|
|
38
40
|
setFormFieldValue: (fieldName: string, value: string | boolean | string[]) => void;
|
|
39
|
-
}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<__VLS_Props> & Readonly<{}>, {
|
|
41
|
+
}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<__VLS_Props> & Readonly<{}>, {
|
|
42
|
+
userName: string;
|
|
43
|
+
zoom: number;
|
|
44
|
+
readonly: boolean;
|
|
45
|
+
active: boolean;
|
|
46
|
+
stamps: StampDefinition[];
|
|
47
|
+
signatureHandlers: SignatureHandlers;
|
|
48
|
+
viewMode: ViewMode;
|
|
49
|
+
shapeDetection: boolean;
|
|
50
|
+
}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
|
|
40
51
|
declare const __VLS_export: __VLS_WithSlots<typeof __VLS_base, __VLS_Slots>;
|
|
41
52
|
declare const _default: typeof __VLS_export;
|
|
42
53
|
export default _default;
|
|
@@ -10,8 +10,35 @@
|
|
|
10
10
|
ref="scrollContainer"
|
|
11
11
|
class="absolute inset-0 overflow-auto bg-muted/50"
|
|
12
12
|
:class="{ 'overflow-hidden': isSinglePageMode }"
|
|
13
|
+
data-testid="viewer-scroll"
|
|
13
14
|
>
|
|
14
|
-
|
|
15
|
+
<!-- Error state -->
|
|
16
|
+
<div
|
|
17
|
+
v-if="viewerState.error.value"
|
|
18
|
+
class="absolute inset-0 flex items-center justify-center"
|
|
19
|
+
>
|
|
20
|
+
<div class="flex flex-col items-center gap-3 text-center px-6">
|
|
21
|
+
<svg
|
|
22
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
23
|
+
class="size-10 text-red-500"
|
|
24
|
+
viewBox="0 0 24 24"
|
|
25
|
+
fill="none"
|
|
26
|
+
stroke="currentColor"
|
|
27
|
+
stroke-width="1.5"
|
|
28
|
+
stroke-linecap="round"
|
|
29
|
+
stroke-linejoin="round"
|
|
30
|
+
>
|
|
31
|
+
<circle cx="12" cy="12" r="10" />
|
|
32
|
+
<line x1="12" y1="8" x2="12" y2="12" />
|
|
33
|
+
<line x1="12" y1="16" x2="12.01" y2="16" />
|
|
34
|
+
</svg>
|
|
35
|
+
<p class="text-base font-medium text-foreground">
|
|
36
|
+
{{ viewerState.error.value }}
|
|
37
|
+
</p>
|
|
38
|
+
</div>
|
|
39
|
+
</div>
|
|
40
|
+
|
|
41
|
+
<ClientOnly v-else>
|
|
15
42
|
<!-- Single-page-at-a-time mode -->
|
|
16
43
|
<div
|
|
17
44
|
v-if="isSinglePageMode && singlePageMeta"
|
|
@@ -135,12 +162,17 @@ import { mapViewModeToFitMode, normalizeZoom, provideViewerState } from "../comp
|
|
|
135
162
|
import { provideViewerSearch } from "../composables/useViewerSearch";
|
|
136
163
|
import { createAnnotationEngine } from "../composables/useAnnotationEngine";
|
|
137
164
|
import { normalizeImportedAnnotations } from "../annotation/engine/import-normalize";
|
|
165
|
+
import {
|
|
166
|
+
decodePdfAnnotationsFromDocument,
|
|
167
|
+
filterCollidingAnnotations
|
|
168
|
+
} from "../annotation/pdf-import/decode";
|
|
138
169
|
import { getTimestampString } from "../annotation/engine/utils";
|
|
139
170
|
import { downloadPdfBytes } from "../annotation/pdf-export/download";
|
|
140
171
|
import { exportAnnotationsToPdf } from "../annotation/pdf-export/export";
|
|
141
172
|
import { createPageVirtualization } from "../composables/usePageVirtualization";
|
|
142
173
|
import { createPageProxyCache } from "../composables/usePageProxyCache";
|
|
143
174
|
import { createSearchIndex } from "../composables/useSearchIndex";
|
|
175
|
+
import { createShapeDetection } from "../composables/useShapeDetection";
|
|
144
176
|
import { provideFormFields } from "../composables/useFormFields";
|
|
145
177
|
import { isIPad, getTouchDistance, getTouchCenter, clampScale } from "../annotation/engine/input-device";
|
|
146
178
|
const props = defineProps({
|
|
@@ -152,6 +184,7 @@ const props = defineProps({
|
|
|
152
184
|
viewMode: { type: String, required: false, default: "fit-width" },
|
|
153
185
|
zoom: { type: Number, required: false, default: 1 },
|
|
154
186
|
readonly: { type: Boolean, required: false, default: false },
|
|
187
|
+
shapeDetection: { type: Boolean, required: false, default: false },
|
|
155
188
|
active: { type: Boolean, required: false, default: true }
|
|
156
189
|
});
|
|
157
190
|
const viewerRoot = ref(null);
|
|
@@ -160,6 +193,9 @@ const { state: viewerState, setScrollToPageFn } = provideViewerState();
|
|
|
160
193
|
watchEffect(() => {
|
|
161
194
|
viewerState.readonly.value = props.readonly ?? false;
|
|
162
195
|
});
|
|
196
|
+
watchEffect(() => {
|
|
197
|
+
viewerState.shapeDetection.value = props.shapeDetection ?? false;
|
|
198
|
+
});
|
|
163
199
|
const viewerSearch = provideViewerSearch();
|
|
164
200
|
const formFieldsState = provideFormFields();
|
|
165
201
|
const pageSettings = providePageSettings(viewerRoot);
|
|
@@ -173,6 +209,7 @@ setScrollToPageFn((pageNumber) => {
|
|
|
173
209
|
});
|
|
174
210
|
const proxyCache = createPageProxyCache();
|
|
175
211
|
const searchIndex = createSearchIndex();
|
|
212
|
+
const shapeDetectionCache = createShapeDetection();
|
|
176
213
|
const doc = shallowRef(null);
|
|
177
214
|
let painter = null;
|
|
178
215
|
const pageMetas = virtualization.pageMetas;
|
|
@@ -287,37 +324,73 @@ function onFreeTextCancel() {
|
|
|
287
324
|
freeTextResolve = null;
|
|
288
325
|
}
|
|
289
326
|
async function loadDocument() {
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
pdfjs
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
327
|
+
viewerState.error.value = null;
|
|
328
|
+
viewerState.isLoading.value = true;
|
|
329
|
+
try {
|
|
330
|
+
const pdfjs = await import("pdfjs-dist/legacy/build/pdf.mjs");
|
|
331
|
+
if (!pdfjs.GlobalWorkerOptions.workerSrc) {
|
|
332
|
+
const workerModule = await import("pdfjs-dist/legacy/build/pdf.worker.min.mjs?url");
|
|
333
|
+
pdfjs.GlobalWorkerOptions.workerSrc = workerModule.default;
|
|
334
|
+
}
|
|
335
|
+
const source = props.source;
|
|
336
|
+
const loadingTask = pdfjs.getDocument(
|
|
337
|
+
source
|
|
338
|
+
);
|
|
339
|
+
const pdfDoc = await loadingTask.promise;
|
|
340
|
+
doc.value = pdfDoc;
|
|
341
|
+
viewerState.doc.value = pdfDoc;
|
|
342
|
+
viewerState.totalPages.value = pdfDoc.numPages;
|
|
343
|
+
proxyCache.setDocument(pdfDoc);
|
|
344
|
+
await virtualization.init(pdfDoc, scrollContainer.value);
|
|
345
|
+
const metas = virtualization.pageMetas.value;
|
|
346
|
+
if (metas.length > 0) {
|
|
347
|
+
viewerState.pageDimensions.value = {
|
|
348
|
+
width: metas[0].width,
|
|
349
|
+
height: metas[0].height
|
|
350
|
+
};
|
|
351
|
+
}
|
|
352
|
+
const nativeDecoded = await decodePdfAnnotationsFromDocument(pdfDoc);
|
|
353
|
+
let collisionSkipped = 0;
|
|
354
|
+
let nativeMergedCount = 0;
|
|
355
|
+
if (painter) {
|
|
356
|
+
const existingIds = new Set(painter.getData().map((item) => item.id));
|
|
357
|
+
const filtered = filterCollidingAnnotations(nativeDecoded.annotations, existingIds);
|
|
358
|
+
const nativeToMerge = filtered.annotations;
|
|
359
|
+
collisionSkipped = filtered.skipped;
|
|
360
|
+
if (nativeToMerge.length > 0) {
|
|
361
|
+
painter.mergeAnnotations(nativeToMerge);
|
|
362
|
+
nativeMergedCount = nativeToMerge.length;
|
|
363
|
+
viewerState.history.reset();
|
|
364
|
+
}
|
|
365
|
+
viewerState.annotations.value = new Map(
|
|
366
|
+
painter.getData().map((annotation) => [annotation.id, annotation])
|
|
367
|
+
);
|
|
368
|
+
triggerRef(viewerState.annotations);
|
|
369
|
+
viewerState.selectedAnnotation.value = null;
|
|
370
|
+
viewerState.selectedAnnotations.value = [];
|
|
371
|
+
viewerState.selectionRect.value = null;
|
|
372
|
+
}
|
|
373
|
+
const totalSkipped = nativeDecoded.skipped + collisionSkipped;
|
|
374
|
+
if (nativeMergedCount > 0 || totalSkipped > 0 || nativeDecoded.errors > 0 || nativeDecoded.pageErrors > 0 || nativeDecoded.stampExtracted > 0 || nativeDecoded.stampSkipped > 0) {
|
|
375
|
+
console.info(
|
|
376
|
+
`[KViewer] Native annotation import: loaded=${nativeMergedCount}, skipped=${totalSkipped}, decodeErrors=${nativeDecoded.errors}, pageErrors=${nativeDecoded.pageErrors}, stampExtracted=${nativeDecoded.stampExtracted}, stampSkipped=${nativeDecoded.stampSkipped}`
|
|
377
|
+
);
|
|
378
|
+
}
|
|
379
|
+
viewerSearch.setScrollToPage((pageNumber) => {
|
|
380
|
+
virtualization.scrollToPage(pageNumber);
|
|
319
381
|
});
|
|
320
|
-
|
|
382
|
+
searchIndex.buildIndex(pdfDoc).then(() => {
|
|
383
|
+
viewerSearch.setSearchIndex({
|
|
384
|
+
getPageText: searchIndex.getPageText,
|
|
385
|
+
totalPages: pdfDoc.numPages
|
|
386
|
+
});
|
|
387
|
+
});
|
|
388
|
+
} catch (err) {
|
|
389
|
+
viewerState.error.value = "Failed to load document";
|
|
390
|
+
console.error("[KViewer] Document load error:", err);
|
|
391
|
+
} finally {
|
|
392
|
+
viewerState.isLoading.value = false;
|
|
393
|
+
}
|
|
321
394
|
}
|
|
322
395
|
let resizeObserver = null;
|
|
323
396
|
function ensurePdfExtension(fileName) {
|
|
@@ -361,13 +434,16 @@ async function exportPdf(options = {}) {
|
|
|
361
434
|
...options
|
|
362
435
|
};
|
|
363
436
|
const pdfData = await doc.value.getData();
|
|
364
|
-
const
|
|
437
|
+
const exportContext = painter.getExportContext();
|
|
365
438
|
const formFieldValues = formFieldsState.getAllFieldValues();
|
|
366
439
|
const bytes = await exportAnnotationsToPdf({
|
|
367
440
|
pdfData,
|
|
368
|
-
annotations,
|
|
441
|
+
annotations: exportContext.annotations,
|
|
369
442
|
options: mergedOptions,
|
|
370
|
-
formFieldValues
|
|
443
|
+
formFieldValues,
|
|
444
|
+
originalAnnotationIds: exportContext.originalIds,
|
|
445
|
+
modifiedAnnotationIds: exportContext.modifiedIds,
|
|
446
|
+
deletedAnnotationIds: exportContext.deletedIds
|
|
371
447
|
});
|
|
372
448
|
if (mergedOptions.download) {
|
|
373
449
|
downloadPdfBytes(bytes, resolveExportFileName(mergedOptions.fileName));
|
|
@@ -572,6 +648,7 @@ watch(
|
|
|
572
648
|
() => {
|
|
573
649
|
viewerSearch.reset();
|
|
574
650
|
formFieldsState.reset();
|
|
651
|
+
shapeDetectionCache.clearAllShapeCache();
|
|
575
652
|
searchIndex.destroy();
|
|
576
653
|
virtualization.destroy();
|
|
577
654
|
proxyCache.clear();
|
|
@@ -605,6 +682,7 @@ onBeforeUnmount(() => {
|
|
|
605
682
|
scrollContainer.value?.removeEventListener("touchcancel", onPinchTouchEnd);
|
|
606
683
|
resizeObserver?.disconnect();
|
|
607
684
|
viewerSearch.reset();
|
|
685
|
+
shapeDetectionCache.clearAllShapeCache();
|
|
608
686
|
searchIndex.destroy();
|
|
609
687
|
virtualization.destroy();
|
|
610
688
|
proxyCache.clear();
|
|
@@ -11,6 +11,8 @@ type __VLS_Props = {
|
|
|
11
11
|
zoom?: number;
|
|
12
12
|
/** When true, the viewer is in view-only mode: annotations, drawing tools, and form editing are disabled. */
|
|
13
13
|
readonly?: boolean;
|
|
14
|
+
/** When true, auto-detect checkbox-like shapes (squares, circles) and create interactive form field checkboxes. */
|
|
15
|
+
shapeDetection?: boolean;
|
|
14
16
|
/** When false, global keyboard shortcuts (e.g. Cmd+F) are suppressed. Used by ViewerTabs to prevent hidden viewers from capturing input. */
|
|
15
17
|
active?: boolean;
|
|
16
18
|
};
|
|
@@ -36,7 +38,16 @@ declare const __VLS_base: import("vue").DefineComponent<__VLS_Props, {
|
|
|
36
38
|
getKonvaCanvasState: typeof getKonvaCanvasState;
|
|
37
39
|
getFormFieldValues: () => import("../annotation/engine/types.js").FormFieldValue[];
|
|
38
40
|
setFormFieldValue: (fieldName: string, value: string | boolean | string[]) => void;
|
|
39
|
-
}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<__VLS_Props> & Readonly<{}>, {
|
|
41
|
+
}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<__VLS_Props> & Readonly<{}>, {
|
|
42
|
+
userName: string;
|
|
43
|
+
zoom: number;
|
|
44
|
+
readonly: boolean;
|
|
45
|
+
active: boolean;
|
|
46
|
+
stamps: StampDefinition[];
|
|
47
|
+
signatureHandlers: SignatureHandlers;
|
|
48
|
+
viewMode: ViewMode;
|
|
49
|
+
shapeDetection: boolean;
|
|
50
|
+
}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
|
|
40
51
|
declare const __VLS_export: __VLS_WithSlots<typeof __VLS_base, __VLS_Slots>;
|
|
41
52
|
declare const _default: typeof __VLS_export;
|
|
42
53
|
export default _default;
|