File Coverage

blib/lib/PDF/Builder/Annotation.pm
Criterion Covered Total %
statement 121 346 34.9
branch 59 208 28.3
condition 29 186 15.5
subroutine 15 28 53.5
pod 15 22 68.1
total 239 790 30.2


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