kviewer 0.3.2 → 0.3.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/module.d.mts +1 -1
- package/dist/module.json +1 -1
- package/dist/module.mjs +15 -15
- package/dist/runtime/annotation/engine/editor/selector.d.ts +1 -0
- package/dist/runtime/annotation/engine/editor/selector.js +17 -5
- package/dist/runtime/annotation/engine/painter.d.ts +2 -0
- package/dist/runtime/annotation/engine/painter.js +4 -0
- package/dist/runtime/annotation/pdf-export/export-form-fields.js +26 -41
- package/dist/runtime/annotation/pdf-export/print.d.ts +12 -0
- package/dist/runtime/annotation/pdf-export/print.js +63 -0
- package/dist/runtime/assets/kviewer.css +1 -1
- package/dist/runtime/components/AnnotationToolbar.vue +12 -1
- package/dist/runtime/components/FormFieldLayer.vue +7 -2
- package/dist/runtime/components/PdfPage.vue +24 -1
- package/dist/runtime/components/SelectionToolbarItems.d.vue.ts +8 -0
- package/dist/runtime/components/SelectionToolbarItems.vue +43 -0
- package/dist/runtime/components/SelectionToolbarItems.vue.d.ts +8 -0
- package/dist/runtime/components/Viewer.d.vue.ts +17 -1
- package/dist/runtime/components/Viewer.vue +45 -2
- package/dist/runtime/components/Viewer.vue.d.ts +17 -1
- package/dist/runtime/components/ViewerBar.d.vue.ts +4 -4
- package/dist/runtime/components/ViewerBar.vue +2 -1
- package/dist/runtime/components/ViewerBar.vue.d.ts +4 -4
- package/dist/runtime/components/ViewerTabs.d.vue.ts +4 -0
- package/dist/runtime/components/ViewerTabs.vue +2 -0
- package/dist/runtime/components/ViewerTabs.vue.d.ts +4 -0
- package/dist/runtime/components/form-fields/FormFieldWrapper.vue +15 -2
- package/dist/runtime/components/form-fields/PlacedFieldChrome.vue +9 -9
- package/dist/runtime/components/form-fields/PlacedFieldToolbar.d.vue.ts +7 -0
- package/dist/runtime/components/form-fields/PlacedFieldToolbar.vue +51 -0
- package/dist/runtime/components/form-fields/PlacedFieldToolbar.vue.d.ts +7 -0
- package/dist/runtime/components/tools/ToolbarMenu.vue +17 -0
- package/dist/runtime/composables/useScriptingManager.d.ts +5 -0
- package/dist/runtime/composables/useScriptingManager.js +6 -0
- package/dist/runtime/composables/useViewerState.d.ts +2 -0
- package/dist/runtime/composables/useViewerState.js +8 -0
- package/dist/runtime/embed/bridge-client.d.ts +4 -0
- package/dist/runtime/embed/bridge-client.js +6 -0
- package/dist/runtime/embed/bridge-host.d.ts +10 -1
- package/dist/runtime/embed/bridge-host.js +102 -28
- package/dist/runtime/embed/protocol.d.ts +17 -0
- package/dist/runtime/embed/protocol.js +1 -0
- package/dist/runtime/i18n/messages.d.ts +2 -0
- package/dist/runtime/i18n/messages.js +2 -0
- package/dist/runtime/public-types.d.ts +4 -3
- package/dist/runtime/public-types.js +4 -1
- package/dist/runtime/selection-items.d.ts +61 -0
- package/dist/runtime/selection-items.js +1 -0
- package/dist/runtime/toolbar-tools.d.ts +4 -1
- package/dist/types.d.mts +1 -1
- package/package.json +1 -1
package/dist/module.d.mts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import * as _nuxt_schema from '@nuxt/schema';
|
|
2
|
-
export { ActiveTool, AddFormFieldPayload, CheckboxStyle, ExportPdfOptions, FormFieldDefinition, FormFieldOrigin, FormFieldType, FormFieldValue, KVIEWER_EMBED_PROTOCOL_VERSION, KViewerApi, KViewerEmbedBridgeOptions, KViewerEmbedClient, KViewerEmbedClientOptions, KViewerEmbedEventName, KViewerEmbedEventPayloads, KViewerEmbedMethodName, PlacedFieldDefaults, SignatureData, SignatureFieldStatus, SignatureHandlers, ViewMode, ViewerBottomToolId, ViewerLayoutToolId, ViewerMenuButtonItem, ViewerMenuCheckboxItem, ViewerMenuItem, ViewerMenuSeparatorItem, ViewerState, ViewerToolEntry, ViewerToolId, ViewerToolSlot, ViewerTopToolId, useKViewerEmbedBridge } from '../dist/runtime/public-types.js';
|
|
2
|
+
export { ActiveTool, AddFormFieldPayload, CheckboxStyle, ExportPdfOptions, FormFieldDefinition, FormFieldOrigin, FormFieldType, FormFieldValue, IAnnotationStore, KVIEWER_EMBED_PROTOCOL_VERSION, KVIEWER_EMBED_UNSUPPORTED_METHOD_ERROR_NAME, KViewerApi, KViewerEmbedBridgeOptions, KViewerEmbedClient, KViewerEmbedClientOptions, KViewerEmbedEventName, KViewerEmbedEventPayloads, KViewerEmbedMethodName, KViewerHostApi, PlacedFieldDefaults, SignatureData, SignatureFieldStatus, SignatureHandlers, ViewMode, ViewerBottomToolId, ViewerLayoutToolId, ViewerMenuButtonItem, ViewerMenuCheckboxItem, ViewerMenuItem, ViewerMenuSeparatorItem, ViewerSelectionButtonItem, ViewerSelectionContext, ViewerSelectionItem, ViewerSelectionSelectItem, ViewerState, ViewerToolEntry, ViewerToolId, ViewerToolSlot, ViewerTopToolId, useKViewerEmbedBridge } from '../dist/runtime/public-types.js';
|
|
3
3
|
|
|
4
4
|
interface ModuleOptions {
|
|
5
5
|
/**
|
package/dist/module.json
CHANGED
package/dist/module.mjs
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { createRequire } from 'node:module';
|
|
2
2
|
import { dirname, join } from 'node:path';
|
|
3
3
|
import { defineNuxtModule, createResolver, extendViteConfig, addPlugin, addComponentsDir, addImports } from '@nuxt/kit';
|
|
4
|
-
export { KVIEWER_EMBED_PROTOCOL_VERSION, KViewerEmbedClient, useKViewerEmbedBridge } from '../dist/runtime/public-types.js';
|
|
4
|
+
export { KVIEWER_EMBED_PROTOCOL_VERSION, KVIEWER_EMBED_UNSUPPORTED_METHOD_ERROR_NAME, KViewerEmbedClient, useKViewerEmbedBridge } from '../dist/runtime/public-types.js';
|
|
5
5
|
|
|
6
6
|
const module$1 = defineNuxtModule({
|
|
7
7
|
meta: {
|
|
@@ -34,19 +34,14 @@ const module$1 = defineNuxtModule({
|
|
|
34
34
|
config.optimizeDeps ||= {};
|
|
35
35
|
config.optimizeDeps.include ||= [];
|
|
36
36
|
config.optimizeDeps.include.push(
|
|
37
|
-
"konva",
|
|
38
|
-
"pdf-lib",
|
|
39
|
-
"pdfjs-dist/legacy/build/pdf.mjs",
|
|
40
|
-
"pdfjs-dist/web/pdf_viewer.mjs",
|
|
41
|
-
//
|
|
42
|
-
//
|
|
43
|
-
//
|
|
44
|
-
//
|
|
45
|
-
// browser receives the raw pako index.js and throws "does not provide
|
|
46
|
-
// an export named 'default'". The `kviewer >` prefix anchors resolution
|
|
47
|
-
// through this module, so it works even when pnpm leaves fontkit
|
|
48
|
-
// un-hoisted in the consumer (a bare '@pdf-lib/fontkit' entry would not
|
|
49
|
-
// resolve from the consumer root and Vite would silently skip it).
|
|
37
|
+
"kviewer > konva",
|
|
38
|
+
"kviewer > pdf-lib",
|
|
39
|
+
"kviewer > pdfjs-dist/legacy/build/pdf.mjs",
|
|
40
|
+
"kviewer > pdfjs-dist/web/pdf_viewer.mjs",
|
|
41
|
+
// fontkit's ESM build does `import pako from 'pako'`, but pako 1.x is
|
|
42
|
+
// CJS with no `default` export — without pre-bundling (where esbuild
|
|
43
|
+
// synthesizes it) the browser throws "does not provide an export named
|
|
44
|
+
// 'default'".
|
|
50
45
|
"kviewer > @pdf-lib/fontkit"
|
|
51
46
|
);
|
|
52
47
|
});
|
|
@@ -71,7 +66,12 @@ const module$1 = defineNuxtModule({
|
|
|
71
66
|
addImports([
|
|
72
67
|
{ name: "useKViewerEmbedBridge", from: resolve("./runtime/embed/bridge-host") },
|
|
73
68
|
{ name: "KViewerEmbedClient", from: resolve("./runtime/embed/bridge-client") },
|
|
74
|
-
{ name: "KVIEWER_EMBED_PROTOCOL_VERSION", from: resolve("./runtime/embed/protocol") }
|
|
69
|
+
{ name: "KVIEWER_EMBED_PROTOCOL_VERSION", from: resolve("./runtime/embed/protocol") },
|
|
70
|
+
{
|
|
71
|
+
name: "UNSUPPORTED_METHOD_ERROR_NAME",
|
|
72
|
+
as: "KVIEWER_EMBED_UNSUPPORTED_METHOD_ERROR_NAME",
|
|
73
|
+
from: resolve("./runtime/embed/protocol")
|
|
74
|
+
}
|
|
75
75
|
]);
|
|
76
76
|
const require_ = createRequire(import.meta.url);
|
|
77
77
|
const pdfjsDir = dirname(require_.resolve("pdfjs-dist/package.json"));
|
|
@@ -52,6 +52,7 @@ export declare class Selector {
|
|
|
52
52
|
private marqueeRect;
|
|
53
53
|
private marqueeStartPos;
|
|
54
54
|
private marqueeStage;
|
|
55
|
+
private suppressStageClick;
|
|
55
56
|
constructor({ konvaCanvasStore, getAnnotationStore, onDelete, onSelected, onMultiSelected, onMultiChanged, onCancel, onChanged, onRequestDeleteConfirm, onFreeTextDoubleClick, getStylusModeEnabled, setExternalGesturesEnabled, }: ISelectorOptions);
|
|
56
57
|
/** Returns true if this event should be rejected (finger touch in stylus mode). */
|
|
57
58
|
private isStylusRejected;
|
|
@@ -24,6 +24,7 @@ export class Selector {
|
|
|
24
24
|
marqueeRect = null;
|
|
25
25
|
marqueeStartPos = null;
|
|
26
26
|
marqueeStage = null;
|
|
27
|
+
suppressStageClick = false;
|
|
27
28
|
constructor({
|
|
28
29
|
konvaCanvasStore,
|
|
29
30
|
getAnnotationStore,
|
|
@@ -88,13 +89,17 @@ export class Selector {
|
|
|
88
89
|
}
|
|
89
90
|
disableStageEvents(konvaStage) {
|
|
90
91
|
konvaStage.off(
|
|
91
|
-
"click mousedown mousemove mouseup touchstart touchmove touchend pointerdown pointermove pointerup"
|
|
92
|
+
"click tap mousedown mousemove mouseup touchstart touchmove touchend pointerdown pointermove pointerup"
|
|
92
93
|
);
|
|
93
94
|
}
|
|
94
95
|
bindStageEvents(konvaStage) {
|
|
95
96
|
konvaStage.on("click tap", (e) => {
|
|
96
97
|
if (e.target !== konvaStage) return;
|
|
97
98
|
if (this.isStylusRejected(e.evt)) return;
|
|
99
|
+
if (this.suppressStageClick) {
|
|
100
|
+
this.suppressStageClick = false;
|
|
101
|
+
return;
|
|
102
|
+
}
|
|
98
103
|
this.clearTransformers();
|
|
99
104
|
});
|
|
100
105
|
}
|
|
@@ -474,6 +479,7 @@ export class Selector {
|
|
|
474
479
|
this.handleShapeClick(shape, konvaStage, false);
|
|
475
480
|
}
|
|
476
481
|
clear() {
|
|
482
|
+
this.suppressStageClick = false;
|
|
477
483
|
this.clearTransformers();
|
|
478
484
|
this.konvaCanvasStore.forEach((konvaCanvas) => {
|
|
479
485
|
const { konvaStage } = konvaCanvas;
|
|
@@ -489,6 +495,7 @@ export class Selector {
|
|
|
489
495
|
const pageGroups = this.getPageShapeGroups(konvaStage);
|
|
490
496
|
this.disableStageEvents(konvaStage);
|
|
491
497
|
this.bindStageEvents(konvaStage);
|
|
498
|
+
this.bindMarqueeEvents(konvaStage, { mouseOnly: true });
|
|
492
499
|
this.enableShapeGroups(pageGroups, konvaStage);
|
|
493
500
|
if (this.selectedId) {
|
|
494
501
|
this.selectedShape(this.selectedId, konvaStage);
|
|
@@ -519,13 +526,17 @@ export class Selector {
|
|
|
519
526
|
this.enableShapeGroups(pageGroups, konvaStage);
|
|
520
527
|
this.bindMarqueeEvents(konvaStage);
|
|
521
528
|
}
|
|
522
|
-
bindMarqueeEvents(konvaStage) {
|
|
523
|
-
|
|
529
|
+
bindMarqueeEvents(konvaStage, opts = {}) {
|
|
530
|
+
const { mouseOnly = false } = opts;
|
|
531
|
+
konvaStage.on(mouseOnly ? "mousedown" : "mousedown touchstart", (e) => {
|
|
524
532
|
if (e.target !== konvaStage) return;
|
|
525
533
|
if (this.isStylusRejected(e.evt)) return;
|
|
534
|
+
const button = e.evt?.button;
|
|
535
|
+
if (mouseOnly && button !== void 0 && button !== 0) return;
|
|
526
536
|
if (this.getStylusModeEnabled?.() && e.evt) e.evt.preventDefault();
|
|
527
537
|
this.clearTransformers();
|
|
528
538
|
this.selectedIds = [];
|
|
539
|
+
this.marqueeRect?.destroy();
|
|
529
540
|
const pos = konvaStage.getRelativePointerPosition();
|
|
530
541
|
if (!pos) return;
|
|
531
542
|
this.marqueeStartPos = pos;
|
|
@@ -543,7 +554,7 @@ export class Selector {
|
|
|
543
554
|
});
|
|
544
555
|
this.getBackgroundLayer(konvaStage).add(this.marqueeRect);
|
|
545
556
|
});
|
|
546
|
-
konvaStage.on("mousemove touchmove", (e) => {
|
|
557
|
+
konvaStage.on(mouseOnly ? "mousemove" : "mousemove touchmove", (e) => {
|
|
547
558
|
if (!this.marqueeRect || !this.marqueeStartPos || !this.marqueeStage) return;
|
|
548
559
|
if (this.isStylusRejected(e.evt)) return;
|
|
549
560
|
if (this.getStylusModeEnabled?.() && e.evt) e.evt.preventDefault();
|
|
@@ -556,7 +567,7 @@ export class Selector {
|
|
|
556
567
|
this.marqueeRect.setAttrs({ x, y, width, height });
|
|
557
568
|
this.marqueeStage.batchDraw();
|
|
558
569
|
});
|
|
559
|
-
konvaStage.on("mouseup touchend", (e) => {
|
|
570
|
+
konvaStage.on(mouseOnly ? "mouseup" : "mouseup touchend", (e) => {
|
|
560
571
|
if (!this.marqueeRect || !this.marqueeStage) return;
|
|
561
572
|
if (this.isStylusRejected(e.evt)) return;
|
|
562
573
|
const selectionBox = this.marqueeRect.getClientRect();
|
|
@@ -567,6 +578,7 @@ export class Selector {
|
|
|
567
578
|
this.marqueeStage = null;
|
|
568
579
|
return;
|
|
569
580
|
}
|
|
581
|
+
if (mouseOnly) this.suppressStageClick = true;
|
|
570
582
|
const groups = this.getPageShapeGroups(this.marqueeStage);
|
|
571
583
|
const matchedIds = [];
|
|
572
584
|
groups.forEach((group) => {
|
|
@@ -128,6 +128,8 @@ export declare class Painter {
|
|
|
128
128
|
activateEraser(): void;
|
|
129
129
|
highlightRange(range: Range, annotation: IAnnotationType): void;
|
|
130
130
|
selectAnnotation(id: string): void;
|
|
131
|
+
/** Clears the current annotation selection without touching the store. */
|
|
132
|
+
clearSelection(): void;
|
|
131
133
|
initAnnotations(annotations: IAnnotationStore[]): Promise<void>;
|
|
132
134
|
update(id: string, updates: Partial<IAnnotationStore>): void;
|
|
133
135
|
delete(id: string, emit?: boolean): void;
|
|
@@ -624,6 +624,10 @@ export class Painter {
|
|
|
624
624
|
this.selector.select(id);
|
|
625
625
|
this.callbacks.setDefaultMode();
|
|
626
626
|
}
|
|
627
|
+
/** Clears the current annotation selection without touching the store. */
|
|
628
|
+
clearSelection() {
|
|
629
|
+
this.selector.delete();
|
|
630
|
+
}
|
|
627
631
|
async initAnnotations(annotations) {
|
|
628
632
|
annotations.forEach((annotation) => {
|
|
629
633
|
this.saveToStore(annotation, true);
|
|
@@ -6,8 +6,7 @@ import {
|
|
|
6
6
|
PDFDict,
|
|
7
7
|
PDFName,
|
|
8
8
|
PDFRawStream,
|
|
9
|
-
PDFString
|
|
10
|
-
StandardFonts
|
|
9
|
+
PDFString
|
|
11
10
|
} from "pdf-lib";
|
|
12
11
|
import { CHECKBOX_STYLE_TABLE } from "../checkbox-styles.js";
|
|
13
12
|
export async function writeFormFieldsToPdf(pdfDoc, fieldValues, placedDefinitions = []) {
|
|
@@ -265,7 +264,7 @@ async function createSignatureField(pdfDoc, page, def, fv) {
|
|
|
265
264
|
if (def.required) sig.setFlagTo(AcroFieldFlags.Required, true);
|
|
266
265
|
const v = fv?.value;
|
|
267
266
|
const signed = typeof v === "string" && v.startsWith("data:image");
|
|
268
|
-
const appearanceRef = signed ? await buildSignedSignatureAppearance(pdfDoc, v, width, height) :
|
|
267
|
+
const appearanceRef = signed ? await buildSignedSignatureAppearance(pdfDoc, v, width, height) : buildEmptySignatureAppearance(pdfDoc, width, height);
|
|
269
268
|
const widgetDict = context.obj({
|
|
270
269
|
Type: "Annot",
|
|
271
270
|
Subtype: "Widget",
|
|
@@ -282,54 +281,25 @@ async function createSignatureField(pdfDoc, page, def, fv) {
|
|
|
282
281
|
form.acroForm.addField(fieldRef);
|
|
283
282
|
page.node.addAnnot(widgetRef);
|
|
284
283
|
}
|
|
285
|
-
|
|
284
|
+
function buildEmptySignatureAppearance(pdfDoc, width, height) {
|
|
286
285
|
const { context } = pdfDoc;
|
|
287
|
-
const font = await pdfDoc.embedFont(StandardFonts.Helvetica);
|
|
288
|
-
const label = "Sign here";
|
|
289
|
-
const fontSize = Math.max(6, Math.min(12, height * 0.4));
|
|
290
|
-
const textWidth = font.widthOfTextAtSize(label, fontSize);
|
|
291
|
-
const textHeight = font.heightAtSize(fontSize);
|
|
292
|
-
const textX = (width - textWidth) / 2;
|
|
293
|
-
const textY = (height - textHeight) / 2;
|
|
294
|
-
const ops = [
|
|
295
|
-
"q",
|
|
296
|
-
"0.55 0.55 0.55 RG",
|
|
297
|
-
"0.75 w",
|
|
298
|
-
"[3 2] 0 d",
|
|
299
|
-
`0.5 0.5 ${(width - 1).toFixed(3)} ${(height - 1).toFixed(3)} re`,
|
|
300
|
-
"S",
|
|
301
|
-
"0.55 0.55 0.55 rg",
|
|
302
|
-
"BT",
|
|
303
|
-
`/F1 ${fontSize.toFixed(3)} Tf`,
|
|
304
|
-
`${textX.toFixed(3)} ${textY.toFixed(3)} Td`,
|
|
305
|
-
`(${label}) Tj`,
|
|
306
|
-
"ET",
|
|
307
|
-
"Q"
|
|
308
|
-
].join("\n");
|
|
309
286
|
const dict = context.obj({
|
|
310
287
|
Type: "XObject",
|
|
311
288
|
Subtype: "Form",
|
|
312
289
|
BBox: [0, 0, width, height],
|
|
313
290
|
Matrix: [1, 0, 0, 1, 0, 0],
|
|
314
|
-
Resources: {
|
|
291
|
+
Resources: {}
|
|
315
292
|
});
|
|
316
|
-
const stream = PDFRawStream.of(dict, new
|
|
293
|
+
const stream = PDFRawStream.of(dict, new Uint8Array(0));
|
|
317
294
|
return context.register(stream);
|
|
318
295
|
}
|
|
319
296
|
async function buildSignedSignatureAppearance(pdfDoc, dataUrl, width, height) {
|
|
320
297
|
const match = dataUrl.match(/^data:(image\/[a-zA-Z0-9.+-]+);base64,(.+)$/);
|
|
321
|
-
if (!match) return
|
|
298
|
+
if (!match) return buildEmptySignatureAppearance(pdfDoc, width, height);
|
|
322
299
|
const mimeType = match[1] ?? "";
|
|
323
300
|
const base64 = match[2] ?? "";
|
|
324
301
|
const image = mimeType.includes("jpeg") || mimeType.includes("jpg") ? await pdfDoc.embedJpg(base64) : await pdfDoc.embedPng(base64);
|
|
325
|
-
const
|
|
326
|
-
const boxAspect = width / height;
|
|
327
|
-
let drawW = width;
|
|
328
|
-
let drawH = height;
|
|
329
|
-
if (imgAspect > boxAspect) drawH = width / imgAspect;
|
|
330
|
-
else drawW = height * imgAspect;
|
|
331
|
-
const drawX = (width - drawW) / 2;
|
|
332
|
-
const drawY = (height - drawH) / 2;
|
|
302
|
+
const { drawX, drawY, drawW, drawH } = fitImageInBox(image, width, height);
|
|
333
303
|
const ops = [
|
|
334
304
|
"q",
|
|
335
305
|
`${drawW.toFixed(3)} 0 0 ${drawH.toFixed(3)} ${drawX.toFixed(3)} ${drawY.toFixed(3)} cm`,
|
|
@@ -346,6 +316,20 @@ async function buildSignedSignatureAppearance(pdfDoc, dataUrl, width, height) {
|
|
|
346
316
|
const stream = PDFRawStream.of(dict, new TextEncoder().encode(ops));
|
|
347
317
|
return pdfDoc.context.register(stream);
|
|
348
318
|
}
|
|
319
|
+
function fitImageInBox(image, boxW, boxH) {
|
|
320
|
+
const imgAspect = image.width / image.height;
|
|
321
|
+
const boxAspect = boxW / boxH;
|
|
322
|
+
let drawW = boxW;
|
|
323
|
+
let drawH = boxH;
|
|
324
|
+
if (imgAspect > boxAspect) drawH = boxW / imgAspect;
|
|
325
|
+
else drawW = boxH * imgAspect;
|
|
326
|
+
return {
|
|
327
|
+
drawX: (boxW - drawW) / 2,
|
|
328
|
+
drawY: (boxH - drawH) / 2,
|
|
329
|
+
drawW,
|
|
330
|
+
drawH
|
|
331
|
+
};
|
|
332
|
+
}
|
|
349
333
|
async function embedSignatureImage(pdfDoc, field) {
|
|
350
334
|
const dataUrl = field.value;
|
|
351
335
|
const match = dataUrl.match(/^data:(image\/[a-zA-Z0-9.+-]+);base64,(.+)$/);
|
|
@@ -370,11 +354,12 @@ async function embedSignatureImage(pdfDoc, field) {
|
|
|
370
354
|
const pageRef = page.ref;
|
|
371
355
|
const widgetPage = widget.P();
|
|
372
356
|
if (widgetPage === pageRef || !widgetPage) {
|
|
357
|
+
const { drawX, drawY, drawW, drawH } = fitImageInBox(image, rect.width, rect.height);
|
|
373
358
|
page.drawImage(image, {
|
|
374
|
-
x: rect.x,
|
|
375
|
-
y: rect.y,
|
|
376
|
-
width:
|
|
377
|
-
height:
|
|
359
|
+
x: rect.x + drawX,
|
|
360
|
+
y: rect.y + drawY,
|
|
361
|
+
width: drawW,
|
|
362
|
+
height: drawH
|
|
378
363
|
});
|
|
379
364
|
break;
|
|
380
365
|
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Print a PDF by handing the actual (vector) bytes to the browser's native
|
|
3
|
+
* PDF pipeline, instead of letting the print dialog rasterize the on-screen
|
|
4
|
+
* canvases: a hidden iframe loads the blob and `contentWindow.print()` opens
|
|
5
|
+
* the dialog with the full-resolution document.
|
|
6
|
+
*
|
|
7
|
+
* On iOS/iPadOS Safari an iframe renders only the first PDF page, so there
|
|
8
|
+
* the blob opens in a new tab and the user prints from the native preview
|
|
9
|
+
* (share sheet → Print). Same fallback if `print()` throws (some browsers
|
|
10
|
+
* refuse to script-print plugin documents).
|
|
11
|
+
*/
|
|
12
|
+
export declare function printPdfBytes(data: Uint8Array): Promise<void>;
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import { isIPad } from "../../annotation/engine/input-device.js";
|
|
2
|
+
let activeFrame = null;
|
|
3
|
+
let activeUrl = null;
|
|
4
|
+
function reclaimPrevious() {
|
|
5
|
+
activeFrame?.remove();
|
|
6
|
+
activeFrame = null;
|
|
7
|
+
if (activeUrl) {
|
|
8
|
+
URL.revokeObjectURL(activeUrl);
|
|
9
|
+
activeUrl = null;
|
|
10
|
+
}
|
|
11
|
+
}
|
|
12
|
+
function openInNewTab(url) {
|
|
13
|
+
const win = window.open(url, "_blank");
|
|
14
|
+
if (!win) {
|
|
15
|
+
throw new Error(
|
|
16
|
+
"Could not open the PDF for printing \u2014 the popup was blocked. Allow popups for this site and try again."
|
|
17
|
+
);
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
export async function printPdfBytes(data) {
|
|
21
|
+
reclaimPrevious();
|
|
22
|
+
const arrayBuffer = Uint8Array.from(data).buffer;
|
|
23
|
+
const blob = new Blob([arrayBuffer], { type: "application/pdf" });
|
|
24
|
+
const url = URL.createObjectURL(blob);
|
|
25
|
+
activeUrl = url;
|
|
26
|
+
const isAppleMobile = isIPad() || /iPhone|iPod/.test(navigator.userAgent);
|
|
27
|
+
if (isAppleMobile) {
|
|
28
|
+
openInNewTab(url);
|
|
29
|
+
return;
|
|
30
|
+
}
|
|
31
|
+
const frame = document.createElement("iframe");
|
|
32
|
+
frame.style.position = "fixed";
|
|
33
|
+
frame.style.right = "0";
|
|
34
|
+
frame.style.bottom = "0";
|
|
35
|
+
frame.style.width = "1px";
|
|
36
|
+
frame.style.height = "1px";
|
|
37
|
+
frame.style.border = "0";
|
|
38
|
+
frame.style.visibility = "hidden";
|
|
39
|
+
frame.src = url;
|
|
40
|
+
activeFrame = frame;
|
|
41
|
+
await new Promise((resolve, reject) => {
|
|
42
|
+
const timer = setTimeout(() => {
|
|
43
|
+
reject(new Error("Timed out loading the PDF for printing"));
|
|
44
|
+
}, 15e3);
|
|
45
|
+
frame.addEventListener(
|
|
46
|
+
"load",
|
|
47
|
+
() => {
|
|
48
|
+
clearTimeout(timer);
|
|
49
|
+
resolve();
|
|
50
|
+
},
|
|
51
|
+
{ once: true }
|
|
52
|
+
);
|
|
53
|
+
document.body.appendChild(frame);
|
|
54
|
+
});
|
|
55
|
+
try {
|
|
56
|
+
const win = frame.contentWindow;
|
|
57
|
+
if (!win) throw new Error("Print frame has no window");
|
|
58
|
+
win.focus();
|
|
59
|
+
win.print();
|
|
60
|
+
} catch {
|
|
61
|
+
openInNewTab(url);
|
|
62
|
+
}
|
|
63
|
+
}
|
|
@@ -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:color-mix(in srgb,var(--kvw-field-color,#1677ff) 14%,transparent);border:1px solid transparent;box-sizing:border-box;color:#000;display:block;font-family:-apple-system,BlinkMacSystemFont,Helvetica Neue,Helvetica,Arial,sans-serif;font-weight:500;height:100%;line-height:normal;margin:0;outline:none;padding:1px 2px;resize:none;width:100%;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale;text-rendering:optimizeLegibility;vertical-align:top}.kviewer-form-input:hover{background-color:color-mix(in srgb,var(--kvw-field-color,#1677ff) 22%,transparent);border-color:rgba(0,0,0,.2)}.kviewer-form-input:focus{background-color:color-mix(in srgb,var(--kvw-field-color,#1677ff) 28%,transparent);border-color:var(--kvw-field-color,var(--color-primary,#3b82f6));outline:2px solid var(--kvw-field-color,var(--color-primary,#3b82f6))}.kviewer-form-uselect{align-items:center;background-color:color-mix(in srgb,var(--kvw-field-color,#1677ff) 14%,transparent);border:1px solid transparent;border-radius:0;box-sizing:border-box;color:#000;cursor:pointer;display:flex;height:100%;line-height:1;padding:0 11px 0 2px;width:100%}.kviewer-form-uselect:hover{background-color:color-mix(in srgb,var(--kvw-field-color,#1677ff) 22%,transparent);border-color:rgba(0,0,0,.2)}.kviewer-form-uselect:focus-visible{background-color:color-mix(in srgb,var(--kvw-field-color,#1677ff) 28%,transparent);outline:2px solid var(--kvw-field-color,var(--color-primary,#3b82f6));outline-offset:-2px}.kviewer-form-uselect:disabled{cursor:default}.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(--kvw-field-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(--kvw-field-color,var(--color-primary,#3b82f6));cursor:pointer;height:80%;margin:0;max-height:18px;max-width:18px;width:80%}.kviewer-form-button{align-items:center;background:#c8c8c8;border:1px solid rgba(0,0,0,.35);box-sizing:border-box;color:#000;cursor:pointer;display:flex;font-family:inherit;font-weight:700;height:100%;justify-content:center;line-height:1;padding:0 6px;text-align:center;width:100%;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}.kviewer-form-button:hover{background:#bdbdbd}.kviewer-form-button:active{background:#b0b0b0}.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:color-mix(in srgb,var(--kvw-field-color,#3b82f6) 8%,transparent);border-color:var(--kvw-field-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}.kviewer-form-signature--static{cursor:default}.kviewer-form-signature--static:hover{background:rgba(255,255,200,.15);border-color:rgba(0,0,0,.2)}.kviewer-form-signature__marker{align-items:center;display:inline-flex;gap:4px}.kviewer-form-signature__marker-icon{flex:0 0 auto;height:12px;width:12px}
|
|
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:color-mix(in srgb,var(--kvw-field-color,#1677ff) 14%,transparent);border:1px solid transparent;box-sizing:border-box;color:#000;display:block;font-family:-apple-system,BlinkMacSystemFont,Helvetica Neue,Helvetica,Arial,sans-serif;font-weight:500;height:100%;line-height:normal;margin:0;outline:none;padding:1px 2px;resize:none;width:100%;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale;text-rendering:optimizeLegibility;vertical-align:top}.kviewer-form-input:hover{background-color:color-mix(in srgb,var(--kvw-field-color,#1677ff) 22%,transparent);border-color:rgba(0,0,0,.2)}.kviewer-form-input:focus{background-color:color-mix(in srgb,var(--kvw-field-color,#1677ff) 28%,transparent);border-color:var(--kvw-field-color,var(--color-primary,#3b82f6));outline:2px solid var(--kvw-field-color,var(--color-primary,#3b82f6))}.kviewer-form-uselect{align-items:center;background-color:color-mix(in srgb,var(--kvw-field-color,#1677ff) 14%,transparent);border:1px solid transparent;border-radius:0;box-sizing:border-box;color:#000;cursor:pointer;display:flex;height:100%;line-height:1;padding:0 11px 0 2px;width:100%}.kviewer-form-uselect:hover{background-color:color-mix(in srgb,var(--kvw-field-color,#1677ff) 22%,transparent);border-color:rgba(0,0,0,.2)}.kviewer-form-uselect:focus-visible{background-color:color-mix(in srgb,var(--kvw-field-color,#1677ff) 28%,transparent);outline:2px solid var(--kvw-field-color,var(--color-primary,#3b82f6));outline-offset:-2px}.kviewer-form-uselect:disabled{cursor:default}.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(--kvw-field-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(--kvw-field-color,var(--color-primary,#3b82f6));cursor:pointer;height:80%;margin:0;max-height:18px;max-width:18px;width:80%}.kviewer-form-button{align-items:center;background:#c8c8c8;border:1px solid rgba(0,0,0,.35);box-sizing:border-box;color:#000;cursor:pointer;display:flex;font-family:inherit;font-weight:700;height:100%;justify-content:center;line-height:1;padding:0 6px;text-align:center;width:100%;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}.kviewer-form-button:hover{background:#bdbdbd}.kviewer-form-button:active{background:#b0b0b0}.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:color-mix(in srgb,var(--kvw-field-color,#3b82f6) 8%,transparent);border-color:var(--kvw-field-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}.kviewer-form-field__body--locked .kviewer-form-signature,.kviewer-form-field__body--locked .kviewer-form-signature:hover{border-color:transparent}.kviewer-form-signature--static{cursor:default}.kviewer-form-signature--static:hover{background:rgba(255,255,200,.15);border-color:rgba(0,0,0,.2)}.kviewer-form-signature__marker{align-items:center;display:inline-flex;gap:4px}.kviewer-form-signature__marker-icon{flex:0 0 auto;height:12px;width:12px}
|
|
@@ -55,6 +55,9 @@
|
|
|
55
55
|
<UIcon name="i-lucide-pencil" class="text-sm text-muted" />
|
|
56
56
|
</button>
|
|
57
57
|
|
|
58
|
+
<!-- Host-supplied custom items (selectionItems prop) -->
|
|
59
|
+
<SelectionToolbarItems :items="customItems" :ctx="selectionCtx" />
|
|
60
|
+
|
|
58
61
|
<!-- Delete -->
|
|
59
62
|
<button
|
|
60
63
|
class="flex items-center justify-center w-7 h-7 rounded-md hover:bg-default cursor-pointer"
|
|
@@ -69,12 +72,20 @@
|
|
|
69
72
|
</template>
|
|
70
73
|
|
|
71
74
|
<script setup>
|
|
72
|
-
import { computed } from "vue";
|
|
75
|
+
import { computed, inject } from "vue";
|
|
73
76
|
import { useViewerState } from "../composables/useViewerState";
|
|
74
77
|
import { useKviewerI18n } from "../composables/useKviewerI18n";
|
|
75
78
|
import { defaultOptions, annotationDefinitions } from "../annotation/engine/config";
|
|
76
79
|
import { AnnotationType } from "../annotation/engine/types";
|
|
80
|
+
import { SELECTION_ITEMS_KEY } from "../selection-items";
|
|
81
|
+
import SelectionToolbarItems from "./SelectionToolbarItems.vue";
|
|
77
82
|
const state = useViewerState();
|
|
83
|
+
const customItems = inject(SELECTION_ITEMS_KEY, void 0);
|
|
84
|
+
const selectionCtx = computed(() => ({
|
|
85
|
+
kind: "annotation",
|
|
86
|
+
annotations: state.selectedAnnotations.value.length > 0 ? state.selectedAnnotations.value : state.selectedAnnotation.value ? [state.selectedAnnotation.value] : [],
|
|
87
|
+
field: null
|
|
88
|
+
}));
|
|
78
89
|
const { t } = useKviewerI18n();
|
|
79
90
|
const colors = defaultOptions.colors;
|
|
80
91
|
const isMultiSelect = computed(() => state.selectedAnnotations.value.length > 1);
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
<div
|
|
3
3
|
v-if="fields.length > 0"
|
|
4
4
|
class="kviewer-form-layer"
|
|
5
|
+
:class="{ 'kviewer-form-layer--interactive': props.pointerEvents === 'auto' }"
|
|
5
6
|
:style="{
|
|
6
7
|
position: 'absolute',
|
|
7
8
|
top: 0,
|
|
@@ -11,7 +12,6 @@
|
|
|
11
12
|
transform: `scale(${props.scale})`,
|
|
12
13
|
transformOrigin: '0 0',
|
|
13
14
|
zIndex: props.zIndex,
|
|
14
|
-
pointerEvents: props.pointerEvents,
|
|
15
15
|
// Force native form controls (inputs, checkboxes, radios, scrollbars)
|
|
16
16
|
// to render in light mode regardless of the user\'s OS theme — PDF
|
|
17
17
|
// pages have a white background, so dark-mode controls clash.
|
|
@@ -57,7 +57,8 @@ watch(() => state.formEditMode.value, (enabled) => {
|
|
|
57
57
|
});
|
|
58
58
|
watch(() => state.activeTool.value, (tool) => {
|
|
59
59
|
const isFormFieldTool = typeof tool === "number" && (tool === AnnotationType.FORM_TEXT || tool === AnnotationType.FORM_CHECKBOX || tool === AnnotationType.FORM_RADIO || tool === AnnotationType.FORM_SIGNATURE);
|
|
60
|
-
|
|
60
|
+
const keepsSelection = tool === "hand" || tool === AnnotationType.SELECT || isFormFieldTool;
|
|
61
|
+
if (!keepsSelection) formFields.selectPlacedField(null);
|
|
61
62
|
});
|
|
62
63
|
function onKeyDown(e) {
|
|
63
64
|
if (!selectedFieldId.value) return;
|
|
@@ -75,3 +76,7 @@ onBeforeUnmount(() => {
|
|
|
75
76
|
window.removeEventListener("keydown", onKeyDown);
|
|
76
77
|
});
|
|
77
78
|
</script>
|
|
79
|
+
|
|
80
|
+
<style>
|
|
81
|
+
.kviewer-form-layer{pointer-events:none}.kviewer-form-layer--interactive .kviewer-form-field{pointer-events:auto}.kviewer-form-layer:not(.kviewer-form-layer--interactive) .kviewer-form-field,.kviewer-form-layer:not(.kviewer-form-layer--interactive) .kviewer-placed-chrome,.kviewer-form-layer:not(.kviewer-form-layer--interactive) .kviewer-placed-toolbar{pointer-events:none}
|
|
82
|
+
</style>
|
|
@@ -63,6 +63,7 @@
|
|
|
63
63
|
height: cssHeight + 'px',
|
|
64
64
|
zIndex: 3,
|
|
65
65
|
pointerEvents: annotationPointerEvents,
|
|
66
|
+
cursor: annotationCursor,
|
|
66
67
|
transform: 'translateZ(0)',
|
|
67
68
|
overflow: 'hidden'
|
|
68
69
|
}"
|
|
@@ -134,9 +135,31 @@ const annotationPointerEvents = computed(() => {
|
|
|
134
135
|
if (state.readonly.value) return "none";
|
|
135
136
|
return isSearchTextInteractive.value ? "none" : isAnnotating.value ? "auto" : "none";
|
|
136
137
|
});
|
|
138
|
+
const annotationCursor = computed(() => {
|
|
139
|
+
const tool = state.activeTool.value;
|
|
140
|
+
if (tool === "marquee" || tool === "eraser") return "crosshair";
|
|
141
|
+
if (typeof tool !== "number") return "";
|
|
142
|
+
switch (tool) {
|
|
143
|
+
case AnnotationType.NONE:
|
|
144
|
+
case AnnotationType.SELECT:
|
|
145
|
+
return "";
|
|
146
|
+
// Stamp/signature hide the native cursor behind a floating image
|
|
147
|
+
// preview (body cursor 'none'); an element cursor here would
|
|
148
|
+
// override that and bring the arrow back on top of the preview.
|
|
149
|
+
case AnnotationType.STAMP:
|
|
150
|
+
case AnnotationType.SIGNATURE:
|
|
151
|
+
return "";
|
|
152
|
+
case AnnotationType.HIGHLIGHT:
|
|
153
|
+
case AnnotationType.STRIKEOUT:
|
|
154
|
+
case AnnotationType.UNDERLINE:
|
|
155
|
+
return "text";
|
|
156
|
+
default:
|
|
157
|
+
return "crosshair";
|
|
158
|
+
}
|
|
159
|
+
});
|
|
137
160
|
const isUsingAnnotationTool = computed(() => {
|
|
138
161
|
const tool = state.activeTool.value;
|
|
139
|
-
return tool === "marquee" || tool === "eraser" || typeof tool === "number" && tool !== AnnotationType.NONE;
|
|
162
|
+
return tool === "marquee" || tool === "eraser" || typeof tool === "number" && tool !== AnnotationType.NONE && tool !== AnnotationType.SELECT;
|
|
140
163
|
});
|
|
141
164
|
const formFieldPointerEvents = computed(() => {
|
|
142
165
|
if (state.readonly.value) return "none";
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { ViewerSelectionContext, ViewerSelectionItem } from '../selection-items.js';
|
|
2
|
+
type __VLS_Props = {
|
|
3
|
+
items: ViewerSelectionItem[] | undefined;
|
|
4
|
+
ctx: ViewerSelectionContext;
|
|
5
|
+
};
|
|
6
|
+
declare const __VLS_export: import("vue").DefineComponent<__VLS_Props, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<__VLS_Props> & Readonly<{}>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
|
|
7
|
+
declare const _default: typeof __VLS_export;
|
|
8
|
+
export default _default;
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<template v-for="item in visibleItems" :key="item.key">
|
|
3
|
+
<button
|
|
4
|
+
v-if="item.type === 'button'"
|
|
5
|
+
class="flex items-center justify-center h-7 rounded-md hover:bg-default cursor-pointer disabled:opacity-50 disabled:cursor-not-allowed"
|
|
6
|
+
:class="item.icon ? 'w-7' : 'px-2'"
|
|
7
|
+
:title="item.label"
|
|
8
|
+
:disabled="item.disabled"
|
|
9
|
+
:data-testid="`selection-item-${item.key}`"
|
|
10
|
+
@click="item.onSelect(props.ctx)"
|
|
11
|
+
>
|
|
12
|
+
<UIcon v-if="item.icon" :name="item.icon" class="text-sm text-muted" />
|
|
13
|
+
<span v-else class="text-xs text-muted whitespace-nowrap">{{ item.label }}</span>
|
|
14
|
+
</button>
|
|
15
|
+
<USelect
|
|
16
|
+
v-else-if="item.type === 'select'"
|
|
17
|
+
:model-value="selectValue(item)"
|
|
18
|
+
:items="item.items"
|
|
19
|
+
:placeholder="item.placeholder"
|
|
20
|
+
:disabled="item.disabled"
|
|
21
|
+
:aria-label="item.label"
|
|
22
|
+
:title="item.label"
|
|
23
|
+
class="w-24"
|
|
24
|
+
size="sm"
|
|
25
|
+
:data-testid="`selection-item-${item.key}`"
|
|
26
|
+
@update:model-value="(v) => item.onUpdate(String(v), props.ctx)"
|
|
27
|
+
/>
|
|
28
|
+
</template>
|
|
29
|
+
</template>
|
|
30
|
+
|
|
31
|
+
<script setup>
|
|
32
|
+
import { computed } from "vue";
|
|
33
|
+
const props = defineProps({
|
|
34
|
+
items: { type: null, required: true },
|
|
35
|
+
ctx: { type: Object, required: true }
|
|
36
|
+
});
|
|
37
|
+
const visibleItems = computed(
|
|
38
|
+
() => (props.items ?? []).filter((item) => item.visible?.(props.ctx) ?? true)
|
|
39
|
+
);
|
|
40
|
+
function selectValue(item) {
|
|
41
|
+
return typeof item.value === "function" ? item.value(props.ctx) : item.value;
|
|
42
|
+
}
|
|
43
|
+
</script>
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { ViewerSelectionContext, ViewerSelectionItem } from '../selection-items.js';
|
|
2
|
+
type __VLS_Props = {
|
|
3
|
+
items: ViewerSelectionItem[] | undefined;
|
|
4
|
+
ctx: ViewerSelectionContext;
|
|
5
|
+
};
|
|
6
|
+
declare const __VLS_export: import("vue").DefineComponent<__VLS_Props, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<__VLS_Props> & Readonly<{}>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
|
|
7
|
+
declare const _default: typeof __VLS_export;
|
|
8
|
+
export default _default;
|
|
@@ -3,6 +3,7 @@ import type { AddFormFieldPayload, FormFieldDefinition, IAnnotationStore, Placed
|
|
|
3
3
|
import { type ExportPdfOptions } from '../annotation/pdf-export/export.js';
|
|
4
4
|
import type { ViewerMenuItem } from '../menu-items.js';
|
|
5
5
|
import type { ViewerToolEntry } from '../toolbar-tools.js';
|
|
6
|
+
import { type ViewerSelectionItem } from '../selection-items.js';
|
|
6
7
|
import { type KviewerMessageOverrides } from '../composables/useKviewerI18n.js';
|
|
7
8
|
import { type KviewerLocale } from '../i18n/messages.js';
|
|
8
9
|
type __VLS_Props = {
|
|
@@ -63,6 +64,11 @@ type __VLS_Props = {
|
|
|
63
64
|
* toggles or actions without replacing the entire header. Ignored
|
|
64
65
|
* when the host supplies a custom `#header` slot. */
|
|
65
66
|
menuItems?: ViewerMenuItem[];
|
|
67
|
+
/** Custom controls appended to the floating selection popover — shown
|
|
68
|
+
* when an annotation or a placed form field (e.g. a signature field)
|
|
69
|
+
* is selected. Buttons and selects; every callback receives the
|
|
70
|
+
* current selection. See {@link ViewerSelectionItem}. */
|
|
71
|
+
selectionItems?: ViewerSelectionItem[];
|
|
66
72
|
/** Configures which toolbar tools render and in what order. Entries are
|
|
67
73
|
* built-in tool IDs (strings) or `{ type: 'slot', ... }` objects that
|
|
68
74
|
* inject a custom button via a `#tool-<name>` slot. When omitted, the
|
|
@@ -78,6 +84,11 @@ type __VLS_Props = {
|
|
|
78
84
|
messages?: KviewerMessageOverrides;
|
|
79
85
|
};
|
|
80
86
|
declare function exportPdf(options?: ExportPdfOptions): Promise<Uint8Array>;
|
|
87
|
+
/** Print via the real PDF instead of the on-screen canvases: export the
|
|
88
|
+
* document (annotations + form values baked in) and hand the vector bytes
|
|
89
|
+
* to the browser's native PDF print pipeline. Screen canvases are sized
|
|
90
|
+
* for the monitor and would come out blurry on paper. */
|
|
91
|
+
declare function printPdf(options?: ExportPdfOptions): Promise<void>;
|
|
81
92
|
type ImportMode = 'replace' | 'merge';
|
|
82
93
|
declare function importAnnotations(annotations: IAnnotationStore[], options?: {
|
|
83
94
|
mode?: ImportMode;
|
|
@@ -100,6 +111,10 @@ declare const __VLS_base: import("vue").DefineComponent<__VLS_Props, {
|
|
|
100
111
|
getAnnotations: () => IAnnotationStore[];
|
|
101
112
|
importAnnotations: typeof importAnnotations;
|
|
102
113
|
exportPdf: typeof exportPdf;
|
|
114
|
+
/** Export the document (same options as `exportPdf`, minus `download`)
|
|
115
|
+
* and open the browser's print dialog on the resulting vector PDF —
|
|
116
|
+
* full print quality, unlike printing the screen canvases. */
|
|
117
|
+
printPdf: typeof printPdf;
|
|
103
118
|
getKonvaCanvasState: typeof getKonvaCanvasState;
|
|
104
119
|
getFormFieldValues: () => import("../annotation/engine/types.js").FormFieldValue[];
|
|
105
120
|
setFormFieldValue: (fieldName: string, value: string | boolean | string[]) => void;
|
|
@@ -180,6 +195,7 @@ declare const __VLS_base: import("vue").DefineComponent<__VLS_Props, {
|
|
|
180
195
|
clickToSign: boolean;
|
|
181
196
|
readonly: boolean;
|
|
182
197
|
active: boolean;
|
|
198
|
+
formEditMode: boolean;
|
|
183
199
|
locale: KviewerLocale;
|
|
184
200
|
menuItems: ViewerMenuItem[];
|
|
185
201
|
tools: ViewerToolEntry[];
|
|
@@ -188,11 +204,11 @@ declare const __VLS_base: import("vue").DefineComponent<__VLS_Props, {
|
|
|
188
204
|
signatureHandlers: SignatureHandlers;
|
|
189
205
|
viewMode: ViewMode;
|
|
190
206
|
shapeDetection: boolean;
|
|
191
|
-
formEditMode: boolean;
|
|
192
207
|
roleColors: Record<string, string>;
|
|
193
208
|
activeRoleId: string | null;
|
|
194
209
|
editablePlacedFields: boolean;
|
|
195
210
|
fieldDefaults: PlacedFieldDefaults;
|
|
211
|
+
selectionItems: ViewerSelectionItem[];
|
|
196
212
|
messages: KviewerMessageOverrides;
|
|
197
213
|
}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
|
|
198
214
|
declare const __VLS_export: __VLS_WithSlots<typeof __VLS_base, __VLS_Slots>;
|