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
package/README.md CHANGED
@@ -81,6 +81,11 @@ await viewerRef.value?.importAnnotations(draft, { mode: 'replace' })
81
81
  // Read / write form field values
82
82
  const fields = viewerRef.value?.getFormFieldValues()
83
83
  viewerRef.value?.setFormFieldValue('email', 'user@example.com')
84
+
85
+ // Toggle form-edit mode programmatically
86
+ viewerRef.value?.setFormEditMode(true)
87
+ viewerRef.value?.toggleFormEditMode()
88
+ const isEditing = viewerRef.value?.formEditMode.value
84
89
  </script>
85
90
  ```
86
91
 
@@ -92,9 +97,33 @@ viewerRef.value?.setFormFieldValue('email', 'user@example.com')
92
97
  | `getFormFieldValues()` | `FormFieldValue[]` |
93
98
  | `setFormFieldValue(fieldName, value)` | `void` |
94
99
  | `getKonvaCanvasState()` | `Record<number, string>` |
100
+ | `formEditMode` | `Ref<boolean>` |
101
+ | `setFormEditMode(enabled)` | `void` |
102
+ | `toggleFormEditMode()` | `boolean` (the new state) |
95
103
 
96
104
  `exportPdf` options default to `{ flatten: false, download: false, preserveOriginalAnnotations: false }`.
97
105
 
106
+ ### Form-edit mode
107
+
108
+ `KViewer` exposes a **form-edit mode** that turns every form field into a movable/resizable widget with a property sidebar. It's also reachable from the burger menu in the toolbar, but consumers can drive it externally via prop binding or the imperative API.
109
+
110
+ Two-way binding via `v-model:form-edit-mode`:
111
+
112
+ ```vue
113
+ <template>
114
+ <button @click="formEditMode = !formEditMode">
115
+ {{ formEditMode ? 'Exit edit mode' : 'Edit form' }}
116
+ </button>
117
+ <KViewer v-model:form-edit-mode="formEditMode" :source="pdfUrl" />
118
+ </template>
119
+
120
+ <script setup lang="ts">
121
+ const formEditMode = ref(false)
122
+ </script>
123
+ ```
124
+
125
+ Or imperatively from the ref API (`setFormEditMode`, `toggleFormEditMode`, reactive `formEditMode` ref).
126
+
98
127
  When native PDF annotations are auto-imported into Konva, exporting with `preserveOriginalAnnotations: true` may duplicate annotations. Prefer `preserveOriginalAnnotations: false` in that workflow.
99
128
 
100
129
  ## Development
package/dist/module.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "kviewer",
3
3
  "configKey": "kviewer",
4
- "version": "0.0.10",
4
+ "version": "0.0.11",
5
5
  "builder": {
6
6
  "@nuxt/module-builder": "1.0.2",
7
7
  "unbuild": "3.6.1"
package/dist/module.mjs CHANGED
@@ -1,3 +1,5 @@
1
+ import { createRequire } from 'node:module';
2
+ import { dirname, join } from 'node:path';
1
3
  import { defineNuxtModule, createResolver, addPlugin, addComponentsDir } from '@nuxt/kit';
2
4
 
3
5
  const module$1 = defineNuxtModule({
@@ -20,6 +22,23 @@ const module$1 = defineNuxtModule({
20
22
  prefix: options.prefix,
21
23
  ignore: ["color-mode/**", "content/**", "prose/**"]
22
24
  });
25
+ const require_ = createRequire(import.meta.url);
26
+ const pdfjsDir = dirname(require_.resolve("pdfjs-dist/package.json"));
27
+ nuxt.hook("nitro:config", (nitroConfig) => {
28
+ nitroConfig.publicAssets ||= [];
29
+ nitroConfig.publicAssets.push(
30
+ {
31
+ dir: join(pdfjsDir, "standard_fonts"),
32
+ baseURL: "/_kviewer/standard_fonts",
33
+ maxAge: 60 * 60 * 24 * 365
34
+ },
35
+ {
36
+ dir: join(pdfjsDir, "cmaps"),
37
+ baseURL: "/_kviewer/cmaps",
38
+ maxAge: 60 * 60 * 24 * 365
39
+ }
40
+ );
41
+ });
23
42
  }
24
43
  });
25
44
 
@@ -0,0 +1,19 @@
1
+ import type { CheckboxStyle } from './engine/types.js';
2
+ /**
3
+ * Maps each standard checkbox style to:
4
+ * - `caption`: the single ZapfDingbats character written to `/MK /CA` on
5
+ * the widget when the PDF is exported (per ISO 32000-1 §12.5.6.19).
6
+ * - `glyph`: a Unicode character with the same visual meaning, used by
7
+ * the HTML viewer to render the checkbox in the user's browser without
8
+ * needing to embed the ZapfDingbats font.
9
+ */
10
+ export declare const CHECKBOX_STYLE_TABLE: Record<CheckboxStyle, {
11
+ caption: string;
12
+ glyph: string;
13
+ }>;
14
+ /** Reverse lookup: ZapfDingbats `/MK /CA` character → CheckboxStyle. */
15
+ export declare function captionToStyle(caption: string): CheckboxStyle | undefined;
16
+ export declare const CHECKBOX_STYLE_OPTIONS: {
17
+ label: string;
18
+ value: CheckboxStyle;
19
+ }[];
@@ -0,0 +1,27 @@
1
+ export const CHECKBOX_STYLE_TABLE = {
2
+ // Heavy check / cross (U+2714 / U+2718) match Apple Preview's chunkier
3
+ // ZapfDingbats-style rendering — the thin U+2713/U+2717 glyphs feel
4
+ // anemic next to the field border. The trailing U+FE0E (variation
5
+ // selector-15) forces the text presentation, otherwise macOS swaps
6
+ // them for colored emoji.
7
+ check: { caption: "4", glyph: "\u2714\uFE0E" },
8
+ cross: { caption: "8", glyph: "\u2718\uFE0E" },
9
+ diamond: { caption: "u", glyph: "\u25C6" },
10
+ circle: { caption: "l", glyph: "\u25CF" },
11
+ star: { caption: "H", glyph: "\u2605" },
12
+ square: { caption: "n", glyph: "\u25A0" }
13
+ };
14
+ export function captionToStyle(caption) {
15
+ for (const [style, info] of Object.entries(CHECKBOX_STYLE_TABLE)) {
16
+ if (info.caption === caption) return style;
17
+ }
18
+ return void 0;
19
+ }
20
+ export const CHECKBOX_STYLE_OPTIONS = [
21
+ { label: "Check", value: "check" },
22
+ { label: "Cross", value: "cross" },
23
+ { label: "Diamond", value: "diamond" },
24
+ { label: "Circle", value: "circle" },
25
+ { label: "Star", value: "star" },
26
+ { label: "Square", value: "square" }
27
+ ];
@@ -279,5 +279,49 @@ export const annotationDefinitions = [
279
279
  resizable: true,
280
280
  draggable: true,
281
281
  icon: "i-lucide-stamp"
282
+ },
283
+ {
284
+ name: "formText",
285
+ type: AnnotationType.FORM_TEXT,
286
+ pdfjsEditorType: PdfjsAnnotationEditorType.NONE,
287
+ pdfjsAnnotationType: PdfjsAnnotationType.WIDGET,
288
+ subtype: "Widget",
289
+ isOnce: true,
290
+ resizable: false,
291
+ draggable: false,
292
+ icon: "i-lucide-text-cursor-input"
293
+ },
294
+ {
295
+ name: "formCheckbox",
296
+ type: AnnotationType.FORM_CHECKBOX,
297
+ pdfjsEditorType: PdfjsAnnotationEditorType.NONE,
298
+ pdfjsAnnotationType: PdfjsAnnotationType.WIDGET,
299
+ subtype: "Widget",
300
+ isOnce: true,
301
+ resizable: false,
302
+ draggable: false,
303
+ icon: "i-lucide-square-check"
304
+ },
305
+ {
306
+ name: "formSignature",
307
+ type: AnnotationType.FORM_SIGNATURE,
308
+ pdfjsEditorType: PdfjsAnnotationEditorType.NONE,
309
+ pdfjsAnnotationType: PdfjsAnnotationType.WIDGET,
310
+ subtype: "Widget",
311
+ isOnce: true,
312
+ resizable: false,
313
+ draggable: false,
314
+ icon: "i-lucide-signature"
315
+ },
316
+ {
317
+ name: "formRadio",
318
+ type: AnnotationType.FORM_RADIO,
319
+ pdfjsEditorType: PdfjsAnnotationEditorType.NONE,
320
+ pdfjsAnnotationType: PdfjsAnnotationType.WIDGET,
321
+ subtype: "Widget",
322
+ isOnce: true,
323
+ resizable: false,
324
+ draggable: false,
325
+ icon: "i-lucide-circle-dot"
282
326
  }
283
327
  ];
@@ -1,12 +1,14 @@
1
1
  import Konva from 'konva';
2
2
  import type { IRect } from 'konva/lib/types';
3
- import { type IAnnotationStore, type IAnnotationStyle, type IAnnotationType } from './types.js';
3
+ import { type IAnnotationStore, type IAnnotationStyle, type IAnnotationType, type PlaceFieldPayload } from './types.js';
4
4
  import { type IEditorFreeTextOptions } from './tools/free-text.js';
5
5
  export interface KonvaCanvas {
6
6
  pageNumber: number;
7
7
  konvaStage: Konva.Stage;
8
8
  wrapper: HTMLDivElement;
9
9
  isActive: boolean;
10
+ /** Base PDF-space page height — needed for coord flipping in placement tools. */
11
+ pageHeight: number;
10
12
  }
11
13
  export interface PainterCallbacks {
12
14
  setDefaultMode: () => void;
@@ -20,6 +22,7 @@ export interface PainterCallbacks {
20
22
  onAnnotationChanged: (annotationStore: IAnnotationStore, selectorRect: IRect) => void;
21
23
  onRequestTextInput: IEditorFreeTextOptions['onRequestTextInput'];
22
24
  onRequestDeleteConfirm?: (id: string) => Promise<boolean>;
25
+ onPlaceField?: (payload: PlaceFieldPayload) => void;
23
26
  }
24
27
  export declare class Painter {
25
28
  private userName;
@@ -22,6 +22,7 @@ import { EditorStamp } from "./tools/stamp.js";
22
22
  import { EditorNote } from "./tools/note.js";
23
23
  import { EditorArrow } from "./tools/arrow.js";
24
24
  import { EditorCloud } from "./tools/cloud.js";
25
+ import { EditorFormField } from "./tools/form-field.js";
25
26
  import { Selector } from "./editor/selector.js";
26
27
  import { Store } from "./store.js";
27
28
  import { isIPad } from "./input-device.js";
@@ -244,6 +245,11 @@ export class Painter {
244
245
  );
245
246
  return;
246
247
  }
248
+ if (storeEditor instanceof EditorFormField) {
249
+ storeEditor.setPageHeight(
250
+ this.konvaCanvasStore.get(pageNumber)?.pageHeight ?? 0
251
+ );
252
+ }
247
253
  storeEditor.activate(konvaStage, annotation);
248
254
  return;
249
255
  }
@@ -312,6 +318,24 @@ export class Painter {
312
318
  case AnnotationType.STRIKEOUT:
313
319
  editor = new EditorHighLight(editorOpts, annotation.type);
314
320
  break;
321
+ case AnnotationType.FORM_TEXT:
322
+ case AnnotationType.FORM_CHECKBOX:
323
+ case AnnotationType.FORM_RADIO:
324
+ case AnnotationType.FORM_SIGNATURE: {
325
+ const onPlaceField = this.callbacks.onPlaceField;
326
+ if (!onPlaceField) {
327
+ console.warn("Form-field placement requires an onPlaceField callback.");
328
+ return;
329
+ }
330
+ const pageHeight = this.konvaCanvasStore.get(pageNumber)?.pageHeight ?? 0;
331
+ const fieldType = annotation.type === AnnotationType.FORM_TEXT ? "text" : annotation.type === AnnotationType.FORM_CHECKBOX ? "checkbox" : annotation.type === AnnotationType.FORM_RADIO ? "radio" : "signature";
332
+ editor = new EditorFormField(editorOpts, {
333
+ fieldType,
334
+ pageHeight,
335
+ onPlaceField
336
+ });
337
+ break;
338
+ }
315
339
  case AnnotationType.SELECT:
316
340
  this.selector.activate(pageNumber);
317
341
  break;
@@ -451,7 +475,8 @@ export class Painter {
451
475
  pageNumber,
452
476
  konvaStage,
453
477
  wrapper: container,
454
- isActive: false
478
+ isActive: false,
479
+ pageHeight: height
455
480
  });
456
481
  this.reDrawAnnotation(pageNumber);
457
482
  this.enablePainting();
@@ -0,0 +1,24 @@
1
+ import type { KonvaEventObject } from 'konva/lib/Node';
2
+ import { type FormFieldType, type IAnnotationStore, type IAnnotationStyle, type PlaceFieldPayload } from '../types.js';
3
+ import { Editor, type IEditorOptions } from '../editor/editor.js';
4
+ export interface IEditorFormFieldOptions {
5
+ fieldType: FormFieldType;
6
+ /** PDF-space page height, needed to flip Konva page-local Y → PDF Y. */
7
+ pageHeight: number;
8
+ onPlaceField: (payload: PlaceFieldPayload) => void;
9
+ }
10
+ export declare class EditorFormField extends Editor {
11
+ private fieldType;
12
+ private pageHeight;
13
+ private onPlaceField;
14
+ private preview;
15
+ private vertex;
16
+ constructor(editorOptions: IEditorOptions, opts: IEditorFormFieldOptions);
17
+ setPageHeight(pageHeight: number): void;
18
+ protected mouseDownHandler(e: KonvaEventObject<MouseEvent | TouchEvent>): void;
19
+ protected mouseMoveHandler(e: KonvaEventObject<MouseEvent | TouchEvent>): void;
20
+ protected mouseUpHandler(): void;
21
+ private globalPointerUpHandler;
22
+ protected changeStyle(_a: IAnnotationStore, _s: IAnnotationStyle): void;
23
+ addSerializedGroupToLayer(): void;
24
+ }
@@ -0,0 +1,117 @@
1
+ import Konva from "konva";
2
+ import {
3
+ AnnotationType
4
+ } from "../types.js";
5
+ import { Editor } from "../editor/editor.js";
6
+ const TOO_SMALL = 6;
7
+ const DEFAULT_CHECKBOX = 18;
8
+ const DEFAULT_RADIO = 18;
9
+ const DEFAULT_SIGNATURE = { width: 200, height: 60 };
10
+ export class EditorFormField extends Editor {
11
+ fieldType;
12
+ pageHeight;
13
+ onPlaceField;
14
+ preview = null;
15
+ vertex = { x: 0, y: 0 };
16
+ constructor(editorOptions, opts) {
17
+ super({
18
+ ...editorOptions,
19
+ editorType: opts.fieldType === "text" ? AnnotationType.FORM_TEXT : opts.fieldType === "checkbox" ? AnnotationType.FORM_CHECKBOX : opts.fieldType === "radio" ? AnnotationType.FORM_RADIO : AnnotationType.FORM_SIGNATURE
20
+ });
21
+ this.fieldType = opts.fieldType;
22
+ this.pageHeight = opts.pageHeight;
23
+ this.onPlaceField = opts.onPlaceField;
24
+ }
25
+ setPageHeight(pageHeight) {
26
+ this.pageHeight = pageHeight;
27
+ }
28
+ mouseDownHandler(e) {
29
+ if (e.currentTarget !== this.konvaStage) return;
30
+ const pos = this.konvaStage.getRelativePointerPosition();
31
+ if (!pos) return;
32
+ this.isPainting = true;
33
+ this.vertex = { x: pos.x, y: pos.y };
34
+ this.preview = new Konva.Rect({
35
+ x: pos.x,
36
+ y: pos.y,
37
+ width: 0,
38
+ height: 0,
39
+ stroke: "#1677ff",
40
+ strokeWidth: 1,
41
+ dash: [4, 4],
42
+ strokeScaleEnabled: false,
43
+ visible: false,
44
+ listening: false
45
+ });
46
+ this.getBgLayer().add(this.preview);
47
+ window.addEventListener("mouseup", this.globalPointerUpHandler);
48
+ }
49
+ mouseMoveHandler(e) {
50
+ if (!this.isPainting || !this.preview) return;
51
+ e.evt.preventDefault();
52
+ const pos = this.konvaStage.getRelativePointerPosition();
53
+ if (!pos) return;
54
+ this.preview.show();
55
+ this.preview.setAttrs({
56
+ x: Math.min(this.vertex.x, pos.x),
57
+ y: Math.min(this.vertex.y, pos.y),
58
+ width: Math.abs(pos.x - this.vertex.x),
59
+ height: Math.abs(pos.y - this.vertex.y)
60
+ });
61
+ }
62
+ mouseUpHandler() {
63
+ if (!this.isPainting) return;
64
+ this.isPainting = false;
65
+ window.removeEventListener("mouseup", this.globalPointerUpHandler);
66
+ const preview = this.preview;
67
+ if (!preview) return;
68
+ let x = preview.x();
69
+ let y = preview.y();
70
+ let w = preview.width();
71
+ let h = preview.height();
72
+ const dragged = preview.isVisible() && Math.max(w, h) >= TOO_SMALL;
73
+ if (!dragged) {
74
+ if (this.fieldType === "checkbox") {
75
+ w = DEFAULT_CHECKBOX;
76
+ h = DEFAULT_CHECKBOX;
77
+ x = this.vertex.x - w / 2;
78
+ y = this.vertex.y - h / 2;
79
+ } else if (this.fieldType === "radio") {
80
+ w = DEFAULT_RADIO;
81
+ h = DEFAULT_RADIO;
82
+ x = this.vertex.x - w / 2;
83
+ y = this.vertex.y - h / 2;
84
+ } else if (this.fieldType === "signature") {
85
+ w = DEFAULT_SIGNATURE.width;
86
+ h = DEFAULT_SIGNATURE.height;
87
+ x = this.vertex.x - w / 2;
88
+ y = this.vertex.y - h / 2;
89
+ } else {
90
+ preview.destroy();
91
+ this.preview = null;
92
+ return;
93
+ }
94
+ }
95
+ preview.destroy();
96
+ this.preview = null;
97
+ const x1 = x;
98
+ const x2 = x + w;
99
+ const y2 = this.pageHeight - y;
100
+ const y1 = this.pageHeight - (y + h);
101
+ this.onPlaceField({
102
+ pageNumber: this.pageNumber,
103
+ fieldType: this.fieldType,
104
+ rectPdf: [x1, y1, x2, y2]
105
+ });
106
+ }
107
+ globalPointerUpHandler = (e) => {
108
+ if (e.button !== 0) return;
109
+ this.mouseUpHandler();
110
+ };
111
+ changeStyle(_a, _s) {
112
+ }
113
+ // Placed fields are never re-drawn from a konva string; the form-field
114
+ // HTML layer owns their visual representation.
115
+ addSerializedGroupToLayer() {
116
+ }
117
+ }
@@ -53,7 +53,11 @@ export declare enum AnnotationType {
53
53
  STAMP = 10,
54
54
  NOTE = 11,
55
55
  ARROW = 12,
56
- CLOUD = 13
56
+ CLOUD = 13,
57
+ FORM_TEXT = 14,
58
+ FORM_CHECKBOX = 15,
59
+ FORM_SIGNATURE = 16,
60
+ FORM_RADIO = 17
57
61
  }
58
62
  export interface IAnnotationType {
59
63
  name: string;
@@ -134,16 +138,42 @@ export interface IAnnotationStore {
134
138
  draggable: boolean;
135
139
  }
136
140
  export type FormFieldType = 'text' | 'checkbox' | 'radio' | 'dropdown' | 'signature' | 'button';
141
+ /**
142
+ * Standard PDF checkbox glyph styles, per Acrobat's "Check Box Style"
143
+ * options. Each maps to a single ZapfDingbats character written to the
144
+ * widget's `/MK /CA` entry — see ISO 32000-1 §12.5.6.19.
145
+ */
146
+ export type CheckboxStyle = 'check' | 'cross' | 'diamond' | 'circle' | 'star' | 'square';
147
+ /**
148
+ * Where a form field came from. Export uses this to decide whether to
149
+ * update an existing PDF widget ('parsed') or create a new one ('placed',
150
+ * 'detected'). Missing = 'parsed' for backward compat.
151
+ */
152
+ export type FormFieldOrigin = 'parsed' | 'detected' | 'placed';
137
153
  export interface FormFieldDefinition {
138
154
  id: string;
139
155
  pageNumber: number;
140
156
  fieldType: FormFieldType;
141
157
  fieldName: string;
142
158
  rect: [number, number, number, number];
159
+ /** The rect this field had when first parsed from the source PDF. Used
160
+ * on export to detect that a parsed field has been moved/resized so
161
+ * its existing widget can be updated in place. Unset for placed and
162
+ * detected fields (their `rect` IS their original rect). */
163
+ originalRect?: [number, number, number, number];
143
164
  readOnly: boolean;
144
165
  required: boolean;
166
+ origin?: FormFieldOrigin;
145
167
  /** Visual shape hint for detected checkboxes (circle renders round, rectangle renders square). */
146
168
  shapeType?: DetectedShapeType;
169
+ /** Checkbox glyph style. Only used when fieldType === 'checkbox'.
170
+ * Defaults to 'check' for placed checkboxes; populated from `/MK /CA`
171
+ * for parsed checkboxes when the source PDF specifies a non-default. */
172
+ checkboxStyle?: CheckboxStyle;
173
+ /** The checkbox style this field had when first parsed from the source
174
+ * PDF. Used on export to detect that a parsed checkbox's style was
175
+ * changed in form-edit mode, so we know to overwrite its `/MK /CA`. */
176
+ originalCheckboxStyle?: CheckboxStyle;
147
177
  defaultValue?: string;
148
178
  maxLen?: number;
149
179
  multiLine?: boolean;
@@ -159,7 +189,23 @@ export interface FormFieldDefinition {
159
189
  multiSelect?: boolean;
160
190
  editable?: boolean;
161
191
  buttonLabel?: string;
192
+ /** Signature-only: placeholder text shown on the unsigned widget.
193
+ * Defaults to 'Click to sign' when unset. */
194
+ promptText?: string;
195
+ /** Signature-only: when this signature is filled, lock other fields in
196
+ * the form so they can no longer be edited. Mirrors PDF spec
197
+ * /Lock /Action: 'all' locks every other field; 'include' locks only
198
+ * fields listed in lockFieldNames; 'exclude' locks every field NOT
199
+ * listed. Enforced inside the viewer (the viewer flattens signatures
200
+ * to images on export, so this doesn't round-trip as a real /Sig
201
+ * /Lock dict). */
202
+ lockAction?: 'all' | 'include' | 'exclude';
203
+ /** Field names referenced by lockAction = 'include' or 'exclude'. */
204
+ lockFieldNames?: string[];
162
205
  fontSize?: number;
206
+ /** PDF font name as parsed from `/DA` (e.g. "Helv", "HelvB", "Times-Bold").
207
+ * Used to derive CSS font-family / font-weight / font-style. */
208
+ fontName?: string;
163
209
  color?: number[];
164
210
  backgroundColor?: number[];
165
211
  textAlignment?: number;
@@ -170,6 +216,13 @@ export interface FormFieldValue {
170
216
  fieldType: FormFieldType;
171
217
  value: string | boolean | string[];
172
218
  }
219
+ /** Payload for placing a new form field via the drawing tool. */
220
+ export interface PlaceFieldPayload {
221
+ pageNumber: number;
222
+ fieldType: FormFieldType;
223
+ /** PDF-space rect [x1, y1, x2, y2], bottom-left origin. */
224
+ rectPdf: [number, number, number, number];
225
+ }
173
226
  export interface DetectedShapeRect {
174
227
  x: number;
175
228
  y: number;
@@ -54,6 +54,10 @@ export var AnnotationType = /* @__PURE__ */ ((AnnotationType2) => {
54
54
  AnnotationType2[AnnotationType2["NOTE"] = 11] = "NOTE";
55
55
  AnnotationType2[AnnotationType2["ARROW"] = 12] = "ARROW";
56
56
  AnnotationType2[AnnotationType2["CLOUD"] = 13] = "CLOUD";
57
+ AnnotationType2[AnnotationType2["FORM_TEXT"] = 14] = "FORM_TEXT";
58
+ AnnotationType2[AnnotationType2["FORM_CHECKBOX"] = 15] = "FORM_CHECKBOX";
59
+ AnnotationType2[AnnotationType2["FORM_SIGNATURE"] = 16] = "FORM_SIGNATURE";
60
+ AnnotationType2[AnnotationType2["FORM_RADIO"] = 17] = "FORM_RADIO";
57
61
  return AnnotationType2;
58
62
  })(AnnotationType || {});
59
63
  export var CommentStatus = /* @__PURE__ */ ((CommentStatus2) => {
@@ -0,0 +1,23 @@
1
+ /**
2
+ * Decode the font name parsed from a PDF widget's `/DA` (Default
3
+ * Appearance) string into CSS font properties.
4
+ *
5
+ * PDF /DA font names come in two flavours:
6
+ * - PDF "abbreviations" used by Acrobat for the standard 14 fonts
7
+ * (`Helv`, `HelvB`, `HelvO`, `HelvBO`, `TiRo`, `TiBo`, `TiIt`,
8
+ * `TiBI`, `Cour`, `CoBo`, `CoOb`, `CoBO`, `ZaDb`, `Symb`).
9
+ * - Full PostScript-style names (`Helvetica-Bold`, `Times-Italic`,
10
+ * `Courier-BoldOblique`, etc.) — common in PDFs produced by other
11
+ * tools.
12
+ *
13
+ * Anything we don't recognize falls back to sans-serif regular —
14
+ * exactly what pdf.js's annotation layer renders. Unrecognized names
15
+ * still keep weight/style detection via the `Bold`/`Italic`/`Oblique`
16
+ * substring check, so e.g. `MyFont-Bold` becomes `bold`.
17
+ */
18
+ export interface DecodedFontStyle {
19
+ fontFamily: string;
20
+ fontWeight: 'normal' | 'bold';
21
+ fontStyle: 'normal' | 'italic';
22
+ }
23
+ export declare function decodeFontName(fontName: string | undefined): DecodedFontStyle;
@@ -0,0 +1,57 @@
1
+ const STANDARD_14 = {
2
+ // Helvetica family
3
+ Helv: { fontFamily: '"Helvetica Neue", Helvetica, Arial, sans-serif', fontWeight: "normal", fontStyle: "normal" },
4
+ HelvB: { fontFamily: '"Helvetica Neue", Helvetica, Arial, sans-serif', fontWeight: "bold", fontStyle: "normal" },
5
+ HelvO: { fontFamily: '"Helvetica Neue", Helvetica, Arial, sans-serif', fontWeight: "normal", fontStyle: "italic" },
6
+ HelvBO: { fontFamily: '"Helvetica Neue", Helvetica, Arial, sans-serif', fontWeight: "bold", fontStyle: "italic" },
7
+ Helvetica: { fontFamily: '"Helvetica Neue", Helvetica, Arial, sans-serif', fontWeight: "normal", fontStyle: "normal" },
8
+ "Helvetica-Bold": { fontFamily: '"Helvetica Neue", Helvetica, Arial, sans-serif', fontWeight: "bold", fontStyle: "normal" },
9
+ "Helvetica-Oblique": { fontFamily: '"Helvetica Neue", Helvetica, Arial, sans-serif', fontWeight: "normal", fontStyle: "italic" },
10
+ "Helvetica-BoldOblique": { fontFamily: '"Helvetica Neue", Helvetica, Arial, sans-serif', fontWeight: "bold", fontStyle: "italic" },
11
+ // Times family
12
+ TiRo: { fontFamily: '"Times New Roman", Times, serif', fontWeight: "normal", fontStyle: "normal" },
13
+ TiBo: { fontFamily: '"Times New Roman", Times, serif', fontWeight: "bold", fontStyle: "normal" },
14
+ TiIt: { fontFamily: '"Times New Roman", Times, serif', fontWeight: "normal", fontStyle: "italic" },
15
+ TiBI: { fontFamily: '"Times New Roman", Times, serif', fontWeight: "bold", fontStyle: "italic" },
16
+ "Times-Roman": { fontFamily: '"Times New Roman", Times, serif', fontWeight: "normal", fontStyle: "normal" },
17
+ "Times-Bold": { fontFamily: '"Times New Roman", Times, serif', fontWeight: "bold", fontStyle: "normal" },
18
+ "Times-Italic": { fontFamily: '"Times New Roman", Times, serif', fontWeight: "normal", fontStyle: "italic" },
19
+ "Times-BoldItalic": { fontFamily: '"Times New Roman", Times, serif', fontWeight: "bold", fontStyle: "italic" },
20
+ // Courier family
21
+ Cour: { fontFamily: '"Courier New", Courier, monospace', fontWeight: "normal", fontStyle: "normal" },
22
+ CoBo: { fontFamily: '"Courier New", Courier, monospace', fontWeight: "bold", fontStyle: "normal" },
23
+ CoOb: { fontFamily: '"Courier New", Courier, monospace', fontWeight: "normal", fontStyle: "italic" },
24
+ CoBO: { fontFamily: '"Courier New", Courier, monospace', fontWeight: "bold", fontStyle: "italic" },
25
+ Courier: { fontFamily: '"Courier New", Courier, monospace', fontWeight: "normal", fontStyle: "normal" },
26
+ "Courier-Bold": { fontFamily: '"Courier New", Courier, monospace', fontWeight: "bold", fontStyle: "normal" },
27
+ "Courier-Oblique": { fontFamily: '"Courier New", Courier, monospace', fontWeight: "normal", fontStyle: "italic" },
28
+ "Courier-BoldOblique": { fontFamily: '"Courier New", Courier, monospace', fontWeight: "bold", fontStyle: "italic" }
29
+ };
30
+ const DEFAULT_STYLE = {
31
+ fontFamily: "sans-serif",
32
+ fontWeight: "normal",
33
+ fontStyle: "normal"
34
+ };
35
+ export function decodeFontName(fontName) {
36
+ if (!fontName) return DEFAULT_STYLE;
37
+ const name = fontName.startsWith("/") ? fontName.slice(1) : fontName;
38
+ const standard = STANDARD_14[name];
39
+ if (standard) return standard;
40
+ const stripped = /^[A-Z]{6}\+/.test(name) ? name.slice(7) : name;
41
+ const lower = stripped.toLowerCase();
42
+ let fontFamily = "sans-serif";
43
+ if (/serif|times|roman|georgia|garamond|cambria/.test(lower)) {
44
+ fontFamily = '"Times New Roman", Times, serif';
45
+ } else if (/mono|courier|consolas|menlo|inconsolata/.test(lower)) {
46
+ fontFamily = '"Courier New", Courier, monospace';
47
+ } else if (/helvet|arial|verdana|tahoma|sans/.test(lower)) {
48
+ fontFamily = '"Helvetica Neue", Helvetica, Arial, sans-serif';
49
+ }
50
+ const isBold = /bold|black|heavy|semibold|demibold/i.test(stripped);
51
+ const isItalic = /italic|oblique/i.test(stripped);
52
+ return {
53
+ fontFamily,
54
+ fontWeight: isBold ? "bold" : "normal",
55
+ fontStyle: isItalic ? "italic" : "normal"
56
+ };
57
+ }
@@ -0,0 +1,21 @@
1
+ import type { CheckboxStyle } from '../engine/types.js';
2
+ /**
3
+ * Walk the AcroForm of a source PDF and extract the checkbox style hint
4
+ * (`/MK /CA`) for every checkbox widget. pdf.js does not surface this
5
+ * field, so without a side-channel parse we cannot know whether an
6
+ * existing checkbox should render as a check, cross, diamond, etc.
7
+ *
8
+ * Returns a Map keyed by field name. If multiple widgets share a name
9
+ * (uncommon for checkboxes, normal for radio groups), the first hit
10
+ * wins.
11
+ */
12
+ export declare function extractCheckboxStyles(pdfBytes: Uint8Array | ArrayBuffer): Promise<Map<string, CheckboxStyle>>;
13
+ /**
14
+ * Walk the AcroForm and extract push-button captions from `/MK /CA`.
15
+ * pdf.js exposes neither the caption nor a button label, so without this
16
+ * side-channel parse every push button would render with our generic
17
+ * "Button" fallback (e.g. a Reset button shows the wrong text).
18
+ *
19
+ * Returns a Map keyed by the button's fully qualified field name.
20
+ */
21
+ export declare function extractButtonCaptions(pdfBytes: Uint8Array | ArrayBuffer): Promise<Map<string, string>>;