File Coverage

blib/lib/PDF/API2/Basic/PDF/Pages.pm
Criterion Covered Total %
statement 118 186 63.4
branch 43 88 48.8
condition 6 41 14.6
subroutine 17 24 70.8
pod 10 16 62.5
total 194 355 54.6


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   360 use strict;
  40         92  
  40         1607  
12 40     40   212 use warnings;
  40         77  
  40         2695  
13              
14 40     40   218 use base 'PDF::API2::Basic::PDF::Dict';
  40         96  
  40         6830  
15              
16             our $VERSION = '2.048'; # VERSION
17              
18 40     40   286 use PDF::API2::Basic::PDF::Array;
  40         83  
  40         1118  
19 40     40   178 use PDF::API2::Basic::PDF::Dict;
  40         108  
  40         924  
20 40     40   180 use PDF::API2::Basic::PDF::Utils;
  40         74  
  40         3912  
21              
22 40     40   233 use Scalar::Util qw(weaken);
  40         85  
  40         98156  
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 786 my ($class, $pdf, $parent) = @_;
50 306 50 0     820 $pdf //= $class->get_top->{' parent'} if ref($class);
51              
52             # Prior to 2.034, $pdf could be an array of PDFs
53 306 50       934 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       675 $class = ref($class) if ref($class);
59 306         1160 my $self = $class->SUPER::new($pdf, $parent);
60              
61 306         1013 $self->{'Type'} = PDFName('Pages');
62 306 100       940 $self->{'Parent'} = $parent if defined $parent;
63 306         881 $self->{'Count'} = PDFNum(0);
64 306         1818 $self->{'Kids'} = PDF::API2::Basic::PDF::Array->new();
65              
66 306         1230 $pdf->new_obj($self);
67 306 100       974 unless (defined $self->{'Parent'}) {
68 164         438 $pdf->{'Root'}->{'Pages'} = $self;
69 164         688 $pdf->out_obj($pdf->{'Root'});
70              
71 164         581 $self->{' parent'} = $pdf;
72 164         498 weaken $self->{' parent'};
73             }
74              
75 306 100       875 weaken $self->{'Parent'} if defined $parent;
76              
77 306         1062 return $self;
78             }
79              
80             sub _pdf {
81 426     426   643 my $self = shift();
82 426         960 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 133     133 1 316 my ($self, $page_number) = @_;
94 133         299 my $top = $self->get_top();
95              
96 133         520 $top->find_page_recurse(\$page_number);
97             }
98              
99              
100             sub find_page_recurse {
101 133     133 0 322 my ($self, $page_number_ref) = @_;
102              
103 133 50       463 if ($self->{'Count'}->realise->val() <= $$page_number_ref) {
104 0         0 $$page_number_ref -= $self->{'Count'}->val();
105 0         0 return;
106             }
107              
108 133         248 my $result;
109 133         498 foreach my $kid ($self->{'Kids'}->realise->elements()) {
110 3 50       33 if ($kid->{'Type'}->realise->val() eq 'Page') {
    0          
111 3 50       17 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         390 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 400 my ($self, $page, $page_number) = @_;
133 142         382 my $top = $self->get_top();
134              
135 142 100 66     540 $page_number = -1 unless defined $page_number and $page_number < $top->{'Count'}->val();
136              
137 142         260 my $previous_page;
138 142 100       373 if ($page_number == -1) {
139 140         352 $previous_page = $top->{' last_page'};
140 140 100       371 unless (defined $previous_page) {
141 133         638 $previous_page = $top->find_page($top->{'Count'}->val() - 1);
142             }
143 140         458 $top->{' last_page'} = $page;
144             }
145             else {
146 2 50       6 $page_number = $top->{'Count'}->val() + $page_number + 1 if $page_number < 0;
147 2 50       5 $page_number = 0 if $page_number < 0;
148 2 50       5 if ($top->{'Count'}->val() == scalar($top->{'Kids'}->realise->elements())) {
149 2         8 $previous_page = ($top->{'Kids'}->elements())[$page_number];
150             }
151             else {
152 0         0 $previous_page = $top->find_page($page_number);
153             }
154             }
155              
156 142         246 my $parent;
157 142 100       402 if (defined $previous_page->{'Parent'}) {
158 12         37 $parent = $previous_page->{'Parent'}->realise();
159             }
160             else {
161 130         257 $parent = $self;
162             }
163              
164 142         392 my $parent_kid_count = scalar $parent->{'Kids'}->realise->elements();
165              
166 142         264 my $page_index;
167 142 100       470 if ($page_number == -1) {
168 140         304 $page_index = -1;
169             }
170             else {
171 2         4 for ($page_index = 0; $page_index < $parent_kid_count; $page_index++) {
172 2 50       10 last if $parent->{'Kids'}{' val'}[$page_index] eq $previous_page;
173             }
174 2 50       4 $page_index = -1 if $page_index == $parent_kid_count;
175             }
176              
177 142         619 $parent->add_page_recurse($page->realise(), $page_index);
178 142         532 for ($parent = $page->{'Parent'}; defined $parent->{'Parent'}; $parent = $parent->{'Parent'}->realise()) {
179 0         0 $parent->set_modified();
180 0         0 $parent->{'Count'}->realise->{'val'}++;
181             }
182 142         463 $parent->set_modified();
183 142         389 $parent->{'Count'}->realise->{'val'}++;
184              
185 142         440 return $page;
186             }
187              
188             sub add_page_recurse {
189 142     142 0 369 my ($self, $page, $page_index) = @_;
190              
191 142         229 my $parent = $self;
192 142         290 my $max_kids_per_parent = 8; # Why?
193 142 50 33     397 if (scalar $parent->{'Kids'}->elements() >= $max_kids_per_parent and $parent->{'Parent'} and $page_index == -1) {
      0        
194 0         0 my $grandparent = $parent->{'Parent'}->realise();
195 0         0 $parent = $parent->new($parent->_pdf(), $grandparent);
196              
197 0         0 my $grandparent_kid_count = scalar $grandparent->{'Kids'}->realise->elements();
198 0         0 my $new_parent_index;
199 0         0 for ($new_parent_index = 0; $new_parent_index < $grandparent_kid_count; $new_parent_index++) {
200 0 0       0 last if $grandparent->{'Kids'}{' val'}[$new_parent_index] eq $self;
201             }
202 0         0 $new_parent_index++;
203 0 0       0 $new_parent_index = -1 if $new_parent_index > $grandparent_kid_count;
204 0         0 $grandparent->add_page_recurse($parent, $new_parent_index);
205             }
206             else {
207 142         444 $parent->set_modified();
208             }
209              
210 142 100       423 if ($page_index < 0) {
211 140         270 push @{$parent->{'Kids'}->realise->{' val'}}, $page;
  140         648  
212             }
213             else {
214 2         4 splice @{$parent->{'Kids'}{' val'}}, $page_index, 0, $page;
  2         6  
215             }
216 142         362 $page->{'Parent'} = $parent;
217 142         387 weaken $page->{'Parent'};
218             }
219              
220             sub set_modified {
221 284     284 0 428 my $self = shift();
222 284         568 $self->_pdf->out_obj($self);
223             }
224              
225             # Previously documented but not implemented
226 0     0 0 0 sub rebuild_tree { return; }
227              
228             =head2 @objects = $p->get_pages()
229              
230             Returns a list of page objects in the document in page order
231              
232             =cut
233              
234             sub get_pages {
235 0     0 1 0 my $self = shift();
236 0         0 return $self->get_top->get_pages_recurse();
237             }
238              
239             # Renamed for clarity
240 0     0 0 0 sub get_kids { return get_pages_recurse(@_) }
241              
242             sub get_pages_recurse {
243 0     0 0 0 my $self = shift();
244 0         0 my @pages;
245              
246 0         0 foreach my $kid ($self->{'Kids'}->elements()) {
247 0         0 $kid->realise();
248 0 0       0 if ($kid->{'Type'}->val() eq 'Pages') {
249 0         0 push @pages, $kid->get_pages_recurse();
250             }
251             else {
252 0         0 push @pages, $kid;
253             }
254             }
255              
256 0         0 return @pages;
257             }
258              
259             =head2 $p->find_prop($key)
260              
261             Searches up through the inheritance tree to find a property.
262              
263             =cut
264              
265             sub find_prop {
266 576     576 1 1169 my ($self, $prop) = @_;
267              
268 576 100       1942 if (defined $self->{$prop}) {
    100          
269 225 50 33     1939 if (ref($self->{$prop}) and $self->{$prop}->isa('PDF::API2::Basic::PDF::Objind')) {
270 225         1021 return $self->{$prop}->realise();
271             }
272             else {
273 0         0 return $self->{$prop};
274             }
275             }
276             elsif (defined $self->{'Parent'}) {
277 167         632 return $self->{'Parent'}->find_prop($prop);
278             }
279              
280 184         572 return;
281             }
282              
283              
284             =head2 $p->add_font($pdf, $font)
285              
286             Creates or edits the resource dictionary at this level in the hierarchy. If
287             the font is already supported even through the hierarchy, then it is not added.
288              
289             =cut
290              
291             sub add_font {
292             # Maintainer's note: arguments are in a different order than what is shown in the POD
293 0     0 1 0 my ($self, $font, $pdf) = @_;
294 0         0 my $name = $font->{'Name'}->val();
295 0         0 my $dict = $self->find_prop('Resources');
296              
297 0 0 0     0 return $self if $dict and defined $dict->{'Font'} and defined $dict->{'Font'}{$name};
      0        
298              
299 0 0       0 unless (defined $self->{'Resources'}) {
300 0 0       0 $dict = $dict ? $dict->copy($pdf) : PDFDict();
301 0         0 $self->{'Resources'} = $dict;
302             }
303             else {
304 0         0 $dict = $self->{'Resources'};
305             }
306 0   0     0 $dict->{'Font'} //= PDFDict();
307              
308 0         0 my $resource = $dict->{'Font'}->val();
309 0   0     0 $resource->{$name} //= $font;
310 0 0 0     0 if (ref($dict) ne 'HASH' and $dict->is_obj($pdf)) {
311 0         0 $pdf->out_obj($dict);
312             }
313 0 0 0     0 if (ref($resource) ne 'HASH' and $resource->is_obj($pdf)) {
314 0         0 $pdf->out_obj($resource);
315             }
316              
317 0         0 return $self;
318             }
319              
320              
321             =head2 $p->bbox($xmin, $ymin, $xmax, $ymax, [$param])
322              
323             Specifies the bounding box for this and all child pages. If the values are
324             identical to those inherited then no change is made. $param specifies the attribute
325             name so that other 'bounding box'es can be set with this method.
326              
327             =cut
328              
329             sub bbox {
330 0     0 1 0 my ($self, @bbox) = @_;
331 0   0     0 my $key = $bbox[4] || 'MediaBox';
332 0         0 my $inherited = $self->find_prop($key);
333              
334 0 0       0 if ($inherited) {
335 0         0 my $is_changed;
336 0         0 my $i = 0;
337 0         0 foreach my $element ($inherited->elements()) {
338 0 0       0 $is_changed = 1 unless $element->val() == $bbox[$i++];
339             }
340 0 0 0     0 return $self if $i == 4 and not $is_changed;
341             }
342              
343 0         0 my $array = PDF::API2::Basic::PDF::Array->new();
344 0         0 foreach my $element (@bbox[0..3]) {
345 0         0 $array->add_elements(PDFNum($element));
346             }
347 0         0 $self->{$key} = $array;
348              
349 0         0 return $self;
350             }
351              
352             =head2 $p->proc_set(@entries)
353              
354             Ensures that the current resource contains all the entries in the proc_sets
355             listed. If necessary it creates a local resource dictionary to achieve this.
356              
357             =cut
358              
359             sub proc_set {
360 306     306 1 1126 my ($self, @entries) = @_;
361              
362 306         1049 my $dict = $self->find_prop('Resources');
363 306 100 66     1346 if ($dict and defined $dict->{'ProcSet'}) {
364 142         427 my @missing = @entries;
365 142         550 foreach my $element ($dict->{'ProcSet'}->elements()) {
366 710         2424 @missing = grep { $_ ne $element } @missing;
  3550         7295  
367             }
368 142 50       470 return $self if scalar @missing == 0;
369 142 50       551 @entries = @missing if defined $self->{'Resources'};
370             }
371              
372 306 50       888 unless (defined $self->{'Resources'}) {
373 306 100       1318 $self->{'Resources'} = $dict ? $dict->copy($self->_pdf()) : PDFDict();
374             }
375              
376 306 50       1257 $self->{'Resources'}{'ProcSet'} = PDFArray() unless defined $self->{'ProcSet'};
377              
378 306         858 foreach my $e (@entries) {
379 1530         3328 $self->{'Resources'}{'ProcSet'}->add_elements(PDFName($e));
380             }
381              
382 306         913 return $self;
383             }
384              
385             sub empty {
386 0     0 1 0 my $self = shift();
387 0         0 my $parent = $self->{'Parent'};
388              
389 0         0 $self->SUPER::empty();
390 0 0       0 if (defined $parent) {
391 0         0 $self->{'Parent'} = $parent;
392 0         0 weaken $self->{'Parent'};
393             }
394              
395 0         0 return $self;
396             }
397              
398             =head2 $p->get_top
399              
400             Returns the top of the pages tree
401              
402             =cut
403              
404             sub get_top {
405 701     701 1 997 my $self = shift();
406              
407 701         990 my $top = $self;
408 701         2934 $top = $top->{'Parent'} while defined $top->{'Parent'};
409              
410 701         1586 return $top->realise();
411             }
412              
413             1;