kviewer 0.0.10 → 0.1.0
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 +29 -0
- package/dist/module.d.mts +9 -1
- package/dist/module.json +1 -1
- package/dist/module.mjs +25 -1
- package/dist/runtime/annotation/checkbox-styles.d.ts +19 -0
- package/dist/runtime/annotation/checkbox-styles.js +27 -0
- package/dist/runtime/annotation/engine/config.js +44 -0
- package/dist/runtime/annotation/engine/painter.d.ts +12 -2
- package/dist/runtime/annotation/engine/painter.js +36 -6
- package/dist/runtime/annotation/engine/tools/form-field.d.ts +30 -0
- package/dist/runtime/annotation/engine/tools/form-field.js +119 -0
- package/dist/runtime/annotation/engine/types.d.ts +118 -1
- package/dist/runtime/annotation/engine/types.js +4 -0
- package/dist/runtime/annotation/font-style.d.ts +23 -0
- package/dist/runtime/annotation/font-style.js +57 -0
- package/dist/runtime/annotation/parsers/extractCheckboxStyles.d.ts +21 -0
- package/dist/runtime/annotation/parsers/extractCheckboxStyles.js +75 -0
- package/dist/runtime/annotation/parsers/parseFormFields.js +16 -3
- package/dist/runtime/annotation/pdf-export/export-form-fields.d.ts +8 -5
- package/dist/runtime/annotation/pdf-export/export-form-fields.js +295 -1
- package/dist/runtime/annotation/pdf-export/export.d.ts +3 -2
- package/dist/runtime/annotation/pdf-export/export.js +9 -2
- package/dist/runtime/assets/kviewer.css +1 -1
- package/dist/runtime/components/FormFieldLayer.d.vue.ts +5 -0
- package/dist/runtime/components/FormFieldLayer.vue +40 -3
- package/dist/runtime/components/FormFieldLayer.vue.d.ts +5 -0
- package/dist/runtime/components/PdfPage.vue +30 -1
- package/dist/runtime/components/Viewer.d.vue.ts +43 -4
- package/dist/runtime/components/Viewer.vue +106 -7
- package/dist/runtime/components/Viewer.vue.d.ts +43 -4
- package/dist/runtime/components/ViewerBar.vue +24 -2
- package/dist/runtime/components/ViewerTabs.d.vue.ts +15 -6
- package/dist/runtime/components/ViewerTabs.vue +7 -2
- package/dist/runtime/components/ViewerTabs.vue.d.ts +15 -6
- package/dist/runtime/components/form-fields/FormButton.vue +16 -4
- package/dist/runtime/components/form-fields/FormCheckbox.vue +31 -15
- package/dist/runtime/components/form-fields/FormDropdown.vue +3 -1
- package/dist/runtime/components/form-fields/FormFieldWrapper.d.vue.ts +12 -1
- package/dist/runtime/components/form-fields/FormFieldWrapper.vue +53 -10
- package/dist/runtime/components/form-fields/FormFieldWrapper.vue.d.ts +12 -1
- package/dist/runtime/components/form-fields/FormRadioButton.vue +3 -1
- package/dist/runtime/components/form-fields/FormSignatureField.vue +2 -1
- package/dist/runtime/components/form-fields/FormTextField.vue +47 -7
- package/dist/runtime/components/form-fields/PlacedFieldChrome.d.vue.ts +16 -0
- package/dist/runtime/components/form-fields/PlacedFieldChrome.vue +131 -0
- package/dist/runtime/components/form-fields/PlacedFieldChrome.vue.d.ts +16 -0
- package/dist/runtime/components/modals/SignatureDrawModal.vue +70 -15
- package/dist/runtime/components/panels/PlacedFieldSidebar.d.vue.ts +3 -0
- package/dist/runtime/components/panels/PlacedFieldSidebar.vue +505 -0
- package/dist/runtime/components/panels/PlacedFieldSidebar.vue.d.ts +3 -0
- package/dist/runtime/components/tools/FormFieldTools.d.vue.ts +3 -0
- package/dist/runtime/components/tools/FormFieldTools.vue +35 -0
- package/dist/runtime/components/tools/FormFieldTools.vue.d.ts +3 -0
- package/dist/runtime/composables/useAnnotationEngine.d.ts +2 -0
- package/dist/runtime/composables/useAnnotationEngine.js +3 -1
- package/dist/runtime/composables/useFormFields.d.ts +48 -2
- package/dist/runtime/composables/useFormFields.js +338 -6
- package/dist/runtime/composables/useInertiaPanzoom.js +4 -0
- package/dist/runtime/composables/usePageVirtualization.d.ts +6 -0
- package/dist/runtime/composables/usePageVirtualization.js +11 -3
- package/dist/runtime/composables/useViewerState.d.ts +3 -0
- package/dist/runtime/composables/useViewerState.js +22 -1
- package/dist/runtime/public-types.d.ts +1 -1
- package/dist/types.d.mts +1 -1
- package/package.json +2 -2
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<div class="flex items-center gap-1">
|
|
3
|
+
<ToolButton
|
|
4
|
+
:tool="getDef(AnnotationType.FORM_TEXT)"
|
|
5
|
+
:active="state.activeTool.value === AnnotationType.FORM_TEXT"
|
|
6
|
+
@click="state.selectTool(AnnotationType.FORM_TEXT)"
|
|
7
|
+
/>
|
|
8
|
+
<ToolButton
|
|
9
|
+
:tool="getDef(AnnotationType.FORM_CHECKBOX)"
|
|
10
|
+
:active="state.activeTool.value === AnnotationType.FORM_CHECKBOX"
|
|
11
|
+
@click="state.selectTool(AnnotationType.FORM_CHECKBOX)"
|
|
12
|
+
/>
|
|
13
|
+
<ToolButton
|
|
14
|
+
:tool="getDef(AnnotationType.FORM_RADIO)"
|
|
15
|
+
:active="state.activeTool.value === AnnotationType.FORM_RADIO"
|
|
16
|
+
@click="state.selectTool(AnnotationType.FORM_RADIO)"
|
|
17
|
+
/>
|
|
18
|
+
<ToolButton
|
|
19
|
+
:tool="getDef(AnnotationType.FORM_SIGNATURE)"
|
|
20
|
+
:active="state.activeTool.value === AnnotationType.FORM_SIGNATURE"
|
|
21
|
+
@click="state.selectTool(AnnotationType.FORM_SIGNATURE)"
|
|
22
|
+
/>
|
|
23
|
+
</div>
|
|
24
|
+
</template>
|
|
25
|
+
|
|
26
|
+
<script setup>
|
|
27
|
+
import { useViewerState } from "../../composables/useViewerState";
|
|
28
|
+
import { AnnotationType } from "../../annotation/engine/types";
|
|
29
|
+
import { annotationDefinitions } from "../../annotation/engine/config";
|
|
30
|
+
import ToolButton from "../ToolButton.vue";
|
|
31
|
+
const state = useViewerState();
|
|
32
|
+
function getDef(type) {
|
|
33
|
+
return annotationDefinitions.find((d) => d.type === type);
|
|
34
|
+
}
|
|
35
|
+
</script>
|
|
@@ -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;
|
|
@@ -4,6 +4,8 @@ export declare function createAnnotationEngine(viewerState: ViewerState, options
|
|
|
4
4
|
userName?: string;
|
|
5
5
|
onRequestTextInput: PainterCallbacks['onRequestTextInput'];
|
|
6
6
|
onRequestDeleteConfirm?: PainterCallbacks['onRequestDeleteConfirm'];
|
|
7
|
+
onPlaceField?: PainterCallbacks['onPlaceField'];
|
|
8
|
+
getFormFieldPreviewColor?: PainterCallbacks['getFormFieldPreviewColor'];
|
|
7
9
|
freehandGroupingDelay?: number;
|
|
8
10
|
setExternalGesturesEnabled?: (enabled: boolean) => void;
|
|
9
11
|
}): Painter;
|
|
@@ -60,7 +60,9 @@ export function createAnnotationEngine(viewerState, options) {
|
|
|
60
60
|
viewerState.history.commit();
|
|
61
61
|
},
|
|
62
62
|
onRequestTextInput: options.onRequestTextInput,
|
|
63
|
-
onRequestDeleteConfirm: options.onRequestDeleteConfirm
|
|
63
|
+
onRequestDeleteConfirm: options.onRequestDeleteConfirm,
|
|
64
|
+
onPlaceField: options.onPlaceField,
|
|
65
|
+
getFormFieldPreviewColor: options.getFormFieldPreviewColor
|
|
64
66
|
};
|
|
65
67
|
const painter = new Painter({
|
|
66
68
|
userName: options.userName ?? "User",
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { type ShallowRef } from 'vue';
|
|
2
|
-
import type { DetectedShape, FormFieldDefinition, FormFieldValue } from '../annotation/engine/types.js';
|
|
1
|
+
import { type Ref, type ShallowRef } from 'vue';
|
|
2
|
+
import type { AddFormFieldPayload, CheckboxStyle, DetectedShape, FormFieldDefinition, FormFieldValue, PlaceFieldPayload } from '../annotation/engine/types.js';
|
|
3
3
|
export interface FormFieldsState {
|
|
4
4
|
/** Field definitions grouped by page number */
|
|
5
5
|
fieldDefinitions: ShallowRef<Map<number, FormFieldDefinition[]>>;
|
|
@@ -21,6 +21,52 @@ export interface FormFieldsState {
|
|
|
21
21
|
resetValues: () => void;
|
|
22
22
|
/** Create synthetic checkbox form fields from detected shapes */
|
|
23
23
|
registerDetectedCheckboxes: (pageNumber: number, shapes: DetectedShape[], scale: number, pageHeight: number) => void;
|
|
24
|
+
/** Add a user-placed form field (from the placement tool). Returns the created def. */
|
|
25
|
+
addPlacedField: (payload: PlaceFieldPayload) => FormFieldDefinition;
|
|
26
|
+
/** Add a form field programmatically. Public API counterpart to
|
|
27
|
+
* `addPlacedField` with full control over the field's properties.
|
|
28
|
+
* Returns the created definition. */
|
|
29
|
+
addFormField: (payload: AddFormFieldPayload) => FormFieldDefinition;
|
|
30
|
+
/** Update a form field's rect, name, or flags. */
|
|
31
|
+
updatePlacedField: (id: string, patch: Partial<FormFieldDefinition>) => void;
|
|
32
|
+
/** Remove a form field and its value. */
|
|
33
|
+
removePlacedField: (id: string) => void;
|
|
34
|
+
/** All form-field definitions, flattened across pages. */
|
|
35
|
+
getAllFields: () => FormFieldDefinition[];
|
|
36
|
+
/** All user-placed field definitions (used by export). */
|
|
37
|
+
getPlacedFields: () => FormFieldDefinition[];
|
|
38
|
+
/** Look up a field definition by id (any origin). */
|
|
39
|
+
getFieldById: (id: string) => FormFieldDefinition | undefined;
|
|
40
|
+
/** Id of the currently selected placed field (null if none). */
|
|
41
|
+
selectedPlacedFieldId: Ref<string | null>;
|
|
42
|
+
/** Select or deselect a placed field. Pass null to deselect. */
|
|
43
|
+
selectPlacedField: (id: string | null) => void;
|
|
44
|
+
/** Opaque role id new placements are auto-tagged with. The host owns
|
|
45
|
+
* what roles exist; the editor only carries the active id forward to
|
|
46
|
+
* newly placed fields and looks up its color via roleColors. */
|
|
47
|
+
activeRoleId: Ref<string | null>;
|
|
48
|
+
/** Set or clear the active role for upcoming placements. */
|
|
49
|
+
setActiveRole: (id: string | null) => void;
|
|
50
|
+
/** Map from roleId → CSS color, supplied by the host via the KViewer
|
|
51
|
+
* `roleColors` prop. Reading this drives the chrome and placement
|
|
52
|
+
* preview color. The editor never mutates role definitions itself. */
|
|
53
|
+
roleColors: ShallowRef<Record<string, string>>;
|
|
54
|
+
/** Replace the host-provided color map. Called by KViewer when the
|
|
55
|
+
* prop changes. */
|
|
56
|
+
setRoleColors: (colors: Record<string, string>) => void;
|
|
57
|
+
/** Apply checkbox-style hints (extracted from `/MK /CA` in the source PDF)
|
|
58
|
+
* to existing parsed checkbox defs and store the map for any defs
|
|
59
|
+
* parsed afterwards. Keyed by checkbox fieldName. */
|
|
60
|
+
applyCheckboxStyles: (styles: Map<string, CheckboxStyle>) => void;
|
|
61
|
+
/** Apply push-button captions (extracted from `/MK /CA` in the source
|
|
62
|
+
* PDF) to existing parsed button defs and store the map for any defs
|
|
63
|
+
* parsed afterwards. Keyed by button fieldName. */
|
|
64
|
+
applyButtonCaptions: (captions: Map<string, string>) => void;
|
|
65
|
+
/** Whether this field is locked by another (signed) signature field's
|
|
66
|
+
* lock rule. Reactive to both definitions and values, so a UI computed
|
|
67
|
+
* that calls this re-runs when any signature is signed or its lock
|
|
68
|
+
* rule changes. */
|
|
69
|
+
isFieldLocked: (fieldId: string) => boolean;
|
|
24
70
|
/** Clear all values and definitions (for document change) */
|
|
25
71
|
reset: () => void;
|
|
26
72
|
}
|
|
@@ -1,18 +1,100 @@
|
|
|
1
1
|
import {
|
|
2
2
|
shallowRef,
|
|
3
|
+
ref,
|
|
3
4
|
triggerRef,
|
|
4
5
|
provide,
|
|
5
6
|
inject
|
|
6
7
|
} from "vue";
|
|
7
8
|
import { parseFormFields } from "../annotation/parsers/parseFormFields.js";
|
|
9
|
+
import { generateUUID } from "../annotation/engine/utils.js";
|
|
8
10
|
const FORM_FIELDS_KEY = Symbol("kviewer-form-fields");
|
|
9
11
|
export function provideFormFields() {
|
|
10
12
|
const fieldDefinitions = shallowRef(/* @__PURE__ */ new Map());
|
|
11
13
|
const fieldValues = shallowRef(/* @__PURE__ */ new Map());
|
|
14
|
+
const selectedPlacedFieldId = ref(null);
|
|
15
|
+
const activeRoleId = ref(null);
|
|
16
|
+
const roleColors = shallowRef({});
|
|
17
|
+
function setActiveRole(id) {
|
|
18
|
+
activeRoleId.value = id;
|
|
19
|
+
}
|
|
20
|
+
function setRoleColors(colors) {
|
|
21
|
+
roleColors.value = colors;
|
|
22
|
+
}
|
|
23
|
+
function selectPlacedField(id) {
|
|
24
|
+
selectedPlacedFieldId.value = id;
|
|
25
|
+
}
|
|
26
|
+
function getFieldById(id) {
|
|
27
|
+
for (const defs of fieldDefinitions.value.values()) {
|
|
28
|
+
for (const d of defs) {
|
|
29
|
+
if (d.id === id) return d;
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
return void 0;
|
|
33
|
+
}
|
|
34
|
+
let checkboxStyleMap = null;
|
|
35
|
+
let buttonCaptionMap = null;
|
|
36
|
+
function applyCheckboxStyles(styles) {
|
|
37
|
+
checkboxStyleMap = styles;
|
|
38
|
+
let changed = false;
|
|
39
|
+
for (const [page, defs] of fieldDefinitions.value.entries()) {
|
|
40
|
+
let pageChanged = false;
|
|
41
|
+
const next = defs.map((d) => {
|
|
42
|
+
if (d.fieldType !== "checkbox" || d.origin !== "parsed") return d;
|
|
43
|
+
const style = styles.get(d.fieldName);
|
|
44
|
+
if (!style) return d;
|
|
45
|
+
if (d.originalCheckboxStyle) return d;
|
|
46
|
+
pageChanged = true;
|
|
47
|
+
return { ...d, checkboxStyle: d.checkboxStyle ?? style, originalCheckboxStyle: style };
|
|
48
|
+
});
|
|
49
|
+
if (pageChanged) {
|
|
50
|
+
fieldDefinitions.value.set(page, next);
|
|
51
|
+
changed = true;
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
if (changed) triggerRef(fieldDefinitions);
|
|
55
|
+
}
|
|
56
|
+
function applyButtonCaptions(captions) {
|
|
57
|
+
buttonCaptionMap = captions;
|
|
58
|
+
let changed = false;
|
|
59
|
+
for (const [page, defs] of fieldDefinitions.value.entries()) {
|
|
60
|
+
let pageChanged = false;
|
|
61
|
+
const next = defs.map((d) => {
|
|
62
|
+
if (d.fieldType !== "button" || d.origin !== "parsed") return d;
|
|
63
|
+
const caption = captions.get(d.fieldName);
|
|
64
|
+
if (!caption) return d;
|
|
65
|
+
if (d.buttonLabel === caption) return d;
|
|
66
|
+
pageChanged = true;
|
|
67
|
+
return { ...d, buttonLabel: caption };
|
|
68
|
+
});
|
|
69
|
+
if (pageChanged) {
|
|
70
|
+
fieldDefinitions.value.set(page, next);
|
|
71
|
+
changed = true;
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
if (changed) triggerRef(fieldDefinitions);
|
|
75
|
+
}
|
|
12
76
|
function parsePageFields(pageNumber, rawAnnotations) {
|
|
13
77
|
if (fieldDefinitions.value.has(pageNumber)) return;
|
|
14
78
|
const defs = parseFormFields(rawAnnotations, pageNumber);
|
|
15
79
|
if (defs.length === 0) return;
|
|
80
|
+
if (checkboxStyleMap) {
|
|
81
|
+
for (const def of defs) {
|
|
82
|
+
if (def.fieldType === "checkbox" && !def.checkboxStyle) {
|
|
83
|
+
const style = checkboxStyleMap.get(def.fieldName);
|
|
84
|
+
if (style) {
|
|
85
|
+
def.checkboxStyle = style;
|
|
86
|
+
def.originalCheckboxStyle = style;
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
if (buttonCaptionMap) {
|
|
92
|
+
for (const def of defs) {
|
|
93
|
+
if (def.fieldType !== "button") continue;
|
|
94
|
+
const caption = buttonCaptionMap.get(def.fieldName);
|
|
95
|
+
if (caption) def.buttonLabel = caption;
|
|
96
|
+
}
|
|
97
|
+
}
|
|
16
98
|
fieldDefinitions.value.set(pageNumber, defs);
|
|
17
99
|
triggerRef(fieldDefinitions);
|
|
18
100
|
for (const def of defs) {
|
|
@@ -30,11 +112,19 @@ export function provideFormFields() {
|
|
|
30
112
|
const existing = fieldValues.value.get(fieldId);
|
|
31
113
|
if (!existing) return;
|
|
32
114
|
existing.value = value;
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
fv.
|
|
115
|
+
const { fieldName, fieldType } = existing;
|
|
116
|
+
if (fieldName) {
|
|
117
|
+
if (fieldType === "radio" && typeof value === "string" && value !== "") {
|
|
118
|
+
for (const [id, fv] of fieldValues.value.entries()) {
|
|
119
|
+
if (id !== fieldId && fv.fieldName === fieldName && fv.fieldType === "radio") {
|
|
120
|
+
fv.value = "";
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
} else if (fieldType !== "radio") {
|
|
124
|
+
for (const [id, fv] of fieldValues.value.entries()) {
|
|
125
|
+
if (id !== fieldId && fv.fieldName === fieldName && fv.fieldType === fieldType) {
|
|
126
|
+
fv.value = value;
|
|
127
|
+
}
|
|
38
128
|
}
|
|
39
129
|
}
|
|
40
130
|
}
|
|
@@ -97,6 +187,7 @@ export function provideFormFields() {
|
|
|
97
187
|
rect: [pdfX1, pdfY1, pdfX2, pdfY2],
|
|
98
188
|
readOnly: false,
|
|
99
189
|
required: false,
|
|
190
|
+
origin: "detected",
|
|
100
191
|
exportValue: "Yes",
|
|
101
192
|
defaultValue: "Off",
|
|
102
193
|
shapeType: s.shapeType
|
|
@@ -115,6 +206,228 @@ export function provideFormFields() {
|
|
|
115
206
|
}
|
|
116
207
|
triggerRef(fieldValues);
|
|
117
208
|
}
|
|
209
|
+
function nextPlacedFieldName(fieldType) {
|
|
210
|
+
const base = fieldType === "text" ? "Text" : fieldType === "checkbox" ? "Checkbox" : fieldType === "signature" ? "Signature" : fieldType === "dropdown" ? "Dropdown" : fieldType === "radio" ? "Radio" : "Field";
|
|
211
|
+
const existing = /* @__PURE__ */ new Set();
|
|
212
|
+
for (const defs of fieldDefinitions.value.values()) {
|
|
213
|
+
for (const d of defs) existing.add(d.fieldName);
|
|
214
|
+
}
|
|
215
|
+
let n = 1;
|
|
216
|
+
while (existing.has(`${base}_${n}`)) n += 1;
|
|
217
|
+
return `${base}_${n}`;
|
|
218
|
+
}
|
|
219
|
+
function placedDefaultValue(fieldType) {
|
|
220
|
+
if (fieldType === "checkbox") return false;
|
|
221
|
+
return "";
|
|
222
|
+
}
|
|
223
|
+
function addPlacedField(payload) {
|
|
224
|
+
const id = `placed-${generateUUID()}`;
|
|
225
|
+
let fieldName;
|
|
226
|
+
if (payload.fieldType === "radio" && selectedPlacedFieldId.value) {
|
|
227
|
+
const sel = getFieldById(selectedPlacedFieldId.value);
|
|
228
|
+
fieldName = sel && sel.fieldType === "radio" ? sel.fieldName : nextPlacedFieldName(payload.fieldType);
|
|
229
|
+
} else {
|
|
230
|
+
fieldName = nextPlacedFieldName(payload.fieldType);
|
|
231
|
+
}
|
|
232
|
+
const def = {
|
|
233
|
+
id,
|
|
234
|
+
pageNumber: payload.pageNumber,
|
|
235
|
+
fieldType: payload.fieldType,
|
|
236
|
+
fieldName,
|
|
237
|
+
rect: [...payload.rectPdf],
|
|
238
|
+
readOnly: false,
|
|
239
|
+
required: false,
|
|
240
|
+
origin: "placed"
|
|
241
|
+
};
|
|
242
|
+
if (activeRoleId.value) {
|
|
243
|
+
def.roleId = activeRoleId.value;
|
|
244
|
+
}
|
|
245
|
+
if (payload.fieldType === "radio") {
|
|
246
|
+
def.buttonValue = nextRadioOptionValue(fieldName);
|
|
247
|
+
}
|
|
248
|
+
if (payload.fieldType === "checkbox") {
|
|
249
|
+
def.checkboxStyle = "check";
|
|
250
|
+
}
|
|
251
|
+
if (payload.fieldType === "dropdown") {
|
|
252
|
+
def.combo = true;
|
|
253
|
+
def.options = [{ exportValue: "Option 1", displayValue: "Option 1" }];
|
|
254
|
+
}
|
|
255
|
+
const existingDefs = fieldDefinitions.value.get(payload.pageNumber) ?? [];
|
|
256
|
+
fieldDefinitions.value.set(payload.pageNumber, [...existingDefs, def]);
|
|
257
|
+
triggerRef(fieldDefinitions);
|
|
258
|
+
fieldValues.value.set(id, {
|
|
259
|
+
fieldId: id,
|
|
260
|
+
fieldName: def.fieldName,
|
|
261
|
+
fieldType: def.fieldType,
|
|
262
|
+
value: placedDefaultValue(def.fieldType)
|
|
263
|
+
});
|
|
264
|
+
triggerRef(fieldValues);
|
|
265
|
+
if (payload.fieldType === "radio") {
|
|
266
|
+
selectedPlacedFieldId.value = id;
|
|
267
|
+
}
|
|
268
|
+
return def;
|
|
269
|
+
}
|
|
270
|
+
function addFormField(payload) {
|
|
271
|
+
const id = `placed-${generateUUID()}`;
|
|
272
|
+
const fieldName = payload.fieldName ?? nextPlacedFieldName(payload.fieldType);
|
|
273
|
+
const def = {
|
|
274
|
+
id,
|
|
275
|
+
pageNumber: payload.pageNumber,
|
|
276
|
+
fieldType: payload.fieldType,
|
|
277
|
+
fieldName,
|
|
278
|
+
rect: [...payload.rect],
|
|
279
|
+
readOnly: payload.readOnly ?? false,
|
|
280
|
+
required: payload.required ?? false,
|
|
281
|
+
origin: "placed"
|
|
282
|
+
};
|
|
283
|
+
if (payload.roleId !== void 0) def.roleId = payload.roleId;
|
|
284
|
+
if (payload.defaultValue !== void 0) def.defaultValue = payload.defaultValue;
|
|
285
|
+
if (payload.fontSize !== void 0) def.fontSize = payload.fontSize;
|
|
286
|
+
if (payload.fontName !== void 0) def.fontName = payload.fontName;
|
|
287
|
+
if (payload.color !== void 0) def.color = payload.color;
|
|
288
|
+
if (payload.backgroundColor !== void 0) def.backgroundColor = payload.backgroundColor;
|
|
289
|
+
if (payload.textAlignment !== void 0) def.textAlignment = payload.textAlignment;
|
|
290
|
+
if (payload.fieldType === "radio") {
|
|
291
|
+
def.buttonValue = payload.buttonValue ?? nextRadioOptionValue(fieldName);
|
|
292
|
+
}
|
|
293
|
+
if (payload.fieldType === "checkbox") {
|
|
294
|
+
def.checkboxStyle = payload.checkboxStyle ?? "check";
|
|
295
|
+
}
|
|
296
|
+
if (payload.fieldType === "dropdown") {
|
|
297
|
+
def.combo = payload.combo ?? true;
|
|
298
|
+
def.options = payload.options ?? [{ exportValue: "Option 1", displayValue: "Option 1" }];
|
|
299
|
+
if (payload.multiSelect !== void 0) def.multiSelect = payload.multiSelect;
|
|
300
|
+
}
|
|
301
|
+
if (payload.fieldType === "text") {
|
|
302
|
+
if (payload.multiLine !== void 0) def.multiLine = payload.multiLine;
|
|
303
|
+
if (payload.password !== void 0) def.password = payload.password;
|
|
304
|
+
if (payload.comb !== void 0) def.comb = payload.comb;
|
|
305
|
+
if (payload.maxLen !== void 0) def.maxLen = payload.maxLen;
|
|
306
|
+
}
|
|
307
|
+
if (payload.fieldType === "signature") {
|
|
308
|
+
if (payload.promptText !== void 0) def.promptText = payload.promptText;
|
|
309
|
+
if (payload.lockAction !== void 0) def.lockAction = payload.lockAction;
|
|
310
|
+
if (payload.lockFieldNames !== void 0) def.lockFieldNames = payload.lockFieldNames;
|
|
311
|
+
}
|
|
312
|
+
const existingDefs = fieldDefinitions.value.get(payload.pageNumber) ?? [];
|
|
313
|
+
fieldDefinitions.value.set(payload.pageNumber, [...existingDefs, def]);
|
|
314
|
+
triggerRef(fieldDefinitions);
|
|
315
|
+
fieldValues.value.set(id, {
|
|
316
|
+
fieldId: id,
|
|
317
|
+
fieldName: def.fieldName,
|
|
318
|
+
fieldType: def.fieldType,
|
|
319
|
+
value: getDefaultValue(def)
|
|
320
|
+
});
|
|
321
|
+
triggerRef(fieldValues);
|
|
322
|
+
return def;
|
|
323
|
+
}
|
|
324
|
+
function getAllFields() {
|
|
325
|
+
const out = [];
|
|
326
|
+
for (const defs of fieldDefinitions.value.values()) {
|
|
327
|
+
for (const d of defs) out.push(d);
|
|
328
|
+
}
|
|
329
|
+
return out;
|
|
330
|
+
}
|
|
331
|
+
function nextRadioOptionValue(groupName) {
|
|
332
|
+
const used = /* @__PURE__ */ new Set();
|
|
333
|
+
for (const defs of fieldDefinitions.value.values()) {
|
|
334
|
+
for (const d of defs) {
|
|
335
|
+
if (d.fieldType === "radio" && d.fieldName === groupName && d.buttonValue) {
|
|
336
|
+
used.add(d.buttonValue);
|
|
337
|
+
}
|
|
338
|
+
}
|
|
339
|
+
}
|
|
340
|
+
let n = 1;
|
|
341
|
+
while (used.has(`Option_${n}`)) n += 1;
|
|
342
|
+
return `Option_${n}`;
|
|
343
|
+
}
|
|
344
|
+
function updatePlacedField(id, patch) {
|
|
345
|
+
let found;
|
|
346
|
+
for (const [page, defs] of fieldDefinitions.value.entries()) {
|
|
347
|
+
const idx = defs.findIndex((d) => d.id === id);
|
|
348
|
+
if (idx === -1) continue;
|
|
349
|
+
const existing = defs[idx];
|
|
350
|
+
const next = { ...existing, ...patch, id: existing.id };
|
|
351
|
+
const nextDefs = defs.slice();
|
|
352
|
+
nextDefs[idx] = next;
|
|
353
|
+
fieldDefinitions.value.set(page, nextDefs);
|
|
354
|
+
found = next;
|
|
355
|
+
break;
|
|
356
|
+
}
|
|
357
|
+
if (!found) return;
|
|
358
|
+
triggerRef(fieldDefinitions);
|
|
359
|
+
const fv = fieldValues.value.get(id);
|
|
360
|
+
if (fv && (fv.fieldName !== found.fieldName || fv.fieldType !== found.fieldType)) {
|
|
361
|
+
fv.fieldName = found.fieldName;
|
|
362
|
+
fv.fieldType = found.fieldType;
|
|
363
|
+
triggerRef(fieldValues);
|
|
364
|
+
}
|
|
365
|
+
}
|
|
366
|
+
function removePlacedField(id) {
|
|
367
|
+
let removed = false;
|
|
368
|
+
for (const [page, defs] of fieldDefinitions.value.entries()) {
|
|
369
|
+
const idx = defs.findIndex((d) => d.id === id);
|
|
370
|
+
if (idx === -1) continue;
|
|
371
|
+
const nextDefs = defs.slice();
|
|
372
|
+
nextDefs.splice(idx, 1);
|
|
373
|
+
if (nextDefs.length === 0) {
|
|
374
|
+
fieldDefinitions.value.delete(page);
|
|
375
|
+
} else {
|
|
376
|
+
fieldDefinitions.value.set(page, nextDefs);
|
|
377
|
+
}
|
|
378
|
+
removed = true;
|
|
379
|
+
break;
|
|
380
|
+
}
|
|
381
|
+
if (!removed) return;
|
|
382
|
+
triggerRef(fieldDefinitions);
|
|
383
|
+
if (fieldValues.value.delete(id)) {
|
|
384
|
+
triggerRef(fieldValues);
|
|
385
|
+
}
|
|
386
|
+
if (selectedPlacedFieldId.value === id) {
|
|
387
|
+
selectedPlacedFieldId.value = null;
|
|
388
|
+
}
|
|
389
|
+
}
|
|
390
|
+
function getPlacedFields() {
|
|
391
|
+
const out = [];
|
|
392
|
+
for (const defs of fieldDefinitions.value.values()) {
|
|
393
|
+
for (const d of defs) {
|
|
394
|
+
if (d.origin === "placed" || d.origin === "detected") {
|
|
395
|
+
out.push(d);
|
|
396
|
+
continue;
|
|
397
|
+
}
|
|
398
|
+
const rectMoved = d.originalRect && rectChanged(d.rect, d.originalRect);
|
|
399
|
+
const styleChanged = d.fieldType === "checkbox" && (d.checkboxStyle ?? "check") !== (d.originalCheckboxStyle ?? "check");
|
|
400
|
+
if (rectMoved || styleChanged) {
|
|
401
|
+
out.push(d);
|
|
402
|
+
}
|
|
403
|
+
}
|
|
404
|
+
}
|
|
405
|
+
return out;
|
|
406
|
+
}
|
|
407
|
+
function rectChanged(a, b) {
|
|
408
|
+
for (let i = 0; i < 4; i++) {
|
|
409
|
+
if (Math.abs((a[i] ?? 0) - (b[i] ?? 0)) > 0.01) return true;
|
|
410
|
+
}
|
|
411
|
+
return false;
|
|
412
|
+
}
|
|
413
|
+
function isFieldLocked(fieldId) {
|
|
414
|
+
const target = getFieldById(fieldId);
|
|
415
|
+
if (!target) return false;
|
|
416
|
+
for (const defs of fieldDefinitions.value.values()) {
|
|
417
|
+
for (const sig of defs) {
|
|
418
|
+
if (sig.fieldType !== "signature") continue;
|
|
419
|
+
if (!sig.lockAction) continue;
|
|
420
|
+
if (sig.id === fieldId) continue;
|
|
421
|
+
const fv = fieldValues.value.get(sig.id);
|
|
422
|
+
if (!fv || typeof fv.value !== "string" || fv.value === "") continue;
|
|
423
|
+
if (sig.lockAction === "all") return true;
|
|
424
|
+
const list = sig.lockFieldNames ?? [];
|
|
425
|
+
if (sig.lockAction === "include" && list.includes(target.fieldName)) return true;
|
|
426
|
+
if (sig.lockAction === "exclude" && !list.includes(target.fieldName)) return true;
|
|
427
|
+
}
|
|
428
|
+
}
|
|
429
|
+
return false;
|
|
430
|
+
}
|
|
118
431
|
function resetValues() {
|
|
119
432
|
for (const [, defs] of fieldDefinitions.value.entries()) {
|
|
120
433
|
for (const def of defs) {
|
|
@@ -129,6 +442,9 @@ export function provideFormFields() {
|
|
|
129
442
|
function reset() {
|
|
130
443
|
fieldDefinitions.value = /* @__PURE__ */ new Map();
|
|
131
444
|
fieldValues.value = /* @__PURE__ */ new Map();
|
|
445
|
+
selectedPlacedFieldId.value = null;
|
|
446
|
+
checkboxStyleMap = null;
|
|
447
|
+
buttonCaptionMap = null;
|
|
132
448
|
triggerRef(fieldDefinitions);
|
|
133
449
|
triggerRef(fieldValues);
|
|
134
450
|
}
|
|
@@ -142,8 +458,24 @@ export function provideFormFields() {
|
|
|
142
458
|
getAllFieldValues,
|
|
143
459
|
setFieldValueByName,
|
|
144
460
|
registerDetectedCheckboxes,
|
|
461
|
+
addPlacedField,
|
|
462
|
+
addFormField,
|
|
463
|
+
updatePlacedField,
|
|
464
|
+
removePlacedField,
|
|
465
|
+
getAllFields,
|
|
466
|
+
getPlacedFields,
|
|
467
|
+
getFieldById,
|
|
468
|
+
selectedPlacedFieldId,
|
|
469
|
+
selectPlacedField,
|
|
470
|
+
applyCheckboxStyles,
|
|
471
|
+
applyButtonCaptions,
|
|
472
|
+
isFieldLocked,
|
|
145
473
|
resetValues,
|
|
146
|
-
reset
|
|
474
|
+
reset,
|
|
475
|
+
activeRoleId,
|
|
476
|
+
setActiveRole,
|
|
477
|
+
roleColors,
|
|
478
|
+
setRoleColors
|
|
147
479
|
};
|
|
148
480
|
provide(FORM_FIELDS_KEY, state);
|
|
149
481
|
return state;
|
|
@@ -281,6 +281,7 @@ export function useInertiaPanzoom(options = {}) {
|
|
|
281
281
|
}
|
|
282
282
|
function onDown(e) {
|
|
283
283
|
if (e.pointerType === "pen") return;
|
|
284
|
+
if (e.pointerType === "mouse") return;
|
|
284
285
|
if (!gesturesEnabled) return;
|
|
285
286
|
stopMomentum();
|
|
286
287
|
setWillChangeActive(true);
|
|
@@ -425,6 +426,9 @@ export function useInertiaPanzoom(options = {}) {
|
|
|
425
426
|
pinchStart = null;
|
|
426
427
|
panStart = null;
|
|
427
428
|
setWillChangeActive(false);
|
|
429
|
+
const b = getBounds();
|
|
430
|
+
tx = Math.max(b.minX, Math.min(b.maxX, tx));
|
|
431
|
+
ty = Math.max(b.minY, Math.min(b.maxY, ty));
|
|
428
432
|
apply();
|
|
429
433
|
},
|
|
430
434
|
stop: () => {
|
|
@@ -4,6 +4,12 @@ export interface PageMeta {
|
|
|
4
4
|
pageNumber: number;
|
|
5
5
|
width: number;
|
|
6
6
|
height: number;
|
|
7
|
+
/** PDF user-space origin of the page's visible area (`view[0]`, `view[1]`).
|
|
8
|
+
* Almost always (0, 0); non-zero for PDFs whose MediaBox has been
|
|
9
|
+
* shifted. Annotations in `/Rect` are in default user space, so any
|
|
10
|
+
* layer drawn at the canvas top-left must subtract this origin. */
|
|
11
|
+
viewOffsetX: number;
|
|
12
|
+
viewOffsetY: number;
|
|
7
13
|
}
|
|
8
14
|
export interface PageVirtualization {
|
|
9
15
|
pageMetas: ShallowRef<PageMeta[]>;
|
|
@@ -120,10 +120,16 @@ export function createPageVirtualization() {
|
|
|
120
120
|
async function fetchPageDimensions(doc, pageNumber) {
|
|
121
121
|
const proxy = await doc.getPage(pageNumber);
|
|
122
122
|
const view = proxy.view;
|
|
123
|
+
const vx = view[0] ?? 0;
|
|
124
|
+
const vy = view[1] ?? 0;
|
|
125
|
+
const vw = view[2] ?? 0;
|
|
126
|
+
const vh = view[3] ?? 0;
|
|
123
127
|
return {
|
|
124
128
|
pageNumber,
|
|
125
|
-
width:
|
|
126
|
-
height:
|
|
129
|
+
width: vw - vx,
|
|
130
|
+
height: vh - vy,
|
|
131
|
+
viewOffsetX: vx,
|
|
132
|
+
viewOffsetY: vy
|
|
127
133
|
};
|
|
128
134
|
}
|
|
129
135
|
async function init(doc, root) {
|
|
@@ -139,7 +145,9 @@ export function createPageVirtualization() {
|
|
|
139
145
|
metas.push({
|
|
140
146
|
pageNumber: i,
|
|
141
147
|
width: firstMeta.width,
|
|
142
|
-
height: firstMeta.height
|
|
148
|
+
height: firstMeta.height,
|
|
149
|
+
viewOffsetX: firstMeta.viewOffsetX,
|
|
150
|
+
viewOffsetY: firstMeta.viewOffsetY
|
|
143
151
|
});
|
|
144
152
|
}
|
|
145
153
|
}
|
|
@@ -57,6 +57,9 @@ export interface ViewerState {
|
|
|
57
57
|
history: AnnotationHistory;
|
|
58
58
|
readonly: Ref<boolean>;
|
|
59
59
|
shapeDetection: Ref<boolean>;
|
|
60
|
+
visualZoom: Ref<number>;
|
|
61
|
+
formEditMode: Ref<boolean>;
|
|
62
|
+
setFormEditMode: (enabled: boolean) => void;
|
|
60
63
|
}
|
|
61
64
|
export interface ViewerStateProvider {
|
|
62
65
|
state: ViewerState;
|
|
@@ -65,7 +65,22 @@ export function provideViewerState() {
|
|
|
65
65
|
}
|
|
66
66
|
const readonly = ref(false);
|
|
67
67
|
const shapeDetectionEnabled = ref(false);
|
|
68
|
+
const visualZoom = ref(1);
|
|
68
69
|
const stylusMode = ref(false);
|
|
70
|
+
const formEditMode = ref(false);
|
|
71
|
+
function isFormFieldTool(t) {
|
|
72
|
+
return typeof t === "number" && (t === AnnotationType.FORM_TEXT || t === AnnotationType.FORM_CHECKBOX || t === AnnotationType.FORM_RADIO || t === AnnotationType.FORM_SIGNATURE);
|
|
73
|
+
}
|
|
74
|
+
function setFormEditMode(enabled) {
|
|
75
|
+
if (formEditMode.value === enabled) return;
|
|
76
|
+
formEditMode.value = enabled;
|
|
77
|
+
const t = activeTool.value;
|
|
78
|
+
if (enabled) {
|
|
79
|
+
if (typeof t === "number" && !isFormFieldTool(t)) selectTool("hand");
|
|
80
|
+
} else {
|
|
81
|
+
if (isFormFieldTool(t)) selectTool("hand");
|
|
82
|
+
}
|
|
83
|
+
}
|
|
69
84
|
function setStylusMode(enabled) {
|
|
70
85
|
stylusMode.value = enabled;
|
|
71
86
|
if (painter.value) {
|
|
@@ -95,6 +110,9 @@ export function provideViewerState() {
|
|
|
95
110
|
}
|
|
96
111
|
function selectTool(tool, dataTransfer) {
|
|
97
112
|
if (readonly.value && tool !== "hand") return;
|
|
113
|
+
if (isFormFieldTool(tool) && !formEditMode.value) {
|
|
114
|
+
formEditMode.value = true;
|
|
115
|
+
}
|
|
98
116
|
activeTool.value = tool;
|
|
99
117
|
selectedAnnotation.value = null;
|
|
100
118
|
selectedAnnotations.value = [];
|
|
@@ -181,7 +199,10 @@ export function provideViewerState() {
|
|
|
181
199
|
downloadPdf,
|
|
182
200
|
history,
|
|
183
201
|
readonly,
|
|
184
|
-
shapeDetection: shapeDetectionEnabled
|
|
202
|
+
shapeDetection: shapeDetectionEnabled,
|
|
203
|
+
visualZoom,
|
|
204
|
+
formEditMode,
|
|
205
|
+
setFormEditMode
|
|
185
206
|
};
|
|
186
207
|
provide(VIEWER_STATE_KEY, state);
|
|
187
208
|
return {
|
|
@@ -1,4 +1,4 @@
|
|
|
1
1
|
export type { ExportPdfOptions } from './annotation/pdf-export/export.js';
|
|
2
2
|
export type { ViewerTabItem, AddTabOptions } from './components/ViewerTabs.vue.js';
|
|
3
|
-
export type { SignatureData, SignatureHandlers } from './annotation/engine/types.js';
|
|
3
|
+
export type { AddFormFieldPayload, CheckboxStyle, FormFieldDefinition, FormFieldOrigin, FormFieldType, FormFieldValue, SignatureData, SignatureHandlers, } from './annotation/engine/types.js';
|
|
4
4
|
export type { ViewMode } from './composables/viewMode.js';
|
package/dist/types.d.mts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export { type ExportPdfOptions, type SignatureData, type SignatureHandlers, type ViewMode } from '../dist/runtime/public-types.js'
|
|
1
|
+
export { type AddFormFieldPayload, type CheckboxStyle, type ExportPdfOptions, type FormFieldDefinition, type FormFieldOrigin, type FormFieldType, type FormFieldValue, type SignatureData, type SignatureHandlers, type ViewMode } from '../dist/runtime/public-types.js'
|
|
2
2
|
|
|
3
3
|
export { default } from './module.mjs'
|
|
4
4
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "kviewer",
|
|
3
|
-
"version": "0.0
|
|
3
|
+
"version": "0.1.0",
|
|
4
4
|
"description": "Kabema PDF Editor",
|
|
5
5
|
"repository": "kabema/kviewer",
|
|
6
6
|
"license": "MIT",
|
|
@@ -28,7 +28,7 @@
|
|
|
28
28
|
],
|
|
29
29
|
"scripts": {
|
|
30
30
|
"prepack": "nuxt-module-build build",
|
|
31
|
-
"dev": "npm run dev:prepare && nuxt dev playground",
|
|
31
|
+
"dev": "npm run dev:prepare && portless kviewer nuxt dev playground",
|
|
32
32
|
"dev:build": "nuxt build playground",
|
|
33
33
|
"dev:prepare": "nuxt-module-build build --stub && nuxt-module-build prepare && nuxt prepare playground",
|
|
34
34
|
"release": "npm run lint && npm run test && npm run prepack && changelogen --release && npm publish && git push --follow-tags",
|