File Coverage

blib/lib/PDF/Builder/Basic/PDF/Pages.pm
Criterion Covered Total %
statement 115 181 63.5
branch 39 82 47.5
condition 6 41 14.6
subroutine 17 23 73.9
pod 10 15 66.6
total 187 342 54.6


line stmt bran cond sub pod time code
1             #=======================================================================
2             #
3             # THIS IS A REUSED PERL MODULE, FOR PROPER LICENCING TERMS SEE BELOW:
4             #
5             # Copyright Martin Hosken
6             #
7             # No warranty or expression of effectiveness, least of all regarding
8             # anyone's safety, is implied in this software or documentation.
9             #
10             # This specific module is licensed under the Perl Artistic License.
11             # Effective 28 January 2021, the original author and copyright holder,
12             # Martin Hosken, has given permission to use and redistribute this module
13             # under the MIT license.
14             #
15             #=======================================================================
16             package PDF::Builder::Basic::PDF::Pages;
17              
18 39     39   287 use strict;
  39         192  
  39         987  
19 39     39   180 use warnings;
  39         75  
  39         896  
20              
21 39     39   174 use base 'PDF::Builder::Basic::PDF::Dict';
  39         77  
  39         8164  
22              
23             our $VERSION = '3.024'; # VERSION
24             our $LAST_UPDATE = '3.024'; # manually update whenever code is changed
25              
26 39     39   328 use PDF::Builder::Basic::PDF::Array;
  39         60  
  39         892  
27 39     39   164 use PDF::Builder::Basic::PDF::Dict;
  39         65  
  39         541  
28 39     39   155 use PDF::Builder::Basic::PDF::Utils;
  39         70  
  39         3016  
29              
30 39     39   249 use Scalar::Util qw(weaken);
  39         73  
  39         72225  
