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