@peculiar/pdf-form-json 1.17.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 ADDED
@@ -0,0 +1,148 @@
1
+ # `@peculiar/pdf-form-json`
2
+
3
+ `@peculiar/pdf-form-json` is a library for working with PDF form data in JSON format. This library provides a FormConverter class that can convert a PDF document with form data into a JSON representation of that data, and also set values on form components based on a JSON payload.
4
+
5
+ ## Installation
6
+
7
+ Using npm:
8
+
9
+ ```bash
10
+ npm install @peculiar/pdf-form-json
11
+ ```
12
+
13
+ Using yarn:
14
+
15
+ ```bash
16
+ yarn add @peculiar/pdf-form-json
17
+ ```
18
+
19
+ ## Usage
20
+
21
+ ### Import
22
+
23
+ ```typescript
24
+ import { FormConverter } from "@peculiar/pdf-form-json";
25
+ ```
26
+
27
+ ### Export Form Data to JSON
28
+
29
+ The export method of the `FormConverter` class can be used to convert a PDF document with form data into a JSON representation of that data. The returned JSON will be of the format:
30
+
31
+ ```typescript
32
+ {
33
+ form: {
34
+ [componentName: string]: {
35
+ type: string,
36
+ id: string,
37
+ name: string,
38
+ position: {
39
+ left: number,
40
+ top: number,
41
+ height: number,
42
+ width: number,
43
+ },
44
+ flags: {
45
+ hidden: boolean,
46
+ invisible: boolean,
47
+ locked: boolean,
48
+ lockedContents: boolean,
49
+ noExport: boolean,
50
+ noRotate: boolean,
51
+ noView: boolean,
52
+ noZoom: boolean,
53
+ print: boolean,
54
+ readOnly: boolean,
55
+ readOnlyAnnot: boolean,
56
+ required: boolean,
57
+ toggleNoView: boolean,
58
+ },
59
+ [otherProperties: string]: any,
60
+ },
61
+ ...
62
+ }
63
+ }
64
+ ```
65
+
66
+ ```typescript
67
+ import fs from "fs";
68
+ import { PDFDocument } from ("@peculiar/pdf-doc");
69
+ import { globalFormConverter } from ("@peculiar/pdf-form-json");
70
+
71
+ // Load PDF document
72
+ const pdfBytes = fs.readFileSync("input.pdf");
73
+ const pdfDoc = await PDFDocument.load(pdfBytes);
74
+
75
+ // Export form data to JSON
76
+ const formData = globalFormConverter.export(pdfDoc);
77
+
78
+ // Write JSON to file
79
+ fs.writeFileSync("output.json", JSON.stringify(formData, null, 2));
80
+ ```
81
+
82
+ ### Set Form Component Values from JSON
83
+
84
+ The setValue method of the FormConverter class can be used to set values on form components based on a JSON payload. The payload should be an array of objects, where each object represents a form component and its updated value. The objects should have the following properties:
85
+
86
+ - `name` (string): The name of the form component to update.
87
+ - `type` (string): The type of the form component to update.
88
+ - Additional properties for the specific form component type being updated.
89
+
90
+ ```typescript
91
+ import fs from "fs";
92
+ import { PDFDocument } from ("@peculiar/pdf-doc");
93
+ import { globalFormConverter } from ("@peculiar/pdf-form-json");
94
+
95
+ // Load PDF document
96
+ const pdfBytes = fs.readFileSync("input.pdf");
97
+ const pdfDoc = await PDFDocument.load(pdfBytes);
98
+
99
+ // Update PDF form
100
+ globalFormConverter.setValue(doc, [
101
+ {
102
+ name: "Text1",
103
+ type: "text_editor",
104
+ text: "Some message",
105
+ },
106
+ {
107
+ name: "Check Box2",
108
+ type: "check_box",
109
+ checked: true,
110
+ },
111
+ {
112
+ name: "Check Box3",
113
+ type: "check_box",
114
+ checked: false,
115
+ },
116
+ {
117
+ name: "Group4",
118
+ type: "radio_button_group",
119
+ selected: "Choice2",
120
+ },
121
+ {
122
+ name: "Dropdown5",
123
+ type: "combo_box",
124
+ selected: ["option2"],
125
+ }
126
+ ]);
127
+
128
+ // Save file
129
+ const pdfRaw = await pdfDoc.save();
130
+ fs.writeFileSync("output.pdf", pdfRaw, {flags: "w+"})
131
+ ```
132
+
133
+ ## License
134
+
135
+ This project is dual-licensed under the AGPL-3.0 and a Commercial License:
136
+
137
+ ### AGPL-3.0
138
+
139
+ The library is open-source and freely available under the terms of the Affero General Public License (AGPL-3.0). This ensures that modifications and usage in network-based applications (e.g., SaaS) must be shared under the same license.
140
+
141
+ You can use the library under AGPL-3.0 if:
142
+
143
+ - Your project is open-source and distributed under a compatible license
144
+ - Your use complies with the AGPL-3.0 requirements
145
+
146
+ ### Commercial License
147
+
148
+ For use in proprietary, closed-source, or commercial projects where the AGPL is not suitable, a commercial license is available. This allows you to use the library without the AGPL's obligations.
@@ -0,0 +1,250 @@
1
+ import * as pdfDoc from '@peculiar/pdf-doc';
2
+ import { FormComponentType } from '@peculiar/pdf-doc';
3
+
4
+ /**
5
+ * Type representing a constructor for an IComponent instance.
6
+ */
7
+ type IComponentConstructor<T extends pdfDoc.IComponent> = new (target: any, doc: pdfDoc.PDFDocument) => T;
8
+ /**
9
+ * Interface representing the JSON object for a form component.
10
+ */
11
+ interface JsonComponent {
12
+ name: string;
13
+ }
14
+ /**
15
+ * Interface representing the JSON object for a PDF form.
16
+ */
17
+ interface JsonForm {
18
+ form: Record<string, JsonComponent>;
19
+ }
20
+ /**
21
+ * Interface representing the update data for a form component.
22
+ */
23
+ interface JsonComponentUpdate {
24
+ type: FormComponentType[keyof FormComponentType];
25
+ name: string;
26
+ }
27
+ /**
28
+ * Interface representing the update data for a check box form component.
29
+ */
30
+ interface JsonCheckBoxUpdate extends JsonComponentUpdate {
31
+ type: "check_box";
32
+ checked: boolean;
33
+ }
34
+ /**
35
+ * Interface representing the update data for a radio button form component.
36
+ */
37
+ interface JsonRadioButtonUpdate extends JsonComponentUpdate {
38
+ type: "radio_button";
39
+ checked: boolean;
40
+ }
41
+ /**
42
+ * Interface representing the update data for a radio button group form component.
43
+ */
44
+ interface JsonRadioButtonGroupUpdate extends JsonComponentUpdate {
45
+ type: "radio_button_group";
46
+ selected: string;
47
+ }
48
+ /**
49
+ * Interface representing the update data for a combo box form component.
50
+ */
51
+ interface JsonComboBoxUpdate extends JsonComponentUpdate {
52
+ type: "combo_box";
53
+ selected: string[];
54
+ }
55
+ /**
56
+ * Interface representing the update data for a text editor form component.
57
+ */
58
+ interface JsonTextEditorUpdate extends JsonComponentUpdate {
59
+ type: "text_editor";
60
+ text: string;
61
+ }
62
+ /**
63
+ * Union type for all possible types of form component updates.
64
+ */
65
+ type JsonUpdateMixed = JsonCheckBoxUpdate | JsonRadioButtonGroupUpdate | JsonRadioButtonUpdate | JsonComboBoxUpdate | JsonTextEditorUpdate;
66
+
67
+ /**
68
+ * Abstract class representing a ComponentConverter for an IComponent type.
69
+ */
70
+ declare abstract class ComponentConverter<T extends pdfDoc.IComponent> {
71
+ /**
72
+ * The constructor function for the associated IComponent type.
73
+ */
74
+ type: pdfDoc.IComponentConstructor<T>;
75
+ /**
76
+ * The string value of the FormComponentType for the associated IComponent type.
77
+ */
78
+ abstract typeJSON: string;
79
+ constructor(type: pdfDoc.IComponentConstructor<T>);
80
+ /**
81
+ * Exports a component to a JSON object.
82
+ * @param component - The component to export.
83
+ * @returns The JSON object representing the component.
84
+ */
85
+ abstract export(component: T): JsonComponent;
86
+ /**
87
+ * Sets the value of a component based on update data.
88
+ * @param component - The component to update.
89
+ * @param data - The update data for the component.
90
+ */
91
+ abstract setValue(component: T, data: JsonComponentUpdate): void;
92
+ }
93
+
94
+ /**
95
+ * The ComponentConverterFactory is a factory that creates ComponentConverter instances for IComponent types.
96
+ * It contains a map of ComponentConverter instances, keyed by their associated IComponent type.
97
+ *
98
+ * @remarks
99
+ * To create a new ComponentConverter instance, call the register method with a new instance of the converter.
100
+ */
101
+ declare class ComponentConverterFactory {
102
+ /**
103
+ * A map of ComponentConverter instances, keyed by their associated IComponent type.
104
+ */
105
+ protected items: Map<IComponentConstructor<pdfDoc.IComponent>, ComponentConverter<pdfDoc.IComponent>>;
106
+ constructor(...converters: ComponentConverter<pdfDoc.IComponent>[]);
107
+ /**
108
+ * Registers a new ComponentConverter instance for a given IComponent type.
109
+ *
110
+ * @param converter - The ComponentConverter instance to register.
111
+ */
112
+ register(converter: ComponentConverter<pdfDoc.IComponent>): void;
113
+ /**
114
+ * Finds a ComponentConverter instance for a given IComponent type.
115
+ *
116
+ * @typeparam T - The IComponent type to find a converter for.
117
+ * @param type - The constructor function for the IComponent type to find a converter for.
118
+ * @returns The ComponentConverter instance for the given IComponent type, or null if not found.
119
+ */
120
+ find<T extends pdfDoc.IComponent>(type: IComponentConstructor<T>): ComponentConverter<T> | null;
121
+ /**
122
+ * Gets a ComponentConverter instance for a given IComponent type.
123
+ *
124
+ * @typeparam T - The IComponent type to get a converter for.
125
+ * @param type - The constructor function for the IComponent type to get a converter for.
126
+ * @returns The ComponentConverter instance for the given IComponent type.
127
+ * @throws If no converter is found for the given IComponent type.
128
+ */
129
+ get<T extends pdfDoc.IComponent>(type: IComponentConstructor<T>): ComponentConverter<T>;
130
+ }
131
+
132
+ /**
133
+ * Abstract class representing a ComponentConverter for a FormComponentGroup type.
134
+ * @typeparam T - The FormComponentGroup type to convert.
135
+ */
136
+ declare abstract class FieldConverter<T extends pdfDoc.FormComponentGroup> extends ComponentConverter<T> {
137
+ export(component: T): JsonComponent;
138
+ /**
139
+ *
140
+ * @param component Called to add additional data to the JSON object representing the form component.
141
+ * @param component - The form component to export.
142
+ * @param json - The JSON object representing the form component.
143
+ */
144
+ protected abstract onExport(component: T, json: Record<string, unknown>): void;
145
+ }
146
+
147
+ /**
148
+ * Abstract class representing a ComponentConverter for a FormComponent type.
149
+ * @typeparam T - The FormComponent type to convert.
150
+ */
151
+ declare abstract class WidgetConverter<T extends pdfDoc.FormComponent> extends ComponentConverter<T> {
152
+ export(component: T): JsonComponent;
153
+ /**
154
+ * Called to add additional data to the JSON object representing the form component.
155
+ * @param component - The form component to export.
156
+ * @param json - The JSON object representing the form component.
157
+ */
158
+ protected abstract onExport(component: T, json: Record<string, unknown>): void;
159
+ }
160
+
161
+ /**
162
+ * A ComponentConverter for CheckBox form components.
163
+ */
164
+ declare class CheckBoxConverter extends WidgetConverter<pdfDoc.CheckBox> {
165
+ typeJSON: pdfDoc.FormComponentType.checkBox;
166
+ constructor();
167
+ protected onExport(component: pdfDoc.CheckBox, json: Record<string, unknown>): void;
168
+ setValue(component: pdfDoc.CheckBox, data: JsonCheckBoxUpdate): void;
169
+ }
170
+
171
+ /**
172
+ * A ComponentConverter for ComboBox form components.
173
+ */
174
+ declare class ComboBoxConverter extends WidgetConverter<pdfDoc.ComboBox> {
175
+ typeJSON: pdfDoc.FormComponentType.comboBox;
176
+ constructor();
177
+ protected onExport(component: pdfDoc.ComboBox, json: Record<string, unknown>): void;
178
+ setValue(component: pdfDoc.ComboBox, data: JsonComboBoxUpdate): void;
179
+ }
180
+
181
+ /**
182
+ * A ComponentConverter for RadioButton form components.
183
+ */
184
+ declare class RadioButtonConverter extends WidgetConverter<pdfDoc.RadioButton> {
185
+ typeJSON: pdfDoc.FormComponentType.radioButton;
186
+ constructor();
187
+ protected onExport(component: pdfDoc.RadioButton, json: Record<string, unknown>): void;
188
+ setValue(component: pdfDoc.RadioButton, data: JsonRadioButtonUpdate): void;
189
+ }
190
+
191
+ /**
192
+ * A ComponentConverter for RadioButtonGroup form components.
193
+ */
194
+ declare class RadioButtonGroupConverter extends FieldConverter<pdfDoc.RadioButtonGroup> {
195
+ typeJSON: pdfDoc.FormComponentType.radioButtonGroup;
196
+ constructor();
197
+ protected onExport(component: pdfDoc.RadioButtonGroup, json: Record<string, unknown>): void;
198
+ setValue(component: pdfDoc.RadioButtonGroup, data: JsonRadioButtonGroupUpdate): void;
199
+ }
200
+
201
+ /**
202
+ * A ComponentConverter for TextEditor form components.
203
+ */
204
+ declare class TextEditorConverter extends WidgetConverter<pdfDoc.TextEditor> {
205
+ typeJSON: pdfDoc.FormComponentType.textEditor;
206
+ constructor();
207
+ protected onExport(component: pdfDoc.TextEditor, json: Record<string, unknown>): void;
208
+ setValue(component: pdfDoc.TextEditor, data: JsonTextEditorUpdate): void;
209
+ }
210
+
211
+ /**
212
+ * A utility class for exporting and updating PDF form components.
213
+ */
214
+ declare class FormConverter {
215
+ /**
216
+ * The ComponentConverterFactory instance to use for converting PDF form components to JSON.
217
+ */
218
+ registry: ComponentConverterFactory;
219
+ /**
220
+ * Creates a FormConverter instance.
221
+ * @param registry - The ComponentConverterFactory instance to use for converting PDF form components to JSON.
222
+ */
223
+ constructor(registry: ComponentConverterFactory);
224
+ /**
225
+ * Exports all PDF form components in a document to JSON format.
226
+ * @param doc - The PDF document to export the form components from.
227
+ * @returns An object containing JSON representations of all form components in the document.
228
+ */
229
+ export(doc: pdfDoc.PDFDocument): JsonForm;
230
+ /**
231
+ * Exports a single PDF form component to JSON format.
232
+ * @param component - The PDF form component to export.
233
+ * @returns The JSON representation of the specified form component,
234
+ * or null if the component is not supported by the current converter registry.
235
+ */
236
+ protected exportComponent(component: pdfDoc.IComponent): JsonComponent | null;
237
+ /**
238
+ * Sets the value of one or more PDF form components based on provided JSON updates.
239
+ * @param doc - The PDF document containing the form components to update.
240
+ * @param data - An array of JSON updates to apply to the PDF form components.
241
+ */
242
+ setValue(doc: pdfDoc.PDFDocument, data: JsonUpdateMixed[]): void;
243
+ }
244
+
245
+ /**
246
+ * A global instance of FormConverter that uses the ComponentConverter registry.
247
+ */
248
+ declare const globalFormConverter: FormConverter;
249
+
250
+ export { CheckBoxConverter, ComboBoxConverter, ComponentConverter, ComponentConverterFactory, FieldConverter, FormConverter, type IComponentConstructor, type JsonCheckBoxUpdate, type JsonComboBoxUpdate, type JsonComponent, type JsonComponentUpdate, type JsonForm, type JsonRadioButtonGroupUpdate, type JsonRadioButtonUpdate, type JsonTextEditorUpdate, type JsonUpdateMixed, RadioButtonConverter, RadioButtonGroupConverter, TextEditorConverter, WidgetConverter, globalFormConverter };
@@ -0,0 +1,203 @@
1
+ import * as pdfDoc from '@peculiar/pdf-doc';
2
+
3
+ class ComponentConverter {
4
+ constructor(type) {
5
+ this.type = type;
6
+ }
7
+ }
8
+
9
+ class ComponentConverterFactory {
10
+ constructor(...converters) {
11
+ this.items = new Map();
12
+ for (const converter of converters) {
13
+ this.register(converter);
14
+ }
15
+ }
16
+ register(converter) {
17
+ this.items.set(converter.type, converter);
18
+ }
19
+ find(type) {
20
+ const converter = this.items.get(type);
21
+ return converter || null;
22
+ }
23
+ get(type) {
24
+ const converter = this.find(type);
25
+ if (!converter) {
26
+ throw new Error(`Converter not found for type ${type.name}`);
27
+ }
28
+ return converter;
29
+ }
30
+ }
31
+
32
+ class FieldConverter extends ComponentConverter {
33
+ export(component) {
34
+ const json = {
35
+ type: this.typeJSON,
36
+ id: component.id,
37
+ name: component.name
38
+ };
39
+ this.onExport(component, json);
40
+ return json;
41
+ }
42
+ }
43
+
44
+ class WidgetConverter extends ComponentConverter {
45
+ export(component) {
46
+ const json = {
47
+ type: this.typeJSON,
48
+ id: component.id,
49
+ name: component.name,
50
+ position: {
51
+ left: component.left,
52
+ top: component.top,
53
+ height: component.height,
54
+ width: component.width
55
+ },
56
+ flags: {
57
+ hidden: component.hidden,
58
+ invisible: component.invisible,
59
+ locked: component.locked,
60
+ lockedContents: component.lockedContents,
61
+ noExport: component.noExport,
62
+ noRotate: component.noRotate,
63
+ noView: component.noView,
64
+ noZoom: component.noZoom,
65
+ print: component.print,
66
+ readOnly: component.readOnly,
67
+ readOnlyAnnot: component.readOnlyAnnot,
68
+ required: component.required,
69
+ toggleNoView: component.toggleNoView
70
+ }
71
+ };
72
+ this.onExport(component, json);
73
+ return json;
74
+ }
75
+ }
76
+
77
+ class CheckBoxConverter extends WidgetConverter {
78
+ constructor() {
79
+ super(pdfDoc.CheckBox);
80
+ this.typeJSON = pdfDoc.FormComponentType.checkBox;
81
+ }
82
+ onExport(component, json) {
83
+ json.checked = component.checked;
84
+ json.value = component.value;
85
+ }
86
+ setValue(component, data) {
87
+ component.checked = data.checked;
88
+ }
89
+ }
90
+
91
+ class ComboBoxConverter extends WidgetConverter {
92
+ constructor() {
93
+ super(pdfDoc.ComboBox);
94
+ this.typeJSON = pdfDoc.FormComponentType.comboBox;
95
+ }
96
+ onExport(component, json) {
97
+ let flags = {};
98
+ if (json.flags && typeof json.flags === "object") {
99
+ flags = json.flags;
100
+ }
101
+ json.flags = {
102
+ ...flags,
103
+ combo: component.combo,
104
+ edit: component.edit,
105
+ sort: component.sort,
106
+ multiSelect: component.multiSelect,
107
+ doNotSpellCheck: component.doNotSpellCheck,
108
+ commitOnSelChange: component.commitOnSelChange
109
+ };
110
+ json.options = component.options;
111
+ json.selected = component.selected;
112
+ }
113
+ setValue(component, data) {
114
+ component.selected = data.selected;
115
+ }
116
+ }
117
+
118
+ class RadioButtonConverter extends WidgetConverter {
119
+ constructor() {
120
+ super(pdfDoc.RadioButton);
121
+ this.typeJSON = pdfDoc.FormComponentType.radioButton;
122
+ }
123
+ onExport(component, json) {
124
+ json.checked = component.checked;
125
+ json.value = component.value;
126
+ }
127
+ setValue(component, data) {
128
+ component.checked = data.checked;
129
+ }
130
+ }
131
+
132
+ class RadioButtonGroupConverter extends FieldConverter {
133
+ constructor() {
134
+ super(pdfDoc.RadioButtonGroup);
135
+ this.typeJSON = pdfDoc.FormComponentType.radioButtonGroup;
136
+ }
137
+ onExport(component, json) {
138
+ json.selected = component.selected;
139
+ }
140
+ setValue(component, data) {
141
+ const button = component.get(data.selected);
142
+ button.checked = true;
143
+ }
144
+ }
145
+
146
+ class TextEditorConverter extends WidgetConverter {
147
+ constructor() {
148
+ super(pdfDoc.TextEditor);
149
+ this.typeJSON = pdfDoc.FormComponentType.textEditor;
150
+ }
151
+ onExport(component, json) {
152
+ json.maxLen = component.maxLen;
153
+ json.multiline = component.multiline;
154
+ json.text = component.text;
155
+ }
156
+ setValue(component, data) {
157
+ component.text = data.text;
158
+ }
159
+ }
160
+
161
+ class FormConverter {
162
+ constructor(registry) {
163
+ this.registry = registry;
164
+ }
165
+ export(doc) {
166
+ const res = {
167
+ form: {}
168
+ };
169
+ const components = doc.getComponents();
170
+ for (const component of components) {
171
+ const obj = this.exportComponent(component);
172
+ if (obj) {
173
+ res.form[obj.name] = obj;
174
+ }
175
+ }
176
+ return res;
177
+ }
178
+ exportComponent(component) {
179
+ const converter = this.registry.find(component.constructor);
180
+ if (converter) {
181
+ return converter.export(component);
182
+ }
183
+ return null;
184
+ }
185
+ setValue(doc, data) {
186
+ for (const item of data) {
187
+ const component = doc.getComponentByName(item.name);
188
+ if (!component) {
189
+ continue;
190
+ }
191
+ const converter = this.registry.find(component.constructor);
192
+ if (!converter || converter.typeJSON !== item.type) {
193
+ continue;
194
+ }
195
+ converter.setValue(component, item);
196
+ }
197
+ }
198
+ }
199
+
200
+ const registry = new ComponentConverterFactory(new CheckBoxConverter(), new RadioButtonConverter(), new TextEditorConverter(), new ComboBoxConverter(), new ComboBoxConverter(), new RadioButtonGroupConverter());
201
+ const globalFormConverter = new FormConverter(registry);
202
+
203
+ export { CheckBoxConverter, ComboBoxConverter, ComponentConverter, ComponentConverterFactory, FieldConverter, FormConverter, RadioButtonConverter, RadioButtonGroupConverter, TextEditorConverter, WidgetConverter, globalFormConverter };