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   321 use strict;
  39         82  
  39         1663  
12 39     39   210 use warnings;
  39         116  
  39         1137  
13              
14 39     39   209 use base 'PDF::API2::Basic::PDF::Dict';
  39         91  
  39         6083  
15              
16             our $VERSION = '2.044'; # VERSION
17              
18 39     39   296 use PDF::API2::Basic::PDF::Array;
  39         88  
  39         970  
19 39     39   200 use PDF::API2::Basic::PDF::Dict;
  39         90  
  39         867  
20 39     39   204 use PDF::API2::Basic::PDF::Utils;
  39         90  
  39         2953  
21              
22 39     39   244 use Scalar::Util qw(weaken);
  39         79  
  39         81459  
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 792 my ($class, $pdf, $parent) = @_;
50 306 50 0     741 $pdf //= $class->get_top->{' parent'} if ref($class);
51              
52             # Prior to 2.034, $pdf could be an array of PDFs
53 306 50       873 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       1001 $class = ref($class) if ref($class);
59 306         1003 my $self = $class->SUPER::new($pdf, $parent);
60              
61 306         850 $self->{'Type'} = PDFName('Pages');
62 306 100       888 $self->{'Parent'} = $parent if defined $parent;
63 306         810 $self->{'Count'} = PDFNum(0);
64 306         1428 $self->{'Kids'} = PDF::API2::Basic::PDF::Array->new();
65              
66 306         1035 $pdf->new_obj($self);
67 306 100       868 unless (defined $self->{'Parent'}) {
68 164         421 $pdf->{'Root'}->{'Pages'} = $self;
69 164         532 $pdf->out_obj($pdf->{'Root'});
70              
71 164         390 $self->{' parent'} = $pdf;
72 164         748 weaken $self->{' parent'};
73             }
74              
75 306 100       1083 weaken $self->{'Parent'} if defined $parent;
76              
77 306         907 return $self;
78             }
79              
80             sub _pdf {
81 426     426   700 my $self = shift();
82 426         902 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 317 my ($self, $page_number) = @_;
94 142         299 my $top = $self->get_top();
95              
96 142         511 $top->find_page_recurse(\$page_number);
97             }
98              
99              
100             sub find_page_recurse {
101 142     142 0 322 my ($self, $page_number_ref) = @_;
102              
103 142 50       476 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         300 my $result;
109 142         460 foreach my $kid ($self->{'Kids'}->realise->elements()) {
110 12 50       73 if ($kid->{'Type'}->realise->val() eq 'Page') {
    0          
111 12 50       67 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         349 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 342 my ($self, $page, $page_number) = @_;
133 142         366 my $top = $self->get_top();
134              
135 142 100 66     572 $page_number = -1 unless defined $page_number and $page_number <= $top->{'Count'}->val();
136              
137 142         269 my $previous_page;
138 142 100       336 if ($page_number == -1) {
139 140         538 $previous_page = $top->find_page($top->{'Count'}->val() - 1);
140             }
141             else {
142 2 50       15 $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         292 my $parent;
147 142 100       419 if (defined $previous_page->{'Parent'}) {
148 12         53 $parent = $previous_page->{'Parent'}->realise();
149             }
150             else {
151 130         232 $parent = $self;
152             }
153              
154 142         381 my $parent_kid_count = scalar $parent->{'Kids'}->realise->elements();
155              
156 142         235 my $page_index;
157 142 100       377 if ($page_number == -1) {
158 140         258 $page_index = -1;
159             }
160             else {
161 2         6 for ($page_index = 0; $page_index < $parent_kid_count; $page_index++) {
162 2 50       11 last if $parent->{'Kids'}{' val'}[$page_index] eq $previous_page;
163             }
164 2 50       6 $page_index = -1 if $page_index == $parent_kid_count;
165             }
166              
167 142         535 $parent->add_page_recurse($page->realise(), $page_index);
168 142         569 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         399 $parent->set_modified();
173 142         438 $parent->{'Count'}->realise->{'val'}++;
174              
175 142         404 return $page;
176             }
177              
178             sub add_page_recurse {
179 142     142 0 359 my ($self, $page, $page_index) = @_;
180              
181 142         221 my $parent = $self;
182 142         252 my $max_kids_per_parent = 8; # Why?
183 142 50 33     373 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         395 $parent->set_modified();
198             }
199              
200 142 100       507 if ($page_index < 0) {
201 140         238 push @{$parent->{'Kids'}->realise->{' val'}}, $page;
  140         416  
202             }
203             else {
204 2         4 splice @{$parent->{'Kids'}{' val'}}, $page_index, 0, $page;
  2         7  
205             }
206 142         369 $page->{'Parent'} = $parent;
207 142         484 weaken $page->{'Parent'};
208             }
209              
210             sub set_modified {
211 284     284 0 444 my $self = shift();
212 284         544 $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 1099 my ($self, $prop) = @_;
257              
258 576 100       1720 if (defined $self->{$prop}) {
    100          
259 225 50 33     1549 if (ref($self->{$prop}) and $self->{$prop}->isa('PDF::API2::Basic::PDF::Objind')) {
260 225         803 return $self->{$prop}->realise();
261             }
262             else {
263 0         0 return $self->{$prop};
264             }
265             }
266             elsif (defined $self->{'Parent'}) {
267 167         525 return $self->{'Parent'}->find_prop($prop);
268             }
269              
270 184         428 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 934 my ($self, @entries) = @_;
351              
352 306         864 my $dict = $self->find_prop('Resources');
353 306 100 66     1164 if ($dict and defined $dict->{'ProcSet'}) {
354 142         396 my @missing = @entries;
355 142         492 foreach my $element ($dict->{'ProcSet'}->elements()) {
356 710         1166 @missing = grep { $_ ne $element } @missing;
  3550         7519  
357             }
358 142 50       461 return $self if scalar @missing == 0;
359 142 50       444 @entries = @missing if defined $self->{'Resources'};
360             }
361              
362 306 50       801 unless (defined $self->{'Resources'}) {
363 306 100       1193 $self->{'Resources'} = $dict ? $dict->copy($self->_pdf()) : PDFDict();
364             }
365              
366 306 50       1289 $self->{'Resources'}{'ProcSet'} = PDFArray() unless defined $self->{'ProcSet'};
367              
368 306         785 foreach my $e (@entries) {
369 1530         3208 $self->{'Resources'}{'ProcSet'}->add_elements(PDFName($e));
370             }
371              
372 306         805 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 1055 my $self = shift();
396              
397 710         1023 my $top = $self;
398 710         1658 $top = $top->{'Parent'} while defined $top->{'Parent'};
399              
400 710         1556 return $top->realise();
401             }
402              
403             1;