File Coverage

blib/lib/PDF/Make/Form.pm
Criterion Covered Total %
statement 150 151 99.3
branch 42 42 100.0
condition 48 49 97.9
subroutine 27 28 96.4
pod 20 20 100.0
total 287 290 98.9


line stmt bran cond sub pod time code
1             package PDF::Make::Form;
2              
3 3     3   1417 use strict;
  3         3  
  3         84  
4 3     3   9 use warnings;
  3         4  
  3         134  
5 3     3   10 no warnings qw(redefine prototype); # XS methods are overridden by Perl wrappers
  3         4  
  3         73  
6 3     3   8 use PDF::Make;
  3         3  
  3         62  
7 3     3   1147 use PDF::Make::Field;
  3         6  
  3         242  
8              
9             =head1 NAME
10              
11             PDF::Make::Form - PDF interactive forms (AcroForms) for PDF::Make
12              
13             =head1 SYNOPSIS
14              
15             use PDF::Make;
16            
17             my $pdf = PDF::Make->new();
18             my $page = $pdf->add_page(width => 612, height => 792);
19            
20             # Create a form
21             my $form = $pdf->create_form();
22            
23             # Add a text field
24             my $name_field = $form->add_text_field(
25             name => 'name',
26             x => 100,
27             y => 700,
28             width => 200,
29             height => 20,
30             );
31             $name_field->set_value('John Doe');
32             $name_field->add_to_page($page);
33            
34             # Add a checkbox
35             my $checkbox = $form->add_checkbox(
36             name => 'agree',
37             x => 100,
38             y => 650,
39             width => 15,
40             height => 15,
41             on_value => 'Yes',
42             );
43             $checkbox->set_value('Yes'); # Check it
44             $checkbox->add_to_page($page);
45            
46             # Finalize and render
47             $form->finalize();
48             my $bytes = $pdf->render();
49              
50             =head1 DESCRIPTION
51              
52             This module provides PDF interactive forms (AcroForms) support per
53             ISO 32000-2:2020 ยง12.7. It supports:
54              
55             =over 4
56              
57             =item * Text fields (single-line, multiline, password)
58              
59             =item * Checkboxes and radio button groups
60              
61             =item * Dropdown (combo) and list boxes
62              
63             =item * Push buttons
64              
65             =item * Signature fields (placeholder)
66              
67             =item * FDF/XFDF export and import
68              
69             =item * Form flattening
70              
71             =back
72              
73             =head1 METHODS
74              
75             =cut
76              
77             use constant {
78             # Field flags (matching C header)
79 3         4381 FF_READONLY => (1 << 0),
80             FF_REQUIRED => (1 << 1),
81             FF_NOEXPORT => (1 << 2),
82             FF_MULTILINE => (1 << 12),
83             FF_PASSWORD => (1 << 13),
84             FF_FILESELECT => (1 << 20),
85             FF_DONOTSPELLCHECK => (1 << 22),
86             FF_DONOTSCROLL => (1 << 23),
87             FF_COMB => (1 << 24),
88             FF_RICHTEXT => (1 << 25),
89             FF_NOTOGGLETOOFF => (1 << 14),
90             FF_RADIO => (1 << 15),
91             FF_PUSHBUTTON => (1 << 16),
92             FF_RADIOSINUNISON => (1 << 25),
93             FF_COMBO => (1 << 17),
94             FF_EDIT => (1 << 18),
95             FF_SORT => (1 << 19),
96             FF_MULTISELECT => (1 << 21),
97             FF_COMMITONSELCHANGE => (1 << 26),
98            
99             # Quadding (text alignment)
100             QUADDING_LEFT => 0,
101             QUADDING_CENTER => 1,
102             QUADDING_RIGHT => 2,
103 3     3   14 };
  3         4  
