1package PDF::Builder::Outline; 2 3use base 'PDF::Builder::Basic::PDF::Dict'; 4 5use strict; 6use warnings; 7 8our $VERSION = '3.023'; # VERSION 9our $LAST_UPDATE = '3.020'; # manually update whenever code is changed 10 11use Carp qw(croak); 12use PDF::Builder::Basic::PDF::Utils; 13use Scalar::Util qw(weaken); 14 15=head1 NAME 16 17PDF::Builder::Outline - Manage PDF outlines (a.k.a. I<bookmarks>) 18 19=head1 METHODS 20 21=over 22 23=item $outline = PDF::Builder::Outline->new($api, $parent, $prev) 24 25Returns a new outline object (called from $outlines->outline()). 26 27=cut 28 29sub new { 30 my ($class, $api, $parent, $prev) = @_; 31 my $self = $class->SUPER::new(); 32 33 $self->{'Parent'} = $parent if defined $parent; 34 $self->{'Prev'} = $prev if defined $prev; 35 $self->{' api'} = $api; 36 weaken $self->{' api'}; 37 weaken $self->{'Parent'} if defined $parent; 38 weaken $self->{'Prev'} if defined $prev; 39 40 return $self; 41} 42 43# unused? 44sub parent { 45 my $self = shift(); 46 $self->{'Parent'} = shift() if defined $_[0]; 47 weaken $self->{'Parent'}; 48 return $self->{'Parent'}; 49} 50 51# internal routine 52sub prev { 53 my $self = shift(); 54 $self->{'Prev'} = shift() if defined $_[0]; 55 weaken $self->{'Prev'}; 56 return $self->{'Prev'}; 57} 58 59# internal routine 60sub next { 61 my $self = shift(); 62 $self->{'Next'} = shift() if defined $_[0]; 63 weaken $self->{'Next'}; 64 return $self->{'Next'}; 65} 66 67# internal routine 68sub first { 69 my $self = shift(); 70 71 $self->{'First'} = $self->{' children'}->[0] 72 if defined $self->{' children'} and defined $self->{' children'}->[0]; 73 weaken $self->{'First'}; 74 return $self->{'First'}; 75} 76 77# internal routine 78sub last { 79 my $self = shift(); 80 81 $self->{'Last'} = $self->{' children'}->[-1] 82 if defined $self->{' children'} and defined $self->{' children'}->[-1]; 83 weaken $self->{'Last'}; 84 return $self->{'Last'}; 85} 86 87# internal routine 88sub count { 89 my $self = shift(); 90 91 my $count = scalar @{$self->{' children'} || []}; 92 $count += $_->count() for @{$self->{' children'}}; 93 $self->{'Count'} = PDFNum($self->{' closed'}? -$count: $count) if $count > 0; 94 return $count; 95} 96 97# internal routine 98sub fix_outline { 99 my ($self) = @_; 100 101 $self->first(); 102 $self->last(); 103 $self->count(); 104 return; 105} 106 107=item $outline->title($text) 108 109Set the title of the outline. 110 111=cut 112 113sub title { 114 my ($self, $text) = @_; 115 $self->{'Title'} = PDFString($text, 'o'); 116 return $self; 117} 118 119=item $outline->closed() 120 121Set the status of the outline to closed (i.e., collapsed). 122 123=cut 124 125sub closed { 126 my $self = shift(); 127 $self->{' closed'} = 1; 128 return $self; 129} 130 131=item $outline->open() 132 133Set the status of the outline to open (i.e., expanded). 134 135=cut 136 137sub open { 138 my $self = shift(); 139 delete $self->{' closed'}; 140 return $self; 141} 142 143=item $child_outline = $parent_outline->outline() 144 145Returns a new sub-outline (nested outline). 146 147=cut 148 149sub outline { 150 my $self = shift(); 151 152 my $child = PDF::Builder::Outline->new($self->{' api'}, $self); 153 if (defined $self->{' children'}) { 154 $child->prev($self->{' children'}->[-1]); 155 $self->{' children'}->[-1]->next($child); 156 } 157 push @{$self->{' children'}}, $child; 158 $self->{' api'}->{'pdf'}->new_obj($child) 159 unless $child->is_obj($self->{' api'}->{'pdf'}); 160 161 return $child; 162} 163 164=item $outline->dest($page_object, %position) 165 166=item $outline->dest($page_object) 167 168Sets the destination page and optional position of the outline. 169 170%position can be any of the following: 171 172=over 173 174=item -fit => 1 175 176Display the page designated by C<$page>, with its contents magnified just enough 177to fit the entire page within the window both horizontally and vertically. If 178the required horizontal and vertical magnification factors are different, use 179the smaller of the two, centering the page within the window in the other 180dimension. 181 182=item -fith => $top 183 184Display the page designated by C<$page>, with the vertical coordinate C<$top> 185positioned at the top edge of the window and the contents of the page magnified 186just enough to fit the entire width of the page within the window. 187 188=item -fitv => $left 189 190Display the page designated by C<$page>, with the horizontal coordinate C<$left> 191positioned at the left edge of the window and the contents of the page magnified 192just enough to fit the entire height of the page within the window. 193 194=item -fitr => [$left, $bottom, $right, $top] 195 196Display the page designated by C<$page>, with its contents magnified just enough 197to fit the rectangle specified by the coordinates C<$left>, C<$bottom>, 198C<$right>, and C<$top> entirely within the window both horizontally and 199vertically. If the required horizontal and vertical magnification factors are 200different, use the smaller of the two, centering the rectangle within the window 201in the other dimension. 202 203=item -fitb => 1 204 205Display the page designated by C<$page>, with its contents magnified just 206enough to fit its bounding box entirely within the window both horizontally and 207vertically. If the required horizontal and vertical magnification factors are 208different, use the smaller of the two, centering the bounding box within the 209window in the other dimension. 210 211=item -fitbh => $top 212 213Display the page designated by C<$page>, with the vertical coordinate C<$top> 214positioned at the top edge of the window and the contents of the page magnified 215just enough to fit the entire width of its bounding box within the window. 216 217=item -fitbv => $left 218 219Display the page designated by C<$page>, with the horizontal coordinate C<$left> 220positioned at the left edge of the window and the contents of the page 221magnified just enough to fit the entire height of its bounding box within the 222window. 223 224=item -xyz => [$left, $top, $zoom] 225 226Display the page designated by C<$page>, with the coordinates C<[$left, $top]> 227positioned at the top-left corner of the window and the contents of the page 228magnified by the factor C<$zoom>. A zero (0) value for any of the parameters 229C<$left>, C<$top>, or C<$zoom> specifies that the current value of that 230parameter is to be retained unchanged. 231 232This is the B<default> fit setting, with position (left and top) and zoom 233the same as the calling page's. 234 235=back 236 237=item $outline->dest($name, %position) 238 239=item $outline->dest($name) 240 241Connect the Outline to a "Named Destination" defined elsewhere, 242and optional positioning as described above. 243 244=cut 245 246sub dest { 247 my ($self, $page, %position) = @_; 248 delete $self->{'A'}; 249 250 if (ref($page)) { 251 $self = $self->_fit($page, %position); 252 } else { 253 $self->{'Dest'} = PDFString($page, 'n'); 254 } 255 256 return $self; 257} 258 259=item $outline->url($url) 260 261Defines the outline as launch-url with url C<$url>. 262 263=cut 264 265sub url { 266 my ($self, $url) = @_; 267 268 delete $self->{'Dest'}; 269 $self->{'A'} = PDFDict(); 270 $self->{'A'}->{'S'} = PDFName('URI'); 271 $self->{'A'}->{'URI'} = PDFString($url, 'u'); 272 273 return $self; 274} 275 276=item $outline->file($file) 277 278Defines the outline as launch-file with filepath C<$file>. 279 280=cut 281 282sub file { 283 my ($self, $file) = @_; 284 285 delete $self->{'Dest'}; 286 $self->{'A'} = PDFDict(); 287 $self->{'A'}->{'S'} = PDFName('Launch'); 288 $self->{'A'}->{'F'} = PDFString($file, 'f'); 289 290 return $self; 291} 292 293=item $outline->pdf_file($pdffile, $page_number, %position) 294 295=item $outline->pdf_file($pdffile, $page_number) 296 297Defines the destination of the outline as a PDF-file with filepath 298C<$pdffile>, on page C<$pagenum> (default 0), and position C<%position> 299(same as dest()). 300 301=cut 302 303sub pdf_file { 304 my ($self, $file, $page_number, %position) = @_; 305 306 delete $self->{'Dest'}; 307 $self->{'A'} = PDFDict(); 308 $self->{'A'}->{'S'} = PDFName('GoToR'); 309 $self->{'A'}->{'F'} = PDFString($file, 'f'); 310 $self->{'A'}->{'D'} = $self->_fit(PDFNum($page_number // 0), %position); 311 312 return $self; 313} 314 315=back 316 317=cut 318 319# process destination, including position setting, with default of -xyz undef*3 320sub _fit { 321 my ($self, $destination, %position) = @_; 322 323 if (defined $position{'-fit'}) { 324 $self->{'Dest'} = PDFArray($destination, PDFName('Fit')); 325 } elsif (defined $position{'-fith'}) { 326 $self->{'Dest'} = PDFArray($destination, PDFName('FitH'), PDFNum($position{'-fith'})); 327 } elsif (defined $position{'-fitb'}) { 328 $self->{'Dest'} = PDFArray($destination, PDFName('FitB')); 329 } elsif (defined $position{'-fitbh'}) { 330 $self->{'Dest'} = PDFArray($destination, PDFName('FitBH'), PDFNum($position{'-fitbh'})); 331 } elsif (defined $position{'-fitv'}) { 332 $self->{'Dest'} = PDFArray($destination, PDFName('FitV'), PDFNum($position{'-fitv'})); 333 } elsif (defined $position{'-fitbv'}) { 334 $self->{'Dest'} = PDFArray($destination, PDFName('FitBV'), PDFNum($position{'-fitbv'})); 335 } elsif (defined $position{'-fitr'}) { 336 croak "Insufficient parameters to -fitr => []) " unless scalar @{$position{'-fitr'}} == 4; 337 $self->{'Dest'} = PDFArray($destination, PDFName('FitR'), map {PDFNum($_)} @{$position{'-fitr'}}); 338 } elsif (defined $position{'-xyz'}) { 339 croak "Insufficient parameters to -xyz => []) " unless scalar @{$position{'-xyz'}} == 3; 340 $self->{'Dest'} = PDFArray($destination, PDFName('XYZ'), map {defined $_? PDFNum($_): PDFNull()} @{$position{'-xyz'}}); 341 } else { 342 # no "fit" option found. use default. 343 $position{'-xyz'} = [undef,undef,undef]; 344 $self->{'Dest'} = PDFArray($destination, PDFName('XYZ'), map {defined $_? PDFNum($_): PDFNull()} @{$position{'-xyz'}}); 345 } 346 347 return $self; 348} 349 350#sub out_obj { 351# my ($self, @param) = @_; 352# 353# $self->fix_outline(); 354# return $self->SUPER::out_obj(@param); 355#} 356 357sub outobjdeep { 358# my ($self, @param) = @_; 359# 360# $self->fix_outline(); 361# foreach my $k (qw/ api apipdf apipage /) { 362# $self->{" $k"} = undef; 363# delete($self->{" $k"}); 364# } 365# my @ret = $self->SUPER::outobjdeep(@param); 366# foreach my $k (qw/ First Parent Next Last Prev /) { 367# $self->{$k} = undef; 368# delete($self->{$k}); 369# } 370# return @ret; 371 my $self = shift(); 372 $self->fix_outline(); 373 return $self->SUPER::outobjdeep(@_); 374} 375 3761; 377