kviewer 0.0.9 → 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.
- package/README.md +29 -0
- package/dist/module.json +1 -1
- package/dist/module.mjs +19 -0
- 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/editor/editor.d.ts +6 -3
- package/dist/runtime/annotation/engine/editor/editor.js +22 -9
- package/dist/runtime/annotation/engine/editor/selector.d.ts +9 -1
- package/dist/runtime/annotation/engine/editor/selector.js +72 -30
- package/dist/runtime/annotation/engine/painter.d.ts +6 -2
- package/dist/runtime/annotation/engine/painter.js +42 -5
- package/dist/runtime/annotation/engine/tools/form-field.d.ts +24 -0
- package/dist/runtime/annotation/engine/tools/form-field.js +117 -0
- package/dist/runtime/annotation/engine/types.d.ts +54 -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 +245 -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.d.vue.ts +6 -1
- package/dist/runtime/components/PdfPage.vue +75 -18
- package/dist/runtime/components/PdfPage.vue.d.ts +6 -1
- package/dist/runtime/components/ToolButton.d.vue.ts +4 -2
- package/dist/runtime/components/ToolButton.vue.d.ts +4 -2
- package/dist/runtime/components/Viewer.d.vue.ts +18 -3
- package/dist/runtime/components/Viewer.vue +278 -170
- package/dist/runtime/components/Viewer.vue.d.ts +18 -3
- package/dist/runtime/components/ViewerBar.vue +24 -2
- 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 +45 -9
- 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/FreeTextModal.vue +27 -3
- 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/ActionTools.vue +4 -5
- 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 +6 -2
- package/dist/runtime/composables/useAnnotationHistory.d.ts +1 -0
- package/dist/runtime/composables/useAnnotationHistory.js +10 -3
- package/dist/runtime/composables/useFormFields.d.ts +29 -2
- package/dist/runtime/composables/useFormFields.js +259 -5
- package/dist/runtime/composables/useInertiaPanzoom.d.ts +40 -0
- package/dist/runtime/composables/useInertiaPanzoom.js +475 -0
- package/dist/runtime/composables/usePageVirtualization.d.ts +9 -0
- package/dist/runtime/composables/usePageVirtualization.js +34 -3
- package/dist/runtime/composables/useViewerSearch.d.ts +1 -0
- package/dist/runtime/composables/useViewerSearch.js +15 -5
- package/dist/runtime/composables/useViewerState.d.ts +3 -0
- package/dist/runtime/composables/useViewerState.js +22 -1
- package/package.json +2 -2
|
@@ -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>>;
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import { PDFButton, PDFCheckBox, PDFDocument, PDFDict, PDFHexString, PDFName, PDFString } from "pdf-lib";
|
|
2
|
+
import { captionToStyle } from "../checkbox-styles.js";
|
|
3
|
+
export async function extractCheckboxStyles(pdfBytes) {
|
|
4
|
+
const out = /* @__PURE__ */ new Map();
|
|
5
|
+
let doc;
|
|
6
|
+
try {
|
|
7
|
+
doc = await PDFDocument.load(pdfBytes, { ignoreEncryption: true });
|
|
8
|
+
} catch {
|
|
9
|
+
return out;
|
|
10
|
+
}
|
|
11
|
+
let form;
|
|
12
|
+
try {
|
|
13
|
+
form = doc.getForm();
|
|
14
|
+
} catch {
|
|
15
|
+
return out;
|
|
16
|
+
}
|
|
17
|
+
for (const field of form.getFields()) {
|
|
18
|
+
if (!(field instanceof PDFCheckBox)) continue;
|
|
19
|
+
const acro = field.acroField;
|
|
20
|
+
const name = field.getName();
|
|
21
|
+
if (!name || out.has(name)) continue;
|
|
22
|
+
const widgets = acro.getWidgets();
|
|
23
|
+
for (const widget of widgets) {
|
|
24
|
+
const mk = widget.dict.get(PDFName.of("MK"));
|
|
25
|
+
if (!(mk instanceof PDFDict)) continue;
|
|
26
|
+
const ca = mk.get(PDFName.of("CA"));
|
|
27
|
+
let caption;
|
|
28
|
+
if (ca instanceof PDFString || ca instanceof PDFHexString) {
|
|
29
|
+
caption = ca.decodeText();
|
|
30
|
+
}
|
|
31
|
+
if (!caption) continue;
|
|
32
|
+
const style = captionToStyle(caption);
|
|
33
|
+
if (style) {
|
|
34
|
+
out.set(name, style);
|
|
35
|
+
break;
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
return out;
|
|
40
|
+
}
|
|
41
|
+
export async function extractButtonCaptions(pdfBytes) {
|
|
42
|
+
const out = /* @__PURE__ */ new Map();
|
|
43
|
+
let doc;
|
|
44
|
+
try {
|
|
45
|
+
doc = await PDFDocument.load(pdfBytes, { ignoreEncryption: true });
|
|
46
|
+
} catch {
|
|
47
|
+
return out;
|
|
48
|
+
}
|
|
49
|
+
let form;
|
|
50
|
+
try {
|
|
51
|
+
form = doc.getForm();
|
|
52
|
+
} catch {
|
|
53
|
+
return out;
|
|
54
|
+
}
|
|
55
|
+
for (const field of form.getFields()) {
|
|
56
|
+
if (!(field instanceof PDFButton)) continue;
|
|
57
|
+
const acro = field.acroField;
|
|
58
|
+
const name = field.getName();
|
|
59
|
+
if (!name || out.has(name)) continue;
|
|
60
|
+
const widgets = acro.getWidgets();
|
|
61
|
+
for (const widget of widgets) {
|
|
62
|
+
const mk = widget.dict.get(PDFName.of("MK"));
|
|
63
|
+
if (!(mk instanceof PDFDict)) continue;
|
|
64
|
+
const ca = mk.get(PDFName.of("CA"));
|
|
65
|
+
if (ca instanceof PDFString || ca instanceof PDFHexString) {
|
|
66
|
+
const caption = ca.decodeText();
|
|
67
|
+
if (caption) {
|
|
68
|
+
out.set(name, caption);
|
|
69
|
+
break;
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
return out;
|
|
75
|
+
}
|
|
@@ -19,8 +19,10 @@ export function parseFormFields(annotations, pageNumber) {
|
|
|
19
19
|
fieldType,
|
|
20
20
|
fieldName: ann.fieldName ?? "",
|
|
21
21
|
rect: normalizedRect,
|
|
22
|
+
originalRect: [...normalizedRect],
|
|
22
23
|
readOnly: Boolean(ann.readOnly),
|
|
23
|
-
required: Boolean(ann.required)
|
|
24
|
+
required: Boolean(ann.required),
|
|
25
|
+
origin: "parsed"
|
|
24
26
|
};
|
|
25
27
|
if (fieldType === "text") {
|
|
26
28
|
if (ann.fieldValue != null) def.defaultValue = String(ann.fieldValue);
|
|
@@ -68,8 +70,19 @@ export function parseFormFields(annotations, pageNumber) {
|
|
|
68
70
|
if (fieldType === "button") {
|
|
69
71
|
def.buttonLabel = ann.alternativeText ?? ann.fieldValue ?? ann.fieldName ?? "Button";
|
|
70
72
|
}
|
|
71
|
-
|
|
72
|
-
if (
|
|
73
|
+
const da = ann.defaultAppearanceData ?? {};
|
|
74
|
+
if (typeof da.fontSize === "number" && da.fontSize > 0) {
|
|
75
|
+
def.fontSize = da.fontSize;
|
|
76
|
+
} else if (typeof ann.fontSize === "number" && ann.fontSize > 0) {
|
|
77
|
+
def.fontSize = ann.fontSize;
|
|
78
|
+
}
|
|
79
|
+
if (typeof da.fontName === "string" && da.fontName.length > 0) {
|
|
80
|
+
def.fontName = da.fontName;
|
|
81
|
+
}
|
|
82
|
+
if (da.fontColor && da.fontColor.length >= 3) {
|
|
83
|
+
const fc = da.fontColor;
|
|
84
|
+
def.color = [fc[0] ?? 0, fc[1] ?? 0, fc[2] ?? 0];
|
|
85
|
+
} else if (Array.isArray(ann.color)) {
|
|
73
86
|
def.color = ann.color.map(
|
|
74
87
|
(c) => (
|
|
75
88
|
// pdf.js colors may be 0-1 floats or 0-255 integers
|
|
@@ -1,9 +1,12 @@
|
|
|
1
|
-
import type
|
|
2
|
-
import type { FormFieldValue } from '../engine/types.js';
|
|
1
|
+
import { type PDFDocument } from 'pdf-lib';
|
|
2
|
+
import type { FormFieldDefinition, FormFieldValue } from '../engine/types.js';
|
|
3
3
|
/**
|
|
4
4
|
* Write form field values back to the PDF using pdf-lib's AcroForm API.
|
|
5
5
|
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
6
|
+
* - Placed/detected fields that originate from the viewer are created as new
|
|
7
|
+
* AcroForm widgets (text, checkbox, radio). Signature fields are drawn as
|
|
8
|
+
* flat images (pdf-lib does not support creating AcroForm signature widgets).
|
|
9
|
+
* - Parsed fields (already present in the source PDF) get their values
|
|
10
|
+
* updated in place.
|
|
8
11
|
*/
|
|
9
|
-
export declare function writeFormFieldsToPdf(pdfDoc: PDFDocument, fieldValues: FormFieldValue[]): Promise<void>;
|
|
12
|
+
export declare function writeFormFieldsToPdf(pdfDoc: PDFDocument, fieldValues: FormFieldValue[], placedDefinitions?: FormFieldDefinition[]): Promise<void>;
|