31              
32             our %inst = map {$_ => 1} qw(Parent Type);
33              
34             =head1 NAME
35              
36             PDF::Builder::Basic::PDF::Pages - a PDF pages hierarchical element.
37             Inherits from L
38              
39             =head1 DESCRIPTION
40              
41             A Pages object is the parent to other pages objects or to page objects
42             themselves.
43              
44             =head1 METHODS
45              
46             =over
47              
48             =item PDF::Builder::Basic::PDF::Pages->new($pdf, $parent)
49              
50             This creates a new Pages object in a PDF. Notice that the C<$parent> here is
51             not the file context for the object, but the parent pages object for these
52             pages. If we are using this class to create a root node, C<$parent> should
53             point to the file context, which is identified by I having a Type of
54             I. C<$pdf> is the file object (or a reference to an array of I
55             file object [3.016 and later, or multiple file objects earlier]) in which to
56             create the new Pages object.
57              
58             =cut
59              
60             sub new {
61 411     411 1 932 my ($class, $pdf, $parent) = @_;
62 411 50 0     965 $pdf //= $class->get_top()->{' parent'} if ref($class);
63              
64             # before PDF::API2 2.034/PDF::Builder 3.016, $pdf could be an array of PDFs
65 411 50       1157 if (ref($pdf) eq 'ARRAY') {
66 0 0       0 die 'Pages: Only one PDF is supported as of version 3.016' if scalar(@$pdf) > 1;
67 0         0 ($pdf) = @$pdf;
68             }
69              
70 411 50       795 $class = ref($class) if ref($class);
71 411         1389 my $self = $class->SUPER::new($pdf, $parent);
72              
73 411         1101 $self->{'Type'} = PDFName('Pages');
74 411 100       1141 $self->{'Parent'} = $parent if defined $parent;
75 411         1026 $self->{'Count'} = PDFNum(0);
76 411         1814 $self->{'Kids'} = PDF::Builder::Basic::PDF::Array->new();
77              
78 411         1275 $pdf->new_obj($self);
79 411 100       1083 unless (defined $self->{'Parent'}) {
80 217         523 $pdf->{'Root'}->{'Pages'} = $self;
81 217         1751 $pdf->out_obj($pdf->{'Root'});
82              
83 217         498 $self->{' parent'} = $pdf;
84 217         909 weaken $self->{' parent'};
85             }
86 411 100       1808 weaken $self->{'Parent'} if defined $parent;
87              
88 411         1066 return $self;
89             }
90              
91             #sub init {
92             # my ($self, $pdf) = @_;
93             # $self->{' destination_pdfs'} = [$pdf];
94             # weaken $self->{' destination_pdfs'}->[0] if defined $pdf;
95             #
96             # return $self;
97             #}
98              
99             #=item $p->out_obj($is_new)
100             #
101             #Tells all the files that this thing is destined for that they should output this
102             #object, come time to output. If this object has no parent, then it must be the
103             #root. So set as the root for the files in question and tell it to be output too.
104             #If C<$is_new> is set, then call C rather than C to create as
105             #a new object in the file.
106             #
107             #=cut
108             #
109             #sub out_obj {
110             # my ($self, $is_new) = @_;
111             #
112             # foreach my $pdf (@{$self->{' destination_pdfs'}}) {
113             # if ($is_new) {
114             # $pdf->new_obj($self);
115             # } else {
116             # $pdf->out_obj($self);
117             # }
118             #
119             # unless (defined $self->{'Parent'}) {
120             # $pdf->{'Root'}{'Pages'} = $self;
121             # $pdf->out_obj($pdf->{'Root'});
122             # }
123             # }
124             #
125             # return $self;
126             #}
127              
128             sub _pdf {
129 582     582   919 my ($self) = @_;
130 582         1211 return $self->get_top()->{' parent'};
131             }
132              
133             =item $p->find_page($page_number)
134              
135             Returns the given page, using the page count values in the pages tree. Pages
136             start at 0.
137              
138             =cut
139              
140             sub find_page {
141 194     194 1 409 my ($self, $page_number) = @_;
142 194         370 my $top = $self->get_top();
143              
144 194         609 return $top->find_page_recursively(\$page_number);
145             }
146              
147             sub find_page_recursively {
148 194     194 0 379 my ($self, $page_number_ref) = @_;
149              
150 194 50       497 if ($self->{'Count'}->realise()->val() <= $$page_number_ref) {
151 0         0 $$page_number_ref -= $self->{'Count'}->val();
152 0         0 return;
153             }
154              
155 194         292 my $result;
156 194         582 foreach my $kid ($self->{'Kids'}->realise()->elements()) {
157 12 50       52 if ($kid->{'Type'}->realise()->val() eq 'Page') {
    0          
158 12 50       63 return $kid if $$page_number_ref == 0;
159 0         0 $$page_number_ref--;
160             } elsif ($result = $kid->realise()->find_page_recursively($page_number_ref)) {
161 0         0 return $result;
162             }
163             }
164              
165 182         443 return;
166             }
167              
168             =item $p->add_page($page, $page_number)
169              
170             Inserts the page before the given C<$page_number>. C<$page_number> can be
171             negative to count backwards from the END of the document. -1 is after the last
172             page. Likewise C<$page_number> can be greater than the number of pages
173             currently in the document, to append.
174              
175             This method only guarantees to provide a reasonable pages tree if pages are
176             appended or prepended to the document. Pages inserted in the middle of the
177             document may simply be inserted in the appropriate leaf in the pages tree
178             without adding any new branches or leaves, leaving it unbalanced (slower
179             performance, but still usable).
180              
181             =cut
182              
183             # -- removed from end of second para:
184             #To tidy up such a mess, it is best
185             #to call C<$p->rebuild_tree()> to rebuild the pages tree into something
186             #efficient. B is currently a no-op!>
187              
188             sub add_page {
189 194     194 1 442 my ($self, $page, $page_number) = @_;
190 194         513 my $top = $self->get_top();
191              
192 194 100 66     693 $page_number = -1 unless defined $page_number and $page_number <= $top->{'Count'}->val();
193              
194 194         324 my $previous_page;
195 194 100       444 if ($page_number == -1) {
196 192         563 $previous_page = $top->find_page($top->{'Count'}->val() - 1);
197             } else {
198 2 50       6 $page_number = $top->{'Count'}->val() + $page_number + 1 if $page_number < 0;
199 2         3 $previous_page = $top->find_page($page_number);
200             }
201              
202 194         359 my $parent;
203 194 100       542 if (defined $previous_page->{'Parent'}) {
204 12         37 $parent = $previous_page->{'Parent'}->realise();
205             } else {
206 182         311 $parent = $self;
207             }
208              
209 194         515 my $parent_kid_count = scalar $parent->{'Kids'}->realise()->elements();
210              
211 194         311 my $page_index;
212 194 100       466 if ($page_number == -1) {
213 192         266 $page_index = -1;
214             } else {
215 2         6 for ($page_index = 0;
216             $page_index < $parent_kid_count;
217             $page_index++) {
218 2 50       7 last if $parent->{'Kids'}{' val'}[$page_index] eq $previous_page;
219             }
220 2 50       5 $page_index = -1 if $page_index == $parent_kid_count;
221             }
222              
223 194         540 $parent->add_page_recursively($page->realise(), $page_index);
224 194         725 for ($parent = $page->{'Parent'};
225             defined $parent->{'Parent'};
226             $parent = $parent->{'Parent'}->realise()) {
227 0         0 $parent->set_modified();
228 0         0 $parent->{'Count'}->realise()->{'val'}++;
229             }
230 194         510 $parent->set_modified();
231 194         481 $parent->{'Count'}->realise()->{'val'}++;
232              
233 194         425 return $page;
234             } # end of add_page()
235              
236             sub add_page_recursively {
237 194     194 0 453 my ($self, $page, $page_index) = @_;
238              
239 194         277 my $parent = $self;
240 194         297 my $max_kids_per_parent = 8; # Why 8?
241 194 50 33     537 if (scalar $parent->{'Kids'}->elements() >= $max_kids_per_parent and
      0        
242             $parent->{'Parent'} and
243             $page_index < 1) {
244 0         0 my $grandparent = $parent->{'Parent'}->realise();
245 0         0 $parent = $parent->new($parent->_pdf(), $grandparent);
246              
247 0         0 my $grandparent_kid_count = scalar $grandparent->{'Kids'}->realise()->elements();
248 0         0 my $new_parent_index;
249 0         0 for ($new_parent_index = 0;
250             $new_parent_index < $grandparent_kid_count;
251             $new_parent_index++) {
252 0 0       0 last if $grandparent->{'Kids'}{' val'}[$new_parent_index] eq $self;
253             }
254 0         0 $new_parent_index++;
255 0 0       0 $new_parent_index = -1 if $new_parent_index > $grandparent_kid_count;
256 0         0 $grandparent->add_page_recursively($parent, $new_parent_index);
257             } else {
258 194         583 $parent->set_modified();
259             }
260              
261 194 100       470 if ($page_index < 0) {
262 192         296 push @{$parent->{'Kids'}->realise()->{' val'}}, $page;
  192         475  
263             } else {
264 2         8 splice @{$parent->{'Kids'}{' val'}}, $page_index, 0, $page;
  2         6  
265             }
266 194         443 $page->{'Parent'} = $parent;
267 194         624 weaken $page->{'Parent'};
268              
269 194         349 return;
270             } # end of add_page_recursively()
271              
272             sub set_modified {
273 388     388 0 671 my ($self) = @_;
274 388         648 $self->_pdf()->out_obj($self);
275 388         604 return;
276             }
277              
278             #=item $root_pages = $p->rebuild_tree([@pglist])
279             #
280             #B
281             #
282             #Rebuilds the pages tree to make a nice balanced tree that conforms to Adobe
283             #recommendations. If passed a C<@pglist> then the tree is built for that list of
284             #pages. No check is made of whether the C<@pglist> contains pages.
285             #
286             #Returns the top of the tree for insertion in the root object.
287             #
288             #=cut
289              
290             # TBD where's the code?
291             #sub rebuild_tree {
292             # my ($self, @pglist) = @_;
293             # return;
294             #}
295              
296             =item @objects = $p->get_pages()
297              
298             Returns a list of page objects in the document, in page order.
299              
300             =cut
301              
302             sub get_pages {
303 0     0 1 0 my ($self) = @_;
304              
305 0         0 return $self->get_top()->get_pages_recursively();
306             }
307              
308             # Renamed for clarity. should this be deprecated?
309             # appears not to have been used, and was undocumented.
310 0     0 0 0 sub get_kids { return get_pages_recursively(@_); }
311              
312             sub get_pages_recursively {
313 0     0 0 0 my ($self) = @_;
314 0         0 my @pages;
315              
316 0         0 foreach my $kid ($self->{'Kids'}->elements()) {
317 0         0 $kid->realise();
318 0 0       0 if ($kid->{'Type'}->val() eq 'Pages') {
319 0         0 push @pages, $kid->get_pages_recursively();
320             } else {
321 0         0 push @pages, $kid;
322             }
323             }
324              
325 0         0 return @pages;
326             }
327              
328             =item $p->find_prop($key)
329              
330             Searches up through the inheritance tree to find a property (key).
331              
332             =cut
333              
334             sub find_prop {
335 683     683 1 1564 my ($self, $key) = @_;
336              
337 683 100       1900 if (defined $self->{$key}) {
    100          
338 245 50 33     1760 if (ref($self->{$key}) and
339             $self->{$key}->isa('PDF::Builder::Basic::PDF::Objind')) {
340 245         834 return $self->{$key}->realise();
341             } else {
342 0         0 return $self->{$key};
343             }
344             # Per Klaus Ethgen (RT 131147), this is an alternative patch for the
345             # problem of Null objects bubbling up. If Vadim Repin's patch in ./File.pm
346             # turns out to have too wide of scope, we might use this one instead.
347             # comment out 1, uncomment 2, and reverse change made in ./File.pm.
348             } elsif (defined $self->{'Parent'}) {
349             #} elsif (defined $self->{'Parent'} and
350             # ref($self->('Parent'}) ne 'PDF::Builder::Basic::PDF::Null') {
351 211         598 return $self->{'Parent'}->find_prop($key);
352             }
353              
354 227         459 return;
355             }
356              
357             =item $p->add_font($pdf, $font)
358              
359             Creates or edits the resource dictionary at this level in the hierarchy. If
360             the font is already supported, even through the hierarchy, then it is not added.
361              
362             B if this method was used in older releases, the code may have
363             swapped the order of C<$pdf> and C<$font>, requiring ad hoc swapping of
364             parameters in user code, contrary to the POD definition above. Now the code
365             matches the documentation.
366              
367             =cut
368              
369             sub add_font {
370 0     0 1 0 my ($self, $pdf, $font) = @_;
371              
372 0         0 my $name = $font->{'Name'}->val();
373 0         0 my $dict = $self->find_prop('Resources');
374              
375             return $self if ($dict and
376             defined $dict->{'Font'} and
377 0 0 0     0 defined $dict->{'Font'}{$name});
      0        
378 0 0       0 unless (defined $self->{'Resources'}) {
379 0 0       0 $dict = $dict ? $dict->copy($pdf) : PDFDict();
380 0         0 $self->{'Resources'} = $dict;
381             } else {
382 0         0 $dict = $self->{'Resources'};
383             }
384 0   0     0 $dict->{'Font'} //= PDFDict();
385              
386 0         0 my $resource = $dict->{'Font'}->val();
387 0   0     0 $resource->{$name} //= $font;
388 0 0 0     0 if (ref($dict) ne 'HASH' and $dict->is_obj($pdf)) {
389 0         0 $pdf->out_obj($dict);
390             }
391 0 0 0     0 if (ref($resource) ne 'HASH' and $resource->is_obj($pdf)) {
392 0         0 $pdf->out_obj($resource);
393             }
394              
395 0         0 return $self;
396             } # end of add_font()
397              
398             =item $p->bbox($xmin,$ymin, $xmax,$ymax, $param)
399              
400             =item $p->bbox($xmin,$ymin, $xmax,$ymax)
401              
402             Specifies the bounding box for this and all child pages. If the values are
403             identical to those inherited, no change is made. C<$param> specifies the
404             attribute name so that other 'bounding box'es can be set with this method.
405              
406             =cut
407              
408             sub bbox {
409 0     0 1 0 my ($self, @bbox) = @_;
410 0   0     0 my $key = $bbox[4] || 'MediaBox';
411 0         0 my $inherited = $self->find_prop($key);
412              
413 0 0       0 if ($inherited) {
414 0         0 my $is_changed = 0;
415 0         0 my $i = 0;
416 0         0 foreach my $element ($inherited->elements()) {
417 0 0       0 $is_changed = 1 unless $element->val() == $bbox[$i++];
418             }
419 0 0 0     0 return $self if $i == 4 and not $is_changed;
420             }
421              
422 0         0 my $array = PDF::Builder::Basic::PDF::Array->new();
423 0         0 foreach my $element (@bbox[0 .. 3]) {
424 0         0 $array->add_elements(PDFNum($element));
425             }
426 0         0 $self->{$key} = $array;
427              
428 0         0 return $self;
429             }
430              
431             =item $p->proc_set(@entries)
432              
433             Ensures that the current resource contains all the entries in the proc_sets
434             listed. If necessary, it creates a local resource dictionary to achieve this.
435              
436             =cut
437              
438             sub proc_set {
439 411     411 1 1214 my ($self, @entries) = @_;
440              
441 411         1210 my $dict = $self->find_prop('Resources');
442 411 100 66     1362 if ($dict and defined $dict->{'ProcSet'}) {
443 194         589 my @missing = @entries;
444 194         577 foreach my $element ($dict->{'ProcSet'}->elements()) {
445 970         1708 @missing = grep { $_ ne $element } @missing;
  4850         8228  
446             }
447 194 50       550 return $self if scalar @missing == 0;
448 194 50       524 @entries = @missing if defined $self->{'Resources'};
449             }
450              
451 411 50       1014 unless (defined $self->{'Resources'}) {
452 411 100       1509 $self->{'Resources'} = $dict ? $dict->copy($self->_pdf()) : PDFDict();
453             }
454              
455 411 50       1536 $self->{'Resources'}{'ProcSet'} = PDFArray() unless defined $self->{'ProcSet'};
456              
457 411         876 foreach my $element (@entries) {
458 2055         3797 $self->{'Resources'}{'ProcSet'}->add_elements(PDFName($element));
459             }
460              
461 411         980 return $self;
462             } # end of proc_set()
463              
464             sub empty {
465 0     0 1 0 my ($self) = @_;
466 0         0 my $parent = $self->{'Parent'};
467              
468 0         0 $self->SUPER::empty();
469 0 0       0 if (defined $parent) {
470 0         0 $self->{'Parent'} = $parent;
471 0         0 weaken $self->{'Parent'};
472             }
473              
474 0         0 return $self;
475             }
476              
477             =item $p->get_top()
478              
479             Returns the top of the pages tree.
480              
481             =cut
482              
483             sub get_top {
484 970     970 1 1323 my ($self) = @_;
485              
486 970         1244 my $top = $self;
487 970         1879 $top = $top->{'Parent'} while defined $top->{'Parent'};
488              
489 970         1865 return $top->realise();
490             }
491              
492             =back
493              
494             =cut
495              
496             1;