File Coverage

blib/lib/PDF/Builder/Annotation.pm
Criterion Covered Total %
statement 97 259 37.4
branch 24 108 22.2
condition 1 60 1.6
subroutine 16 28 57.1
pod 19 22 86.3
total 157 477 32.9


line stmt bran cond sub pod time code
1             package PDF::Builder::Annotation;
2              
3 2     2   1670 use base 'PDF::Builder::Basic::PDF::Dict';
  2         5  
  2         366  
4              
5 2     2   18 use strict;
  2         4  
  2         92  
6 2     2   11 use warnings;
  2         5  
  2         246  
7              
8             our $VERSION = '3.028'; # VERSION
9             our $LAST_UPDATE = '3.028'; # manually update whenever code is changed
10              
11 2     2   13 use PDF::Builder::Basic::PDF::Utils;
  2         4  
  2         272  
12 2     2   15 use List::Util qw(min max);
  2         5  
  2         222  
13 2     2   16 use Carp;
  2         3  
  2         12361  
14              
15             =head1 NAME
16              
17             PDF::Builder::Annotation - Add annotations to a PDF
18              
19             Inherits from L<PDF::Builder::Basic::PDF::Dict>
20              
21             =head1 SYNOPSIS
22              
23             An "annotation" is an extra feature on a page that may change the appearance
24             (e.g., highlighting), or perform some action when
25             clicked on. Some actions may require a single click and others a double click;
26             this depends on the PDF Reader used. Some may warn you and/or ask permission
27             to perform a certain action, while others may refuse to do something (which
28             may be configurable).
29              
30             my $pdf = PDF::Builder->new();
31             my $font = $pdf->font('Helvetica');
32             my $page1 = $pdf->page();
33             my $page2 = $pdf->page();
34              
35             my $content = $page1->text();
36             my $message = 'Go to Page 2';
37             my $size = 18;
38              
39             $content->distance(1 * 72, 9 * 72);
40             $content->font($font, $size);
41             $content->text($message);
42              
43             my $annotation = $page1->annotation();
44             my $width = $content->text_width($message);
45             $annotation->rect(1 * 72, 9 * 72, 1 * 72 + $width, 9 * 72 + $size);
46             $annotation->link($page2);
47              
48             $pdf->save('sample.pdf');
49              
50             =head1 METHODS
51              
52             Note that the handling of annotations can vary from Reader to Reader. The
53             available icon set may be larger or smaller than given here, and some Readers
54             activate an annotation on a single mouse click, while others require a double
55             click. Not all features provided here may be available on all PDF Readers.
56             In particular, Named Destinations seem to be handled in widely varying ways!
57              
58             =head2 new
59              
60             $annotation = PDF::Builder::Annotation->new()
61              
62             =over
63              
64             Returns an annotation object (called from $page->annotation()).
65              
66             It is normally I<not> necessary to explicitly call this method (see examples).
67              
68             =back
69              
70             =cut
71              
72             # %opts removed, as there are currently none
73             sub new {
74 6     6 1 14 my $class = shift;
75              
76 6         36 my $self = $class->SUPER::new();
77 6         22 $self->{'Type'} = PDFName('Annot');
78 6         77 $self->{'Border'} = PDFArray(PDFNum(0), PDFNum(0), PDFNum(0)); # no border
79              
80 6         21 return $self;
81             }
82              
83             #sub outobjdeep {
84             # my ($self, @opts) = @_;
85             #
86             # foreach my $k (qw[ api apipdf apipage ]) {
87             # $self->{" $k"} = undef;
88             # delete($self->{" $k"});
89             # }
90             # return $self->SUPER::outobjdeep(@opts);
91             #}
92              
93             # ============== start of annotation types =======================
94              
95             # note that %opts is given as the only format in most cases, as rect
96             # is a mandatory "option"
97              
98             =head2 Common parameters
99              
100             For the annotation calls given below, all require a clickable rectangular area
101             (button) on the page to activate the call. These all share common parameters to
102             define this "button". Note that I<your> code is responsible for creating the
103             visible content of the button (if any), while the 'rect' parameter tells the
104             annotation what (invisible) area on the page activates it, and the 'border'
105             and 'color' parameters describe the border drawn around the button.
106              
107             Note that this is in contrast to C<NamedDestination>, which ignores any of
108             these entries, as it does not define a "button" to press.
109              
110             'rect' => [ LLx,LLy, URx,URy ]
111              
112             This defines the rectangle of the "button". Your code is responsible for any
113             fill or text visible to the user, via normal text and graphics calls.
114             As an alternative (mandatory if 'rect' is not given), you may
115             specify the rectangle with the C<$ann-E<gt>rect(LLx,LLy, URx,URy);> call.
116             I<Note:> the other diagonals (UL to LR, LR to UL, UR to LL) are usually allowed.
117              
118             Instead of this hash element being used, the
119              
120             $ann->rect( LLx,LLy, URx,URy )
121              
122             method may be used (I<before> the annotation method itself is invoked).
123              
124             'border' => [ Rx,Ry, w, [dash-pattern] ]
125              
126             This defines the border around the button. If not given, the default is
127             [ 0, 0, 1 ]. Rx and Ry are corner radii (0 for sharp corners), I<w> is the
128             stroke width (0 for no visible border), and an optional dash-pattern may be
129             given to draw something other than a solid line (e.g., [5, 5] for a 5pt long
130             dash and 5pt space pattern). Note that the square brackets [ ] are actually
131             used, as the dash-pattern is an anonymous array.
132              
133             Instead of this hash element being used, the
134              
135             $ann->border(Rx,Ry, w, [dash-pattern])
136              
137             method may be used (I<before> the annotation method itself is invoked).
138              
139             'color' => [ Red, Green, Blue ]
140              
141             This defines the stroke color of the button border, using the RGB triplet way
142             of describing a color (RGB anonymous array, each value 0 to 1).
143             If 0 width does not work on all Readers to suppress the border, selecting a
144             color that matches any fill or background color in the button may work instead.
145              
146             Instead of this hash element being used, the
147              
148             $ann->Color(Red, Green, Blue)
149              
150             method may be used (I<before> the annotation method itself is invoked). Note
151             that Color is capitalized, unlike the hash option method.
152              
153             It's a matter of stylistic choice whether to give these settings as options
154             to the annotation object in the call, or call their methods directly I<before>
155             the annotation action method itself is called.
156              
157             Some calls have additional optional parameters, which will be described in
158             their section.
159              
160             =head2 Annotation types
161              
162             =head3 goto, link
163              
164             $annotation->goto($page, $location, @args, %opts) # preferred
165              
166             $annotation->goto($page, %opts) # location info as a hash element
167              
168             =over
169              
170             Defines the annotation as a PDF launch-page with page object C<$page> (within
171             I<this> document), 'fit', and opts %opts (common parameters and optionally
172             I<fit>: see descriptions below).
173              
174             B<Note> that C<$page> is I<not> a simple page number, but is either a page
175             I<object> such as C<$pdf-E<gt>openpage(page_number)>, I<or> a Named
176             Destination defined elsewhere (a prefix of '#' or '/' is optional, except when
177             the Named Destination is entirely numeric, and then it is required). 'foo',
178             '#foo', and '/3659' are examples of Named Destinations.
179             If a Named Destination is given, the page fit (location) is ignored, as the
180             Named Destination handles that.
181              
182             Note that the I<options> %opts is a hash, permitting 'rect', 'border', and
183             'color' to be described in a more natural manner than flattening the hash into
184             an array. The location and its arguments (fit data) may be either a list
185             (in the PDF::API2 style) or another hash element.
186              
187             B<Alternate name:> C<link>
188              
189             Originally this method was named C<link>, but PDF::API2 changed it to
190             C<goto>, to correspond to the B<GoTo> PDF command used. For compatibility, it
191             has been changed to C<goto>, with C<link> still available as an alias.
192              
193             =back
194              
195             =cut
196              
197             # this nonsense seems to be necessary (to alias goto with link) because
198             # Perl gets confused and thinks it's a "goto label" statement!
199             sub link { ## no critic
200 1     1 1 7 my $self = shift;
201 1         7 return $self->goto(@_); }
202              
203             sub goto {
204 1     1 1 4 my ($self, $page, @args) = @_;
205             # page may be a page object, or a Named Destination string
206             # if location and its parms are a list, made a hash element,
207             # like any rect/color/border hash elements
208              
209             # dest() will process it again, but we need hash for button settings
210 1         12 my %opts = PDF::Builder::NamedDestination->list2hash(@args);
211 1         4 $self->{'Subtype'} = PDFName('Link');
212 1         4 $self->{'A'} = PDFDict();
213 1         3 $self->{'A'}->{'S'} = PDFName('GoTo');
214 1 50       4 if (ref($page)) {
215             # page structure
216 1         35 $self->{'A'}->{'D'} =
217             PDF::Builder::NamedDestination->dest($page, %opts);
218             # fit type and parameters in args
219             } else {
220             # named destination
221             # strip off any # or /, make sure it's a string
222 0 0       0 if ($page =~ /^[#\/](.+)/) {
223 0         0 $page = "$1";
224             }
225 0         0 $self->{'A'}->{'D'} = PDFString($page, 'n');
226             }
227              
228             # click area rectangle (button)
229 1 50       5 $self->rect(@{$opts{'rect'}}) if defined $opts{'rect'};
  0         0  
230 1 50       4 $self->border(@{$opts{'border'}}) if defined $opts{'border'};
  0         0  
231 1 50       14 $self->Color(@{$opts{'color'}}) if defined $opts{'color'};
  0         0  
232              
233 1         4 return $self;
234             }
235              
236             =head3 pdf, pdfile, pdf_file
237              
238             $annotation->pdf($pdffile, $page_number, $location, @args, %opts) # preferred
239              
240             $annotation->pdf($pdffile, $page_number, %opts) # including location & data
241              
242             =over
243              
244             Defines the annotation as an B<external> PDF-file with filepath C<$pdffile>,
245             on page C<$page_number>, and options %opts (common parameters and I<fit>: see
246             descriptions below). This differs from the C<goto> call in that the target
247             is found in a I<different> PDF file, not the current document. Your operating
248             system may warn you that you are going to a different file.
249              
250             C<$page_number> is the physical page number, starting at 1: 1, 2,..., I<or> a
251             Named Destination defined in that document (a prefix of '#' or '/' is optional,
252             except when the Named Destination is entirely numeric, and then it is
253             required). 'foo', '/foo', and '#3659' are examples of Named Destinations.
254             If a Named Destination is used, the page fit (location) is ignored, as the
255             Named Destination handles that.
256              
257             Note that the I<options> %opts is a hash, while the location and fit @args
258             may be described as a list (PDF::API2 style), or as another hash element.
259             This permits 'rect', 'border', and 'color' to be
260             described in a more natural manner than flattening the hash into an array.
261              
262             B<Alternate names:> C<pdfile> and C<pdf_file>
263              
264             Originally this method was named C<pdfile>, and then C<pdf_file> but
265             PDF::API2 changed it to C<pdf>. For compatibility, it has been changed to
266             C<pdf>, with C<pdfile> and C<pdf_file> still available as aliases.
267              
268             =back
269              
270             =cut
271              
272 0     0 1 0 sub pdfile { return pdf(@_); } ## no critic
273 0     0 1 0 sub pdf_file { return pdf(@_); } ## no critic
274              
275             sub pdf {
276             # page may be a page number, or a Named Destination string
277             # args include location and its parms as either hash element or a list,
278             # and optionally (if not set externally), rect/color/border hash elements
279              
280 1     1 1 8 my ($self, $file, $page_number, @args) = @_;
281             # dest() will process it again, but we need hash for button settings
282 1         12 my %opts = PDF::Builder::NamedDestination->list2hash(@args);
283              
284 1         4 $self->{'Subtype'} = PDFName('Link');
285 1         5 $self->{'A'} = PDFDict();
286 1         4 $self->{'A'}->{'S'} = PDFName('GoToR');
287 1         4 $self->{'A'}->{'F'} = PDFString($file, 'u');
288              
289             # if an integer $page_number, /D [page_num fit]
290 1 50       10 if ($page_number =~ /^\d+$/) {
291 1         3 $page_number--; # wants it numbered starting at 0
292 1         8 $self->{'A'}->{'D'} =
293             PDF::Builder::NamedDestination->dest($page_number, %opts);
294             # fit info opts
295             } else {
296             # string 'page_number' is a Named Destination, /D (foo)
297             # any prefix of '#' or '/' will be stripped off
298 0 0       0 if ($page_number =~ /^[#\/](.+)$/) {
299 0         0 $page_number = "$1"; # if all numeric, make sure it's a string
300             }
301 0         0 $self->{'A'}->{'D'} = PDFString("$page_number", 'n');
302             }
303              
304 1 50       5 $self->rect(@{$opts{'rect'}}) if defined $opts{'rect'};
  0         0  
305 1 50       4 $self->border(@{$opts{'border'}}) if defined $opts{'border'};
  0         0  
306 1 50       4 $self->Color(@{$opts{'color'}}) if defined $opts{'color'};
  0         0  
307              
308 1         4 return $self;
309             }
310              
311             =head3 uri, url
312              
313             $annotation->uri($url, %opts)
314              
315             =over
316              
317             Defines the annotation as a launch-url with url C<$url> and
318             options %opts (common parameters).
319             This page is usually brought up in a browser, and may be remote. This
320             depends on your operating system configuration.
321              
322             B<Alternate name:> C<url>
323              
324             Originally this method was named C<url>, but PDF::API2 changed it to
325             C<uri> to correspond to the B<URI> PDF command used. For compatibility, it has
326             been changed to C<uri>, with C<url> still available as an alias.
327              
328             =back
329              
330             =cut
331              
332 0     0 1 0 sub url { return uri(@_); } ## no critic
333              
334             sub uri {
335 1     1 1 8 my ($self, $url, %opts) = @_;
336             # copy dashed names over to preferred non-dashed names
337             # there are no other options, unlike goto() and pdf(), so no call dest()
338 1         5 %opts = dashed2nondashed(%opts);
339              
340 1         4 $self->{'Subtype'} = PDFName('Link');
341 1         3 $self->{'A'} = PDFDict();
342             # note that the following code produces /A << /S /URI /URI (url) >>
343 1         4 $self->{'A'}->{'S'} = PDFName('URI');
344 1         4 $self->{'A'}->{'URI'} = PDFString($url, 'u');
345              
346 1 50       4 $self->rect(@{$opts{'rect'}}) if defined $opts{'rect'};
  0         0  
347 1 50       5 $self->Color(@{$opts{'color'}}) if defined $opts{'color'};
  0         0  
348 1 50       4 $self->border(@{$opts{'border'}}) if defined $opts{'border'};
  0         0  
349              
350 1         3 return $self;
351             }
352              
353             =head3 launch, file
354              
355             $annotation->launch($file, %opts)
356              
357             =over
358              
359             Defines the annotation as a launch-file with filepath C<$file> (a local file,
360             often an executable or script/batch file)
361             and options %opts (common parameters).
362             I<How> the file is "launched" or displayed depends on the operating system,
363             type of file, and local configuration or mapping. Common applications are to
364             bring up a text editor to display a file, or start a photo viewer.
365              
366             B<Alternate name:> C<file>
367              
368             Originally this method was named C<file>, but PDF::API2 changed it to
369             C<launch> to correspond to the B<Launch> PDF command used. For compatibility,
370             it has been changed to C<launch>, with C<file>
371             still available as an alias.
372              
373             =back
374              
375             =cut
376              
377 0     0 1 0 sub file { return launch(@_); } ## no critic
378              
379             sub launch {
380 1     1 1 9 my ($self, $file, %opts) = @_;
381             # copy dashed names over to preferred non-dashed names
382 1         6 %opts = dashed2nondashed(%opts);
383              
384 1         4 $self->{'Subtype'} = PDFName('Link');
385 1         5 $self->{'A'} = PDFDict();
386 1         4 $self->{'A'}->{'S'} = PDFName('Launch');
387 1         4 $self->{'A'}->{'F'} = PDFString($file, 'f');
388              
389 1 50       5 $self->rect(@{$opts{'rect'}}) if defined $opts{'rect'};
  0         0  
390 1 50       5 $self->Color(@{$opts{'color'}}) if defined $opts{'color'};
  0         0  
391 1 50       4 $self->border(@{$opts{'border'}}) if defined $opts{'border'};
  0         0  
392              
393 1         4 return $self;
394             }
395              
396             =head3 text
397              
398             $annotation->text($text, %opts)
399              
400             =over
401              
402             Defines the annotation as a text note with content string C<$text> and
403             options %opts (common parameters, text, open: see descriptions below).
404             The C<$text> may include newlines \n for multiple lines. Note that the option
405             'border' is ignored, since an I<icon> is used.
406              
407             The option C<text> is the popup's label string, not to be confused with the
408             main C<$text>.
409              
410             The icon appears in the upper left corner of the C<rect> selection rectangle,
411             and its active clickable area is fixed by the icon (it is I<not> equal to the
412             rectangle). The icon size is fixed, and its fill color set by C<color>.
413              
414             Additional options:
415              
416             =back
417              
418             =over
419              
420             =item icon => name_string
421              
422             =item icon => reference
423              
424             Specify the B<icon> to be used. The default is Reader-specific (usually
425             C<Note>), and others may be
426             defined by the Reader. C<Comment>, C<Key>, C<Help>, C<NewParagraph>,
427             C<Paragraph>, and C<Insert> are also supposed to
428             be available on all PDF Readers. Note that the name I<case> must exactly match.
429             The icon is of fixed size.
430             Any I<AP> dictionary entry will override the icon setting.
431              
432             A I<reference> to an icon may be passed instead of a name.
433              
434             =item opacity => I<value>
435              
436             Define the opacity (non-transparency, opaqueness) of the icon. This value
437             ranges from 0.0 (transparent) to 1.0 (fully opaque), and applies to both
438             the outline and the fill color. The default is 1.0.
439              
440             =back
441              
442             =cut
443              
444             # the icon size appears to be fixed. the last font size used does not affect it
445             # and enabling icon_appearance() for it doesn't seem to do anything
446              
447             sub text {
448 2     2 1 22 my ($self, $text, %opts) = @_;
449             # copy dashed names over to preferred non-dashed names
450 2         9 %opts = dashed2nondashed(%opts);
451              
452 2         9 $self->{'Subtype'} = PDFName('Text');
453 2         9 $self->content($text);
454              
455 2 50       8 $self->rect(@{$opts{'rect'}}) if defined $opts{'rect'};
  2         11  
456 2 50       10 $self->Color(@{$opts{'color'}}) if defined $opts{'color'};
  0         0  
457             #$self->border($opts{'border'}) if defined $opts{'border'}; # ignored
458 2 50       6 $self->open($opts{'open'}) if defined $opts{'open'};
459             # popup label (title)
460             # have seen /T as (xFEFF UTF-16 chars)
461 2 50       7 $self->{'T'} = PDFString($opts{'text'}, 'p') if exists $opts{'text'};
462             # icon opacity?
463 2 50       7 if (defined $opts{'opacity'}) {
464 0         0 $self->{'CA'} = PDFNum($opts{'opacity'});
465             }
466              
467             # Icon Name will be ignored if there is an AP.
468 2         6 my $icon; # perlcritic doesn't want 2 lines combined
469 2 50       5 $icon = $opts{'icon'} if exists $opts{'icon'};
470 2 50 33     9 $self->{'Name'} = PDFName($icon) if $icon && !ref($icon); # icon name
471             # Set the icon appearance
472 2 50       6 $self->icon_appearance($icon, %opts) if $icon;
473              
474 2         8 return $self;
475             }
476              
477             =head3 markup
478              
479             $annotation->markup($text, $PointList, $highlight, %opts)
480              
481             =over
482              
483             Defines the annotation as a text note with content string C<$text> and
484             options %opts (color, text, open, opacity: see descriptions below).
485             The C<$text> may include newlines \n for multiple lines.
486              
487             C<text> is the popup's label string, not to be confused with the main C<$text>.
488              
489             There is no icon. Instead, the annotated text marked by C<$PointList> is
490             highlighted in one of four ways specified by C<$highlight>.
491              
492             =back
493              
494             =over
495              
496             =item $PointList => [ 8n numbers ]
497              
498             One or more sets of numeric coordinates are given, defining the quadrilateral
499             (usually a rectangle) around the text to be highlighted and selectable
500             (clickable, to bring up the annotation text). These
501             are four sets of C<x,y> coordinates, given (for Left-to-Right text) as the
502             upper bound Upper Left to Upper Right and then the lower bound Lower Left to
503             Lower Right. B<Note that this is different from what is (erroneously)
504             documented in some PDF specifications!> It is important that the coordinates
505             be given in this order.
506              
507             Multiple sets of quadrilateral corners may be given, such as for highlighted
508             text that wraps around to new line(s). The minimum is one set (8 numbers).
509             Any I<AP> dictionary entry will override the C<$PointList> setting. Finally,
510             the "Rect" selection rectangle is created I<just outside> the convex bounding
511             box defined by C<$PointList>.
512              
513             =item $highlight => 'string'
514              
515             The following highlighting effects are permitted. The C<string> must be
516             spelled and capitalized I<exactly> as given:
517              
518             =over
519              
520             =item Highlight
521              
522             The effect of a translucent "highlighter" marker.
523              
524             =item Squiggly
525              
526             The effect is an underline written in a "squiggly" manner.
527              
528             =item StrikeOut
529              
530             The text is struck-through with a straight line.
531              
532             =item Underline
533              
534             The text is marked by a straight underline.
535              
536             =back
537              
538             =item color => I<array of values>
539              
540             If C<color> is not given (an array of numbers in the range 0.0-1.0), a
541             medium gray should be used by default.
542             Named colors are not supported at this time.
543              
544             =item opacity => I<value>
545              
546             Define the opacity (non-transparency, opaqueness) of the icon. This value
547             ranges from 0.0 (transparent) to 1.0 (fully opaque), and applies to both
548             the outline and the fill color. The default is 1.0.
549              
550             =back
551              
552             =cut
553              
554             sub markup {
555 0     0 1 0 my ($self, $text, $PointList, $highlight, %opts) = @_;
556             # copy dashed names over to preferred non-dashed names
557 0         0 %opts = dashed2nondashed(%opts);
558              
559 0         0 my @pointList = @{ $PointList };
  0         0  
560 0 0 0     0 if ((scalar @pointList) == 0 || (scalar @pointList)%8) {
561 0         0 die "markup point list does not have 8*N entries!\n";
562             }
563 0         0 $self->{'Subtype'} = PDFName($highlight);
564 0         0 delete $self->{'Border'};
565 0         0 $self->{'QuadPoints'} = PDFArray(map {PDFNum($_)} @pointList);
  0         0  
566 0         0 $self->content($text);
567              
568 0         0 my $minX = min($pointList[0], $pointList[2], $pointList[4], $pointList[6]);
569 0         0 my $maxX = max($pointList[0], $pointList[2], $pointList[4], $pointList[6]);
570 0         0 my $minY = min($pointList[1], $pointList[3], $pointList[5], $pointList[7]);
571 0         0 my $maxY = max($pointList[1], $pointList[3], $pointList[5], $pointList[7]);
572 0         0 $self->rect($minX-.5,$minY-.5, $maxX+.5,$maxY+.5);
573              
574 0 0       0 $self->open($opts{'open'}) if defined $opts{'open'};
575 0 0       0 if (defined $opts{'color'}) {
576 0         0 $self->Color(@{$opts{'color'}});
  0         0  
577             } else {
578 0         0 $self->Color([]);
579             }
580             # popup label (title)
581             # have seen /T as (xFEFF UTF-16 chars)
582 0 0       0 $self->{'T'} = PDFString($opts{'text'}, 'p') if exists $opts{'text'};
583             # opacity?
584 0 0       0 if (defined $opts{'opacity'}) {
585 0         0 $self->{'CA'} = PDFNum($opts{'opacity'});
586             }
587              
588 0         0 return $self;
589             }
590              
591             =head3 movie
592              
593             $annotation->movie($file, $contentType, %opts)
594              
595             =over
596              
597             Defines the annotation as a movie from C<$file> with
598             content (MIME) type C<$contentType> and
599             options %opts (common parameters, text: see descriptions below).
600              
601             The C<rect> rectangle B<also serves as the area where the movie is played>, so
602             it should be of usable size and aspect ratio. It does not use a separate popup
603             player. It is known to play .avi and .wav files -- others have not been tested.
604             Using Adobe Reader, it will not play .mpg files (unsupported type). More work
605             is probably needed on this annotation method.
606              
607             =back
608              
609             =cut
610              
611             sub movie {
612 0     0 1 0 my ($self, $file, $contentType, %opts) = @_;
613             # copy dashed names over to preferred non-dashed names
614 0         0 %opts = dashed2nondashed(%opts);
615              
616 0         0 $self->{'Subtype'} = PDFName('Movie'); # subtype = movie (req)
617 0         0 $self->{'A'} = PDFBool(1); # play using default activation parms
618 0         0 $self->{'Movie'} = PDFDict();
619             #$self->{'Movie'}->{'S'} = PDFName($contentType);
620 0         0 $self->{'Movie'}->{'F'} = PDFString($file, 'f');
621              
622             # PDF::API2 2.034 changes don't seem to work
623             # $self->{'Movie'}->{'F'} = PDFString($file, 'f'); line above removed
624             #$self->{'Movie'}->{'F'} = PDFDict();
625             #$self->{' apipdf'}->new_obj($self->{'Movie'}->{'F'});
626             #my $f = $self->{'Movie'}->{'F'};
627             #$f->{'Type'} = PDFName('EmbeddedFile');
628             #$f->{'Subtype'} = PDFName($contentType);
629             #$f->{' streamfile'} = $file;
630              
631 0 0       0 $self->rect(@{$opts{'rect'}}) if defined $opts{'rect'};
  0         0  
632 0 0       0 $self->border(@{$opts{'border'}}) if defined $opts{'border'};
  0         0  
633 0 0       0 $self->Color(@{$opts{'color'}}) if defined $opts{'color'};
  0         0  
634             # popup label (title) DOESN'T SEEM TO SHOW UP ANYWHERE
635             # self->A->T and self->T also fail to display
636 0 0       0 $self->{'Movie'}->{'T'} = PDFString($opts{'text'}, 'p') if exists $opts{'text'};
637              
638 0         0 return $self;
639             }
640              
641             =head3 file_attachment
642              
643             $annotation->file_attachment($file, %opts)
644              
645             =over
646              
647             Defines the annotation as a file attachment with file $file and options %opts
648             (common parameters: see descriptions below). Note that C<color> applies to
649             the icon fill color, not to a selectable area outline. The icon is resized
650             (including aspect ratio changes) based on the selectable rectangle given by
651             C<rect>, so watch your rectangle dimensions!
652              
653             The file, along with its name, is I<embedded> in the PDF document and may be
654             extracted for viewing with the appropriate viewer.
655              
656             This differs from the C<file> method in that C<file> looks for and launches
657             a file I<already> on the Reader's machine, while C<file_attachment> embeds the
658             file in the PDF, and makes it available on the Reader's machine for actions
659             of the user's choosing.
660              
661             B<Note 1:> some Readers may only permit an "open" action, and may also restrict
662             file types (extensions) that will be handled. This may be configurable with
663             your Reader's security settings.
664              
665             B<Note 2:> the displayed file name (pop-up during mouse rollover of the target
666             rectangle) is given with the I<path> trimmed off (file name only). If you want
667             the displayed name to exactly match the path that was passed to the call,
668             including the path, give the C<notrimpath> option.
669              
670             Options:
671              
672             =back
673              
674             =over
675              
676             =item icon => name_string
677              
678             =item icon => reference
679              
680             Specify the B<icon> to be used. The default is Reader-specific (usually
681             C<PushPin>), and others may be
682             defined by the Reader. C<Paperclip>, C<Graph>, and C<Tag> are also supposed to
683             be available on all PDF Readers. Note that the name I<case> must exactly match.
684             C<None> is a custom invisible icon defined by PDF::Builder.
685             The icon is stretched/squashed to fill the defined target rectangle, so take
686             care when defining C<rect> dimensions.
687             Any I<AP> dictionary entry will override the icon setting.
688              
689             A I<reference> to an icon may be passed instead of a name.
690              
691             =item opacity => I<value>
692              
693             Define the opacity (non-transparency, opaqueness) of the icon. This value
694             ranges from 0.0 (transparent) to 1.0 (fully opaque), and applies to both
695             the outline and the fill color. The default is 1.0.
696              
697             =item notrimpath => 1
698              
699             If given, show the entire path and file name on mouse rollover, rather than
700             just the file name.
701              
702             =item text => string
703              
704             A text label for the popup (on mouseover) that contains the file name.
705              
706             =back
707              
708             Note that while PDF permits different specifications (paths) to DOS/Windows,
709             Mac, and Unix (including Linux) versions of a file, and different format copies
710             to be embedded, at this time PDF::Builder only permits a single file (format of
711             your choice) to be embedded. If there is user demand for multiple file formats
712             to be referenced and/or embedded, we could look into providing this, I<although
713             separate OS version paths B<may> be considered obsolescent!>.
714              
715             =cut
716              
717             # TBD it is possible to specify different files for DOS, Mac, Unix
718             # (see PDF 1.7 7.11.4.2). This might solve problem of different line
719             # ends, at the cost of 3 copies of each file.
720              
721             sub file_attachment {
722 0     0 1 0 my ($self, $file, %opts) = @_;
723             # copy dashed names over to preferred non-dashed names
724 0         0 %opts = dashed2nondashed(%opts);
725              
726 0         0 my $icon; # defaults to Reader's default (usually PushPin)
727 0 0       0 $icon = $opts{'icon'} if exists $opts{'icon'};
728              
729 0 0       0 $self->rect(@{$opts{'rect'}}) if defined $opts{'rect'};
  0         0  
730             # descriptive text on mouse rollover
731 0 0       0 $self->{'T'} = PDFString($opts{'text'}, 'p') if exists $opts{'text'};
732             # icon opacity?
733 0 0       0 if (defined $opts{'opacity'}) {
734 0         0 $self->{'CA'} = PDFNum($opts{'opacity'});
735             }
736              
737 0         0 $self->{'Subtype'} = PDFName('FileAttachment');
738              
739             # 9 0 obj <<
740             # /Type /Annot
741             # /Subtype /FileAttachment
742             # /Name /PushPin
743             # /C [ 1 1 0 ]
744             # /Contents (test.txt)
745             # /FS <<
746             # /Type /F
747             # /EF << /F 10 0 R >>
748             # /F (test.txt)
749             # >>
750             # /Rect [ 100 100 200 200 ]
751             # /Border [ 0 0 1 ]
752             # >> endobj
753             #
754             # 10 0 obj <<
755             # /Type /EmbeddedFile
756             # /Length ...
757             # >> stream
758             # ...
759             # endstream endobj
760              
761             # text label on pop-up for mouse rollover
762 0         0 my $cName = $file;
763             # trim off any path, leaving just the file name. less confusing that way
764 0 0       0 if (!defined $opts{'notrimpath'}) {
765 0 0       0 if ($cName =~ m#([^/\\]+)$#) { $cName = $1; }
  0         0  
766             }
767 0         0 $self->{'Contents'} = PDFString($cName, 's');
768              
769             # Icon Name will be ignored if there is an AP.
770 0 0 0     0 $self->{'Name'} = PDFName($icon) if $icon && !ref($icon); # icon name
771             #$self->{'F'} = PDFNum(0b0); # flags default to 0
772 0 0       0 $self->Color(@{ $opts{'color'} }) if defined $opts{'color'};
  0         0  
773              
774             # The File Specification.
775 0         0 $self->{'FS'} = PDFDict();
776 0         0 $self->{'FS'}->{'F'} = PDFString($file, 'f');
777 0         0 $self->{'FS'}->{'Type'} = PDFName('Filespec');
778 0         0 $self->{'FS'}->{'EF'} = PDFDict($file);
779 0         0 $self->{'FS'}->{'EF'}->{'F'} = PDFDict($file);
780 0         0 $self->{' apipdf'}->new_obj($self->{'FS'}->{'EF'}->{'F'});
781 0         0 $self->{'FS'}->{'EF'}->{'F'}->{'Type'} = PDFName('EmbeddedFile');
782 0         0 $self->{'FS'}->{'EF'}->{'F'}->{' streamfile'} = $file;
783              
784             # Set the icon appearance
785 0 0       0 $self->icon_appearance($icon, %opts) if $icon;
786              
787 0         0 return $self;
788             }
789              
790             # TBD additional annotation types without icons
791             # free text, line, square, circle, polygon (1.5), polyline (1.5), highlight,
792             # underline, squiggly, strikeout, caret (1.5), ink, popup, sound, widget,
793             # screen (1.5), printermark, trapnet, watermark (1.6), 3D (1.6), redact (1.7)
794              
795             # TBD additional annotation types with icons
796             # stamp
797             # icons: Approved, Experimental, NotApproved, Asis, Expired,
798             # NotForPublicRelease, Confidential, Final, Sold, Departmental,
799             # ForComment, TopSecret, Draft (def.), ForPublicRelease
800             # sound
801             # icons: Speaker (def.), Mic
802              
803             # =============== end of annotation types ========================
804              
805             =head2 Internal routines and common options
806              
807             The common options may be called separately (applied against $annotation before
808             calling the action routine), or passed as options to the call.
809              
810             =head3 rect
811              
812             $annotation->rect($llx,$lly, $urx,$ury)
813              
814             =over
815              
816             Sets the rectangle (active click area) of the annotation, given by 'rect'
817             option. This is any pair of diagonally opposite corners of the rectangle.
818              
819             The default clickable area is the icon itself.
820              
821             Defining option. I<Note that this "option" is actually B<required>.>
822              
823             I<This call may be replaced by a hash element 'rect'=E<gt> in many calls
824             (see Common parameters).>
825              
826             =back
827              
828             =over
829              
830             =item rect => [LLx, LLy, URx, URy]
831              
832             Set annotation rectangle I<as an option> at C<[LLx,LLy]> to C<[URx,URy]>
833             (lower left and upper right coordinates).
834             LL to UR is customary, but any diagonal is allowed.
835              
836             =back
837              
838             =cut
839              
840             sub rect {
841 2     2 1 5 my ($self, @r) = @_;
842              
843 2 50       9 die "Insufficient parameters to annotation->rect() " unless scalar @r == 4;
844 2         8 $self->{'Rect'} = PDFArray( map { PDFNum($_) } $r[0],$r[1],$r[2],$r[3]);
  8         18  
845 2         5 return $self;
846             }
847              
848             =head3 border
849              
850             $annotation->border(@b)
851              
852             =over
853              
854             Sets the border-style of the annotation, if applicable, as given by the
855             border option. There are three entries in the array:
856             horizontal and vertical corner radii, and border width.
857             An optional fourth entry (described below) may be used for a dashed or dotted
858             line.
859              
860             A border is used in annotations where text or some other material is put down,
861             and a clickable rectangle is defined over it (rect). A border is not shown
862             when an B<icon> is being used to mark the clickable area.
863              
864             A I<PDF Reader> normally defaults to [0 0 1] (solid line of width 1, with
865             sharp corners) if no border (C</Border>) is specified. Keeping compatibility
866             with PDF::API2's longstanding practice, PDF::Builder defaults to no visible
867             border C<[0 0 0]> (solid line of width 0, and thus invisible).
868              
869             Defining option:
870              
871             =back
872              
873             =over
874              
875             =item border => [CRh, CRv, W]
876              
877             =item border => [CRh, CRv, W, [on, off...]]
878              
879             Note that the square brackets [ and ] are literally I<there>, indicating a
880             vector or array of values. They do B<not> indicate optional values!
881              
882             Set annotation B<border style> of horizontal and vertical corner radii C<CRh>
883             and C<CRv> (value 0 for squared corners) and width C<W> (value 0 for no border).
884             The PDF::Builder default is no border (while a I<PDF Reader> typically defaults
885             to no border ([0 0 0]), if no /Border entry is given).
886             Optionally, a dash pattern array may be given (C<on> length, C<off> length,
887             as one or more I<pairs>). The default is a solid line.
888              
889             The border vector seems to ignore the first two settings (corner radii), but
890             the line thickness works, on basic Readers.
891             The corner radii I<may> work on some other Readers.
892              
893             =back
894              
895             I<This call may be replaced by a hash element 'border'=E<gt> in many calls
896             (see Common parameters).>
897              
898             =cut
899              
900             sub border {
901 0     0 1 0 my ($self, @b) = @_;
902              
903 0 0       0 if (scalar @b == 3) {
    0          
904 0         0 $self->{'Border'} = PDFArray( map { PDFNum($_) } $b[0],$b[1],$b[2]);
  0         0  
905             } elsif (scalar @b == 4) {
906             # b[3] is an anonymous array
907 0         0 my @first = map { PDFNum($_) } $b[0], $b[1], $b[2];
  0         0  
908 0         0 $self->{'Border'} = PDFArray( @first, PDFArray( map { PDFNum($_) } @{$b[3]} ));
  0         0  
  0         0  
909             } else {
910 0         0 die "annotation->border() style requires 3 or 4 parameters ";
911             }
912 0         0 return $self;
913             }
914              
915             =head3 Color
916              
917             $annotation->Color(@color)
918              
919             =over
920              
921             Set the icon's fill color I<or> the click area's border color. The color is
922             an array of 1, 3, or 4 numbers, each
923             in the range 0.0 to 1.0. If 1 number is given, it is the grayscale value (0 =
924             black to 1 = white). If 3 numbers are given, it is an RGB color value. If 4
925             numbers are given, it is a CMYK color value. Currently, named colors (strings)
926             are not handled.
927              
928             For link and url annotations, this is the color of the rectangle border
929             (border given with a width of at least 1).
930              
931             If an invalid array length or numeric value is given, a medium gray ( [0.5] )
932             value is used, without any message. If no color is given, the usual fill color
933             is black.
934              
935             Defining option:
936              
937             Named colors (e.g., 'black') are not supported at this time.
938              
939             =back
940              
941             =over
942              
943             =item color => [ ] or not 1, 3, or 4 numbers 0.0-1.0
944              
945             A medium gray (0.5 value) will be used if an invalid color is given.
946              
947             =item color => [ g ]
948              
949             If I<g> is between 0.0 (black) and 1.0 (white), the fill color will be gray.
950              
951             =item color => [ r, g, b ]
952              
953             If I<r> (red), I<g> (green), and I<b> (blue) are all between 0.0 and 1.0, the
954             fill color will be the defined RGB hue. [ 0, 0, 0 ] is black, [ 1, 1, 0 ] is
955             yellow, and [ 1, 1, 1 ] is white.
956              
957             =item color => [ c, m, y, k ]
958              
959             If I<c> (red), I<m> (magenta), I<y> (yellow), and I<k> (black) are all between
960             0.0 and 1.0, the fill color will be the defined CMYK hue. [ 0, 0, 0, 0 ] is
961             white, [ 1, 0, 1, 0 ] is green, and [ 1, 1, 1, 1 ] is black.
962              
963             =back
964              
965             I<This call may be replaced by a hash element 'color'=E<gt> in many calls
966             (see Common parameters).>
967              
968             =cut
969              
970             sub Color {
971 0     0 1 0 my ($self, @color) = @_;
972              
973 0 0 0     0 if (scalar @color == 1 &&
    0 0        
    0 0        
      0        
      0        
      0        
      0        
      0        
      0        
      0        
      0        
      0        
      0        
      0        
      0        
      0        
974             $color[0] >= 0 && $color[0] <= 1.0) {
975 0         0 $self->{'C'} = PDFArray(map { PDFNum($_) } $color[0]);
  0         0  
976             } elsif (scalar @color == 3 &&
977             $color[0] >= 0 && $color[0] <= 1.0 &&
978             $color[1] >= 0 && $color[1] <= 1.0 &&
979             $color[2] >= 0 && $color[2] <= 1.0) {
980 0         0 $self->{'C'} = PDFArray(map { PDFNum($_) } $color[0], $color[1], $color[2]);
  0         0  
981             } elsif (scalar @color == 4 &&
982             $color[0] >= 0 && $color[0] <= 1.0 &&
983             $color[1] >= 0 && $color[1] <= 1.0 &&
984             $color[2] >= 0 && $color[2] <= 1.0 &&
985             $color[3] >= 0 && $color[3] <= 1.0) {
986 0         0 $self->{'C'} = PDFArray(map { PDFNum($_) } $color[0], $color[1], $color[2], $color[3]);
  0         0  
987             } else {
988             # invalid color entry. just set to medium gray without message
989 0         0 $self->{'C'} = PDFArray(map { PDFNum($_) } 0.5 );
  0         0  
990             }
991              
992 0         0 return $self;
993             }
994              
995             =head3 content
996              
997             $annotation->content(@lines)
998              
999             =over
1000              
1001             Sets the text-content of the C<text()> annotation.
1002             This is a text string or array of strings.
1003              
1004             =back
1005              
1006             =cut
1007              
1008             sub content {
1009 2     2 1 6 my ($self, @lines) = @_;
1010 2         7 my $text = join("\n", @lines);
1011            
1012 2         6 $self->{'Contents'} = PDFString($text, 's');
1013 2         6 return $self;
1014             }
1015              
1016             # unused internal routine? TBD
1017             sub name {
1018 0     0 0 0 my ($self, $name) = @_;
1019 0         0 $self->{'Name'} = PDFName($name);
1020 0         0 return $self;
1021             }
1022              
1023             =head3 open
1024              
1025             $annotation->open($bool)
1026              
1027             =over
1028              
1029             Display the C<text()> annotation either open or closed, if applicable.
1030              
1031             Both are editable; the "open" form brings up the page with the entry area
1032             already open for editing, while "closed" has to be clicked on to edit it.
1033              
1034             Defining option:
1035              
1036             =back
1037              
1038             =over
1039              
1040             =item open => boolean
1041              
1042             If true (1), the annotation will be marked as initially "open".
1043             If false (0), or the option is not given, the annotation is initially "closed".
1044              
1045             =back
1046              
1047             =cut
1048              
1049             sub open { ## no critic
1050 0     0 1 0 my ($self, $bool) = @_;
1051 0 0       0 $self->{'Open'} = PDFBool($bool? 1: 0);
1052 0         0 return $self;
1053             }
1054              
1055             =head3 text string
1056              
1057             'text' => string
1058              
1059             =over
1060              
1061             Specify an optional B<text label> for annotation. This text or comment only
1062             shows up I<as a title> in the pop-up containing the file or text.
1063              
1064             =back
1065              
1066             =cut
1067              
1068             sub icon_appearance {
1069 0     0 0 0 my ($self, $icon, %opts) = @_;
1070             # $icon is a string with name of icon (confirmed not empty) or a reference.
1071             # if a string (text), has already defined /Name. "None" and ref handle here.
1072             # options of interest: rect (to define size of icon)
1073              
1074             # copy dashed names over to preferred non-dashed names
1075 0 0 0     0 if (defined $opts{'-rect'} && !defined $opts{'rect'}) { $opts{'rect'} = delete($opts{'-rect'}); }
  0         0  
1076            
1077             # text also permits icon and custom icon, including None
1078             #return unless $self->{'Subtype'}->val() eq 'FileAttachment';
1079              
1080 0         0 my @r; # perlcritic doesn't want 2 lines combined
1081 0 0       0 @r = @{$opts{'rect'}} if defined $opts{'rect'};
  0         0  
1082             # number of parameters should be 4, checked above (rect method)
1083              
1084             # Handle custom icon type 'None' and icon reference.
1085 0 0       0 if ($icon eq 'None') {
    0          
1086             # It is not clear what viewers will do, so provide an
1087             # appearance dict with no graphics content.
1088              
1089             # 9 0 obj <<
1090             # ...
1091             # /AP << /D 11 0 R /N 11 0 R /R 11 0 R >>
1092             # ...
1093             # >>
1094             # 11 0 obj <<
1095             # /BBox [ 0 0 100 100 ]
1096             # /FormType 1
1097             # /Length 6
1098             # /Matrix [ 1 0 0 1 0 0 ]
1099             # /Resources <<
1100             # /ProcSet [ /PDF ]
1101             # >>
1102             # >> stream
1103             # 0 0 m
1104             # endstream endobj
1105              
1106 0         0 $self->{'AP'} = PDFDict();
1107 0         0 my $d = PDFDict();
1108 0         0 $self->{' apipdf'}->new_obj($d);
1109 0         0 $d->{'FormType'} = PDFNum(1);
1110 0         0 $d->{'Matrix'} = PDFArray(map { PDFNum($_) } 1, 0, 0, 1, 0, 0);
  0         0  
1111 0         0 $d->{'Resources'} = PDFDict();
1112 0         0 $d->{'Resources'}->{'ProcSet'} = PDFArray( map { PDFName($_) } qw(PDF));
  0         0  
1113 0         0 $d->{'BBox'} = PDFArray( map { PDFNum($_) } 0, 0, $r[2]-$r[0], $r[3]-$r[1] );
  0         0  
1114 0         0 $d->{' stream'} = "0 0 m";
1115 0         0 $self->{'AP'}->{'N'} = $d; # normal appearance
1116             # Should default to N, but be sure.
1117 0         0 $self->{'AP'}->{'R'} = $d; # Rollover
1118 0         0 $self->{'AP'}->{'D'} = $d; # Down
1119              
1120             # Handle custom icon.
1121             } elsif (ref $icon) {
1122             # Provide an appearance dict with the image.
1123              
1124             # 9 0 obj <<
1125             # ...
1126             # /AP << /D 11 0 R /N 11 0 R /R 11 0 R >>
1127             # ...
1128             # >>
1129             # 11 0 obj <<
1130             # /BBox [ 0 0 1 1 ]
1131             # /FormType 1
1132             # /Length 13
1133             # /Matrix [ 1 0 0 1 0 0 ]
1134             # /Resources <<
1135             # /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ]
1136             # /XObject << /PxCBA 7 0 R >>
1137             # >>
1138             # >> stream
1139             # q /PxCBA Do Q
1140             # endstream endobj
1141              
1142 0         0 $self->{'AP'} = PDFDict();
1143 0         0 my $d = PDFDict();
1144 0         0 $self->{' apipdf'}->new_obj($d);
1145 0         0 $d->{'FormType'} = PDFNum(1);
1146 0         0 $d->{'Matrix'} = PDFArray(map { PDFNum($_) } 1, 0, 0, 1, 0, 0);
  0         0  
1147 0         0 $d->{'Resources'} = PDFDict();
1148 0         0 $d->{'Resources'}->{'ProcSet'} = PDFArray(map { PDFName($_) } qw(PDF Text ImageB ImageC ImageI));
  0         0  
1149 0         0 $d->{'Resources'}->{'XObject'} = PDFDict();
1150 0         0 my $im = $icon->{'Name'}->val();
1151 0         0 $d->{'Resources'}->{'XObject'}->{$im} = $icon;
1152             # Note that the image is scaled to one unit in user space.
1153 0         0 $d->{'BBox'} = PDFArray(map { PDFNum($_) } 0, 0, 1, 1);
  0         0  
1154 0         0 $d->{' stream'} = "q /$im Do Q";
1155 0         0 $self->{'AP'}->{'N'} = $d; # normal appearance
1156              
1157 0         0 if (0) {
1158             # Testing... Provide an alternative for R and D.
1159             # Works only with Adobe Reader.
1160             $d = PDFDict();
1161             $self->{' apipdf'}->new_obj($d);
1162             $d->{'Type'} = PDFName('XObject');
1163             $d->{'Subtype'} = PDFName('Form');
1164             $d->{'FormType'} = PDFNum(1);
1165             $d->{'Matrix'} = PDFArray(map { PDFNum($_) } 1, 0, 0, 1, 0, 0);
1166             $d->{'Resources'} = PDFDict();
1167             $d->{'Resources'}->{'ProcSet'} = PDFArray(map { PDFName($_) } qw(PDF));
1168             $d->{'BBox'} = PDFArray(map { PDFNum($_) } 0, 0, $r[2]-$r[0], $r[3]-$r[1]);
1169             $d->{' stream'} =
1170             join( " ",
1171             # black outline
1172             0, 0, 'm',
1173             0, $r[2]-$r[0], 'l',
1174             $r[2]-$r[0], $r[3]-$r[1], 'l',
1175             $r[2]-$r[0], 0, 'l',
1176             's',
1177             );
1178             }
1179              
1180             # Should default to N, but be sure.
1181 0         0 $self->{'AP'}->{'R'} = $d; # Rollover
1182 0         0 $self->{'AP'}->{'D'} = $d; # Down
1183             }
1184              
1185 0         0 return $self;
1186             }
1187              
1188             # strip off any leading - from all options (hash keys)
1189             sub dashed2nondashed {
1190 4     4 0 12 my @opts = @_;
1191 4         17 for (my $i=0; $i<@opts; $i+=2) {
1192 2 50       27 if ($opts[$i] =~ /^-(.*)$/) {
1193 0         0 $opts[$i] = $1;
1194             }
1195             }
1196 4         15 return @opts;
1197             }
1198              
1199             1;