File Coverage

blib/lib/PDF/Builder/Page.pm
Criterion Covered Total %
statement 204 307 66.4
branch 63 146 43.1
condition 29 82 35.3
subroutine 35 42 83.3
pod 20 29 68.9
total 351 606 57.9


line stmt bran cond sub pod time code
1             package PDF::Builder::Page;
2              
3 38     38   259 use base 'PDF::Builder::Basic::PDF::Pages';
  38         102  
  38         4502  
4              
5 38     38   241 use strict;
  38         72  
  38         758  
6 38     38   164 use warnings;
  38         68  
  38         1702  
7              
8             our $VERSION = '3.024'; # VERSION
9             our $LAST_UPDATE = '3.024'; # manually update whenever code is changed
10              
11 38     38   199 use Carp;
  38         73  
  38         2535  
12 38     38   226 use POSIX qw(floor);
  38         70  
  38         259  
13 38     38   2653 use Scalar::Util qw(looks_like_number weaken);
  38         89  
  38         1720  
14              
15 38     38   210 use PDF::Builder::Basic::PDF::Utils;
  38         82  
  38         2635  
16 38     38   32048 use PDF::Builder::Content;
  38         111  
  38         1493  
17 38     38   361 use PDF::Builder::Content::Text;
  38         92  
  38         759  
18 38     38   216 use PDF::Builder::Util;
  38         93  
  38         122246  
