InteractiveForms

Sample JavaScript code for using Apryse SDK with interactive forms (also known as AcroForms). Capabilities include programatically creating new fields and widget annotations, form filling, modifying existing field values, form templating, and flattening form fields. Learn more about our Web SDK and PDF Data Extraction SDK Capabilities.

1//---------------------------------------------------------------------------------------
2// Copyright (c) 2001-2023 by Apryse Software Inc. All Rights Reserved.
3// Consult legal.txt regarding legal and license information.
4//---------------------------------------------------------------------------------------
5(exports => {
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21 exports.runInteractiveFormsTest = () => {
22 const PDFNet = exports.Core.PDFNet;
23
24 PDFNet.CheckStyle = {
25 e_check: 0,
26 e_circle: 1,
27 e_cross: 2,
28 e_diamond: 3,
29 e_square: 4,
30 e_star: 5,
31 };
32
33 const RenameAllFields = async (doc, name) => {
34 let itr = await doc.getFieldIterator(name);
35 for (let counter = 0; await itr.hasNext(); itr = await doc.getFieldIterator(name), ++counter) {
36 const f = await itr.current();
37 f.rename(name + counter);
38 }
39 };
40
41 // Note: The visual appearance of check-marks and radio-buttons in PDF documents is
42 // not limited to CheckStyle-s. It is possible to create a visual appearance using
43 // arbitrary glyph, text, raster image, or path object. Although most PDF producers
44 // limit the options to the above 'standard' styles, using PDFNetJS you can generate
45 // arbitrary appearances.
46 const CreateCheckmarkAppearance = async (doc, style) => {
47 const builder = await PDFNet.ElementBuilder.create();
48 const writer = await PDFNet.ElementWriter.create();
49 writer.begin(doc);
50 writer.writeElement(await builder.createTextBegin());
51
52 let symbol;
53 switch (style) {
54 case PDFNet.CheckStyle.e_circle:
55 symbol = '\x6C';
56 break;
57 case PDFNet.CheckStyle.e_diamond:
58 symbol = '\x75';
59 break;
60 case PDFNet.CheckStyle.e_cross:
61 symbol = '\x35';
62 break;
63 case PDFNet.CheckStyle.e_square:
64 symbol = '\x6E';
65 break;
66 case PDFNet.CheckStyle.e_star:
67 symbol = '\x48';
68 break;
69 // ...
70 // See section D.4 "ZapfDingbats Set and Encoding" in PDF Reference Manual
71 // (http://www.pdftron.com/downloads/PDFReference16.pdf) for the complete
72 // graphical map for ZapfDingbats font. Please note that all character codes
73 // are represented using the 'octal' notation.
74 default:
75 // e_check
76 symbol = '\x34';
77 }
78
79 const zapfDingbatsFont = await PDFNet.Font.create(doc, PDFNet.Font.StandardType1Font.e_zapf_dingbats);
80 const checkmark = await builder.createTextRun(symbol, zapfDingbatsFont, 12);
81 writer.writeElement(checkmark);
82 writer.writeElement(await builder.createTextEnd());
83
84 const stm = await writer.end();
85 await stm.putRect('BBox', -0.2, -0.2, 1, 1); // Clip
86 await stm.putName('Subtype', 'Form');
87 return stm;
88 };
89
90 const CreateButtonAppearance = async (doc, buttonDown) => {
91 // Create a button appearance stream ------------------------------------
92
93 const builder = await PDFNet.ElementBuilder.create();
94 const writer = await PDFNet.ElementWriter.create();
95 writer.begin(doc);
96
97 // Draw background
98 let element = await builder.createRect(0, 0, 101, 37);
99 element.setPathFill(true);
100 element.setPathStroke(false);
101
102 let elementGState = await element.getGState();
103 elementGState.setFillColorSpace(await PDFNet.ColorSpace.createDeviceGray());
104 elementGState.setFillColorWithColorPt(await PDFNet.ColorPt.init(0.75));
105 writer.writeElement(element);
106
107 // Draw 'Submit' text
108 writer.writeElement(await builder.createTextBegin());
109
110 const text = 'Submit';
111 const HelveticaBoldFont = await PDFNet.Font.create(doc, PDFNet.Font.StandardType1Font.e_helvetica_bold);
112 element = await builder.createTextRun(text, HelveticaBoldFont, 12);
113 elementGState = await element.getGState();
114 elementGState.setFillColorWithColorPt(await PDFNet.ColorPt.init(0));
115
116 if (buttonDown) {
117 element.setTextMatrixEntries(1, 0, 0, 1, 33, 10);
118 } else {
119 element.setTextMatrixEntries(1, 0, 0, 1, 30, 13);
120 }
121 writer.writeElement(element);
122
123 writer.writeElement(await builder.createTextEnd());
124
125 const stm = await writer.end();
126
127 // Set the bounding box
128 await stm.putRect('BBox', 0, 0, 101, 37);
129 await stm.putName('Subtype', 'Form');
130 return stm;
131 };
132
133 const main = async () => {
134 let docBuffer = null;
135
136 try {
137 console.log('Beginning Test 1');
138
139 // Relative path to the folder containing test files.
140 // eslint-disable-next-line @typescript-eslint/no-unused-vars
141 const inputPath = '../TestFiles/';
142
143 const doc = await PDFNet.PDFDoc.create();
144 doc.initSecurityHandler();
145 doc.lock();
146 console.log('PDF document initialized and locked');
147
148 const blankPage = await doc.pageCreate();
149
150 // create new fields
151 const empFirstName = await doc.fieldCreateFromStrings('employee.name.first', PDFNet.Field.Type.e_text, 'John', '');
152 const empLastName = await doc.fieldCreateFromStrings('employee.name.last', PDFNet.Field.Type.e_text, 'Doe', '');
153 const empLastCheck1 = await doc.fieldCreateFromStrings('employee.name.check1', PDFNet.Field.Type.e_check, 'Yes', '');
154
155 const submit = await doc.fieldCreate('submit', PDFNet.Field.Type.e_button);
156
157 // Create page annotations for the above fields.
158
159 // Create text annotation
160 const annot1 = await PDFNet.WidgetAnnot.create(doc, await PDFNet.Rect.init(50, 550, 350, 600), empFirstName);
161 const annot2 = await PDFNet.WidgetAnnot.create(doc, await PDFNet.Rect.init(50, 450, 350, 500), empLastName);
162
163 // create checkbox annotation
164 const annot3 = await PDFNet.WidgetAnnot.create(doc, await PDFNet.Rect.init(64, 356, 120, 410), empLastCheck1);
165 // Set the annotation appearance for the "Yes" state
166 // NOTE: if we call refreshFieldAppearances after this the appearance will be discarded
167 const checkMarkApp = await CreateCheckmarkAppearance(doc, PDFNet.CheckStyle.e_check);
168 // Set the annotation appearance for the "Yes" state...
169 annot3.setAppearance(checkMarkApp, PDFNet.Annot.State.e_normal, 'Yes');
170
171 // Create button annotation
172 const annot4 = await PDFNet.WidgetAnnot.create(doc, await PDFNet.Rect.init(64, 284, 163, 320), submit);
173 // Set the annotation appearances for the down and up state...
174 const falseButtonApp = await CreateButtonAppearance(doc, false);
175 const trueButtonApp = await CreateButtonAppearance(doc, true);
176 await annot4.setAppearance(falseButtonApp, PDFNet.Annot.State.e_normal);
177 await annot4.setAppearance(trueButtonApp, PDFNet.Annot.State.e_down);
178
179 // Create 'SubmitForm' action. The action will be linked to the button.
180 const url = await PDFNet.FileSpec.createURL(doc, 'http://www.apryse.com');
181 const buttonAction = await PDFNet.Action.createSubmitForm(url);
182
183 // Associate the above action with 'Down' event in annotations action dictionary.
184 const annotAction = await (await annot4.getSDFObj()).putDict('AA');
185 annotAction.put('D', await buttonAction.getSDFObj());
186
187 blankPage.annotPushBack(annot1); // Add annotations to the page
188 blankPage.annotPushBack(annot2);
189 blankPage.annotPushBack(annot3);
190 blankPage.annotPushBack(annot4);
191
192 doc.pagePushBack(blankPage); // Add the page as the last page in the document.
193
194 // If you are not satisfied with the look of default auto-generated appearance
195 // streams you can delete "AP" entry from the Widget annotation and set
196 // "NeedAppearances" flag in AcroForm dictionary:
197 // doc.GetAcroForm().PutBool("NeedAppearances", true);
198 // This will force the viewer application to auto-generate new appearance streams
199 // every time the document is opened.
200 //
201 // Alternatively you can generate custom annotation appearance using ElementWriter
202 // and then set the "AP" entry in the widget dictionary to the new appearance
203 // stream.
204 //
205 // Yet another option is to pre-populate field entries with dummy text. When
206 // you edit the field values using PDFNet the new field appearances will match
207 // the old ones.
208
209 // doc.GetAcroForm().PutBool("NeedAppearances", true);
210 // NOTE: refreshFieldAppearances will replace previously generated appearance streams
211 doc.refreshFieldAppearances();
212
213 docBuffer = await doc.saveMemoryBuffer(0);
214 saveBufferAsPDFDoc(docBuffer, 'forms_test1.pdf');
215
216 console.log('Example 1 complete and everything deallocated.');
217 } catch (err) {
218 console.log(err.stack);
219 }
220 //----------------------------------------------------------------------------------
221 // Example 2:
222 // Fill-in forms / Modify values of existing fields.
223 // Traverse all form fields in the document (and print out their names).
224 // Search for specific fields in the document.
225 //----------------------------------------------------------------------------------
226
227 try {
228 console.log('Beginning Test 2');
229
230 // we use the forms test doc from the previous sample
231 // Buffers passed into PDFNetJS functions are made invalid afterwards due to the functions taking ownership.
232 // If you are using the same buffer to initialize multiple documents, pass in a copy of the buffer.
233 const copyOfBuffer = new Uint8Array(docBuffer.buffer.slice(0));
234 const doc2 = await PDFNet.PDFDoc.createFromBuffer(copyOfBuffer);
235
236 doc2.initSecurityHandler();
237 doc2.lock();
238 console.log('Sample 2 PDF document initialized and locked');
239 const itr = await doc2.getFieldIteratorBegin();
240
241 for (; await itr.hasNext(); itr.next()) {
242 const currentItr = await itr.current();
243 console.log('Field name: ' + (await currentItr.getName()));
244 console.log('Field partial name: ' + (await currentItr.getPartialName()));
245
246 console.log('Field type: ');
247 const type = await currentItr.getType();
248 const strVal = await currentItr.getValueAsString();
249
250 switch (type) {
251 case PDFNet.Field.Type.e_button: {
252 console.log('Button');
253 break;
254 }
255 case PDFNet.Field.Type.e_radio: {
256 console.log('Radio button: Value = ' + strVal);
257 break;
258 }
259 case PDFNet.Field.Type.e_check: {
260 const currItr = await itr.current();
261 currItr.setValueAsBool(true);
262 console.log('Check box: Value = ' + strVal);
263 break;
264 }
265 case PDFNet.Field.Type.e_text: {
266 console.log('Text');
267 // Edit all variable text in the document
268 const currItr = await itr.current();
269 currItr.setValueAsString('This is a new value. The old one was: ' + strVal);
270 break;
271 }
272 case PDFNet.Field.Type.e_choice: {
273 console.log('Choice');
274 break;
275 }
276 case PDFNet.Field.Type.e_signature: {
277 console.log('Signature');
278 break;
279 }
280 }
281 console.log('-----------------------');
282 }
283 const f = await doc2.getField('employee.name.first');
284 if (f) {
285 console.log('Field search for ' + (await f.getName()) + ' was successful');
286 } else {
287 console.log('Field search failed');
288 }
289 // Regenerate field appearances.
290 doc2.refreshFieldAppearances();
291
292 const docBuffer2 = await doc2.saveMemoryBuffer(0);
293 saveBufferAsPDFDoc(docBuffer2, 'forms_test_edit.pdf');
294 console.log('Example 2 complete and everything deallocated.');
295 } catch (err) {
296 console.log(err);
297 }
298 //----------------------------------------------------------------------------------
299 // Sample 3: Form templating
300 // Replicate pages and form data within a document. Then rename field names to make
301 // them unique.
302 //----------------------------------------------------------------------------------
303 try {
304 // we still keep using our original forms test doc.
305 // If you are using the same buffer to initialize multiple documents, pass in a copy of the buffer.
306 const copyOfBuffer3 = new Uint8Array(docBuffer.buffer.slice(0));
307 const doc3 = await PDFNet.PDFDoc.createFromBuffer(copyOfBuffer3);
308 doc3.initSecurityHandler();
309 doc3.lock();
310 console.log('Sample 3 PDF document initialized and locked');
311 const srcPage = await doc3.getPage(1);
312 doc3.pagePushBack(srcPage); // Append several copies of the first page
313 doc3.pagePushBack(srcPage); // Note that forms are successfully copied
314 doc3.pagePushBack(srcPage);
315 doc3.pagePushBack(srcPage);
316
317 // Now we rename fields in order to make every field unique.
318 // You can use this technique for dynamic template filling where you have a 'master'
319 // form page that should be replicated, but with unique field names on every page.
320 await RenameAllFields(doc3, 'employee.name.first');
321 await RenameAllFields(doc3, 'employee.name.last');
322 await RenameAllFields(doc3, 'employee.name.check1');
323 await RenameAllFields(doc3, 'submit');
324
325 const docBuffer3 = await doc3.saveMemoryBuffer(0);
326 saveBufferAsPDFDoc(docBuffer3, 'forms_test1_cloned.pdf');
327 console.log('Example 3 complete and everything deallocated.');
328 } catch (err) {
329 console.log(err);
330 }
331
332 //----------------------------------------------------------------------------------
333 // Sample:
334 // Flatten all form fields in a document.
335 // Note that this sample is intended to show that it is possible to flatten
336 // individual fields. PDFNet provides a utility function PDFDoc.FlattenAnnotations()
337 // that will automatically flatten all fields.
338 //----------------------------------------------------------------------------------
339
340 try {
341 const copyOfBuffer4 = new Uint8Array(docBuffer.buffer.slice(0));
342 const doc4 = await PDFNet.PDFDoc.createFromBuffer(copyOfBuffer4);
343 doc4.initSecurityHandler();
344 doc4.lock();
345 console.log('Sample 4 PDF document initialized and locked');
346
347 // Flatten all pages
348 // eslint-disable-next-line no-constant-condition
349 if (true) {
350 doc4.flattenAnnotations();
351 } else {
352 // Manual flattening
353 for (let pitr = await doc4.getPageIterator(); await pitr.hasNext(); await pitr.next()) {
354 const page = await pitr.current();
355 const annots = await page.getAnnots();
356
357 if (annots) {
358 // Look for all widget annotations (in reverse order)
359 for (let i = parseInt(await annots.size(), 10) - 1; i >= 0; --i) {
360 const annotObj = await annots.getAt(i);
361 const annotObjSubtype = await annotObj.get('Subtype');
362 // eslint-disable-next-line @typescript-eslint/no-unused-vars
363 const annotObjVal = await annotObjSubtype.value();
364 const annotObjName = await (await (await annotObj.get('Subtype')).value()).getName();
365
366 if (annotObjName === 'Widget') {
367 const field = await PDFNet.Field.create(annotObj);
368 field.flatten(page);
369
370 // Another way of making a read only field is by modifying
371 // field's e_read_only flag:
372 // field.SetFlag(Field::e_read_only, true);
373 }
374 }
375 }
376 }
377 }
378
379 const docBuffer4 = await doc4.saveMemoryBuffer(0);
380 saveBufferAsPDFDoc(docBuffer4, 'forms_test1_flattened.pdf');
381 console.log('done - Example 4 complete and everything deallocated.');
382 } catch (err) {
383 console.log(err);
384 }
385 };
386
387 // add your own license key as the second parameter, e.g. PDFNet.runWithCleanup(main, 'YOUR_LICENSE_KEY')
388 PDFNet.runWithCleanup(main);
389 };
390})(window);
391// eslint-disable-next-line spaced-comment
392//# sourceURL=InteractiveFormsTest.js

Did you find this helpful?

Trial setup questions?

Ask experts on Discord

Need other help?

Contact Support

Pricing or product questions?

Contact Sales