File Coverage

lib/ChordPro/Output/PDF/Writer.pm
Criterion Covered Total %
statement 344 672 51.1
branch 84 296 28.3
condition 49 222 22.0
subroutine 46 68 67.6
pod 0 42 0.0
total 523 1300 40.2


line stmt bran cond sub pod time code
1             #! perl
2              
3             package main;
4              
5             our $config;
6              
7             package ChordPro::Output::PDF::Writer;
8              
9 10     10   77 use strict;
  10         25  
  10         1235  
10 10     10   643 use warnings;
  10         22  
  10         858  
11 10     10   63 use Text::Layout;
  10         23  
  10         272  
12 10     10   6448 use IO::String;
  10         55237  
  10         1945  
13 10     10   143 use Carp;
  10         26  
  10         1269  
14 10     10   82 use utf8;
  10         29  
  10         95  
15              
16 10     10   408 use ChordPro::Files;
  10         22  
  10         1892  
17 10     10   81 use ChordPro::Paths;
  10         25  
  10         758  
18 10     10   86 use ChordPro::Utils qw( expand_tilde demarkup min is_corefont maybe is_true is_odd );
  10         25  
  10         993  
19 10     10   74 use ChordPro::Output::Common qw( fmt_subst prep_outlines );
  10         21  
  10         689  
20 10     10   66 use Ref::Util qw( is_arrayref is_hashref );
  10         23  
  10         681  
21 10     10   81 use feature 'state';
  10         24  
  10         1717  
22 10     10   106 use Unicode::Collate;
  10         25  
  10         343  
23 10     10   7673 use Unicode::Normalize;
  10         46651  
  10         4797  