19              
20             =head1 NAME
21              
22             PDF::Builder::Page - Methods to interact with individual pages
23              
24             =head1 SYNOPSIS
25              
26             my $pdf = PDF::Builder->new();
27              
28             # Add a page to a new or existing PDF
29             my $page = $pdf->page();
30              
31             # Set the physical (media) page size
32             # Set prepress page boundaries, a convenience function for those times when
33             # it is not necessary to set other prepress (print-related) page boundaries
34             $page->size('letter'); # by common size name
35             #$page->size([0, 0, 612, 792]); # by points LLx,LLy, URx,URy
36              
37             # alternately, can set (or get) prepress page boundaries
38             $page->boundaries('media' => '12x18', 'trim' => 0.5 * 72);
39              
40             # Add an image
41             my $image = $pdf->image('/path/to/file.jpg');
42             $page->object($image, $x,$y, $w,$h);
43              
44             # Create a content object for text
45             my $text = $page->text();
46              
47             # Create a content object for drawing shapes
48             my $canvas = $page->graphics(); # or gfx()
49              
50             # Now to draw graphics (using $canvas object) and text (using $text object).
51             # NOTE that everything in the graphics (canvas) object will be laid down on
52             # the page BEFORE anything in the text object is laid down. That is,
53             # text will cover graphics, but not vice-versa. This is simply due to
54             # the order in which the objects were defined.
55            
56             =head1 METHODS
57              
58             =over
59              
60             =item $page = PDF::Builder::Page->new($pdf, $parent, $index)
61              
62             Returns a page object (called from $pdf->page()).
63              
64             =cut
65              
66             sub new {
67 194     194 1 513 my ($class, $pdf, $parent, $index) = @_;
68 194         344 my $self = {};
69              
70 194 50       457 $class = ref($class) if ref($class);
71 194         812 $self = $class->SUPER::new($pdf, $parent);
72 194         572 $self->{'Type'} = PDFName('Page');
73 194         755 $self->proc_set(qw( PDF Text ImageB ImageC ImageI ));
74 194         628 delete $self->{'Count'};
75 194         489 delete $self->{'Kids'};
76 194         780 $parent->add_page($self, $index);
77              
78             # copy global UU (if exists) to local, possibly to be overridden
79             # we can access UserUnit (should be not 1.0, if exists) but not userUnit
80 194 50       549 if (defined $self->{'Parent'}->{'UserUnit'}) {
81 0         0 my $UU = $self->{'Parent'}->{'UserUnit'}->{'val'};
82 0         0 $self->{' userUnit'} = $UU;
83             # AND set the local one if global is not 1.0
84             # (some readers don't let a page inherit the global UU)
85 0 0       0 if ($UU != 1.0) {
86 0         0 $self->userunit($UU);
87             }
88             } else {
89             # not setting a global userUnit, so default this one to 1.0
90 194         432 $self->{' userUnit'} = 1;
91             # don't set a local UserUnit for now
92             }
93              
94 194         611 return $self;
95             }
96              
97             #=item $page = PDF::Builder::Page->coerce($pdf, $pdfpage)
98             #
99             #Returns a page object converted from $pdfpage (called from $pdf->open_page()).
100             #
101             #=cut
102              
103             # appears to be unused TBD
104              
105             sub coerce {
106 0     0 0 0 my ($class, $pdf, $page) = @_;
107 0         0 my $self = $page;
108 0         0 bless $self, $class;
109 0         0 $self->{' apipdf'} = $pdf;
110 0         0 weaken $self->{' apipdf'};
111 0         0 return $self;
112             }
113              
114             #=item $page->update()
115             #
116             #Marks a page to be updated (by $pdf->update()).
117             #
118             #=cut
119              
120             # appears to be internal routine
121             # replace any use by call to out_obj: $self->{' apipdf'}->out_obj($self);
122              
123             # PDF::API2 2.042: DEPRECATED
124             # Marking the page as dirty should only be needed in rare cases
125             # when the page hash is being edited directly rather than through the API. In
126             # that case, the out_obj call can be made manually. There's no reason (that I
127             # can think of) to have a specific call just (and only) for Page objects.
128              
129             sub update {
130 1     1 0 8 my $self = shift;
131              
132 1         4 $self->{' apipdf'}->out_obj($self);
133 1         1 return $self;
134             }
135              
136             =back
137              
138             =head2 Page Size Methods
139              
140             =over
141              
142             =item $page->userunit($value)
143              
144             Sets the User Unit for this one page.
145             See L for more information.
146              
147             =cut
148              
149             sub userunit {
150 0     0 1 0 my ($self, $value) = @_;
151              
152 0 0       0 if (float($value) <= 0.0) {
153 0         0 warn "Invalid User Unit value '$value', set to 1.0";
154 0         0 $value = 1.0;
155             }
156              
157             # assume that even if $value is 1.0, it's being called for some reason
158             # (perhaps overriding non-1.0 global setting)
159 0         0 $PDF::Builder::global_pdf->verCheckOutput(1.6, "set User Unit");
160 0         0 $self->{' userUnit'} = float($value); # this is local (page) UU
161 0         0 $self->{'UserUnit'} = PDFNum(float($value));
162              
163 0         0 return $self;
164             }
165              
166             sub _bbox {
167 127     127   256 my ($box, $self, @corners) = @_;
168             # $box is the box name (e.g., MediaBox)
169             # $self points to the page object
170             # at least one element in @corners if setting. get if no elements.
171              
172             # if 1 or 3 elements in @corners, and [0] contains a letter, it's a name
173 127         161 my $isName = 0;
174 127 100 100     459 if (scalar @corners && $corners[0] =~ m/[a-z]/i) { $isName = 1; }
  29         63  
175              
176 127 100       279 if (scalar @corners == 0) {
    50          
177             # is a query ('get')
178             # if previously set for this page, just return array
179             # if not set, check parent (PDF), then up chain as far as MediaBox
180 78 100       148 if (defined $self->{$box}) {
181 53         124 return map { $_->val() } $self->{$box}->elements();
  212         336  
182             } else {
183 25         30 my $pdf = $self->{' api'};
184 25 100       71 if (defined $pdf->{'pages'}->{$box}) {
185             # parent (global) box is defined
186 17         48 return map { $_->val() } $pdf->{'pages'}->{$box}->elements();
  68         111  
187             } else {
188             # go up the chain until find defined global
189 8 100 100     32 if ($box eq 'ArtBox' || $box eq 'TrimBox' || $box eq 'BleedBox') {
      100        
190 6         8 $box = 'CropBox';
191             }
192 8 50 33     30 if ($box eq 'CropBox' && !defined $pdf->{'pages'}->{'CropBox'}) {
193 8         11 $box = 'MediaBox';
194             }
195 8 50 33     25 if ($box ne 'CropBox' && $box ne 'MediaBox') {
196             # invalid box name. silent error: just return Media Box
197 0         0 $box = 'MediaBox';
198             }
199 8         23 return map { $_->val() } $pdf->{'pages'}->{$box}->elements();
  32         54  
200             }
201             }
202            
203             } elsif (scalar @corners == 3) {
204             # have a name and one option (orient)
205 0         0 my ($name, %opts) = @corners;
206             # copy dashed option names to preferred undashed names
207 0 0 0     0 if (defined $opts{'-orient'} && !defined $opts{'orient'}) { $opts{'orient'} = delete($opts{'-orient'}); }
  0         0  
208              
209 0         0 @corners = page_size(($name)); # now 4 numeric values
210 0 0       0 if (defined $opts{'orient'}) {
211 0 0       0 if ($opts{'orient'} =~ m/^l/i) { # 'landscape' or just 'l'
212             # 0 0 W H -> 0 0 H W
213 0         0 my $temp;
214 0         0 $temp = $corners[2]; $corners[2] = $corners[3]; $corners[3] = $temp;
  0         0  
  0         0  
215             }
216             }
217             } else {
218             # name without [orient] option, or numeric coordinates given
219 49         152 @corners = page_size(@corners);
220             }
221              
222             # scale down size if User Unit given (e.g., Letter => 0 0 8.5 11)
223             # we have a global userUnit, and possibly a page userUnit overriding it
224 49 100       112 if ($isName) {
225 29         51 my $UU = $self->{' userUnit'};
226 29 50       58 if ($UU != 1.0) {
227 0         0 for (my $i=0; $i<4; $i++) {
228 0         0 $corners[$i] /= $UU;
229             }
230             }
231             }
232              
233 49         87 $self->{$box} = PDFArray( map { PDFNum(float($_)) } @corners );
  196         344  
234             # return 4 element array of box corners
235 49         141 return @corners;
236             }
237              
238             =item $page->mediabox($alias)
239              
240             =item $page->mediabox($alias, 'orient' => 'orientation')
241              
242             =item $page->mediabox($w,$h)
243              
244             =item $page->mediabox($llx,$lly, $urx,$ury)
245              
246             =item ($llx,$lly, $urx,$ury) = $page->mediabox()
247              
248             Sets or gets the Media Box for this one page.
249             See L for more information.
250             The method always returns the current bounds (after any set operation).
251              
252             =cut
253              
254             sub mediabox {
255 75     75 1 51942 return _bbox('MediaBox', @_);
256             }
257              
258             =item ($llx,$lly, $urx,$ury) = $page->get_mediabox()
259              
260             Gets the Media Box corner coordinates based on best estimates or the default.
261             These are in the order given in a mediabox call (4 coordinates).
262              
263             This method is B, and will likely be removed in the future. Use
264             the global (C<$pdf>) or page (C<$page>) mediabox() call with no parameters
265             instead.
266              
267             =cut
268              
269             sub get_mediabox {
270 9     9 1 47 my $self = shift();
271 9         25 return $self->mediabox();
272             }
273              
274             =item $page->cropbox($alias)
275              
276             =item $page->cropbox($alias, 'orient' => 'orientation')
277              
278             =item $page->cropbox($w,$h)
279              
280             =item $page->cropbox($llx,$lly, $urx,$ury)
281              
282             =item ($llx,$lly, $urx,$ury) = $page->cropbox()
283              
284             Sets or gets the Crop Box for this one page.
285             See L for more information.
286             The method always returns the current bounds (after any set operation).
287              
288             =cut
289              
290             sub cropbox {
291 13     13 1 3612 return _bbox('CropBox', @_);
292             }
293              
294             =item ($llx,$lly, $urx,$ury) = $page->get_cropbox()
295              
296             Gets the Crop Box based on best estimates or the default.
297              
298             This method is B, and will likely be removed in the future. Use
299             the global (C<$pdf>) or page (C<$page>) cropbox() call with no parameters
300             instead.
301              
302             =cut
303              
304             sub get_cropbox {
305 6     6 1 44 my $self = shift();
306 6         13 return $self->cropbox();
307             }
308              
309             =item $page->bleedbox($alias)
310              
311             =item $page->bleedbox($alias, 'orient' => 'orientation')
312              
313             =item $page->bleedbox($w,$h)
314              
315             =item $page->bleedbox($llx,$lly, $urx,$ury)
316              
317             =item ($llx,$lly, $urx,$ury) = $page->bleedbox()
318              
319             Sets or gets or gets the Bleed Box for this one page.
320             See L for more information.
321             The method always returns the current bounds (after any set operation).
322              
323             =cut
324              
325             sub bleedbox {
326 13     13 1 3565 return _bbox('BleedBox', @_);
327             }
328              
329             =item ($llx,$lly, $urx,$ury) = $page->get_bleedbox()
330              
331             Gets the Bleed Box based on best estimates or the default.
332              
333             This method is B, and will likely be removed in the future. Use
334             the global (C<$pdf>) or page (C<$page>) bleedbox() call with no parameters
335             instead.
336              
337             =cut
338              
339             sub get_bleedbox {
340 6     6 1 36 my $self = shift();
341 6         13 return $self->bleedbox();
342             }
343              
344             =item $page->trimbox($alias)
345              
346             =item $page->trimbox($alias, 'orient' => 'orientation')
347              
348             =item $page->trimbox($w,$h)
349              
350             =item $page->trimbox($llx,$lly, $urx,$ury)
351              
352             =item ($llx,$lly, $urx,$ury) = $page->trimbox()
353              
354             Sets or gets the Trim Box for this one page.
355             See L for more information.
356             The method always returns the current bounds (after any set operation).
357              
358             =cut
359              
360             sub trimbox {
361 13     13 1 3577 return _bbox('TrimBox', @_);
362             }
363              
364             =item ($llx,$lly, $urx,$ury) = $page->get_trimbox()
365              
366             Gets the Trim Box based on best estimates or the default.
367              
368             This method is B, and will likely be removed in the future. Use
369             the global (C<$pdf>) or page (C<$page>) trimbox() call with no parameters
370             instead.
371              
372             =cut
373              
374             sub get_trimbox {
375 6     6 1 32 my $self = shift();
376 6         13 return $self->trimbox();
377             }
378              
379             =item $page->artbox($alias)
380              
381             =item $page->artbox($alias, 'orient' => 'orientation')
382              
383             =item $page->artbox($w,$h)
384              
385             =item $page->artbox($llx,$lly, $urx,$ury)
386              
387             =item ($llx,$lly, $urx,$ury) = $page->artbox()
388              
389             Sets or gets the Art Box for this one page.
390             See L for more information.
391             The method always returns the current bounds (after any set operation).
392              
393             =cut
394              
395             sub artbox {
396 13     13 1 3614 return _bbox('ArtBox', @_);
397             }
398              
399             =item ($llx,$lly, $urx,$ury) = $page->get_artbox()
400              
401             Gets the Art Box based on best estimates or the default.
402              
403             This method is B, and will likely be removed in the future. Use
404             the global (C<$pdf>) or page (C<$page>) artbox() call with no parameters
405             instead.
406              
407             =cut
408              
409             sub get_artbox {
410 6     6 1 33 my $self = shift();
411 6         13 return $self->artbox();
412             }
413              
414             =item $page->rotate($deg)
415              
416             Rotates the page by the given degrees, which must be a multiple of 90.
417             An angle that is not a multiple of 90 will be rounded to the nearest 90
418             degrees, with a message.
419              
420             Note that the rotation angle is I for a positive amount!
421             E.g., a rotation of +90 (or -270) will have the bottom edge of the paper at
422             the left of the screen.
423             After rotating the page 180 degrees, C<[0, 0]> (originally lower left corner)
424             will be be in the top right corner of the page, rather than the bottom left.
425             X will increase to the right, and Y will increase downward.
426              
427             (This allows you to auto-rotate to landscape without changing the mediabox!
428             There are other ways to accomplish this end, such as using the C
429             method, which will not change the coordinate system (move the origin).)
430              
431             Do not confuse this C call with the I rotation
432             (Content.pm) C, which permits any angle, is of opposite direction,
433             and does not shift the origin!
434              
435             B C
436              
437             This has been added for PDF::API2 compatibility.
438              
439             =cut
440              
441 0     0 0 0 sub rotation { return rotate(@_); } ## no critic
442              
443             sub rotate {
444 0     0 1 0 my ($self, $degrees) = @_;
445              
446 0   0     0 $degrees //= 0;
447             # Ignore rotation of 360 or more (in either direction)
448 0         0 $degrees = $degrees % 360; # range [0, 360)
449              
450 0 0       0 if ($degrees % 90) {
451 0         0 my $deg = int(($degrees + 45)/90)*90;
452 0         0 carp "page rotate($degrees) invalid, not multiple of 90 degrees.\nChanged to $deg";
453 0         0 $degrees = $deg;
454             }
455              
456 0         0 $self->{'Rotate'} = PDFNum($degrees);
457              
458 0         0 return $self;
459             }
460              
461             =item $page->size($size) # Set
462              
463             =item @rectangle = $page->size() # Get
464              
465             Set the physical page size or return the coordinates of the rectangle enclosing
466             the physical page size.
467             This is an alternate method provided for compatibility with PDF::API2.
468              
469             # Set the physical page (media) size using a common size name
470             $page->size('letter');
471              
472             # Set the page size using coordinates in points (X1, Y1, X2, Y2)
473             $page->size([0, 0, 612, 792]);
474              
475             # Get the page coordinates in points
476             my @rectangle = $page->size();
477              
478             See Page Sizes below for possible values.
479             The size method is a convenient shortcut for setting the PDF's media box when
480             other prepress (print-related) page boundaries aren't required. It's equivalent
481             to the following:
482              
483             # Set
484             $page = $page->boundaries('media' => $size);
485              
486             # Get
487             @rectangle = $page->boundaries()->{'media'}->@*;
488              
489             =cut
490            
491             sub size {
492 1     1 1 2124 my $self = shift;
493              
494 1 50       4 if (@_) { # Set (also returns object, for easy chaining)
495 1         4 return $self->boundaries('media' => @_);
496             } else { # Get
497 0         0 my %boundaries = $self->boundaries();
498 0         0 return @{ $boundaries{'media'} };
  0         0  
499             }
500             }
501              
502             =item $page = $page->boundaries(%boundaries)
503              
504             =item \%boundaries = $page->boundaries()
505              
506             Set prepress page boundaries to facilitate printing. Returns the current page
507             boundaries if called without arguments.
508             This is an alternate method provided for compatibility with PDF::API2.
509              
510             # Set
511             $page->boundaries(
512             # 13x19 inch physical sheet size
513             'media' => '13x19',
514             # sheet content is 11x17 with 0.25" bleed
515             'bleed' => [0.75 * 72, 0.75 * 72, 12.25 * 72, 18.25 * 72],
516             # 11x17 final trimmed size
517             'trim' => 0.25 * 72,
518             );
519              
520             # Get
521             %boundaries = $page->boundaries();
522             ($x1,$y1, $x2,$y2) = $page->boundaries('trim');
523              
524             The C<%boundaries> hash contains one or more page boundary keys (see Page
525             Boundaries) to set or replace, each with a corresponding size (see Page Sizes).
526              
527             If called without arguments, the returned hashref will contain (Get) all five
528             boundaries. If called with one string argument, it returns the coordinates for
529             the specified page boundary. If more than one boundary type is given, only the
530             first is processed, and a warning is given that the remainder are ignored.
531              
532             =back
533              
534             =head3 Page Boundaries
535              
536             PDF defines five page boundaries. When creating PDFs for print shops, you'll
537             most commonly use just the media box and trim box. Traditional print shops may
538             also use the bleed box when adding printer's marks and other information.
539              
540             =over
541              
542             =item media
543              
544             The media box defines the boundaries of the physical medium on which the page is
545             to be printed. It may include any extended area surrounding the finished page
546             for bleed, printing marks, or other such purposes. The default value is as
547             defined for PDF, a US letter page (8.5" x 11").
548              
549             =item crop
550              
551             The crop box defines the region to which the contents of the page shall be
552             clipped (cropped) when displayed or printed. The default value is the page's
553             media box.
554             This is a historical page boundary. You'll likely want to set the bleed and/or
555             trim boxes instead.
556              
557             =item bleed
558              
559             The bleed box defines the region to which the contents of the page shall be
560             clipped when output in a production environment. This may include any extra
561             bleed area needed to accommodate the physical limitations of cutting, folding,
562             and trimming equipment. The actual printed page (media box) may include
563             printing marks that fall outside the bleed box. The default value is the page's
564             crop box.
565              
566             =item trim
567              
568             The trim box defines the intended dimensions of the finished page after
569             trimming. It may be smaller than the media box to allow for production-related
570             content, such as printing instructions, cut marks, or color bars. The default
571             value is the page's crop box.
572              
573             =item art
574              
575             The art box defines the extent of the page's meaningful content (including
576             potential white space) as intended by the page's creator. The default value is
577             the page's crop box.
578              
579             =back
580              
581             =head3 Page Sizes
582              
583             PDF page sizes are stored as rectangular coordinates. For convenience,
584             PDF::Builder also supports a number of aliases and shortcuts that are more
585             human-friendly. The following formats are available:
586              
587             =over
588              
589             =item a standard paper size
590              
591             $page->boundaries('media' => 'A4');
592              
593             Aliases for the most common paper sizes are built in (case-insensitive).
594             US: Letter, Legal, Ledger, Tabloid (and others)
595             Metric: 4A0, 2A0, A0 - A6, 4B0, 2B0, and B0 - B6 (and others)
596              
597             =item a "WxH" string in inches
598              
599             $page->boundaries('media' => '8.5x11');
600              
601             Many US paper sizes are commonly identified by their size in inches rather than
602             by a particular name. These can be passed as strings with the width and height
603             separated by an C.
604             Examples: C<4x6>, C<12x18>, C<8.5x11>
605              
606             =item a number representing a reduction (in points) from the next-larger box
607              
608             For example, a 12" x 18" physical sheet to be trimmed down to an 11" x 17" sheet
609             can be specified as follows:
610              
611             # Note: There are 72 points per inch
612             $page->boundaries('media' => '12x18', 'trim' => 0.5 * 72);
613              
614             # Equivalent
615             $page->boundaries('media' => [0, 0, 12 * 72, 18 * 72],
616             'trim' => [0.5 * 72, 0.5 * 72, 11.5 * 72, 17.5 * 72]);
617              
618             This example shows a 12" x 18" physical sheet that will be reduced to a final
619             size of 11" x 17" by trimming 0.5" from each edge. The smaller page boundary is
620             assumed to be centered within the larger one.
621              
622             The "next-larger box" follows this order, stopping at the first defined value:
623              
624             art -> trim -> bleed -> media
625             crop -> media
626              
627             This option isn't available for the media box, since it is by definition, the
628             largest boundary.
629              
630             =item [$width, $height] in points
631              
632             $page->boundaries('media' => [8.5 * 72, 11 * 7.2]);
633              
634             For other page or boundary sizes, the width and height (in points) can be given
635             directly as an array.
636              
637             =item [$x1, $y1, $x2, $y2] in points
638              
639             $page->boundaries('media' => [0, 0, 8.5 * 72, 11 * 72]);
640              
641             Finally, the absolute (raw) coordinates of the bottom-left and top-right corners
642             of a rectangle can be specified.
643              
644             =cut
645              
646             sub _to_rectangle {
647 6     6   10 my $value = shift();
648              
649             # An array of two or four numbers in points
650 6 50       10 if (ref($value) eq 'ARRAY') {
651 0 0       0 if (@$value == 2) {
    0          
652 0         0 return (0, 0, @$value);
653             } elsif (@$value == 4) {
654 0         0 return @$value;
655             }
656 0         0 croak "Page boundary array must contain two or four numbers";
657             }
658              
659             # WxH in inches
660 6 100       26 if ($value =~ /^([0-9.]+)\s*x\s*([0-9.]+)$/) {
661 2         8 my ($w, $h) = ($1, $2);
662 2 50 33     20 if (looks_like_number($w) and looks_like_number($h)) {
663 2         7 return (0, 0, $w * 72, $h * 72);
664             }
665             }
666              
667             # Common names for page sizes
668 4         11 my %page_sizes = PDF::Builder::Resource::PaperSizes::get_paper_sizes();
669              
670 4 50       28 if ($page_sizes{lc $value}) {
671 4         7 return (0, 0, @{$page_sizes{lc $value}});
  4         58  
672             }
673              
674 0 0       0 if (ref($value)) {
675 0         0 croak "Unrecognized page size";
676             } else {
677 0         0 croak "Unrecognized page size: $value";
678             }
679             }
680              
681             sub boundaries {
682 6     6 1 4323 my $self = shift();
683              
684             # Get
685 6 50       20 if (@_ == 0) { # empty list -- do all boxes
    50          
686 0         0 my %boundaries;
687 0         0 foreach my $box (qw(Media Crop Bleed Trim Art)) {
688 0         0 $boundaries{lc($box)} = [$self->_bounding_box($box . 'Box')];
689             }
690 0         0 return %boundaries;
691             } elsif (@_ == 1) { # request one specific box
692 0         0 my $box = shift();
693             # apparently it is normal to have an array of boxes, and use only first
694             #if (@_) { # more than one box in arg list?
695             # carp "More than one box requested for boundaries(). Using only first ($box)";
696             #}
697 0         0 my @coordinates = $self->_bounding_box(ucfirst($box) . 'Box');
698 0         0 return @coordinates;
699             }
700              
701             # Set
702 6         15 my %boxes = @_;
703 6         12 foreach my $box (qw(media crop bleed trim art)) {
704 30 100       65 next unless exists $boxes{$box};
705              
706             # Special case: A single number as the value for anything other than
707             # MediaBox means to take the next larger size and reduce it by this
708             # amount in points on all four sides, provided the larger size was also
709             # included.
710 8         11 my $value = $boxes{$box};
711 8         10 my @rectangle;
712 8 100 66     29 if ($box ne 'media' and not ref($value) and looks_like_number($value)) {
      66        
713 2 50       10 my $parent = ($box eq 'crop' ? 'media' :
    50          
    50          
714             $box eq 'bleed' ? 'media' :
715             $box eq 'trim' ? 'bleed' : 'trim');
716 2 50 33     6 $parent = 'bleed' if $parent eq 'trim' and not $boxes{'trim'};
717 2 50 33     20 $parent = 'media' if $parent eq 'bleed' and not $boxes{'bleed'};
718 2 50 33     6 $parent = 'media' if $parent eq 'bleed' and not $boxes{'bleed'};
719 2 50       3 unless ($boxes{$parent}) {
720 0         0 croak "Single-number argument for $box requires $parent";
721             }
722              
723 2         4 @rectangle = @{$boxes{$parent}};
  2         4  
724 2         4 $rectangle[0] += $value;
725 2         3 $rectangle[1] += $value;
726 2         3 $rectangle[2] -= $value;
727 2         2 $rectangle[3] -= $value;
728             }
729             else {
730 6         11 @rectangle = _to_rectangle($value);
731             }
732              
733 8         24 my $box_name = ucfirst($box) . 'Box';
734 8         25 $self->_bounding_box($box_name, @rectangle);
735 8         19 $boxes{$box} = [@rectangle];
736             }
737              
738 6         15 return $self;
739             }
740              
741             sub _bounding_box {
742 8     8   2183 my $self = shift();
743 8         12 my $type = shift();
744              
745             # Get
746 8 100       13 unless (scalar @_) {
747 4         13 my $box = $self->find_prop($type);
748 4 50       10 unless ($box) {
749             # Default to letter (for historical PDF::API2 reasons, not per the
750             # PDF specification)
751 0 0       0 return (0, 0, 612, 792) if $type eq 'MediaBox';
752              
753             # Use defaults per PDF 1.7 section 14.11.2 Page Boundaries
754 0 0       0 return $self->_bounding_box('MediaBox') if $type eq 'CropBox';
755 0         0 return $self->_bounding_box('CropBox');
756             }
757 4         10 return map { $_->val() } $box->elements();
  16         24  
758             }
759              
760             # Set
761 4         12 $self->{$type} = PDFArray(map { PDFNum(float($_)) } page_size(@_));
  16         27  
762 4         8 return $self;
763             }
764              
765             sub fixcontents {
766 161     161 0 301 my ($self) = @_;
767              
768 161   66     624 $self->{'Contents'} = $self->{'Contents'} || PDFArray();
769 161 50       749 if (ref($self->{'Contents'}) =~ /Objind$/) {
770 0         0 $self->{'Contents'}->realise();
771             }
772 161 50       911 if (ref($self->{'Contents'}) !~ /Array$/) {
773 0         0 $self->{'Contents'} = PDFArray($self->{'Contents'});
774             }
775 161         352 return;
776             }
777              
778             sub content {
779 154     154 0 400 my ($self, $obj, $dir) = @_;
780              
781 154 50 33     626 if (defined($dir) && $dir > 0) {
782 0         0 $self->precontent($obj);
783             } else {
784 154         481 $self->addcontent($obj);
785             }
786 154 50       556 $self->{' apipdf'}->new_obj($obj) unless $obj->is_obj($self->{' apipdf'});
787 154         600 $obj->{' apipdf'} = $self->{' apipdf'};
788 154         330 $obj->{' api'} = $self->{' api'};
789 154         315 $obj->{' apipage'} = $self;
790              
791 154         530 weaken $obj->{' apipdf'};
792 154         375 weaken $obj->{' api'};
793 154         447 weaken $obj->{' apipage'};
794              
795 154         241 return $obj;
796             }
797              
798             sub addcontent {
799 154     154 0 389 my ($self, @objs) = @_;
800              
801 154         571 $self->fixcontents();
802 154         507 $self->{'Contents'}->add_elements(@objs);
803 154         258 return;
804             }
805              
806             sub precontent {
807 0     0 0 0 my ($self, @objs) = @_;
808              
809 0         0 $self->fixcontents();
810 0         0 unshift(@{$self->{'Contents'}->val()}, @objs);
  0         0  
811 0         0 return;
812             }
813              
814             =item $gfx = $page->gfx(%opts)
815              
816             =item $gfx = $page->gfx($prepend)
817              
818             =item $gfx = $page->gfx()
819              
820             Returns a graphics content object, for drawing paths and shapes.
821              
822             You may specify the "prepend" flag in the old or new way. The old way is to
823             give a single boolean value (0 false, non-zero true). The new way is to give
824             a hash element named 'prepend', with the same values.
825              
826             =over
827              
828             =item gfx(boolean_value $prepend)
829              
830             =item gfx('prepend' => boolean_value)
831              
832             =back
833              
834             If $prepend is I, or the option 'prepend' is given with a I value,
835             the content will be prepended to the page description (at the beginning of
836             the page's content stream).
837             Otherwise, it will be appended.
838             The default is I.
839              
840             =over
841              
842             =item gfx('compress' => boolean_value)
843              
844             =back
845              
846             You may specify a compression flag saying whether the drawing instructions
847             are to be compressed. If not given, the default is for the overall PDF
848             compression setting to be used (I by default).
849              
850             You may have more than one I object. They and I objects will be
851             output as objects and streams I, with all actions pertaining
852             to this I object appearing in one stream. However, note that graphics
853             and text objects are not fully independent of each other: the exit state
854             (linewidth, strokecolor, etc.) of one object is the entry state of the next
855             object in line to be output, and so on.
856              
857             If you intermix multiple I and I objects on
858             a page, the results may be confusing. Say you have $gfx1, $text1, $gfx2, and
859             $text2 on your page (I). PDF::Builder will output all the
860             $gfx1->I calls in one stream, then all the $text1->I calls in
861             the next stream, and likewise for $gfx2 usage and finally $text2.
862              
863             Then it's PDF's turn to confuse you. PDF will process the entire $gfx1 object
864             stream, accumulating the graphics state to the end of the stream, and using
865             that as the entry state into $text1. In a similar manner, $gfx2 and $text2 are
866             read, processed, and rendered. Thus, a change in, say, the dash pattern in the
867             middle of $gfx1, I you have output some $gfx2, $text1, and $text2
868             material, may suddenly show up at the beginning of $text1 (and continue through
869             $gfx2 and $text2)!
870              
871             It is possible to use multiple graphics objects, to avoid having to change
872             settings constantly, but you may want to consider resetting all your settings
873             at the first call to each object, so that you are starting from a known base.
874             This may most easily be done by using $I->restore() and ->save() just
875             after creating $I:
876              
877             =over
878              
879             $text1 = $page->text();
880             $text1->save();
881             $grfx1 = $page->gfx();
882             $grfx1->restore();
883             $grfx1->save();
884             $text2 = $page->text();
885             $text2->restore();
886             $text2->save();
887             $grfx2 = $page->gfx();
888             $grfx1->restore();
889              
890             =back
891              
892             B C
893              
894             This has been added for PDF::API2 compatibility.
895              
896             =cut
897              
898 3     3 0 15 sub graphics { return gfx(@_); } ## no critic
899              
900             sub gfx {
901 134     134 1 701 my ($self, @params) = @_;
902              
903 134         229 my ($prepend, $compress);
904 134         233 $prepend = $compress = 0; # default for both is False
905 134 50       341 if (scalar @params == 0) {
    0          
    0          
906             # gfx() call. no change
907             } elsif (scalar @params == 1) {
908             # one scalar value, $prepend
909 0         0 $prepend = $params[0];
910             } elsif ((scalar @params)%2) {
911             # odd number of values, can't be a hash list
912 0         0 carp "Invalid parameters passed to gfx or graphics call!";
913             } else {
914             # hash list with at least one element
915 0         0 my %hash = @params;
916             # copy dashed hash names to preferred undashed names
917 0 0 0     0 if (defined $hash{'-prepend'} && !defined $hash{'prepend'}) { $hash{'prepend'} = delete($hash{'-prepend'}); }
  0         0  
918 0 0 0     0 if (defined $hash{'-compress'} && !defined $hash{'compress'}) { $hash{'compress'} = delete($hash{'-compress'}); }
  0         0  
919              
920 0 0       0 if (defined $hash{'prepend'}) { $prepend = $hash{'prepend'}; }
  0         0  
921 0 0       0 if (defined $hash{'compress'}) { $compress = $hash{'compress'}; }
  0         0  
922             }
923 134 50       336 if ($prepend) { $prepend = 1; }
  0         0  
924             $compress //= $self->{' api'}->{'forcecompress'} eq 'flate' ||
925 134   0     342 $self->{' api'}->{'forcecompress'} =~ m/^[1-9]\d*$/;
      33        
926              
927 134         1189 my $gfx = PDF::Builder::Content->new();
928 134 50       306 $gfx->compressFlate() if $compress;
929 134         530 $self->content($gfx, $prepend);
930              
931 134         511 return $gfx;
932             }
933              
934             =item $text = $page->text(%opts)
935              
936             =item $text = $page->text($prepend)
937              
938             =item $text = $page->text()
939              
940             Returns a text content object, for writing text.
941             See L for details.
942              
943             You may specify the "prepend" flag in the old or new way. The old way is to
944             give a single boolean value (0 false, non-zero true). The new way is to give
945             a hash element named 'prepend', with the same values.
946              
947             =over
948              
949             =item text(boolean_value $prepend)
950              
951             =item text('prepend' => boolean_value)
952              
953             =back
954              
955             If $prepend is I, or the option 'prepend' is given with a I value,
956             the content will be prepended to the page description (at the beginning of
957             the page's content stream).
958             Otherwise, it will be appended.
959             The default is I.
960              
961             =over
962              
963             =item text('compress' => boolean_value)
964              
965             =back
966              
967             You may specify a compression flag saying whether the text content is
968             to be compressed. If not given, the default is for the overall PDF
969             compression setting to be used (I by default).
970              
971             Please see the discussion above in C regarding multiple graphics and
972             text objects on one page, how they are grouped into PDF objects and streams,
973             and the rendering consequences of running through one entire object at a time,
974             before moving on to the next.
975              
976             The I object has many settings and attributes of its own, but shares many
977             with graphics (I), such as strokecolor, fillcolor, linewidth, linedash,
978             and the like. Thus there is some overlap in attributes, and graphics and text
979             calls can affect each other.
980              
981             =cut
982              
983             sub text {
984 20     20 1 823 my ($self, @params) = @_;
985              
986 20         36 my ($prepend, $compress);
987 20         64 $prepend = $compress = 0; # default for both is False
988 20 50       65 if (scalar @params == 0) {
    0          
    0          
989             # text() call. no change
990             } elsif (scalar @params == 1) {
991             # one scalar value, $prepend
992 0         0 $prepend = $params[0];
993             } elsif ((scalar @params)%2) {
994             # odd number of values, can't be a hash list
995 0         0 carp "Invalid parameters passed to text() call!";
996             } else {
997             # hash list with at least one element
998 0         0 my %hash = @params;
999             # copy dashed hash names to preferred undashed names
1000 0 0 0     0 if (defined $hash{'-prepend'} && !defined $hash{'prepend'}) { $hash{'prepend'} = delete($hash{'-prepend'}); }
  0         0  
1001 0 0 0     0 if (defined $hash{'-compress'} && !defined $hash{'compress'}) { $hash{'compress'} = delete($hash{'-compress'}); }
  0         0  
1002              
1003 0 0       0 if (defined $hash{'prepend'}) { $prepend = $hash{'prepend'}; }
  0         0  
1004 0 0       0 if (defined $hash{'compress'}) { $compress = $hash{'compress'}; }
  0         0  
1005             }
1006 20 50       54 if ($prepend) { $prepend = 1; }
  0         0  
1007             $compress //= $self->{' api'}->{'forcecompress'} eq 'flate' ||
1008 20   0     46 $self->{' api'}->{'forcecompress'} =~ m/^[1-9]\d*$/;
      33        
1009              
1010 20         146 my $text = PDF::Builder::Content::Text->new();
1011 20 50       51 $text->compressFlate() if $compress;
1012 20         85 $self->content($text, $prepend);
1013              
1014 20         84 return $text;
1015             }
1016              
1017             =item $page = $page->object($object, $x,$y, $scale_x,$scale_y)
1018              
1019             Places an image or other external object (a.k.a. XObject) on the page in the
1020             specified location.
1021              
1022             For images, C<$scale_x> and C<$scale_y> represent the width and height of the
1023             image on the page in points. If C<$scale_x> is omitted, it will default to 72
1024             pixels per inch. If C<$scale_y> is omitted, the image will be scaled
1025             proportionally based on the image dimensions.
1026              
1027             For other external objects, the scale is a multiplier, where 1 (the default)
1028             represents 100% (i.e. no change).
1029              
1030             If the object to be placed depends on a coordinate transformation (e.g. rotation
1031             or skew), first create a content object using L, then call
1032             L after making the appropriate transformations.
1033              
1034             =cut
1035              
1036             sub object {
1037 0     0 1 0 my $self = shift();
1038 0         0 $self->graphics()->object(@_);
1039 0         0 return $self;
1040             }
1041              
1042             =item $ant = $page->annotation()
1043              
1044             Returns a new annotation object.
1045              
1046             =cut
1047              
1048             sub annotation {
1049 6     6 1 28 my $self = shift;
1050              
1051 6 100 33     15 unless (exists $self->{'Annots'}) {
1052 5         13 $self->{'Annots'} = PDFArray();
1053 5         28 $self->{' apipdf'}->out_obj($self);
1054             } elsif (ref($self->{'Annots'}) =~ /Objind/) {
1055             $self->{'Annots'}->realise();
1056             }
1057              
1058 6         602 require PDF::Builder::Annotation;
1059 6         30 my $ant = PDF::Builder::Annotation->new();
1060 6         20 $self->{'Annots'}->add_elements($ant);
1061 6         19 $self->{' apipdf'}->new_obj($ant);
1062 6         11 $ant->{' apipdf'} = $self->{' apipdf'};
1063 6         8 $ant->{' apipage'} = $self;
1064 6         16 weaken $ant->{' apipdf'};
1065 6         11 weaken $ant->{' apipage'};
1066              
1067 6 100       16 if ($self->{'Annots'}->is_obj($self->{' apipdf'})) {
1068 1         14 $self->{' apipdf'}->out_obj($self->{'Annots'});
1069             }
1070              
1071 6         20 return $ant;
1072             }
1073              
1074             =item $page->resource($type, $key, $obj)
1075              
1076             Adds a resource to the page-inheritance tree.
1077              
1078             B
1079              
1080             $co->resource('Font', $fontkey, $fontobj);
1081             $co->resource('XObject', $imagekey, $imageobj);
1082             $co->resource('Shading', $shadekey, $shadeobj);
1083             $co->resource('ColorSpace', $spacekey, $speceobj);
1084              
1085             B You only have to add the required resources if
1086             they are NOT handled by the *font*, *image*, *shade* or *space*
1087             methods.
1088              
1089             =cut
1090              
1091             sub resource {
1092 32     32 1 95 my ($self, $type, $key, $obj, $force) = @_;
1093              
1094 32         136 my $dict = $self->find_prop('Resources');
1095              
1096 32   0     115 $dict = $dict || $self->{'Resources'} || PDFDict();
1097              
1098 32 50       144 $dict->realise() if ref($dict) =~ /Objind$/;
1099              
1100 32   66     177 $dict->{$type} = $dict->{$type} || PDFDict();
1101 32 50       169 $dict->{$type}->realise() if ref($dict->{$type}) =~ /Objind$/;
1102              
1103 32 100       91 unless (defined $obj) {
1104 2   50     15 return $dict->{$type}->{$key} || undef;
1105             } else {
1106 30 50       81 if ($force) {
1107 0         0 $dict->{$type}->{$key} = $obj;
1108             } else {
1109 30   33     174 $dict->{$type}->{$key} = $dict->{$type}->{$key} || $obj;
1110             }
1111              
1112 30 50       143 $self->{' apipdf'}->out_obj($dict) if $dict->is_obj($self->{' apipdf'});
1113 30 50       115 $self->{' apipdf'}->out_obj($dict->{$type}) if $dict->{$type}->is_obj($self->{' apipdf'});
1114 30 100       107 $self->{' apipdf'}->out_obj($obj) if $obj->is_obj($self->{' apipdf'});
1115 30         104 $self->{' apipdf'}->out_obj($self);
1116              
1117 30         92 return $dict;
1118             }
1119             }
1120              
1121             sub ship_out {
1122 0     0 0   my ($self, $pdf) = @_;
1123              
1124 0           $pdf->ship_out($self);
1125 0 0         if (defined $self->{'Contents'}) {
1126 0           $pdf->ship_out($self->{'Contents'}->elements());
1127             }
1128 0           return $self;
1129             }
1130              
1131             #sub outobjdeep {
1132             # my ($self, @opts) = @_;
1133             #
1134             # foreach my $k (qw/ api apipdf /) {
1135             # $self->{" $k"} = undef;
1136             # delete($self->{" $k"});
1137             # }
1138             # return $self->SUPER::outobjdeep(@opts);
1139             #}
1140              
1141             =back
1142              
1143             =cut
1144              
1145             1;