File Coverage

blib/lib/PDF/API2/Basic/PDF/Pages.pm
Criterion Covered Total %
statement 113 180 62.7
branch 39 82 47.5
condition 6 41 14.6
subroutine 17 24 70.8
pod 10 16 62.5
total 185 343 53.9


line stmt bran cond sub pod time code
1             # Code in the PDF::API2::Basic::PDF namespace was originally copied from the
2             # Text::PDF distribution.
3             #
4             # Copyright Martin Hosken
5             #
6             # Martin Hosken's code may be used under the terms of the MIT license.
7             # Subsequent versions of the code have the same license as PDF::API2.
8              
9             package PDF::API2::Basic::PDF::Pages;
10              
11 39     39   465 use strict;
  39         84  
  39         1894  
12 39     39   235 use warnings;
  39         87  
  39         1225  
13              
14 39     39   207 use base 'PDF::API2::Basic::PDF::Dict';
  39         81  
  39         5773  
15              
16             our $VERSION = '2.043'; # VERSION
17              
18 39     39   306 use PDF::API2::Basic::PDF::Array;
  39         85  
  39         1000  
19 39     39   217 use PDF::API2::Basic::PDF::Dict;
  39         83  
  39         957  
20 39     39   228 use PDF::API2::Basic::PDF::Utils;
  39         93  
  39         3057  
21              
22 39     39   256 use Scalar::Util qw(weaken);
  39         78  
  39         82664  
