File Coverage

blib/lib/PDF/Make/Builder/Page/HeaderFooterContext.pm
Criterion Covered Total %
statement 167 167 100.0
branch 61 68 89.7
condition 75 113 66.3
subroutine 23 23 100.0
pod 15 15 100.0
total 341 386 88.3


line stmt bran cond sub pod time code
1             package PDF::Make::Builder::Page::HeaderFooterContext;
2 42     42   392 use strict;
  42         153  
  42         1235  
3 42     42   253 use warnings;
  42         62  
  42         1498  
4 42     42   151 use Object::Proto;
  42         67  
  42         1975  
5 42     42   169 use PDF::Make::Builder::Font;
  42         64  
  42         2343  
6              
7             BEGIN {
8 42     42   2361 Object::Proto::define('PDF::Make::Builder::Page::HeaderFooterContext',
9             'builder:Any:required',
10             'page:Any:required',
11             'canvas:Any:required',
12             'x0:Num:required',
13             'y0:Num:required',
14             'w:Num:required',
15             'h:Num:required',
16             'padding:Num:default(20)',
17             'num:Int:default(0)',
18             'role:Str:default(header)',
19             );
20 42         75933 Object::Proto::import_accessors('PDF::Make::Builder::Page::HeaderFooterContext');
21             }
22              
23             # ── Region accessors ──────────────────────────────────────
24              
25 28     28 1 147599 sub left { my $s = shift; x0 $s }
  28         88  
26 12     12 1 1239 sub right { my $s = shift; x0($s) + w($s) }
  12         34  
27 29     29 1 43 sub bottom { my $s = shift; y0 $s }
  29         106  
28 15     15 1 1700 sub top { my $s = shift; y0($s) + h($s) }
  15         56  
29 6     6 1 34 sub center_x { my $s = shift; x0($s) + w($s) / 2 }
  6         27  
30 1     1 1 2 sub center_y { my $s = shift; y0($s) + h($s) / 2 }
  1         6  