24              
25             # For regression testing, run perl with PERL_HASH_SEED set to zero.
26             # This eliminates the arbitrary order of font definitions and triggers
27             # us to pinpoint some other data that would otherwise be varying.
28             my $regtest = defined($ENV{PERL_HASH_SEED}) && $ENV{PERL_HASH_SEED} == 0;
29             my $faketime = 1465041600;
30              
31             my %fontcache; # speeds up 2 seconds per song
32              
33             sub new {
34 8     8 0 37 my ( $pkg, $ps, $pdfapi ) = @_;
35 8         43 my $self = bless { ps => $ps }, $pkg;
36 8         88 $self->{pdfapi} = $pdfapi;
37 8         119 $self->{pdf} = $pdfapi->new;
38 8 50       34649 $self->{pdf}->{forcecompress} = 0 if $regtest;
39             $self->{pdf}->mediabox( $ps->{papersize}->[0],
40 8         90 $ps->{papersize}->[1] );
41             $self->{pdf}->page_layout( $ps->{page_layout} )
42 8 50       1736 if $ps->{page_layout};
43 8         123 $self->{layout} = Text::Layout->new( $self->{pdf} );
44 8         340928 $self->{tmplayout} = undef;
45              
46 10     10   101 no strict 'refs';
  10         28  
  10         2318  
47             # Patches and enhancements to PDF library.
48 8         34 *{$pdfapi . '::Resource::XObject::Form::width' } = \&_xo_width;
  8         212  
49 8         36 *{$pdfapi . '::Resource::XObject::Form::height'} = \&_xo_height;
  8         64  
50              
51 8 50       47 if ( $pdfapi eq 'PDF::API2' ) {
    0          
52 8         20 my $apiversion = ${$pdfapi . '::VERSION'};
  8         72  
53 10     10   85 no warnings 'redefine';
  10         23  
  10         4766  
54              
55             # Fix date validation.
56 0     0   0 *{$pdfapi . '::_is_date'} = sub { 1 }
  0         0  
57 8 50       86 if $apiversion < 2.045;
58              
59             # Enhanced version that allows named destinations.
60 8     8   3091 eval "use $pdfapi" . "::Annotation";
  8         5607  
  8         33950  
  8         439  
61 8 50       101 *{$pdfapi . '::Annotation::pdf' } = \&pdfapi_annotation_pdf
  8         338  
62             if $apiversion < 999; # no milestone yet
63              
64             # Enhanced version that doesn't blow up.
65 8     8   1688 eval "use $pdfapi" . "::Basic::PDF::Array";
  8         85  
  8         20  
  8         220  
66 8 50       84 *{$pdfapi . '::Basic::PDF::Array::outobjdeep' } = \&pdfapi_outobjdeep
  8         231  
67             if $apiversion < 999; # no milestone yet
68             }
69             elsif ( $pdfapi eq 'PDF::Builder' ) {
70 0         0 my $apiversion = ${$pdfapi . '::VERSION'};
  0         0  
71 10     10   90 no warnings 'redefine';
  10         26  
  10         108770  
72              
73             # Enhanced version that allows named destinations.
74 0         0 eval "use $pdfapi" . "::Annotation";
75 0 0       0 *{$pdfapi . '::Annotation::pdf' } = \&pdfapi_annotation_pdf
  0         0  
76             if $apiversion < 999; # no milestone yet
77             }
78              
79             # Text::Layout hooks.
80 8         33 *{$pdfapi . '::named_dest_register' } = \&pdfapi_named_dest_register;
  8         72  
81 8         25 *{$pdfapi . '::named_dest_fiddle' } = \&pdfapi_named_dest_fiddle;
  8         47  
82              
83 8         35 %fontcache = ();
84              
85 8         157 $self->{pdf}->{_pr} = $self;
86             }
87              
88             sub info {
89 8     8 0 49 my ( $self, %info ) = @_;
90              
91 8   33     92 $info{CreationDate} //= pdf_date();
92              
93 8 50       102 if ( $self->{pdf}->can("info_metadata") ) {
94 8         37 for ( keys(%info) ) {
95 32         2140 $self->{pdf}->info_metadata( $_, demarkup($info{$_}) );
96             }
97 8 50       747 if ( $config->{debug}->{runtimeinfo} ) {
98 8         55 $self->{pdf}->info_metadata( "RuntimeInfo",
99             "Runtime Info:\n" . ::runtimeinfo() );
100             }
101             }
102             else {
103 0         0 $self->{pdf}->info(%info);
104             }
105             }
106              
107             # Return a PDF compliant date/time string.
108             sub pdf_date {
109 8     8 0 26 my ( $t ) = @_;
110 8 50 33     77 $t ||= $regtest ? $faketime : time;
111              
112 8         57 my @tm = gmtime($t);
113              
114 8         107 return sprintf(
115             "%04d%02d%02d%02d%02d%02d+00'00'",
116             1900 + $tm[5],
117             $tm[4] + 1,
118             $tm[3],
119             $tm[2],
120             $tm[1],
121             $tm[0],
122             );
123             }
124              
125             sub wrap {
126 192     192 0 757 my ( $self, $text, $m ) = @_;
127              
128 192         585 my $ex = "";
129 192         527 my $sp = "";
130             #warn("TEXT: |$text| ($m)\n");
131 192         818 while ( $self->strwidth($text) > $m ) {
132 0         0 my ( $l, $s, $r ) = $text =~ /^(.+)([-_,.:;\s])(.+)$/;
133 0 0       0 return ( $text, $ex ) unless defined $s;
134             #warn("WRAP: |$text| -> |$l|$s|$r$sp$ex|\n");
135 0 0       0 if ( $s =~ /\S/ ) {
136 0         0 $l .= $s;
137 0         0 $s = "";
138             }
139 0         0 $text = $l;
140 0         0 $ex = $r . $sp . $ex;
141 0         0 $sp = $s;
142             }
143              
144 192         162904 return ( $text, $ex );
145             }
146              
147             sub _fgcolor {
148 497     497   1434 my ( $self, $col ) = @_;
149 497 50 33     7041 if ( !defined($col) || $col =~ /^foreground(?:-medium|-light)?$/ ) {
    0          
    0          
150 497   50     2886 $col = $self->{ps}->{theme}->{$col//"foreground"};
151             }
152             elsif ( $col eq "background" ) {
153 0         0 $col = $self->{ps}->{theme}->{background};
154             }
155             elsif ( !$col ) {
156 0         0 Carp::confess("Undefined fgcolor: $col");
157             }
158 497         1718 $col;
159             }
160              
161             sub _bgcolor {
162 593     593   2108 my ( $self, $col ) = @_;
163 593 100 100     4228 if ( !defined($col) || $col eq "background" ) {
    50          
    0          
164 545         2393 $col = $self->{ps}->{theme}->{background};
165             }
166             elsif ( $col =~ /^foreground(?:-medium|-light)?$/ ) {
167 48         197 $col = $self->{ps}->{theme}->{$col};
168             }
169             elsif ( !$col ) {
170 0         0 Carp::confess("Undefined bgcolor: $col");
171             }
172 593         1849 $col;
173             }
174              
175             sub fix_musicsyms {
176 1182     1182 0 3068 my ( $text, $font ) = @_;
177              
178 1182         3047 for ( $text ) {
179 1182 50       4765 if ( /♯/ ) {
180 0 0 0     0 unless ( $font->{has_sharp} //=
181             $font->{fd}->{font}->glyphByUni(ord("♯")) ne ".notdef" ) {
182 0         0 s;♯;;g;
183             }
184             }
185 1182 50       3667 if ( /♭/ ) {
186 0 0 0     0 unless ( $font->{has_flat} //=
187             $font->{fd}->{font}->glyphByUni(ord("♭")) ne ".notdef" ) {
188 0         0 s;♭;;g;
189             }
190             }
191 1182 50       3746 if ( /Δ/ ) {
192 0 0 0     0 unless ( $font->{has_delta} //=
193             $font->{fd}->{font}->glyphByUni(ord("Δ")) ne ".notdef" ) {
194 0         0 s;Δ;;g;
195             }
196             }
197             }
198 1182         3580 return $text;
199             }
200              
201             sub text {
202 497     497 0 54306 my ( $self, $text, $x, $y, $font, $size, $nomarkup ) = @_;
203             # print STDERR ("T: @_\n");
204 497   66     2322 $font ||= $self->{font};
205 497         1642 $text = fix_musicsyms( $text, $font );
206 497   33     3307 $size ||= $font->{size};
207              
208 497         3389 $self->{layout}->set_font_description($font->{fd});
209 497         11472 $self->{layout}->set_font_size($size);
210             # We don't have set_color in the API.
211 497         5512 $self->{layout}->{_currentcolor} = $self->_fgcolor($font->{color});
212             # Watch out for regression... May have to do this in the nomarkup case only.
213 497 50       1403 if ( $nomarkup ) {
214 0         0 $text =~ s/'/\x{2019}/g; # friendly quote
215 0         0 $self->{layout}->set_text($text);
216             }
217             else {
218 497         2249 $self->{layout}->set_markup($text);
219 497         41336 for ( @{ $self->{layout}->{_content} } ) {
  497         2290  
220 497 50       2020 next unless $_->{type} eq "text";
221 497         1885 $_->{text} =~ s/\'/\x{2019}/g; # friendly quote
222             }
223             }
224 497         2187 $y -= $self->{layout}->get_baseline;
225 497         310089 $self->{layout}->show( $x, $y, $self->{pdftext} );
226              
227 497         2322206 my $e = $self->{layout}->get_pixel_extents;
228 497         17062 $e->{y} += $e->{height};
229              
230             # Handle decorations (background, box).
231 497         3167 my $bgcol = $self->_bgcolor($font->{background});
232 497 50 33     5657 undef $bgcol if $bgcol && $bgcol =~ /^no(?:ne)?$/i;
233 497 50       1962 my $debug = $ENV{CHORDPRO_DEBUG_TEXT} ? "magenta" : undef;
234 497   33     2400 my $frame = $font->{frame} || $debug;
235 497 50 33     1732 undef $frame if $frame && $frame =~ /^no(?:ne)?$/i;
236 497 50 33     2288 if ( $bgcol || $frame ) {
237 0 0       0 printf("BB: %.2f %.2f %.2f %.2f\n", @{$e}{qw( x y width height ) } )
  0         0  
238             if $debug;
239             # Draw background and.or frame.
240 0 0       0 my $d = $debug ? 0 : 1;
241 0 0 0     0 $frame = $debug || $font->{color} || $self->{ps}->{theme}->{foreground} if $frame;
242             $self->rectxy( $x + $e->{x} - $d,
243             $y + $e->{y} + $d,
244             $x + $e->{x} + $e->{width} + $d,
245 0         0 $y + $e->{y} - $e->{height} - $d,
246             0.5, $bgcol, $frame);
247             }
248              
249 497         1437 $x += $e->{width};
250             # print STDERR ("TX: $x\n");
251 497         3369 return $x;
252             }
253              
254             sub setfont {
255 449     449 0 1468 my ( $self, $font, $size ) = @_;
256 449         1415 $self->{font} = $font;
257             warn("PDF: Font ", $font->{_ff}, " should have a size!\n")
258 449 50 33     3301 unless $size ||= $font->{size};
259 449   0     1695 $self->{fontsize} = $size ||= $font->{size} || $font->{fd}->{size};
      33        
260 449         3589 $self->{pdftext}->font( $font->{fd}->{font}, $size );
261             }
262              
263             sub font_bl {
264 280     280 0 25298 my ( $self, $font ) = @_;
265             # $font->{size} / ( 1 - $font->{fd}->{font}->descender / $font->{fd}->{font}->ascender );
266 280         1983 $font->{size} * $font->{fd}->{font}->ascender / 1000;
267             }
268              
269             sub font_ul {
270 0     0 0 0 my ( $self, $font ) = @_;
271 0         0 $font->{fd}->{font}->underlineposition / 1024 * $font->{size};
272             }
273              
274             sub strwidth {
275 685     685 0 2274 my ( $self, $text, $font, $size ) = @_;
276 685   66     4180 $font ||= $self->{font};
277 685         2358 $text = fix_musicsyms( $text, $font );
278 685   33     4529 $size ||= $self->{fontsize} || $font->{size};
      33        
279 685   66     2645 $self->{tmplayout} //= $self->{layout}->copy;
280 685         4501 $self->{tmplayout}->set_font_description($font->{fd});
281 685         15341 $self->{tmplayout}->set_font_size($size);
282 685         7285 $self->{tmplayout}->set_markup($text);
283             wantarray ? $self->{tmplayout}->get_pixel_size
284 685 50       62794 : $self->{tmplayout}->get_pixel_size->{width};
285             }
286              
287             sub strheight {
288 0     0 0 0 my ( $self, $text, $font, $size ) = @_;
289 0   0     0 $font ||= $self->{font};
290 0         0 $text = fix_musicsyms( $text, $font );
291 0   0     0 $size ||= $self->{fontsize} || $font->{size};
      0        
292 0   0     0 $self->{tmplayout} //= $self->{layout}->copy;
293 0         0 $self->{tmplayout}->set_font_description($font->{fd});
294 0         0 $self->{tmplayout}->set_font_size($size);
295 0         0 $self->{tmplayout}->set_markup($text);
296             wantarray ? $self->{tmplayout}->get_pixel_size
297 0 0       0 : $self->{tmplayout}->get_pixel_size->{height};
298             }
299              
300             sub line {
301 0     0 0 0 my ( $self, $x0, $y0, $x1, $y1, $lw, $color ) = @_;
302 0         0 my $gfx = $self->{pdfgfx};
303 0         0 $gfx->save;
304 0         0 $gfx->strokecolor( $self->_fgcolor($color) );
305 0         0 $gfx->linecap(1);
306 0   0     0 $gfx->linewidth($lw||1);
307 0         0 $gfx->move( $x0, $y0 );
308 0         0 $gfx->line( $x1, $y1 );
309 0         0 $gfx->stroke;
310 0         0 $gfx->restore;
311             }
312              
313             sub hline {
314 0     0 0 0 my ( $self, $x, $y, $w, $lw, $color, $cap ) = @_;
315 0   0     0 $cap //= 2;
316 0         0 my $gfx = $self->{pdfgfx};
317 0         0 $gfx->save;
318 0         0 $gfx->strokecolor( $self->_fgcolor($color) );
319 0         0 $gfx->linecap($cap);
320 0   0     0 $gfx->linewidth($lw||1);
321 0         0 $gfx->move( $x, $y );
322 0         0 $gfx->hline( $x + $w );
323 0         0 $gfx->stroke;
324 0         0 $gfx->restore;
325             }
326              
327             sub vline {
328 0     0 0 0 my ( $self, $x, $y, $h, $lw, $color, $cap ) = @_;
329 0   0     0 $cap //= 2;
330 0         0 my $gfx = $self->{pdfgfx};
331 0         0 $gfx->save;
332 0         0 $gfx->strokecolor( $self->_fgcolor($color) );
333 0         0 $gfx->linecap($cap);
334 0   0     0 $gfx->linewidth($lw||1);
335 0         0 $gfx->move( $x, $y );
336 0         0 $gfx->vline( $y - $h );
337 0         0 $gfx->stroke;
338 0         0 $gfx->restore;
339             }
340              
341             sub rectxy {
342 0     0 0 0 my ( $self, $x, $y, $x1, $y1, $lw, $fillcolor, $strokecolor ) = @_;
343 0         0 my $gfx = $self->{pdfgfx};
344 0         0 $gfx->save;
345 0 0       0 $gfx->strokecolor($self->_fgcolor($strokecolor)) if $strokecolor;
346 0 0       0 $gfx->fillcolor($self->_fgcolor($fillcolor)) if $fillcolor;
347 0         0 $gfx->linecap(2);
348 0   0     0 $gfx->linewidth($lw||1);
349 0         0 $gfx->rectxy( $x, $y, $x1, $y1 );
350 0 0 0     0 $gfx->fill if $fillcolor && !$strokecolor;
351 0 0 0     0 $gfx->fillstroke if $fillcolor && $strokecolor;
352 0 0 0     0 $gfx->stroke if $strokecolor && !$fillcolor;
353 0         0 $gfx->restore;
354             }
355              
356             sub poly {
357 0     0 0 0 my ( $self, $points, $lw, $fillcolor, $strokecolor ) = @_;
358 0 0       0 undef $strokecolor unless $lw;
359 0         0 my $gfx = $self->{pdfgfx};
360 0         0 $gfx->save;
361 0 0       0 $gfx->strokecolor($self->_fgcolor($strokecolor)) if $strokecolor;
362 0 0       0 $gfx->fillcolor($self->_fgcolor($fillcolor)) if $fillcolor;
363 0         0 $gfx->linecap(2);
364 0         0 $gfx->linewidth($lw);
365 0         0 $gfx->poly( @$points );
366 0         0 $gfx->close;
367 0 0 0     0 $gfx->fill if $fillcolor && !$strokecolor;
368 0 0 0     0 $gfx->fillstroke if $fillcolor && $strokecolor;
369 0 0 0     0 $gfx->stroke if $strokecolor && !$fillcolor;
370 0         0 $gfx->restore;
371             }
372              
373             sub circle {
374 0     0 0 0 my ( $self, $x, $y, $r, $lw, $fillcolor, $strokecolor ) = @_;
375 0         0 my $gfx = $self->{pdfgfx};
376 0         0 $gfx->save;
377 0 0       0 $gfx->strokecolor($self->_fgcolor($strokecolor)) if $strokecolor;
378 0 0       0 $gfx->fillcolor($self->_fgcolor($fillcolor)) if $fillcolor;
379 0   0     0 $gfx->linewidth($lw||1);
380 0         0 $gfx->circle( $x, $y, $r );
381 0 0 0     0 $gfx->fill if $fillcolor && !$strokecolor;
382 0 0 0     0 $gfx->fillstroke if $fillcolor && $strokecolor;
383 0 0 0     0 $gfx->stroke if $strokecolor && !$fillcolor;
384 0         0 $gfx->restore;
385             }
386              
387             sub cross {
388 0     0 0 0 my ( $self, $x, $y, $r, $lw, $strokecolor ) = @_;
389 0         0 my $gfx = $self->{pdfgfx};
390 0         0 $gfx->save;
391 0 0       0 $gfx->strokecolor($self->_fgcolor($strokecolor)) if $strokecolor;
392 0   0     0 $gfx->linewidth($lw||1);
393 0         0 $r = 0.9 * $r;
394 0         0 $gfx->move( $x-$r, $y-$r );
395 0         0 $gfx->line( $x+$r, $y+$r );
396 0 0       0 $gfx->stroke if $strokecolor;
397 0         0 $gfx->move( $x-$r, $y+$r );
398 0         0 $gfx->line( $x+$r, $y-$r );
399 0 0       0 $gfx->stroke if $strokecolor;
400 0         0 $gfx->restore;
401             }
402              
403             # Fetch an image or xform object.
404             # Source is $elt->{uri} (for files), $elt->{chord} (for chords).
405             # Result is delivered, and stored in $elt->{data};
406             sub get_image {
407 0     0 0 0 my ( $self, $elt ) = @_;
408              
409 0         0 my $img;
410 0         0 my $subtype = $elt->{subtype};
411 0         0 my $data;
412              
413 0 0       0 if ( $subtype eq "delegate" ) {
414 0         0 croak("delegated image in get_image()");
415             }
416              
417 0 0       0 if ( $elt->{data} ) { # have data
418 0         0 $data = $elt->{data};
419             warn("get_image($elt->{subtype}): data ", length($data), " bytes\n")
420 0 0       0 if $config->{debug}->{images};
421 0         0 return $data;
422             }
423              
424 0         0 my $uri = $elt->{uri};
425 0 0 0     0 if ( !$subtype && $uri =~ /\.(\w+)$/ ) {
426 0   0     0 $subtype //= $1;
427             }
428              
429 0 0       0 if ( $subtype =~ /^(jpg|png|gif)$/ ) {
    0          
430 0         0 $img = $self->{pdf}->image($uri);
431             warn("get_image($subtype, $uri): img ", length($img), " bytes\n")
432 0 0       0 if $config->{debug}->{images};
433             }
434             elsif ( $subtype =~ /^(xform)$/ ) {
435 0         0 $img = $data;
436             warn("get_image($subtype): xobject (",
437             # join(" ", $img->bbox),
438 0         0 join(" ", @{$data->{bbox}}),
439             ")\n")
440 0 0       0 if $config->{debug}->{images};
441             }
442             else {
443 0         0 croak("Unhandled image type: $subtype\n");
444             }
445 0         0 return $img;
446             }
447              
448             sub _xo_width {
449 0     0   0 my ( $self ) = @_;
450 0         0 my @bb = $self->bbox;
451 0         0 return abs($bb[2]-$bb[0]);
452             }
453             sub _xo_height {
454 0     0   0 my ( $self ) = @_;
455 0         0 my @bb = $self->bbox;
456 0         0 return abs($bb[3]-$bb[1]);
457             }
458              
459             sub add_object {
460 0     0 0 0 my ( $self, $o, $x, $y, %options ) = @_;
461              
462 0   0     0 my $scale_x = $options{"xscale"} || $options{"scale"} || 1;
463 0   0     0 my $scale_y = $options{"yscale"} || $options{"scale"} || $scale_x;
464              
465 0   0     0 my $va = $options{valign} // "bottom";
466 0   0     0 my $ha = $options{align} // "left";
467              
468 0         0 my $gfx = $self->{pdfgfx};
469 0         0 my $w = $o->width * $scale_x;
470 0         0 my $h = $o->height * $scale_y;
471              
472             warn( sprintf("add_object x=%.1f y=%.1f w=%.1f h=%.1f scale=%.1f,%.1f %s\n",
473             $x, $y, $w, $h, $scale_x, $scale_y, $ha,
474 0 0       0 ) ) if $config->{debug}->{images};
475              
476 0 0       0 $self->crosshairs( $x, $y, color => "lime" ) if $config->{debug}->{images};
477 0 0       0 if ( $va eq "top" ) {
    0          
478 0         0 $y -= $h;
479             }
480             elsif ( $va eq "middle" ) {
481 0         0 $y -= $h/2;
482             }
483 0 0       0 if ( $ha eq "right" ) {
    0          
484 0         0 $x -= $w;
485             }
486             elsif ( $ha eq "center" ) {
487 0         0 $x -= $w/2;
488             }
489              
490 0 0       0 $self->crosshairs( $x, $y, color => "red" ) if $config->{debug}->{images};
491 0         0 $gfx->save;
492 0 0       0 if ( ref($o) =~ /::Resource::XObject::Image::/ ) {
493             # Image wants width and height.
494 0         0 $gfx->object( $o, $x, $y, $w, $h );
495             }
496             else {
497             # XO_Form wants xscale and yscale.
498 0         0 my @bb = $o->bbox;
499 0         0 $gfx->object( $o, $x-min($bb[0],$bb[2])*$scale_x,
500             $y-min($bb[1],$bb[3])*$scale_y, $scale_x, $scale_y );
501             }
502              
503 0 0       0 if ( $options{border} ) {
504 0   0     0 my $bc = $self->_fgcolor($options{"bordercolor"} || $options{"color"});
505 0         0 my $lw = $options{border};
506              
507             # Selective parts, Top Right Bottom Left.
508 0   0     0 my $trbl = lc( $options{bordertrbl} // "trbl" );
509 0 0       0 unless ( $trbl =~ /^[trbl]*$/ ) {
510 0         0 warn("Image with invalid bordertrbl ($trbl)\n");
511 0         0 $trbl = "trbl";
512             }
513 0 0       0 $gfx->stroke_color($bc) if $bc;
514 0 0 0     0 if ( $trbl =~ /t/ && $trbl =~ /r/
    0 0        
      0        
515             && $trbl =~ /b/ && $trbl =~ /l/ ) { # full rect
516 0         0 $gfx->rectangle( $x, $y, $x+$w, $y+$h )
517             ->line_width($lw)
518             ->stroke;
519             }
520             elsif ( $trbl ) {
521             # Projecting square cap.
522 0         0 $gfx->line_width($lw)->line_cap(2);
523 0 0       0 $gfx->move( $x, $y )->vline( $y+$h ) if $trbl =~ /l/;
524 0 0       0 $gfx->move( $x, $y )->hline( $x+$w ) if $trbl =~ /b/;
525 0 0       0 $gfx->move( $x+$w, $y )->vline( $y+$h ) if $trbl =~ /r/;
526 0 0       0 $gfx->move( $x, $y+$h )->hline( $x+$w ) if $trbl =~ /t/;
527 0         0 $gfx->stroke;
528             }
529             }
530              
531 0 0       0 if ( $options{href} ) {
532 0         0 my $a = $gfx->{' apipage'}->annotation;
533 0         0 $a->url( $options{href}, -rect => [ $x, $y, $x+$w, $y+$h ] );
534             }
535              
536 0         0 $gfx->restore;
537             }
538              
539             # For convenience.
540             sub crosshairs {
541 0     0 0 0 my ( $self, $x, $y, %options ) = @_;
542 0         0 my $gfx = $self->{pdfgfx};
543 0   0     0 my $col = $options{colour} || $options{color} || "black";
544 0   0     0 my $lw = $options{linewidth} || 0.1;
545 0   0     0 my $w = ( $options{width} || 40 ) / 2;
546 0   0     0 my $h = ( $options{width} || $options{height} || 40 ) / 2;
547 0         0 for ( $gfx ) {
548 0         0 $_->save;
549 0         0 $_->line_width($lw);
550 0         0 $_->stroke_color($col);
551 0         0 $_->move($x-$w,$y);
552 0         0 $_->hline($x+$w);
553 0         0 $_->move($x,$y+$h);
554 0         0 $_->vline($y-$h);
555 0         0 $_->stroke;
556 0         0 $_->restore;
557             }
558             }
559              
560             sub add_image {
561 0     0 0 0 my ( $self, $img, $x, $y, $w, $h,
562             $border, $trbl ) = @_;
563 0         0 $self->add_object( $img, $x, $y,
564             xscale => $w/$img->width,
565             yscale => $h/$img->height,
566             valign => "bottom",
567             maybe border => $border,
568             maybe bordertrbl => $trbl );
569             }
570              
571             sub newpage {
572 64     64 0 214 my ( $self, $page ) = @_;
573 64         208 my $ps = $self->{ps};
574             #$self->{pdftext}->textend if $self->{pdftext};
575 64   100     697 $page ||= 0;
576              
577             # PDF::API2 says $page must refer to an existing page.
578             # Set to 0 to append.
579 64 100       619 $page = 0 if $page == $self->{pdf}->pages + 1;
580              
581 64         1264 $self->{pdfpage} = $self->{pdf}->page($page);
582             $self->{pdfpage}->mediabox( $ps->{papersize}->[0],
583 64         67757 $ps->{papersize}->[1] );
584              
585 64         12748 $self->{pdfgfx} = $self->{pdfpage}->gfx;
586 64         21560 $self->{pdftext} = $self->{pdfpage}->text;
587 64 50       26186 unless ($ps->{theme}->{background} =~ /^white|none|#ffffff$/i ) {
588 0         0 for ( $self->{pdfgfx} ) {
589 0         0 $_->save;
590 0         0 $_->fillcolor( $ps->{theme}->{background} );
591 0         0 $_->linewidth(0);
592             $_->rectxy( 0, 0, $ps->{papersize}->[0],
593 0         0 $ps->{papersize}->[1] );
594 0         0 $_->fill;
595 0         0 $_->restore;
596             }
597             }
598             }
599              
600             # Align.
601             # Ordinal page numbers start with 1.
602             # Assuming the next page to be written is $page, do we need
603             # to insert alignment pages?
604             # If so, insert them, and return the number of pages inserted (zero or one).
605             # Alignment is to an odd page, except for the back matter, whose
606             # final page must be even.
607              
608             sub page_align {
609 58     58 0 269 my ( $self, $pagectrl, $part, $page, $even ) = @_;
610 58         368 my $ret = $self->_page_align( $pagectrl, $part, $page, $even );
611             warn( "ALIGN( $part, page $page, ",
612             defined($even) ? "even $even, " : "",
613             ChordPro::Output::PDF::pagectrl_msg($pagectrl),
614             " ) -> $ret\n")
615             if exists($::config->{debug}->{pagealign})
616 58 0 33     415 && $::config->{debug}->{pagealign};
    0          
617 58         250 return $ret;
618             }
619              
620             sub _page_align {
621 58     58   221 my ( $self, $pagectrl, $part, $page, $even ) = @_;
622 58   100     458 $even ||= 0;
623              
624             # Only align to odd pages.
625 58 100 75     270 return 0 if $even xor is_odd($page); # already odd/even
626 29 100       201 return 0 unless $pagectrl->{dual_pages}; # no alignment
627 25 100       109 return 0 unless $pagectrl->{align_songs}; # no alignment
628              
629 10     10   152 use List::Util 'shuffle';
  10         26  
  10         95332  
630 24         75 my $ps = $self->{ps};
631 24         100 my $bg;
632             my $ffile;
633 24         0 my $filler;
634 24 50 33     220 if ( ($bg = $ps->{formats}->{filler}->{background})
      33        
635             &&
636             ( $ffile = expand_tilde($bg) )
637             &&
638             ( $filler = $self->{pdfapi}->open($ffile) )
639             ) {
640 0         0 state $file = "";
641 0         0 state @pages;
642 0 0 0     0 if ( $file ne $ffile || !@pages ) {
643 0         0 $file = $ffile;
644             # Try to make it reproducible.
645 0         0 local $ENV{PERL_HASH_SEED} = 0x12a02ab;
646 0         0 srand();
647 0         0 @pages = shuffle( 1..$filler->pages );
648             }
649             # Pick a random page.
650 0         0 $self->{pdf}->import_page( $filler, shift(@pages), $page );
651             }
652             else {
653 24         162 $self->newpage($page);
654             }
655 24         106 return 1; # number of pages added
656             }
657              
658             sub openpage {
659 40     40 0 171 my ( $self, $page ) = @_;
660 40         450 $self->{pdfpage} = $self->{pdf}->openpage($page);
661 40 50       2571 confess("Fatal: Page $page not found.") unless $self->{pdfpage};
662 40         295 $self->{pdfgfx} = $self->{pdfpage}->gfx;
663 40         14579 $self->{pdftext} = $self->{pdfpage}->text;
664             }
665              
666             sub importpage {
667 0     0 0 0 my ( $self, $fn, $pg ) = @_;
668 0         0 my $bg = $self->{pdfapi}->open($fn);
669 0 0       0 return unless $bg; # should have been checked
670 0 0       0 $pg = $bg->pages if $pg > $bg->pages;
671 0         0 $self->{pdf}->import_page( $bg, $pg, $self->{pdfpage} );
672             # Make sure the contents get on top of it.
673 0         0 $self->{pdfgfx} = $self->{pdfpage}->gfx;
674 0         0 $self->{pdftext} = $self->{pdfpage}->text;
675             }
676              
677             sub importfile {
678 0     0 0 0 my ( $self, $filename ) = @_;
679 0         0 my $pdf = $self->{pdfapi}->open($filename);
680 0 0       0 return unless $pdf; # should have been checked
681 0         0 for ( my $page = 1; $page <= $pdf->pages; $page++ ) {
682 0         0 $self->{pdf}->import_page( $pdf, $page );
683             }
684 0         0 return { pages => $pdf->pages, $pdf->info_metadata };
685             }
686              
687             sub pagelabel {
688 30     30 0 102 my ( $self, $page, $style, $prefix, $start ) = @_;
689 30   50     113 $style //= 'arabic';
690 30   100     176 $start //= 1;
691              
692             # PDF::API2 2.042 has some incompatible changes...
693 30         156 my $c = $self->{pdf}->can("page_labels");
694 30 50       132 if ( $c ) { # 2.042+
695 30 50       283 my $opts = { style => $style eq 'Roman' ? 'R' :
    50          
    100          
    50          
    100          
696             $style eq 'roman' ? 'r' :
697             $style eq 'Alpha' ? 'A' :
698             $style eq 'alpha' ? 'a' : 'D',
699             defined $prefix ? ( prefix => $prefix ) : (),
700             start => $start };
701 30         166 $c->( $self->{pdf}, $page+1, %$opts );
702             }
703             else {
704 0 0       0 my $opts = { -style => $style,
705             defined $prefix ? ( -prefix => $prefix ) : (),
706             -start => $start };
707 0         0 $self->{pdf}->pageLabel( $page, $opts );
708             }
709             }
710              
711             sub make_outlines {
712 8     8 0 33 my ( $self, $bk, $start ) = @_;
713 8 50 33     112 return unless $bk && @$bk; # unlikely
714              
715 8         31 my $pdf = $self->{pdf};
716 8         22 $start--; # 1-relative
717 8         37 my $ol_root;
718              
719             # Process outline defs from config.
720 8         24 foreach my $ctl ( @{ $self->{ps}->{outlines} } ) {
  8         48  
721 24 100 100     1968 next if is_true( $ctl->{omit} // 0 );
722 16         58 my $book;
723              
724 16 50 33     41 if ( @{$ctl->{fields}} == 1 && $ctl->{fields}->[0] eq "bookmark" ) {
  16         112  
725 0         0 my @book;
726 0         0 while ( my ($k,$v) = each %{$self->{ps}->{pr}->{_nd}} ) {
  0         0  
727 0         0 push( @book,
728 0         0 [ $k =~ s/^song_([0-9]+)$/sprintf("song_%06d",$1)/er,
729             { meta => { tocpage => $v,
730             bookmark => $k } } ] );
731             }
732 0         0 my $cmp = Unicode::Collate->new;
733 0         0 $book = [ sort { $cmp->cmp($a->[0], $b->[0]) } @book ];
  0         0  
734             }
735             else {
736 16         113 $book = prep_outlines( $bk, $ctl );
737             }
738 16 50       126 next unless @$book;
739              
740             # Seems not to matter whether we re-use the root or create new.
741 16   66     157 $ol_root //= $pdf->outlines;
742              
743 16         55417 my $outline;
744              
745             # Skip level for a single outline.
746 16 50       66 if ( @{ $self->{ps}->{outlines} } == 1 ) {
  16         165  
747 0         0 $outline = $ol_root;
748 0 0       0 $outline->closed if $ctl->{collapse}; # TODO?
749             }
750             else {
751 16         127 $outline = $ol_root->outline;
752 16         2846 $outline->title( $ctl->{label} );
753 16 50       807 $outline->closed if $ctl->{collapse};
754             }
755              
756 16         47 my %lh; # letter hierarchy
757 16         53 my $needlh = 0;
758 16 50       99 if ( $ctl->{letter} > 0 ) {
759 16         61 for ( @$book ) {
760             # Group on first letter.
761             # That's why we left the sort fields in...
762 48         371 my $cur = uc(substr(NFKD($_->[0]),0,1) );
763 48   100     270 $lh{$cur} //= [];
764             # Last item is the song.
765 48         82 push( @{$lh{$cur}}, $_->[-1] );
  48         136  
766             }
767             # Need letter hierarchy?
768 16         70 $needlh = keys(%lh) >= $ctl->{letter};
769             }
770              
771 16 50       81 if ( $needlh ) {
772 0         0 my $cur_ol;
773 0         0 my $cur_let = "";
774 0         0 my $cmp = Unicode::Collate->new;
775 0         0 foreach my $let ( $cmp->sort( keys %lh )) {
776 0         0 foreach my $song ( @{$lh{$let}} ) {
  0         0  
777 0 0 0     0 unless ( defined $cur_ol && ( $let eq $cur_let ) ) {
778             # Intermediate level autoline.
779 0         0 $cur_ol = $outline->outline;
780 0         0 $cur_ol->title($let);
781 0         0 $cur_let = $let;
782             }
783             # Leaf outline.
784 0         0 my $ol = $cur_ol->outline;
785             # Display info.
786 0         0 $ol->title( demarkup( fmt_subst( $song, $ctl->{line} ) ) );
787 0         0 my $p = $song->{meta}->{tocpage};
788 0 0       0 $p = $pdf->openpage( $p + $start ) unless ref($p);
789 0   0     0 my $c = $ol->can("destination") // $ol->can("dest");
790 0         0 $ol->$c($p);
791             }
792             }
793             }
794             else {
795             ####TODO: Why?
796 16 50 33     112 if ( @$book == 1 && ref($book->[0]) eq 'ChordPro::Song' ) {
797 0         0 $book = [[ $book->[0] ]];
798             }
799 16         68 foreach my $b ( @$book ) {
800 48         3522 my $song = $b->[-1];
801             # Leaf outline.
802 48         186 my $ol = $outline->outline;
803             # Display info.
804 48         7032 $ol->title( demarkup( fmt_subst( $song, $ctl->{line} ) ) );
805 48         2572 my $p = $song->{meta}->{tocpage};
806 48 50       496 $p = $pdf->openpage( $p + $start ) unless ref($p);
807 48   33     3036 my $c = $ol->can("destination") // $ol->can("dest");
808 48         385 $ol->$c($p);
809             }
810             }
811             }
812              
813             =for xxx
814              
815             # Add bookmarks.
816             my $outline = $ol_root->outline;
817             $outline->title("Bookmarks");
818             $outline->closed;
819              
820             my @tops =
821             map { $_->[0] }
822             sort { $a->[1] cmp $b->[1] }
823             map { [ $_ => s/^song_([0-9]+)$/sprintf("song_%06d",$1)/er ] }
824             grep { ! /^(?:cover|front|toc|back)$/ }
825             keys %{ $self->{_nd} };
826              
827             for ( "cover", "front", "toc", @tops, "back" ) {
828             next unless my $p = $self->{_nd}->{$_};
829             my $ol = $outline->outline;
830             $ol->title($_);
831             if ( my $c = $ol->can("destination") ) {
832             $c->( $ol, $p );
833             }
834             else {
835             $ol->dest($p);
836             }
837             }
838              
839             =cut
840              
841             }
842              
843             sub finish {
844 8     8 0 37 my ( $self, $file ) = @_;
845              
846             ::dump($self->{pdf}->{pagestack})
847 8 50       74 if $::config->{debug}->{pages} & 0x04;
848              
849 8 50 33     78 if ( $file && $file ne "-" ) {
850 8         165 my $fd = fs_open( $file, '>:raw' );
851 8         112 print $fd $self->{pdf}->stringify;
852 8         2179333 close($fd);
853             }
854             else {
855 0         0 binmode(STDOUT);
856 0         0 print STDOUT ( $self->{pdf}->stringify );
857 0         0 close(STDOUT);
858             }
859             }
860              
861             sub init_fonts {
862 40     40 0 163 my ( $self ) = @_;
863 40         153 my $ps = $self->{ps};
864 40         88 my $fail;
865              
866 40         659 my $fc = Text::Layout::FontConfig->new( debug => $config->{debug}->{fonts} > 1 );
867              
868             # Add font dirs.
869 40         1109 my @dirs;
870 40         100 my @d = ( @{$ps->{fontdir}}, @{ CP->findresdirs("fonts") }, $ENV{FONTDIR} );
  40         142  
  40         372  
871             # Avoid rsc result if dummy.
872 40 50       249 splice( @d, -2, 1 ) if $d[-2] eq "fonts/";
873 40         123 for my $fontdir ( @d ) {
874 80 100       342 next unless $fontdir;
875 40         260 $fontdir = expand_tilde($fontdir);
876 40 50       203 if ( fs_test( d => $fontdir ) ) {
877 40         707 $self->{pdfapi}->can("addFontDirs")->($fontdir);
878 40         938 $fc->add_fontdirs($fontdir);
879 40         1648 push( @dirs, $fontdir );
880             }
881             else {
882 0         0 warn("PDF: Ignoring fontdir $fontdir [$!]\n");
883 0         0 undef $fontdir;
884             }
885             }
886              
887             # Make sure we have this one.
888 40         321 $fc->register_font( "ChordProSymbols.ttf", "chordprosymbols", "", {} );
889              
890             # Remap corefonts if possible.
891 40   33     6191 my $remap = $ENV{CHORDPRO_COREFONTS_REMAP} // $ps->{corefonts}->{remap};
892             # Packager adds the fonts.
893 40 50 0     242 $remap //= "free" if CP->packager;
894              
895 40 50       157 unless ( defined $remap ) {
896              
897             # Not defined -- find the GNU Free Fonts.
898 40         151 for my $dir ( @dirs ) {
899 40         101 my $have = 1;
900 40         133 for my $font ( qw( FreeSerif.ttf
901             FreeSerifBoldItalic.ttf
902             FreeSerifBold.ttf
903             FreeSerifItalic.ttf
904             FreeSans.ttf
905             FreeSansBoldOblique.ttf
906             FreeSansBold.ttf
907             FreeSansOblique.ttf
908             FreeMono.ttf
909             FreeMonoBoldOblique.ttf
910             FreeMonoBold.ttf
911             FreeMonoOblique.ttf
912             ) ) {
913 480 50       1869 $have = 0, last unless fs_test( fs => "$dir/$font" );
914             }
915 40 50       212 $remap = "free", last if $have;
916             }
917             }
918 40 50       393 $fc->register_corefonts( remap => $remap ) if $remap;
919              
920             # Process the fontconfig.
921 40         63406 foreach my $ff ( keys( %{ $ps->{fontconfig} } ) ) {
  40         339  
922 440         1335 my @fam = split( /\s*,\s*/, $ff );
923 440         774 foreach my $s ( keys( %{ $ps->{fontconfig}->{$ff} } ) ) {
  440         1727  
924 1640         74581 my $v = $ps->{fontconfig}->{$ff}->{$s};
925 1640 50       3403 if ( is_hashref($v) ) {
926 0         0 my $file = delete( $v->{file} );
927 0         0 $fc->register_font( $file, $fam[0], $s, $v );
928             }
929             else {
930 1640         4804 $fc->register_font( $v, $fam[0], $s );
931             }
932             }
933 440 50       26410 $fc->register_aliases(@fam) if @fam > 1;
934             }
935              
936 40         180 foreach my $ff ( keys( %{ $ps->{fonts} } ) ) {
  40         414  
937 880 50       20392 $self->init_font($ff) or $fail++;
938             }
939              
940 40 50       1488 die("Unhandled fonts detected -- aborted\n") if $fail;
941             }
942              
943             sub init_font {
944 880     880 0 2248 my ( $self, $ff ) = @_;
945 880         2031 my $ps = $self->{ps};
946 880         1419 my $fd;
947 880 100       5329 if ( $ps->{fonts}->{$ff}->{file} ) {
    100          
    50          
948 40         270 $fd = $self->init_filefont($ff);
949             }
950             elsif ( $ps->{fonts}->{$ff}->{description} ) {
951 80         475 $fd = $self->init_pangofont($ff);
952             }
953             elsif ( $ps->{fonts}->{$ff}->{name} ) {
954 760         2433 $fd = $self->init_corefont($ff);
955             }
956 880 50       3943 warn("No font found for \"$ff\"\n") unless $fd;
957 880         24216 $fd;
958             }
959              
960             sub init_pangofont {
961 80     80 0 230 my ( $self, $ff ) = @_;
962              
963 80         234 my $ps = $self->{ps};
964 80         235 my $font = $ps->{fonts}->{$ff};
965              
966 80         547 my $fc = Text::Layout::FontConfig->new( debug => $config->{debug}->{fonts} > 1 );
967 80 50       1360 eval {
968 80         500 $font->{fd} = $fc->from_string($font->{description});
969 80         20024 $font->{fd}->get_font($self->{layout}); # force load
970 80 50       1387133 $font->{fd}->{font}->{Name}->{val} =~ s/~.*/~$faketime/ if $regtest;
971 80         306 $font->{_ff} = $ff;
972 80   33     496 $font->{fd}->set_shaping( $font->{fd}->get_shaping || $font->{shaping}//0);
      50        
973 80 100       2157 $font->{size} = $font->{fd}->get_size if $font->{fd}->get_size;
974 80         1703 1;
975             } or return;
976 80         425 $font->{fd};
977             }
978              
979             sub init_filefont {
980 40     40 0 140 my ( $self, $ff ) = @_;
981              
982 40         147 my $ps = $self->{ps};
983 40         119 my $font = $ps->{fonts}->{$ff};
984              
985 40         320 my $fc = Text::Layout::FontConfig->new( debug => $config->{debug}->{fonts} > 1 );
986 40         705 eval {
987 40         239 my $t = $fc->from_filename(expand_tilde($font->{file}));
988 40         4357 $t->get_font($self->{layout}); # force load
989 40 50       271895 $t->{font}->{Name}->{val} =~ s/~.*/~$faketime/ if $regtest;
990 40         145 $t->{_ff} = $ff;
991 40         182 $font->{fd} = $t;
992             };
993 40         224 $font->{fd};
994             }
995              
996             sub init_corefont {
997 760     760 0 1665 my ( $self, $ff ) = @_;
998              
999 760         1581 my $ps = $self->{ps};
1000 760         1662 my $font = $ps->{fonts}->{$ff};
1001 760         3330 my $cf = is_corefont($font->{name});
1002 760 50       2088 die("Config error: \"$font->{name}\" is not a built-in font\n")
1003             unless $cf;
1004 760         5100 my $fc = Text::Layout::FontConfig->new( debug => $config->{debug}->{fonts} > 1 );
1005 760         12649 eval {
1006 760         2629 $font->{fd} = $fc->from_filename($cf);
1007 760         70766 $font->{fd}->get_font($self->{layout}); # force load
1008 760         60185040 $font->{_ff} = $ff;
1009             };
1010 760         3705 $font->{fd};
1011             }
1012              
1013             sub show_vpos {
1014 0     0 0 0 my ( $self, $y, $w ) = @_;
1015 0         0 $self->{pdfgfx}->move(100*$w,$y)->linewidth(0.25)->hline(100*(1+$w))->stroke;
1016             }
1017              
1018             sub embed {
1019 0     0 0 0 my ( $self, $file ) = @_;
1020 0 0       0 return unless fs_test( 'f', $file );
1021              
1022             # Borrow some routines from PDF Api.
1023 0         0 *PDFNum = \&{$self->{pdfapi} . '::Basic::PDF::Utils::PDFNum'};
  0         0  
1024 0         0 *PDFStr = \&{$self->{pdfapi} . '::Basic::PDF::Utils::PDFStr'};
  0         0  
1025              
1026             # The song.
1027             # Apparently the 'hidden' flag does not hide it completely,
1028             # so give it a rect outside the page.
1029 0         0 my $a = $self->{pdfpage}->annotation();
1030 0         0 $a->text( fs_load( $file, { fail => "soft", split => 0 } ),
1031             -open => 0, -rect => [0,0,-1,-1] );
1032 0         0 $a->{T} = PDFStr("ChordProSong");
1033 0         0 $a->{F} = PDFNum(2); # hidden
1034              
1035             # The config.
1036 0         0 $a = $self->{pdfpage}->annotation();
1037 0         0 $a->text( ChordPro::Config::config_final(),
1038             -open => 0, -rect => [0,0,-1,-1]);
1039 0         0 $a->{T} = PDFStr("ChordProConfig");
1040 0         0 $a->{F} = PDFNum(2); # hidden
1041              
1042             # Runtime info.
1043 0         0 $a = $self->{pdfpage}->annotation();
1044 0         0 $a->text( ::runtimeinfo(),
1045             -open => 0, -rect => [0,0,-1,-1] );
1046 0         0 $a->{T} = PDFStr("ChordProRunTime");
1047 0         0 $a->{F} = PDFNum(2); # hidden
1048              
1049             # Call.
1050 0         0 $a = $self->{pdfpage}->annotation();
1051 0         0 $a->text( join(" ", @{$::options->{_argv}}) . "\n",
  0         0  
1052             -open => 0, -rect => [0,0,-1,-1] );
1053 0         0 $a->{T} = PDFStr("ChordProCall");
1054 0         0 $a->{F} = PDFNum(2); # hidden
1055             }
1056              
1057             # Add a Named Destination.
1058              
1059             sub named_dest {
1060 54     54 0 2674 my ( $self, $name, $page ) = @_;
1061 54 50       248 $name = $name->[-1] if is_arrayref($name);
1062 54         164 my $pdf = $self->{pdf};
1063 54         190 my $nd = ref($pdf) . '::NamedDestination';
1064 54         573 my $dest = $nd->new($pdf);
1065 54         6751 $dest->goto( $page, xyz => (undef,undef,undef) );
1066 54         8312 $pdf->named_destination( 'Dests', $name, $dest );
1067 54         7433 $pdf->named_dest_register( $name, $page );
1068             }
1069              
1070             sub pdfapi_named_dest_register {
1071 54     54 0 183 my ( $self, $name, $page ) = @_;
1072 54 50       208 Carp::cluck("Undef \$name in pdfapi_named_dest_register")
1073             unless defined $name;
1074 54         380 $self->{_pr}->{_nd}->{$name} = $page;
1075             }
1076              
1077             sub pdfapi_named_dest_fiddle {
1078 0     0 0 0 my ( $self, $name ) = @_;
1079 0 0       0 $name eq 'top' ? $self->{_pr}->{bookmark} : $name;
1080             }
1081              
1082             # Enhanced version that allows named destinations.
1083             sub pdfapi_annotation_pdf {
1084             package PDF::API2;
1085 0     0 0 0 my $self = shift();
1086 0         0 my $file = shift();
1087 0         0 my $dest = shift();
1088 0         0 my $location;
1089             my @args;
1090              
1091             # Deprecated options
1092 0         0 my %options;
1093 0 0 0     0 if ($_[0] and $_[0] =~ /^-/) {
1094 0         0 %options = @_;
1095             }
1096             else {
1097 0         0 $location = shift();
1098 0         0 @args = @_;
1099             }
1100              
1101 0         0 $self->{'Subtype'} = PDFName('Link');
1102 0         0 $self->{'A'} = PDFDict();
1103 0         0 $self->{'A'}->{'S'} = PDFName('GoToR');
1104 0         0 $self->{'A'}->{'F'} = PDFStr($file);
1105              
1106 0 0       0 unless (%options) {
1107 0 0       0 if ( $dest =~ /^\/(.+)/ ) { # named dest
1108 0         0 $self->{'A'}->{'D'} = PDFName($1);
1109             }
1110             else {
1111 0         0 my $destination = PDFNum($dest);
1112 0         0 $self->{'A'}->{'D'} = _destination($destination, $location, @args);
1113             }
1114             }
1115             else {
1116             # Deprecated
1117 0         0 $self->dest(PDFNum($dest), %options);
1118 0 0       0 $self->rect(@{$options{'-rect'}}) if defined $options{'-rect'};
  0         0  
1119 0 0       0 $self->border(@{$options{'-border'}}) if defined $options{'-border'};
  0         0  
1120             }
1121              
1122 0         0 return $self;
1123             }
1124              
1125             # Prevent from blowing up.
1126             sub pdfapi_outobjdeep {
1127 1493     1493 0 33388259 my ( $self, $fh, $pdf ) = @_;
1128              
1129 1493         5310 $fh->print('[ ');
1130 1493         8783 foreach my $obj (@{$self->{' val'}}) {
  1493         4909  
1131             # if no graphics object (page->gfx), creates an invalid Contents object
1132             # (unblessed HASH containing no keys) for this page's graphics, and
1133             # this function blows up
1134 4522 50       29104 if ($obj !~ /^PDF::API2/) { next; }
  0         0  
1135              
1136 4522         14824 $obj->outobj($fh, $pdf);
1137 4522         116090 $fh->print(' ');
1138             }
1139 1493         10183 $fh->print(']');
1140             }
1141              
1142             1;