23              
24             our %inst = map {$_ => 1} qw(Parent Type);
25              
26             =head1 NAME
27              
28             PDF::API2::Basic::PDF::Pages - Low-level page tree object
29              
30             =head1 DESCRIPTION
31              
32             A Pages object is the parent to other pages objects or to page objects
33             themselves.
34              
35             =head1 METHODS
36              
37             =head2 PDF::API2::Basic::PDF::Pages->new($pdf, $parent)
38              
39             This creates a new Pages object in a PDF. Notice that $parent here is
40             not the file context for the object but the parent pages object for
41             this pages. If we are using this class to create a root node, then
42             $parent should point to the file context, which is identified by not
43             having a Type of Pages. $pdf is the file object (or a reference to an
44             array of file objects) in which to create the new Pages object.
45              
46             =cut
47              
48             sub new {
49 306     306 1 771 my ($class, $pdf, $parent) = @_;
50 306 50 0     762 $pdf //= $class->get_top->{' parent'} if ref($class);
51              
52             # Prior to 2.034, $pdf could be an array of PDFs
53 306 50       852 if (ref($pdf) eq 'ARRAY') {
54 0 0       0 die 'Only one PDF is supported as of version 2.034' if scalar(@$pdf) > 1;
55 0         0 ($pdf) = @$pdf;
56             }
57              
58 306 50       621 $class = ref($class) if ref($class);
59 306         1019 my $self = $class->SUPER::new($pdf, $parent);
60              
61 306         830 $self->{'Type'} = PDFName('Pages');
62 306 100       831 $self->{'Parent'} = $parent if defined $parent;
63 306         770 $self->{'Count'} = PDFNum(0);
64 306         1406 $self->{'Kids'} = PDF::API2::Basic::PDF::Array->new();
65              
66 306         1052 $pdf->new_obj($self);
67 306 100       797 unless (defined $self->{'Parent'}) {
68 164         415 $pdf->{'Root'}->{'Pages'} = $self;
69 164         566 $pdf->out_obj($pdf->{'Root'});
70              
71 164         390 $self->{' parent'} = $pdf;
72 164         676 weaken $self->{' parent'};
73             }
74              
75 306 100       1032 weaken $self->{'Parent'} if defined $parent;
76              
77 306         888 return $self;
78             }
79              
80             sub _pdf {
81 426     426   611 my $self = shift();
82 426         900 return $self->get_top->{' parent'};
83             }
84              
85             =head2 $p->find_page($page_number)
86              
87             Returns the given page, using the page count values in the pages tree. Pages
88             start at 0.
89              
90             =cut
91              
92             sub find_page {
93 142     142 1 338 my ($self, $page_number) = @_;
94 142         299 my $top = $self->get_top();
95              
96 142         489 $top->find_page_recurse(\$page_number);
97             }
98              
99              
100             sub find_page_recurse {
101 142     142 0 313 my ($self, $page_number_ref) = @_;
102              
103 142 50       407 if ($self->{'Count'}->realise->val() <= $$page_number_ref) {
104 0         0 $$page_number_ref -= $self->{'Count'}->val();
105 0         0 return;
106             }
107              
108 142         275 my $result;
109 142         440 foreach my $kid ($self->{'Kids'}->realise->elements()) {
110 12 50       71 if ($kid->{'Type'}->realise->val() eq 'Page') {
    0          
111 12 50       63 return $kid if $$page_number_ref == 0;
112 0         0 $$page_number_ref--;
113             }
114             elsif ($result = $kid->realise->find_page_recurse($page_number_ref)) {
115 0         0 return $result;
116             }
117             }
118              
119 130         333 return;
120             }
121              
122             =head2 $p->add_page($page, $page_number)
123              
124             Inserts the page before the given $page_number. $page_number can be negative to
125             count from the END of the document. -1 is after the last page. Likewise
126             $page_number can be greater than the number of pages currently in the document,
127             to append.
128              
129             =cut
130              
131             sub add_page {
132 142     142 1 331 my ($self, $page, $page_number) = @_;
133 142         325 my $top = $self->get_top();
134              
135 142 100 66     498 $page_number = -1 unless defined $page_number and $page_number <= $top->{'Count'}->val();
136              
137 142         216 my $previous_page;
138 142 100       339 if ($page_number == -1) {
139 140         501 $previous_page = $top->find_page($top->{'Count'}->val() - 1);
140             }
141             else {
142 2 50       7 $page_number = $top->{'Count'}->val() + $page_number + 1 if $page_number < 0;
143 2         6 $previous_page = $top->find_page($page_number);
144             }
145              
146 142         288 my $parent;
147 142 100       378 if (defined $previous_page->{'Parent'}) {
148 12         49 $parent = $previous_page->{'Parent'}->realise();
149             }
150             else {
151 130         202 $parent = $self;
152             }
153              
154 142         352 my $parent_kid_count = scalar $parent->{'Kids'}->realise->elements();
155              
156 142         233 my $page_index;
157 142 100       324 if ($page_number == -1) {
158 140         217 $page_index = -1;
159             }
160             else {
161 2         7 for ($page_index = 0; $page_index < $parent_kid_count; $page_index++) {
162 2 50       14 last if $parent->{'Kids'}{' val'}[$page_index] eq $previous_page;
163             }
164 2 50       7 $page_index = -1 if $page_index == $parent_kid_count;
165             }
166              
167 142         510 $parent->add_page_recurse($page->realise(), $page_index);
168 142         479 for ($parent = $page->{'Parent'}; defined $parent->{'Parent'}; $parent = $parent->{'Parent'}->realise()) {
169 0         0 $parent->set_modified();
170 0         0 $parent->{'Count'}->realise->{'val'}++;
171             }
172 142         360 $parent->set_modified();
173 142         436 $parent->{'Count'}->realise->{'val'}++;
174              
175 142         514 return $page;
176             }
177              
178             sub add_page_recurse {
179 142     142 0 305 my ($self, $page, $page_index) = @_;
180              
181 142         220 my $parent = $self;
182 142         245 my $max_kids_per_parent = 8; # Why?
183 142 50 33     370 if (scalar $parent->{'Kids'}->elements() >= $max_kids_per_parent and $parent->{'Parent'} and $page_index < 1) {
      0        
184 0         0 my $grandparent = $parent->{'Parent'}->realise();
185 0         0 $parent = $parent->new($parent->_pdf(), $grandparent);
186              
187 0         0 my $grandparent_kid_count = scalar $grandparent->{'Kids'}->realise->elements();
188 0         0 my $new_parent_index;
189 0         0 for ($new_parent_index = 0; $new_parent_index < $grandparent_kid_count; $new_parent_index++) {
190 0 0       0 last if $grandparent->{'Kids'}{' val'}[$new_parent_index] eq $self;
191             }
192 0         0 $new_parent_index++;
193 0 0       0 $new_parent_index = -1 if $new_parent_index > $grandparent_kid_count;
194 0         0 $grandparent->add_page_recurse($parent, $new_parent_index);
195             }
196             else {
197 142         374 $parent->set_modified();
198             }
199              
200 142 100       414 if ($page_index < 0) {
201 140         207 push @{$parent->{'Kids'}->realise->{' val'}}, $page;
  140         357  
202             }
203             else {
204 2         5 splice @{$parent->{'Kids'}{' val'}}, $page_index, 0, $page;
  2         5  
205             }
206 142         299 $page->{'Parent'} = $parent;
207 142         444 weaken $page->{'Parent'};
208             }
209              
210             sub set_modified {
211 284     284 0 414 my $self = shift();
212 284         528 $self->_pdf->out_obj($self);
213             }
214              
215             # Previously documented but not implemented
216 0     0 0 0 sub rebuild_tree { return; }
217              
218             =head2 @objects = $p->get_pages()
219              
220             Returns a list of page objects in the document in page order
221              
222             =cut
223              
224             sub get_pages {
225 0     0 1 0 my $self = shift();
226 0         0 return $self->get_top->get_pages_recurse();
227             }
228              
229             # Renamed for clarity
230 0     0 0 0 sub get_kids { return get_pages_recurse(@_) }
231              
232             sub get_pages_recurse {
233 0     0 0 0 my $self = shift();
234 0         0 my @pages;
235              
236 0         0 foreach my $kid ($self->{'Kids'}->elements()) {
237 0         0 $kid->realise();
238 0 0       0 if ($kid->{'Type'}->val() eq 'Pages') {
239 0         0 push @pages, $kid->get_pages_recurse();
240             }
241             else {
242 0         0 push @pages, $kid;
243             }
244             }
245              
246 0         0 return @pages;
247             }
248              
249             =head2 $p->find_prop($key)
250              
251             Searches up through the inheritance tree to find a property.
252              
253             =cut
254              
255             sub find_prop {
256 576     576 1 1072 my ($self, $prop) = @_;
257              
258 576 100       1677 if (defined $self->{$prop}) {
    100          
259 225 50 33     2010 if (ref($self->{$prop}) and $self->{$prop}->isa('PDF::API2::Basic::PDF::Objind')) {
260 225         788 return $self->{$prop}->realise();
261             }
262             else {
263 0         0 return $self->{$prop};
264             }
265             }
266             elsif (defined $self->{'Parent'}) {
267 167         462 return $self->{'Parent'}->find_prop($prop);
268             }
269              
270 184         394 return;
271             }
272              
273              
274             =head2 $p->add_font($pdf, $font)
275              
276             Creates or edits the resource dictionary at this level in the hierarchy. If
277             the font is already supported even through the hierarchy, then it is not added.
278              
279             =cut
280              
281             sub add_font {
282             # Maintainer's note: arguments are in a different order than what is shown in the POD
283 0     0 1 0 my ($self, $font, $pdf) = @_;
284 0         0 my $name = $font->{'Name'}->val();
285 0         0 my $dict = $self->find_prop('Resources');
286              
287 0 0 0     0 return $self if $dict and defined $dict->{'Font'} and defined $dict->{'Font'}{$name};
      0        
288              
289 0 0       0 unless (defined $self->{'Resources'}) {
290 0 0       0 $dict = $dict ? $dict->copy($pdf) : PDFDict();
291 0         0 $self->{'Resources'} = $dict;
292             }
293             else {
294 0         0 $dict = $self->{'Resources'};
295             }
296 0   0     0 $dict->{'Font'} //= PDFDict();
297              
298 0         0 my $resource = $dict->{'Font'}->val();
299 0   0     0 $resource->{$name} //= $font;
300 0 0 0     0 if (ref($dict) ne 'HASH' and $dict->is_obj($pdf)) {
301 0         0 $pdf->out_obj($dict);
302             }
303 0 0 0     0 if (ref($resource) ne 'HASH' and $resource->is_obj($pdf)) {
304 0         0 $pdf->out_obj($resource);
305             }
306              
307 0         0 return $self;
308             }
309              
310              
311             =head2 $p->bbox($xmin, $ymin, $xmax, $ymax, [$param])
312              
313             Specifies the bounding box for this and all child pages. If the values are
314             identical to those inherited then no change is made. $param specifies the attribute
315             name so that other 'bounding box'es can be set with this method.
316              
317             =cut
318              
319             sub bbox {
320 0     0 1 0 my ($self, @bbox) = @_;
321 0   0     0 my $key = $bbox[4] || 'MediaBox';
322 0         0 my $inherited = $self->find_prop($key);
323              
324 0 0       0 if ($inherited) {
325 0         0 my $is_changed;
326 0         0 my $i = 0;
327 0         0 foreach my $element ($inherited->elements()) {
328 0 0       0 $is_changed = 1 unless $element->val() == $bbox[$i++];
329             }
330 0 0 0     0 return $self if $i == 4 and not $is_changed;
331             }
332              
333 0         0 my $array = PDF::API2::Basic::PDF::Array->new();
334 0         0 foreach my $element (@bbox[0..3]) {
335 0         0 $array->add_elements(PDFNum($element));
336             }
337 0         0 $self->{$key} = $array;
338              
339 0         0 return $self;
340             }
341              
342             =head2 $p->proc_set(@entries)
343              
344             Ensures that the current resource contains all the entries in the proc_sets
345             listed. If necessary it creates a local resource dictionary to achieve this.
346              
347             =cut
348              
349             sub proc_set {
350 306     306 1 901 my ($self, @entries) = @_;
351              
352 306         825 my $dict = $self->find_prop('Resources');
353 306 100 66     1209 if ($dict and defined $dict->{'ProcSet'}) {
354 142         371 my @missing = @entries;
355 142         473 foreach my $element ($dict->{'ProcSet'}->elements()) {
356 710         1114 @missing = grep { $_ ne $element } @missing;
  3550         6885  
357             }
358 142 50       451 return $self if scalar @missing == 0;
359 142 50       417 @entries = @missing if defined $self->{'Resources'};
360             }
361              
362 306 50       766 unless (defined $self->{'Resources'}) {
363 306 100       1145 $self->{'Resources'} = $dict ? $dict->copy($self->_pdf()) : PDFDict();
364             }
365              
366 306 50       1402 $self->{'Resources'}{'ProcSet'} = PDFArray() unless defined $self->{'ProcSet'};
367              
368 306         702 foreach my $e (@entries) {
369 1530         3067 $self->{'Resources'}{'ProcSet'}->add_elements(PDFName($e));
370             }
371              
372 306         774 return $self;
373             }
374              
375             sub empty {
376 0     0 1 0 my $self = shift();
377 0         0 my $parent = $self->{'Parent'};
378              
379 0         0 $self->SUPER::empty();
380 0 0       0 if (defined $parent) {
381 0         0 $self->{'Parent'} = $parent;
382 0         0 weaken $self->{'Parent'};
383             }
384              
385 0         0 return $self;
386             }
387              
388             =head2 $p->get_top
389              
390             Returns the top of the pages tree
391              
392             =cut
393              
394             sub get_top {
395 710     710 1 1001 my $self = shift();
396              
397 710         922 my $top = $self;
398 710         1632 $top = $top->{'Parent'} while defined $top->{'Parent'};
399              
400 710         1438 return $top->realise();
401             }
402              
403             1;