31              
32             sub baseline {
33 1     1 1 2 my ($self, $offset) = @_;
34 1   50     4 $offset //= 0;
35 1         4 return y0($self) + $offset;
36             }
37              
38             sub inset {
39 1     1 1 3 my ($self, $dx, $dy) = @_;
40 1 50       3 $dy = $dx unless defined $dy;
41 1         9 return (x0($self) + $dx, y0($self) + $dy,
42             w($self) - 2 * $dx, h($self) - 2 * $dy);
43             }
44              
45             # ── Font helpers ──────────────────────────────────────────
46              
47             sub _font {
48 8     8   15 my ($self, $override) = @_;
49 8         17 my $base = $self->builder->font;
50 8 100       27 return $base unless $override;
51             return PDF::Make::Builder::Font->new(
52             colour => $override->{colour} // $base->colour,
53             size => $override->{size} // $base->size,
54             family => $override->{family} // $base->family,
55             bold => $override->{bold} // $base->bold,
56             italic => $override->{italic} // $base->italic,
57 5   33     42 line_height => $override->{line_height} // $base->effective_line_height,
      33        
      66        
      66        
      66        
      66        
58             );
59             }
60              
61             sub _default_baseline_y {
62 8     8   14 my ($self, $font_size) = @_;
63 8 100       18 if ($self->role eq 'footer') {
64 4         13 return $self->bottom + 4;
65             }
66 4         6 return $self->top - $font_size;
67             }
68              
69             # ── Text ──────────────────────────────────────────────────
70              
71             sub text {
72 9     9 1 53 my ($self, %args) = @_;
73 9         13 my $txt = $args{text};
74 9 100 66     27 return $self unless defined $txt && length $txt;
75              
76 8         40 my $font = $self->_font($args{font});
77 8         16 my $size = $font->size;
78 8         23 my $res = $font->ensure_loaded($self->page->xs_page);
79 8         20 my ($r, $g, $b) = $font->hex_to_rgb($font->colour);
80              
81 8         16 my $tw = $font->measure_text($txt);
82              
83 8   100     18 my $align = $args{align} // 'left';
84 8 100       18 my $pad = defined $args{padding} ? $args{padding} : $self->padding;
85              
86 8         8 my $x = $args{x};
87 8 50       12 if (!defined $x) {
88 8 100       16 if ($align eq 'right') { $x = $self->right - $pad - $tw }
  2 100       5  
89 3         7 elsif ($align eq 'center') { $x = $self->center_x - $tw / 2 }
90 3         7 else { $x = $self->left + $pad }
91             }
92              
93 8         10 my $y = $args{y};
94 8 50       27 $y = $self->_default_baseline_y($size) unless defined $y;
95              
96 8         88 $self->canvas->BT
97             ->rg($r, $g, $b)
98             ->Tf($res, $size)
99             ->Tm(1, 0, 0, 1, $x, $y)
100             ->Tj($txt)
101             ->ET;
102 8         38 return $self;
103             }
104              
105             sub page_num {
106 4     4 1 36 my ($self, %args) = @_;
107 4   100     14 my $format = $args{format} // $args{text} // 'Page {num}';
      100        
108 4         31 my $n = $self->num;
109 4         15 $format =~ s/\{num\}/$n/g;
110 4 100       16 if ($format =~ /\{total\}/) {
111 2         7 my $total = $self->builder->page_count;
112 2         6 $format =~ s/\{total\}/$total/g;
113             }
114              
115 4   100     11 my $font = $args{font} // {};
116             my %font = (
117             size => $font->{size} // 8,
118             colour => $font->{colour} // '#666',
119             (exists $font->{family} ? (family => $font->{family}) : ()),
120             (exists $font->{bold} ? (bold => $font->{bold}) : ()),
121 4 100 50     31 (exists $font->{italic} ? (italic => $font->{italic}) : ()),
    100 50        
    100          
122             );
123             return $self->text(
124             text => $format,
125             align => $args{align} // 'right',
126             x => $args{x},
127             y => $args{y},
128             padding => $args{padding},
129 4   100     27 font => \%font,
130             );
131             }
132              
133             # ── Shapes ────────────────────────────────────────────────
134              
135             sub line {
136 5     5 1 13 my ($self, %args) = @_;
137 5         6 my ($x1, $y1, $x2, $y2);
138 5 100 66     21 if ($args{from} && $args{to}) {
139 1         2 ($x1, $y1) = @{$args{from}};
  1         3  
140 1         1 ($x2, $y2) = @{$args{to}};
  1         2  
141             } else {
142 4   66     9 $x1 = $args{x1} // $self->left;
143 4   66     9 $y1 = $args{y1} // $self->bottom;
144 4   66     14 $x2 = $args{x2} // $self->right;
145 4   66     8 $y2 = $args{y2} // $y1;
146             }
147 5   66     15 my $colour = $args{colour} // $args{fill_colour} // '#000';
      50        
148 5         9 my $font = $self->builder->font;
149 5         10 my ($r, $g, $b) = $font->hex_to_rgb($colour);
150 5         7 my $canvas = $self->canvas;
151              
152 5   100     35 $canvas->q->w($args{width} // 1)->RG($r, $g, $b);
153              
154 5   100     9 my $type = $args{type} // 'solid';
155 5 100       16 if ($args{dash}) {
    100          
    100          
156 1         5 $canvas->d($args{dash}, 0);
157             } elsif ($type eq 'dashed') {
158 1         6 $canvas->d([6, 3], 0);
159             } elsif ($type eq 'dots') {
160 1         6 $canvas->J(1)->d([0, 4], 0);
161             }
162              
163 5         36 $canvas->m($x1, $y1)->l($x2, $y2)->S->Q;
164 5         15 return $self;
165             }
166              
167             sub box {
168 2     2 1 14 my ($self, %args) = @_;
169 2   66     11 my $x = $args{x} // $self->left;
170 2   66     6 my $y = $args{y} // $self->bottom;
171 2   66     15 my $w = $args{w} // $self->w;
172 2   66     7 my $h = $args{h} // $self->h;
173 2   33     5 my $colour = $args{fill_colour} // $args{colour} // '#000';
      0        
174 2         6 my $font = $self->builder->font;
175 2         5 my ($r, $g, $b) = $font->hex_to_rgb($colour);
176 2         4 my $canvas = $self->canvas;
177              
178 2         10 $canvas->q;
179 2 100       6 if ($colour eq 'transparent') {
180 1   50     13 $canvas->w($args{width} // 1)->RG($r, $g, $b)->re($x, $y, $w, $h)->S;
181             } else {
182 1         11 $canvas->rg($r, $g, $b)->re($x, $y, $w, $h)->f;
183             }
184 2         6 $canvas->Q;
185 2         7 return $self;
186             }
187              
188             # ── Image ─────────────────────────────────────────────────
189              
190             sub image {
191 6     6 1 956 my ($self, %args) = @_;
192 6         63 require PDF::Make::Image;
193              
194             my $src = $args{src} // $args{image}
195 6 100 100     42 or die "HeaderFooterContext::image requires src or image";
196 5         256498 my $img = PDF::Make::Image->from_file($src);
197 5         72 my $img_w = $img->width;
198 5         26 my $img_h = $img->height;
199              
200 5         56 my $doc = $self->builder->doc;
201 5         222 my $obj_num = $img->write_to_doc($doc);
202 5         24 my $res_name = 'Im' . $obj_num;
203 5         76 $self->page->xs_page->add_image($res_name, $obj_num);
204              
205 5         14 my $draw_w = $args{w};
206 5         11 my $draw_h = $args{h};
207 5 100 100     42 if (!defined $draw_w && !defined $draw_h) {
    100          
    100          
208 1         4 $draw_h = $self->h;
209 1         3 $draw_w = $draw_h * ($img_w / $img_h);
210             } elsif (!defined $draw_h) {
211 2         10 $draw_h = $draw_w * ($img_h / $img_w);
212             } elsif (!defined $draw_w) {
213 1         5 $draw_w = $draw_h * ($img_w / $img_h);
214             }
215              
216 5   100     26 my $align = $args{align} // 'left';
217 5 50       23 my $pad = defined $args{padding} ? $args{padding} : $self->padding;
218 5         17 my $x = $args{x};
219 5 100       14 if (!defined $x) {
220 4 100       13 if ($align eq 'right') { $x = $self->right - $pad - $draw_w }
  1 100       4  
221 1         5 elsif ($align eq 'center') { $x = $self->center_x - $draw_w / 2 }
222 2         9 else { $x = $self->left + $pad }
223             }
224 5   66     24 my $y = $args{y} // ($self->bottom + ($self->h - $draw_h) / 2);
225              
226 5         71 $self->canvas->image($res_name, $x, $y, $draw_w, $draw_h);
227 5         307 return $self;
228             }
229              
230             # ── Annotations ───────────────────────────────────────────
231              
232             sub _rect {
233 11     11   28 my ($self, %args) = @_;
234 11 100       44 return $args{rect} if $args{rect};
235 2         5 my $x = $args{x};
236 2         6 my $y = $args{y};
237 2         5 my $w = $args{w};
238 2         5 my $h = $args{h};
239 2 50 66     26 die "HeaderFooterContext: annotation requires rect or x/y/w/h"
      66        
      33        
240             unless defined $x && defined $y && defined $w && defined $h;
241 1         6 return [$x, $y, $x + $w, $y + $h];
242             }
243              
244             sub note {
245 3     3 1 597 my ($self, %args) = @_;
246 3         12 my $rect = $self->_rect(%args);
247 2   50     8 my $text = $args{text} // '';
248 2   100     13 my $icon = $args{icon} // 'Note';
249 2   50     8 my $open = $args{open} // 0;
250 2         9 my $xs_doc = $self->builder->doc;
251 2         24 my $annot_num = $xs_doc->add_text_annot(@$rect, $text, $icon, $open);
252 2 50       35 $self->page->xs_page->add_annot($annot_num) if $annot_num;
253 2         13 return $self;
254             }
255              
256             sub link {
257 8     8 1 37 my ($self, %args) = @_;
258 8         25 my $rect = $self->_rect(%args);
259 8         45 my $xs_doc = $self->builder->doc;
260 8   50     31 my $hl = $args{highlight} // 'Invert';
261 8         18 my $annot_num;
262 8 100       30 if ($args{url}) {
    100          
    100          
    100          
263 1         9 $annot_num = $xs_doc->add_link_uri(@$rect, $args{url});
264             } elsif (defined $args{page}) {
265 2         27 $annot_num = $xs_doc->add_link_goto(@$rect, $args{page});
266             } elsif ($args{action}) {
267 2         25 $annot_num = $xs_doc->add_link_named_action(@$rect, $args{action}, $hl);
268             } elsif ($args{file}) {
269             my $action = $xs_doc->action_gotor(
270 2   50     28 $args{file}, $args{file_page} // 0, $args{new_window} // 0);
      50        
271 2         36 $annot_num = $xs_doc->add_link_with_action(@$rect, $action, $hl);
272             } else {
273 1         16 die "HeaderFooterContext::link requires url, page, action, or file";
274             }
275 7 50       46 $self->page->xs_page->add_annot($annot_num) if $annot_num;
276 7         29 return $self;
277             }
278              
279             1;
280              
281             __END__