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