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 <Martin_Hosken@sil.org>
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 40     40   404 use strict;
  40         101  
  40         1943  
19 40     40   232 use warnings;
  40         153  
  40         2616  
20              
21 40     40   241 use base 'PDF::Builder::Basic::PDF::Dict';
  40         89  
  40         7978  
22              
23             our $VERSION = '3.028'; # VERSION
24             our $LAST_UPDATE = '3.027'; # manually update whenever code is changed
25              
26 40     40   473 use PDF::Builder::Basic::PDF::Array;
  40         119  
  40         1105  
27 40     40   200 use PDF::Builder::Basic::PDF::Dict;
  40         82  
  40         1084  
28 40     40   195 use PDF::Builder::Basic::PDF::Utils;
  40         124  
  40         4752  
29              
30 40     40   277 use Scalar::Util qw(weaken);
  40         95  
  40         110112  
31              
32             our %inst = map {$_ => 1} qw(Parent Type);
33              
34             =head1 NAME
35              
36             PDF::Builder::Basic::PDF::Pages - PDF pages hierarchical element
37              
38             Inherits from L<PDF::Builder::Basic::PDF::Dict>
39              
40             =head1 DESCRIPTION
41              
42             A Pages object is the parent to other pages objects or to page objects
43             themselves.
44              
45             =head1 METHODS
46              
47             =head2 new
48              
49             PDF::Builder::Basic::PDF::Pages->new($pdf, $parent)
50              
51             =over
52              
53             This creates a new Pages object in a PDF. Notice that the C<$parent> here is
54             not the file context for the object, but the parent pages object for these
55             pages. If we are using this class to create a root node, C<$parent> should
56             point to the file context, which is identified by I<not> having a Type of
57             I<Pages>. C<$pdf> is the file object (or a reference to an array of I<one>
58             file object [3.016 and later, or multiple file objects earlier]) in which to
59             create the new Pages object.
60              
61             =back
62              
63             =cut
64              
65             sub new {
66 426     426 1 1346 my ($class, $pdf, $parent) = @_;
67 426 50 0     1354 $pdf //= $class->get_top()->{' parent'} if ref($class);
68              
69             # before PDF::API2 2.034/PDF::Builder 3.016, $pdf could be an array of PDFs
70 426 50       1538 if (ref($pdf) eq 'ARRAY') {
71 0 0       0 die 'Pages: Only one PDF is supported as of version 3.016' if scalar(@$pdf) > 1;
72 0         0 ($pdf) = @$pdf;
73             }
74              
75 426 50       1184 $class = ref($class) if ref($class);
76 426         2084 my $self = $class->SUPER::new($pdf, $parent);
77              
78 426         1566 $self->{'Type'} = PDFName('Pages');
79 426 100       1488 $self->{'Parent'} = $parent if defined $parent;
80 426         1500 $self->{'Count'} = PDFNum(0);
81 426         2728 $self->{'Kids'} = PDF::Builder::Basic::PDF::Array->new();
82              
83 426         2053 $pdf->new_obj($self);
84 426 100       1508 unless (defined $self->{'Parent'}) {
85 234         801 $pdf->{'Root'}->{'Pages'} = $self;
86 234         1067 $pdf->out_obj($pdf->{'Root'});
87              
88 234         726 $self->{' parent'} = $pdf;
89 234         764 weaken $self->{' parent'};
90             }
91 426 100       1340 weaken $self->{'Parent'} if defined $parent;
92              
93 426         1644 return $self;
94             }
95              
96             #sub init {
97             # my ($self, $pdf) = @_;
98             # $self->{' destination_pdfs'} = [$pdf];
99             # weaken $self->{' destination_pdfs'}->[0] if defined $pdf;
100             #
101             # return $self;
102             #}
103              
104             #=head2 out_obj
105             #
106             # $p->out_obj($is_new)
107             #
108             #=over
109             #
110             #Tells all the files that this thing is destined for that they should output this
111             #object, come time to output. If this object has no parent, then it must be the
112             #root. So set as the root for the files in question and tell it to be output too.
113             #If C<$is_new> is set, then call C<new_obj> rather than C<out_obj> to create as
114             #a new object in the file.
115             #
116             #=back
117             #
118             #=cut
119             #
120             #sub out_obj {
121             # my ($self, $is_new) = @_;
122             #
123             # foreach my $pdf (@{$self->{' destination_pdfs'}}) {
124             # if ($is_new) {
125             # $pdf->new_obj($self);
126             # } else {
127             # $pdf->out_obj($self);
128             # }
129             #
130             # unless (defined $self->{'Parent'}) {
131             # $pdf->{'Root'}{'Pages'} = $self;
132             # $pdf->out_obj($pdf->{'Root'});
133             # }
134             # }
135             #
136             # return $self;
137             #}
138              
139             sub _pdf {
140 576     576   1149 my ($self) = @_;
141 576         1515 return $self->get_top()->{' parent'};
142             }
143              
144             =head2 find_page
145              
146             $p->find_page($page_number)
147              
148             =over
149              
150             Returns the given page, using the page count values in the pages tree. Pages
151             start at 0.
152              
153             =back
154              
155             =cut
156              
157             sub find_page {
158 192     192 1 528 my ($self, $page_number) = @_;
159 192         571 my $top = $self->get_top();
160              
161 192         839 return $top->find_page_recursively(\$page_number);
162             }
163              
164             sub find_page_recursively {
165 192     192 0 540 my ($self, $page_number_ref) = @_;
166              
167 192 50       669 if ($self->{'Count'}->realise()->val() <= $$page_number_ref) {
168 0         0 $$page_number_ref -= $self->{'Count'}->val();
169 0         0 return;
170             }
171              
172 192         422 my $result;
173 192         802 foreach my $kid ($self->{'Kids'}->realise()->elements()) {
174 12 50       81 if ($kid->{'Type'}->realise()->val() eq 'Page') {
    0          
175 12 50       64 return $kid if $$page_number_ref == 0;
176 0         0 $$page_number_ref--;
177             } elsif ($result = $kid->realise()->find_page_recursively($page_number_ref)) {
178 0         0 return $result;
179             }
180             }
181              
182 180         573 return;
183             }
184              
185             =head2 add_page
186              
187             $p->add_page($page, $page_number)
188              
189             =over
190              
191             Inserts the page before the given C<$page_number>. C<$page_number> can be
192             negative to count backwards from the END of the document. -1 is after the last
193             page. Likewise C<$page_number> can be greater than the number of pages
194             currently in the document, to append.
195              
196             This method only guarantees to provide a reasonable pages tree if pages are
197             appended or prepended to the document. Pages inserted in the middle of the
198             document may simply be inserted in the appropriate leaf in the pages tree
199             without adding any new branches or leaves, leaving it unbalanced (slower
200             performance, but still usable).
201              
202             =back
203              
204             =cut
205              
206             # -- removed from end of second para:
207             #To tidy up such a mess, it is best
208             #to call C<$p->rebuild_tree()> to rebuild the pages tree into something
209             #efficient. B<Note that C<rebuild_tree> is currently a no-op!>
210              
211             sub add_page {
212 192     192 1 698 my ($self, $page, $page_number) = @_;
213 192         557 my $top = $self->get_top();
214              
215 192 100 66     825 $page_number = -1 unless defined $page_number and $page_number <= $top->{'Count'}->val();
216              
217 192         417 my $previous_page;
218 192 100       620 if ($page_number == -1) {
219 190         11273 $previous_page = $top->find_page($top->{'Count'}->val() - 1);
220             } else {
221 2 50       5 $page_number = $top->{'Count'}->val() + $page_number + 1 if $page_number < 0;
222 2         4 $previous_page = $top->find_page($page_number);
223             }
224              
225 192         674 my $parent;
226 192 100       687 if (defined $previous_page->{'Parent'}) {
227 12         69 $parent = $previous_page->{'Parent'}->realise();
228             } else {
229 180         383 $parent = $self;
230             }
231              
232 192         700 my $parent_kid_count = scalar $parent->{'Kids'}->realise()->elements();
233              
234 192         416 my $page_index;
235 192 100       505 if ($page_number == -1) {
236 190         377 $page_index = -1;
237             } else {
238 2         5 for ($page_index = 0;
239             $page_index < $parent_kid_count;
240             $page_index++) {
241 2 50       6 last if $parent->{'Kids'}{' val'}[$page_index] eq $previous_page;
242             }
243 2 50       5 $page_index = -1 if $page_index == $parent_kid_count;
244             }
245              
246 192         674 $parent->add_page_recursively($page->realise(), $page_index);
247 192         1095 for ($parent = $page->{'Parent'};
248             defined $parent->{'Parent'};
249             $parent = $parent->{'Parent'}->realise()) {
250 0         0 $parent->set_modified();
251 0         0 $parent->{'Count'}->realise()->{'val'}++;
252             }
253 192         602 $parent->set_modified();
254 192         647 $parent->{'Count'}->realise()->{'val'}++;
255              
256 192         711 return $page;
257             } # end of add_page()
258              
259             sub add_page_recursively {
260 192     192 0 594 my ($self, $page, $page_index) = @_;
261              
262 192         384 my $parent = $self;
263 192         353 my $max_kids_per_parent = 8; # Why 8? effort to somewhat balance tree?
264 192 50 33     701 if (scalar $parent->{'Kids'}->elements() >= $max_kids_per_parent and
      0        
265             $parent->{'Parent'} and
266             $page_index < 0) {
267 0         0 my $grandparent = $parent->{'Parent'}->realise();
268 0         0 $parent = $parent->new($parent->_pdf(), $grandparent);
269              
270 0         0 my $grandparent_kid_count = scalar $grandparent->{'Kids'}->realise()->elements();
271 0         0 my $new_parent_index;
272 0         0 for ($new_parent_index = 0;
273             $new_parent_index < $grandparent_kid_count;
274             $new_parent_index++) {
275 0 0       0 last if $grandparent->{'Kids'}{' val'}[$new_parent_index] eq $self;
276             }
277 0         0 $new_parent_index++;
278 0 0       0 $new_parent_index = -1 if $new_parent_index > $grandparent_kid_count;
279 0         0 $grandparent->add_page_recursively($parent, $new_parent_index);
280             } else {
281 192         768 $parent->set_modified();
282             }
283              
284 192 100       709 if ($page_index < 0) {
285 190         406 push @{$parent->{'Kids'}->realise()->{' val'}}, $page;
  190         635  
286             } else {
287 2         4 splice @{$parent->{'Kids'}{' val'}}, $page_index, 0, $page;
  2         7  
288             }
289 192         597 $page->{'Parent'} = $parent;
290 192         544 weaken $page->{'Parent'};
291              
292 192         423 return;
293             } # end of add_page_recursively()
294              
295             sub set_modified {
296 384     384 0 809 my ($self) = @_;
297 384         927 $self->_pdf()->out_obj($self);
298 384         726 return;
299             }
300              
301             #=head2 rebuild_tree
302             #
303             # $root_pages = $p->rebuild_tree([@pglist])
304             #
305             #=over
306             #
307             #B<WARNING: Not yet implemented. Do not attempt to use!>
308             #
309             #Rebuilds the pages tree to make a nice balanced tree that conforms to Adobe
310             #recommendations. If passed a C<@pglist> then the tree is built for that list of
311             #pages. No check is made of whether the C<@pglist> contains pages.
312             #
313             #Returns the top of the tree for insertion in the root object.
314             #
315             #=back
316             #
317             #=cut
318              
319             # TBD where's the code?
320             #sub rebuild_tree {
321             # my ($self, @pglist) = @_;
322             # return;
323             #}
324              
325             =head2 get_pages
326              
327             @objects = $p->get_pages()
328              
329             =over
330              
331             Returns a list of page objects in the document, in page order.
332              
333             =back
334              
335             =cut
336              
337             sub get_pages {
338 0     0 1 0 my ($self) = @_;
339              
340 0         0 return $self->get_top()->get_pages_recursively();
341             }
342              
343             # Renamed for clarity. should this be deprecated?
344             # appears not to have been used, and was undocumented.
345 0     0 0 0 sub get_kids { return get_pages_recursively(@_); }
346              
347             sub get_pages_recursively {
348 0     0 0 0 my ($self) = @_;
349 0         0 my @pages;
350              
351 0         0 foreach my $kid ($self->{'Kids'}->elements()) {
352 0         0 $kid->realise();
353 0 0       0 if ($kid->{'Type'}->val() eq 'Pages') {
354 0         0 push @pages, $kid->get_pages_recursively();
355             } else {
356 0         0 push @pages, $kid;
357             }
358             }
359              
360 0         0 return @pages;
361             }
362              
363             =head2 find_prop
364              
365             $p->find_prop($key)
366              
367             =over
368              
369             Searches up through the inheritance tree to find a property (key).
370              
371             =back
372              
373             =cut
374              
375             sub find_prop {
376 694     694 1 1666 my ($self, $key) = @_;
377              
378 694 100       2688 if (defined $self->{$key}) {
    100          
379 242 50 33     2550 if (ref($self->{$key}) and
380             $self->{$key}->isa('PDF::Builder::Basic::PDF::Objind')) {
381 242         1042 return $self->{$key}->realise();
382             } else {
383 0         0 return $self->{$key};
384             }
385             # Per Klaus Ethgen (RT 131147), this is an alternative patch for the
386             # problem of Null objects bubbling up. If Vadim Repin's patch in ./File.pm
387             # turns out to have too wide of scope, we might use this one instead.
388             # comment out 1, uncomment 2, and reverse change made in ./File.pm.
389             } elsif (defined $self->{'Parent'}) {
390             #} elsif (defined $self->{'Parent'} and
391             # ref($self->('Parent'}) ne 'PDF::Builder::Basic::PDF::Null') {
392 208         862 return $self->{'Parent'}->find_prop($key);
393             }
394              
395 244         712 return;
396             }
397              
398             =head2 add_font
399              
400             $p->add_font($pdf, $font)
401              
402             =over
403              
404             Creates or edits the resource dictionary at this level in the hierarchy. If
405             the font is already supported, even through the hierarchy, then it is not added.
406              
407             B<CAUTION:> if this method was used in older releases, the code may have
408             swapped the order of C<$pdf> and C<$font>, requiring ad hoc swapping of
409             parameters in user code, contrary to the POD definition above. Now the code
410             matches the documentation.
411              
412             =back
413              
414             =cut
415              
416             sub add_font {
417 0     0 1 0 my ($self, $pdf, $font) = @_;
418              
419 0         0 my $name = $font->{'Name'}->val();
420 0         0 my $dict = $self->find_prop('Resources');
421              
422             return $self if ($dict and
423             defined $dict->{'Font'} and
424 0 0 0     0 defined $dict->{'Font'}{$name});
      0        
425 0 0       0 unless (defined $self->{'Resources'}) {
426 0 0       0 $dict = $dict ? $dict->copy($pdf) : PDFDict();
427 0         0 $self->{'Resources'} = $dict;
428             } else {
429 0         0 $dict = $self->{'Resources'};
430             }
431 0   0     0 $dict->{'Font'} //= PDFDict();
432              
433 0         0 my $resource = $dict->{'Font'}->val();
434 0   0     0 $resource->{$name} //= $font;
435 0 0 0     0 if (ref($dict) ne 'HASH' and $dict->is_obj($pdf)) {
436 0         0 $pdf->out_obj($dict);
437             }
438 0 0 0     0 if (ref($resource) ne 'HASH' and $resource->is_obj($pdf)) {
439 0         0 $pdf->out_obj($resource);
440             }
441              
442 0         0 return $self;
443             } # end of add_font()
444              
445             =head2 bbox
446              
447             $p->bbox($xmin,$ymin, $xmax,$ymax, $param)
448              
449             $p->bbox($xmin,$ymin, $xmax,$ymax)
450              
451             =over
452              
453             Specifies the bounding box for this and all child pages. If the values are
454             identical to those inherited, no change is made. C<$param> specifies the
455             attribute name so that other 'bounding box'es can be set with this method.
456              
457             =back
458              
459             =cut
460              
461             sub bbox {
462 0     0 1 0 my ($self, @bbox) = @_;
463 0   0     0 my $key = $bbox[4] || 'MediaBox';
464 0         0 my $inherited = $self->find_prop($key);
465              
466 0 0       0 if ($inherited) {
467 0         0 my $is_changed = 0;
468 0         0 my $i = 0;
469 0         0 foreach my $element ($inherited->elements()) {
470 0 0       0 $is_changed = 1 unless $element->val() == $bbox[$i++];
471             }
472 0 0 0     0 return $self if $i == 4 and not $is_changed;
473             }
474              
475 0         0 my $array = PDF::Builder::Basic::PDF::Array->new();
476 0         0 foreach my $element (@bbox[0 .. 3]) {
477 0         0 $array->add_elements(PDFNum($element));
478             }
479 0         0 $self->{$key} = $array;
480              
481 0         0 return $self;
482             }
483              
484             =head2 proc_set
485              
486             $p->proc_set(@entries)
487              
488             =over
489              
490             Ensures that the current resource contains all the entries in the proc_sets
491             listed. If necessary, it creates a local resource dictionary to achieve this.
492              
493             =back
494              
495             =cut
496              
497             sub proc_set {
498 426     426 1 2017 my ($self, @entries) = @_;
499              
500 426         1705 my $dict = $self->find_prop('Resources');
501 426 100 66     2011 if ($dict and defined $dict->{'ProcSet'}) {
502 192         668 my @missing = @entries;
503 192         833 foreach my $element ($dict->{'ProcSet'}->elements()) {
504 960         1767 @missing = grep { $_ ne $element } @missing;
  4800         10690  
505             }
506 192 50       729 return $self if scalar @missing == 0;
507 192 50       826 @entries = @missing if defined $self->{'Resources'};
508             }
509              
510 426 50       1373 unless (defined $self->{'Resources'}) {
511 426 100       1922 $self->{'Resources'} = $dict ? $dict->copy($self->_pdf()) : PDFDict();
512             }
513              
514 426 50       2063 $self->{'Resources'}{'ProcSet'} = PDFArray() unless defined $self->{'ProcSet'};
515              
516 426         1162 foreach my $element (@entries) {
517 2130         5047 $self->{'Resources'}{'ProcSet'}->add_elements(PDFName($element));
518             }
519              
520 426         1393 return $self;
521             } # end of proc_set()
522              
523             sub empty {
524 0     0 1 0 my ($self) = @_;
525 0         0 my $parent = $self->{'Parent'};
526              
527 0         0 $self->SUPER::empty();
528 0 0       0 if (defined $parent) {
529 0         0 $self->{'Parent'} = $parent;
530 0         0 weaken $self->{'Parent'};
531             }
532              
533 0         0 return $self;
534             }
535              
536             =head2 get_top
537              
538             $p->get_top()
539              
540             =over
541              
542             Returns the top of the pages tree.
543              
544             =back
545              
546             =cut
547              
548             sub get_top {
549 960     960 1 1888 my ($self) = @_;
550              
551 960         1560 my $top = $self;
552 960         2805 $top = $top->{'Parent'} while defined $top->{'Parent'};
553              
554 960         2429 return $top->realise();
555             }
556              
557             1;