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   327 use strict;
  39         174  
  39         1202  
19 39     39   233 use warnings;
  39         98  
  39         1047  
20              
21 39     39   260 use base 'PDF::Builder::Basic::PDF::Dict';
  39         89  
  39         9758  
22              
23             our $VERSION = '3.025'; # VERSION
24             our $LAST_UPDATE = '3.024'; # manually update whenever code is changed
25              
26 39     39   361 use PDF::Builder::Basic::PDF::Array;
  39         79  
  39         1020  
27 39     39   197 use PDF::Builder::Basic::PDF::Dict;
  39         89  
  39         624  
28 39     39   170 use PDF::Builder::Basic::PDF::Utils;
  39         98  
  39         3592  
29              
30 39     39   261 use Scalar::Util qw(weaken);
  39         79  
  39         88102  
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 1132 my ($class, $pdf, $parent) = @_;
62 411 50 0     1079 $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       1259 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       884 $class = ref($class) if ref($class);
71 411         1421 my $self = $class->SUPER::new($pdf, $parent);
72              
73 411         1227 $self->{'Type'} = PDFName('Pages');
74 411 100       1134 $self->{'Parent'} = $parent if defined $parent;
75 411         1104 $self->{'Count'} = PDFNum(0);
76 411         1762 $self->{'Kids'} = PDF::Builder::Basic::PDF::Array->new();
77              
78 411         1395 $pdf->new_obj($self);
79 411 100       1208 unless (defined $self->{'Parent'}) {
80 217         609 $pdf->{'Root'}->{'Pages'} = $self;
81 217         740 $pdf->out_obj($pdf->{'Root'});
82              
83 217         501 $self->{' parent'} = $pdf;
84 217         1063 weaken $self->{' parent'};
85             }
86 411 100       1521 weaken $self->{'Parent'} if defined $parent;
87              
88 411         1194 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   1058 my ($self) = @_;
130 582         1160 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 399 my ($self, $page_number) = @_;
142 194         443 my $top = $self->get_top();
143              
144 194         624 return $top->find_page_recursively(\$page_number);
145             }
146              
147             sub find_page_recursively {
148 194     194 0 417 my ($self, $page_number_ref) = @_;
149              
150 194 50       576 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         357 my $result;
156 194         654 foreach my $kid ($self->{'Kids'}->realise()->elements()) {
157 12 50       68 if ($kid->{'Type'}->realise()->val() eq 'Page') {
    0          
158 12 50       76 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         468 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 496 my ($self, $page, $page_number) = @_;
190 194         459 my $top = $self->get_top();
191              
192 194 100 66     672 $page_number = -1 unless defined $page_number and $page_number <= $top->{'Count'}->val();
193              
194 194         321 my $previous_page;
195 194 100       500 if ($page_number == -1) {
196 192         615 $previous_page = $top->find_page($top->{'Count'}->val() - 1);
197             } else {
198 2 50       4 $page_number = $top->{'Count'}->val() + $page_number + 1 if $page_number < 0;
199 2         6 $previous_page = $top->find_page($page_number);
200             }
201              
202 194         425 my $parent;
203 194 100       578 if (defined $previous_page->{'Parent'}) {
204 12         58 $parent = $previous_page->{'Parent'}->realise();
205             } else {
206 182         328 $parent = $self;
207             }
208              
209 194         520 my $parent_kid_count = scalar $parent->{'Kids'}->realise()->elements();
210              
211 194         328 my $page_index;
212 194 100       445 if ($page_number == -1) {
213 192         324 $page_index = -1;
214             } else {
215 2         5 for ($page_index = 0;
216             $page_index < $parent_kid_count;
217             $page_index++) {
218 2 50       10 last if $parent->{'Kids'}{' val'}[$page_index] eq $previous_page;
219             }
220 2 50       17 $page_index = -1 if $page_index == $parent_kid_count;
221             }
222              
223 194         599 $parent->add_page_recursively($page->realise(), $page_index);
224 194         681 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         525 $parent->set_modified();
231 194         548 $parent->{'Count'}->realise()->{'val'}++;
232              
233 194         508 return $page;
234             } # end of add_page()
235              
236             sub add_page_recursively {
237 194     194 0 416 my ($self, $page, $page_index) = @_;
238              
239 194         338 my $parent = $self;
240 194         329 my $max_kids_per_parent = 8; # Why 8?
241 194 50 33     480 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         537 $parent->set_modified();
259             }
260              
261 194 100       526 if ($page_index < 0) {
262 192         317 push @{$parent->{'Kids'}->realise()->{' val'}}, $page;
  192         509  
263             } else {
264 2         3 splice @{$parent->{'Kids'}{' val'}}, $page_index, 0, $page;
  2         7  
265             }
266 194         446 $page->{'Parent'} = $parent;
267 194         651 weaken $page->{'Parent'};
268              
269 194         332 return;
270             } # end of add_page_recursively()
271              
272             sub set_modified {
273 388     388 0 676 my ($self) = @_;
274 388         761 $self->_pdf()->out_obj($self);
275 388         664 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 1799 my ($self, $key) = @_;
336              
337 683 100       2123 if (defined $self->{$key}) {
    100          
338 245 50 33     1792 if (ref($self->{$key}) and
339             $self->{$key}->isa('PDF::Builder::Basic::PDF::Objind')) {
340 245         829 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         617 return $self->{'Parent'}->find_prop($key);
352             }
353              
354 227         483 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 1247 my ($self, @entries) = @_;
440              
441 411         1175 my $dict = $self->find_prop('Resources');
442 411 100 66     1435 if ($dict and defined $dict->{'ProcSet'}) {
443 194         538 my @missing = @entries;
444 194         567 foreach my $element ($dict->{'ProcSet'}->elements()) {
445 970         1685 @missing = grep { $_ ne $element } @missing;
  4850         9759  
446             }
447 194 50       642 return $self if scalar @missing == 0;
448 194 50       604 @entries = @missing if defined $self->{'Resources'};
449             }
450              
451 411 50       1023 unless (defined $self->{'Resources'}) {
452 411 100       1525 $self->{'Resources'} = $dict ? $dict->copy($self->_pdf()) : PDFDict();
453             }
454              
455 411 50       1685 $self->{'Resources'}{'ProcSet'} = PDFArray() unless defined $self->{'ProcSet'};
456              
457 411         995 foreach my $element (@entries) {
458 2055         4400 $self->{'Resources'}{'ProcSet'}->add_elements(PDFName($element));
459             }
460              
461 411         5300 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 1571 my ($self) = @_;
485              
486 970         1347 my $top = $self;
487 970         2290 $top = $top->{'Parent'} while defined $top->{'Parent'};
488              
489 970         2238 return $top->realise();
490             }
491              
492             =back
493              
494             =cut
495              
496             1;