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   89425 use strict;
  9         24  
  9         278  
3 9     9   50 use warnings;
  9         19  
  9         292  
4              
5 9     9   59 use File::Basename qw/fileparse/;
  9         21  
  9         607  
6 9     9   62 use File::Spec;
  9         20  
  9         242  
7 9     9   680 use PDF::API2;
  9         237041  
  9         191  
8 9     9   44 use File::Temp ();
  9         14  
  9         150  
9 9     9   508 use File::Copy;
  9         1969  
  9         601  
10 9     9   59 use POSIX ();
  9         19  
  9         235  
11 9     9   46 use Types::Standard qw/Int Bool Num HashRef Str Maybe Object Enum/;
  9         17  
  9         94  
12              
13 9     9   13445 use Moo::Role;
  9         23  
  9         75  
14             requires qw(_do_impose);
15              
16 9     9   3984 use constant { DEBUG => $ENV{AMW_DEBUG} };
  9         22  
  9         20746  
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   610729 my ($self, $sig, $total_pages) = @_;
125 475 100       1376 unless ($total_pages) {
126 25         413 $total_pages = $self->total_pages;
127             }
128 470 50       1309 return 0 unless $sig;
129 470 50       1653 my $ppsheet = $self->pages_per_sheet or die;
130 470         747 print "# pages per sheet is $ppsheet\n" if DEBUG;
131 470 100       1828 if ($sig =~ m/^[0-9]+$/s) {
132 17 50       68 die "Signature must be a multiple of $ppsheet" if $sig % $ppsheet;
133 17         134 return $sig;
134             }
135 453         796 my ($min, $max);
136 453 50       2184 if ($sig =~ m/^([0-9]+)?-([0-9]+)?$/s) {
137 453   66     1833 $min = $1 || $ppsheet;
138 453   33     1386 $max = $2 || $total_pages;
139 453         1179 $min = $min + (($ppsheet - ($min % $ppsheet)) % $ppsheet);
140 453         817 $max = $max + (($ppsheet - ($max % $ppsheet)) % $ppsheet);
141 453 50       977 die "Bad range $max - $min" unless $max > $min;
142 453 50       1008 die "bad min $min" if $min % $ppsheet;
143 453 50       983 die "bad max $max" if $max % $ppsheet;
144             }
145             else {
146 0         0 die "Unrecognized range $sig";
147             }
148 453         851 my $signature = 0;
149 453         848 my $roundedpages = $total_pages + (($ppsheet - ($total_pages % $ppsheet)) % $ppsheet);
150 453         696 my $needed = $roundedpages - $total_pages;
151 453 50       859 die "Something is wrong" if $roundedpages % $ppsheet;
152 453 100       878 if ($roundedpages <= $min) {
153 90 100       324 wantarray ? return ($roundedpages, $needed) : return $roundedpages;
154             }
155 363         887 $signature = $self->_find_signature($roundedpages, $max);
156 363 100       780 if ($roundedpages > $max) {
157 340         753 while ($signature < $min) {
158 344         459 $roundedpages += $ppsheet;
159 344         419 $needed += $ppsheet;
160 344         637 $signature = $self->_find_signature($roundedpages, $max)
161             }
162             }
163             # warn "Needed $needed blank pages";
164 363 100       1424 wantarray ? return ($signature, $needed) : return $signature;
165             }
166              
167             sub _find_signature {
168 707     707   1380 my ($self, $num, $max) = @_;
169 707 50       1737 my $ppsheet = $self->pages_per_sheet or die;
170 707 50       1454 die "not a multiple of $ppsheet" if $num % $ppsheet;
171 707 50       1296 die "uh?" unless $num;
172 707         984 my $i = $max;
173 707         1347 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       8832 if (($num % $i) == 0) {
178 707         1901 return $i;
179             }
180 4821         7483 $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 62     62   784 my $self = shift;
214 62         1202 my $pdf = $self->in_pdf_obj;
215 62         791 my ($x, $y, $w, $h) = $pdf->openpage(1)->get_mediabox; # use the first page
216 62 50       286737 warn $self->file . "use x-y offset, cannot proceed safely" if ($x + $y);
217 62 50 33     492 die "Cannot retrieve paper dimensions" unless $w && $h;
218 62         1122 my %dimensions = (
219             w => sprintf('%.2f', $w),
220             h => sprintf('%.2f', $h),
221             );
222             # return a copy
223 62         1785 return \%dimensions;
224             }
225              
226             has total_pages => (is => 'lazy',
227             isa => Int);
228              
229             sub _build_total_pages {
230 95     95   1243 my $self = shift;
231 95         1688 my $count = $self->in_pdf_obj->pages;
232 90         3508 return $count;
233             }
234              
235             sub orig_width {
236 476     476 1 15989 return shift->dimensions->{w};
237             }
238              
239             sub orig_height {
240 295     295 1 11144 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 95     95   1132 my $self = shift;
262 95         173 my $input;
263 95 100       1619 if ($self->file) {
264 90 50       2232 die "File " . $self->file . " doesn't exists" unless -f $self->file;
265 90         2011 print $self->file . ": building in_pdf_obj\n" if DEBUG;
266 90         1944 my ($basename, $path, $suff) = fileparse($self->file, qr{\.pdf}i);
267 90         4402 my $tmpfile = File::Spec->catfile($self->_tmp_dir,
268             $basename . $suff);
269 90 50       2815 copy($self->file, $tmpfile) or die "copy to $tmpfile failed $!";
270              
271 90         33966 eval {
272 90         823 $input = PDF::API2->open($tmpfile);
273             };
274 90 50       4025640 if ($@) {
275 0         0 die "Couldn't open $tmpfile $@";
276             }
277             else {
278 90         218 print "$tmpfile built\n" if DEBUG;
279             }
280 90         3413 $self->_in_pdf_object_is_open(1);
281             }
282 95         5235 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 63     63   1143 my $self = shift;
295 63         134 my $pdf;
296 63 50       1225 if ($self->file) {
297 63 100       1493 die "File " . $self->file . " is not a file" unless -f $self->file;
298 62         1457 print $self->file . ": building out_pdf_object\n" if DEBUG;
299 62         600 $pdf = PDF::API2->new();
300 62         54367 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 62         488 $pdf->info(%info);
307 62         11915 $self->_out_pdf_object_is_open(1);
308             }
309 62         3320 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 928     928 1 2505 my ($self, $page) = @_;
322 928 50 66     27070 if ((!defined $page) || ($page <= 0) || ($page > $self->total_pages)) {
      66        
323 101         289 return undef;
324             }
325 827         23577 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 63     63 1 167 my $self = shift;
337 63         270 my $out = $self->output_filename;
338 63         463 $self->_do_impose;
339 62         25245 $self->out_pdf_obj->saveas($out);
340 62         5581214 $self->out_pdf_obj->end;
341 62         2460 $self->_out_pdf_object_is_open(0);
342 62         3392 $self->in_pdf_obj->end;
343 62         844283 $self->_in_pdf_object_is_open(0);
344 62         3708 $self->outfile($out);
345 62         1073 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 68     68 1 763 my $self = shift;
357 68         1863 my $out = $self->outfile;
358 68 100       730 unless ($out) {
359 42         910 my ($name, $path, $suffix) = fileparse($self->file, qr{\.pdf}i);
360 42 50       1826 die $self->file . " has a suffix not recognized" unless $suffix;
361 42         965 $out = File::Spec->catfile($path, $name . $self->suffix . $suffix);
362             }
363 68         978 return $out;
364             }
365              
366              
367             sub _orig_file_timestamp {
368 62     62   66891 my $self = shift;
369 62         1702 my $mtime = (stat($self->file))[9];
370 62         2151 return $self->_format_timestamp($mtime);
371             }
372              
373             sub _now_timestamp {
374 62     62   280 return shift->_format_timestamp(time());
375             }
376              
377             sub _format_timestamp {
378 124     124   346 my ($self, $epoc) = @_;
379 124         6459 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 25034 my $self = shift;
400 129   100     2516 my $signature = $self->signature || 0;
401 129 100       1389 if ($signature) {
402 25         390 return $self->_optimize_signature($self->signature) + 0;
403             }
404             else {
405 104         1847 my $pages = $self->total_pages;
406 104         2140 my $ppsheet = $self->pages_per_sheet;
407 104         811 return $pages + (($ppsheet - ($pages % $ppsheet)) % $ppsheet);
408             }
409             }
410              
411             sub total_output_pages {
412 44     44 1 249 my $self = shift;
413 44         790 my $pages = $self->total_pages;
414 44         390 my $signature = $self->computed_signature;
415 44         171 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 116     116 1 901627 my $self = shift;
438 116 50       2631 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 116 100       3256 if ($self->_in_pdf_object_is_open) {
443 28         184 print $self->file . ": closing inpdf object\n" if DEBUG;
444 28         446 $self->in_pdf_obj->end;
445             }
446             }
447              
448             1;
449              
450             =head1 SEE ALSO
451              
452             L
453              
454             =cut
455              
456