kviewer 0.0.10 → 0.0.11

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.
Files changed (59) hide show
  1. package/README.md +29 -0
  2. package/dist/module.json +1 -1
  3. package/dist/module.mjs +19 -0
  4. package/dist/runtime/annotation/checkbox-styles.d.ts +19 -0
  5. package/dist/runtime/annotation/checkbox-styles.js +27 -0
  6. package/dist/runtime/annotation/engine/config.js +44 -0
  7. package/dist/runtime/annotation/engine/painter.d.ts +4 -1
  8. package/dist/runtime/annotation/engine/painter.js +26 -1
  9. package/dist/runtime/annotation/engine/tools/form-field.d.ts +24 -0
  10. package/dist/runtime/annotation/engine/tools/form-field.js +117 -0
  11. package/dist/runtime/annotation/engine/types.d.ts +54 -1
  12. package/dist/runtime/annotation/engine/types.js +4 -0
  13. package/dist/runtime/annotation/font-style.d.ts +23 -0
  14. package/dist/runtime/annotation/font-style.js +57 -0
  15. package/dist/runtime/annotation/parsers/extractCheckboxStyles.d.ts +21 -0
  16. package/dist/runtime/annotation/parsers/extractCheckboxStyles.js +75 -0
  17. package/dist/runtime/annotation/parsers/parseFormFields.js +16 -3
  18. package/dist/runtime/annotation/pdf-export/export-form-fields.d.ts +8 -5
  19. package/dist/runtime/annotation/pdf-export/export-form-fields.js +245 -1
  20. package/dist/runtime/annotation/pdf-export/export.d.ts +3 -2
  21. package/dist/runtime/annotation/pdf-export/export.js +9 -2
  22. package/dist/runtime/assets/kviewer.css +1 -1
  23. package/dist/runtime/components/FormFieldLayer.d.vue.ts +5 -0
  24. package/dist/runtime/components/FormFieldLayer.vue +40 -3
  25. package/dist/runtime/components/FormFieldLayer.vue.d.ts +5 -0
  26. package/dist/runtime/components/PdfPage.vue +24 -0
  27. package/dist/runtime/components/Viewer.d.vue.ts +18 -3
  28. package/dist/runtime/components/Viewer.vue +66 -7
  29. package/dist/runtime/components/Viewer.vue.d.ts +18 -3
  30. package/dist/runtime/components/ViewerBar.vue +24 -2
  31. package/dist/runtime/components/form-fields/FormButton.vue +16 -4
  32. package/dist/runtime/components/form-fields/FormCheckbox.vue +31 -15
  33. package/dist/runtime/components/form-fields/FormDropdown.vue +3 -1
  34. package/dist/runtime/components/form-fields/FormFieldWrapper.d.vue.ts +12 -1
  35. package/dist/runtime/components/form-fields/FormFieldWrapper.vue +45 -9
  36. package/dist/runtime/components/form-fields/FormFieldWrapper.vue.d.ts +12 -1
  37. package/dist/runtime/components/form-fields/FormRadioButton.vue +3 -1
  38. package/dist/runtime/components/form-fields/FormSignatureField.vue +2 -1
  39. package/dist/runtime/components/form-fields/FormTextField.vue +47 -7
  40. package/dist/runtime/components/form-fields/PlacedFieldChrome.d.vue.ts +16 -0
  41. package/dist/runtime/components/form-fields/PlacedFieldChrome.vue +131 -0
  42. package/dist/runtime/components/form-fields/PlacedFieldChrome.vue.d.ts +16 -0
  43. package/dist/runtime/components/modals/SignatureDrawModal.vue +70 -15
  44. package/dist/runtime/components/panels/PlacedFieldSidebar.d.vue.ts +3 -0
  45. package/dist/runtime/components/panels/PlacedFieldSidebar.vue +505 -0
  46. package/dist/runtime/components/panels/PlacedFieldSidebar.vue.d.ts +3 -0
  47. package/dist/runtime/components/tools/FormFieldTools.d.vue.ts +3 -0
  48. package/dist/runtime/components/tools/FormFieldTools.vue +35 -0
  49. package/dist/runtime/components/tools/FormFieldTools.vue.d.ts +3 -0
  50. package/dist/runtime/composables/useAnnotationEngine.d.ts +1 -0
  51. package/dist/runtime/composables/useAnnotationEngine.js +2 -1
  52. package/dist/runtime/composables/useFormFields.d.ts +29 -2
  53. package/dist/runtime/composables/useFormFields.js +259 -5
  54. package/dist/runtime/composables/useInertiaPanzoom.js +3 -0
  55. package/dist/runtime/composables/usePageVirtualization.d.ts +6 -0
  56. package/dist/runtime/composables/usePageVirtualization.js +11 -3
  57. package/dist/runtime/composables/useViewerState.d.ts +3 -0
  58. package/dist/runtime/composables/useViewerState.js +22 -1
  59. package/package.json +2 -2
