File Coverage

blib/lib/PDF/Collage/Template.pm
Criterion Covered Total %
statement 162 215 75.3
branch 19 50 38.0
condition 10 30 33.3
subroutine 27 34 79.4
pod 1 1 100.0
total 219 330 66.3


line stmt bran cond sub pod time code
1             package PDF::Collage::Template;
2 2     2   26 use v5.24;
  2         8  
3 2     2   12 use warnings;
  2         4  
  2         181  
4             { our $VERSION = '0.003' }
5              
6 2     2   13 use Carp;
  2         5  
  2         165  
7 2     2   12 use English;
  2         3  
  2         21  
8 2     2   2608 use Template::Perlish ();
  2         11512  
  2         70  
9 2     2   18 use Data::Resolver ();
  2         4  
  2         44  
10 2     2   3099 use PDF::Builder;
  2         844167  
  2         121  
11              
12 2     2   1633 use Moo;
  2         18103  
  2         28  
13 2     2   3676 use experimental qw< signatures >;
  2         4  
  2         20  
14 2     2   476 no warnings qw< experimental::signatures >;
  2         5  
  2         86  
15              
16 2     2   1204 use namespace::clean;
  2         35937  
  2         16  
17              
18             has commands => (is => ro => required => 1);
19             has functions => (is => 'lazy');
20             has logger => (is => 'lazy');
21             has metadata => (is => 'lazy');
22              
23             has _src_cache => (is => 'lazy');
24             has _data => (is => 'lazy');
25             has _defaults => (is => 'lazy');
26             has _fonts => (is => 'lazy');
27             has _pdf => (is => 'lazy');
28              
29 0     0   0 sub _build_functions ($self) { return {} }
  0         0  
  0         0  
  0         0  
30 0     0   0 sub _build_logger ($self) {
  0         0  
  0         0  
31 0         0 eval { require Log::Any; Log::Any->get_logger }
  0         0  
  0         0  
32             }
33 0     0   0 sub _build_metadata ($self) { return {} }
  0         0  
  0         0  
  0         0  
34 16     16   183 sub _build__src_cache ($self) { return {} }
  16         31  
  16         30  
  16         247  
35 0     0   0 sub _build__data ($self) { return {} }
  0         0  
  0         0  
  0         0  
36 17     17   176 sub _build__defaults ($self) { return {} }
  17         36  
  17         24  
  17         61  
37 16     16   179 sub _build__fonts ($self) { return {} }
  16         34  
  16         31  
  16         103  
38 16     16   193 sub _build__pdf ($self) { return PDF::Builder->new }
  16         29  
  16         27  
  16         116  
39              
40 17     17 1 6813 sub render ($self, $data) {
  17         44  
  17         36  
  17         30  
41 17         593 $self->new( # hand over to a disposable clone
42             commands => $self->commands,
43             functions => $self->functions,
44             _data => $data,
45             )->_real_render;
46             } ## end sub render
47              
48 17     17   940 sub _real_render ($self) {
  17         33  
  17         76  
49 17         94 for my $command ($self->commands->@*) {
50 66         530 my $op = $command->{op} =~ s{-}{_}rgmxs;
51 66 50       600 my $method = $self->can('_op_' . $op)
52             or croak "unsupported op<$command->{op}>";
53 66         300 $self->$method($command);
54             } ## end for my $command ($self->...)
55 16         819 return $self->_pdf;
56             } ## end sub _real_render
57              
58 193     193   290 sub _tpr ($self, $tmpl) {
  193         304  
  193         354  
  193         333  
59 193         5438 return Template::Perlish::render($tmpl, $self->_data,
60             {functions => $self->functions});
61             }
62              
63 49     49   106 sub _expand ($self, $command, @keys) {
  49         80  
  49         86  
  49         186  
  49         73  
64 49         125 my %auto_expand = map { $_ => 1 } @keys;
  276         725  
65 49         1975 my %overall = ($self->_defaults->%*, $command->%*);
66 49         847 my %retval;
67 49         377 for my $key (sort { $a cmp $b } keys %overall) {
  530         905  
68 307         327122 my $nkey = $key =~ s{-}{_}rgmxs;
69 307 50       893 next if exists $retval{$nkey};
70 307         569 my $value = $overall{$key};
71 307 100       1294 $retval{$nkey} = $auto_expand{$nkey} ? $self->_tpr($value) : $value;
72             } ## end for my $key (sort { $a ...})
73 48         49125 return \%retval;
74             } ## end sub _expand
75              
76 64 100   64   640 sub __pageno ($input) { return $input eq 'last' ? 0 : $input }
  64         158  
  64         135  
  64         469  
