kviewer 0.2.0 → 0.2.2
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 +47 -0
- package/dist/module.d.mts +8 -1
- package/dist/module.json +1 -1
- package/dist/module.mjs +25 -3
- package/dist/runtime/annotation/engine/canvas-resolution.d.ts +7 -0
- package/dist/runtime/annotation/engine/canvas-resolution.js +21 -0
- package/dist/runtime/annotation/engine/editor/editor.d.ts +5 -0
- package/dist/runtime/annotation/engine/editor/editor.js +14 -0
- package/dist/runtime/annotation/engine/editor/selector.d.ts +13 -1
- package/dist/runtime/annotation/engine/editor/selector.js +60 -8
- package/dist/runtime/annotation/engine/painter.d.ts +31 -0
- package/dist/runtime/annotation/engine/painter.js +88 -2
- package/dist/runtime/annotation/engine/store.js +3 -0
- package/dist/runtime/annotation/engine/tools/arrow.js +0 -1
- package/dist/runtime/annotation/engine/tools/circle.js +0 -2
- package/dist/runtime/annotation/engine/tools/free-highlight.js +0 -2
- package/dist/runtime/annotation/engine/tools/freehand.js +0 -2
- package/dist/runtime/annotation/engine/tools/rectangle.js +0 -2
- package/dist/runtime/annotation/engine/types.d.ts +5 -0
- package/dist/runtime/annotation/font-style.d.ts +15 -0
- package/dist/runtime/annotation/font-style.js +19 -0
- package/dist/runtime/annotation/parsers/extractCheckboxStyles.d.ts +8 -0
- package/dist/runtime/annotation/parsers/extractCheckboxStyles.js +24 -1
- package/dist/runtime/annotation/pdf-export/annotation-flags.d.ts +8 -0
- package/dist/runtime/annotation/pdf-export/annotation-flags.js +5 -0
- package/dist/runtime/annotation/pdf-export/export-form-fields.js +3 -1
- package/dist/runtime/annotation/pdf-export/export.js +13 -1
- package/dist/runtime/annotation/pdf-export/parse_circle.js +3 -0
- package/dist/runtime/annotation/pdf-export/parse_freetext.js +31 -19
- package/dist/runtime/annotation/pdf-export/parse_highlight.js +3 -0
- package/dist/runtime/annotation/pdf-export/parse_ink.js +5 -1
- package/dist/runtime/annotation/pdf-export/parse_line.js +5 -1
- package/dist/runtime/annotation/pdf-export/parse_polyline.js +5 -1
- package/dist/runtime/annotation/pdf-export/parse_square.js +3 -0
- package/dist/runtime/annotation/pdf-export/parse_stamp.js +4 -2
- package/dist/runtime/annotation/pdf-export/parse_strikeout.js +3 -0
- package/dist/runtime/annotation/pdf-export/parse_text.js +3 -0
- package/dist/runtime/annotation/pdf-export/parse_underline.js +3 -0
- package/dist/runtime/annotation/pdf-export/unicode-font.d.ts +5 -0
- package/dist/runtime/annotation/pdf-export/unicode-font.js +31 -0
- package/dist/runtime/annotation/pdf-export/winansi.d.ts +17 -0
- package/dist/runtime/annotation/pdf-export/winansi.js +77 -0
- package/dist/runtime/annotation/pdf-import/decode_circle.js +0 -1
- package/dist/runtime/annotation/pdf-import/decode_ink.js +0 -1
- package/dist/runtime/annotation/pdf-import/decode_line.js +0 -1
- package/dist/runtime/annotation/pdf-import/decode_square.js +0 -1
- package/dist/runtime/annotation/pdf-import/extract_stamp_appearance.d.ts +12 -0
- package/dist/runtime/annotation/pdf-import/extract_stamp_appearance.js +17 -4
- package/dist/runtime/annotation/pdf-import/types.d.ts +2 -0
- package/dist/runtime/assets/fonts/LICENSE-LiberationSans +102 -0
- package/dist/runtime/assets/fonts/LiberationSans-Regular.ttf +0 -0
- package/dist/runtime/assets/kviewer.css +1 -1
- package/dist/runtime/components/AnnotationToolbar.vue +6 -4
- package/dist/runtime/components/PdfPage.d.vue.ts +16 -0
- package/dist/runtime/components/PdfPage.vue +170 -9
- package/dist/runtime/components/PdfPage.vue.d.ts +16 -0
- package/dist/runtime/components/PerfHud.d.vue.ts +3 -0
- package/dist/runtime/components/PerfHud.vue +97 -0
- package/dist/runtime/components/PerfHud.vue.d.ts +3 -0
- package/dist/runtime/components/ToolButton.vue +4 -1
- package/dist/runtime/components/Viewer.d.vue.ts +37 -2
- package/dist/runtime/components/Viewer.vue +208 -13
- package/dist/runtime/components/Viewer.vue.d.ts +37 -2
- package/dist/runtime/components/ViewerBar.d.vue.ts +21 -1
- package/dist/runtime/components/ViewerBar.vue +183 -101
- package/dist/runtime/components/ViewerBar.vue.d.ts +21 -1
- package/dist/runtime/components/ViewerTabs.d.vue.ts +10 -2
- package/dist/runtime/components/ViewerTabs.vue +13 -2
- package/dist/runtime/components/ViewerTabs.vue.d.ts +10 -2
- package/dist/runtime/components/form-fields/FormDropdown.vue +50 -19
- package/dist/runtime/components/form-fields/FormSignatureField.vue +4 -2
- package/dist/runtime/components/form-fields/FormTextField.vue +7 -18
- package/dist/runtime/components/modals/FreeTextModal.vue +6 -4
- package/dist/runtime/components/modals/SignatureDrawModal.vue +6 -4
- package/dist/runtime/components/panels/PlacedFieldSidebar.vue +67 -50
- package/dist/runtime/components/panels/SignaturePicker.vue +6 -4
- package/dist/runtime/components/panels/StampPicker.vue +3 -1
- package/dist/runtime/components/tools/AnnotationToolButton.d.vue.ts +10 -0
- package/dist/runtime/components/tools/AnnotationToolButton.vue +33 -0
- package/dist/runtime/components/tools/AnnotationToolButton.vue.d.ts +10 -0
- package/dist/runtime/components/tools/MarqueeTool.vue +3 -1
- package/dist/runtime/components/tools/PageSettings.vue +13 -11
- package/dist/runtime/components/tools/SearchTool.vue +12 -10
- package/dist/runtime/components/tools/ToolProperties.vue +7 -5
- package/dist/runtime/components/tools/ToolbarMenu.d.vue.ts +9 -0
- package/dist/runtime/components/tools/ToolbarMenu.vue +79 -0
- package/dist/runtime/components/tools/ToolbarMenu.vue.d.ts +9 -0
- package/dist/runtime/components/tools/ZoomControls.vue +17 -9
- package/dist/runtime/composables/canvas-limits.d.ts +22 -0
- package/dist/runtime/composables/canvas-limits.js +10 -0
- package/dist/runtime/composables/useFormFields.d.ts +6 -0
- package/dist/runtime/composables/useFormFields.js +28 -0
- package/dist/runtime/composables/useInertiaPanzoom.d.ts +10 -0
- package/dist/runtime/composables/useInertiaPanzoom.js +57 -10
- package/dist/runtime/composables/useKviewerI18n.d.ts +18 -0
- package/dist/runtime/composables/useKviewerI18n.js +29 -0
- package/dist/runtime/composables/usePageVirtualization.d.ts +11 -0
- package/dist/runtime/composables/usePageVirtualization.js +40 -0
- package/dist/runtime/composables/useScriptingManager.d.ts +8 -4
- package/dist/runtime/composables/useScriptingManager.js +2 -0
- package/dist/runtime/composables/useViewerState.d.ts +4 -0
- package/dist/runtime/composables/useViewerState.js +8 -0
- package/dist/runtime/embed/bridge-client.d.ts +9 -1
- package/dist/runtime/embed/bridge-client.js +13 -0
- package/dist/runtime/embed/bridge-host.d.ts +3 -0
- package/dist/runtime/embed/bridge-host.js +30 -8
- package/dist/runtime/embed/protocol.d.ts +21 -0
- package/dist/runtime/i18n/messages.d.ts +252 -0
- package/dist/runtime/i18n/messages.js +249 -0
- package/dist/runtime/public-types.d.ts +2 -0
- package/dist/runtime/toolbar-tools.d.ts +54 -0
- package/dist/runtime/toolbar-tools.js +79 -0
- package/dist/types.d.mts +1 -1
- package/package.json +14 -31
|
@@ -16,6 +16,15 @@
|
|
|
16
16
|
ref="canvasRef"
|
|
17
17
|
:style="{ display: 'block', width: cssWidth + 'px', height: cssHeight + 'px' }"
|
|
18
18
|
/>
|
|
19
|
+
<!-- Crisp viewport-window raster: when the full page would blow the
|
|
20
|
+
canvas budget at high zoom, the base canvas above is only a
|
|
21
|
+
low-res fallback and this canvas renders just the visible region
|
|
22
|
+
(plus margin) at full resolution. -->
|
|
23
|
+
<canvas
|
|
24
|
+
v-show="windowCanvasVisible"
|
|
25
|
+
ref="windowCanvasRef"
|
|
26
|
+
:style="windowCanvasStyle"
|
|
27
|
+
/>
|
|
19
28
|
<div
|
|
20
29
|
ref="textLayerRef"
|
|
21
30
|
class="kviewer-text-layer textLayer"
|
|
@@ -33,6 +42,16 @@
|
|
|
33
42
|
:pointer-events="formFieldPointerEvents"
|
|
34
43
|
:z-index="formFieldZIndex"
|
|
35
44
|
/>
|
|
45
|
+
<!-- transform: translateZ(0) isolates the Konva canvases in their own
|
|
46
|
+
compositing layer: per-frame canvas updates during annotation
|
|
47
|
+
drags then only re-upload the (budget-capped) Konva texture
|
|
48
|
+
instead of triggering an iPadOS re-rasterization of the subtree
|
|
49
|
+
containing the much larger PDF canvases. -->
|
|
50
|
+
<!-- overflow: hidden — between a zoom commit and the async Konva
|
|
51
|
+
re-init the old (differently sized) stage briefly overflows this
|
|
52
|
+
div; unclipped it inflates the zoom wrapper's scrollHeight and
|
|
53
|
+
the panzoom clamps against phantom bounds, leaving the page
|
|
54
|
+
resting out of position. -->
|
|
36
55
|
<div
|
|
37
56
|
ref="annotationRef"
|
|
38
57
|
class="kviewer-annotation-layer"
|
|
@@ -43,7 +62,9 @@
|
|
|
43
62
|
width: cssWidth + 'px',
|
|
44
63
|
height: cssHeight + 'px',
|
|
45
64
|
zIndex: 3,
|
|
46
|
-
pointerEvents: annotationPointerEvents
|
|
65
|
+
pointerEvents: annotationPointerEvents,
|
|
66
|
+
transform: 'translateZ(0)',
|
|
67
|
+
overflow: 'hidden'
|
|
47
68
|
}"
|
|
48
69
|
/>
|
|
49
70
|
</div>
|
|
@@ -58,21 +79,35 @@ import { usePageProxyCache } from "../composables/usePageProxyCache";
|
|
|
58
79
|
import { useFormFields } from "../composables/useFormFields";
|
|
59
80
|
import { useShapeDetection } from "../composables/useShapeDetection";
|
|
60
81
|
import { AnnotationType } from "../annotation/engine/types";
|
|
82
|
+
import { isIPad } from "../annotation/engine/input-device";
|
|
83
|
+
import { extractSignedSignatureAppearancesForPage } from "../annotation/pdf-import/extract_stamp_appearance";
|
|
84
|
+
import { pageTransformFromProxy } from "../annotation/pdf-import/transform";
|
|
85
|
+
import {
|
|
86
|
+
clampRenderScale,
|
|
87
|
+
DEFAULT_MAX_CANVAS_PIXELS,
|
|
88
|
+
IOS_MAX_CANVAS_PIXELS
|
|
89
|
+
} from "../composables/canvas-limits";
|
|
61
90
|
import FormFieldLayer from "./FormFieldLayer.vue";
|
|
62
91
|
const PDFJS_ANNOTATION_MODE_DISABLE = 0;
|
|
63
92
|
const props = defineProps({
|
|
64
93
|
pageNumber: { type: Number, required: true },
|
|
65
94
|
pageMeta: { type: Object, required: true },
|
|
66
95
|
scale: { type: Number, required: true },
|
|
67
|
-
renderQuality: { type: Number, required: false, default: 1 }
|
|
96
|
+
renderQuality: { type: Number, required: false, default: 1 },
|
|
97
|
+
viewWindow: { type: [Object, null], required: false, default: null }
|
|
68
98
|
});
|
|
69
99
|
const canvasRef = ref(null);
|
|
100
|
+
const windowCanvasRef = ref(null);
|
|
101
|
+
const windowCanvasVisible = ref(false);
|
|
102
|
+
const windowCanvasStyle = ref({});
|
|
70
103
|
const textLayerRef = ref(null);
|
|
71
104
|
const annotationRef = ref(null);
|
|
72
105
|
const runtimeConfig = useRuntimeConfig();
|
|
73
106
|
const minRenderPixelRatio = Number(
|
|
74
107
|
runtimeConfig.public.kviewer?.minRenderPixelRatio ?? 2
|
|
75
108
|
);
|
|
109
|
+
const isAppleMobile = typeof navigator !== "undefined" && (isIPad() || /iPhone|iPod/.test(navigator.userAgent));
|
|
110
|
+
const maxCanvasPixels = isAppleMobile ? IOS_MAX_CANVAS_PIXELS : DEFAULT_MAX_CANVAS_PIXELS;
|
|
76
111
|
const state = useViewerState();
|
|
77
112
|
const search = useViewerSearch();
|
|
78
113
|
const proxyCache = usePageProxyCache();
|
|
@@ -80,6 +115,11 @@ const formFields = useFormFields();
|
|
|
80
115
|
const shapeDetection = useShapeDetection();
|
|
81
116
|
const pageProxy = shallowRef(null);
|
|
82
117
|
let currentRenderTask = null;
|
|
118
|
+
let windowRenderTask = null;
|
|
119
|
+
let windowed = false;
|
|
120
|
+
let renderedWindowKey = null;
|
|
121
|
+
const FALLBACK_MAX_CANVAS_PIXELS = 4e6;
|
|
122
|
+
const WINDOW_MAX_CANVAS_PIXELS = 8e6;
|
|
83
123
|
let textLayer = null;
|
|
84
124
|
const baseWidth = props.pageMeta.width;
|
|
85
125
|
const baseHeight = props.pageMeta.height;
|
|
@@ -109,8 +149,8 @@ onMounted(async () => {
|
|
|
109
149
|
await renderTextLayer();
|
|
110
150
|
initKonva();
|
|
111
151
|
if (pageProxy.value) {
|
|
112
|
-
|
|
113
|
-
formFields.parsePageFields(props.pageNumber,
|
|
152
|
+
rawPageAnnotations = await pageProxy.value.getAnnotations();
|
|
153
|
+
formFields.parsePageFields(props.pageNumber, rawPageAnnotations);
|
|
114
154
|
}
|
|
115
155
|
if (state.shapeDetection.value && pageProxy.value) {
|
|
116
156
|
const viewport = pageProxy.value.getViewport({ scale: props.scale });
|
|
@@ -129,6 +169,27 @@ onMounted(async () => {
|
|
|
129
169
|
}
|
|
130
170
|
}
|
|
131
171
|
});
|
|
172
|
+
let rawPageAnnotations = null;
|
|
173
|
+
const attemptedSignatureSeedIds = /* @__PURE__ */ new Set();
|
|
174
|
+
const pendingSignedSignatureKey = computed(() => formFields.getFieldsForPage(props.pageNumber).filter((def) => def.fieldType === "signature" && def.signedInSource && formFields.getFieldValue(def.id)?.value === "").map((def) => def.id).join(","));
|
|
175
|
+
watch(pendingSignedSignatureKey, (key) => {
|
|
176
|
+
if (key !== "") void seedSignedSignatureAppearances();
|
|
177
|
+
});
|
|
178
|
+
async function seedSignedSignatureAppearances() {
|
|
179
|
+
if (!pageProxy.value || !rawPageAnnotations) return;
|
|
180
|
+
const pending = formFields.getFieldsForPage(props.pageNumber).filter((def) => def.fieldType === "signature" && def.signedInSource && formFields.getFieldValue(def.id)?.value === "" && !attemptedSignatureSeedIds.has(def.id));
|
|
181
|
+
if (pending.length === 0) return;
|
|
182
|
+
for (const def of pending) attemptedSignatureSeedIds.add(def.id);
|
|
183
|
+
const appearances = await extractSignedSignatureAppearancesForPage(
|
|
184
|
+
pageProxy.value,
|
|
185
|
+
pageTransformFromProxy(pageProxy.value),
|
|
186
|
+
rawPageAnnotations
|
|
187
|
+
);
|
|
188
|
+
for (const def of pending) {
|
|
189
|
+
const appearance = appearances.get(def.id);
|
|
190
|
+
if (appearance) formFields.setFieldValue(def.id, appearance.dataUrl);
|
|
191
|
+
}
|
|
192
|
+
}
|
|
132
193
|
watch(
|
|
133
194
|
() => state.shapeDetection.value,
|
|
134
195
|
async (enabled) => {
|
|
@@ -151,10 +212,22 @@ watch(
|
|
|
151
212
|
);
|
|
152
213
|
watch(
|
|
153
214
|
() => props.scale,
|
|
154
|
-
async () => {
|
|
215
|
+
async (newScale, oldScale) => {
|
|
155
216
|
if (!pageProxy.value) return;
|
|
217
|
+
if (windowCanvasVisible.value && oldScale && newScale !== oldScale) {
|
|
218
|
+
const r = newScale / oldScale;
|
|
219
|
+
const s = windowCanvasStyle.value;
|
|
220
|
+
windowCanvasStyle.value = {
|
|
221
|
+
...s,
|
|
222
|
+
left: Number.parseFloat(s.left ?? "0") * r + "px",
|
|
223
|
+
top: Number.parseFloat(s.top ?? "0") * r + "px",
|
|
224
|
+
width: Number.parseFloat(s.width ?? "0") * r + "px",
|
|
225
|
+
height: Number.parseFloat(s.height ?? "0") * r + "px"
|
|
226
|
+
};
|
|
227
|
+
renderedWindowKey = null;
|
|
228
|
+
}
|
|
156
229
|
shapeDetection.clearShapeCache(props.pageNumber);
|
|
157
|
-
await renderCanvas();
|
|
230
|
+
await renderCanvas({ doubleBuffer: true });
|
|
158
231
|
updateTextLayer();
|
|
159
232
|
if (state.painter.value) {
|
|
160
233
|
state.painter.value.destroyCanvasForPage(props.pageNumber);
|
|
@@ -183,9 +256,16 @@ async function renderCanvas(opts) {
|
|
|
183
256
|
}
|
|
184
257
|
const dpr = Math.max(window.devicePixelRatio || 1, minRenderPixelRatio);
|
|
185
258
|
const quality = props.renderQuality ?? 1;
|
|
186
|
-
const
|
|
187
|
-
|
|
188
|
-
|
|
259
|
+
const desiredScale = props.scale * quality * dpr;
|
|
260
|
+
const fullScale = clampRenderScale(desiredScale, baseWidth, baseHeight, maxCanvasPixels);
|
|
261
|
+
windowed = fullScale < desiredScale * 0.999;
|
|
262
|
+
const renderScale = windowed ? clampRenderScale(
|
|
263
|
+
desiredScale,
|
|
264
|
+
baseWidth,
|
|
265
|
+
baseHeight,
|
|
266
|
+
Math.min(FALLBACK_MAX_CANVAS_PIXELS, maxCanvasPixels)
|
|
267
|
+
) : fullScale;
|
|
268
|
+
const viewport = proxy.getViewport({ scale: renderScale });
|
|
189
269
|
if (opts?.doubleBuffer) {
|
|
190
270
|
const tmp = document.createElement("canvas");
|
|
191
271
|
tmp.width = viewport.width;
|
|
@@ -225,7 +305,84 @@ async function renderCanvas(opts) {
|
|
|
225
305
|
currentRenderTask = null;
|
|
226
306
|
}
|
|
227
307
|
}
|
|
308
|
+
if (windowed) {
|
|
309
|
+
void renderWindowCanvas();
|
|
310
|
+
} else {
|
|
311
|
+
hideWindowCanvas();
|
|
312
|
+
}
|
|
313
|
+
}
|
|
314
|
+
function hideWindowCanvas() {
|
|
315
|
+
windowCanvasVisible.value = false;
|
|
316
|
+
renderedWindowKey = null;
|
|
317
|
+
const c = windowCanvasRef.value;
|
|
318
|
+
if (c && c.width > 0) {
|
|
319
|
+
c.width = 0;
|
|
320
|
+
c.height = 0;
|
|
321
|
+
}
|
|
228
322
|
}
|
|
323
|
+
async function renderWindowCanvas() {
|
|
324
|
+
const proxy = pageProxy.value;
|
|
325
|
+
const canvas = windowCanvasRef.value;
|
|
326
|
+
const win = props.viewWindow;
|
|
327
|
+
if (!proxy || !canvas || !win || !windowed) return;
|
|
328
|
+
const x = Math.max(0, Math.min(win.x, cssWidth.value));
|
|
329
|
+
const y = Math.max(0, Math.min(win.y, cssHeight.value));
|
|
330
|
+
const w = Math.min(win.x + win.width, cssWidth.value) - x;
|
|
331
|
+
const h = Math.min(win.y + win.height, cssHeight.value) - y;
|
|
332
|
+
if (w <= 0 || h <= 0) return;
|
|
333
|
+
const key = [x, y, w, h, props.scale].map((n) => Math.round(n)).join(",");
|
|
334
|
+
if (key === renderedWindowKey && windowCanvasVisible.value) return;
|
|
335
|
+
const dpr = window.devicePixelRatio || 1;
|
|
336
|
+
const renderScale = clampRenderScale(
|
|
337
|
+
props.scale * dpr,
|
|
338
|
+
w / props.scale,
|
|
339
|
+
h / props.scale,
|
|
340
|
+
Math.min(WINDOW_MAX_CANVAS_PIXELS, maxCanvasPixels)
|
|
341
|
+
);
|
|
342
|
+
const k = renderScale / props.scale;
|
|
343
|
+
const viewport = proxy.getViewport({ scale: renderScale });
|
|
344
|
+
windowRenderTask?.cancel();
|
|
345
|
+
await currentRenderTask?.promise.catch(() => {
|
|
346
|
+
});
|
|
347
|
+
const tmp = document.createElement("canvas");
|
|
348
|
+
tmp.width = Math.floor(w * k);
|
|
349
|
+
tmp.height = Math.floor(h * k);
|
|
350
|
+
try {
|
|
351
|
+
windowRenderTask = proxy.render({
|
|
352
|
+
canvasContext: tmp.getContext("2d"),
|
|
353
|
+
viewport,
|
|
354
|
+
transform: [1, 0, 0, 1, -x * k, -y * k],
|
|
355
|
+
annotationMode: PDFJS_ANNOTATION_MODE_DISABLE
|
|
356
|
+
});
|
|
357
|
+
await windowRenderTask.promise;
|
|
358
|
+
canvas.width = tmp.width;
|
|
359
|
+
canvas.height = tmp.height;
|
|
360
|
+
canvas.getContext("2d").drawImage(tmp, 0, 0);
|
|
361
|
+
windowCanvasStyle.value = {
|
|
362
|
+
position: "absolute",
|
|
363
|
+
left: x + "px",
|
|
364
|
+
top: y + "px",
|
|
365
|
+
width: w + "px",
|
|
366
|
+
height: h + "px"
|
|
367
|
+
};
|
|
368
|
+
windowCanvasVisible.value = true;
|
|
369
|
+
renderedWindowKey = key;
|
|
370
|
+
} catch (e) {
|
|
371
|
+
if (e instanceof Error && e.message?.includes("Rendering cancelled")) return;
|
|
372
|
+
console.warn("[KViewer] Window render failed:", e);
|
|
373
|
+
} finally {
|
|
374
|
+
windowRenderTask = null;
|
|
375
|
+
tmp.width = 0;
|
|
376
|
+
tmp.height = 0;
|
|
377
|
+
}
|
|
378
|
+
}
|
|
379
|
+
watch(
|
|
380
|
+
() => props.viewWindow,
|
|
381
|
+
(win) => {
|
|
382
|
+
if (windowed) void renderWindowCanvas();
|
|
383
|
+
state.painter.value?.setStageWindow(props.pageNumber, win ?? null);
|
|
384
|
+
}
|
|
385
|
+
);
|
|
229
386
|
function initKonva() {
|
|
230
387
|
if (!annotationRef.value || !state.painter.value) return;
|
|
231
388
|
state.painter.value.initCanvasForPage(
|
|
@@ -235,6 +392,9 @@ function initKonva() {
|
|
|
235
392
|
baseHeight,
|
|
236
393
|
props.scale
|
|
237
394
|
);
|
|
395
|
+
if (props.viewWindow) {
|
|
396
|
+
state.painter.value.setStageWindow(props.pageNumber, props.viewWindow);
|
|
397
|
+
}
|
|
238
398
|
}
|
|
239
399
|
async function renderTextLayer() {
|
|
240
400
|
const container = textLayerRef.value;
|
|
@@ -279,6 +439,7 @@ function updateTextLayer() {
|
|
|
279
439
|
}
|
|
280
440
|
onBeforeUnmount(() => {
|
|
281
441
|
currentRenderTask?.cancel();
|
|
442
|
+
windowRenderTask?.cancel();
|
|
282
443
|
textLayer?.cancel();
|
|
283
444
|
textLayer = null;
|
|
284
445
|
search.unregisterPageLayer(props.pageNumber);
|
|
@@ -6,9 +6,25 @@ type __VLS_Props = {
|
|
|
6
6
|
/** Canvas internal resolution multiplier (CSS size unchanged).
|
|
7
7
|
* Used by panzoom to sharpen the PDF when visually zoomed in. */
|
|
8
8
|
renderQuality?: number;
|
|
9
|
+
/** Visible region of this page in page-CSS coordinates (already
|
|
10
|
+
* includes margin), supplied by the viewer's panzoom. Drives the
|
|
11
|
+
* viewport-window raster at high zoom. Null = unknown/inapplicable
|
|
12
|
+
* (e.g. rotated pages), full-page rendering only. */
|
|
13
|
+
viewWindow?: {
|
|
14
|
+
x: number;
|
|
15
|
+
y: number;
|
|
16
|
+
width: number;
|
|
17
|
+
height: number;
|
|
18
|
+
} | null;
|
|
9
19
|
};
|
|
10
20
|
declare const __VLS_export: import("vue").DefineComponent<__VLS_Props, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<__VLS_Props> & Readonly<{}>, {
|
|
11
21
|
renderQuality: number;
|
|
22
|
+
viewWindow: {
|
|
23
|
+
x: number;
|
|
24
|
+
y: number;
|
|
25
|
+
width: number;
|
|
26
|
+
height: number;
|
|
27
|
+
} | null;
|
|
12
28
|
}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
|
|
13
29
|
declare const _default: typeof __VLS_export;
|
|
14
30
|
export default _default;
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
declare const __VLS_export: import("vue").DefineComponent<{}, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<{}> & Readonly<{}>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, true, {}, any>;
|
|
2
|
+
declare const _default: typeof __VLS_export;
|
|
3
|
+
export default _default;
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<div v-if="enabled" class="perf-hud">
|
|
3
|
+
<div>fps {{ fps }} | p95 {{ p95 }}ms | max {{ maxGap }}ms</div>
|
|
4
|
+
<div>evt/s {{ evtRate }} | konva {{ drawPerSec }}ms/s | long {{ longSupported ? longMs + "ms/s" : "n/a" }}</div>
|
|
5
|
+
<div>{{ canvasInfo }}</div>
|
|
6
|
+
<button class="perf-hud-btn" @pointerdown.stop @click="noDraw = !noDraw">
|
|
7
|
+
konva draw: {{ noDraw ? "OFF" : "on" }}
|
|
8
|
+
</button>
|
|
9
|
+
</div>
|
|
10
|
+
</template>
|
|
11
|
+
|
|
12
|
+
<script setup>
|
|
13
|
+
import { ref, onMounted, onBeforeUnmount } from "vue";
|
|
14
|
+
import Konva from "konva";
|
|
15
|
+
const enabled = ref(false);
|
|
16
|
+
const fps = ref(0);
|
|
17
|
+
const p95 = ref(0);
|
|
18
|
+
const maxGap = ref(0);
|
|
19
|
+
const evtRate = ref(0);
|
|
20
|
+
const drawPerSec = ref(0);
|
|
21
|
+
const longMs = ref(0);
|
|
22
|
+
const longSupported = ref(false);
|
|
23
|
+
const canvasInfo = ref("");
|
|
24
|
+
const noDraw = ref(false);
|
|
25
|
+
let raf = 0;
|
|
26
|
+
let interval = 0;
|
|
27
|
+
let last = 0;
|
|
28
|
+
let gaps = [];
|
|
29
|
+
let evtCount = 0;
|
|
30
|
+
let drawMs = 0;
|
|
31
|
+
let longTaskMs = 0;
|
|
32
|
+
let po = null;
|
|
33
|
+
let origDraw = null;
|
|
34
|
+
function onAnyMove() {
|
|
35
|
+
evtCount++;
|
|
36
|
+
}
|
|
37
|
+
onMounted(() => {
|
|
38
|
+
enabled.value = new URLSearchParams(window.location.search).has("perf");
|
|
39
|
+
if (!enabled.value) return;
|
|
40
|
+
origDraw = Konva.Layer.prototype.drawScene;
|
|
41
|
+
Konva.Layer.prototype.drawScene = function(...args) {
|
|
42
|
+
if (noDraw.value) return this;
|
|
43
|
+
const t0 = performance.now();
|
|
44
|
+
const r = origDraw.apply(this, args);
|
|
45
|
+
drawMs += performance.now() - t0;
|
|
46
|
+
return r;
|
|
47
|
+
};
|
|
48
|
+
window.addEventListener("pointermove", onAnyMove, { capture: true, passive: true });
|
|
49
|
+
window.addEventListener("touchmove", onAnyMove, { capture: true, passive: true });
|
|
50
|
+
try {
|
|
51
|
+
po = new PerformanceObserver((list) => {
|
|
52
|
+
for (const e of list.getEntries()) longTaskMs += e.duration;
|
|
53
|
+
});
|
|
54
|
+
po.observe({ type: "longtask" });
|
|
55
|
+
longSupported.value = true;
|
|
56
|
+
} catch {
|
|
57
|
+
longSupported.value = false;
|
|
58
|
+
}
|
|
59
|
+
const tick = (t) => {
|
|
60
|
+
if (last) gaps.push(t - last);
|
|
61
|
+
last = t;
|
|
62
|
+
raf = requestAnimationFrame(tick);
|
|
63
|
+
};
|
|
64
|
+
raf = requestAnimationFrame(tick);
|
|
65
|
+
interval = window.setInterval(() => {
|
|
66
|
+
const sorted = [...gaps].sort((a, b) => a - b);
|
|
67
|
+
const total = gaps.reduce((s, g) => s + g, 0);
|
|
68
|
+
fps.value = total > 0 ? Math.round(gaps.length / total * 1e3) : 0;
|
|
69
|
+
p95.value = Math.round(sorted[Math.floor(sorted.length * 0.95)] ?? 0);
|
|
70
|
+
maxGap.value = Math.round(sorted[sorted.length - 1] ?? 0);
|
|
71
|
+
evtRate.value = evtCount;
|
|
72
|
+
drawPerSec.value = Math.round(drawMs);
|
|
73
|
+
longMs.value = Math.round(longTaskMs);
|
|
74
|
+
gaps = [];
|
|
75
|
+
evtCount = 0;
|
|
76
|
+
drawMs = 0;
|
|
77
|
+
longTaskMs = 0;
|
|
78
|
+
const pageEl = document.querySelector(".kviewer-page");
|
|
79
|
+
if (pageEl) {
|
|
80
|
+
canvasInfo.value = [...pageEl.querySelectorAll(":scope > canvas, .konvajs-content canvas")].map((c) => `${c.width}x${c.height}`).join(" | ");
|
|
81
|
+
}
|
|
82
|
+
}, 1e3);
|
|
83
|
+
});
|
|
84
|
+
onBeforeUnmount(() => {
|
|
85
|
+
if (!enabled.value) return;
|
|
86
|
+
cancelAnimationFrame(raf);
|
|
87
|
+
clearInterval(interval);
|
|
88
|
+
window.removeEventListener("pointermove", onAnyMove, { capture: true });
|
|
89
|
+
window.removeEventListener("touchmove", onAnyMove, { capture: true });
|
|
90
|
+
po?.disconnect();
|
|
91
|
+
if (origDraw) Konva.Layer.prototype.drawScene = origDraw;
|
|
92
|
+
});
|
|
93
|
+
</script>
|
|
94
|
+
|
|
95
|
+
<style scoped>
|
|
96
|
+
.perf-hud{background:rgba(0,0,0,.8);border-radius:6px;color:#4ade80;font:11px/1.4 ui-monospace,monospace;left:50%;padding:4px 8px;pointer-events:none;position:fixed;text-align:center;top:4px;transform:translateX(-50%);white-space:nowrap;z-index:99999}.perf-hud-btn{background:#333;border:1px solid #666;border-radius:4px;color:#fff;font:inherit;margin-top:2px;padding:2px 8px;pointer-events:auto}
|
|
97
|
+
</style>
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
declare const __VLS_export: import("vue").DefineComponent<{}, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<{}> & Readonly<{}>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, true, {}, any>;
|
|
2
|
+
declare const _default: typeof __VLS_export;
|
|
3
|
+
export default _default;
|
|
@@ -3,7 +3,8 @@
|
|
|
3
3
|
v-if="tool"
|
|
4
4
|
class="relative flex items-center justify-center w-8 h-8 rounded-md transition-colors cursor-pointer"
|
|
5
5
|
:class="active ? 'bg-default text-highlighted' : 'text-muted hover:bg-default/50 hover:text-highlighted'"
|
|
6
|
-
:title="tool.name"
|
|
6
|
+
:title="toolLabel(tool.name)"
|
|
7
|
+
:data-testid="`tool-${tool.name}`"
|
|
7
8
|
@click="$emit('click')"
|
|
8
9
|
>
|
|
9
10
|
<UIcon :name="tool.icon" class="text-lg" />
|
|
@@ -17,6 +18,8 @@
|
|
|
17
18
|
</template>
|
|
18
19
|
|
|
19
20
|
<script setup>
|
|
21
|
+
import { useKviewerI18n } from "../composables/useKviewerI18n";
|
|
22
|
+
const { toolLabel } = useKviewerI18n();
|
|
20
23
|
defineProps({
|
|
21
24
|
tool: { type: null, required: true },
|
|
22
25
|
active: { type: Boolean, required: true },
|
|
@@ -2,6 +2,9 @@ import { type ViewMode } from '../composables/useViewerState.js';
|
|
|
2
2
|
import type { AddFormFieldPayload, FormFieldDefinition, IAnnotationStore, StampDefinition, SignatureHandlers } from '../annotation/engine/types.js';
|
|
3
3
|
import { type ExportPdfOptions } from '../annotation/pdf-export/export.js';
|
|
4
4
|
import type { ViewerMenuItem } from '../menu-items.js';
|
|
5
|
+
import type { ViewerToolEntry } from '../toolbar-tools.js';
|
|
6
|
+
import { type KviewerMessageOverrides } from '../composables/useKviewerI18n.js';
|
|
7
|
+
import { type KviewerLocale } from '../i18n/messages.js';
|
|
5
8
|
type __VLS_Props = {
|
|
6
9
|
source: string | Uint8Array | object;
|
|
7
10
|
textLayer?: boolean;
|
|
@@ -45,6 +48,19 @@ type __VLS_Props = {
|
|
|
45
48
|
* toggles or actions without replacing the entire header. Ignored
|
|
46
49
|
* when the host supplies a custom `#header` slot. */
|
|
47
50
|
menuItems?: ViewerMenuItem[];
|
|
51
|
+
/** Configures which toolbar tools render and in what order. Entries are
|
|
52
|
+
* built-in tool IDs (strings) or `{ type: 'slot', ... }` objects that
|
|
53
|
+
* inject a custom button via a `#tool-<name>` slot. When omitted, the
|
|
54
|
+
* default toolbar is rendered unchanged. Ignored when the host supplies
|
|
55
|
+
* a custom `#header` slot. See {@link ViewerToolEntry}. */
|
|
56
|
+
tools?: ViewerToolEntry[];
|
|
57
|
+
/** UI language for built-in viewer strings (toolbar, search, modals,
|
|
58
|
+
* form sidebar). Defaults to the `kviewer.locale` module option, then
|
|
59
|
+
* 'en'. Hosts with their own i18n just bind their active locale. */
|
|
60
|
+
locale?: KviewerLocale;
|
|
61
|
+
/** Per-string overrides for built-in messages — wins over the locale
|
|
62
|
+
* catalog. Useful for app-specific wording without forking locales. */
|
|
63
|
+
messages?: KviewerMessageOverrides;
|
|
48
64
|
};
|
|
49
65
|
declare function exportPdf(options?: ExportPdfOptions): Promise<Uint8Array>;
|
|
50
66
|
type ImportMode = 'replace' | 'merge';
|
|
@@ -55,11 +71,15 @@ declare function importAnnotations(annotations: IAnnotationStore[], options?: {
|
|
|
55
71
|
skipped: number;
|
|
56
72
|
}>;
|
|
57
73
|
declare function getKonvaCanvasState(): Record<number, string>;
|
|
58
|
-
declare var __VLS_1: {},
|
|
74
|
+
declare var __VLS_1: {}, __VLS_11: string, __VLS_12: {
|
|
75
|
+
state: import("../composables/useViewerState.js").ViewerState;
|
|
76
|
+
}, __VLS_62: {};
|
|
59
77
|
type __VLS_Slots = {} & {
|
|
78
|
+
[K in NonNullable<typeof __VLS_11>]?: (props: typeof __VLS_12) => any;
|
|
79
|
+
} & {
|
|
60
80
|
header?: (props: typeof __VLS_1) => any;
|
|
61
81
|
} & {
|
|
62
|
-
footer?: (props: typeof
|
|
82
|
+
footer?: (props: typeof __VLS_62) => any;
|
|
63
83
|
};
|
|
64
84
|
declare const __VLS_base: import("vue").DefineComponent<__VLS_Props, {
|
|
65
85
|
getAnnotations: () => IAnnotationStore[];
|
|
@@ -84,12 +104,24 @@ declare const __VLS_base: import("vue").DefineComponent<__VLS_Props, {
|
|
|
84
104
|
setFormEditMode: (enabled: boolean) => void;
|
|
85
105
|
/** Flip form-edit mode. Returns the new state. */
|
|
86
106
|
toggleFormEditMode: () => boolean;
|
|
107
|
+
/** True once the user has viewed every page — each page has entered the
|
|
108
|
+
* viewport at least once. Use to gate a "must read all pages" sign action.
|
|
109
|
+
* See also the `all-pages-read` event for a push-style signal. */
|
|
110
|
+
allPagesRead: () => boolean;
|
|
111
|
+
/** Page numbers viewed so far, ascending. */
|
|
112
|
+
getViewedPages: () => number[];
|
|
113
|
+
/** Clear viewed-page tracking, e.g. to restart a signing session. */
|
|
114
|
+
resetViewedPages: () => void;
|
|
87
115
|
}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {
|
|
116
|
+
"all-pages-read": () => any;
|
|
88
117
|
"update:formEditMode": (value: boolean) => any;
|
|
89
118
|
"update:activeRoleId": (value: string | null) => any;
|
|
119
|
+
"update:viewedPages": (pages: number[]) => any;
|
|
90
120
|
}, string, import("vue").PublicProps, Readonly<__VLS_Props> & Readonly<{
|
|
121
|
+
"onAll-pages-read"?: (() => any) | undefined;
|
|
91
122
|
"onUpdate:formEditMode"?: ((value: boolean) => any) | undefined;
|
|
92
123
|
"onUpdate:activeRoleId"?: ((value: string | null) => any) | undefined;
|
|
124
|
+
"onUpdate:viewedPages"?: ((pages: number[]) => any) | undefined;
|
|
93
125
|
}>, {
|
|
94
126
|
userName: string;
|
|
95
127
|
freehandGroupingDelay: number;
|
|
@@ -97,6 +129,7 @@ declare const __VLS_base: import("vue").DefineComponent<__VLS_Props, {
|
|
|
97
129
|
readonly: boolean;
|
|
98
130
|
active: boolean;
|
|
99
131
|
menuItems: ViewerMenuItem[];
|
|
132
|
+
tools: ViewerToolEntry[];
|
|
100
133
|
scripting: boolean;
|
|
101
134
|
stamps: StampDefinition[];
|
|
102
135
|
signatureHandlers: SignatureHandlers;
|
|
@@ -105,6 +138,8 @@ declare const __VLS_base: import("vue").DefineComponent<__VLS_Props, {
|
|
|
105
138
|
formEditMode: boolean;
|
|
106
139
|
roleColors: Record<string, string>;
|
|
107
140
|
activeRoleId: string | null;
|
|
141
|
+
locale: KviewerLocale;
|
|
142
|
+
messages: KviewerMessageOverrides;
|
|
108
143
|
}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
|
|
109
144
|
declare const __VLS_export: __VLS_WithSlots<typeof __VLS_base, __VLS_Slots>;
|
|
110
145
|
declare const _default: typeof __VLS_export;
|