@@ -1,18 +1,92 @@
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
+ function selectPlacedField(id) {
16
+ selectedPlacedFieldId.value = id;
17
+ }
18
+ function getFieldById(id) {
19
+ for (const defs of fieldDefinitions.value.values()) {
20
+ for (const d of defs) {
21
+ if (d.id === id) return d;
22
+ }
23
+ }
24
+ return void 0;
25
+ }
26
+ let checkboxStyleMap = null;
27
+ let buttonCaptionMap = null;
28
+ function applyCheckboxStyles(styles) {
29
+ checkboxStyleMap = styles;
30
+ let changed = false;
31
+ for (const [page, defs] of fieldDefinitions.value.entries()) {
32
+ let pageChanged = false;
33
+ const next = defs.map((d) => {
34
+ if (d.fieldType !== "checkbox" || d.origin !== "parsed") return d;
35
+ const style = styles.get(d.fieldName);
36
+ if (!style) return d;
37
+ if (d.originalCheckboxStyle) return d;
38
+ pageChanged = true;
39
+ return { ...d, checkboxStyle: d.checkboxStyle ?? style, originalCheckboxStyle: style };
40
+ });
41
+ if (pageChanged) {
42
+ fieldDefinitions.value.set(page, next);
43
+ changed = true;
44
+ }
45
+ }
46
+ if (changed) triggerRef(fieldDefinitions);
47
+ }
48
+ function applyButtonCaptions(captions) {
49
+ buttonCaptionMap = captions;
50
+ let changed = false;
51
+ for (const [page, defs] of fieldDefinitions.value.entries()) {
52
+ let pageChanged = false;
53
+ const next = defs.map((d) => {
54
+ if (d.fieldType !== "button" || d.origin !== "parsed") return d;
55
+ const caption = captions.get(d.fieldName);
56
+ if (!caption) return d;
57
+ if (d.buttonLabel === caption) return d;
58
+ pageChanged = true;
59
+ return { ...d, buttonLabel: caption };
60
+ });
61
+ if (pageChanged) {
62
+ fieldDefinitions.value.set(page, next);
63
+ changed = true;
64
+ }
65
+ }
66
+ if (changed) triggerRef(fieldDefinitions);
67
+ }
12
68
  function parsePageFields(pageNumber, rawAnnotations) {
13
69
  if (fieldDefinitions.value.has(pageNumber)) return;
14
70
  const defs = parseFormFields(rawAnnotations, pageNumber);
15
71
  if (defs.length === 0) return;
72
+ if (checkboxStyleMap) {
73
+ for (const def of defs) {
74
+ if (def.fieldType === "checkbox" && !def.checkboxStyle) {
75
+ const style = checkboxStyleMap.get(def.fieldName);
76
+ if (style) {
77
+ def.checkboxStyle = style;
78
+ def.originalCheckboxStyle = style;
79
+ }
80
+ }
81
+ }
82
+ }
83
+ if (buttonCaptionMap) {
84
+ for (const def of defs) {
85
+ if (def.fieldType !== "button") continue;
86
+ const caption = buttonCaptionMap.get(def.fieldName);
87
+ if (caption) def.buttonLabel = caption;
88
+ }
89
+ }
16
90
  fieldDefinitions.value.set(pageNumber, defs);
17
91
  triggerRef(fieldDefinitions);
18
92
  for (const def of defs) {
@@ -30,11 +104,19 @@ export function provideFormFields() {
30
104
  const existing = fieldValues.value.get(fieldId);
31
105
  if (!existing) return;
32
106
  existing.value = value;
33
- if (existing.fieldType === "radio" && typeof value === "string" && value !== "") {
34
- const fieldName = existing.fieldName;
35
- for (const [id, fv] of fieldValues.value.entries()) {
36
- if (id !== fieldId && fv.fieldName === fieldName && fv.fieldType === "radio") {
37
- fv.value = "";
107
+ const { fieldName, fieldType } = existing;
108
+ if (fieldName) {
109
+ if (fieldType === "radio" && typeof value === "string" && value !== "") {
110
+ for (const [id, fv] of fieldValues.value.entries()) {
111
+ if (id !== fieldId && fv.fieldName === fieldName && fv.fieldType === "radio") {
112
+ fv.value = "";
113
+ }
114
+ }
115
+ } else if (fieldType !== "radio") {
116
+ for (const [id, fv] of fieldValues.value.entries()) {
117
+ if (id !== fieldId && fv.fieldName === fieldName && fv.fieldType === fieldType) {
118
+ fv.value = value;
119
+ }
38
120
  }
39
121
  }
40
122
  }
@@ -97,6 +179,7 @@ export function provideFormFields() {
97
179
  rect: [pdfX1, pdfY1, pdfX2, pdfY2],
98
180
  readOnly: false,
99
181
  required: false,
182
+ origin: "detected",
100
183
  exportValue: "Yes",
101
184
  defaultValue: "Off",
102
185
  shapeType: s.shapeType
@@ -115,6 +198,164 @@ export function provideFormFields() {
115
198
  }
116
199
  triggerRef(fieldValues);
117
200
  }
201
+ function nextPlacedFieldName(fieldType) {
202
+ const base = fieldType === "text" ? "Text" : fieldType === "checkbox" ? "Checkbox" : fieldType === "signature" ? "Signature" : fieldType === "dropdown" ? "Dropdown" : fieldType === "radio" ? "Radio" : "Field";
203
+ const existing = /* @__PURE__ */ new Set();
204
+ for (const defs of fieldDefinitions.value.values()) {
205
+ for (const d of defs) existing.add(d.fieldName);
206
+ }
207
+ let n = 1;
208
+ while (existing.has(`${base}_${n}`)) n += 1;
209
+ return `${base}_${n}`;
210
+ }
211
+ function placedDefaultValue(fieldType) {
212
+ if (fieldType === "checkbox") return false;
213
+ return "";
214
+ }
215
+ function addPlacedField(payload) {
216
+ const id = `placed-${generateUUID()}`;
217
+ let fieldName;
218
+ if (payload.fieldType === "radio" && selectedPlacedFieldId.value) {
219
+ const sel = getFieldById(selectedPlacedFieldId.value);
220
+ fieldName = sel && sel.fieldType === "radio" ? sel.fieldName : nextPlacedFieldName(payload.fieldType);
221
+ } else {
222
+ fieldName = nextPlacedFieldName(payload.fieldType);
223
+ }
224
+ const def = {
225
+ id,
226
+ pageNumber: payload.pageNumber,
227
+ fieldType: payload.fieldType,
228
+ fieldName,
229
+ rect: [...payload.rectPdf],
230
+ readOnly: false,
231
+ required: false,
232
+ origin: "placed"
233
+ };
234
+ if (payload.fieldType === "radio") {
235
+ def.buttonValue = nextRadioOptionValue(fieldName);
236
+ }
237
+ if (payload.fieldType === "checkbox") {
238
+ def.checkboxStyle = "check";
239
+ }
240
+ if (payload.fieldType === "dropdown") {
241
+ def.combo = true;
242
+ def.options = [{ exportValue: "Option 1", displayValue: "Option 1" }];
243
+ }
244
+ const existingDefs = fieldDefinitions.value.get(payload.pageNumber) ?? [];
245
+ fieldDefinitions.value.set(payload.pageNumber, [...existingDefs, def]);
246
+ triggerRef(fieldDefinitions);
247
+ fieldValues.value.set(id, {
248
+ fieldId: id,
249
+ fieldName: def.fieldName,
250
+ fieldType: def.fieldType,
251
+ value: placedDefaultValue(def.fieldType)
252
+ });
253
+ triggerRef(fieldValues);
254
+ if (payload.fieldType === "radio") {
255
+ selectedPlacedFieldId.value = id;
256
+ }
257
+ return def;
258
+ }
259
+ function nextRadioOptionValue(groupName) {
260
+ const used = /* @__PURE__ */ new Set();
261
+ for (const defs of fieldDefinitions.value.values()) {
262
+ for (const d of defs) {
263
+ if (d.fieldType === "radio" && d.fieldName === groupName && d.buttonValue) {
264
+ used.add(d.buttonValue);
265
+ }
266
+ }
267
+ }
268
+ let n = 1;
269
+ while (used.has(`Option_${n}`)) n += 1;
270
+ return `Option_${n}`;
271
+ }
272
+ function updatePlacedField(id, patch) {
273
+ let found;
274
+ for (const [page, defs] of fieldDefinitions.value.entries()) {
275
+ const idx = defs.findIndex((d) => d.id === id);
276
+ if (idx === -1) continue;
277
+ const existing = defs[idx];
278
+ const next = { ...existing, ...patch, id: existing.id };
279
+ const nextDefs = defs.slice();
280
+ nextDefs[idx] = next;
281
+ fieldDefinitions.value.set(page, nextDefs);
282
+ found = next;
283
+ break;
284
+ }
285
+ if (!found) return;
286
+ triggerRef(fieldDefinitions);
287
+ const fv = fieldValues.value.get(id);
288
+ if (fv && (fv.fieldName !== found.fieldName || fv.fieldType !== found.fieldType)) {
289
+ fv.fieldName = found.fieldName;
290
+ fv.fieldType = found.fieldType;
291
+ triggerRef(fieldValues);
292
+ }
293
+ }
294
+ function removePlacedField(id) {
295
+ let removed = false;
296
+ for (const [page, defs] of fieldDefinitions.value.entries()) {
297
+ const idx = defs.findIndex((d) => d.id === id);
298
+ if (idx === -1) continue;
299
+ const nextDefs = defs.slice();
300
+ nextDefs.splice(idx, 1);
301
+ if (nextDefs.length === 0) {
302
+ fieldDefinitions.value.delete(page);
303
+ } else {
304
+ fieldDefinitions.value.set(page, nextDefs);
305
+ }
306
+ removed = true;
307
+ break;
308
+ }
309
+ if (!removed) return;
310
+ triggerRef(fieldDefinitions);
311
+ if (fieldValues.value.delete(id)) {
312
+ triggerRef(fieldValues);
313
+ }
314
+ if (selectedPlacedFieldId.value === id) {
315
+ selectedPlacedFieldId.value = null;
316
+ }
317
+ }
318
+ function getPlacedFields() {
319
+ const out = [];
320
+ for (const defs of fieldDefinitions.value.values()) {
321
+ for (const d of defs) {
322
+ if (d.origin === "placed" || d.origin === "detected") {
323
+ out.push(d);
324
+ continue;
325
+ }
326
+ const rectMoved = d.originalRect && rectChanged(d.rect, d.originalRect);
327
+ const styleChanged = d.fieldType === "checkbox" && (d.checkboxStyle ?? "check") !== (d.originalCheckboxStyle ?? "check");
328
+ if (rectMoved || styleChanged) {
329
+ out.push(d);
330
+ }
331
+ }
332
+ }
333
+ return out;
334
+ }
335
+ function rectChanged(a, b) {
336
+ for (let i = 0; i < 4; i++) {
337
+ if (Math.abs((a[i] ?? 0) - (b[i] ?? 0)) > 0.01) return true;
338
+ }
339
+ return false;
340
+ }
341
+ function isFieldLocked(fieldId) {
342
+ const target = getFieldById(fieldId);
343
+ if (!target) return false;
344
+ for (const defs of fieldDefinitions.value.values()) {
345
+ for (const sig of defs) {
346
+ if (sig.fieldType !== "signature") continue;
347
+ if (!sig.lockAction) continue;
348
+ if (sig.id === fieldId) continue;
349
+ const fv = fieldValues.value.get(sig.id);
350
+ if (!fv || typeof fv.value !== "string" || fv.value === "") continue;
351
+ if (sig.lockAction === "all") return true;
352
+ const list = sig.lockFieldNames ?? [];
353
+ if (sig.lockAction === "include" && list.includes(target.fieldName)) return true;
354
+ if (sig.lockAction === "exclude" && !list.includes(target.fieldName)) return true;
355
+ }
356
+ }
357
+ return false;
358
+ }
118
359
  function resetValues() {
119
360
  for (const [, defs] of fieldDefinitions.value.entries()) {
120
361
  for (const def of defs) {
@@ -129,6 +370,9 @@ export function provideFormFields() {
129
370
  function reset() {
130
371
  fieldDefinitions.value = /* @__PURE__ */ new Map();
131
372
  fieldValues.value = /* @__PURE__ */ new Map();
373
+ selectedPlacedFieldId.value = null;
374
+ checkboxStyleMap = null;
375
+ buttonCaptionMap = null;
132
376
  triggerRef(fieldDefinitions);
133
377
  triggerRef(fieldValues);
134
378
  }
@@ -142,6 +386,16 @@ export function provideFormFields() {
142
386
  getAllFieldValues,
143
387
  setFieldValueByName,
144
388
  registerDetectedCheckboxes,
389
+ addPlacedField,
390
+ updatePlacedField,
391
+ removePlacedField,
392
+ getPlacedFields,
393
+ getFieldById,
394
+ selectedPlacedFieldId,
395
+ selectPlacedField,
396
+ applyCheckboxStyles,
397
+ applyButtonCaptions,
398
+ isFieldLocked,
145
399
  resetValues,
146
400
  reset
147
401
  };
@@ -425,6 +425,9 @@ export function useInertiaPanzoom(options = {}) {
425
425
  pinchStart = null;
426
426
  panStart = null;
427
427
  setWillChangeActive(false);
428
+ const b = getBounds();
429
+ tx = Math.max(b.minX, Math.min(b.maxX, tx));
430
+ ty = Math.max(b.minY, Math.min(b.maxY, ty));
428
431
  apply();
429
432
  },
430
433
  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: view[2] - view[0],
126
- height: view[3] - view[1]
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 {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "kviewer",
3
- "version": "0.0.10",
3
+ "version": "0.0.11",
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",