File Coverage

blib/lib/Mxpress/PDF.pm
Criterion Covered Total %
statement 648 694 93.3
branch 85 144 59.0
condition 65 155 41.9
subroutine 47 47 100.0
pod n/a
total 845 1040 81.2


line stmt bran cond sub pod time code
1 2     2   55953 use v5.18;
  2         7  
2 2     2   10 use strict;
  2         2  
  2         34  
3 2     2   8 use warnings;
  2         3  
  2         114  
4              
5             package Mxpress::PDF {
6             our $VERSION = '0.02';
7             use MooX::Pression (
8 2         28 version => '0.02',
9             authority => 'cpan:LNATION',
10 2     2   1233 );
  2         585007  
11 2     2   45872 use Colouring::In;
  2         4962  
  2         12  
12 2     2   76 use constant mm => 25.4 / 72;
  2         5  
  2         135  
13 2     2   10 use constant pt => 1;
  2         4  
  2         156  
14 2     2   107462 class File () {
  2         5  
  2         17  
  1         9  
  1         2  
  1         2  
15 1         5 has file_name (type => Str, required => 1);
  1         15  
16 1         5 has pdf (required => 1, type => Object);
  1         9  
17 1         5 has pages (required => 1, type => ArrayRef);
  1         15  
18 1         3 has page (type => Object);
  1         7  
19 1         3 has page_args (type => HashRef);
  1         12  
20 1         4 has onsave_cbs (type => ArrayRef);
  1         13  
21 2 50 33     21785 method add_page (Map %args) {
  2         10  
  2         6  
  2         9  
  2         4  
22             my $page = $self->FACTORY->page(
23             $self->pdf,
24             page_size => 'A4',
25 2 100       33 %{ $self->page_args },
  2         68  
26             ($self->page ? (num => $self->page->num + 1) : ()),
27             %args,
28             );
29 2         6 push @{$self->pages}, $page;
  2         37  
30 2         40 $self->page($page);
31 2 50       45 $self->boxed->add( fill_colour => $page->background ) if $page->background;
32 2         42 $self->page->set_position($page->parse_position([]));
33 2         30 $self;
  1         7  
34 1   33     18 }
  1         28  
  1         12  
  1         3  
35 1 50       18 method save {
36 1         9 if ($self->onsave_cbs) {
  1         12  
37 1         6 for my $cb (@{$self->onsave_cbs}) {
  1         4  
38 1         16 my ($plug, $meth, $args) = @{$cb};
  1         17  
39             $self->$plug->$meth(%{$args});
40             }
41 1         38 }
42 1         147649 $self->pdf->saveas();
  1         5  
43 1 50 33     11 $self->pdf->end();
  1         1699  
  1         4  
  1         3  
  1         5  
  1         2  
44 1   50     17 }
45 1         10 method onsave (Str $plug, Str $meth, Map %args) {
  1         4  
46 1         15 my $cbs = $self->onsave_cbs || [];
47 2     2   937433 push @{$cbs}, [$plug, $meth, \%args];
  2     2   4  
  2     2   36  
  1         390  
  1         10  
  2         136  
  2         4  
  2         8  
  2         33623  
  2         5  
  2         16  
  2         17  
48 2         16 $self->onsave_cbs($cbs);
  2         16  
49 2         12 }
  2         28  
50 2         5 }
  2         14  
51 2         11 class Page {
  2         16  
52 2         10 with Utils;
  2         14  
53 2         5 has page_size (type => Str, required => 1);
  2         12  
54 2         4 has background (type => Str);
  2         15  
55 2         4 has num (type => Num, required => 1);
  2         12  
56 2         3 has current (type => Object);
  2         16  
57 2         4 has is_rotated (type => Num);
  2         13  
58 2 50       1225 has x (type => Num);
  2         7  
  2         10  
  2         5  
59 2 50       16 has y (type => Num);
60 2         1489 has w (type => Num);
61 2         296 has h (type => Num);
62             factory page (Object $pdf, Map %args) {
63             my $page = $args{open} ? $pdf->openpage($args{num}) : $pdf->page($args{num});
64             $page->mediabox($args{page_size});
65             my ($blx, $bly, $trx, $try) = $page->get_mediabox;
66 2 50 100     131 my $new_page = $class->new(
67             current => $page,
68             num => $args{num} || 1,
69             ($args{is_rotated} ? (
70             x => 0,
71             w => $try,
72             h => $trx,
73             y => $trx,
74             ) : (
75             x => 0,
76             w => $trx,
77             h => $try,
78             y => $try,
79 2         3983 )),
  2         14  
80 2   0     25 padding => 0,
  0         0  
  0         0  
  0         0  
81 0         0 %args
82 0         0 );
83             return $new_page;
84             }
85             method rotate {
86             my ($h, $w) = ($self->h, $self->w);
87             $self->current->mediabox(
88 0         0 0,
89 0         0 0,
90 2     2   523549 $self->w($h),
  2     2   20  
  2     2   18  
  2         14  
  2         168  
  2         4  
  2         6  
  2         112210  
  2         4  
  2         24  
  2         17  
91 2         13 $self->h($self->y($w))
  2         31  
92 0   0     0 );
  0         0  
  0         0  
93 0         0 $self->is_rotated(!$self->is_rotated);
  2         15  
94 2   33     22 return $self;
  3         1813  
  3         66  
  3         5  
95 3 100       29 }
96 3         66 }
97 3         68 role Utils {
98 3         77 has padding (type => Num);
99 3         69 method add_padding (Num $padding) {
100 3         73 $self->padding($self->padding + $padding);
  2         11  
101 2   33     16 }
  19         2815  
  19         65  
  19         26  
102             method set_position (Num $x, Num $y, Num $w, Num $h) {
103 8 50       38 my $page = $self->can('file') ? $self->file->page : $self;
104 19         36 $page->x($x);
  19         46  
105 19 100       305 $page->y($y);
106 19 100       534 $page->w($w);
107 19 100       595 $page->h($h);
108 19 50       454 return ($x, $y, $w, $h);
109 19 100       253 }
    100          
110 19 100       594 method parse_position (ArrayRef $position, Bool $xy?) {
111 19 50       385 my ($x, $y, $w, $h) = map {
  2         10  
112 2   33     19 $_ =~ m/[^\d\.]/ ? $_ : $_/mm
  8         1161  
  8         36  
  8         14  
113 8         58 } @{$position};
  2         11  
114 2   33     11 my $page = $self->can('file') ? $self->file->page : $self;
  3         76  
  3         11  
  3         4  
115 3         12 $x = $page->x + $self->padding/mm unless defined $x;
116 3         6 $y = $page->y - $self->padding/mm unless defined $y;
117 3 100 66     48 $y = $page->y if $y =~ m/current/;
    50 50        
118 2         74 $w = $page->w - ($self->padding ? ($x + $self->padding/mm) : 0) unless defined $w;
119 1         75 $h = $y - ($self->padding + $page->padding)/mm unless defined $h;
120 1         7 return $xy ? ($x, $y) : ($x, $y, $w, $h);
  1         13  
121 1 50       7 }
122             method valid_colour (Str $css) {
123             return Colouring::In->new($css)->toHEX(1);
124 0         0 }
125 2     2   657341 method _recurse_find {
  2     2   5  
  2     2   19  
  2         11  
  2         162  
  2         4  
  2         9  
  2         303945  
  2         5  
  2         24  
  2         12  
  0         0  
126 2         10 my ($self, $check, $recurse, $val, @items) = @_;
  2         15  
127 2         14 for (@items) {
  2         18  
128 21 50 33     1499 if (defined $_->$check && $_->$check =~ $val) {
  21         70  
  21         51  
  21         59  
  21         30  
129 21   66     185 return $_;
  2         11  
130 2     2   94937 } elsif ($_->$recurse && scalar @{$_->$recurse}) {
  2         19  
  2         19  
  2         18  
  2         18  
131 2         13 my $val = $self->_recurse_find($check, $recurse, $val, @{$_->$recurse});
  2         32  
132 2         12 return $val if $val;
  2         18  
133 2         4 }
  2         14  
134 2         16 }
  2         65  
135 2         5 return undef;
  2         15  
136 7 50       1325 }
  7         24  
  7         26  
  7         10  
137             }
138             class Plugin {
139             with Utils;
140             has file (type => Object);
141 7   50     102 method set_attrs (Map %args) {
      100        
142             $self->can($_) && $self->$_($args{$_}) for keys %args;
143             }
144 2         15 class +Font {
  2         15  
145 2   33     32 has colour (type => Str);
  2   33     18  
  11         875  
  11         34  
  11         23  
  11         158  
  11         1451  
  11         31  
  11         17  
146 11         150 has size (type => Num);
147 11 100       77 has family (type => Str);
148 7   50     99 has loaded (type => HashRef);
149 7         176136 has line_height ( type => Num);
150             factory font (Object $file, Map %args) {
151 11         278 return $class->new(
  2         3  
152 2     2   295689 file => $file,
  2     2   17  
  2     2   28  
  2         12  
  2         179  
  2         4  
  2         6  
  2         15160  
  2         4  
  2         18  
  2         18  
153 2         11 colour => $file->page->valid_colour($args{colour} || '#000'),
  2         29  
154 2         10 size => 9,
  2         31  
155 1 50       1226 line_height => $args{size} || 9,
  1         5  
  1         4  
  1         2  
156             family => 'Times',
157             %args
158             );
159 1   50     17 }
      50        
160 2         14 method load () { $self->find($self->family); }
161 2 50 33     26 method find (Str $family, Str $enc?) {
  2         987  
  2         9  
  2         9  
  2         9  
  2         4  
162 2         42 my $loaded = $self->loaded;
163 2         69 unless ($loaded->{$family}) {
164 2   50     671 $loaded->{$family} = $self->file->pdf->corefont($family, -encoding => $enc || 'latin1');
165 2         352 $self->loaded($loaded);
166 2         280 }
167 2         103 return $loaded->{$family};
  2         4  
168 2     2   221020 }
  2     2   4  
  2     2   28  
  2         18  
  2         174  
  2         2  
  2         8  
  2         129341  
  2         6  
  2         27  
  2         20  
169 2         12 }
  2         31  
170 2         12 class +Boxed {
  2         18  
171 2         4 has fill_colour ( type => Str );
  2         15  
172 2         6 has position ( type => ArrayRef );
  2         14  
173 2         11 factory boxed (Object $file, Map %args) {
  2         14  
174 2         4 return $class->new(
  2         15  
175 2         4 file => $file,
  2         14  
176 2         5 fill_colour => $file->page->valid_colour($args{fill_colour} || '#fff'),
  2         16  
177 2         16 padding => $args{padding} || 0
  2         15  
178 2         4 );
  2         14  
179 2         10 }
  2         32  
180 2         7 method add (Map %args) {
  2         9  
181 1 50       1185 $self->set_attrs(%args);
  1         5  
  1         4  
  1         2  
182 1         20 my $box = $self->file->page->current->gfx;
  2         15  
183 2 50 33     29 my $boxed = $box->rect($self->parse_position($self->position || [0, 0, $self->file->page->w * mm, $self->file->page->h * mm]));
  4         1303  
  4         14  
  4         13  
  4         20  
  4         17  
184             $boxed->fillcolor($self->fill_colour);
185             $boxed->fill;
186 4         23 return $self->file;
187 0   0     0 }
  0         0  
  0         0  
188 0         0 }
189 0         0 class +Text {
190 0         0 has font (type => Object);
191 4         48 has paragraph_space (type => Num);
192             has first_line_indent (type => Num);
193             has first_paragraph_indent (type => Num);
194             has align (type => Str); #enum
195             has margin_top (type => Num);
196 4         728 has margin_bottom (type => Num);
197             has indent (type => Num);
198             has pad (type => Str);
199             has pad_end (type => Str);
200 4 50 50     64 has position (type => ArrayRef);
  28         3918  
201             has next_page;
202 2         15 factory text (Object $file, Map %args) {
203 2 50 33     44 $class->generic_new($file, %args);
  11         1560  
  11         54  
  11         29  
  11         33  
  11         14  
204 11         185 }
205 11         20 method generic_new (Object $file, Map %args) {
206 11         41 return $class->new({
207 11         185 file => $file,
208 11         3764 page => $file->page,
209 11         2713 next_page => do { method {
210 11         1866 my $self = shift;
211 11         208 $file->add_page;
212             return $file->page;
213             } },
214             padding => 0,
215 11         25 align => 'left',
216 11 50       169 font => $class->FACTORY->font(
217 11   50     117 $file,
218             %{$args{font}}
219 11         34 ),
220 22 100       50 position => $args{position} || [],
221 11 50       28 (map {
222 0         0 $args{$_} ? ( $_ => $args{$_} ) : ()
223 0 0       0 } qw/margin_bottom margin_top indent align padding pad pad_end/)
224 0 0       0 });
225 0         0 }
226             method add (Str $string, Map %args) {
227 11         24 $self->set_attrs(%args);
228 11         204 my ($xpos, $ypos);
229 11   66     70 my @paragraphs = split /\n/, $string;
230 42         59 my $text = $self->file->page->current->text;
231 42         115 $text->font( $self->font->load, $self->font->size/pt );
232             $text->fillcolor( $self->font->colour );
233 11         18 my ($total_width, $space_width, %width) = $self->_calculate_widths($string, $text);
234 11 50 33     172 my ($l, $x, $y, $w, $h) = (
      33        
235 0 0       0 $self->font->line_height/pt,
236 0         0 $self->parse_position($self->position)
237             );
238 0         0 $ypos = $y - $l;
239 0         0 $ypos -= $self->margin_top/mm if $self->margin_top;
240             my ($fl, $fp, @paragraph) = (1, 1, split ( / /, shift(@paragraphs) || '' ));
241 11 50       388 # while we have enough height to add a new line
242 11         209 while ($ypos >= $y - $h) {
243             unless (@paragraph) {
244 11         27 last unless scalar @paragraphs;
245 11 50       29 @paragraph = split( / /, shift(@paragraphs) );
246 0         0 $ypos -= $self->paragraph_space/mm if $self->paragraph_space;
247 0         0 last unless $ypos >= $y - $h;
248 0         0 ($fl, $fp) = (1, 0);
249 0 0       0 }
250             my ($xpos, $lw, $line_width, @line) = ($x, $w, 0);
251             ($xpos, $lw) = $self->_set_indent($xpos, $lw, $fl, $fp);
252 11 50       47 while (@paragraph and ($line_width + (scalar(@line) * $space_width) + $width{$paragraph[0]}) < $lw) {
    50          
253 0         0 $line_width += $width{$paragraph[0]};
254             push @line, shift(@paragraph);
255 0         0 }
256             my ($wordspace, $align);
257 11         50 if ($self->align eq 'fulljustify' or $self->align eq 'justify' and @paragraph) {
258 11         5284 if (scalar(@line) == 1) {
259             @line = split( //, $line[0] );
260 11 50       3729 }
    100          
261 0 0       0 $wordspace = ($lw - $line_width) / (scalar(@line) - 1);
262             $align = 'justify';
263 3         64 } else {
264 3         53 $align = ($self->align eq 'justify') ? 'left' : $self->align;
265             $wordspace = $space_width;
266             }
267             $line_width += $wordspace * (scalar(@line) - 1);
268             if ($align eq 'justify') {
269             foreach my $word (@line) {
270 3         547 $text->translate($xpos, $ypos);
271 3         3332 $text->text($word);
272             $xpos += ($width{$word} + $wordspace) if (@line);
273 11         2358 }
274             } else {
275 11 50       25 if ($align eq 'right') {
276 11 50       193 $xpos += $lw - $line_width;
277 11         194 } elsif ($align eq 'center') {
278 11 50 33     437 $xpos += ($lw/2) - ($line_width / 2);
279 0         0 }
280 0         0 $text->translate($xpos, $ypos);
281             $text->text(join(' ', @line));
282 11         166 }
  2         21  
283 2   33     24 if (@paragraph) {
  11         2122  
  11         35  
  11         20  
284 11 50 33     181 $ypos -= $l if @paragraph;
    50 33        
    100          
285 0         0 } elsif ($self->pad) {
286 0         0 my $pad_end = $self->pad_end;
287             my $pad = sprintf ("%s%s",
288 0         0 $self->pad x int(((
289 0         0 (((($lw + $wordspace) - $line_width) - $text->advancewidth($self->pad . $pad_end)) - ($self->padding/mm))
290             ) / $text->advancewidth($self->pad))),
291 2         116 $pad_end
292 2         36 );
293             $text->translate($xpos + ( $lw - $text->advancewidth($pad) ), $ypos);
294 11         423 $text->text($pad);
  2         9  
295 2   33     14 }
  11         1597  
  11         34  
  11         21  
296 11         55 $fl = 0;
297             }
298 11         50 unshift( @paragraphs, join( ' ', @paragraph ) ) if scalar(@paragraph);
299             $ypos -= $self->margin_bottom/mm if $self->margin_bottom;
300 11         843 $self->file->page->y($ypos);
301 11         20 if (scalar @paragraphs && $self->next_page) {
302 11         20 my $next_page = $self->next_page->($self);
303 42 50       86 return $self->add(join("\n", @paragraphs), %args);
304 42         75 }
305 42         3717 return $self->file;
306             }
307 11         72 method _set_indent (Num $xpos, Num $w, Num $fl, Num $fp) {
  2         2  
308 2     2   960949 if ($fl && $self->first_line_indent) {
  2     2   6  
  2     2   36  
  2         12  
  2         214  
  2         4  
  2         7  
  2         5134  
  2         5  
  2         11  
  2         19  
309 2         9 $xpos += $self->first_line_indent/mm;
  2         20  
310 1 50       1121 $w -= $self->first_line_indent/mm;
  1         7  
  1         4  
  1         2  
311 1   50     8 } elsif ($fp && $self->first_paragraph_indent) {
312 1   50     6 $xpos += $self->first_paragraph_indent/mm;
313 1         30 $w -= $self->first_paragraph_indent/mm;
  2         3  
314 2     2   211544 } elsif ($self->indent) {
  2     2   4  
  2     2   27  
  2         24  
  2         182  
  2         5  
  2         7  
  2         4734  
  2         4  
  2         12  
  2         19  
315 2         8 $xpos += $self->indent/mm;
  2         20  
316 1 50       1163 $w -= $self->indent/mm
  1         4  
  1         4  
  1         3  
317 1   50     7 }
318 1   50     7 return ($xpos, $w);
319 1         27 }
  2         3  
320 2     2   139647 method _calculate_widths (Str $string, Object $text) {
  2     2   5  
  2     2   29  
  2         22  
  2         190  
  2         4  
  2         8  
  2         4689  
  2         4  
  2         12  
  2         18  
321 2         10 my @words = split /\s+/, $string;
  2         18  
322 1 50       1311 # calculate width of space
  1         4  
  1         5  
  1         2  
323 1   50     8 my $space_width = $text->advancewidth(' ');
324 1   50     6 # calculate the width of each word
325 1         28 my %width = ();
  2         3  
326 2     2   140909 my $total_width = 0;
  2     2   4  
  2     2   28  
  2         23  
  2         184  
  2         4  
  2         7  
  2         51308  
  2         19  
  2         22  
  2         19  
327 2         9 foreach (@words) {
  2         29  
328 2         10 next if exists $width{$_};
  2         28  
329 2         11 $width{$_} = $text->advancewidth($_);
  2         18  
330 2         4 $total_width += $width{$_} + $space_width;
  2         14  
331 2         11 }
  2         15  
332 2         5 return ($total_width, $space_width, %width);
  2         13  
333 2         5 }
  2         13  
334 2         9 }
  2         32  
335 3 50       1560 class +Title {
  3         11  
  3         14  
  3         6  
336 3   50     47 extends Plugin::Text;
337 3         10 factory title (Object $file, Map %args) {
338             $args{font}->{size} ||= 50/pt;
339             $args{font}->{line_height} ||= 40/pt;
340 3         15 $class->generic_new($file, %args);
341             }
342             }
343             class +Subtitle {
344             extends Plugin::Text;
345             factory subtitle (Object $file, Map %args) {
346             $args{font}->{size} ||= 25;
347             $args{font}->{line_height} ||= 20;
348             $class->generic_new($file, %args);
349             }
350             }
351             class +Subsubtitle {
352 3         119 extends Plugin::Text;
353             factory subsubtitle (Object $file, Map %args) {
354             $args{font}->{size} ||= 20;
355             $args{font}->{line_height} ||= 15;
356             $class->generic_new($file, %args);
357             }
358             }
359             class +TOC::Outline {
360             extends Plugin::Text;
361             has outline (type => Object);
362             has x (type => Num);
363             has y (type => Num);
364             has title (type => Str);
365 3 50 100     867 has page (type => Object);
  15   50     547  
      33        
      50        
      50        
      50        
366             has level (type => Num);
367 2         17 has children (type => ArrayRef);
368 2 50 33     27 factory add_outline (Object $file, Object $outline, Map %args) {
  3         945  
  3         13  
  3         10  
  3         12  
  3         6  
369 3         57 my ($x, $y) = $file->page->parse_position($args{position} || []);
370 3         49 $y += $args{jump_lh};
371 3         343 my $new = $outline->outline()->open()
372 3         63 ->title($args{title})
373 3         404 ->dest($file->page->current, '-xyz' => [$x, $y, 0]);
374             return $class->new(
375             x => $x,
376 3         3467 y => $y,
  3         53  
377 2         46 children => [],
378             level => $args{level} || 0,
379 2     2   334301 title => $args{title},
  2     2   5  
  2     2   51  
  2         20  
  2         181  
  2         3  
  2         7  
  2         68752  
  2         4  
  2         24  
  2         17  
380 2         12 file => $file,
  2         30  
381 2         22 page => $file->page,
  2         31  
382 2         11 outline => $new,
  2         17  
383 2         9 font => $class->FACTORY->font(
  2         28  
384 2         6 $file,
  2         17  
385 2         4 %{$args{font}}
  2         22  
386 2         6 ),
  2         13  
387 2         5 pad => $args{pad} || '.',
  2         23  
388 1 50       1264 next_page => $args{next_page} || do { method {
  1         5  
  1         4  
  1         2  
389             my $self = shift;
390             $file->add_page(open => 1);
391             $file->page->set_position($file->toc->parse_position([]));
392             return $file->page;
393             } },
394             padding => $args{padding} || 0,
395             align => $args{align} || 'left',
396             position => $args{position} || [],
397             (map {
398 1 50 50     18 $args{$_} ? ( $_ => $args{$_} ) : ()
      50        
      50        
399 2         16 } qw/margin_bottom margin_top indent align pad_end/)
400 2 50 33     28 );
  1         995  
  1         4  
  1         3  
  1         12  
  1         3  
401 1         46 }
402 1 50       20 method render (Map %args) {
  0         0  
403             $self->set_attrs(%args);
404             $self->pad_end($self->page->num + $args{page_offset});
405 1   50     23 $self->add($self->title);
406             my ($x, $y, $w) = ($self->file->page->x, $self->file->page->y, $self->file->page->w);
407 1         52 my $annotation = $self->file->page->current->annotation()->rect(
408 1         24 $x, $y + 3.5, $w, $y - 3.5
409 1         15 )->link($self->page->current, -xyz => [$self->x, $self->y, 0]);
  2         17  
410 2 50 33     19 for (@{$self->children}) {
  3         919  
  3         10  
  3         11  
  3         9  
  3         5  
411 3         47 $_->render(%args);
412 3         50 }
413 3         43 }
414 3         7 }
415 3         4 class +TOC {
  3         43  
416 6 100       27 has count (type => Num);
417 3 50       12 has toc_placeholder (type => HashRef);
  0         0  
418 3         5 has outline (type => Object);
419 3   66     12 has outlines (type => ArrayRef);
420 3         40 has indent (type => Num);
421 3         70 has levels (type => ArrayRef);
422             has toc_line_offset (type => Num);
423 3         5 has font (type => HashRef);
424             factory toc (Object $file, Map %args) {
425 3   33     57 return $class->new(
426 3         21 file => $file,
427 3 100       12 outline => $file->pdf->outlines()->outline,
  2         65  
428 3 100       50 outlines => [],
429 3 100       7753 count => 0,
430 2         38 toc_line_offset => $args{toc_line_offset} || 0,
431 2         87 padding => $args{padding} || 0,
  2         28  
432             levels => [qw/title subtitle subsubtitle/],
433 1         2 indent => $args{indent} || 5,
  1         20  
434             ($args{font} ? (font => $args{font}) : ())
435 3         75 );
436 3         58 }
  2         10  
437 2 50 33     16 method placeholder (Map %args) {
  1         1183  
  1         5  
  1         3  
  1         4  
  1         3  
438 1         16 $self->set_attrs(%args);
439 1         17 $self->file->subtitle->add($args{title} ? @{$args{title}} : 'Table of contents');
440 1         7 $self->toc_placeholder({
  1         24  
441             page => $self->file->page,
442 1         3 position => [$self->parse_position($args{position} || [])]
443 1         15 });
444 1         75 $self->file->onsave('toc', 'render', %args);
445 1         11 $self->file->add_page;
446 0         0 return $self->file;
447 0         0 }
448 0         0 method add (Map %args) {
449             $self->set_attrs(%args);
450 1         14 $self->count($self->count + 1);
451 1         26 $args{level} = 0;
  1         13  
452 1         20 my ($text, %targs, $level);
453             for (@{$self->levels}) {
454 2     2   413057 if (defined $args{$_}) {
  2     2   3  
  2     2   26  
  2     2   15  
  2     2   171  
  2         4  
  2         6  
  2         105  
  2         3  
  2         10  
  2         110  
  2         4  
  2         6  
  2         27840  
  2         4  
  2         15  
455 2     2   1361 ($text, %targs) = ref $args{$_} ? @{$args{$_}} : $args{$_};
  2         369709  
  2         69  
  2         19  
456 1 50       14399 $level = $_;
  1         5  
  1         6  
  1         2  
457 1 50       8 $args{title} ||= $text;
  0         0  
458 1         21 $args{jump_lh} = $self->file->$level->font->line_height;
459 1         13 last;
460 7         16 }
461 7         13 $args{level}++;
462             }
463             $args{font} ||= $self->font;
464 18         610 my $outline;
465 18 100       117 $outline = $self->_recurse_find('level', 'children', $args{level} - 1, reverse @{$self->outlines}) if $args{level};
466 6         14 my $add = $self->FACTORY->add_outline($self->file, ($outline ? $outline->outline : $self->outline), %args);
  6         137  
467 6         30273 if ($outline) {
468             $add->indent($self->indent * $add->level);
469 18         359 push @{ $outline->children }, $add;
470             } else {
471 7         47 push @{ $self->outlines }, $add;
472             }
473             $self->file->$level->add($text, %targs);
474             return $self->file;
475             }
476             method render (Map %args) {
477             $self->set_attrs(%args);
478             my $placeholder = $self->toc_placeholder;
479             my ($x, $y, $w, $h) = $self->set_position(@{$placeholder->{position}});
480             # todo better
481             $args{page_offset} = 0;
482             my $one_toc_link = $self->outlines->[0]->font->size + $self->toc_line_offset/mm;
483             my $total_height = ($self->count * $one_toc_link) - $h;
484             while ($total_height > 0) {
485             $args{page_offset}++;
486             $self->file->add_page(num => $placeholder->{page}->num + $args{page_offset});
487             $total_height -= $self->file->page->h;
488             }
489             $self->file->page($placeholder->{page});
490 1   50     10 for my $outline (@{$self->outlines}) {
491             $outline->render(%args);
492 2     2   166219 }
  2     2   4  
  2         25  
  2         27  
  2         175  
  2         4  
  2         9  
493             }
494             }
495             }
496             class Factory {
497             use PDF::API2;
498             factory new_pdf (Str $name, Map %args) {
499             my @plugins = (qw/font boxed text title subtitle subsubtitle toc/, ($args{plugins} ? @{$args{plugins}} : ()));
500             my $spec = Mxpress::PDF::File->_generate_package_spec();
501             for my $p (@plugins) {
502             my $meth = sprintf('_store_%s', $p);
503             $spec->{has}->{$meth} = { type => Object };
504             $spec->{can}->{$p} = {
505             code => sub {
506             my $class = $_[0]->$meth;
507             if (!$class) {
508             $class = $factory->$p($_[0], %{$args{$p}});
509             $_[0]->$meth($class)
510             }
511             return $class;
512             }
513             };
514             }
515             return MooX::Press->generate_package(
516             'class',
517             "Mxpress::PDF::File",
518             {
519             factory_package => $factory,
520             caller => $class,
521             prefix => $factory,
522             toolkit => 'Moo',
523             type_library => 'Mxpress::PDF::Types',
524             },
525             $spec
526             )->new(
527             file_name => $name,
528             pages => [],
529             num => 0,
530             page_size => 'A4',
531             page_args => $args{page} || {},
532             pdf => PDF::API2->new( -file => sprintf("%s.pdf", $name)),
533             );
534             }
535             }
536             }
537              
538             # probably should dry-run to calculate positions
539              
540             1;
541              
542             __END__
543              
544             =head1 NAME
545              
546             Mxpress::PDF - PDF
547              
548             =head1 VERSION
549              
550             Version 0.02
551              
552             =cut
553              
554             =head1 SYNOPSIS
555              
556             use Mxpress::PDF;
557              
558             my $pdf = Mxpress::PDF->new_pdf('test-pdf',
559             page => {
560             background => '#000',
561             padding => 5
562             },
563             toc => {
564             font => { colour => '#00f' },
565             },
566             title => {
567             font => { colour => '#f00' },
568             },
569             subtitle => {
570             font => { colour => '#0ff' },
571             },
572             subsubtitle => {
573             font => { colour => '#f0f' },
574             },
575             text => {
576             font => { colour => '#fff' },
577             },
578             )->add_page->title->add(
579             'This is a title'
580             )->toc->placeholder->toc->add(
581             title => 'This is a title'
582             )->text->add(
583             'Add some text.'
584             )->toc->add(
585             subtitle => 'This is a subtitle'
586             )->text->add(
587             'Add some more text.'
588             )->toc->add(
589             subsubtitle => 'This is a subsubtitle'
590             )->text->add(
591             'Add some more text.'
592             )->save;
593              
594             =head2 Note
595              
596             experimental.
597              
598             =head1 AUTHOR
599              
600             LNATION, C<< <thisusedtobeanemail at gmail.com> >>
601              
602             =head1 BUGS
603              
604             Please report any bugs or feature requests to C<bug-mxpress-pdf at rt.cpan.org>, or through
605             the web interface at L<https://rt.cpan.org/NoAuth/ReportBug.html?Queue=Mxpress-PDF>. I will be notified, and then you'll
606             automatically be notified of progress on your bug as I make changes.
607              
608             =head1 SUPPORT
609              
610             You can find documentation for this module with the perldoc command.
611              
612             perldoc Mxpress::PDF
613              
614             You can also look for information at:
615              
616             =over 4
617              
618             =item * RT: CPAN's request tracker (report bugs here)
619              
620             L<https://rt.cpan.org/NoAuth/Bugs.html?Dist=Mxpress-PDF>
621              
622             =item * AnnoCPAN: Annotated CPAN documentation
623              
624             L<http://annocpan.org/dist/Mxpress-PDF>
625              
626             =item * CPAN Ratings
627              
628             L<https://cpanratings.perl.org/d/Mxpress-PDF>
629              
630             =item * Search CPAN
631              
632             L<https://metacpan.org/release/Mxpress-PDF>
633              
634             =back
635              
636             =head1 ACKNOWLEDGEMENTS
637              
638             =head1 LICENSE AND COPYRIGHT
639              
640             This software is Copyright (c) 2020 by LNATION.
641              
642             This is free software, licensed under:
643              
644             The Artistic License 2.0 (GPL Compatible)
645              
646             =cut
647              
648             1; # End of Mxpress::PDF