| 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; |