File Coverage

blib/lib/PDF/Imposition/Schema.pm
Criterion Covered Total %
statement 152 159 95.6
branch 44 64 68.7
condition 10 17 58.8
subroutine 28 29 96.5
pod 9 9 100.0
total 243 278 87.4


line stmt bran cond sub pod time code
1             package PDF::Imposition::Schema;
2 9     9   60496 use strict;
  9         11  
  9         233  
3 9     9   31 use warnings;
  9         9  
  9         226  
4              
5 9     9   36 use File::Basename qw/fileparse/;
  9         9  
  9         421  
6 9     9   35 use File::Spec;
  9         9  
  9         180  
7 9     9   504 use PDF::API2;
  9         157316  
  9         147  
8 9     9   33 use File::Temp ();
  9         6  
  9         105  
9 9     9   454 use File::Copy;
  9         1707  
  9         389  
10 9     9   37 use POSIX ();
  9         15  
  9         155  
11 9     9   27 use Types::Standard qw/Int Bool Num HashRef Str Maybe Object Enum/;
  9         12  
  9         54  
12              
13 9     9   6928 use Moo::Role;
  9         13  
  9         46  
14             requires qw(_do_impose);
15              
16 9     9   2237 use constant { DEBUG => $ENV{AMW_DEBUG} };
  9         12  
  9         13816  
