File Coverage

blib/lib/PDF/Make/Builder/Form/Field.pm
Criterion Covered Total %
statement 85 86 98.8
branch 23 26 88.4
condition 7 12 58.3
subroutine 10 11 90.9
pod 0 1 0.0
total 125 136 91.9


line stmt bran cond sub pod time code
1             package PDF::Make::Builder::Form::Field;
2 42     42   233 use strict;
  42         66  
  42         1299  
3 42     42   136 use warnings;
  42         57  
  42         1500  
4 42     42   159 use Object::Proto;
  42         80  
  42         1888  
5 42     42   157 use PDF::Make ();
  42         63  
  42         2805  
6              
7             BEGIN {
8 42     42   4344 Object::Proto::define('PDF::Make::Builder::Form::Field',
9             'field_name:Str:required',
10             'x:Num',
11             'y:Num',
12             'w:Num:default(200)',
13             'h:Num:default(24)',
14             'default_value:Str',
15             'da:Str',
16             'label:Str',
17             'label_colour:Str:default(#444)',
18             'label_size:Num:default(9)',
19             'font_size:Num:default(10)',
20             'font_name:Str:default(Helv)',
21             'border_colour:Str:default(#aaa)',
22             'bg_colour:Str:default(#fff)',
23             'readonly:Bool:default(0)',
24             'required:Bool:default(0)',
25             'inline_label:Bool:default(0)',
26             'raw_mode:Bool:default(0)',
27             'margin_top:Num:default(4)',
28             'margin_bottom:Num:default(8)',
29             );
30 42         33611 Object::Proto::import_accessors('PDF::Make::Builder::Form::Field');
31             }
32              
33             sub _ensure_form {
34 24     24   37 my ($doc) = @_;
35 24         32 my $form = eval { PDF::Make::FormPtr::get($doc) };
  24         180  
36 24 100       69 unless ($form) {
37 6         40 $form = PDF::Make::FormPtr::create($doc);
38 6         118 PDF::Make::FormPtr::set_need_appearances($form, 1);
39             }
40 24         77 return $form;
41             }
42              
43             sub _create_field {
44 0     0   0 die "PDF::Make::Builder::Form::Field::_create_field must be overridden";
45             }
46              
47             sub add {
48 24     24 0 50 my ($self, $builder) = @_;
49 24         60 my $page = $builder->page;
50 24         58 my $canvas = $page->canvas;
51 24         56 my $doc = $builder->doc;
52 24         67 my $bfont = $builder->font;
53              
54 24         55 _ensure_form($doc);
55              
56 24   66     90 my $fx = $self->x // $page->content_x;
57 24         51 my $fw = $self->w;
58 24         46 my $fh = $self->h;
59 24         43 my $name = field_name $self;
60 24         29 my $is_inline = inline_label $self;
61 24         51 my $lbl = $self->label;
62              
63             # ── Raw mode: create/place widget without layout/chrome/cursor changes ──
64 24 100       84 if ($self->raw_mode) {
65 9 50       45 my $fy = defined($self->y) ? $self->y : ($page->cursor_y - $fh);
66 9         46 my $field = $self->_create_field($doc, $name, $fx, $fy, $fw, $fh);
67 9         51 $self->_apply_field_props($field);
68 9         82 $field->add_to_page($page->xs_page);
69 9         56 return $self;
70             }
71              
72             # Top margin
73 15         36 $page->advance_y(margin_top $self);
74              
75 15 100       27 if ($is_inline) {
76             # ── Inline layout (checkbox/radio): field then label beside it ──
77              
78 2         5 my $fy = $page->cursor_y - $fh;
79              
80             # Draw label to the right of the field
81 2 50 33     7 if (defined $lbl && length $lbl) {
82 2         2 my $lsize = label_size $self;
83 2         5 my ($lr, $lg, $lb) = $bfont->hex_to_rgb(label_colour $self);
84 2         7 $bfont->ensure_loaded($page->xs_page);
85 2         4 my $res = $bfont->resource_name;
86 2         32 $canvas->BT
87             ->Tf($res, $lsize)
88             ->rg($lr, $lg, $lb)
89             ->Td($fx + $fw + 8, $fy + ($fh - $lsize) / 2 + 1)
90             ->Tj($lbl)
91             ->ET;
92             }
93              
94             # Create XS field
95 2         6 my $field = $self->_create_field($doc, $name, $fx, $fy, $fw, $fh);
96 2         7 $self->_apply_field_props($field);
97 2         8 $field->add_to_page($page->xs_page);
98              
99             # Draw styled border (unless subclass handles its own appearance)
100 2 50       3 $self->_draw_field_chrome($canvas, $bfont, $fx, $fy, $fw, $fh)
101             unless $self->_draws_own_chrome;
102              
103             # Advance past the field
104 2         5 $page->advance_y($fh + margin_bottom $self);
105              
106             } else {
107             # ── Stacked layout (text/combo/list): label above, field below ──
108              
109             # Draw label first
110 13 100 66     36 if (defined $lbl && length $lbl) {
111 10         14 my $lsize = label_size $self;
112 10         29 my ($lr, $lg, $lb) = $bfont->hex_to_rgb(label_colour $self);
113 10         40 $bfont->ensure_loaded($page->xs_page);
114 10         19 my $res = $bfont->resource_name;
115 10 100       32 my $req_marker = ($self->required) ? ' *' : '';
116 10         21 my $label_y = $page->cursor_y - $lsize;
117 10         222 $canvas->BT
118             ->Tf($res, $lsize)
119             ->rg($lr, $lg, $lb)
120             ->Td($fx, $label_y)
121             ->Tj($lbl . $req_marker)
122             ->ET;
123 10         23 $page->advance_y($lsize + 3);
124             }
125              
126             # Field goes at current cursor
127 13         19 my $fy = $page->cursor_y - $fh;
128              
129             # Create XS field
130 13         35 my $field = $self->_create_field($doc, $name, $fx, $fy, $fw, $fh);
131 13         99 $self->_apply_field_props($field);
132 13         42 $field->add_to_page($page->xs_page);
133              
134             # Draw styled border (unless subclass handles its own appearance)
135 13 100       35 $self->_draw_field_chrome($canvas, $bfont, $fx, $fy, $fw, $fh)
136             unless $self->_draws_own_chrome;
137              
138             # Advance past the field
139 13         26 $page->advance_y($fh + margin_bottom $self);
140             }
141              
142 15         30 return $self;
143             }
144              
145             sub _apply_field_props {
146 24     24   43 my ($self, $field) = @_;
147 24         58 my $da = $self->da;
148 24 100 66     320 $da = sprintf('/%s %g Tf 0 g', $self->font_name, $self->font_size)
149             unless defined $da && length $da;
150 24         157 $field->set_da($da);
151 24         34 my $dv = default_value $self;
152 24 100       53 $field->set_value($dv) if defined $dv;
153 24 100       62 $field->readonly if $self->readonly;
154 24 100       62 $field->required if $self->required;
155             }
156              
157 11     11   39 sub _draws_own_chrome { 0 }
158              
159             sub _draw_field_chrome {
160 11     11   18 my ($self, $canvas, $bfont, $fx, $fy, $fw, $fh) = @_;
161 11         23 my ($bgr, $bgg, $bgb) = $bfont->hex_to_rgb(bg_colour $self);
162 11         18 my ($bdr, $bdg, $bdb) = $bfont->hex_to_rgb(border_colour $self);
163              
164 11         32 $canvas->q;
165             # White fill
166 11         75 $canvas->rg($bgr, $bgg, $bgb)
167             ->re($fx, $fy, $fw, $fh)->f;
168             # Border stroke
169 11         94 $canvas->w(0.75)
170             ->RG($bdr, $bdg, $bdb)
171             ->re($fx + 0.25, $fy + 0.25, $fw - 0.5, $fh - 0.5)->S;
172 11         26 $canvas->Q;
173             }
174              
175             1;
176              
177             __END__