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 40     40   332 use strict;
  40         84  
  40         1660  
12 40     40   213 use warnings;
  40         82  
  40         1114  
13              
14 40     40   222 use base 'PDF::API2::Basic::PDF::Dict';
  40         77  
  40         5721  
15              
16             our $VERSION = '2.045'; # VERSION
17              
18 40     40   288 use PDF::API2::Basic::PDF::Array;
  40         93  
  40         942  
19 40     40   209 use PDF::API2::Basic::PDF::Dict;
  40         128  
  40         884  
20 40     40   215 use PDF::API2::Basic::PDF::Utils;
  40         82  
  40         2927  
21              
22 40     40   252 use Scalar::Util qw(weaken);
  40         87  
  40         79456  
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 736 my ($class, $pdf, $parent) = @_;
50 306 50 0     711 $pdf //= $class->get_top->{' parent'} if ref($class);
51              
52             # Prior to 2.034, $pdf could be an array of PDFs
53 306 50       820 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       661 $class = ref($class) if ref($class);
59 306         1114 my $self = $class->SUPER::new($pdf, $parent);
60              
61 306         842 $self->{'Type'} = PDFName('Pages');
62 306 100       923 $self->{'Parent'} = $parent if defined $parent;
63 306         762 $self->{'Count'} = PDFNum(0);
64 306         1358 $self->{'Kids'} = PDF::API2::Basic::PDF::Array->new();
65              
66 306         1026 $pdf->new_obj($self);
67 306 100       814 unless (defined $self->{'Parent'}) {
68 164         366 $pdf->{'Root'}->{'Pages'} = $self;
69 164         536 $pdf->out_obj($pdf->{'Root'});
70              
71 164         335 $self->{' parent'} = $pdf;
72 164         768 weaken $self->{' parent'};
73             }
74              
75 306 100       1057 weaken $self->{'Parent'} if defined $parent;
76              
77 306         897 return $self;
78             }
79              
80             sub _pdf {
81 426     426   685 my $self = shift();
82 426         835 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 297 my ($self, $page_number) = @_;
94 142         315 my $top = $self->get_top();
95              
96 142         446 $top->find_page_recurse(\$page_number);
97             }
98              
99              
100             sub find_page_recurse {
101 142     142 0 300 my ($self, $page_number_ref) = @_;
102              
103 142 50       878 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         260 my $result;
109 142         441 foreach my $kid ($self->{'Kids'}->realise->elements()) {
110 12 50       58 if ($kid->{'Type'}->realise->val() eq 'Page') {
    0          
111 12 50       77 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         318 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 327 my ($self, $page, $page_number) = @_;
133 142         334 my $top = $self->get_top();
134              
135 142 100 66     854 $page_number = -1 unless defined $page_number and $page_number <= $top->{'Count'}->val();
136              
137 142         228 my $previous_page;
138 142 100       323 if ($page_number == -1) {
139 140         508 $previous_page = $top->find_page($top->{'Count'}->val() - 1);
140             }
141             else {
142 2 50       5 $page_number = $top->{'Count'}->val() + $page_number + 1 if $page_number < 0;
143 2         5 $previous_page = $top->find_page($page_number);
144             }
145              
146 142         252 my $parent;
147 142 100       373 if (defined $previous_page->{'Parent'}) {
148 12         39 $parent = $previous_page->{'Parent'}->realise();
149             }
150             else {
151 130         229 $parent = $self;
152             }
153              
154 142         361 my $parent_kid_count = scalar $parent->{'Kids'}->realise->elements();
155              
156 142         230 my $page_index;
157 142 100       322 if ($page_number == -1) {
158 140         242 $page_index = -1;
159             }
160             else {
161 2         7 for ($page_index = 0; $page_index < $parent_kid_count; $page_index++) {
162 2 50       10 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         485 $parent->add_page_recurse($page->realise(), $page_index);
168 142         502 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         351 $parent->set_modified();
173 142         450 $parent->{'Count'}->realise->{'val'}++;
174              
175 142         355 return $page;
176             }
177              
178             sub add_page_recurse {
179 142     142 0 287 my ($self, $page, $page_index) = @_;
180              
181 142         225 my $parent = $self;
182 142         222 my $max_kids_per_parent = 8; # Why?
183 142 50 33     389 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         383 $parent->set_modified();
198             }
199              
200 142 100       409 if ($page_index < 0) {
201 140         251 push @{$parent->{'Kids'}->realise->{' val'}}, $page;
  140         352  
202             }
203             else {
204 2         4 splice @{$parent->{'Kids'}{' val'}}, $page_index, 0, $page;
  2         6  
205             }
206 142         342 $page->{'Parent'} = $parent;
207 142         523 weaken $page->{'Parent'};
208             }
209              
210             sub set_modified {
211 284     284 0 413 my $self = shift();
212 284         520 $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 1063 my ($self, $prop) = @_;
257              
258 576 100       1668 if (defined $self->{$prop}) {
    100          
259 225 50 33     1376 if (ref($self->{$prop}) and $self->{$prop}->isa('PDF::API2::Basic::PDF::Objind')) {
260 225         760 return $self->{$prop}->realise();
261             }
262             else {
263 0         0 return $self->{$prop};
264             }
265             }
266             elsif (defined $self->{'Parent'}) {
267 167         448 return $self->{'Parent'}->find_prop($prop);
268             }
269              
270 184         417 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 900 my ($self, @entries) = @_;
351              
352 306         826 my $dict = $self->find_prop('Resources');
353 306 100 66     1042 if ($dict and defined $dict->{'ProcSet'}) {
354 142         369 my @missing = @entries;
355 142         462 foreach my $element ($dict->{'ProcSet'}->elements()) {
356 710         1157 @missing = grep { $_ ne $element } @missing;
  3550         6912  
357             }
358 142 50       440 return $self if scalar @missing == 0;
359 142 50       457 @entries = @missing if defined $self->{'Resources'};
360             }
361              
362 306 50       722 unless (defined $self->{'Resources'}) {
363 306 100       1137 $self->{'Resources'} = $dict ? $dict->copy($self->_pdf()) : PDFDict();
364             }
365              
366 306 50       1211 $self->{'Resources'}{'ProcSet'} = PDFArray() unless defined $self->{'ProcSet'};
367              
368 306         704 foreach my $e (@entries) {
369 1530         3069 $self->{'Resources'}{'ProcSet'}->add_elements(PDFName($e));
370             }
371              
372 306         914 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 977 my $self = shift();
396              
397 710         936 my $top = $self;
398 710         1564 $top = $top->{'Parent'} while defined $top->{'Parent'};
399              
400 710         1433 return $top->realise();
401             }
402              
403             1;