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 27 55.5
pod 15 21 71.4
total 239 788 30.3


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