InteractiveForms

Sample Obj-C 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 iOS SDK and PDF Data Extraction SDK Capabilities.

1//---------------------------------------------------------------------------------------
2// Copyright (c) 2001-2024 by Apryse Software Inc. All Rights Reserved.
3// Consult legal.txt regarding legal and license information.
4//---------------------------------------------------------------------------------------
5
6#import <OBJC/PDFNetOBJC.h>
7#import <Foundation/Foundation.h>
8#import <Foundation/NSDictionary.h>
9#import <Foundation/NSArray.h>
10
11//---------------------------------------------------------------------------------------
12// This sample illustrates basic PDFNet capabilities related to interactive
13// forms (also known as AcroForms).
14//---------------------------------------------------------------------------------------
15
16// field_nums has to be greater than 0.
17void RenameAllFields(PTPDFDoc* doc, NSString* name, int field_nums)
18{
19 PTFieldIterator * itr = [doc GetFieldIteratorWithName: name];
20 int counter;
21 for (counter=1; [itr HasNext]; itr=[doc GetFieldIteratorWithName: name], ++counter)
22 {
23 PTField *f = [itr Current];
24 int update_count = (int)(ceil(counter/(double)field_nums));
25 [f Rename: [name stringByAppendingFormat: @"-%d", update_count]];
26 }
27}
28
29PTObj* CreateCustomButtonAppearance(PTPDFDoc* doc, bool button_down)
30{
31 // Create a button appearance stream ------------------------------------
32 PTElementBuilder *build = [[PTElementBuilder alloc] init];
33 PTElementWriter *writer = [[PTElementWriter alloc] init];
34 [writer WriterBeginWithSDFDoc: [doc GetSDFDoc] compress: YES];
35
36 // Draw background
37 PTElement *element = [build CreateRect: 0 y: 0 width: 101 height: 137];
38 [element SetPathFill: YES];
39 [element SetPathStroke: NO];
40 [[element GetGState] SetFillColorSpace: [PTColorSpace CreateDeviceGray]];
41 [[element GetGState] SetFillColorWithColorPt: [[PTColorPt alloc] initWithX: 0.75 y: 0 z: 0 w: 0]];
42 [writer WriteElement: element];
43
44 // Draw 'Submit' text
45 [writer WriteElement: [build CreateTextBegin]];
46 {
47 NSString *text = @"Submit";
48 element = [build CreateTextRunWithFont: text font: [PTFont Create: [doc GetSDFDoc] type: e_pthelvetica_bold embed: NO] font_sz: 12];
49 [[element GetGState] SetFillColorWithColorPt: [[PTColorPt alloc] initWithX: 0 y: 0 z: 0 w: 0]];
50
51 if (button_down)
52 [element SetTextMatrixWithMatrix2D: [[PTMatrix2D alloc] initWithA: 1 b: 0 c: 0 d: 1 h: 33 v: 10]];
53 else
54 [element SetTextMatrixWithMatrix2D: [[PTMatrix2D alloc] initWithA: 1 b: 0 c: 0 d: 1 h: 30 v: 13]];
55 [writer WriteElement: element];
56 }
57 [writer WriteElement: [build CreateTextEnd]];
58
59 PTObj * stm = [writer End];
60
61 // Set the bounding box
62 [stm PutRect: @"BBox" x1: 0 y1: 0 x2: 101 y2: 37];
63 [stm PutName: @"Subtype" name: @"Form"];
64 return stm;
65}
66
67int main(int argc, char *argv[])
68{
69 @autoreleasepool {
70 int ret = 0;
71 [PTPDFNet Initialize: 0];
72
73 // The vector used to store the name and count of all fields.
74 // This is used later on to clone the fields
75 NSMutableDictionary *field_names = [[NSMutableDictionary alloc] init];
76
77 //----------------------------------------------------------------------------------
78 // Example 1: Programatically create new Form Fields and Widget Annotations.
79 //----------------------------------------------------------------------------------
80 @try
81 {
82 PTPDFDoc *doc = [[PTPDFDoc alloc] init];
83 // Create a blank new page and add some form fields.
84 PTPage *blank_page = [doc PageCreate: [[PTPDFRect alloc] initWithX1: 0 y1: 0 x2: 612 y2: 792]];
85
86 // Text Widget Creation
87 // Create an empty text widget with black text.
88 PTTextWidget *text1 = [PTTextWidget Create: doc pos: [[PTPDFRect alloc] initWithX1: 110 y1: 700 x2: 380 y2: 730] field_name: @""];
89 [text1 SetText: @"Basic Text Field"];
90 [text1 RefreshAppearance];
91 [blank_page AnnotPushBack: text1];
92 // Create a vertical text widget with blue text and a yellow background.
93 PTTextWidget *text2 = [PTTextWidget Create: doc pos: [[PTPDFRect alloc] initWithX1: 50 y1: 400 x2: 90 y2: 730] field_name: @""];
94 [text2 SetRotation: 90];
95 // Set the text content.
96 [text2 SetText: @" ****Lucky Stars!****"];
97 // Set the font type, text color, font size, border color and background color.
98 [text2 SetFont: [PTFont Create: [doc GetSDFDoc] type: e_pthelvetica_oblique embed: NO]];
99 [text2 SetFontSize: 28];
100 [text2 SetTextColor: [[PTColorPt alloc] initWithX: 0 y: 0 z: 1 w: 0] col_comp: 3];
101 [text2 SetBorderColor: [[PTColorPt alloc] initWithX: 0 y: 0 z: 0 w: 0] compnum: 3];
102 [text2 SetBackgroundColor: [[PTColorPt alloc] initWithX: 1 y: 1 z: 0 w: 0] compnum: 3];
103 [text2 RefreshAppearance];
104 // Add the annotation to the page.
105 [blank_page AnnotPushBack: text2];
106 // Create two new text widget with Field names employee.name.first and employee.name.last
107 // This logic shows how these widgets can be created using either a field name string or
108 // a Field object
109 PTTextWidget *text3 = [PTTextWidget Create: doc pos: [[PTPDFRect alloc] initWithX1: 110 y1: 660 x2: 380 y2: 690] field_name: @"employee.name.first"];
110 [text3 SetText: @"Levi"];
111 [text3 SetFont: [PTFont Create: [doc GetSDFDoc] type: e_pttimes_bold embed: NO]];
112 [text3 RefreshAppearance];
113 [blank_page AnnotPushBack: text3];
114 PTField *emp_last_name = [doc FieldCreateWithString: @"employee.name.last" type: e_pttext field_value: @"Ackerman" def_field_value: @""];
115 PTTextWidget *text4 = [PTTextWidget CreateWithField: doc pos: [[PTPDFRect alloc] initWithX1: 110 y1: 620 x2: 380 y2: 650] field: emp_last_name];
116 [text4 SetFont: [PTFont Create: [doc GetSDFDoc] type: e_pttimes_bold embed: NO]];
117 [text4 RefreshAppearance];
118 [blank_page AnnotPushBack: text4];
119
120 // Signature Widget Creation (unsigned)
121 PTSignatureWidget *signature1 = [PTSignatureWidget Create: doc pos: [[PTPDFRect alloc] initWithX1: 110 y1: 560 x2: 260 y2: 610] field_name: @""];
122 [signature1 RefreshAppearance];
123 [blank_page AnnotPushBack: signature1];
124
125 // CheckBox Widget Creation
126 // Create a check box widget that is not checked.
127 PTCheckBoxWidget *check1 = [PTCheckBoxWidget Create: doc pos: [[PTPDFRect alloc] initWithX1: 140 y1: 490 x2: 170 y2: 520] field_name: @""];
128 [check1 RefreshAppearance];
129 [blank_page AnnotPushBack: check1];
130 // Create a check box widget that is checked.
131 PTCheckBoxWidget *check2 = [PTCheckBoxWidget Create: doc pos: [[PTPDFRect alloc] initWithX1: 190 y1: 490 x2: 250 y2: 540] field_name: @"employee.name.check1"];
132 [check2 SetBackgroundColor: [[PTColorPt alloc] initWithX: 1 y: 1 z: 1 w: 0] compnum: 3];
133 [check2 SetBorderColor: [[PTColorPt alloc] initWithX: 0 y: 0 z: 0 w: 0] compnum: 3];
134 // Check the widget (by default it is unchecked).
135 [check2 SetChecked: true];
136 [check2 RefreshAppearance];
137 [blank_page AnnotPushBack: check2];
138
139 // PushButton Widget Creation
140 PTPushButtonWidget *pushbutton1 = [PTPushButtonWidget Create: doc pos: [[PTPDFRect alloc] initWithX1: 380 y1: 490 x2: 520 y2: 540] field_name: @""];
141 [pushbutton1 SetTextColor: [[PTColorPt alloc] initWithX: 1 y: 1 z: 1 w: 0] col_comp: 3];
142 [pushbutton1 SetFontSize: 36];
143 [pushbutton1 SetBackgroundColor: [[PTColorPt alloc] initWithX: 0 y: 0 z: 0 w: 0] compnum: 3];
144 // Add a caption for the pushbutton.
145 [pushbutton1 SetStaticCaptionText: @"PushButton"];
146 [pushbutton1 RefreshAppearance];
147 [blank_page AnnotPushBack: pushbutton1];
148
149 // ComboBox Widget Creation
150 PTComboBoxWidget *combo1 = [PTComboBoxWidget Create: doc pos: [[PTPDFRect alloc] initWithX1: 280 y1: 560 x2: 580 y2: 610] field_name: @""];
151 // Add options to the combobox widget.
152 [combo1 AddOption: @"Combo Box No.1"];
153 [combo1 AddOption: @"Combo Box No.2"];
154 [combo1 AddOption: @"Combo Box No.3"];
155 // Make one of the options in the combo box selected by default.
156 [combo1 SetSelectedOption: @"Combo Box No.2"];
157 [combo1 SetTextColor: [[PTColorPt alloc] initWithX: 1 y: 0 z: 0 w: 0] col_comp: 3];
158 [combo1 SetFontSize: 28];
159 [combo1 RefreshAppearance];
160 [blank_page AnnotPushBack: combo1];
161
162 // ListBox Widget Creation
163 PTListBoxWidget *list1 = [PTListBoxWidget Create: doc pos: [[PTPDFRect alloc] initWithX1: 400 y1: 620 x2: 580 y2: 730] field_name: @""];
164 // Add one option to the listbox widget.
165 [list1 AddOption: @"List Box No.1"];
166 // Add multiple options to the listbox widget in a batch.
167 NSArray *list_options = [NSArray arrayWithObjects: @"List Box No.2", @"List Box No.3", nil];
168 [list1 AddOptions: list_options];
169 // Select some of the options in list box as default options
170 [list1 SetSelectedOptions: list_options];
171 // Enable list box to have multi-select when editing.
172 [[list1 GetField] SetFlag: e_ptmultiselect value: true];
173 [list1 SetFont: [PTFont Create: [doc GetSDFDoc] type: e_pttimes_italic embed: NO]];
174 [list1 SetTextColor: [[PTColorPt alloc] initWithX: 1 y: 0 z: 0 w: 0] col_comp: 3];
175 [list1 SetFontSize: 28];
176 [list1 SetBackgroundColor: [[PTColorPt alloc] initWithX: 1 y: 1 z: 1 w: 0] compnum: 3];
177 [list1 RefreshAppearance];
178 [blank_page AnnotPushBack: list1];
179
180 // RadioButton Widget Creation
181 // Create a radio button group and add three radio buttons in it.
182 PTRadioButtonGroup *radio_group = [PTRadioButtonGroup Create: doc field_name: @"RadioGroup"];
183 PTRadioButtonWidget *radiobutton1 = [radio_group Add: [[PTPDFRect alloc] initWithX1: 140 y1: 410 x2: 190 y2: 460] onstate: @""];
184 [radiobutton1 SetBackgroundColor: [[PTColorPt alloc] initWithX: 1 y: 1 z: 0 w: 0] compnum: 3];
185 [radiobutton1 RefreshAppearance];
186 PTRadioButtonWidget *radiobutton2 = [radio_group Add: [[PTPDFRect alloc] initWithX1: 310 y1: 410 x2: 360 y2: 460] onstate: @""];
187 [radiobutton2 SetBackgroundColor: [[PTColorPt alloc] initWithX: 0 y: 1 z: 0 w: 0] compnum: 3];
188 [radiobutton2 RefreshAppearance];
189 PTRadioButtonWidget *radiobutton3 = [radio_group Add: [[PTPDFRect alloc] initWithX1: 480 y1: 410 x2: 530 y2: 460] onstate: @""];
190 // Enable the third radio button. By default the first one is selected
191 [radiobutton3 EnableButton];
192 [radiobutton3 SetBackgroundColor: [[PTColorPt alloc] initWithX: 0 y: 1 z: 1 w: 0] compnum: 3];
193 [radiobutton3 RefreshAppearance];
194 [radio_group AddGroupButtonsToPage: blank_page];
195
196 // Custom push button annotation creation
197 PTPushButtonWidget *custom_pushbutton1 = [PTPushButtonWidget Create: doc pos: [[PTPDFRect alloc] initWithX1: 260 y1: 320 x2: 360 y2: 360] field_name: @""];
198 // Set the annotation appearance.
199 [custom_pushbutton1 SetAppearance: CreateCustomButtonAppearance(doc, NO) annot_state: e_ptnormal app_state: nil];
200 // Create 'SubmitForm' action. The action will be linked to the button.
201 PTFileSpec *url = [PTFileSpec CreateURL: [doc GetSDFDoc] url: @"http://www.pdftron.com"];
202 PTAction *button_action = [PTAction CreateSubmitForm: url];
203 // Associate the above action with 'Down' event in annotations action dictionary.
204 PTObj * annot_action = [[custom_pushbutton1 GetSDFObj] PutDict: @"AA"];
205 [annot_action Put: @"D" obj: [button_action GetSDFObj]];
206 [blank_page AnnotPushBack: custom_pushbutton1];
207
208 // Add the page as the last page in the document.
209 [doc PagePushBack: blank_page];
210
211 // If you are not satisfied with the look of default auto-generated appearance
212 // streams you can delete "AP" entry from the Widget annotation and set
213 // "NeedAppearances" flag in AcroForm dictionary:
214 // doc.GetAcroForm().PutBool("NeedAppearances", true);
215 // This will force the viewer application to auto-generate new appearance streams
216 // every time the document is opened.
217 //
218 // Alternatively you can generate custom annotation appearance using ElementWriter
219 // and then set the "AP" entry in the widget dictionary to the new appearance
220 // stream.
221 //
222 // Yet another option is to pre-populate field entries with dummy text. When
223 // you edit the field values using PDFNet the new field appearances will match
224 // the old ones.
225
226 //doc.GetAcroForm().PutBool("NeedAppearances", true);
227 [doc RefreshFieldAppearances];
228
229 [doc SaveToFile: @"../../TestFiles/Output/forms_test1.pdf" flags: 0];
230 printf("Done.\n");
231 }
232 @catch (NSException *e)
233 {
234 NSLog(@"%@", e.reason);
235 ret = 1;
236 }
237
238 //----------------------------------------------------------------------------------
239 // Example 2:
240 // Fill-in forms / Modify values of existing fields.
241 // Traverse all form fields in the document (and print out their names).
242 // Search for specific fields in the document.
243 //----------------------------------------------------------------------------------
244 @try
245 {
246 PTPDFDoc *doc = [[PTPDFDoc alloc] initWithFilepath: @"../../TestFiles/Output/forms_test1.pdf"];
247 [doc InitSecurityHandler];
248
249 PTFieldIterator * itr = [doc GetFieldIterator];
250 for(; [itr HasNext]; [itr Next])
251 {
252 NSString *cur_field_name = [[itr Current] GetName];
253 // Add one to the count for this field name for later processing
254 if ([field_names objectForKey: cur_field_name] != nil)
255 {
256 int same_field_name_count = [[field_names objectForKey: cur_field_name] intValue] + 1;
257 [field_names setObject: [NSNumber numberWithInt: same_field_name_count] forKey: cur_field_name];
258 }
259 else
260 {
261 [field_names setObject: @"1" forKey: cur_field_name];
262 }
263
264 printf("Field name: %s\n", [[[itr Current] GetName] UTF8String]);
265 printf("Field partial name: %s\n", [[[itr Current] GetPartialName] UTF8String]);
266
267 printf("Field type: ");
268 PTFieldType type = [[itr Current] GetType];
269 NSString *str_val = [[itr Current] GetValueAsString];
270
271 switch(type)
272 {
273 case e_ptcheck:
274 [[itr Current] SetValueWithBool: YES];
275 printf("Check box: Value = %s\n", [str_val UTF8String]);
276 break;
277 case e_ptbutton:
278 printf("Button\n");
279 break;
280 case e_ptradio:
281 printf("Radio button: Value = %s\n", [str_val UTF8String]);
282 break;
283 case e_pttext:
284 {
285 printf("Text\n");
286 // Edit all variable text in the document
287 [[itr Current] SetValueWithString: [@"This is a new value. The old one was: " stringByAppendingString: str_val]];
288 }
289 break;
290 case e_ptchoice: printf("Choice\n"); break;
291 case e_ptsignature: printf("Signature\n"); break;
292 default: break;
293 }
294
295 printf("------------------------------\n");
296 }
297
298 // Search for a specific field
299 PTField *f = [doc GetField: @"employee.name.first"];
300 if (f)
301 {
302 printf("Field search for %s was successful\n", [[f GetName] UTF8String]);
303 }
304 else
305 {
306 printf("Field search failed \n");
307 }
308
309 // Regenerate field appearances.
310 [doc RefreshFieldAppearances];
311 [doc SaveToFile: @"../../TestFiles/Output/forms_test_edit.pdf" flags: 0];
312 printf("Done.\n");
313 }
314 @catch(NSException *e)
315 {
316 NSLog(@"%@", e.reason);
317 ret = 1;
318 }
319
320 //----------------------------------------------------------------------------------
321 // Sample: Form templating
322 // Replicate pages and form data within a document. Then rename field names to make
323 // them unique.
324 //----------------------------------------------------------------------------------
325 @try
326 {
327 // Sample: Copying the page with forms within the same document
328 PTPDFDoc *doc = [[PTPDFDoc alloc] initWithFilepath: @"../../TestFiles/Output/forms_test1.pdf"];
329 [doc InitSecurityHandler];
330
331 PTPage *src_page = [doc GetPage: 1];
332 [doc PagePushBack: src_page]; // Append several copies of the first page
333 [doc PagePushBack: src_page]; // Note that forms are successfully copied
334 [doc PagePushBack: src_page];
335 [doc PagePushBack: src_page];
336
337 // Now we rename fields in order to make every field unique.
338 // You can use this technique for dynamic template filling where you have a 'master'
339 // form page that should be replicated, but with unique field names on every page.
340 for (NSString* key in field_names)
341 {
342 RenameAllFields(doc, key, [[field_names valueForKey: key] intValue]);
343 }
344
345 [doc SaveToFile: @"../../TestFiles/Output/forms_test1_cloned.pdf" flags: 0];
346 printf("Done.\n");
347 }
348 @catch(NSException *e)
349 {
350 NSLog(@"%@", e.reason);
351 ret = 1;
352 }
353
354 //----------------------------------------------------------------------------------
355 // Sample:
356 // Flatten all form fields in a document.
357 // Note that this sample is intended to show that it is possible to flatten
358 // individual fields. PDFNet provides a utility function PDFDoc.FlattenAnnotations()
359 // that will automatically flatten all fields.
360 //----------------------------------------------------------------------------------
361 @try
362 {
363 PTPDFDoc *doc = [[PTPDFDoc alloc] initWithFilepath: @"../../TestFiles/Output/forms_test1.pdf"];
364 [doc InitSecurityHandler];
365
366 // Traverse all pages
367 if (true) {
368 [doc FlattenAnnotations: NO];
369 }
370 else // Manual flattening
371 {
372 PTPageIterator *pitr;
373 for (pitr = [doc GetPageIterator: 1];
374 [pitr HasNext]; [pitr Next])
375 {
376 PTPage *page = [pitr Current];
377 for (int i = (int)[page GetNumAnnots] - 1; i >= 0; --i)
378 {
379 PTAnnot *annot = [page GetAnnot: i];
380 if ([annot GetType] == e_ptWidget)
381 {
382 [annot Flatten: page];
383 }
384 }
385 }
386 }
387
388 [doc SaveToFile: @"../../TestFiles/Output/forms_test1_flattened.pdf" flags: 0];
389 printf("Done.\n");
390 }
391 @catch(NSException *e)
392 {
393 NSLog(@"%@", e.reason);
394 ret = 1;
395 }
396 [PTPDFNet Terminate: 0];
397 return ret;
398 }
399}

Did you find this helpful?

Trial setup questions?

Ask experts on Discord

Need other help?

Contact Support

Pricing or product questions?

Contact Sales