File Coverage

lib/ChordPro/Output/PDF/Writer.pm
Criterion Covered Total %
statement 270 468 57.6
branch 58 194 29.9
condition 38 133 28.5
subroutine 37 49 75.5
pod 0 33 0.0
total 403 877 45.9


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   88 use strict;
  10         24  
  10         449  
10 10     10   93 use warnings;
  10         23  
  10         409  
11 10     10   74 use Encode;
  10         21  
  10         1009  
12 10     10   5508 use Text::Layout;
  10         112912  
  10         403  
13 10     10   5706 use IO::String;
  10         30246  
  10         354  
14 10     10   87 use Carp;
  10         23  
  10         604  
15 10     10   74 use utf8;
  10         29  
  10         73  
16              
17 10     10   331 use ChordPro::Utils qw( expand_tilde demarkup );
  10         30  
  10         718  
18 10     10   93 use ChordPro::Output::Common qw( fmt_subst prep_outlines );
  10         30  
  10         2762  
19              
20             # For regression testing, run perl with PERL_HASH_SEED set to zero.
21             # This eliminates the arbitrary order of font definitions and triggers
22             # us to pinpoint some other data that would otherwise be varying.
23             my $regtest = defined($ENV{PERL_HASH_SEED}) && $ENV{PERL_HASH_SEED} == 0;
24             my $faketime = 1465041600;
25              
26             my %fontcache; # speeds up 2 seconds per song
27              
28             sub new {
29 8     8 0 34 my ( $pkg, $ps, $pdfapi ) = @_;
30 8         42 my $self = bless { ps => $ps }, $pkg;
31 8         67 $self->{pdfapi} = $pdfapi;
32 8         79 $self->{pdf} = $pdfapi->new;
33 8 50       23680 $self->{pdf}->{forcecompress} = 0 if $regtest;
34             $self->{pdf}->mediabox( $ps->{papersize}->[0],
35 8         65 $ps->{papersize}->[1] );
36 8         1619 $self->{layout} = Text::Layout->new( $self->{pdf} );
37 8         48052 $self->{tmplayout} = undef;
38              
39 8         29 %fontcache = ();
40              
41 8         34 $self;
42             }
43              
44             sub info {
45 8     8 0 48 my ( $self, %info ) = @_;
46              
47 8   33     91 $info{CreationDate} //= pdf_date();
48              
49             # PDF::API2 2.42+ does not accept the final apostrophe.
50 10     10   95 no warnings 'redefine';
  10         25  
  10         2382  
51 8     8   83 local *PDF::API2::_is_date = sub { 1 };
  8         198  
52              
53 8 50       102 if ( $self->{pdf}->can("info_metadata") ) {
54 8         45 for ( keys(%info) ) {
55 24         732 $self->{pdf}->info_metadata( $_, $info{$_} );
56             }
57             }
58             else {
59 0         0 $self->{pdf}->info(%info);
60             }
61             }
62              
63             # Return a PDF compliant date/time string.
64             sub pdf_date {
65 8     8 0 29 my ( $t ) = @_;
66 8 50 33     74 $t ||= $regtest ? $faketime : time;
67              
68 10     10   124 use POSIX qw( strftime );
  10         28  
  10         105  
69 8         454 my $r = strftime( "%Y%m%d%H%M%S%z", localtime($t) );
70             # Don't use s///r to keep PERL_MIN_VERSION low.
71 8         127 $r =~ s/(..)$/'$1'/; # +0100 -> +01'00'
72 8         56 $r;
73             }
74              
75             sub wrap {
76 144     144 0 347 my ( $self, $text, $m ) = @_;
77              
78 144         248 my $ex = "";
79 144         255 my $sp = "";
80             #warn("TEXT: |$text| ($m)\n");
81 144         401 while ( $self->strwidth($text) > $m ) {
82 0         0 my ( $l, $s, $r ) = $text =~ /^(.+)([-_,.:;\s])(.+)$/;
83 0 0       0 return ( $text, $ex ) unless defined $s;
84             #warn("WRAP: |$text| -> |$l|$s|$r$sp$ex|\n");
85 0 0       0 if ( $s =~ /\S/ ) {
86 0         0 $l .= $s;
87 0         0 $s = "";
88             }
89 0         0 $text = $l;
90 0         0 $ex = $r . $sp . $ex;
91 0         0 $sp = $s;
92             }
93              
94 144         40460 return ( $text, $ex );
95             }
96              
97             sub _fgcolor {
98 1044     1044   2233 my ( $self, $col ) = @_;
99 1044 100 66     6180 if ( !defined($col) || $col =~ /^foreground(?:-medium|-light)?$/ ) {
    50          
    50          
100 684   50     2601 $col = $self->{ps}->{theme}->{$col//"foreground"};
101             }
102             elsif ( $col eq "background" ) {
103 0         0 $col = $self->{ps}->{theme}->{background};
104             }
105             elsif ( !$col ) {
106 0         0 Carp::confess("Undefined fgcolor: $col");
107             }
108 1044         3051 $col;
109             }
110              
111             sub _bgcolor {
112 732     732   1741 my ( $self, $col ) = @_;
113 732 50 66     3609 if ( !defined($col) || $col eq "background" ) {
    0          
    0          
114 732         1759 $col = $self->{ps}->{theme}->{background};
115             }
116             elsif ( $col =~ /^foreground(?:-medium|-light)?$/ ) {
117 0         0 $col = $self->{ps}->{theme}->{$col};
118             }
119             elsif ( !$col ) {
120 0         0 Carp::confess("Undefined bgcolor: $col");
121             }
122 732         1532 $col;
123             }
124              
125             sub fix_musicsyms {
126 1243     1243 0 2623 my ( $text, $font ) = @_;
127              
128 1243         2594 for ( $text ) {
129 1243 50       3560 if ( /♯/ ) {
130 0 0 0     0 unless ( $font->{has_sharp} //=
131             $font->{fd}->{font}->glyphByUni(ord("♯")) ne ".notdef" ) {
132 0         0 s;♯;#;g;
133             }
134             }
135 1243 50       3274 if ( /♭/ ) {
136 0 0 0     0 unless ( $font->{has_flat} //=
137             $font->{fd}->{font}->glyphByUni(ord("♭")) ne ".notdef" ) {
138 0         0 s;♭;!;g;
139             }
140             }
141             }
142 1243         2710 return $text;
143             }
144              
145             sub text {
146 684     684 0 27372 my ( $self, $text, $x, $y, $font, $size, $nomarkup ) = @_;
147             # print STDERR ("T: @_\n");
148 684   66     2259 $font ||= $self->{font};
149 684         1515 $text = fix_musicsyms( $text, $font );
150 684   66     2834 $size ||= $font->{size};
151              
152 684         2756 $self->{layout}->set_font_description($font->{fd});
153 684         10747 $self->{layout}->set_font_size($size);
154             # We don't have set_color in the API.
155 684         4435 $self->{layout}->{_currentcolor} = $self->_fgcolor($font->{color});
156             # Watch out for regression... May have to do this in the nomarkup case only.
157 684 50       1398 if ( $nomarkup ) {
158 0         0 $text =~ s/'/\x{2019}/g; # friendly quote
159 0         0 $self->{layout}->set_text($text);
160             }
161             else {
162 684         2175 $self->{layout}->set_markup($text);
163 684         34346 for ( @{ $self->{layout}->{_content} } ) {
  684         1903  
164 684         1892 $_->{text} =~ s/\'/\x{2019}/g; # friendly quote
165             }
166             }
167 684         2079 $y -= $self->{layout}->get_baseline;
168 684         167847 $self->{layout}->show( $x, $y, $self->{pdftext} );
169              
170 684         1126842 my $e = $self->{layout}->get_pixel_extents;
171 684         17462 $e->{y} += $e->{height};
172              
173             # Handle decorations (background, box).
174 684         2232 my $bgcol = $self->_bgcolor($font->{background});
175 684 50 33     5074 undef $bgcol if $bgcol && $bgcol =~ /^no(?:ne)?$/i;
176 684 50       2086 my $debug = $ENV{CHORDPRO_DEBUG_TEXT} ? "magenta" : undef;
177 684   33     2143 my $frame = $font->{frame} || $debug;
178 684 50 33     1707 undef $frame if $frame && $frame =~ /^no(?:ne)?$/i;
179 684 50 33     2208 if ( $bgcol || $frame ) {
180 0 0       0 printf("BB: %.2f %.2f %.2f %.2f\n", @{$e}{qw( x y width height ) } )
  0         0  
181             if $debug;
182             # Draw background and.or frame.
183 0 0       0 my $d = $debug ? 0 : 1;
184 0 0 0     0 $frame = $debug || $font->{color} || $self->{ps}->{theme}->{foreground} if $frame;
185             # $self->crosshair( $x, $y, 20, 0.2, "magenta" );
186             $self->rectxy( $x + $e->{x} - $d,
187             $y + $e->{y} + $d,
188             $x + $e->{x} + $e->{width} + $d,
189 0         0 $y + $e->{y} - $e->{height} - $d,
190             0.5, $bgcol, $frame);
191             }
192              
193 684         1263 $x += $e->{width};
194             # print STDERR ("TX: $x\n");
195 684         3058 return $x;
196             }
197              
198             sub setfont {
199 682     682 0 1608 my ( $self, $font, $size ) = @_;
200 682         1257 $self->{font} = $font;
201             warn("PDF: Font ", $font->{_ff}, " should have a size!\n")
202 682 50 66     2915 unless $size ||= $font->{size};
203 682   0     1854 $self->{fontsize} = $size ||= $font->{size} || $font->{fd}->{size};
      33        
204 682         2782 $self->{pdftext}->font( $font->{fd}->{font}, $size );
205             }
206              
207             sub strwidth {
208 559     559 0 1405 my ( $self, $text, $font, $size ) = @_;
209 559   33     2755 $font ||= $self->{font};
210 559         1300 $text = fix_musicsyms( $text, $font );
211 559   33     2838 $size ||= $self->{fontsize} || $font->{size};
      33        
212 559   66     1702 $self->{tmplayout} //= Text::Layout->new( $self->{pdf} );
213 559         2829 $self->{tmplayout}->set_font_description($font->{fd});
214 559         8760 $self->{tmplayout}->set_font_size($size);
215 559         3971 $self->{tmplayout}->set_markup($text);
216 559         29463 $self->{tmplayout}->get_pixel_size->{width};
217             }
218              
219             sub strheight {
220 0     0 0 0 my ( $self, $text, $font, $size ) = @_;
221 0   0     0 $font ||= $self->{font};
222 0         0 $text = fix_musicsyms( $text, $font );
223 0   0     0 $size ||= $self->{fontsize} || $font->{size};
      0        
224 0   0     0 $self->{tmplayout} //= Text::Layout->new( $self->{pdf} );
225 0         0 $self->{tmplayout}->set_font_description($font->{fd});
226 0         0 $self->{tmplayout}->set_font_size($size);
227 0         0 $self->{tmplayout}->set_markup($text);
228 0         0 $self->{tmplayout}->get_pixel_size->{height};
229             }
230              
231             sub line {
232 0     0 0 0 my ( $self, $x0, $y0, $x1, $y1, $lw, $color ) = @_;
233 0         0 my $gfx = $self->{pdfgfx};
234 0         0 $gfx->save;
235 0         0 $gfx->strokecolor( $self->_fgcolor($color) );
236 0         0 $gfx->linecap(1);
237 0   0     0 $gfx->linewidth($lw||1);
238 0         0 $gfx->move( $x0, $y0 );
239 0         0 $gfx->line( $x1, $y1 );
240 0         0 $gfx->stroke;
241 0         0 $gfx->restore;
242             }
243              
244             sub hline {
245 120     120 0 354 my ( $self, $x, $y, $w, $lw, $color, $cap ) = @_;
246 120   50     566 $cap //= 2;
247 120         255 my $gfx = $self->{pdfgfx};
248 120         423 $gfx->save;
249 120         5834 $gfx->strokecolor( $self->_fgcolor($color) );
250 120         17785 $gfx->linecap($cap);
251 120   50     6793 $gfx->linewidth($lw||1);
252 120         6132 $gfx->move( $x, $y );
253 120         12535 $gfx->hline( $x + $w );
254 120         11354 $gfx->stroke;
255 120         5129 $gfx->restore;
256             }
257              
258             sub vline {
259 144     144 0 5831 my ( $self, $x, $y, $h, $lw, $color, $cap ) = @_;
260 144   50     723 $cap //= 2;
261 144         288 my $gfx = $self->{pdfgfx};
262 144         446 $gfx->save;
263 144         6620 $gfx->strokecolor( $self->_fgcolor($color) );
264 144         20267 $gfx->linecap($cap);
265 144   50     7917 $gfx->linewidth($lw||1);
266 144         6980 $gfx->move( $x, $y );
267 144         15128 $gfx->vline( $y - $h );
268 144         13467 $gfx->stroke;
269 144         5857 $gfx->restore;
270             }
271              
272             sub rectxy {
273 0     0 0 0 my ( $self, $x, $y, $x1, $y1, $lw, $fillcolor, $strokecolor ) = @_;
274 0         0 my $gfx = $self->{pdfgfx};
275 0         0 $gfx->save;
276 0 0       0 $gfx->strokecolor($self->_fgcolor($strokecolor)) if $strokecolor;
277 0 0       0 $gfx->fillcolor($self->_fgcolor($fillcolor)) if $fillcolor;
278 0         0 $gfx->linecap(2);
279 0   0     0 $gfx->linewidth($lw||1);
280 0         0 $gfx->rectxy( $x, $y, $x1, $y1 );
281 0 0 0     0 $gfx->fill if $fillcolor && !$strokecolor;
282 0 0 0     0 $gfx->fillstroke if $fillcolor && $strokecolor;
283 0 0 0     0 $gfx->stroke if $strokecolor && !$fillcolor;
284 0         0 $gfx->restore;
285             }
286              
287             sub poly {
288 0     0 0 0 my ( $self, $points, $lw, $fillcolor, $strokecolor ) = @_;
289 0 0       0 undef $strokecolor unless $lw;
290 0         0 my $gfx = $self->{pdfgfx};
291 0         0 $gfx->save;
292 0 0       0 $gfx->strokecolor($self->_fgcolor($strokecolor)) if $strokecolor;
293 0 0       0 $gfx->fillcolor($self->_fgcolor($fillcolor)) if $fillcolor;
294 0         0 $gfx->linecap(2);
295 0         0 $gfx->linewidth($lw);
296 0         0 $gfx->poly( @$points );
297 0         0 $gfx->close;
298 0 0 0     0 $gfx->fill if $fillcolor && !$strokecolor;
299 0 0 0     0 $gfx->fillstroke if $fillcolor && $strokecolor;
300 0 0 0     0 $gfx->stroke if $strokecolor && !$fillcolor;
301 0         0 $gfx->restore;
302             }
303              
304             sub circle {
305 48     48 0 169 my ( $self, $x, $y, $r, $lw, $fillcolor, $strokecolor ) = @_;
306 48         110 my $gfx = $self->{pdfgfx};
307 48         189 $gfx->save;
308 48 50       2577 $gfx->strokecolor($self->_fgcolor($strokecolor)) if $strokecolor;
309 48 50       7286 $gfx->fillcolor($self->_fgcolor($fillcolor)) if $fillcolor;
310 48   50     279 $gfx->linewidth($lw||1);
311 48         2585 $gfx->circle( $x, $y, $r );
312 48 50       180435 $gfx->fill if $fillcolor;
313 48 50       264 $gfx->stroke if $strokecolor;
314 48         2151 $gfx->restore;
315             }
316              
317             sub cross {
318 48     48 0 177 my ( $self, $x, $y, $r, $lw, $strokecolor ) = @_;
319 48         109 my $gfx = $self->{pdfgfx};
320 48         245 $gfx->save;
321 48 50       2828 $gfx->strokecolor($self->_fgcolor($strokecolor)) if $strokecolor;
322 48   50     7427 $gfx->linewidth($lw||1);
323 48         2450 $r = 0.9 * $r;
324 48         262 $gfx->move( $x-$r, $y-$r );
325 48         5959 $gfx->line( $x+$r, $y+$r );
326 48 50       5499 $gfx->stroke if $strokecolor;
327 48         2200 $gfx->move( $x-$r, $y+$r );
328 48         5358 $gfx->line( $x+$r, $y-$r );
329 48 50       5185 $gfx->stroke if $strokecolor;
330 48         2034 $gfx->restore;
331             }
332              
333             sub crosshair { # for debugging
334 0     0 0 0 my ( $self, $x, $y, $r, $lw, $strokecolor ) = @_;
335 0         0 my $gfx = $self->{pdfgfx};
336 0         0 $gfx->save;
337 0 0       0 $gfx->strokecolor($self->_fgcolor($strokecolor)) if $strokecolor;
338 0   0     0 $gfx->linewidth($lw||1);
339 0         0 $gfx->move( $x, $y - $r );
340 0         0 $gfx->line( $x, $y + $r );
341 0 0       0 $gfx->stroke if $strokecolor;
342 0         0 $gfx->move( $x - $r, $y );
343 0         0 $gfx->line( $x + $r, $y );
344 0 0       0 $gfx->stroke if $strokecolor;
345 0         0 $gfx->restore;
346             }
347              
348             sub get_image {
349 0     0 0 0 my ( $self, $elt ) = @_;
350              
351 0         0 my $img;
352 0         0 my $uri = $elt->{uri};
353 0 0       0 warn("get_image($uri)\n") if $config->{debug}->{images};
354 0 0       0 if ( $uri =~ /^id=(.+)/ ) {
355 0         0 my $a = $ChordPro::Output::PDF::assets->{$1};
356              
357 0 0       0 if ( $a->{type} eq "abc" ) {
    0          
    0          
    0          
358 0         0 my $res = ChordPro::Output::PDF::abc2image( undef, $self, $a );
359 0         0 return $self->get_image( { %$elt, uri => $res->{src} } );
360             }
361             elsif ( $a->{type} eq "jpg" ) {
362 0         0 $img = $self->{pdf}->image_jpeg(IO::String->new($a->{data}));
363             }
364             elsif ( $a->{type} eq "png" ) {
365 0         0 $img = $self->{pdf}->image_png(IO::String->new($a->{data}));
366             }
367             elsif ( $a->{type} eq "gif" ) {
368 0         0 $img = $self->{pdf}->image_gif(IO::String->new($a->{data}));
369             }
370 0         0 return $img;
371             }
372 0         0 for ( $uri ) {
373 0 0       0 $img = $self->{pdf}->image_png($_) if /\.png$/i;
374 0 0       0 $img = $self->{pdf}->image_jpeg($_) if /\.jpe?g$/i;
375 0 0       0 $img = $self->{pdf}->image_gif($_) if /\.gif$/i;
376             }
377 0         0 return $img;
378             }
379              
380             sub add_image {
381 0     0 0 0 my ( $self, $img, $x, $y, $w, $h, $border ) = @_;
382              
383 0         0 my $gfx = $self->{pdfgfx};
384              
385 0         0 $gfx->save;
386 0         0 $gfx->image( $img, $x, $y-$h, $w, $h );
387 0 0       0 if ( $border ) {
388 0         0 $gfx->rect( $x, $y-$h, $w, $h )
389             ->linewidth($border)
390             ->stroke;
391             }
392 0         0 $gfx->restore;
393             }
394              
395             sub newpage {
396 67     67 0 244 my ( $self, $ps, $page ) = @_;
397             #$self->{pdftext}->textend if $self->{pdftext};
398 67   100     267 $page ||= 0;
399              
400             # PDF::API2 says $page must refer to an existing page.
401             # Set to 0 to append.
402 67 100       353 $page = 0 if $page == $self->{pdf}->pages + 1;
403              
404 67         924 $self->{pdfpage} = $self->{pdf}->page($page);
405             $self->{pdfpage}->mediabox( $ps->{papersize}->[0],
406 67         59256 $ps->{papersize}->[1] );
407              
408 67         11325 $self->{pdfgfx} = $self->{pdfpage}->gfx;
409 67         15626 $self->{pdftext} = $self->{pdfpage}->text;
410 67 50       20735 unless ($ps->{theme}->{background} =~ /^white|none|#ffffff$/i ) {
411 0         0 for ( $self->{pdfgfx} ) {
412 0         0 $_->save;
413 0         0 $_->fillcolor( $ps->{theme}->{background} );
414 0         0 $_->linewidth(0);
415             $_->rectxy( 0, 0, $ps->{papersize}->[0],
416 0         0 $ps->{papersize}->[1] );
417 0         0 $_->fill;
418 0         0 $_->restore;
419             }
420             }
421             }
422              
423             sub openpage {
424 42     42 0 136 my ( $self, $ps, $page ) = @_;
425 42         266 $self->{pdfpage} = $self->{pdf}->openpage($page);
426 42         2130 $self->{pdfgfx} = $self->{pdfpage}->gfx;
427 42         10800 $self->{pdftext} = $self->{pdfpage}->text;
428             }
429              
430             sub importpage {
431 0     0 0 0 my ( $self, $fn, $pg ) = @_;
432 0         0 my $bg = $self->{pdfapi}->open($fn);
433 0 0       0 return unless $bg; # should have been checked
434 0 0       0 $pg = $bg->pages if $pg > $bg->pages;
435 0         0 $self->{pdf}->import_page( $bg, $pg, $self->{pdfpage} );
436             # Make sure the contents get on top of it.
437 0         0 $self->{pdfgfx} = $self->{pdfpage}->gfx;
438 0         0 $self->{pdftext} = $self->{pdfpage}->text;
439             }
440              
441             sub importfile {
442 0     0 0 0 my ( $self, $filename ) = @_;
443 0         0 my $pdf = $self->{pdfapi}->open($filename);
444 0 0       0 return unless $pdf; # should have been checked
445 0         0 for ( my $page = 1; $page <= $pdf->pages; $page++ ) {
446 0         0 $self->{pdf}->import_page( $pdf, $page );
447             }
448 0         0 return { pages => $pdf->pages, $pdf->info_metadata };
449             }
450              
451             sub pagelabel {
452 22     22 0 71 my ( $self, $page, $style, $prefix ) = @_;
453 22   50     66 $style //= 'arabic';
454              
455             # PDF::API2 2.042 has some incompatible changes...
456 22         109 my $c = $self->{pdf}->can("page_labels");
457 22 50       71 if ( $c ) { # 2.042+
458 22 50       183 my $opts = { style => $style eq 'Roman' ? 'R' :
    50          
    100          
    50          
    100          
459             $style eq 'roman' ? 'r' :
460             $style eq 'Alpha' ? 'A' :
461             $style eq 'alpha' ? 'a' : 'D',
462             defined $prefix ? ( prefix => $prefix ) : (),
463             start => 1 };
464 22         124 $c->( $self->{pdf}, $page+1, %$opts );
465             }
466             else {
467 0 0       0 my $opts = { -style => $style,
468             defined $prefix ? ( -prefix => $prefix ) : (),
469             -start => 1 };
470 0         0 $self->{pdf}->pageLabel( $page, $opts );
471             }
472             }
473              
474             sub make_outlines {
475 8     8 0 32 my ( $self, $book, $start ) = @_;
476 8 50 33     64 return unless $book && @$book; # unlikely
477              
478 8         32 my $pdf = $self->{pdf};
479 8         19 $start--; # 1-relative
480 8         19 my $ol_root;
481              
482             # Process outline defs from config.
483 8         18 foreach my $ctl ( @{ $self->{ps}->{outlines} } ) {
  8         41  
484 16         988 my $book = prep_outlines( $book, $ctl );
485 16 50       69 next unless @$book;
486              
487             # Seems not to matter whether we re-use the root or create new.
488 16   66     112 $ol_root //= $pdf->outlines;
489              
490 16         36993 my $outline;
491              
492             # Skip level for a single outline.
493 16 50       38 if ( @{ $self->{ps}->{outlines} } == 1 ) {
  16         88  
494 0         0 $outline = $ol_root;
495 0 0       0 $outline->closed if $ctl->{collapse}; # TODO?
496             }
497             else {
498 16         87 $outline = $ol_root->outline;
499 16         1814 $outline->title( $ctl->{label} );
500 16 50       456 $outline->closed if $ctl->{collapse};
501             }
502              
503 16         197 my %lh; # letter hierarchy
504 16         40 my $needlh = 0;
505 16 50       79 if ( $ctl->{letter} > 0 ) {
506 16         58 for ( @$book ) {
507             # Group on first letter.
508             # That's why we left the sort fields in...
509 48         157 my $cur = uc(substr( $_->[0], 0, 1 ));
510 48   100     183 $lh{$cur} //= [];
511             # Last item is the song.
512 48         82 push( @{$lh{$cur}}, $_->[-1] );
  48         124  
513             }
514             # Need letter hierarchy?
515 16         88 $needlh = keys(%lh) >= $ctl->{letter};
516             }
517              
518 16 50       62 if ( $needlh ) {
519 0         0 my $cur_ol;
520 0         0 my $cur_let = "";
521 0         0 foreach my $let ( sort keys %lh ) {
522 0         0 foreach my $song ( @{$lh{$let}} ) {
  0         0  
523 0 0 0     0 unless ( defined $cur_ol && $cur_let eq $let ) {
524             # Intermediate level autoline.
525 0         0 $cur_ol = $outline->outline;
526 0         0 $cur_ol->title($let);
527 0         0 $cur_let = $let;
528             }
529             # Leaf outline.
530 0         0 my $ol = $cur_ol->outline;
531             # Display info.
532 0         0 $ol->title( demarkup( fmt_subst( $song, $ctl->{line} ) ) );
533 0 0       0 if ( my $c = $ol->can("destination") ) {
534 0         0 $c->( $ol, $pdf->openpage( $song->{meta}->{tocpage} + $start ) );
535             }
536             else {
537 0         0 $ol->dest($pdf->openpage( $song->{meta}->{tocpage} + $start ));
538             }
539             }
540             }
541             }
542             else {
543 16         55 foreach my $b ( @$book ) {
544 48         3409 my $song = $b->[-1];
545             # Leaf outline.
546 48         153 my $ol = $outline->outline;
547             # Display info.
548 48         5210 $ol->title( demarkup( fmt_subst( $song, $ctl->{line} ) ) );
549 48 50       1835 if ( my $c = $ol->can("destination") ) {
550 48         293 $c->( $ol, $pdf->openpage( $song->{meta}->{tocpage} + $start ) );
551             }
552             else {
553 0         0 $ol->dest($pdf->openpage( $song->{meta}->{tocpage} + $start ));
554             }
555             }
556             }
557             }
558             }
559              
560             sub finish {
561 8     8 0 41 my ( $self, $file ) = @_;
562              
563 8 50 33     74 if ( $file && $file ne "-" ) {
564 8         54 $self->{pdf}->saveas($file);
565             }
566             else {
567 0         0 binmode(STDOUT);
568 0         0 print STDOUT ( $self->{pdf}->stringify );
569 0         0 close(STDOUT);
570             }
571             }
572              
573             sub init_fonts {
574 40     40 0 150 my ( $self ) = @_;
575 40         113 my $ps = $self->{ps};
576 40         110 my $fail;
577              
578 40         418 my $fc = Text::Layout::FontConfig->new( debug => $config->{debug}->{fonts} > 1 );
579              
580             # Add font dirs.
581 40         879 my @d = ( @{$ps->{fontdir}}, ::rsc_or_file("fonts/"), $ENV{FONTDIR} );
  40         324  
582             # Avoid rsc result if dummy.
583 40 50       212 splice( @d, -2, 1 ) if $d[-2] eq "fonts/";
584 40         145 for my $fontdir ( @d ) {
585 80 100       1699 next unless $fontdir;
586 40         158 $fontdir = expand_tilde($fontdir);
587 40 50       731 if ( -d $fontdir ) {
588 40         644 $self->{pdfapi}->can("addFontDirs")->($fontdir);
589 40         781 $fc->add_fontdirs($fontdir);
590             }
591             else {
592 0         0 warn("PDF: Ignoring fontdir $fontdir [$!]\n");
593 0         0 undef $fontdir;
594             }
595             }
596              
597             # Make sure we have this one.
598 40         316 $fc->register_font( "ChordProSymbols.ttf", "chordprosymbols", "", {} );
599              
600             # Process the fontconfig.
601 40         4859 foreach my $ff ( keys( %{ $ps->{fontconfig} } ) ) {
  40         244  
602 160         463 my @fam = split( /\s*,\s*/, $ff );
603 160         258 foreach my $s ( keys( %{ $ps->{fontconfig}->{$ff} } ) ) {
  160         509  
604 520         17109 my $v = $ps->{fontconfig}->{$ff}->{$s};
605 520 50       2181 if ( UNIVERSAL::isa( $v, 'HASH' ) ) {
606 0         0 my $file = delete( $v->{file} );
607 0         0 $fc->register_font( $file, $fam[0], $s, $v );
608             }
609             else {
610 520         1259 $fc->register_font( $v, $fam[0], $s );
611             }
612             }
613 160 50       7513 $fc->register_aliases(@fam) if @fam > 1;
614             }
615              
616 40         134 foreach my $ff ( keys( %{ $ps->{fonts} } ) ) {
  40         332  
617 760 50       14717 $self->init_font($ff) or $fail++;
618             }
619              
620 40 50       1145 die("Unhandled fonts detected -- aborted\n") if $fail;
621             }
622              
623             sub init_font {
624 760     760 0 1585 my ( $self, $ff ) = @_;
625 760         1269 my $ps = $self->{ps};
626 760         1131 my $fd;
627 760 100       3321 if ( $ps->{fonts}->{$ff}->{file} ) {
    50          
    50          
628 80         358 $fd = $self->init_filefont($ff);
629             }
630             elsif ( $ps->{fonts}->{$ff}->{description} ) {
631 0         0 $fd = $self->init_pangofont($ff);
632             }
633             elsif ( $ps->{fonts}->{$ff}->{name} ) {
634 680         1548 $fd = $self->init_corefont($ff);
635             }
636 760 50       2337 warn("No font found for \"$ff\"\n") unless $fd;
637 760         17453 $fd;
638             }
639              
640             sub init_pangofont {
641 0     0 0 0 my ( $self, $ff ) = @_;
642              
643 0         0 my $ps = $self->{ps};
644 0         0 my $font = $ps->{fonts}->{$ff};
645              
646 0         0 my $fc = Text::Layout::FontConfig->new( debug => $config->{debug}->{fonts} > 1 );
647 0         0 eval {
648 0         0 $font->{fd} = $fc->from_string($font->{description});
649 0         0 $font->{fd}->get_font($self->{layout}); # force load
650 0 0       0 $font->{fd}->{font}->{Name}->{val} =~ s/~.*/~$faketime/ if $regtest;
651 0         0 $font->{_ff} = $ff;
652 0   0     0 $font->{fd}->set_shaping( $font->{fd}->get_shaping || $font->{shaping}//0);
      0        
653 0 0       0 $font->{size} = $font->{fd}->get_size if $font->{fd}->get_size;
654             };
655 0         0 $font->{fd};
656             }
657              
658             sub init_filefont {
659 80     80 0 214 my ( $self, $ff ) = @_;
660              
661 80         188 my $ps = $self->{ps};
662 80         167 my $font = $ps->{fonts}->{$ff};
663              
664 80         334 my $fc = Text::Layout::FontConfig->new( debug => $config->{debug}->{fonts} > 1 );
665 80         1011 eval {
666 80         382 my $t = $fc->from_filename(expand_tilde($font->{file}));
667 80         6148 $t->get_font($self->{layout}); # force load
668 80 50       929389 $t->{font}->{Name}->{val} =~ s/~.*/~$faketime/ if $regtest;
669 80         217 $t->{_ff} = $ff;
670 80         358 $font->{fd} = $t;
671             };
672 80         287 $font->{fd};
673             }
674              
675             sub init_corefont {
676 680     680 0 1179 my ( $self, $ff ) = @_;
677              
678 680         1146 my $ps = $self->{ps};
679 680         1055 my $font = $ps->{fonts}->{$ff};
680 680         1945 my $cf = ChordPro::Output::PDF::is_corefont($font->{name});
681 680 50       1463 die("Config error: \"$font->{name}\" is not a built-in font\n")
682             unless $cf;
683 680         2447 my $fc = Text::Layout::FontConfig->new( debug => $config->{debug}->{fonts} > 1 );
684 680         8422 eval {
685 680         1680 $font->{fd} = $fc->from_filename($cf);
686 680         44016 $font->{fd}->get_font($self->{layout}); # force load
687 680         1255503 $font->{_ff} = $ff;
688             };
689 680         2080 $font->{fd};
690             }
691              
692             sub show_vpos {
693 0     0 0   my ( $self, $y, $w ) = @_;
694 0           $self->{pdfgfx}->move(100*$w,$y)->linewidth(0.25)->hline(100*(1+$w))->stroke;
695             }
696              
697 10     10   62689 use File::Temp;
  10         32  
  10         4668  
698              
699             my $cname;
700             my $rname;
701             sub embed {
702 0     0 0   my ( $self, $file ) = @_;
703 0 0         return unless -f $file;
704 0           my $a = $self->{pdfpage}->annotation();
705              
706             # The only reliable way currently is pretend it's a movie :) .
707 0           $a->movie($file, "ChordPro" );
708 0           $a->open(1);
709              
710             # Create/reuse temp file for (final) config and run time info.
711 0           my $cf;
712 0 0         if ( $cname ) {
713 0           open( $cf, '>', $cname );
714             }
715             else {
716 0           ( $cf, $cname ) = File::Temp::tempfile( UNLINK => 0);
717             }
718 0           binmode( $cf, ':utf8' );
719 0           print $cf ChordPro::Config::config_final(0);
720 0           close($cf);
721              
722 0           $a = $self->{pdfpage}->annotation();
723 0           $a->movie($cname, "ChordProConfig" );
724 0           $a->open(0);
725              
726 0           my $rf;
727 0 0         if ( $rname ) {
728 0           open( $rf, '>', $rname );
729             }
730             else {
731 0           ( $rf, $rname ) = File::Temp::tempfile( UNLINK => 0);
732             }
733 0           binmode( $rf, ':utf8' );
734 0           open( $rf, '>', $rname );
735 0           binmode( $rf, ':utf8' );
736 0           print $rf (::runtimeinfo());
737 0           close($rf);
738              
739 0           $a = $self->{pdfpage}->annotation();
740 0           $a->movie($rname, "ChordProRunTime" );
741 0           $a->open(0);
742             }
743              
744             END {
745 10 50   10   10744 return unless $cname;
746 0         0 unlink($cname);
747 0         0 undef $cname;
748 0         0 unlink($rname);
749 0         0 undef $rname;
750             }
751              
752             1;