17              
18             =head1 NAME
19              
20             PDF::Imposition::Schema - Role for the imposition schemas.
21              
22             =head1 SYNOPSIS
23              
24             This class provides the shared method for real imposition schemas and
25             can't me called directly.
26              
27             Consuming classes must provide a C<_do_impose> method.
28              
29             use PDF::Imposition;
30             my $imposer = PDF::Imposition->new(file => "test.pdf",
31             # either use
32             outfile => "out.pdf",
33             # or suffix
34             suffix => "-2up"
35             );
36             $imposer->impose;
37             or
38              
39             use PDF::Imposition;
40             my $imposer = PDF::Imposition->new();
41             $imposer->file("test.pdf");
42            
43             $imposer->outfile("out.pdf");
44             # or
45             $imposer->suffix("-imp");
46              
47             $imposer->impose;
48            
49             =head1 METHODS
50              
51             =head2 Read/write accessors
52              
53             All the following accessors accept an argument, which sets the
54             value.
55              
56             =head3 file
57              
58             Unsurprisingly, the input file, which must exist.
59              
60             =cut
61              
62             has _version => (is => 'ro',
63             isa => Str,
64             default => sub { 'PDF::Imposition' });
65              
66             has file => (is => 'rw',
67             isa => sub { die "$_[0] is not a pdf" unless $_[0] && $_[0] =~ m/\.pdf\z/i });
68              
69             has _tmp_dir => (is => 'ro',
70             default => sub { File::Temp->newdir(CLEANUP => 1); });
71              
72             =head3 outfile
73              
74             The destination file of the imposition. You may prefer to use the
75             suffix method below, which takes care of the filename.
76              
77             =head3 suffix
78              
79             The suffix of the file. By default, '-imp', so test.pdf imposed will
80             be saved as 'test-imp.pdf'. If test-imp.pdf already exists, it will be
81             replaced merciless.
82              
83             =cut
84              
85             has outfile => (is => 'rw',
86             isa => sub { die "$_[0] is not a pdf file"
87             unless $_[0] && $_[0] =~ m/\.pdf\z/i });
88              
89             has suffix => (is => 'rw',
90             isa => Str,
91             default => sub { '-imp' });
92              
93             =head3 signature($num_or_range)
94              
95             The signature, must be a multiple of the C option
96             (usually 4 or 8), or a range, like the string "20-100". If a range is
97             selected, the signature is determined heuristically to minimize the
98             white pages left on the last signature. The wider the range, the better
99             the results.
100              
101             =cut
102              
103             has signature => (is => 'rw',
104             isa => Str,
105             default => sub { '0' });
106              
107             =head3 pages_per_sheet
108              
109             The number of logical pages which fit on a sheet, recto-verso. Default
110             to 1. Subclasses usually change this and ignore your option unless
111             otherwise specified.
112              
113             =head3 title
114              
115             The title to set in the PDF meta information. Defaults to the basename.
116              
117             =cut
118              
119             has pages_per_sheet => (is => 'ro',
120             default => sub { 1 },
121             isa => Enum[qw/1 2 4 8 16 32/]);
122              
123             sub _optimize_signature {
124 475     475   405514 my ($self, $sig, $total_pages) = @_;
125 475 100       996 unless ($total_pages) {
126 25         326 $total_pages = $self->total_pages;
127             }
128 470 50       917 return 0 unless $sig;
129 470 50       1010 my $ppsheet = $self->pages_per_sheet or die;
130 470         328 print "# pages per sheet is $ppsheet\n" if DEBUG;
131 470 100       1099 if ($sig =~ m/^[0-9]+$/s) {
132 17 50       47 die "Signature must be a multiple of $ppsheet" if $sig % $ppsheet;
133 17         109 return $sig;
134             }
135 453         326 my ($min, $max);
136 453 50       1405 if ($sig =~ m/^([0-9]+)?-([0-9]+)?$/s) {
137 453   66     1094 $min = $1 || $ppsheet;
138 453   33     774 $max = $2 || $total_pages;
139 453         669 $min = $min + (($ppsheet - ($min % $ppsheet)) % $ppsheet);
140 453         399 $max = $max + (($ppsheet - ($max % $ppsheet)) % $ppsheet);
141 453 50       619 die "Bad range $max - $min" unless $max > $min;
142 453 50       634 die "bad min $min" if $min % $ppsheet;
143 453 50       649 die "bad max $max" if $max % $ppsheet;
144             }
145             else {
146 0         0 die "Unrecognized range $sig";
147             }
148 453         368 my $signature = 0;
149 453         424 my $roundedpages = $total_pages + (($ppsheet - ($total_pages % $ppsheet)) % $ppsheet);
150 453         317 my $needed = $roundedpages - $total_pages;
151 453 50       582 die "Something is wrong" if $roundedpages % $ppsheet;
152 453 100       603 if ($roundedpages <= $min) {
153 90 100       215 wantarray ? return ($roundedpages, $needed) : return $roundedpages;
154             }
155 363         514 $signature = $self->_find_signature($roundedpages, $max);
156 363 100       530 if ($roundedpages > $max) {
157 340         471 while ($signature < $min) {
158 344         230 $roundedpages += $ppsheet;
159 344         209 $needed += $ppsheet;
160 344         359 $signature = $self->_find_signature($roundedpages, $max)
161             }
162             }
163             # warn "Needed $needed blank pages";
164 363 100       898 wantarray ? return ($signature, $needed) : return $signature;
165             }
166              
167             sub _find_signature {
168 707     707   647 my ($self, $num, $max) = @_;
169 707 50       1075 my $ppsheet = $self->pages_per_sheet or die;
170 707 50       962 die "not a multiple of $ppsheet" if $num % $ppsheet;
171 707 50       845 die "uh?" unless $num;
172 707         489 my $i = $max;
173 707         847 while ($i > 0) {
174             # check if the the pagenumber is divisible by the signature
175             # with modulo 0
176             # warn "trying $i for $num / max $max\n";
177 5528 100       5931 if (($num % $i) == 0) {
178 707         1213 return $i;
179             }
180 4821         5075 $i -= $ppsheet;
181             }
182 0         0 warn "_find_signature loop ended with no result\n";
183             }
184              
185              
186              
187             =head2 Internal methods accessors
188              
189             The following methods are used internally but documented for schema's
190             authors.
191              
192             =head3 dimensions
193              
194             Returns an hashref with the original pdf dimensions in points.
195              
196             { w => 800, h => 600 }
197              
198             =head3 orig_width
199              
200             =head3 orig_height
201              
202             =head3 total_pages
203              
204             Returns the number of pages
205              
206              
207             =cut
208              
209             has dimensions => (is => 'lazy',
210             isa => HashRef[Num]);
211              
212             sub _build_dimensions {
213 61     61   531 my $self = shift;
214 61         817 my $pdf = $self->in_pdf_obj;
215 61         527 my ($x, $y, $w, $h) = $pdf->openpage(1)->get_mediabox; # use the first page
216 61 50       164367 warn $self->file . "use x-y offset, cannot proceed safely" if ($x + $y);
217 61 50 33     429 die "Cannot retrieve paper dimensions" unless $w && $h;
218 61         1450 my %dimensions = (
219             w => sprintf('%.2f', $w),
220             h => sprintf('%.2f', $h),
221             );
222             # return a copy
223 61         1400 return \%dimensions;
224             }
225              
226             has total_pages => (is => 'lazy',
227             isa => Int);
228              
229             sub _build_total_pages {
230 94     94   919 my $self = shift;
231 94         1249 my $count = $self->in_pdf_obj->pages;
232 89         2393 return $count;
233             }
234              
235             sub orig_width {
236 460     460 1 11585 return shift->dimensions->{w};
237             }
238              
239             sub orig_height {
240 288     288 1 7692 return shift->dimensions->{h};
241             }
242              
243              
244              
245             =head3 in_pdf_obj
246              
247             Internal usage. It's the PDF::API2 object used as source.
248              
249             =head3 out_pdf_obj
250              
251             Internal usage. The PDF::API2 object used as output.
252              
253             =cut
254              
255             has in_pdf_obj => (is => 'lazy',
256             isa => Maybe[Object]);
257              
258             has _in_pdf_object_is_open => (is => 'rw', isa => Bool);
259              
260             sub _build_in_pdf_obj {
261 94     94   767 my $self = shift;
262 94         122 my $input;
263 94 100       1201 if ($self->file) {
264 89 50       1401 die "File " . $self->file . " doesn't exists" unless -f $self->file;
265 89         1376 print $self->file . ": building in_pdf_obj\n" if DEBUG;
266 89         1289 my ($basename, $path, $suff) = fileparse($self->file, qr{\.pdf}i);
267 89         3737 my $tmpfile = File::Spec->catfile($self->_tmp_dir,
268             $basename . $suff);
269 89 50       2187 copy($self->file, $tmpfile) or die "copy to $tmpfile failed $!";
270              
271 89         27979 eval {
272 89         658 $input = PDF::API2->open($tmpfile);
273             };
274 89 50       2052663 if ($@) {
275 0         0 die "Couldn't open $tmpfile $@";
276             }
277             else {
278 89         189 print "$tmpfile built\n" if DEBUG;
279             }
280 89         2733 $self->_in_pdf_object_is_open(1);
281             }
282 94         3571 return $input;
283             }
284              
285             has out_pdf_obj => (is => 'lazy',
286             isa => Maybe[Object]);
287              
288             has _out_pdf_object_is_open => (is => 'rw', isa => Bool);
289              
290             has title => (is => 'ro',
291             isa => Maybe[Str]);
292              
293             sub _build_out_pdf_obj {
294 62     62   785 my $self = shift;
295 62         82 my $pdf;
296 62 50       851 if ($self->file) {
297 62 100       1013 die "File " . $self->file . " is not a file" unless -f $self->file;
298 61         1057 print $self->file . ": building out_pdf_object\n" if DEBUG;
299 61         494 $pdf = PDF::API2->new();
300 61         31459 my %info = (
301             $self->in_pdf_obj->info,
302             Creator => $self->_version,
303             CreationDate => $self->_orig_file_timestamp,
304             ModDate => $self->_now_timestamp,
305             );
306 61         293 $pdf->info(%info);
307 61         6828 $self->_out_pdf_object_is_open(1);
308             }
309 61         2239 return $pdf;
310             }
311              
312             =head3 get_imported_page($pagenumber)
313              
314             Retrieve the page form object from the input pdf to the output pdf,
315             and return it. The method return undef if the page is out of range.
316              
317             =cut
318              
319              
320             sub get_imported_page {
321 904     904 1 1041 my ($self, $page) = @_;
322 904 50 66     20357 if ((!defined $page) || ($page <= 0) || ($page > $self->total_pages)) {
      66        
323 95         193 return undef;
324             }
325 809         15526 return $self->out_pdf_obj->importPageIntoForm($self->in_pdf_obj, $page)
326             }
327              
328             =head3 impose
329              
330             Do the job and leave the output in C<< $self->outfile >>, cleaning up
331             the internal objects.
332              
333             =cut
334              
335             sub impose {
336 62     62 1 114 my $self = shift;
337 62         230 my $out = $self->output_filename;
338 62         274 $self->_do_impose;
339 61         12577 $self->out_pdf_obj->saveas($out);
340 61         2831671 $self->out_pdf_obj->end;
341 61         1779 $self->_out_pdf_object_is_open(0);
342 61         2585 $self->in_pdf_obj->end;
343 61         430572 $self->_in_pdf_object_is_open(0);
344 61         2417 $self->outfile($out);
345 61         762 return $out;
346             }
347              
348             =head3 output_filename
349              
350             If outfile is not provided, use the suffix provided and return the
351             filename.
352              
353             =cut
354              
355             sub output_filename {
356 67     67 1 596 my $self = shift;
357 67         1474 my $out = $self->outfile;
358 67 100       517 unless ($out) {
359 41         595 my ($name, $path, $suffix) = fileparse($self->file, qr{\.pdf}i);
360 41 50       1296 die $self->file . " has a suffix not recognized" unless $suffix;
361 41         636 $out = File::Spec->catfile($path, $name . $self->suffix . $suffix);
362             }
363 67         571 return $out;
364             }
365              
366              
367             sub _orig_file_timestamp {
368 61     61   34136 my $self = shift;
369 61         1213 my $mtime = (stat($self->file))[9];
370 61         2282 return $self->_format_timestamp($mtime);
371             }
372              
373             sub _now_timestamp {
374 61     61   223 return shift->_format_timestamp(time());
375             }
376              
377             sub _format_timestamp {
378 122     122   180 my ($self, $epoc) = @_;
379 122         8390 return POSIX::strftime(q{%Y%m%d%H%M%S+00'00'}, localtime($epoc));
380             }
381              
382             =head3 computed_signature
383              
384             Return the actual number of signature, resolving 0 to the nearer
385             signature.
386              
387             =head3 total_output_pages
388              
389             Return the computed number of pages of the output, taking in account
390             the signature handling.
391              
392             =head3 DEMOLISH
393              
394             Object cleanup.
395              
396             =cut
397              
398             sub computed_signature {
399 129     129 1 12145 my $self = shift;
400 129   100     1847 my $signature = $self->signature || 0;
401 129 100       946 if ($signature) {
402 25         297 return $self->_optimize_signature($self->signature) + 0;
403             }
404             else {
405 104         1395 my $pages = $self->total_pages;
406 104         1425 my $ppsheet = $self->pages_per_sheet;
407 104         724 return $pages + (($ppsheet - ($pages % $ppsheet)) % $ppsheet);
408             }
409             }
410              
411             sub total_output_pages {
412 44     44 1 132 my $self = shift;
413 44         573 my $pages = $self->total_pages;
414 44         195 my $signature = $self->computed_signature;
415 44         127 return $pages + (($signature - ($pages % $signature)) % $signature);
416             }
417              
418             =head3 cropmarks_options
419              
420             By default, cropmarks are centered and twoside is true.
421              
422             =cut
423              
424              
425             sub cropmarks_options {
426 0     0 1 0 my %options = (
427             top => 1,
428             bottom => 1,
429             inner => 1,
430             outer => 1,
431             twoside => 1,
432             );
433 0         0 return %options;
434             }
435              
436             sub DEMOLISH {
437 114     114 1 499263 my $self = shift;
438 114 50       1761 if ($self->_out_pdf_object_is_open) {
439 0         0 print $self->file . ": closing outpdf object\n" if DEBUG;
440 0         0 $self->out_pdf_obj->end;
441             }
442 114 100       2302 if ($self->_in_pdf_object_is_open) {
443 28         126 print $self->file . ": closing inpdf object\n" if DEBUG;
444 28         361 $self->in_pdf_obj->end;
445             }
446             }
447              
448             1;
449              
450             =head1 SEE ALSO
451              
452             L
453              
454             =cut
455              
456