104              
105             our @EXPORT_OK = qw(
106             FF_READONLY FF_REQUIRED FF_NOEXPORT
107             FF_MULTILINE FF_PASSWORD FF_FILESELECT FF_DONOTSPELLCHECK
108             FF_DONOTSCROLL FF_COMB FF_RICHTEXT
109             FF_NOTOGGLETOOFF FF_RADIO FF_PUSHBUTTON FF_RADIOSINUNISON
110             FF_COMBO FF_EDIT FF_SORT FF_MULTISELECT FF_COMMITONSELCHANGE
111             QUADDING_LEFT QUADDING_CENTER QUADDING_RIGHT
112             );
113              
114             our %EXPORT_TAGS = (
115             flags => [qw(FF_READONLY FF_REQUIRED FF_NOEXPORT FF_MULTILINE
116             FF_PASSWORD FF_COMB FF_COMBO FF_EDIT FF_MULTISELECT)],
117             quadding => [qw(QUADDING_LEFT QUADDING_CENTER QUADDING_RIGHT)],
118             all => \@EXPORT_OK,
119             );
120              
121             =head2 new
122              
123             my $form = PDF::Make::Form->new($document);
124              
125             Create a new form for the given document. Usually called via
126             C<< $pdf->create_form() >> rather than directly.
127              
128             =cut
129              
130             sub new {
131 44     44 1 533483 my ($class, $doc) = @_;
132            
133 44 100       120 die "PDF::Make::Form->new: document required" unless $doc;
134            
135             # $doc is the raw pdfmake_doc_t pointer (Document object)
136 43         241 my $form = PDF::Make::FormPtr::create($doc);
137            
138 43         195 return bless {
139             _form => $form,
140             _doc => $doc,
141             _fields => [],
142             }, $class;
143             }
144              
145             =head2 add_text_field
146              
147             my $field = $form->add_text_field(
148             name => 'field_name',
149             x => 100,
150             y => 500,
151             width => 200,
152             height => 20,
153             );
154              
155             Create a text field at the specified location.
156              
157             Options:
158              
159             =over 4
160              
161             =item * name - Field name (required)
162              
163             =item * x, y - Lower-left corner coordinates
164              
165             =item * width, height - Field dimensions
166              
167             =item * value - Initial value
168              
169             =item * default_value - Default value for reset
170              
171             =item * max_len - Maximum characters allowed
172              
173             =item * multiline - Enable multiline input
174              
175             =item * password - Mask input as password
176              
177             =item * readonly - Make field read-only
178              
179             =item * required - Mark as required
180              
181             =item * quadding - Text alignment (0=left, 1=center, 2=right)
182              
183             =item * da - Default appearance string
184              
185             =back
186              
187             =cut
188              
189             sub add_text_field {
190 38     38 1 212 my ($self, %opts) = @_;
191            
192 38 100       102 my $name = delete $opts{name} or die "add_text_field: name required";
193 37   100     96 my $x = delete $opts{x} // 0;
194 37   100     68 my $y = delete $opts{y} // 0;
195 37   100     70 my $width = delete $opts{width} // 100;
196 37   100     89 my $height = delete $opts{height} // 20;
197            
198             my $field = PDF::Make::FieldPtr::text(
199 37         190 $self->{_doc}, $name, $x, $y, $width, $height
200             );
201            
202 37         95 $self->_configure_field($field, %opts);
203            
204 37         70 push @{$self->{_fields}}, $field;
  37         71  
205            
206 37         95 return PDF::Make::Field->_wrap($field, $self);
207             }
208              
209             =head2 add_checkbox
210              
211             my $field = $form->add_checkbox(
212             name => 'agree',
213             x => 100,
214             y => 500,
215             width => 15,
216             height => 15,
217             on_value => 'Yes',
218             );
219              
220             Create a checkbox field.
221              
222             =cut
223              
224             sub add_checkbox {
225 6     6 1 555 my ($self, %opts) = @_;
226            
227 6 100       26 my $name = delete $opts{name} or die "add_checkbox: name required";
228 5   100     15 my $x = delete $opts{x} // 0;
229 5   100     11 my $y = delete $opts{y} // 0;
230 5   100     13 my $width = delete $opts{width} // 15;
231 5   100     24 my $height = delete $opts{height} // 15;
232 5   100     17 my $on_value = delete $opts{on_value} // 'Yes';
233            
234             my $field = PDF::Make::FieldPtr::checkbox(
235 5         27 $self->{_doc}, $name, $x, $y, $width, $height, $on_value
236             );
237            
238 5         14 $self->_configure_field($field, %opts);
239            
240 5         6 push @{$self->{_fields}}, $field;
  5         9  
241            
242 5         12 return PDF::Make::Field->_wrap($field, $self);
243             }
244              
245             =head2 add_radio_group
246              
247             my $group = $form->add_radio_group(name => 'choice');
248             $group->add_option(x => 100, y => 500, width => 15, height => 15, value => 'A');
249             $group->add_option(x => 100, y => 480, width => 15, height => 15, value => 'B');
250             $group->add_option(x => 100, y => 460, width => 15, height => 15, value => 'C');
251              
252             Create a radio button group.
253              
254             =cut
255              
256             sub add_radio_group {
257 6     6 1 458 my ($self, %opts) = @_;
258            
259 6 100       22 my $name = delete $opts{name} or die "add_radio_group: name required";
260            
261 5         22 my $group = PDF::Make::FieldPtr::radio_group($self->{_doc}, $name);
262            
263 5         7 push @{$self->{_fields}}, $group;
  5         10  
264            
265 5         14 return PDF::Make::Field->_wrap($group, $self);
266             }
267              
268             =head2 add_choice
269              
270             my $field = $form->add_choice(
271             name => 'country',
272             x => 100,
273             y => 500,
274             width => 150,
275             height => 20,
276             combo => 1, # 1 for dropdown, 0 for listbox
277             options => [
278             { display => 'United States', export => 'US' },
279             { display => 'Canada', export => 'CA' },
280             { display => 'Mexico', export => 'MX' },
281             ],
282             );
283              
284             Create a choice field (dropdown or listbox).
285              
286             =cut
287              
288             sub add_choice {
289 7     7 1 452 my ($self, %opts) = @_;
290            
291 7 100       41 my $name = delete $opts{name} or die "add_choice: name required";
292 6   100     16 my $x = delete $opts{x} // 0;
293 6   100     14 my $y = delete $opts{y} // 0;
294 6   100     15 my $width = delete $opts{width} // 100;
295 6   100     11 my $height = delete $opts{height} // 20;
296 6   100     21 my $combo = delete $opts{combo} // 1;
297 6   100     18 my $options = delete $opts{options} // [];
298            
299             my $field = PDF::Make::FieldPtr::choice(
300 6         29 $self->{_doc}, $name, $x, $y, $width, $height, $combo
301             );
302            
303             # Add options
304 6         12 for my $opt (@$options) {
305 10 100       20 if (ref $opt eq 'HASH') {
306 4         6 my $display = $opt->{display};
307 4         5 my $export = $opt->{export};
308 4 100       8 if (defined $export) {
309 3         25 $field->add_option($display, $export);
310             } else {
311 1         32 $field->add_option($display);
312             }
313             } else {
314 6         12 $field->add_option($opt);
315             }
316             }
317            
318 6         16 $self->_configure_field($field, %opts);
319            
320 6         6 push @{$self->{_fields}}, $field;
  6         9  
321            
322 6         16 return PDF::Make::Field->_wrap($field, $self);
323             }
324              
325             =head2 add_combo
326              
327             my $field = $form->add_combo(name => 'country', ...);
328              
329             Shorthand for C 1, ...)>.
330              
331             =cut
332              
333             sub add_combo {
334 2     2 1 18 my ($self, %opts) = @_;
335 2         32 return $self->add_choice(%opts, combo => 1);
336             }
337              
338             =head2 add_listbox
339              
340             my $field = $form->add_listbox(name => 'items', ...);
341              
342             Shorthand for C 0, ...)>.
343              
344             =cut
345              
346             sub add_listbox {
347 2     2 1 15 my ($self, %opts) = @_;
348 2         7 return $self->add_choice(%opts, combo => 0);
349             }
350              
351             =head2 add_button
352              
353             my $field = $form->add_button(
354             name => 'submit',
355             x => 100,
356             y => 100,
357             width => 80,
358             height => 25,
359             caption => 'Submit',
360             );
361              
362             Create a push button.
363              
364             =cut
365              
366             sub add_button {
367 6     6 1 444 my ($self, %opts) = @_;
368            
369 6 100       22 my $name = delete $opts{name} or die "add_button: name required";
370 5   100     14 my $x = delete $opts{x} // 0;
371 5   100     12 my $y = delete $opts{y} // 0;
372 5   100     11 my $width = delete $opts{width} // 80;
373 5   100     11 my $height = delete $opts{height} // 25;
374 5   66     10 my $caption = delete $opts{caption} // $name;
375            
376             my $field = PDF::Make::FieldPtr::button(
377 5         31 $self->{_doc}, $name, $x, $y, $width, $height, $caption
378             );
379            
380 5         13 $self->_configure_field($field, %opts);
381            
382 5         6 push @{$self->{_fields}}, $field;
  5         7  
383            
384 5         24 return PDF::Make::Field->_wrap($field, $self);
385             }
386              
387             =head2 add_signature
388              
389             my $field = $form->add_signature(
390             name => 'sig',
391             x => 100,
392             y => 100,
393             width => 200,
394             height => 50,
395             );
396              
397             Create a signature field (placeholder for digital signature).
398              
399             =cut
400              
401             sub add_signature {
402 4     4 1 437 my ($self, %opts) = @_;
403            
404 4 100       17 my $name = delete $opts{name} or die "add_signature: name required";
405 3   100     10 my $x = delete $opts{x} // 0;
406 3   100     9 my $y = delete $opts{y} // 0;
407 3   100     16 my $width = delete $opts{width} // 200;
408 3   100     11 my $height = delete $opts{height} // 50;
409            
410             my $field = PDF::Make::FieldPtr::signature(
411 3         17 $self->{_doc}, $name, $x, $y, $width, $height
412             );
413            
414 3         10 $self->_configure_field($field, %opts);
415            
416 3         3 push @{$self->{_fields}}, $field;
  3         6  
417            
418 3         7 return PDF::Make::Field->_wrap($field, $self);
419             }
420              
421             =head2 field_count
422              
423             my $count = $form->field_count();
424              
425             Return the number of top-level fields.
426              
427             =cut
428              
429             sub field_count {
430 4     4 1 433 my ($self) = @_;
431 4         28 return $self->{_form}->field_count();
432             }
433              
434             =head2 field_at
435              
436             my $field = $form->field_at($index);
437              
438             Get field at the given index.
439              
440             =cut
441              
442             sub field_at {
443 5     5 1 862 my ($self, $idx) = @_;
444 5         22 my $field = $self->{_form}->field_at($idx);
445 5 100       18 return unless $field;
446 3         13 return PDF::Make::Field->_wrap($field, $self);
447             }
448              
449             =head2 field_by_name
450              
451             my $field = $form->field_by_name('person.name.first');
452              
453             Find a field by its full name.
454              
455             =cut
456              
457             sub field_by_name {
458 4     4 1 12 my ($self, $name) = @_;
459 4         17 my $field = $self->{_form}->field_by_name($name);
460 4 100       16 return unless $field;
461 2         8 return PDF::Make::Field->_wrap($field, $self);
462             }
463              
464             =head2 fields
465              
466             my @fields = $form->fields();
467              
468             Get all top-level fields.
469              
470             =cut
471              
472             sub fields {
473 2     2 1 269 my ($self) = @_;
474 2         12 return map { PDF::Make::Field->_wrap($_, $self) } $self->{_form}->fields();
  3         7  
475             }
476              
477             =head2 finalize
478              
479             $form->finalize();
480              
481             Finalize the form: create AcroForm dictionary, field dictionaries,
482             widget annotations, and appearance streams. Call this before rendering.
483              
484             =cut
485              
486             sub finalize {
487 7     7 1 538 my ($self) = @_;
488 7         455 $self->{_form}->finalize();
489 7         13 return $self;
490             }
491              
492             =head2 flatten
493              
494             $form->flatten();
495              
496             Flatten all form fields: render values into page content and remove
497             interactive elements. After flattening, the PDF is no longer editable.
498              
499             =cut
500              
501             sub flatten {
502 1     1 1 4 my ($self) = @_;
503 1         41 $self->{_form}->flatten();
504 1         2 return $self;
505             }
506              
507             =head2 export_fdf
508              
509             my $fdf_data = $form->export_fdf();
510              
511             Export form data in FDF format.
512              
513             =cut
514              
515             sub export_fdf {
516 2     2 1 11 my ($self) = @_;
517 2         21 return $self->{_form}->export_fdf();
518             }
519              
520             =head2 export_xfdf
521              
522             my $xfdf_data = $form->export_xfdf();
523              
524             Export form data in XFDF (XML) format.
525              
526             =cut
527              
528             sub export_xfdf {
529 2     2 1 1573 my ($self) = @_;
530 2         35 return $self->{_form}->export_xfdf();
531             }
532              
533             =head2 import_fdf
534              
535             $form->import_fdf($fdf_data);
536              
537             Import form data from FDF format.
538              
539             =cut
540              
541             sub import_fdf {
542 1     1 1 6 my ($self, $data) = @_;
543 1         6 $self->{_form}->import_fdf($data);
544 1         2 return $self;
545             }
546              
547             =head2 import_xfdf
548              
549             $form->import_xfdf($xfdf_data);
550              
551             Import form data from XFDF format.
552              
553             =cut
554              
555             sub import_xfdf {
556 1     1 1 6 my ($self, $data) = @_;
557 1         34 $self->{_form}->import_xfdf($data);
558 1         2 return $self;
559             }
560              
561             =head2 set_need_appearances
562              
563             $form->set_need_appearances(1);
564              
565             Set the NeedAppearances flag. If true, the PDF viewer will generate
566             field appearances. If false (default), appearances are generated
567             during finalization.
568              
569             =cut
570              
571             sub set_need_appearances {
572 4     4 1 290 my ($self, $need) = @_;
573 4 100       35 $self->{_form}->set_need_appearances($need ? 1 : 0);
574 4         7 return $self;
575             }
576              
577             # Internal: configure field with common options
578             sub _configure_field {
579 56     56   84 my ($self, $field, %opts) = @_;
580            
581 56 100       111 if (defined $opts{value}) {
582 8         22 $field->set_value($opts{value});
583             }
584 56 100       77 if (defined $opts{default_value}) {
585 1         4 $field->set_default_value($opts{default_value});
586             }
587 56 100       76 if (defined $opts{max_len}) {
588 2         7 $field->set_max_len($opts{max_len});
589             }
590 56 100       75 if (defined $opts{quadding}) {
591 1         4 $field->set_quadding($opts{quadding});
592             }
593 56 100       99 if (defined $opts{da}) {
594 1         4 $field->set_da($opts{da});
595             }
596            
597             # Flags
598 56 100       83 if ($opts{multiline}) {
599 2         5 $field->multiline(1);
600             }
601 56 100       77 if ($opts{password}) {
602 1         3 $field->password(1);
603             }
604 56 100       108 if ($opts{readonly}) {
605 1         3 $field->readonly(1);
606             }
607 56 100       109 if ($opts{required}) {
608 1         3 $field->required(1);
609             }
610             }
611              
612             # Internal: get raw form pointer
613             sub _form {
614 0     0     return $_[0]->{_form};
615             }
616              
617             1;
618              
619             __END__