| 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 |