77              
78 0     0   0 sub __fc_list ($key) {
  0         0  
  0         0  
79 0         0 my @command = ('fc-list', $key, qw< file style >);
80 0 0       0 open my $fh, '-|', @command or croak "fc-list: $OS_ERROR";
81             my @candidates = map {
82 0         0 s{\s+\z}{}mxs;
  0         0  
83 0 0       0 my ($filename, $style) = m{\A (.*?): \s* :style=(.*)}mxs
84             or croak "fc-list: unexpected line '$_'";
85 0         0 my %style = map { $_ => 1 } split m{,}mxs, $style;
  0         0  
86 0         0 {filename => $filename, style => \%style};
87             } <$fh>;
88 0 0       0 return unless @candidates;
89 0 0       0 return $candidates[0]{filename} if @candidates == 1;
90              
91             # get Regular/Normal if exists
92 0         0 for my $candidate (@candidates) {
93             return $candidate->{filename}
94 0 0 0     0 if $candidate->{style}{Regular} || $candidate->{style}{Normal};
95             }
96              
97             # bail out, request more data
98 0         0 croak "fc-list: too many outputs for '$key'";
99             }
100              
101 16     16   51 sub _font ($s, $key) {
  16         29  
  16         37  
  16         31  
102 16 50       490 if (! defined($s->_fonts->{$key})) {
103 16 50       102 $key = $key =~ m{\A fc: (.*) \z}mxs ? __fc_list($1)
    50          
104             : $key =~ m{\A file: (.*) \z}mxs ? $1
105             : $key;
106 16         437 $s->_fonts->{$key} = $s->_pdf->font($key);
107             }
108 16         888636 return $s->_fonts->{$key};
109             }
110              
111 16     16   42 sub _op_add_image ($self, $command) {
  16         41  
  16         42  
  16         30  
112 16         122 my $opts = $self->_expand($command, qw< page path x y width height >);
113 16   50     546 my $page = $self->_pdf->open_page(__pageno($opts->{page} // 'last'));
114 16         1302 my $image = $self->_pdf->image($opts->{path});
115 16         596048 $page->object($image, $opts->@{qw< x y width height >});
116 16         17009 return;
117             } ## end sub _op_add_image
118              
119 16     16   35 sub __parse_pages ($input) {
  16         50  
  16         27  
120 16 50       54 return $input if ref($input); # already represented as an array
121             my @pages = map {
122 16         94 my ($from, $to) = split m{-}mxs, $_, 2;
  16         84  
123 16 50       91 defined($to) ? ($from .. $to) : $from;
124             } split m{[\s,]+}mxs, $input;
125 16         56 return \@pages;
126             }
127              
128 17     17   30 sub _op_add_page ($self, $command) {
  17         30  
  17         33  
  17         30  
129 17         79 my $opts =
130             $self->_expand($command, qw< page from from_path from_page >);
131 16   50     137 my $target_n = __pageno($opts->{page} // 'last');
132             defined(my $source_path = $opts->{from} // $opts->{from_path})
133 16 50 33     82 or return $self->_pdf->page($target_n);
134 16   33     476 my $source = $self->_src_cache->{$source_path}
135             //= PDF::Builder->open($source_path);
136              
137 16         206363 my $retval;
138 16   50     140 my $source_ns = __parse_pages($opts->{from_page} // 'last');
139 16         50 for my $sn ($source_ns->@*) {
140 16         73 my $source_n = __pageno($sn);
141 16         704 $retval = $self->_pdf->import_page($source, $source_n, $target_n);
142 16 50       381292 $target_n++ if $target_n; # only advance if not 0 = last
143             }
144 16         175 return $retval;
145             } ## end sub _op_add_page
146              
147 16     16   52 sub _op_add_text ($self, $command) {
  16         39  
  16         28  
  16         24  
148 16         73 my $opts =
149             $self->_expand($command, qw< align page font font_family font_size x y >);
150              
151             my $content =
152 16         119 $self->_render_text($opts->@{qw< text text_template text_var >});
153              
154 16   33     29302 my $font = $self->_font($opts->{font} // $opts->{font_family});
155 16         565 my $font_size = $opts->{font_size};
156              
157 16   50     88 my ($x, $y) = map { $_ // 0 } $opts->@{qw< x y >};
  32         164  
158              
159 16   50     109 my $align = $opts->{align} // 'start';
160 16 50       67 if ($align ne 'start') {
161 0         0 my $width = $font_size * $font->width($content);
162 0 0       0 $x -= $align eq 'end' ? $width : ($width / 2);
163             }
164              
165 16   50     451 my $page = $self->_pdf->open_page(__pageno($opts->{page} // 'last'));
166 16         981 my $text = $page->text;
167 16         8269 $text->position($x, $y);
168 16         2851 $text->font($font, $opts->{font_size});
169 16   50     4323 $text->text($content // '');
170              
171 16         279714 return $self;
172             } ## end sub _op_add_text
173              
174 16     16   44 sub _render_text ($self, $plain, $template, $crumbs) {
  16         47  
  16         36  
  16         37  
  16         31  
  16         31  
175 16 50       65 return $plain if defined $plain;
176 16 50       78 return $self->_tpr($template) if defined $template;
177 0 0 0     0 return Template::Perlish::traverse($self->_data, $crumbs) // ''
178             if defined $crumbs;
179 0         0 return;
180             } ## end sub _render_text
181              
182 17     17   46 sub _op_set_defaults ($self, $command) {
  17         30  
  17         28  
  17         40  
183 17         418 my $defaults = $self->_defaults;
184 17         104 while (my ($key, $value) = each $command->%*) {
185 51 100       209 next if $key eq 'op';
186 34 50       102 if (defined $value) { $defaults->{$key} = $value }
  34         160  
187 0         0 else { delete $defaults->{$key} }
188             }
189 17         47 return;
190             } ## end sub _op_set_defaults
191              
192 0     0     sub _default_log ($self, $command) {
  0            
  0            
  0            
193 0           warn "[$command->{level}] $command->{message}\n";
194 0           return $self;
195             }
196              
197 0     0     sub _op_log ($self, $command) {
  0            
  0            
  0            
198 0 0         my $logger = $self->logger or return $self->_default_log($command);
199 0 0 0       my $method = $logger->can(lc($command->{level}) // 'info')
200             or return $self->_default_log($command);
201 0           $logger->$method($command->{message});
202 0           return $self;
203             }
204              
205             1;