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   91350 use strict;
  9         24  
  9         300  
3 9     9   51 use warnings;
  9         21  
  9         303  
4              
5 9     9   57 use File::Basename qw/fileparse/;
  9         31  
  9         614  
6 9     9   62 use File::Spec;
  9         17  
  9         272  
7 9     9   765 use PDF::API2;
  9         228672  
  9         230  
8 9     9   57 use File::Temp ();
  9         19  
  9         201  
9 9     9   597 use File::Copy;
  9         2397  
  9         740  
10 9     9   69 use POSIX ();
  9         25  
  9         280  
11 9     9   54 use Types::Standard qw/Int Bool Num HashRef Str Maybe Object Enum/;
  9         18  
  9         102  
12              
13 9     9   13081 use Moo::Role;
  9         23  
  9         64  
14             requires qw(_do_impose);
15              
16 9     9   3852 use constant { DEBUG => $ENV{AMW_DEBUG} };
  9         23  
  9         21057  
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   576806 my ($self, $sig, $total_pages) = @_;
125 475 100       1404 unless ($total_pages) {
126 25         408 $total_pages = $self->total_pages;
127             }
128 470 50       1405 return 0 unless $sig;
129 470 50       1452 my $ppsheet = $self->pages_per_sheet or die;
130 470         735 print "# pages per sheet is $ppsheet\n" if DEBUG;
131 470 100       1710 if ($sig =~ m/^[0-9]+$/s) {
132 17 50       67 die "Signature must be a multiple of $ppsheet" if $sig % $ppsheet;
133 17         144 return $sig;
134             }
135 453         769 my ($min, $max);
136 453 50       2172 if ($sig =~ m/^([0-9]+)?-([0-9]+)?$/s) {
137 453   66     1521 $min = $1 || $ppsheet;
138 453   33     1180 $max = $2 || $total_pages;
139 453         1110 $min = $min + (($ppsheet - ($min % $ppsheet)) % $ppsheet);
140 453         836 $max = $max + (($ppsheet - ($max % $ppsheet)) % $ppsheet);
141 453 50       887 die "Bad range $max - $min" unless $max > $min;
142 453 50       995 die "bad min $min" if $min % $ppsheet;
143 453 50       914 die "bad max $max" if $max % $ppsheet;
144             }
145             else {
146 0         0 die "Unrecognized range $sig";
147             }
148 453         626 my $signature = 0;
149 453         760 my $roundedpages = $total_pages + (($ppsheet - ($total_pages % $ppsheet)) % $ppsheet);
150 453         619 my $needed = $roundedpages - $total_pages;
151 453 50       844 die "Something is wrong" if $roundedpages % $ppsheet;
152 453 100       935 if ($roundedpages <= $min) {
153 90 100       313 wantarray ? return ($roundedpages, $needed) : return $roundedpages;
154             }
155 363         765 $signature = $self->_find_signature($roundedpages, $max);
156 363 100       693 if ($roundedpages > $max) {
157 340         701 while ($signature < $min) {
158 344         467 $roundedpages += $ppsheet;
159 344         418 $needed += $ppsheet;
160 344         624 $signature = $self->_find_signature($roundedpages, $max)
161             }
162             }
163             # warn "Needed $needed blank pages";
164 363 100       1257 wantarray ? return ($signature, $needed) : return $signature;
165             }
166              
167             sub _find_signature {
168 707     707   1203 my ($self, $num, $max) = @_;
169 707 50       1430 my $ppsheet = $self->pages_per_sheet or die;
170 707 50       1387 die "not a multiple of $ppsheet" if $num % $ppsheet;
171 707 50       1191 die "uh?" unless $num;
172 707         978 my $i = $max;
173 707         1290 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       8989 if (($num % $i) == 0) {
178 707         1795 return $i;
179             }
180 4821         7526 $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 63     63   904 my $self = shift;
214 63         1128 my $pdf = $self->in_pdf_obj;
215 63         896 my ($x, $y, $w, $h) = $pdf->openpage(1)->get_mediabox; # use the first page
216 63 50       304508 warn $self->file . "use x-y offset, cannot proceed safely" if ($x + $y);
217 63 50 33     597 die "Cannot retrieve paper dimensions" unless $w && $h;
218 63         1594 my %dimensions = (
219             w => sprintf('%.2f', $w),
220             h => sprintf('%.2f', $h),
221             );
222             # return a copy
223 63         1937 return \%dimensions;
224             }
225              
226             has total_pages => (is => 'lazy',
227             isa => Int);
228              
229             sub _build_total_pages {
230 96     96   1531 my $self = shift;
231 96         1636 my $count = $self->in_pdf_obj->pages;
232 91         4022 return $count;
233             }
234              
235             sub orig_width {
236 483     483 1 16000 return shift->dimensions->{w};
237             }
238              
239             sub orig_height {
240 298     298 1 11759 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 96     96   1371 my $self = shift;
262 96         199 my $input;
263 96 100       1676 if ($self->file) {
264 91 50       2032 die "File " . $self->file . " doesn't exists" unless -f $self->file;
265 91         2357 print $self->file . ": building in_pdf_obj\n" if DEBUG;
266 91         2106 my ($basename, $path, $suff) = fileparse($self->file, qr{\.pdf}i);
267 91         5073 my $tmpfile = File::Spec->catfile($self->_tmp_dir,
268             $basename . $suff);
269 91 50       2939 copy($self->file, $tmpfile) or die "copy to $tmpfile failed $!";
270              
271 91         39977 eval {
272 91         934 $input = PDF::API2->open($tmpfile);
273             };
274 91 50       4010522 if ($@) {
275 0         0 die "Couldn't open $tmpfile $@";
276             }
277             else {
278 91         236 print "$tmpfile built\n" if DEBUG;
279             }
280 91         3763 $self->_in_pdf_object_is_open(1);
281             }
282 96         5696 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 64     64   1268 my $self = shift;
295 64         140 my $pdf;
296 64 50       1230 if ($self->file) {
297 64 100       1476 die "File " . $self->file . " is not a file" unless -f $self->file;
298 63         1742 print $self->file . ": building out_pdf_object\n" if DEBUG;
299 63         720 $pdf = PDF::API2->new();
300 63         56779 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 63         546 $pdf->info(%info);
307 63         11960 $self->_out_pdf_object_is_open(1);
308             }
309 63         3549 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 936     936 1 2791 my ($self, $page) = @_;
322 936 50 66     26690 if ((!defined $page) || ($page <= 0) || ($page > $self->total_pages)) {
      66        
323 101         289 return undef;
324             }
325 835         22559 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 64     64 1 185 my $self = shift;
337 64         321 my $out = $self->output_filename;
338 64         506 $self->_do_impose;
339 63         24958 $self->out_pdf_obj->saveas($out);
340 63         5471202 $self->out_pdf_obj->end;
341 63         2673 $self->_out_pdf_object_is_open(0);
342 63         3998 $self->in_pdf_obj->end;
343 63         847970 $self->_in_pdf_object_is_open(0);
344 63         3899 $self->outfile($out);
345 63         1143 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 69     69 1 892 my $self = shift;
357 69         1829 my $out = $self->outfile;
358 69 100       804 unless ($out) {
359 43         842 my ($name, $path, $suffix) = fileparse($self->file, qr{\.pdf}i);
360 43 50       1890 die $self->file . " has a suffix not recognized" unless $suffix;
361 43         926 $out = File::Spec->catfile($path, $name . $self->suffix . $suffix);
362             }
363 69         941 return $out;
364             }
365              
366              
367             sub _orig_file_timestamp {
368 63     63   62493 my $self = shift;
369 63         1730 my $mtime = (stat($self->file))[9];
370 63         2785 return $self->_format_timestamp($mtime);
371             }
372              
373             sub _now_timestamp {
374 63     63   344 return shift->_format_timestamp(time());
375             }
376              
377             sub _format_timestamp {
378 126     126   376 my ($self, $epoc) = @_;
379 126         8098 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             =head3 DEBUG
397              
398             Constant picked from AMW_DEBUG environment.
399              
400             =cut
401              
402             sub computed_signature {
403 131     131 1 25725 my $self = shift;
404 131   100     2491 my $signature = $self->signature || 0;
405 131 100       1580 if ($signature) {
406 25         382 return $self->_optimize_signature($self->signature) + 0;
407             }
408             else {
409 106         1872 my $pages = $self->total_pages;
410 106         2446 my $ppsheet = $self->pages_per_sheet;
411 106         934 return $pages + (($ppsheet - ($pages % $ppsheet)) % $ppsheet);
412             }
413             }
414              
415             sub total_output_pages {
416 45     45 1 235 my $self = shift;
417 45         849 my $pages = $self->total_pages;
418 45         403 my $signature = $self->computed_signature;
419 45         204 return $pages + (($signature - ($pages % $signature)) % $signature);
420             }
421              
422             =head3 cropmarks_options
423              
424             By default, cropmarks are centered and twoside is true.
425              
426             =cut
427              
428              
429             sub cropmarks_options {
430 0     0 1 0 my %options = (
431             top => 1,
432             bottom => 1,
433             inner => 1,
434             outer => 1,
435             twoside => 1,
436             );
437 0         0 return %options;
438             }
439              
440             sub DEMOLISH {
441 118     118 1 953949 my $self = shift;
442 118 50       2305 if ($self->_out_pdf_object_is_open) {
443 0         0 print $self->file . ": closing outpdf object\n" if DEBUG;
444 0         0 $self->out_pdf_obj->end;
445             }
446 118 100       3551 if ($self->_in_pdf_object_is_open) {
447 28         233 print $self->file . ": closing inpdf object\n" if DEBUG;
448 28         475 $self->in_pdf_obj->end;
449             }
450             }
451              
452             1;
453              
454             =head1 SEE ALSO
455              
456             L
457              
458             =cut
459              
460