File Coverage

blib/lib/PDF/Builder/Annotation.pm
Criterion Covered Total %
statement 43 254 16.9
branch 9 124 7.2
condition 1 60 1.6
subroutine 9 22 40.9
pod 15 17 88.2
total 77 477 16.1


line stmt bran cond sub pod time code
1             package PDF::Builder::Annotation;
2              
3 2     2   1628 use base 'PDF::Builder::Basic::PDF::Dict';
  2         4  
  2         313  
4              
5 2     2   14 use strict;
  2         5  
  2         48  
6 2     2   10 use warnings;
  2         4  
  2         181  
7              
8             our $VERSION = '3.023'; # VERSION
9             our $LAST_UPDATE = '3.022'; # manually update whenever code is changed
10              
11 2     2   14 use PDF::Builder::Basic::PDF::Utils;
  2         5  
  2         259  
12 2     2   15 use List::Util qw(min max);
  2         4  
  2         7579  
13              
14             =head1 NAME
15              
16             PDF::Builder::Annotation - Add annotations to a PDF
17              
18             =head1 METHODS
19              
20             Note that the handling of annotations can vary from Reader to Reader. The
21             available icon set may be larger or smaller than given here, and some Readers
22             activate an annotation on a single mouse click, while others require a double
23             click. Not all features provided here may be available on all PDF Readers.
24              
25             =over
26              
27             =item $annotation = PDF::Builder::Annotation->new()
28              
29             Returns an annotation object (called from $page->annotation()).
30              
31             It is normally I necessary to explicitly call this method (see examples).
32              
33             =cut
34              
35             # %options removed, as there are currently none
36             sub new {
37 2     2 1 12 my ($class) = @_;
38              
39 2         26 my $self = $class->SUPER::new();
40 2         15 $self->{'Type'} = PDFName('Annot');
41 2         10 $self->{'Border'} = PDFArray(PDFNum(0), PDFNum(0), PDFNum(1));
42              
43 2         11 return $self;
44             }
45              
46             #sub outobjdeep {
47             # my ($self, @options) = @_;
48             #
49             # foreach my $k (qw[ api apipdf apipage ]) {
50             # $self->{" $k"} = undef;
51             # delete($self->{" $k"});
52             # }
53             # return $self->SUPER::outobjdeep(@options);
54             #}
55              
56             # ============== start of annotation types =======================
57              
58             # note that %options is given as the only format in most cases, as -rect
59             # is a mandatory "option"
60              
61             =back
62              
63             =head2 Annotation types
64              
65             =over
66              
67             =item $annotation->link($page, %options)
68              
69             =item $annotation->link($page)
70              
71             Defines the annotation as a launch-page with page C<$page> (within I
72             document) and options %options (-rect, -border, -color, I: see
73             descriptions below).
74              
75             B that C<$page> is I a simple page number, but is a page structure
76             such as C<$pdf-Eopenpage(page_number)>.
77              
78             =cut
79              
80             sub link { ## no critic
81 0     0 1 0 my ($self, $page, %options) = @_;
82              
83 0         0 $self->{'Subtype'} = PDFName('Link');
84 0 0       0 if (ref($page)) {
85 0         0 $self->{'A'} = PDFDict();
86 0         0 $self->{'A'}->{'S'} = PDFName('GoTo');
87             }
88 0         0 $self->dest($page, %options);
89 0 0       0 $self->rect(@{$options{'-rect'}}) if defined $options{'-rect'};
  0         0  
90 0 0       0 $self->border(@{$options{'-border'}}) if defined $options{'-border'};
  0         0  
91 0 0       0 $self->Color(@{$options{'-color'}}) if defined $options{'-color'};
  0         0  
92              
93 0         0 return $self;
94             }
95              
96             =item $annotation->pdf_file($pdffile, $page_number, %options)
97              
98             Defines the annotation as a PDF-file with filepath C<$pdffile>, on page
99             C<$page_number>, and options %options (-rect, -border, -color, I: see
100             descriptions below). This differs from the C call in that the target
101             is found in a different PDF file, not the current document.
102              
103             C<$page_number> is the physical page number, starting at 1: 1, 2,...
104              
105             =cut
106              
107             # Note: renamed from pdfile() to pdf_file().
108              
109             sub pdf_file {
110 0     0 1 0 my ($self, $url, $page_number, %options) = @_;
111             # note that although "url" is used, it may be a local file
112              
113 0         0 $self->{'Subtype'} = PDFName('Link');
114 0         0 $self->{'A'} = PDFDict();
115 0         0 $self->{'A'}->{'S'} = PDFName('GoToR');
116 0         0 $self->{'A'}->{'F'} = PDFString($url, 'u');
117              
118 0         0 $page_number--; # wants it numbered starting at 0
119 0         0 $self->dest(PDFNum($page_number), %options);
120 0 0       0 $self->rect(@{$options{'-rect'}}) if defined $options{'-rect'};
  0         0  
121 0 0       0 $self->border(@{$options{'-border'}}) if defined $options{'-border'};
  0         0  
122 0 0       0 $self->Color(@{$options{'-color'}}) if defined $options{'-color'};
  0         0  
123              
124 0         0 return $self;
125             }
126              
127             =item $annotation->file($file, %options)
128              
129             Defines the annotation as a launch-file with filepath C<$file> (a local file)
130             and options %options (-rect, -border, -color: see descriptions below).
131             I the file is displayed depends on the operating system, type of file,
132             and local configuration or mapping.
133              
134             =cut
135              
136             sub file {
137 0     0 1 0 my ($self, $file, %options) = @_;
138              
139 0         0 $self->{'Subtype'} = PDFName('Link');
140 0         0 $self->{'A'} = PDFDict();
141 0         0 $self->{'A'}->{'S'} = PDFName('Launch');
142 0         0 $self->{'A'}->{'F'} = PDFString($file, 'f');
143 0 0       0 $self->rect(@{$options{'-rect'}}) if defined $options{'-rect'};
  0         0  
144 0 0       0 $self->border(@{$options{'-border'}}) if defined $options{'-border'};
  0         0  
145 0 0       0 $self->Color(@{$options{'-color'}}) if defined $options{'-color'};
  0         0  
146              
147 0         0 return $self;
148             }
149              
150             =item $annotation->url($url, %options)
151              
152             Defines the annotation as a launch-url with url C<$url> and
153             options %options (-rect, -border, -color: see descriptions below).
154             This page is usually brought up in a browser, and may be remote.
155              
156             =cut
157              
158             sub url {
159 0     0 1 0 my ($self, $url, %options) = @_;
160              
161 0         0 $self->{'Subtype'} = PDFName('Link');
162 0         0 $self->{'A'} = PDFDict();
163 0         0 $self->{'A'}->{'S'} = PDFName('URI');
164 0         0 $self->{'A'}->{'URI'} = PDFString($url, 'u');
165 0 0       0 $self->rect(@{$options{'-rect'}}) if defined $options{'-rect'};
  0         0  
166 0 0       0 $self->border(@{$options{'-border'}}) if defined $options{'-border'};
  0         0  
167 0 0       0 $self->Color(@{$options{'-color'}}) if defined $options{'-color'};
  0         0  
168              
169 0         0 return $self;
170             }
171              
172             =item $annotation->text($text, %options)
173              
174             Defines the annotation as a text note with content string C<$text> and
175             options %options (-rect, -color, -text, -open: see descriptions below).
176             The C<$text> may include newlines \n for multiple lines.
177              
178             C<-text> is the popup's label string, not to be confused with the main C<$text>.
179              
180             The icon appears in the upper left corner of the C<-rect> selection rectangle,
181             and its active clickable area is fixed by the icon (it is I equal to the
182             rectangle). The icon size is fixed, and its fill color set by C<-color>.
183              
184             Additional options:
185              
186             =over
187              
188             =item -icon => name_string
189              
190             =item -icon => reference
191              
192             Specify the B to be used. The default is Reader-specific (usually
193             C), and others may be
194             defined by the Reader. C, C, C, C,
195             C, and C are also supposed to
196             be available on all PDF Readers. Note that the name I must exactly match.
197             The icon is of fixed size.
198             Any I dictionary entry will override the -icon setting.
199              
200             A I to an icon may be passed instead of a name.
201              
202             =item -opacity => I
203              
204             Define the opacity (non-transparency, opaqueness) of the icon. This value
205             ranges from 0.0 (transparent) to 1.0 (fully opaque), and applies to both
206             the outline and the fill color. The default is 1.0.
207              
208             =back
209              
210             =cut
211              
212             # the icon size appears to be fixed. the last font size used does not affect it
213             # and enabling icon_appearance() for it doesn't seem to do anything
214              
215             sub text {
216 2     2 1 25 my ($self, $text, %options) = @_;
217              
218 2         7 $self->{'Subtype'} = PDFName('Text');
219 2         11 $self->content($text);
220              
221 2 50       8 $self->rect(@{$options{'-rect'}}) if defined $options{'-rect'};
  2         13  
222 2 50       7 $self->open($options{'-open'}) if defined $options{'-open'};
223 2 50       9 $self->Color(@{$options{'-color'}}) if defined $options{'-color'};
  0         0  
224             # popup label (title)
225             # have seen /T as (xFEFF UTF-16 chars)
226 2 50       8 $self->{'T'} = PDFString($options{'-text'}, 'p') if exists $options{'-text'};
227             # icon opacity?
228 2 50       6 if (defined $options{'-opacity'}) {
229 0         0 $self->{'CA'} = PDFNum($options{'-opacity'});
230             }
231              
232             # Icon Name will be ignored if there is an AP.
233 2         5 my $icon; # perlcritic doesn't want 2 lines combined
234 2 50       7 $icon = $options{'-icon'} if exists $options{'-icon'};
235 2 50 33     8 $self->{'Name'} = PDFName($icon) if $icon && !ref($icon); # icon name
236             # Set the icon appearance
237 2 50       4 $self->icon_appearance($icon, %options) if $icon;
238              
239 2         8 return $self;
240             }
241              
242             =item $annotation->markup($text, $PointList, $highlight, %options)
243              
244             Defines the annotation as a text note with content string C<$text> and
245             options %options (-rect, -color, -text, -open: see descriptions below).
246             The C<$text> may include newlines \n for multiple lines.
247              
248             C<-text> is the popup's label string, not to be confused with the main C<$text>.
249              
250             There is no icon. Instead, the annotated text marked by C<$PointList> is
251             highlighted in one of four ways specified by C<$highlight>.
252              
253             =over
254              
255             =item $PointList => [ 8n numbers ]
256              
257             One or more sets of numeric coordinates are given, defining the quadrilateral
258             (usually a rectangle) around the text to be highlighted and selectable
259             (clickable, to bring up the annotation text). These
260             are four sets of C coordinates, given (for Left-to-Right text) as the
261             upper bound Upper Left to Upper Right and then the lower bound Lower Left to
262             Lower Right. B
263             documented in the PDF specification!> It is important that the coordinates be
264             given in this order.
265              
266             Multiple sets of quadrilateral corners may be given, such as for highlighted
267             text that wraps around to new line(s). The minimum is one set (8 numbers).
268             Any I dictionary entry will override the C<$PointList> setting. Finally,
269             the "Rect" selection rectangle is created I the convex bounding
270             box defined by C<$PointList>.
271              
272             =item $highlight => 'string'
273              
274             The following highlighting effects are permitted. The C must be
275             spelled and capitalized I as given:
276              
277             =over
278              
279             =item Highlight
280              
281             The effect of a translucent "highlighter" marker.
282              
283             =item Squiggly
284              
285             The effect is an underline written in a "squiggly" manner.
286              
287             =item StrikeOut
288              
289             The text is struck-through with a straight line.
290              
291             =item Underline
292              
293             The text is marked by a straight underline.
294              
295             =back
296              
297             =item -color => I
298              
299             If C<-color> is not given (an array of numbers in the range 0.0-1.0), a
300             medium gray should be used by default.
301             Named colors are not supported at this time.
302              
303             =item -opacity => I
304              
305             Define the opacity (non-transparency, opaqueness) of the icon. This value
306             ranges from 0.0 (transparent) to 1.0 (fully opaque), and applies to both
307             the outline and the fill color. The default is 1.0.
308              
309             =back
310              
311             =cut
312              
313             sub markup {
314 0     0 1 0 my ($self, $text, $PointList, $highlight, %options) = @_;
315              
316 0         0 my @pointList = @{ $PointList };
  0         0  
317 0 0 0     0 if ((scalar @pointList) == 0 || (scalar @pointList)%8) {
318 0         0 die "markup point list does not have 8*N entries!\n";
319             }
320 0         0 $self->{'Subtype'} = PDFName($highlight);
321 0         0 delete $self->{'Border'};
322 0         0 $self->{'QuadPoints'} = PDFArray(map {PDFNum($_)} @pointList);
  0         0  
323 0         0 $self->content($text);
324              
325 0         0 my $minX = min($pointList[0], $pointList[2], $pointList[4], $pointList[6]);
326 0         0 my $maxX = max($pointList[0], $pointList[2], $pointList[4], $pointList[6]);
327 0         0 my $minY = min($pointList[1], $pointList[3], $pointList[5], $pointList[7]);
328 0         0 my $maxY = max($pointList[1], $pointList[3], $pointList[5], $pointList[7]);
329 0         0 $self->rect($minX-.5,$minY-.5, $maxX+.5,$maxY+.5);
330              
331 0 0       0 $self->open($options{'-open'}) if defined $options{'-open'};
332 0 0       0 if (defined $options{'-color'}) {
333 0         0 $self->Color(@{$options{'-color'}});
  0         0  
334             } else {
335 0         0 $self->Color([]);
336             }
337             # popup label (title)
338             # have seen /T as (xFEFF UTF-16 chars)
339 0 0       0 $self->{'T'} = PDFString($options{'-text'}, 'p') if exists $options{'-text'};
340             # opacity?
341 0 0       0 if (defined $options{'-opacity'}) {
342 0         0 $self->{'CA'} = PDFNum($options{'-opacity'});
343             }
344              
345 0         0 return $self;
346             }
347              
348             =item $annotation->movie($file, $contentType, %options)
349              
350             Defines the annotation as a movie from C<$file> with
351             content (MIME) type C<$contentType> and
352             options %options (-rect, -border, -color: see descriptions below).
353              
354             The C<-rect> rectangle also serves as the area where the movie is played, so it
355             should be of usable size and aspect ratio. It does not use a separate popup
356             player. It is known to play .avi and .wav files -- others have not been tested.
357             Using Adobe Reader, it will not play .mpg files (unsupported type). More work
358             is probably needed on this annotation method.
359              
360             =cut
361              
362             sub movie {
363 0     0 1 0 my ($self, $file, $contentType, %options) = @_;
364              
365 0         0 $self->{'Subtype'} = PDFName('Movie'); # subtype = movie (req)
366 0         0 $self->{'A'} = PDFBool(1); # play using default activation parms
367 0         0 $self->{'Movie'} = PDFDict();
368             #$self->{'Movie'}->{'S'} = PDFName($contentType);
369 0         0 $self->{'Movie'}->{'F'} = PDFString($file, 'f');
370              
371             # PDF::API2 2.034 changes don't seem to work
372             # $self->{'Movie'}->{'F'} = PDFString($file, 'f'); line above removed
373             #$self->{'Movie'}->{'F'} = PDFDict();
374             #$self->{' apipdf'}->new_obj($self->{'Movie'}->{'F'});
375             #my $f = $self->{'Movie'}->{'F'};
376             #$f->{'Type'} = PDFName('EmbeddedFile');
377             #$f->{'Subtype'} = PDFName($contentType);
378             #$f->{' streamfile'} = $file;
379              
380 0 0       0 $self->rect(@{$options{'-rect'}}) if defined $options{'-rect'};
  0         0  
381 0 0       0 $self->border(@{$options{'-border'}}) if defined $options{'-border'};
  0         0  
382 0 0       0 $self->Color(@{$options{'-color'}}) if defined $options{'-color'};
  0         0  
383             # popup label (title) DOESN'T SEEM TO SHOW UP ANYWHERE
384             # self->A->T and self->T also fail to display
385 0 0       0 $self->{'Movie'}->{'T'} = PDFString($options{'-text'}, 'p') if exists $options{'-text'};
386              
387 0         0 return $self;
388             }
389              
390             =item $annotation->file_attachment($file, %options)
391              
392             Defines the annotation as a file attachment with file $file and options %options
393             (-rect, -color: see descriptions below). Note that C<-color> applies to
394             the icon fill color, not to a selectable area outline. The icon is resized
395             (including aspect ratio changes) based on the selectable rectangle given by
396             C<-rect>, so watch your rectangle dimensions!
397              
398             The file, along with its name, is I in the PDF document and may be
399             extracted for viewing with the appropriate viewer.
400              
401             This differs from the C method in that C looks for and launches
402             a file I on the Reader's machine, while C embeds the
403             file in the PDF, and makes it available on the Reader's machine for actions
404             of the user's choosing.
405              
406             B some Readers may only permit an "open" action, and may also restrict
407             file types (extensions) that will be handled. This may be configurable with
408             your Reader's security settings.
409              
410             B the displayed file name (pop-up during mouse rollover of the target
411             rectangle) is given with the I trimmed off (file name only). If you want
412             the displayed name to exactly match the path that was passed to the call,
413             including the path, give the C<-notrimpath> option.
414              
415             Options:
416              
417             =over
418              
419             =item -icon => name_string
420              
421             =item -icon => reference
422              
423             Specify the B to be used. The default is Reader-specific (usually
424             C), and others may be
425             defined by the Reader. C, C, and C are also supposed to
426             be available on all PDF Readers. Note that the name I must exactly match.
427             C is a custom invisible icon defined by PDF::Builder.
428             The icon is stretched/squashed to fill the defined target rectangle, so take
429             care when defining C<-rect> dimensions.
430             Any I dictionary entry will override the -icon setting.
431              
432             A I to an icon may be passed instead of a name.
433              
434             =item -opacity => I
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             =item -notrimpath => 1
441              
442             If given, show the entire path and file name on mouse rollover, rather than
443             just the file name.
444              
445             =item -text => string
446              
447             A text label for the popup (on mouseover) that contains the file name.
448              
449             =back
450              
451             Note that while PDF permits different specifications (paths) to DOS/Windows,
452             Mac, and Unix (including Linux) versions of a file, and different format copies
453             to be embedded, at this time PDF::Builder only permits a single file (format of
454             your choice) to be embedded. If there is user demand for multiple file formats
455             to be referenced and/or embedded, we could look into providing this, I
456             separate OS version paths B be considered obsolescent!>.
457              
458             =cut
459              
460             # TBD it is possible to specify different files for DOS, Mac, Unix
461             # (see PDF 1.7 7.11.4.2). This might solve problem of different line
462             # ends, at the cost of 3 copies of each file.
463              
464             sub file_attachment {
465 0     0 1 0 my ($self, $file, %options) = @_;
466              
467 0         0 my $icon; # defaults to Reader's default (usually PushPin)
468 0 0       0 $icon = $options{'-icon'} if exists $options{'-icon'};
469              
470 0 0       0 $self->rect(@{$options{'-rect'}}) if defined $options{'-rect'};
  0         0  
471             # descriptive text on mouse rollover
472 0 0       0 $self->{'T'} = PDFString($options{'-text'}, 'p') if exists $options{'-text'};
473             # icon opacity?
474 0 0       0 if (defined $options{'-opacity'}) {
475 0         0 $self->{'CA'} = PDFNum($options{'-opacity'});
476             }
477              
478 0         0 $self->{'Subtype'} = PDFName('FileAttachment');
479              
480             # 9 0 obj <<
481             # /Type /Annot
482             # /Subtype /FileAttachment
483             # /Name /PushPin
484             # /C [ 1 1 0 ]
485             # /Contents (test.txt)
486             # /FS <<
487             # /Type /F
488             # /EF << /F 10 0 R >>
489             # /F (test.txt)
490             # >>
491             # /Rect [ 100 100 200 200 ]
492             # /Border [ 0 0 1 ]
493             # >> endobj
494             #
495             # 10 0 obj <<
496             # /Type /EmbeddedFile
497             # /Length ...
498             # >> stream
499             # ...
500             # endstream endobj
501              
502             # text label on pop-up for mouse rollover
503 0         0 my $cName = $file;
504             # trim off any path, leaving just the file name. less confusing that way
505 0 0       0 if (!defined $options{'-notrimpath'}) {
506 0 0       0 if ($cName =~ m#([^/\\]+)$#) { $cName = $1; }
  0         0  
507             }
508 0         0 $self->{'Contents'} = PDFString($cName, 's');
509              
510             # Icon Name will be ignored if there is an AP.
511 0 0 0     0 $self->{'Name'} = PDFName($icon) if $icon && !ref($icon); # icon name
512             #$self->{'F'} = PDFNum(0b0); # flags default to 0
513 0 0       0 $self->Color(@{ $options{'-color'} }) if defined $options{'-color'};
  0         0  
514              
515             # The File Specification.
516 0         0 $self->{'FS'} = PDFDict();
517 0         0 $self->{'FS'}->{'F'} = PDFString($file, 'f');
518 0         0 $self->{'FS'}->{'Type'} = PDFName('Filespec');
519 0         0 $self->{'FS'}->{'EF'} = PDFDict($file);
520 0         0 $self->{'FS'}->{'EF'}->{'F'} = PDFDict($file);
521 0         0 $self->{' apipdf'}->new_obj($self->{'FS'}->{'EF'}->{'F'});
522 0         0 $self->{'FS'}->{'EF'}->{'F'}->{'Type'} = PDFName('EmbeddedFile');
523 0         0 $self->{'FS'}->{'EF'}->{'F'}->{' streamfile'} = $file;
524              
525             # Set the icon appearance
526 0 0       0 $self->icon_appearance($icon, %options) if $icon;
527              
528 0         0 return $self;
529             }
530              
531             # TBD additional annotation types without icons
532             # free text, line, square, circle, polygon (1.5), polyline (1.5), highlight,
533             # underline, squiggly, strikeout, caret (1.5), ink, popup, sound, widget,
534             # screen (1.5), printermark, trapnet, watermark (1.6), 3D (1.6), redact (1.7)
535              
536             # TBD additional annotation types with icons
537             # stamp
538             # icons: Approved, Experimental, NotApproved, Asis, Expired,
539             # NotForPublicRelease, Confidential, Final, Sold, Departmental,
540             # ForComment, TopSecret, Draft (def.), ForPublicRelease
541             # sound
542             # icons: Speaker (def.), Mic
543              
544             # =============== end of annotation types ========================
545              
546             =back
547              
548             =head2 Internal routines and common options
549              
550             =over
551              
552             =item $annotation->rect($llx,$lly, $urx,$ury)
553              
554             Sets the rectangle (active click area) of the annotation, given by -rect option.
555             This is any pair of diagonally opposite corners of the rectangle.
556              
557             The default clickable area is the icon itself.
558              
559             Defining option. I.>
560              
561             =over
562              
563             =item -rect => [LLx, LLy, URx, URy]
564              
565             Set annotation rectangle at C<[LLx,LLy]> to C<[URx,URy]> (lower left and
566             upper right coordinates). LL to UR is customary, but any diagonal is allowed.
567              
568             =back
569              
570             =cut
571              
572             sub rect {
573 2     2 1 7 my ($self, @r) = @_;
574              
575 2 50       10 die "Insufficient parameters to annotation->rect() " unless scalar @r == 4;
576 2         9 $self->{'Rect'} = PDFArray( map { PDFNum($_) } $r[0],$r[1],$r[2],$r[3]);
  8         18  
577 2         6 return $self;
578             }
579              
580             =item $annotation->border(@b)
581              
582             Sets the border-style of the annotation, if applicable, as given by the
583             -border option. There are three entries in the array:
584             horizontal and vertical corner radii, and border width.
585              
586             A border is used in annotations where text or some other material is put down,
587             and a clickable rectangle is defined over it (-rect). A border is not used
588             when an icon is being used to mark the clickable area.
589              
590             The default is [0 0 1] (solid line of width 1, with sharp corners).
591              
592             Defining option:
593              
594             =over
595              
596             =item -border => [CRh, CRv, W]
597              
598             =item -border => [CRh, CRv, W [, on, off...]]
599              
600             Set annotation B of horizontal and vertical corner radii C
601             and C (value 0 for squared corners) and width C (value 0 for no border).
602             The default is squared corners and a solid line of width 1 ([0 0 1]).
603             Optionally, a dash pattern array may be given (C length, C length,
604             as one or more I). The default is a solid line.
605              
606             The border vector seems to ignore the first two settings (corner radii), but
607             the line thickness works, on basic Readers.
608             The radii I work on some other Readers.
609              
610             =back
611              
612             =cut
613              
614             sub border {
615 0     0 1 0 my ($self, @b) = @_;
616              
617 0 0       0 if (scalar @b == 3) {
    0          
618 0         0 $self->{'Border'} = PDFArray( map { PDFNum($_) } $b[0],$b[1],$b[2]);
  0         0  
619             } elsif (scalar @b == 4) {
620             # b[3] is an anonymous array
621 0         0 my @first = map { PDFNum($_) } $b[0], $b[1], $b[2];
  0         0  
622 0         0 $self->{'Border'} = PDFArray( @first, PDFArray( map { PDFNum($_) } @{$b[3]} ));
  0         0  
  0         0  
623             } else {
624 0         0 die "annotation->border() style requires 3 or 4 parameters ";
625             }
626 0         0 return $self;
627             }
628              
629             =item $annotation->content(@lines)
630              
631             Sets the text-content of the C annotation.
632             This is a text string or array of strings.
633              
634             =cut
635              
636             sub content {
637 2     2 1 7 my ($self, @lines) = @_;
638 2         7 my $text = join("\n", @lines);
639            
640 2         9 $self->{'Contents'} = PDFString($text, 's');
641 2         5 return $self;
642             }
643              
644             # unused internal routine? TBD
645             sub name {
646 0     0 0   my ($self, $name) = @_;
647 0           $self->{'Name'} = PDFName($name);
648 0           return $self;
649             }
650              
651             =item $annotation->open($bool)
652              
653             Display the C annotation either open or closed, if applicable.
654              
655             Both are editable; the "open" form brings up the page with the entry area
656             already open for editing, while "closed" has to be clicked on to edit it.
657              
658             Defining option:
659              
660             =over
661              
662             =item -open => boolean
663              
664             If true (1), the annotation will be marked as initially "open".
665             If false (0), or the option is not given, the annotation is initially "closed".
666              
667             =back
668              
669             =cut
670              
671             sub open { ## no critic
672 0     0 1   my ($self, $bool) = @_;
673 0 0         $self->{'Open'} = PDFBool($bool? 1: 0);
674 0           return $self;
675             }
676              
677             =item $annotation->dest($page, I)
678              
679             For certain annotation types (C or C), the I
680             specifies how the content of the page C<$page> is to be fit to the window,
681             while preserving its aspect ratio.
682             These fit settings are:
683              
684             =over
685              
686             =item -fit => 1
687              
688             Display the page with its contents magnified just enough
689             to fit the entire page within the window both horizontally and vertically. If
690             the required horizontal and vertical magnification factors are different, use
691             the smaller of the two, centering the page within the window in the other
692             dimension.
693              
694             =item -fith => $top
695              
696             Display the page with the vertical coordinate C<$top>
697             positioned at the top edge of the window and the contents of the page magnified
698             just enough to fit the entire width of the page within the window.
699              
700             =item -fitv => $left
701              
702             Display the page with the horizontal coordinate C<$left>
703             positioned at the left edge of the window and the contents of the page magnified
704             just enough to fit the entire height of the page within the window.
705              
706             =item -fitr => [$left, $bottom, $right, $top]
707              
708             Display the page with its contents magnified just enough
709             to fit the rectangle specified by the coordinates C<$left>, C<$bottom>,
710             C<$right>, and C<$top> entirely within the window both horizontally and
711             vertically. If the required horizontal and vertical magnification factors are
712             different, use the smaller of the two, centering the rectangle within the window
713             in the other dimension.
714              
715             =item -fitb => 1
716              
717             Display the page with its contents magnified
718             just enough to fit its bounding box entirely within the window both horizontally
719             and vertically. If the required horizontal and vertical magnification factors
720             are different, use the smaller of the two, centering the bounding box within the
721             window in the other dimension.
722              
723             =item -fitbh => $top
724              
725             Display the page with the vertical coordinate
726             C<$top> positioned at the top edge of the window and the contents of the page
727             magnified just enough to fit the entire width of its bounding box within the
728             window.
729              
730             =item -fitbv => $left
731              
732             Display the page with the horizontal
733             coordinate C<$left> positioned at the left edge of the window and the contents
734             of the page magnified just enough to fit the entire height of its bounding box
735             within the window.
736              
737             =item -xyz => [$left, $top, $zoom]
738              
739             Display the page with the coordinates C<[$left, $top]>
740             positioned at the top-left corner of the window and the contents of the page
741             magnified by the factor C<$zoom>. A zero (0) value for any of the parameters
742             C<$left>, C<$top>, or C<$zoom> specifies that the current value of that
743             parameter is to be retained unchanged.
744              
745             This is the B fit setting, with position (left and top) and zoom
746             the same as the calling page's ([undef, undef, undef]).
747              
748             =back
749              
750             =item $annotation->dest($name)
751              
752             Connect the Annotation to a "Named Destination" defined elsewhere, including
753             the optional desired I (default: -xyz undef*3).
754              
755             =cut
756              
757             sub dest {
758 0     0 1   my ($self, $page, %position) = @_;
759              
760 0 0         if (ref $page) {
761 0   0       $self->{'A'} ||= PDFDict();
762              
763 0 0         if (defined $position{'-fit'}) {
    0          
    0          
    0          
    0          
    0          
    0          
    0          
764 0           $self->{'A'}->{'D'} = PDFArray($page, PDFName('Fit'));
765             } elsif (defined $position{'-fith'}) {
766 0           $self->{'A'}->{'D'} = PDFArray($page, PDFName('FitH'), PDFNum($position{'-fith'}));
767             } elsif (defined $position{'-fitb'}) {
768 0           $self->{'A'}->{'D'} = PDFArray($page, PDFName('FitB'));
769             } elsif (defined $position{'-fitbh'}) {
770 0           $self->{'A'}->{'D'} = PDFArray($page, PDFName('FitBH'), PDFNum($position{'-fitbh'}));
771             } elsif (defined $position{'-fitv'}) {
772 0           $self->{'A'}->{'D'} = PDFArray($page, PDFName('FitV'), PDFNum($position{'-fitv'}));
773             } elsif (defined $position{'-fitbv'}) {
774 0           $self->{'A'}->{'D'} = PDFArray($page, PDFName('FitBV'), PDFNum($position{'-fitbv'}));
775             } elsif (defined $position{'-fitr'}) {
776 0 0         die "Insufficient parameters to -fitr => []) " unless scalar @{$position{'-fitr'}} == 4;
  0            
777 0           $self->{'A'}->{'D'} = PDFArray($page, PDFName('FitR'), map {PDFNum($_)} @{$position{'-fitr'}});
  0            
  0            
778             } elsif (defined $position{'-xyz'}) {
779 0 0         die "Insufficient parameters to -xyz => []) " unless scalar @{$position{'-xyz'}} == 3;
  0            
780 0 0         $self->{'A'}->{'D'} = PDFArray($page, PDFName('XYZ'), map {defined $_ ? PDFNum($_) : PDFNull()} @{$position{'-xyz'}});
  0            
  0            
781             } else {
782             # no "fit" option found. use default.
783 0           $position{'-xyz'} = [undef,undef,undef];
784 0 0         $self->{'A'}->{'D'} = PDFArray($page, PDFName('XYZ'), map {defined $_ ? PDFNum($_) : PDFNull()} @{$position{'-xyz'}});
  0            
  0            
785             }
786             } else {
787 0           $self->{'Dest'} = PDFString($page, 'n');
788             }
789              
790 0           return $self;
791             }
792              
793             =item $annotation->Color(@color)
794              
795             Set the icon's fill color. The color is an array of 1, 3, or 4 numbers, each
796             in the range 0.0 to 1.0. If 1 number is given, it is the grayscale value (0 =
797             black to 1 = white). If 3 numbers are given, it is an RGB color value. If 4
798             numbers are given, it is a CMYK color value. Currently, named colors (strings)
799             are not handled.
800              
801             For link and url annotations, this is the color of the rectangle border
802             (-border given with a width of at least 1).
803              
804             If an invalid array length or numeric value is given, a medium gray ( [0.5] )
805             value is used, without any message. If no color is given, the usual fill color
806             is black.
807              
808             Defining option:
809              
810             Named colors are not supported at this time.
811              
812             =over
813              
814             =item -color => [ ] or not 1, 3, or 4 numbers 0.0-1.0
815              
816             A medium gray (0.5 value) will be used if an invalid color is given.
817              
818             =item -color => [ g ]
819              
820             If I is between 0.0 (black) and 1.0 (white), the fill color will be gray.
821              
822             =item -color => [ r, g, b ]
823              
824             If I (red), I (green), and I (blue) are all between 0.0 and 1.0, the
825             fill color will be the defined RGB hue. [ 0, 0, 0 ] is black, [ 1, 1, 0 ] is
826             yellow, and [ 1, 1, 1 ] is white.
827              
828             =item -color => [ c, m, y, k ]
829              
830             If I (red), I (magenta), I (yellow), and I (black) are all between
831             0.0 and 1.0, the fill color will be the defined CMYK hue. [ 0, 0, 0, 0 ] is
832             white, [ 1, 0, 1, 0 ] is green, and [ 1, 1, 1, 1 ] is black.
833              
834             =back
835              
836             =cut
837              
838             sub Color {
839 0     0 1   my ($self, @color) = @_;
840              
841 0 0 0       if (scalar @color == 1 &&
    0 0        
    0 0        
      0        
      0        
      0        
      0        
      0        
      0        
      0        
      0        
      0        
      0        
      0        
      0        
      0        
842             $color[0] >= 0 && $color[0] <= 1.0) {
843 0           $self->{'C'} = PDFArray(map { PDFNum($_) } $color[0]);
  0            
844             } elsif (scalar @color == 3 &&
845             $color[0] >= 0 && $color[0] <= 1.0 &&
846             $color[1] >= 0 && $color[1] <= 1.0 &&
847             $color[2] >= 0 && $color[2] <= 1.0) {
848 0           $self->{'C'} = PDFArray(map { PDFNum($_) } $color[0], $color[1], $color[2]);
  0            
849             } elsif (scalar @color == 4 &&
850             $color[0] >= 0 && $color[0] <= 1.0 &&
851             $color[1] >= 0 && $color[1] <= 1.0 &&
852             $color[2] >= 0 && $color[2] <= 1.0 &&
853             $color[3] >= 0 && $color[3] <= 1.0) {
854 0           $self->{'C'} = PDFArray(map { PDFNum($_) } $color[0], $color[1], $color[2], $color[3]);
  0            
855             } else {
856             # invalid -color entry. just set to medium gray without message
857 0           $self->{'C'} = PDFArray(map { PDFNum($_) } 0.5 );
  0            
858             }
859              
860 0           return $self;
861             }
862              
863             =item -text => string
864              
865             Specify an optional B for annotation. This text or comment only
866             shows up I in the pop-up containing the file or text.
867              
868             =cut
869              
870             sub icon_appearance {
871 0     0 0   my ($self, $icon, %options) = @_;
872             # $icon is a string with name of icon (confirmed not empty) or a reference.
873             # if a string (text), has already defined /Name. "None" and ref handle here.
874             # options of interest: -rect (to define size of icon)
875              
876             # text also permits icon and custom icon, including None
877             #return unless $self->{'Subtype'}->val() eq 'FileAttachment';
878              
879 0           my @r; # perlcritic doesn't want 2 lines combined
880 0 0         @r = @{$options{'-rect'}} if defined $options{'-rect'};
  0            
881             # number of parameters should be 4, checked above (rect method)
882              
883             # Handle custom icon type 'None' and icon reference.
884 0 0         if ($icon eq 'None') {
    0          
885             # It is not clear what viewers will do, so provide an
886             # appearance dict with no graphics content.
887              
888             # 9 0 obj <<
889             # ...
890             # /AP << /D 11 0 R /N 11 0 R /R 11 0 R >>
891             # ...
892             # >>
893             # 11 0 obj <<
894             # /BBox [ 0 0 100 100 ]
895             # /FormType 1
896             # /Length 6
897             # /Matrix [ 1 0 0 1 0 0 ]
898             # /Resources <<
899             # /ProcSet [ /PDF ]
900             # >>
901             # >> stream
902             # 0 0 m
903             # endstream endobj
904              
905 0           $self->{'AP'} = PDFDict();
906 0           my $d = PDFDict();
907 0           $self->{' apipdf'}->new_obj($d);
908 0           $d->{'FormType'} = PDFNum(1);
909 0           $d->{'Matrix'} = PDFArray(map { PDFNum($_) } 1, 0, 0, 1, 0, 0);
  0            
910 0           $d->{'Resources'} = PDFDict();
911 0           $d->{'Resources'}->{'ProcSet'} = PDFArray( map { PDFName($_) } qw(PDF));
  0            
912 0           $d->{'BBox'} = PDFArray( map { PDFNum($_) } 0, 0, $r[2]-$r[0], $r[3]-$r[1] );
  0            
913 0           $d->{' stream'} = "0 0 m";
914 0           $self->{'AP'}->{'N'} = $d; # normal appearance
915             # Should default to N, but be sure.
916 0           $self->{'AP'}->{'R'} = $d; # Rollover
917 0           $self->{'AP'}->{'D'} = $d; # Down
918              
919             # Handle custom icon.
920             } elsif (ref $icon) {
921             # Provide an appearance dict with the image.
922              
923             # 9 0 obj <<
924             # ...
925             # /AP << /D 11 0 R /N 11 0 R /R 11 0 R >>
926             # ...
927             # >>
928             # 11 0 obj <<
929             # /BBox [ 0 0 1 1 ]
930             # /FormType 1
931             # /Length 13
932             # /Matrix [ 1 0 0 1 0 0 ]
933             # /Resources <<
934             # /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ]
935             # /XObject << /PxCBA 7 0 R >>
936             # >>
937             # >> stream
938             # q /PxCBA Do Q
939             # endstream endobj
940              
941 0           $self->{'AP'} = PDFDict();
942 0           my $d = PDFDict();
943 0           $self->{' apipdf'}->new_obj($d);
944 0           $d->{'FormType'} = PDFNum(1);
945 0           $d->{'Matrix'} = PDFArray(map { PDFNum($_) } 1, 0, 0, 1, 0, 0);
  0            
946 0           $d->{'Resources'} = PDFDict();
947 0           $d->{'Resources'}->{'ProcSet'} = PDFArray(map { PDFName($_) } qw(PDF Text ImageB ImageC ImageI));
  0            
948 0           $d->{'Resources'}->{'XObject'} = PDFDict();
949 0           my $im = $icon->{'Name'}->val();
950 0           $d->{'Resources'}->{'XObject'}->{$im} = $icon;
951             # Note that the image is scaled to one unit in user space.
952 0           $d->{'BBox'} = PDFArray(map { PDFNum($_) } 0, 0, 1, 1);
  0            
953 0           $d->{' stream'} = "q /$im Do Q";
954 0           $self->{'AP'}->{'N'} = $d; # normal appearance
955              
956 0           if (0) {
957             # Testing... Provide an alternative for R and D.
958             # Works only with Adobe Reader.
959             $d = PDFDict();
960             $self->{' apipdf'}->new_obj($d);
961             $d->{'Type'} = PDFName('XObject');
962             $d->{'Subtype'} = PDFName('Form');
963             $d->{'FormType'} = PDFNum(1);
964             $d->{'Matrix'} = PDFArray(map { PDFNum($_) } 1, 0, 0, 1, 0, 0);
965             $d->{'Resources'} = PDFDict();
966             $d->{'Resources'}->{'ProcSet'} = PDFArray(map { PDFName($_) } qw(PDF));
967             $d->{'BBox'} = PDFArray(map { PDFNum($_) } 0, 0, $r[2]-$r[0], $r[3]-$r[1]);
968             $d->{' stream'} =
969             join( " ",
970             # black outline
971             0, 0, 'm',
972             0, $r[2]-$r[0], 'l',
973             $r[2]-$r[0], $r[3]-$r[1], 'l',
974             $r[2]-$r[0], 0, 'l',
975             's',
976             );
977             }
978              
979             # Should default to N, but be sure.
980 0           $self->{'AP'}->{'R'} = $d; # Rollover
981 0           $self->{'AP'}->{'D'} = $d; # Down
982             }
983              
984 0           return $self;
985             }
986              
987             =back
988              
989             =cut
990              
991             1;