File Coverage

lib/ChordPro/Output/PDF.pm
Criterion Covered Total %
statement 748 1734 43.1
branch 262 908 28.8
condition 94 434 21.6
subroutine 48 83 57.8
pod 0 43 0.0
total 1152 3202 35.9


line stmt bran cond sub pod time code
1             #!/usr/bin/perl
2              
3             package main;
4              
5 10     10   3465 use utf8;
  10         26  
  10         77  
6             our $config;
7             our $options;
8              
9             package ChordPro::Output::PDF;
10              
11 10     10   835 use strict;
  10         29  
  10         328  
12 10     10   107 use warnings;
  10         53  
  10         438  
13 10     10   73 use Encode qw( encode_utf8 );
  10         39  
  10         696  
14 10     10   73 use App::Packager;
  10         25  
  10         95  
15 10     10   9722 use File::Temp ();
  10         196846  
  10         397  
16 10     10   98 use Storable qw(dclone);
  10         24  
  10         618  
17 10     10   611 use List::Util qw(any);
  10         145  
  10         746  
18 10     10   84 use Carp;
  10         40  
  10         615  
19 10     10   85 use feature 'state';
  10         23  
  10         1173  
20              
21             use ChordPro::Output::Common
22 10     10   93 qw( roman prep_outlines fmt_subst );
  10         22  
  10         728  
23              
24 10     10   4434 use ChordPro::Output::PDF::Writer;
  10         43  
  10         434  
25 10     10   82 use ChordPro::Utils;
  10         36  
  10         1378  
26              
27             my $pdfapi;
28              
29 10     10   79 use Text::Layout;
  10         33  
  10         265  
30 10     10   65 use String::Interpolate::Named;
  10         33  
  10         31079  
31              
32             my $verbose = 0;
33              
34             # For regression testing, run perl with PERL_HASH_SEED set to zero.
35             # This eliminates the arbitrary order of font definitions and triggers
36             # us to pinpoint some other data that would otherwise be varying.
37             my $regtest = defined($ENV{PERL_HASH_SEED}) && $ENV{PERL_HASH_SEED} == 0;
38              
39             sub generate_songbook {
40 8     8 0 37 my ( $self, $sb ) = @_;
41              
42 8 50       46 return [] unless $sb->{songs}->[0]->{body}; # no songs
43 8   33     64 $verbose ||= $options->{verbose};
44              
45 8         30 my $ps = $config->{pdf};
46              
47 8         94 my $pr = (__PACKAGE__."::Writer")->new( $ps, $pdfapi );
48 8 50 0     52 warn("Generating PDF ", $options->{output} || "__new__.pdf", "...\n") if $options->{verbose};
49              
50 8         63 my $name = ::runtimeinfo("short");
51 8 50       42 $name =~ s/version.*/regression testing/ if $regtest;
52 8         86 my %info = ( Title => $sb->{songs}->[0]->{meta}->{title}->[0],
53             Creator => $name );
54 8         27 while ( my ( $k, $v ) = each %{ $ps->{info} } ) {
  40         2146  
55 32 100 66     165 next unless defined($v) && $v ne "";
56 8         73 $info{ucfirst($k)} = fmt_subst( $sb->{songs}->[0], $v );
57             }
58 8         99 $pr->info(%info);
59              
60             # The book consists of 4 parts:
61             # 1. The front matter.
62             # 2. The table of contents.
63             # 3. The songs.
64             # 4. The back matter.
65 8         303 my ( %start_of, %pages_of );
66 8         38 for ( qw( front toc songbook back ) ) {
67 32         73 $start_of{$_} = 1;
68 32         65 $pages_of{$_} = 0;
69             }
70              
71             # The songbook...
72 8         29 my @book;
73 8   50     67 my $page = $options->{"start-page-number"} ||= 1;
74              
75 8 50 66     111 if ( $ps->{'even-odd-pages'} && !($page % 2) ) {
76 0         0 warn("Warning: Specifying an even start page when pdf.odd-even-pages is in effect may yield surprising results.\n");
77             }
78              
79 8         33 my $first_song_aligned;
80             my $songindex;
81 8         21 foreach my $song ( @{$sb->{songs}} ) {
  8         36  
82 24         68 $songindex++;
83              
84             # Align.
85 24 100 100     173 if ( $ps->{'pagealign-songs'} && !($page % 2) ) {
86 10         71 $pr->newpage($ps, $page);
87 10         31 $page++;
88 10   50     40 $first_song_aligned //= 1;
89             }
90 24   100     101 $first_song_aligned //= 0;
91              
92 24         105 $song->{meta}->{tocpage} = $page;
93 24         132 push( @book, [ $song->{meta}->{title}->[0], $song ] );
94              
95             # Copy persistent assets into each of the songs.
96 24 50       141 if ( $sb->{assets} ) {
97 0   0     0 $song->{assets} //= {};
98 0         0 while ( my ($k,$v) = each %{$sb->{assets}} ) {
  0         0  
99 0         0 $song->{assets}->{$k} = $v;
100             }
101             }
102              
103             $page += $song->{meta}->{pages} =
104             generate_song( $song, { pr => $pr,
105             startpage => $page,
106             songindex => $songindex,
107 24         91 numsongs => scalar(@{$sb->{songs}}),
  24         203  
108             } );
109             # Easy access to toc page.
110 24         180 $song->{meta}->{page} = $song->{meta}->{tocpage};
111             }
112 8         47 $pages_of{songbook} = $page - 1;
113 8         28 $start_of{back} = $page;
114              
115             $::config->{contents} //=
116             [ { $::config->{toc}->{order} eq "alpha"
117             ? ( fields => [ "title" ] )
118             : ( fields => [ "songindex" ] ),
119             label => $::config->{toc}->{title},
120 8 0 50     56 line => $::config->{toc}->{line} } ];
121              
122 8         34 foreach my $ctl ( reverse( @{ $::config->{contents} } ) ) {
  8         40  
123 24 50 33     256 next unless $options->{toc} // @book > 1;
124              
125 24         78 for ( qw( fields label line pageno ) ) {
126 96 50       266 next if exists $ctl->{$_};
127 0         0 die("Config error: \"contents\" is missing \"$_\"\n");
128             }
129 24 100       125 next if $ctl->{omit};
130              
131 16         208 my $book = prep_outlines( [ map { $_->[1] } @book ], $ctl );
  48         220  
132              
133             # Create a pseudo-song for the table of contents.
134 16         102 my $t = fmt_subst( $book[0][-1], $ctl->{label} );
135 16         1370 my $l = $ctl->{line};
136 16         84 my $start = $start_of{songbook} - $options->{"start-page-number"};
137 16         42 my $pgtpl = $ctl->{pageno};
138             my $song =
139             { title => $t,
140             meta => { title => [ $t ] },
141             structure => "linear",
142             body => [
143 16         93 map { +{ type => "tocline",
144             context => "toc",
145             title => fmt_subst( $_->[-1], $l ),
146 48         6261 page => $pr->{pdf}->openpage($_->[-1]->{meta}->{tocpage}+$start),
147             pageno => fmt_subst( $_->[-1], $pgtpl ),
148             } } @$book,
149             ],
150             };
151              
152             # Prepend the toc.
153 16         3304 $page = generate_song( $song,
154             { pr => $pr, prepend => 1, roman => 1,
155             startpage => 1,
156             songindex => 1, numsongs => 1,
157             } );
158 16         109 $pages_of{toc} += $page;
159             #### TODO: This is not correct if there are more TOCs.
160 16 50       68 $pages_of{toc}++ if $first_song_aligned;
161              
162             # Align.
163 16 50 66     168 if ( $ps->{'even-odd-pages'} && $page % 2 && !$first_song_aligned ) {
      66        
164 12         87 $pr->newpage($ps, $page+1);
165 12         52 $page++;
166             }
167 16         57 $start_of{songbook} += $page;
168 16         230 $start_of{back} += $page;
169             }
170              
171 8 100       53 if ( $ps->{'front-matter'} ) {
172 3         10 $page = 1;
173 3         19 my $matter = $pdfapi->open( expand_tilde($ps->{'front-matter'}) );
174 3 50       25828 die("Missing front matter: ", $ps->{'front-matter'}, "\n") unless $matter;
175 3         17 for ( 1 .. $matter->pages ) {
176 3         52 $pr->{pdf}->import_page( $matter, $_, $_ );
177 3         25024 $page++;
178             }
179 3         17 $pages_of{front} = $matter->pages;
180              
181             # Align to ODD page. Frontmatter starts on a right page but
182             # songs on a left page.
183             $pr->newpage( $ps, 1+$matter->pages ), $page++
184 3 50 33     69 if $ps->{'even-odd-pages'} && !($page % 2);
185              
186 3         13 $start_of{toc} += $page - 1;
187 3         15 $start_of{songbook} += $page - 1;
188 3         270 $start_of{back} += $page - 1;
189             }
190              
191 8 100       56 if ( $ps->{'back-matter'} ) {
192 3         23 my $matter = $pdfapi->open( expand_tilde($ps->{'back-matter'}) );
193 3 50       21654 die("Missing back matter: ", $ps->{'back-matter'}, "\n") unless $matter;
194 3         12 $page = $start_of{back};
195             $pr->newpage($ps), $page++, $start_of{back}++
196 3 50 33     26 if $ps->{'even-odd-pages'} && ($page % 2);
197 3         18 for ( 1 .. $matter->pages ) {
198 3         45 $pr->{pdf}->import_page( $matter, $_, $page );
199 3         22218 $page++;
200             }
201 3         18 $pages_of{back} = $matter->pages;
202             }
203             # warn ::dump(\%start_of) =~ s/\s+/ /gsr, "\n";
204             # warn ::dump(\%pages_of) =~ s/\s+/ /gsr, "\n";
205              
206             # Note that the page indices run from zero.
207             $pr->pagelabel( $start_of{front}-1, 'arabic', 'front-' )
208 8 100       291 if $pages_of{front};
209             $pr->pagelabel( $start_of{toc}-1, 'roman' )
210 8 50       589 if $pages_of{toc};
211             $pr->pagelabel( $start_of{songbook}-1, 'arabic' )
212 8 50       1013 if $pages_of{songbook};
213             $pr->pagelabel( $start_of{back}-1, 'arabic', 'back-' )
214 8 100       783 if $pages_of{back};
215              
216             # Add the outlines.
217 8         329 $pr->make_outlines( [ map { $_->[1] } @book ], $start_of{songbook} );
  24         118  
218              
219 8   50     1018 $pr->finish( $options->{output} || "__new__.pdf" );
220 8 50       679024 warn("Generated PDF...\n") if $options->{verbose};
221              
222             generate_csv( \@book, $page, \%pages_of, \%start_of )
223 8 50       107 if $options->{csv};
224              
225 8 50       43 _dump($ps) if $verbose;
226              
227 8         144 []
228             }
229              
230             sub generate_csv {
231 8     8 0 35 my ( $book, $page, $pages_of, $start_of ) = @_;
232              
233             # Create an MSPro compatible CSV for this PDF.
234 8         65 push( @$book, [ "CSV", { meta => { tocpage => $page } } ] );
235 8         96 ( my $csv = $options->{output} ) =~ s/\.pdf$/.csv/i;
236 8 50       125 open( my $fd, '>:utf8', encode_utf8($csv) )
237             or die( encode_utf8($csv), ": $!\n" );
238              
239             warn("Generating CSV ", encode_utf8($csv), "...\n")
240 8 50 33     1290 if $config->{debug}->{csv} || $options->{verbose};
241              
242 8         38 my $ps = $config->{pdf};
243 8         29 my $ctl = $ps->{csv};
244 8   50     50 my $sep = $ctl->{separator} // ";";
245 8   50     75 my $vsep = $ctl->{vseparator} // "|";
246              
247             my $rfc4180 = sub {
248 328     328   46867 my ( $v ) = @_;
249 328 50       1165 $v = [$v] unless ref($v) eq 'ARRAY';
250 328 50 33     1442 return "" unless defined($v) && defined($v->[0]);
251 328         839 $v = join( $sep, @$v );
252 328 50       4558 return $v unless $v =~ m/[$sep"\n\r]/s;
253 0         0 $v =~ s/"/""/g;
254 0         0 return '"' . $v . '"';
255 8         75 };
256              
257             my $pagerange = sub {
258 33     33   90 my ( $pages, $page ) = @_;
259 33 100       100 if ( @_ == 1 ) {
260 9         27 $pages = $pages_of->{$_[0]};
261 9         23 $page = $start_of->{$_[0]};
262             }
263 33 100       349 $pages > 1
264             ? ( $page ."-". ($page+$pages-1) )
265             : $page,
266 8         54 };
267              
268             my $csvline = sub {
269 33     33   71 my ( $m ) = @_;
270 33         74 my @cols = ();
271 33         56 for ( @{ $ctl->{fields} } ) {
  33         96  
272 297 100       911 next if $_->{omit};
273 264   33     1137 my $v = $_->{value} // '%{'.$_->{meta}.'}';
274 264         794 local( $config->{metadata}->{separator} ) = $vsep;
275 264         1063 push( @cols, $rfc4180->( fmt_subst( { meta => $m }, $v ) ) );
276             }
277 33         495 print $fd ( join( $sep, @cols ), "\n" );
278 33         223 scalar(@cols);
279 8         50 };
280              
281 8         30 my @cols;
282             my $ncols;
283 8         25 for ( @{ $ctl->{fields} } ) {
  8         51  
284 72 100       279 next if $_->{omit};
285 64         146 push( @cols, $rfc4180->($_->{name}) );
286             }
287 8         111 $ncols = @cols;
288             #warn( "CSV: $ncols fields\n" );
289 8         295 print $fd ( join( $sep, @cols ), "\n" );
290              
291 8 100       51 unless ( $ctl->{songsonly} ) {
292             $csvline->( { title => '__front_matter__',
293             pagerange => $pagerange->("front"),
294             sorttitle => 'Front Matter',
295             artist => 'ChordPro' } )
296 3 50       28 if $pages_of->{front};
297             $csvline->( { title => '__table_of_contents__',
298             pagerange => $pagerange->("toc"),
299             sorttitle => 'Table of Contents',
300             artist => 'ChordPro' } )
301 3 50       31 if $pages_of->{toc};
302             }
303              
304             warn( "CSV: ", scalar(@$book), " songs in book\n")
305 8 50       98 if $config->{debug}->{csv};
306 8         70 for ( my $p = 0; $p < @$book-1; $p++ ) {
307 24         49 my ( $title, $song ) = @{$book->[$p]};
  24         98  
308             my $page = $start_of->{songbook} + $song->{meta}->{tocpage}
309 24   50     157 - ($options->{"start-page-number"} || 1);
310 24         76 my $pp = $song->{meta}->{pages};
311 24         44 my $m = { %{$song->{meta}},
  24         189  
312             pagerange => [ $pagerange->($pp, $page) ] };
313 24         91 $csvline->($m);
314             }
315              
316 8 100       65 unless ( $ctl->{songsonly} ) {
317             $csvline->( { title => '__back_matter__',
318             pagerange => $pagerange->("back"),
319             sorttitle => 'Back Matter',
320             artist => 'ChordPro'} )
321 3 50       23 if $pages_of->{back};
322             }
323 8         599 close($fd);
324             warn("Generated CSV...\n")
325 8 50 33     336 if $config->{debug}->{csv} || $options->{verbose};
326             }
327              
328             my $source; # song source
329             my $structured = 0; # structured data
330             my $suppress_empty_chordsline = 0; # suppress chords line when empty
331             my $suppress_empty_lyricsline = 0; # suppress lyrics line when blank
332             my $lyrics_only = 0; # suppress all chord lines
333             my $inlinechords = 0; # chords inline
334             my $inlineannots; # format for inline annots
335             my $chordsunder = 0; # chords under the lyrics
336             my $chordscol = 0; # chords in a separate column
337             my $chordscapo = 0; # capo in a separate column
338             my $i_tag;
339             our $assets;
340              
341 10         174176 use constant SIZE_ITEMS => [ qw( chord text chorus tab grid diagram
342 10     10   114 toc title footer ) ];
  10         457  
343              
344             sub generate_song {
345 40     40 0 154 my ( $s, $opts ) = @_;
346              
347 40         108 my $pr = $opts->{pr};
348              
349 40 50       172 unless ( $s->{body} ) { # empty song, or embedded
350 0 0       0 return unless $s->{source}->{embedding};
351 0 0       0 return unless $s->{source}->{embedding} eq "pdf";
352 0         0 my $p = $pr->importfile($s->{source}->{file});
353 0         0 $s->{meta}->{pages} = $p->{pages};
354              
355             # Copy the title of the embedded document, provided there
356             # was no override.
357 0 0 0     0 if ( $s->{meta}->{title}->[0] eq $s->{source}->{file}
358             and $p->{Title} ) {
359 0         0 $s->{meta}->{title} = [ $s->{title} = $p->{Title} ];
360             }
361 0         0 return $s->{meta}->{pages};
362             }
363              
364 40   66     67431 local $config = dclone( $s->{config} // $config );
365              
366 40         267 $source = $s->{source};
367 40   50     302 $assets = $s->{assets} || {};
368              
369 40         153 $suppress_empty_chordsline = $::config->{settings}->{'suppress-empty-chords'};
370 40         177 $suppress_empty_lyricsline = $::config->{settings}->{'suppress-empty-lyrics'};
371 40         131 $inlinechords = $::config->{settings}->{'inline-chords'};
372 40         120 $inlineannots = $::config->{settings}->{'inline-annotations'};
373 40         97 $chordsunder = $::config->{settings}->{'chords-under'};
374 40         311 my $ps = $::config->clone->{pdf};
375 40         4950 $ps->{pr} = $pr;
376 40         152 $pr->{ps} = $ps;
377 40         140 $ps->{_s} = $s;
378 40         427 $pr->{_df} = {};
379             # warn("X1: ", $ps->{fonts}->{$_}->{size}, "\n") for "text";
380 40         295 $pr->init_fonts();
381 40         131 my $fonts = $ps->{fonts};
382 40         160 $pr->{_df}->{$_} = { %{$fonts->{$_}} } for qw( text chorus chord grid toc tab );
  240         1230  
383             # warn("X2: ", $pr->{_df}->{$_}->{size}, "\n") for "text";
384              
385 40   50     386 $structured = ( $options->{'backend-option'}->{structure} // '' ) eq 'structured';
386 40 50       169 $s->structurize if $structured;
387              
388             # Diagrams drawer.
389 40         109 my $dd;
390             my $dctl;
391 40 50       183 if ( $::config->{instrument}->{type} eq "keyboard" ) {
392 0         0 require ChordPro::Output::PDF::KeyboardDiagrams;
393 0         0 $dd = ChordPro::Output::PDF::KeyboardDiagrams->new($ps);
394 0         0 $dctl = $ps->{kbdiagrams};
395             }
396             else {
397 40         4853 require ChordPro::Output::PDF::StringDiagrams;
398 40         334 $dd = ChordPro::Output::PDF::StringDiagrams->new($ps);
399 40         121 $dctl = $ps->{diagrams};
400             }
401             $dctl->{show} = $s->{settings}->{diagrampos}
402 40 50       189 if defined $s->{settings}->{diagrampos};
403 40         135 $ps->{dd} = $dd;
404 40         113 my $sb = $s->{body};
405              
406             # set_columns needs these, set provisional values.
407 40         131 $ps->{_leftmargin} = $ps->{marginleft};
408 40         124 $ps->{_rightmargin} = $ps->{marginright};
409             set_columns( $ps,
410 40   33     393 $s->{settings}->{columns} || $::config->{settings}->{columns} );
411              
412 40         109 $chordscol = $ps->{chordscolumn};
413 40         125 $lyrics_only = $::config->{settings}->{'lyrics-only'};
414 40         127 $chordscapo = $s->{meta}->{capo};
415              
416 40         88 my $fail;
417 40         105 for my $item ( @{ SIZE_ITEMS() } ) {
  40         135  
418 360         994 for ( $options->{"$item-font"} ) {
419 360 50       701 next unless $_;
420 0         0 delete( $fonts->{$item}->{file} );
421 0         0 delete( $fonts->{$item}->{name} );
422 0         0 delete( $fonts->{$item}->{description} );
423 0 0       0 if ( m;/; ) {
    0          
424 0         0 $fonts->{$item}->{file} = $_;
425             }
426             elsif ( is_corefont($_) ) {
427 0         0 $fonts->{$item}->{name} = is_corefont($_);
428             }
429             else {
430 0         0 $fonts->{$item}->{description} = $_;
431             }
432 0 0       0 $pr->init_font($item) or $fail++;
433             }
434 360         786 for ( $options->{"$item-size"} ) {
435 360 50       749 next unless $_;
436 0         0 $fonts->{$item}->{size} = $_;
437             }
438             }
439 40 50       171 die("Unhandled fonts detected -- aborted\n") if $fail;
440              
441 40 50       267 if ( $ps->{labels}->{comment} ) {
    50          
442 0         0 $ps->{_indent} = 0;
443             }
444             elsif ( $ps->{labels}->{width} eq "auto" ) {
445 40 50 33     196 if ( $s->{labels} && @{ $s->{labels} } ) {
  0         0  
446 0         0 my $longest = 0;
447 0   0     0 my $ftext = $fonts->{label} || $fonts->{text};
448 0         0 my $w = $pr->strwidth(" ", $ftext);
449 0         0 for ( @{ $s->{labels} } ) {
  0         0  
450 0         0 for ( split( /\\n/, $_ ) ) {
451 0         0 my $t = $pr->strwidth( $_, $ftext ) + $w;
452 0 0       0 $longest = $t if $t > $longest;
453             }
454             }
455 0         0 $ps->{_indent} = $longest;
456             }
457             else {
458 40         118 $ps->{_indent} = 0;
459             }
460             }
461             else {
462 0         0 $ps->{_indent} = $ps->{labels}->{width};
463             }
464              
465             my $set_sizes = sub {
466 40     40   128 $ps->{lineheight} = $fonts->{text}->{size} - 1; # chordii
467 40         131 $ps->{chordheight} = $fonts->{chord}->{size};
468 40         247 };
469 40         138 $set_sizes->();
470 40         133 $ps->{'vertical-space'} = $options->{'vertical-space'};
471 40         92 for ( @{ SIZE_ITEMS() } ) {
  40         118  
472 360         785 $fonts->{$_}->{_size} = $fonts->{$_}->{size};
473             }
474              
475 40         149 my $x;
476 40         159 my $y = $ps->{papersize}->[1] - $ps->{margintop};
477              
478 40 50       162 $ps->{'even-odd-pages'} = 1 if $options->{'even-pages-number-left'};
479 40 50       163 $ps->{'even-odd-pages'} = -1 if $options->{'odd-pages-number-left'};
480              
481 40   33     1070 my $st = $s->{settings}->{titles} || $::config->{settings}->{titles};
482 40 50 33     349 if ( defined($st)
483             && ! $ps->{'titles-directive-ignore'} ) {
484             my $swap = sub {
485 0     0   0 my ( $from, $to ) = @_;
486 0         0 for my $class ( qw( default title first ) ) {
487 0         0 for ( qw( title subtitle footer ) ) {
488 0 0       0 next unless defined $ps->{formats}->{$class}->{$_};
489 0 0       0 unless ( ref($ps->{formats}->{$class}->{$_}) eq 'ARRAY' ) {
490 0         0 warn("Oops -- pdf.formats.$class.$_ is not an array\n");
491 0         0 next;
492             }
493 0 0       0 unless ( ref($ps->{formats}->{$class}->{$_}->[0]) eq 'ARRAY' ) {
494             $ps->{formats}->{$class}->{$_} =
495 0         0 [ $ps->{formats}->{$class}->{$_} ];
496             }
497 0         0 for my $l ( @{$ps->{formats}->{$class}->{$_}} ) {
  0         0  
498 0         0 ( $l->[$from], $l->[$to] ) =
499             ( $l->[$to], $l->[$from] );
500             }
501             }
502             }
503 40         1524 };
504              
505 40 50       229 if ( $st eq "left" ) {
506 0         0 $swap->(0,1);
507             }
508 40 50       635 if ( $st eq "right" ) {
509 0         0 $swap->(2,1);
510             }
511             }
512              
513             my $do_size = sub {
514 0     0   0 my ( $tag, $value ) = @_;
515 0 0       0 if ( $value =~ /^(.+)\%$/ ) {
516             $fonts->{$tag}->{_size} //=
517 0   0     0 $::config->{pdf}->{fonts}->{$tag}->{size};
518             $fonts->{$tag}->{size} =
519 0         0 ( $1 / 100 ) * $fonts->{$tag}->{_size};
520             }
521             else {
522             $fonts->{$tag}->{size} =
523 0         0 $fonts->{$tag}->{_size} = $value;
524             }
525 0         0 $set_sizes->();
526 40         183 };
527              
528 40         89 my $col;
529              
530             my $col_adjust = sub {
531 42 50   42   141 if ( $ps->{columns} <= 1 ) {
532             warn("L=", $ps->{__leftmargin},
533             ", R=", $ps->{__rightmargin},
534 42 50       146 "\n") if $config->{debug}->{spacing};
535 42         103 return;
536             }
537 0         0 $x = $ps->{_leftmargin} + $ps->{columnoffsets}->[$col];
538 0         0 $ps->{__leftmargin} = $x;
539             $ps->{__rightmargin} =
540             $ps->{_leftmargin}
541 0         0 + $ps->{columnoffsets}->[$col+1];
542             $ps->{__rightmargin} -= $ps->{columnspace}
543 0 0       0 if $col < $ps->{columns}-1;
544             warn("C=$col, L=", $ps->{__leftmargin},
545             ", R=", $ps->{__rightmargin},
546 0 0       0 "\n") if $config->{debug}->{spacing};
547 0         0 $y = $ps->{_top};
548 0         0 $x += $ps->{_indent};
549 40         163 };
550              
551 40         85 my $vsp_ignorefirst;
552 40   50     139 my $startpage = $opts->{startpage} || 1;
553 40         94 my $thispage = $startpage - 1;
554 40         80 my $spreadimage;
555              
556             # Physical newpage handler.
557             my $newpage = sub {
558              
559             # Add page to the PDF.
560 42 100   42   347 $pr->newpage($ps, $opts->{prepend} ? $thispage+1 : () );
561              
562             # Put titles and footer.
563              
564             # If even/odd pages, leftpage signals whether the
565             # header/footer parts must be swapped.
566 42         118 my $rightpage = 1;
567 42 100       194 if ( $ps->{"even-odd-pages"} ) {
568             # Even/odd printing...
569 32         111 $rightpage = $thispage % 2 == 0;
570             # Odd/even printing...
571 32 100       134 $rightpage = !$rightpage if $ps->{'even-odd-pages'} < 0;
572             }
573              
574             # margin* are offsets from the edges of the paper.
575             # _*margin are offsets taking even/odd pages into account.
576             # _margin* are physical coordinates, taking ...
577 42 100       136 if ( $rightpage ) {
578 34         91 $ps->{_leftmargin} = $ps->{marginleft};
579 34         89 $ps->{_marginleft} = $ps->{marginleft};
580 34         74 $ps->{_rightmargin} = $ps->{marginright};
581 34         129 $ps->{_marginright} = $ps->{papersize}->[0] - $ps->{marginright};
582             }
583             else {
584 8         20 $ps->{_leftmargin} = $ps->{marginright};
585 8         19 $ps->{_marginleft} = $ps->{marginright};
586 8         18 $ps->{_rightmargin} = $ps->{marginleft};
587 8         25 $ps->{_marginright} = $ps->{papersize}->[0] - $ps->{marginleft};
588             }
589 42         125 $ps->{_marginbottom} = $ps->{marginbottom};
590 42         117 $ps->{_margintop} = $ps->{papersize}->[1] - $ps->{margintop};
591 42         104 $ps->{_bottommargin} = $ps->{marginbottom};
592              
593             # Physical coordinates; will be adjusted to columns if needed.
594 42         103 $ps->{__leftmargin} = $ps->{_marginleft};
595 42         102 $ps->{__rightmargin} = $ps->{_marginright};
596 42         114 $ps->{__topmargin} = $ps->{_margintop};
597 42         104 $ps->{__bottommargin} = $ps->{_marginbottom};
598              
599 42         113 $thispage++;
600             $s->{meta}->{page} = [ $s->{page} = $opts->{roman}
601 42 100       254 ? roman($thispage) : $thispage ];
602              
603             # Determine page class and background.
604 42         83 my $class = 2; # default
605 42         136 my $bgpdf = $ps->{formats}->{default}->{background};
606 42 100       192 if ( $thispage == 1 ) {
    100          
607 24         47 $class = 0; # very first page
608             $bgpdf = $ps->{formats}->{first}->{background}
609             || $ps->{formats}->{title}->{background}
610 24   33     204 || $bgpdf;
611             }
612             elsif ( $thispage == $startpage ) {
613 16         37 $class = 1; # first of a song
614             $bgpdf = $ps->{formats}->{title}->{background}
615 16   33     94 || $bgpdf;
616             }
617 42 50       148 if ( $bgpdf ) {
618 0         0 my ( $fn, $pg ) = ( $bgpdf, 1 );
619 0 0       0 if ( $bgpdf =~ /^(.+):(\d+)$/ ) {
620 0         0 ( $bgpdf, $pg ) = ( $1, $2 );
621             }
622 0         0 $fn = ::rsc_or_file($bgpdf);
623 0 0       0 if ( -s -r $fn ) {
624 0 0 0     0 $pg++ if $ps->{"even-odd-pages"} && !$rightpage;
625 0         0 $pr->importpage( $fn, $pg );
626             }
627             else {
628 0         0 warn( "PDF: Missing or empty background document: ",
629             $bgpdf, "\n" );
630             }
631             }
632              
633 42         84 $x = $ps->{__leftmargin};
634 42         95 $x += $ps->{_indent};
635 42         86 $y = $ps->{_margintop};
636 42 50 33     201 $y += $ps->{headspace} if $ps->{'head-first-only'} && $class == 2;
637              
638 42 50       609 if ( $spreadimage ) {
639 0         0 $y -= imagespread( $spreadimage, $x, $y, $ps );
640 0         0 undef $spreadimage;
641             }
642 42         120 $ps->{_top} = $y;
643 42         117 $col = 0;
644 42         84 $vsp_ignorefirst = 1;
645 42         109 $col_adjust->();
646 40         447 };
647              
648             my $checkspace = sub {
649              
650             # Verify that the amount of space if still available.
651             # If not, perform a column break or page break.
652             # Use negative argument to force a break.
653             # Returns true if there was space.
654              
655 144     144   279 my $vsp = $_[0];
656 144 50 33     798 return 1 if $vsp >= 0 && $y - $vsp >= $ps->{_bottommargin};
657              
658 0 0       0 if ( ++$col >= $ps->{columns}) {
659 0         0 $newpage->();
660 0         0 $vsp_ignorefirst = 0;
661             }
662 0         0 $col_adjust->();
663 0         0 return;
664 40         156 };
665              
666             my $chorddiagrams = sub {
667 40     40   94 my ( $chords, $show, $ldisp ) = @_;
668 40 50 33     313 return if $lyrics_only || !$dctl->{show};
669 40         511 my @chords;
670             $chords = $s->{chords}->{chords}
671 40 100 66     287 if !defined($chords) && $s->{chords};
672 40   33     206 $show //= $dctl->{show};
673 40 100       116 if ( $chords ) {
674 24         73 for ( @$chords ) {
675 48 50       190 if ( my $i = $s->{chordsinfo}->{$_} ) {
676 48 50       198 push( @chords, $i ) if $i->has_diagram;
677             }
678             else {
679 0         0 warn("PDF: Missing chord info for \"$_\"\n");
680             }
681             }
682             }
683 40 100       160 return unless @chords;
684              
685             # Determine page class.
686 24         49 my $class = 2; # default
687 24 100       108 if ( $thispage == 1 ) {
    50          
688 8         28 $class = 0; # very first page
689             }
690             elsif ( $thispage == $startpage ) {
691 16         33 $class = 1; # first of a song
692             }
693              
694             # If chord diagrams are to be printed in the right column, put
695             # them on the first page.
696 24 50 33     290 if ( $show eq "right" && $class <= 1 ) {
    50 33        
    50 33        
    0 33        
697 0         0 my $vsp = $dd->vsp( undef, $ps );
698              
699 0         0 my $v = int( ( $ps->{_margintop} - $ps->{marginbottom} ) / $vsp );
700 0         0 my $c = int( ( @chords - 1) / $v ) + 1;
701             # warn("XXX ", scalar(@chords), ", $c colums of $v max\n");
702             my $column =
703             $ps->{_marginright} - $ps->{_marginleft}
704 0         0 - ($c-1) * $dd->hsp(undef,$ps)
705             - $dd->hsp0(undef,$ps);
706              
707 0         0 my $hsp = $dd->hsp(undef,$ps);
708 0         0 my $x = $x + $column - $ps->{_indent};
709 0         0 $ps->{_rightmargin} = $ps->{papersize}->[0] - $x + $ps->{columnspace};
710 0         0 $ps->{__rightmargin} = $x - $ps->{columnspace};
711             set_columns( $ps,
712 0   0     0 $s->{settings}->{columns} || $::config->{settings}->{columns} );
713 0         0 $col_adjust->();
714 0         0 my $y = $y;
715 0         0 while ( @chords ) {
716              
717 0         0 for ( 0..$c-1 ) {
718 0 0       0 last unless @chords;
719 0         0 $dd->draw( shift(@chords), $x + $_*$hsp, $y, $ps );
720             }
721              
722 0         0 $y -= $vsp;
723             }
724             }
725             elsif ( $show eq "top" && $class <= 1 ) {
726              
727 0         0 my $ww = ( $ps->{_marginright} - $ps->{_marginleft} );
728              
729             # Number of diagrams, based on minimal required interspace.
730 0         0 my $h = int( ( $ww
731             # Add one interspace (cuts off right)
732             + $dd->hsp1(undef,$ps) )
733             / $dd->hsp(undef,$ps) );
734 0 0       0 die("ASSERT: $h should be greater than 0") unless $h > 0;
735              
736 0         0 my $hsp = $dd->hsp(undef,$ps);
737 0         0 my $vsp = $dd->vsp( undef, $ps );
738 0         0 while ( @chords ) {
739 0         0 my $x = $x - $ps->{_indent};
740              
741 0         0 for ( 0..$h-1 ) {
742 0 0       0 last unless @chords;
743 0         0 $dd->draw( shift(@chords), $x + $_*$hsp, $y, $ps );
744             }
745              
746 0         0 $y -= $vsp;
747             }
748 0         0 $ps->{_top} = $y;
749             }
750             elsif ( $show eq "bottom" && $class <= 1 && $col == 0 ) {
751              
752 24         77 my $ww = ( $ps->{_marginright} - $ps->{_marginleft} );
753              
754             # Number of diagrams, based on minimal required interspace.
755 24         115 my $h = int( ( $ww
756             # Add one interspace (cuts off right)
757             + $dd->hsp1(undef,$ps) )
758             / $dd->hsp(undef,$ps) );
759 24 50       106 die("ASSERT: $h should be greater than 0") unless $h > 0;
760              
761 24         114 my $vsp = $dd->vsp( undef, $ps );
762 24         90 my $hsp = $dd->hsp( undef, $ps );
763              
764 24         140 my $y = $ps->{marginbottom} + (int((@chords-1)/$h) + 1) * $vsp;
765 24         66 $ps->{_bottommargin} = $y;
766              
767 24         72 $y -= $dd->vsp1( undef, $ps ); # advance height
768              
769 24         104 while ( @chords ) {
770 24         67 my $x = $x - $ps->{_indent};
771 24         94 $checkspace->($vsp);
772 24 50       99 $pr->show_vpos( $y, 0 ) if $config->{debug}->{spacing};
773              
774 24         71 for ( 1..$h ) {
775 72 100       214 last unless @chords;
776 48         219 $dd->draw( shift(@chords), $x, $y, $ps );
777 48         139 $x += $hsp;
778             }
779              
780 24         80 $y -= $vsp;
781 24 50       207 $pr->show_vpos( $y, 1 ) if $config->{debug}->{spacing};
782             }
783             }
784             elsif ( $show eq "below" ) {
785              
786 0         0 my $vsp = $dd->vsp( undef, $ps );
787 0         0 my $hsp = $dd->hsp( undef, $ps );
788             my $h = int( ( $ps->{__rightmargin}
789             - $ps->{__leftmargin}
790 0         0 + $dd->hsp1( undef, $ps ) ) / $hsp );
791 0         0 while ( @chords ) {
792 0         0 $checkspace->($vsp);
793 0         0 my $x = $x - $ps->{_indent};
794 0 0       0 $pr->show_vpos( $y, 0 ) if $config->{debug}->{spacing};
795              
796 0         0 for ( 1..$h ) {
797 0 0       0 last unless @chords;
798 0         0 $dd->draw( shift(@chords), $x, $y, $ps );
799 0         0 $x += $hsp;
800             }
801              
802 0         0 $y -= $vsp;
803 0 0       0 $pr->show_vpos( $y, 1 ) if $config->{debug}->{spacing};
804             }
805             }
806 40         1468 };
807              
808 40         136 my @elts;
809             my $elt; # current element
810 40         91 my @sb = @{$sb};
  40         163  
811 40         108 my $redo = [];
812 40         153 while ( @sb ) {
813 160 50       390 $elt = @$redo ? shift(@$redo) : shift(@sb);
814 160 50 33     865 if ( $elt->{type} eq "image"
    50 33        
      33        
815             && $elt->{opts}->{spread} ) {
816 0 0       0 if ( $spreadimage ) {
817 0         0 warn("Ignoring superfluous spread image\n");
818             }
819             else {
820 0 0       0 warn("Got spread image\n") if $config->{debug}->{images};
821 0   0     0 $spreadimage //= $elt;
822 0         0 next;
823             }
824             }
825             elsif ( $elt->{type} eq "delegate"
826             && $elt->{subtype} eq "image"
827             && $elt->{data}->[0] =~ /\bspread=\d+\b$/
828             ) {
829 0 0       0 if ( $spreadimage ) {
830 0         0 warn("Ignoring superfluous spread delegate\n");
831             }
832             else {
833 0         0 my $delegate = $elt->{delegate};
834 0 0       0 warn("Got spread delegate $delegate\n") if $config->{debug}->{images};
835 0         0 my $pkg = __PACKAGE__;
836 0         0 $pkg =~ s/::Output::\w+$/::Delegate::$delegate/;
837 0 0       0 eval "require $pkg" || die($@);
838 0   0     0 my $hd = $pkg->can($elt->{handler}) //
839             die("PDF: Missing delegate handler ${pkg}::$elt->{handler}\n");
840              
841             my $pw = $ps->{papersize}->[0]
842             - $ps->{marginleft}
843 0         0 - $ps->{marginright};
844 0         0 $redo = $hd->( $s, $pw, $elt );
845 0         0 next;
846             }
847             }
848 160         406 push( @elts, $elt );
849             }
850              
851             # Get going.
852 40         165 $newpage->();
853              
854             # Embed source and config for debugging;
855 40 50 66     291 $pr->embed($source->{file}) if $source->{file} && $options->{debug};
856              
857 40         604 my $prev; # previous element
858              
859             my $grid_cellwidth;
860 40         159 my $grid_barwidth = 0.5 * $fonts->{chord}->{size};
861 40         398 my $grid_margin;
862 40         96 my $did = 0;
863 40         433 my $curctx = "";
864              
865 40         140 while ( @elts ) {
866 160         372 $elt = shift(@elts);
867              
868 160 50       521 if ( $elt->{type} eq "newpage" ) {
869 0         0 $newpage->();
870 0         0 next;
871             }
872              
873 160 50       384 if ( $elt->{type} eq "colb" ) {
874 0         0 $checkspace->(-1);
875 0         0 next;
876             }
877              
878 160 100 66     712 if ( $elt->{type} ne "set" && !$did++ ) {
879             # Insert top/left/right/bottom chord diagrams.
880 40 50       235 $chorddiagrams->() unless $dctl->{show} eq "below";
881 40 50 33     149 showlayout($ps) if $ps->{showlayout} || $config->{debug}->{spacing};
882             }
883              
884 160 100       900 if ( $elt->{type} eq "empty" ) {
885 40         99 my $y0 = $y;
886             warn("***SHOULD NOT HAPPEN1***")
887 40 50       166 if $s->{structure} eq "structured";
888 40 100       154 $vsp_ignorefirst = 0, next if $vsp_ignorefirst;
889 16 50       90 $pr->show_vpos( $y, 0 ) if $config->{debug}->{spacing};
890 16         75 my $vsp = empty_vsp( $elt, $ps );
891 16         56 $y -= $vsp;
892 16 50       81 $pr->show_vpos( $y, 1 ) if $config->{debug}->{spacing};
893 16         40 next;
894             }
895              
896 120 50       560 unless ( $elt->{type} =~ /^(?:control|set|ignore)$/ ) {
897 120         268 $vsp_ignorefirst = 0;
898             }
899              
900 120 50 66     640 if ( $elt->{type} eq "songline"
      66        
901             or $elt->{type} eq "tabline"
902             or $elt->{type} =~ /^comment(?:_box|_italic)?$/ ) {
903              
904 72 50       242 if ( $elt->{context} ne $curctx ) {
905 0         0 $curctx = $elt->{context};
906             }
907              
908 72         151 my $fonts = $ps->{fonts};
909 72         119 my $type = $elt->{type};
910              
911 72         129 my $ftext;
912 72 50       174 if ( $type eq "songline" ) {
    0          
    0          
913 72 50       204 $ftext = $curctx eq "chorus" ? $fonts->{chorus} : $fonts->{text};
914             }
915             elsif ( $type =~ /^comment/ ) {
916 0   0     0 $ftext = $fonts->{$type} || $fonts->{comment};
917             }
918             elsif ( $type eq "tabline" ) {
919 0         0 $ftext = $fonts->{tab};
920             }
921              
922             # Get vertical space the songline will occupy.
923 72         245 my $vsp = songline_vsp( $elt, $ps );
924 72 50 33     409 if ( $elt->{type} eq "songline" && !$elt->{indent} ) {
925 72         283 my $e = wrap( $pr, $elt, $x );
926 72 50       359 if ( @$e > 1 ) {
927 0         0 $checkspace->($vsp * scalar( @$e ));
928 0         0 $elt = shift( @$e );
929 0         0 unshift( @elts, @$e );
930             }
931             }
932              
933             # Add prespace if fit. Otherwise newpage.
934 72         248 $checkspace->($vsp);
935              
936 72 50       255 $pr->show_vpos( $y, 0 ) if $config->{debug}->{spacing};
937              
938 72         154 my $indent = 0;
939              
940             # Handle decorations.
941              
942 72 50       203 if ( $elt->{context} eq "chorus" ) {
943 0         0 my $style = $ps->{chorus};
944 0         0 $indent = $style->{indent};
945 0 0 0     0 if ( $style->{bar}->{offset} && $style->{bar}->{width} ) {
946             my $cx = $ps->{__leftmargin} + $ps->{_indent}
947             - $style->{bar}->{offset}
948 0         0 + $indent;
949             $pr->vline( $cx, $y, $vsp,
950             $style->{bar}->{width},
951 0         0 $style->{bar}->{color} );
952             }
953 0         0 $curctx = "chorus";
954 0 0       0 $i_tag = "" unless $config->{settings}->{choruslabels};
955             }
956              
957             # Substitute metadata in comments.
958 72 50 33     303 if ( $elt->{type} =~ /^comment/ && !$elt->{indent} ) {
959 0         0 $elt = { %$elt };
960             # Flatten chords/phrases.
961 0 0       0 if ( $elt->{chords} ) {
962 0         0 $elt->{text} = "";
963 0         0 for ( 0..$#{ $elt->{chords} } ) {
  0         0  
964 0         0 $elt->{text} .= $elt->{chords}->[$_] . $elt->{phrases}->[$_];
965             }
966             }
967 0         0 $elt->{text} = fmt_subst( $s, $elt->{text} );
968             }
969              
970             # Comment decorations.
971              
972 72         276 $pr->setfont( $ftext );
973              
974             =begin xxx
975              
976             my $text = $elt->{text};
977             my $w = $pr->strwidth( $text );
978              
979             # Draw background.
980             my $bgcol = $ftext->{background};
981             if ( $elt->{type} eq "comment" ) {
982             # Default to grey.
983             $bgcol ||= "#E5E5E5";
984             # Since we default to grey, we need a way to cancel it.
985             undef $bgcol if $bgcol eq "none";
986             }
987             if ( $bgcol ) {
988             $pr->rectxy( $x + $indent - 2, $y + 2,
989             $x + $indent + $w + 2, $y - $vsp, 3, $bgcol );
990             }
991              
992             # Draw box.
993             my $x0 = $x;
994             if ( $elt->{type} eq "comment_box" ) {
995             $x0 += 0.25; # add some offset for the box
996             $pr->rectxy( $x0 + $indent, $y + 1,
997             $x0 + $indent + $w + 1, $y - $vsp + 1,
998             0.5, undef,
999             $ftext->{color} || $ps->{theme}->{foreground} );
1000             }
1001              
1002             =cut
1003              
1004 72         13820 my $r = songline( $elt, $x, $y, $ps, song => $s, indent => $indent );
1005              
1006 72         164 $y -= $vsp;
1007 72 50       272 $pr->show_vpos( $y, 1 ) if $config->{debug}->{spacing};
1008              
1009 72 50       173 unshift( @elts, $r ) if $r;
1010 72         185 next;
1011             }
1012              
1013 48 50       139 if ( $elt->{type} eq "chorus" ) {
1014 0         0 warn("NYI: type => chorus\n");
1015 0         0 my $cy = $y + vsp($ps,-2); # ####TODO????
1016 0         0 foreach my $e ( @{$elt->{body}} ) {
  0         0  
1017 0 0       0 if ( $e->{type} eq "songline" ) {
    0          
1018 0         0 $y = songline( $e, $x, $y, $ps );
1019 0         0 next;
1020             }
1021             elsif ( $e->{type} eq "empty" ) {
1022 0         0 warn("***SHOULD NOT HAPPEN2***");
1023 0         0 $y -= vsp($ps);
1024 0         0 next;
1025             }
1026             }
1027 0         0 my $style = $ps->{chorus};
1028 0         0 my $cx = $ps->{__leftmargin} - $style->{bar}->{offset};
1029 0         0 $pr->vline( $cx, $cy, vsp($ps), 1, $style->{bar}->{color} );
1030 0         0 $y -= vsp($ps,4); # chordii
1031 0         0 next;
1032             }
1033              
1034 48 50       133 if ( $elt->{type} eq "verse" ) {
1035 0         0 warn("NYI: type => verse\n");
1036 0         0 foreach my $e ( @{$elt->{body}} ) {
  0         0  
1037 0 0       0 if ( $e->{type} eq "songline" ) {
    0          
1038 0         0 my $h = songline_vsp( $e, $ps );
1039 0         0 $checkspace->($h);
1040 0         0 songline( $e, $x, $y, $ps );
1041 0         0 $y -= $h;
1042 0         0 next;
1043             }
1044             elsif ( $e->{type} eq "empty" ) {
1045 0         0 warn("***SHOULD NOT HAPPEN2***");
1046 0         0 $y -= vsp($ps);
1047 0         0 next;
1048             }
1049             }
1050 0         0 $y -= vsp($ps,4); # chordii
1051 0         0 next;
1052             }
1053              
1054 48 50       138 if ( $elt->{type} eq "gridline" ) {
1055              
1056 0 0 0     0 next if $lyrics_only || !$ps->{grids}->{show};
1057              
1058 0         0 my $vsp = grid_vsp( $elt, $ps );
1059 0         0 $checkspace->($vsp);
1060 0 0       0 $pr->show_vpos( $y, 0 ) if $config->{debug}->{spacing};
1061              
1062 0         0 my $cells = $grid_margin->[2];
1063             $grid_cellwidth = ( $ps->{__rightmargin}
1064             - $ps->{_indent}
1065             - $ps->{__leftmargin}
1066 0         0 - ($cells)*$grid_barwidth
1067             ) / $cells;
1068             warn("L=", $ps->{__leftmargin},
1069             ", I=", $ps->{_indent},
1070             ", R=", $ps->{__rightmargin},
1071             ", C=$cells, GBW=$grid_barwidth, W=", $grid_cellwidth,
1072 0 0       0 "\n") if $config->{debug}->{spacing};
1073              
1074 0         0 gridline( $elt, $x, $y,
1075             $grid_cellwidth,
1076             $grid_barwidth,
1077             $grid_margin,
1078             $ps, song => $s );
1079              
1080 0         0 $y -= $vsp;
1081 0 0       0 $pr->show_vpos( $y, 1 ) if $config->{debug}->{spacing};
1082              
1083 0         0 next;
1084             }
1085              
1086 48 50       133 if ( $elt->{type} eq "tab" ) {
1087 0         0 warn("NYI? tab\n");
1088 0         0 $pr->setfont( $fonts->{tab} );
1089 0         0 my $dy = $fonts->{tab}->{size};
1090 0         0 foreach my $e ( @{$elt->{body}} ) {
  0         0  
1091 0 0       0 next unless $e->{type} eq "tabline";
1092 0         0 $pr->text( $e->{text}, $x, $y );
1093 0         0 $y -= $dy;
1094             }
1095 0         0 next;
1096             }
1097              
1098 48 50       129 if ( $elt->{type} eq "tabline" ) {
1099              
1100 0         0 my $vsp = tab_vsp( $elt, $ps );
1101 0         0 $checkspace->($vsp);
1102 0 0       0 $pr->show_vpos( $y, 0 ) if $config->{debug}->{spacing};
1103              
1104 0         0 songline( $elt, $x, $y, $ps );
1105              
1106 0         0 $y -= $vsp;
1107 0 0       0 $pr->show_vpos( $y, 1 ) if $config->{debug}->{spacing};
1108              
1109 0         0 next;
1110             }
1111              
1112 48 50       128 if ( $elt->{type} eq "delegate" ) {
1113 0 0       0 if ( $elt->{subtype} =~ /^image(?:-(\w+))?$/ ) {
1114 0   0     0 my $delegate = $1 // $elt->{delegate};
1115 0         0 my $pkg = __PACKAGE__;
1116 0         0 $pkg =~ s/::Output::\w+$/::Delegate::$delegate/;
1117 0 0       0 eval "require $pkg" || die($@);
1118 0   0     0 my $hd = $pkg->can($elt->{handler}) //
1119             die("PDF: Missing delegate handler ${pkg}::$elt->{handler}\n");
1120 0         0 my $pw; # available width
1121 0 0       0 if ( $ps->{columns} > 1 ) {
1122             $pw = $ps->{columnoffsets}->[1]
1123             - $ps->{columnoffsets}->[0]
1124 0         0 - $ps->{columnspace};
1125             }
1126             else {
1127 0         0 $pw = $ps->{__rightmargin} - $ps->{_leftmargin};
1128             }
1129 0         0 my $res = $hd->( $s, $pw, $elt );
1130 0 0       0 next unless $res; # assume errors have been given
1131 0         0 unshift( @elts, @$res );
1132 0         0 next;
1133             }
1134 0         0 die("PDF: Unsupported delegation $elt->{subtype}\n");
1135             }
1136              
1137 48 50       124 if ( $elt->{type} eq "image" ) {
1138             # Images are slightly more complex.
1139             # Only after establishing the desired height we can issue
1140             # the checkspace call, and we must get $y after that.
1141              
1142             my $gety = sub {
1143 0     0   0 my $h = shift;
1144 0         0 $checkspace->($h);
1145 0 0       0 $ps->{pr}->show_vpos( $y, 1 ) if $config->{debug}->{spacing};
1146 0         0 return $y;
1147 0         0 };
1148              
1149 0         0 my $vsp = imageline( $elt, $x, $ps, $gety );
1150              
1151             # Turn error into comment.
1152 0 0       0 unless ( $vsp =~ /^\d/ ) {
1153 0         0 unshift( @elts, { %$elt,
1154             type => "comment_box",
1155             text => $vsp,
1156             } );
1157 0         0 redo;
1158             }
1159              
1160 0         0 $y -= $vsp;
1161 0 0       0 $pr->show_vpos( $y, 1 ) if $config->{debug}->{spacing};
1162              
1163 0         0 next;
1164             }
1165              
1166 48 50       128 if ( $elt->{type} eq "svg" ) {
1167             # We turn SVG into one (or more) XForm objects.
1168              
1169 0         0 require SVGPDF;
1170 0         0 SVGPDF->VERSION(0.070);
1171              
1172             # Note we need special font and text handlers.
1173             my $p = SVGPDF->new
1174             ( pdf => $ps->{pr}->{pdf},
1175 0     0   0 fc => sub { svg_fonthandler( $ps, @_ ) },
1176 0     0   0 tc => sub { svg_texthandler( $ps, @_ ) },
1177             atts => { debug => $config->{debug}->{svg} > 1,
1178             verbose => $config->{debug}->{svg},
1179 0         0 } );
1180 0         0 my $o = $p->process( $elt->{uri} );
1181             warn("PDF: SVG objects: ", 0+@$o, "\n")
1182 0 0 0     0 if $config->{debug}->{svg} || !@$o;
1183 0 0       0 if ( ! @$o ) {
1184 0         0 warn("Error in SVG embedding (no SVG objects found)\n");
1185 0         0 next;
1186             }
1187              
1188 0         0 my @res;
1189 0         0 my $i = 0;
1190 0         0 for my $xo ( @$o ) {
1191 0         0 state $imgcnt = 0;
1192 0         0 $i++;
1193 0         0 my $assetid = sprintf("XFOasset%03d", $imgcnt++);
1194 0         0 $assets->{$assetid} = { type => "xform", data => $xo };
1195 0 0 0     0 my $sep = $i == @$o ? 0 : $elt->{opts}->{sep} || 0;
1196              
1197             push( @res,
1198             { type => "xform",
1199             width => $xo->{width},
1200             height => $xo->{height},
1201             vwidth => $xo->{vwidth},
1202             vheight => $xo->{vheight},
1203             id => $assetid,
1204             opts => { center => $elt->{opts}->{center},
1205 0   0     0 scale => $elt->{opts}->{scale} || 1,
1206             sep => $sep },
1207             }
1208             );
1209             warn("Created asset $assetid (xform, ",
1210             $xo->{vwidth}, "x", $xo->{vheight}, ")",
1211             " scale=", $elt->{opts}->{scale} || 1,
1212             " center=", $elt->{opts}->{center}//0,
1213             " sep=", $sep,
1214             "\n")
1215 0 0 0     0 if $config->{debug}->{images};
      0        
1216             }
1217 0         0 unshift( @elts, @res );
1218 0         0 next;
1219             }
1220              
1221 48 50       124 if ( $elt->{type} eq "xform" ) {
1222 0         0 my $h = $elt->{height};# + ($elt->{opts}->{sep}||0);
1223 0         0 my $w = $elt->{width};
1224 0         0 my $vh = $elt->{vheight};
1225 0         0 my $vw = $elt->{vwidth};
1226 0         0 my $xo = $assets->{ $elt->{id} };
1227              
1228 0         0 my $scale = min( $vw / $w, $vh / $h );
1229 0   0     0 my $sep = $elt->{opts}->{sep} || 0;
1230              
1231             # Available width and height.
1232 0         0 my $pw;
1233 0 0       0 if ( $ps->{columns} > 1 ) {
1234             $pw = $ps->{columnoffsets}->[1]
1235             - $ps->{columnoffsets}->[0]
1236 0         0 - $ps->{columnspace};
1237             }
1238             else {
1239 0         0 $pw = $ps->{__rightmargin} - $ps->{_leftmargin};
1240             }
1241 0         0 my $ph = $ps->{_margintop} - $ps->{_marginbottom};
1242              
1243 0 0       0 if ( $w * $scale > $pw ) {
1244 0         0 $scale = $pw / $w;
1245             }
1246 0 0       0 if ( $h * $scale > $ph ) {
1247 0         0 $scale = $ph / $h;
1248             }
1249             warn("XForm asset ", $elt->{id}, " (",
1250             $vw, "x", $vh, ")",
1251             " [$x,$y]",
1252             " scale=", $scale,
1253             " center=", $elt->{opts}->{center}//0,
1254             " sep=", $sep,
1255             "\n")
1256 0 0 0     0 if $config->{debug}->{images};
1257              
1258 0         0 $scale *= $elt->{opts}->{scale};
1259 0         0 my $vsp = $h * $scale;
1260 0         0 $checkspace->($vsp);
1261 0 0       0 $ps->{pr}->show_vpos( $y, 1 ) if $config->{debug}->{spacing};
1262              
1263             $pr->{pdfgfx}->object( $xo->{data}->{xo},
1264 0         0 $x-$xo->{data}->{vbox}->[0]*$scale,
1265             $y, $scale );
1266              
1267 0         0 $y -= $vsp + $sep;
1268 0 0       0 $pr->show_vpos( $y, 1 ) if $config->{debug}->{spacing};
1269              
1270 0         0 next;
1271             }
1272              
1273 48 50       124 if ( $elt->{type} eq "rechorus" ) {
1274 0         0 my $t = $ps->{chorus}->{recall};
1275 0 0       0 if ( $t->{type} !~ /^comment(?:_italic|_box)?$/ ) {
1276 0         0 die("Config error: Invalid value for pdf.chorus.recall.type\n");
1277             }
1278              
1279 0 0 0     0 if ( $t->{quote} && $elt->{chorus} ) {
    0 0        
    0 0        
      0        
1280 0         0 unshift( @elts, @{ $elt->{chorus} } );
  0         0  
1281             }
1282              
1283             elsif ( $elt->{chorus}
1284             && $elt->{chorus}->[0]->{type} eq "set"
1285             && $elt->{chorus}->[0]->{name} eq "label" ) {
1286 0 0       0 if ( $config->{settings}->{choruslabels} ) {
1287             # Use as margin label.
1288             unshift( @elts, { %$elt,
1289             type => $t->{type} // "comment",
1290             font => $ps->{fonts}->{label},
1291             text => $ps->{chorus}->{recall}->{tag},
1292             } )
1293 0 0 0     0 if $ps->{chorus}->{recall}->{tag} ne "";
1294             unshift( @elts, { %$elt,
1295             type => "set",
1296             name => "label",
1297             value => $elt->{chorus}->[0]->{value},
1298 0         0 } );
1299             }
1300             else {
1301             # Use as tag.
1302             unshift( @elts, { %$elt,
1303             type => $t->{type} // "comment",
1304             font => $ps->{fonts}->{label},
1305             text => $elt->{chorus}->[0]->{value},
1306             } )
1307 0   0     0 }
1308 0 0       0 if ( $ps->{chorus}->{recall}->{choruslike} ) {
1309 0         0 $elts[0]->{context} = $elts[1]->{context} = "chorus";
1310             }
1311             }
1312             elsif ( $t->{tag} && $t->{type} =~ /^comment(?:_(?:box|italic))?/ ) {
1313             unshift( @elts, { %$elt,
1314             type => $t->{type},
1315             text => $t->{tag},
1316 0         0 } );
1317 0 0       0 if ( $ps->{chorus}->{recall}->{choruslike} ) {
1318 0         0 $elts[0]->{context} = "chorus";
1319             }
1320             }
1321 0         0 redo;
1322             }
1323              
1324 48 50       148 if ( $elt->{type} eq "tocline" ) {
1325 48         137 my $vsp = toc_vsp( $elt, $ps );
1326 48         154 $checkspace->($vsp);
1327 48 50       167 $pr->show_vpos( $y, 0 ) if $config->{debug}->{spacing};
1328              
1329 48         161 tocline( $elt, $x, $y, $ps );
1330              
1331 48         3196 $y -= $vsp;
1332 48 50       207 $pr->show_vpos( $y, 1 ) if $config->{debug}->{spacing};
1333 48         106 next;
1334             }
1335              
1336 0 0       0 if ( $elt->{type} eq "diagrams" ) {
1337 0         0 $chorddiagrams->( $elt->{chords}, "below", $elt->{line} );
1338 0         0 next;
1339             }
1340              
1341 0 0       0 if ( $elt->{type} eq "control" ) {
1342 0 0       0 if ( $elt->{name} =~ /^(text|chord|chorus|grid|toc|tab)-size$/ ) {
    0          
    0          
1343 0 0       0 if ( defined $elt->{value} ) {
1344 0         0 $do_size->( $1, $elt->{value} );
1345             }
1346             else {
1347             # Restore default.
1348             $ps->{fonts}->{$1}->{size} =
1349 0         0 $pr->{_df}->{$1}->{size};
1350             warn("No size to restore for font $1\n")
1351 0 0       0 unless $ps->{fonts}->{$1}->{size};
1352             }
1353             }
1354             elsif ( $elt->{name} =~ /^(text|chord|chorus|grid|toc|tab)-font$/ ) {
1355 0         0 my $f = $1;
1356 0 0       0 if ( defined $elt->{value} ) {
1357 0         0 my ( $fn, $sz ) = $elt->{value} =~ /^(.*) (\d+(?:\.\d+)?)$/;
1358 0   0     0 $fn //= $elt->{value};
1359 0 0 0     0 if ( $fn =~ m;/;
    0          
1360             ||
1361             $fn =~ m;\.(ttf|otf)$;i ) {
1362 0         0 delete $ps->{fonts}->{$f}->{description};
1363 0         0 delete $ps->{fonts}->{$f}->{name};
1364 0         0 $ps->{fonts}->{$f}->{file} = $elt->{value};
1365             # Discard $sz. There will be an {xxxsize} following.
1366             }
1367             elsif ( is_corefont( $fn ) ) {
1368 0         0 delete $ps->{fonts}->{$f}->{description};
1369 0         0 delete $ps->{fonts}->{$f}->{file};
1370 0         0 $ps->{fonts}->{$f}->{name} = is_corefont($fn);
1371             # Discard $sz. There will be an {xxxsize} following.
1372             }
1373             else {
1374 0         0 delete $ps->{fonts}->{$f}->{file};
1375 0         0 delete $ps->{fonts}->{$f}->{name};
1376 0         0 $ps->{fonts}->{$f}->{description} = $elt->{value};
1377             }
1378             }
1379             else {
1380             # Restore default.
1381             $ps->{fonts}->{$f} =
1382 0         0 { %{ $pr->{_df}->{$f} } };
  0         0  
1383             }
1384 0         0 $pr->init_font($f);
1385             }
1386             elsif ( $elt->{name} =~ /^(text|chord|chorus|grid|toc|tab)-color$/ ) {
1387 0 0       0 if ( defined $elt->{value} ) {
1388 0         0 $ps->{fonts}->{$1}->{color} = $elt->{value};
1389             }
1390             else {
1391             # Restore default.
1392             $ps->{fonts}->{$1}->{color} =
1393 0         0 $pr->{_df}->{$1}->{color};
1394             }
1395             }
1396 0         0 next;
1397             }
1398              
1399 0 0       0 if ( $elt->{type} eq "set" ) {
1400 0 0       0 if ( $elt->{name} eq "lyrics-only" ) {
    0          
    0          
    0          
    0          
1401             $lyrics_only = $elt->{value}
1402 0 0       0 unless $lyrics_only > 1;
1403             }
1404             elsif ( $elt->{name} eq "gridparams" ) {
1405 0         0 my @v = @{ $elt->{value} };
  0         0  
1406 0         0 my $cells;
1407 0         0 my $bars = 8;
1408 0         0 $grid_margin = [ 0, 0 ];
1409 0 0       0 if ( $v[1] ) {
1410 0         0 $cells = $v[0] * $v[1];
1411 0         0 $bars = $v[0];
1412             }
1413             else {
1414 0         0 $cells = $v[0];
1415             }
1416 0 0       0 $cells += $grid_margin->[0] = $v[2] if $v[2];
1417 0 0       0 $cells += $grid_margin->[1] = $v[3] if $v[3];
1418 0         0 $grid_margin->[2] = $cells;
1419 0 0 0     0 if ( $ps->{labels}->{comment} && $v[4] ne "" ) {
1420             unshift( @elts, { %$elt,
1421             type => $ps->{labels}->{comment},
1422 0         0 text => $v[4],
1423             } );
1424 0         0 redo;
1425             }
1426 0         0 $i_tag = $v[4];
1427             }
1428             elsif ( $elt->{name} eq "label" ) {
1429 0 0 0     0 if ( $ps->{labels}->{comment} && $elt->{value} ne "" ) {
1430             unshift( @elts, { %$elt,
1431             type => $ps->{labels}->{comment},
1432             text => $elt->{value},
1433 0         0 } );
1434 0         0 redo;
1435             }
1436 0         0 $i_tag = $elt->{value};
1437             }
1438             elsif ( $elt->{name} eq "context" ) {
1439 0         0 $curctx = $elt->{value};
1440             }
1441             # Arbitrary config values.
1442             elsif ( $elt->{name} =~ /^pdf\.(.+)/ ) {
1443             # $ps is inuse, modify in place.
1444 0         0 my @k = split( /[.]/, $1 );
1445 0         0 my $cc = $ps;
1446 0         0 my $c = \$cc;
1447 0         0 foreach ( @k ) {
1448 0         0 $c = \($$c->{$_});
1449             }
1450 0         0 $$c = $elt->{value};
1451             }
1452 0         0 next;
1453             }
1454 0 0       0 if ( $elt->{type} eq "ignore" ) {
1455 0         0 next;
1456             }
1457              
1458 0         0 warn("PDF: Unhandled operator: ", $elt->{type}, " (ignored)\n");
1459             }
1460             continue {
1461 160         489 $prev = $elt;
1462             }
1463              
1464 40 50       242 if ( $dctl->{show} eq "below" ) {
1465 0         0 $chorddiagrams->( undef, "below");
1466             }
1467              
1468 40         159 my $pages = $thispage - $startpage + 1;
1469             $newpage->(), $pages++,
1470             if $ps->{'pagealign-songs'} > 1 && $pages % 2
1471 40 100 66     259 && $opts->{songindex} < $opts->{numsongs};
      100        
1472              
1473             # Now for the page headings and footers.
1474 40         99 $thispage = $startpage - 1;
1475 40         162 $s->{meta}->{pages} = [ $pages ];
1476              
1477 40         150 for my $p ( 1 .. $pages ) {
1478              
1479 42         267 $pr->openpage($ps, $thispage+1 );
1480              
1481             # Put titles and footer.
1482              
1483             # If even/odd pages, leftpage signals whether the
1484             # header/footer parts must be swapped.
1485 42         13164 my $rightpage = 1;
1486 42 100       246 if ( $ps->{"even-odd-pages"} ) {
1487             # Even/odd printing...
1488 32         120 $rightpage = $thispage % 2 == 0;
1489             # Odd/even printing...
1490 32 100       134 $rightpage = !$rightpage if $ps->{'even-odd-pages'} < 0;
1491             }
1492              
1493             # margin* are offsets from the edges of the paper.
1494             # _*margin are offsets taking even/odd pages into account.
1495             # _margin* are physical coordinates, taking ...
1496 42 100       236 if ( $rightpage ) {
1497 34         97 $ps->{_leftmargin} = $ps->{marginleft};
1498 34         89 $ps->{_marginleft} = $ps->{marginleft};
1499 34         86 $ps->{_rightmargin} = $ps->{marginright};
1500 34         122 $ps->{_marginright} = $ps->{papersize}->[0] - $ps->{marginright};
1501             }
1502             else {
1503 8         27 $ps->{_leftmargin} = $ps->{marginright};
1504 8         20 $ps->{_marginleft} = $ps->{marginright};
1505 8         20 $ps->{_rightmargin} = $ps->{marginleft};
1506 8         27 $ps->{_marginright} = $ps->{papersize}->[0] - $ps->{marginleft};
1507             }
1508 42         107 $ps->{_marginbottom} = $ps->{marginbottom};
1509 42         121 $ps->{_margintop} = $ps->{papersize}->[1] - $ps->{margintop};
1510 42         88 $ps->{_bottommargin} = $ps->{marginbottom};
1511              
1512             # Physical coordinates; will be adjusted to columns if needed.
1513 42         101 $ps->{__leftmargin} = $ps->{_marginleft};
1514 42         96 $ps->{__rightmargin} = $ps->{_marginright};
1515 42         85 $ps->{__topmargin} = $ps->{_margintop};
1516 42         105 $ps->{__bottommargin} = $ps->{_marginbottom};
1517              
1518 42         82 $thispage++;
1519             $s->{meta}->{page} = [ $s->{page} = $opts->{roman}
1520 42 100       290 ? roman($thispage) : $thispage ];
1521              
1522             # Determine page class.
1523 42         98 my $class = 2; # default
1524 42 100       207 if ( $thispage == 1 ) {
    100          
1525 24         87 $class = 0; # very first page
1526             }
1527             elsif ( $thispage == $startpage ) {
1528 16         41 $class = 1; # first of a song
1529             }
1530              
1531             # Three-part title handlers.
1532 42     126   301 my $tpt = sub { tpt( $ps, $class, $_[0], $rightpage, $x, $y, $s ) };
  126         517  
1533              
1534 42         119 $x = $ps->{__leftmargin};
1535 42 50       150 if ( $ps->{headspace} ) {
1536             warn("Metadata for pageheading: ", ::dump($s->{meta}), "\n")
1537 42 50       212 if $config->{debug}->{meta};
1538 42         100 $y = $ps->{_margintop} + $ps->{headspace};
1539 42         152 $y -= font_bl($fonts->{title});
1540 42         540 $y = $tpt->("title");
1541 42         132 $y = $tpt->("subtitle");
1542             }
1543              
1544 42 50       207 if ( $ps->{footspace} ) {
1545 42         139 $y = $ps->{marginbottom} - $ps->{footspace};
1546 42         126 $tpt->("footer");
1547             }
1548              
1549             }
1550              
1551 40         15845 return $pages;
1552             }
1553              
1554             sub font_bl {
1555 282     282 0 574 my ( $font ) = @_;
1556             # $font->{size} / ( 1 - $font->{fd}->{font}->descender / $font->{fd}->{font}->ascender );
1557 282         1249 $font->{size} * $font->{fd}->{font}->ascender / 1000;
1558             }
1559              
1560             sub font_ul {
1561 0     0 0 0 my ( $font ) = @_;
1562 0         0 $font->{fd}->{font}->underlineposition / 1024 * $font->{size};
1563             }
1564              
1565             sub prlabel {
1566 144     144 0 408 my ( $ps, $label, $x, $y, $font) = @_;
1567 144 50 33     503 return if $label eq "" || $ps->{_indent} == 0;
1568 0         0 my $align = $ps->{labels}->{align};
1569 0   0     0 $font ||= $ps->{fonts}->{label} || $ps->{fonts}->{text};
      0        
1570 0   0     0 $font->{size} ||= $font->{fd}->{size};
1571 0         0 $ps->{pr}->setfont($font); # for strwidth.
1572 0         0 for ( split( /\\n/, $label ) ) {
1573 0         0 my $label = $_;
1574 0 0       0 if ( $align eq "right" ) {
    0          
1575 0         0 my $avg_space_width = $ps->{pr}->strwidth("m");
1576             $ps->{pr}->text( $label,
1577 0         0 $x - $avg_space_width - $ps->{pr}->strwidth($label),
1578             $y, $font );
1579             }
1580             elsif ( $align =~ /^cent(?:er|re)$/ ) {
1581             $ps->{pr}->text( $label,
1582 0         0 $x - $ps->{_indent} + $ps->{pr}->strwidth($label)/2,
1583             $y, $font );
1584             }
1585             else {
1586             $ps->{pr}->text( $label,
1587 0         0 $x - $ps->{_indent}, $y, $font );
1588             }
1589 0         0 $y -= $font->{size} * 1.2;
1590             }
1591             }
1592              
1593             # Propagate markup entries over the fragments so that each fragment
1594             # is properly terminated.
1595             sub defrag {
1596 146     146 0 2415 my ( $frag ) = @_;
1597 146         265 my @stack;
1598             my @res;
1599              
1600 146         348 foreach my $f ( @$frag ) {
1601 292         918 my @a = split( /(<.*?>)/, $f );
1602 292 100       663 if ( @stack ) {
1603 2         6 unshift( @a, @stack );
1604 2         5 @stack = ();
1605             }
1606 292         445 my @r;
1607 292         528 foreach my $a ( @a ) {
1608 305 100       952 if ( $a =~ m;^<\s*/\s*(\w+)(.*)>$; ) {
    100          
1609 3         7 my $k = $1;
1610             #$a =~ s/\b //g;
1611             #$a =~ s/ \b//g;
1612 3 50       6 if ( @stack ) {
1613 3 50       73 if ( $stack[-1] =~ /^<\s*$k\b/ ) {
1614 3         10 pop(@stack);
1615             }
1616             else {
1617 0         0 warn("Markup error: \"@$frag\"\n",
1618             " Closing <$k> but $stack[-1] is open\n");
1619 0         0 next;
1620             }
1621             }
1622             else {
1623 0         0 warn("Markup error: \"@$frag\"\n",
1624             " Closing <$k> but no markup is open\n");
1625 0         0 next;
1626             }
1627             }
1628             elsif ( $a =~ m;^<\s*(\w+)(.*)>$; ) {
1629 6         13 my $k = $1;
1630 6         20 push( @stack, "<$k$2>" );
1631             }
1632 305         659 push( @r, $a );
1633             }
1634 292 100       594 if ( @stack ) {
1635 2         5 push( @r, map { my $t = $_;
  3         7  
1636 3         19 $t =~ s;^<\s*(\w+).*;;;
1637 3         12 $t; } reverse @stack );
1638             }
1639 292         932 push( @res, join("", @r ) );
1640             }
1641 146 50       345 if ( @stack ) {
1642 0         0 warn("Markup error: \"@$frag\"\n",
1643 0         0 " Unclosed markup: @{[ reverse @stack ]}\n" );
1644             }
1645             #warn("defrag: ", join('', @res), "\n");
1646 146         549 \@res;
1647             }
1648              
1649             sub songline {
1650 72     72 0 366 my ( $elt, $x, $ytop, $ps, %opts ) = @_;
1651              
1652             # songline draws text in boxes as follows:
1653             #
1654             # +------------------------------
1655             # | C F G
1656             # |
1657             # +------------------------------
1658             # | Lyrics text
1659             # +------------------------------
1660             #
1661             # Variants are:
1662             #
1663             # +------------------------------
1664             # | Lyrics text (lyrics-only, or single-space and no chords)
1665             # +------------------------------
1666             #
1667             # Likewise comments and tabs (which may have different fonts /
1668             # decorations).
1669             #
1670             # And:
1671             #
1672             # +-----------------------+-------
1673             # | Lyrics text | C F G
1674             # +-----------------------+-------
1675             #
1676             # Note that printing text involves baselines, and that chords
1677             # may have a different height than lyrics.
1678             #
1679             # To find the upper/lower extents, the ratio
1680             #
1681             # $font->ascender / $font->descender
1682             #
1683             # can be used. E.g., a font of size 16 with descender -250 and
1684             # ascender 750 must be drawn at 12 points under $ytop.
1685              
1686 72         176 my $pr = $ps->{pr};
1687 72         163 my $fonts = $ps->{fonts};
1688              
1689 72         154 my $type = $elt->{type};
1690              
1691 72         131 my $ftext;
1692             my $ytext;
1693 72   50     314 my $tag = $i_tag // "";
1694 72         129 $i_tag = undef;
1695              
1696 72         116 my @phrases = @{ defrag( $elt->{phrases} ) };
  72         184  
1697              
1698 72 50       262 if ( $type =~ /^comment/ ) {
1699 0   0     0 $ftext = $elt->{font} || $fonts->{$type} || $fonts->{comment};
1700 0         0 $ytext = $ytop - font_bl($ftext);
1701 0         0 my $song = $opts{song};
1702 0 0       0 $x += $opts{indent} if $opts{indent};
1703 0 0       0 $x += $elt->{indent} if $elt->{indent};
1704 0         0 prlabel( $ps, $tag, $x, $ytext );
1705 0         0 my $t = $elt->{text};
1706 0 0       0 if ( $elt->{chords} ) {
1707 0         0 $t = "";
1708 0         0 my @ph = @{ $elt->{phrases} };
  0         0  
1709 0         0 for my $chord ( @{ $elt->{chords} }) {
  0         0  
1710 0 0       0 if ( $chord eq '' ) {
1711             }
1712             else {
1713 0         0 $chord = $chord->chord_display;
1714             }
1715 0         0 $t .= $chord . shift(@ph);
1716             }
1717             }
1718 0         0 my ( $text, $ex ) = wrapsimple( $pr, $t, $x, $ftext );
1719 0         0 $pr->text( $text, $x, $ytext, $ftext );
1720 0 0       0 return $ex ne ""
1721             ? { %$elt, indent => $pr->strwidth("x"), text => $ex, chords => undef }
1722             : undef;
1723             }
1724 72 50       210 if ( $type eq "tabline" ) {
1725 0         0 $ftext = $fonts->{tab};
1726 0         0 $ytext = $ytop - font_bl($ftext);
1727 0 0       0 $x += $opts{indent} if $opts{indent};
1728 0         0 prlabel( $ps, $tag, $x, $ytext );
1729 0         0 $pr->text( $elt->{text}, $x, $ytext, $ftext, undef, "no markup" );
1730 0         0 return;
1731             }
1732              
1733             # assert $type eq "songline";
1734 72 50       336 $ftext = $fonts->{ $elt->{context} eq "chorus" ? "chorus" : "text" };
1735 72         198 $ytext = $ytop - font_bl($ftext); # unless lyrics AND chords
1736              
1737 72         663 my $fchord = $fonts->{chord};
1738 72         189 my $ychord = $ytop - font_bl($fchord);
1739              
1740             # Just print the lyrics if no chords.
1741 72 50 33     672 if ( $lyrics_only
      33        
1742             or
1743             $suppress_empty_chordsline && !has_visible_chords($elt)
1744             ) {
1745 0         0 my $x = $x;
1746 0 0       0 $x += $opts{indent} if $opts{indent};
1747 0 0       0 $x += $elt->{indent} if $elt->{indent};
1748 0         0 prlabel( $ps, $tag, $x, $ytext );
1749 0         0 my ( $text, $ex ) = wrapsimple( $pr, join( "", @phrases ),
1750             $x, $ftext );
1751 0         0 $pr->text( $text, $x, $ytext, $ftext );
1752 0 0       0 return $ex ne "" ? { %$elt, indent => $pr->strwidth("x"), phrases => [$ex] } : undef;
1753             }
1754              
1755 72 50 33     365 if ( $chordscol || $inlinechords ) {
    50          
1756 0 0       0 $ytext = $ychord if $ytext > $ychord;
1757 0         0 $ychord = $ytext;
1758             }
1759             elsif ( $chordsunder ) {
1760 0         0 ( $ytext, $ychord ) = ( $ychord, $ytext );
1761             # Adjust lyrics baseline for the chords.
1762             $ychord -= $ps->{fonts}->{text}->{size}
1763 0         0 * $ps->{spacing}->{lyrics};
1764             }
1765             else {
1766             # Adjust lyrics baseline for the chords.
1767             $ytext -= $ps->{fonts}->{chord}->{size}
1768 72         1537 * $ps->{spacing}->{chords};
1769             }
1770              
1771 72   50     224 $elt->{chords} //= [ '' ];
1772 72 50       222 $x += $elt->{indent} if $elt->{indent};
1773              
1774 72         138 my $chordsx = $x;
1775 72 50       155 $chordsx += $ps->{chordscolumn} if $chordscol;
1776 72 50       189 if ( $chordsx < 0 ) { #### EXPERIMENTAL
1777 0         0 ($x, $chordsx) = (-$chordsx, $x);
1778             }
1779 72 50       185 $x += $opts{indent} if $opts{indent};
1780              
1781             # How to embed the chords.
1782 72 50       174 if ( $inlinechords ) {
1783 0 0       0 $inlinechords = '[%s]' unless $inlinechords =~ /%[cs]/;
1784 0         0 $ychord = $ytext;
1785             }
1786              
1787 72         617 my @chords;
1788 72         117 my $n = $#{$elt->{chords}};
  72         183  
1789 72         199 foreach my $i ( 0 .. $n ) {
1790              
1791 144         301 my $chord = $elt->{chords}->[$i];
1792 144         269 my $phrase = $phrases[$i];
1793              
1794 144 50 33     412 if ( $chordscol && $chord ne "" ) {
1795              
1796 0 0       0 if ( $chordscapo ) {
1797             $pr->text(fmt_subst( $opts{song}, $ps->{capoheading} ),
1798             $chordsx,
1799             $ytext + $ftext->{size} *
1800             $ps->{spacing}->{chords},
1801 0         0 $fonts->{chord} );
1802 0         0 undef $chordscapo;
1803             }
1804              
1805             # Underline the first word of the phrase, to indicate
1806             # the actual chord position. Skip leading non-letters.
1807 0 0       0 $phrase = " " if $phrase eq "";
1808 0         0 my ( $pre, $word, $rest ) = $phrase =~ /^(\W+)?(\w+)(.+)?$/;
1809 0         0 my $ulstart = $x;
1810 0 0       0 $ulstart += $pr->strwidth($pre) if defined($pre);
1811 0   0     0 my $w = $pr->strwidth( $word//" ", $ftext );
1812             # Avoid running together of syllables.
1813 0 0       0 $w *= 0.75 unless defined($rest);
1814              
1815             $pr->hline( $ulstart, $ytext + font_ul($ftext), $w,
1816 0         0 0.25, $ps->{theme}->{foreground} );
1817              
1818             # Print the text.
1819 0         0 prlabel( $ps, $tag, $x, $ytext );
1820 0         0 $tag = "";
1821 0         0 $x = $pr->text( $phrase, $x, $ytext, $ftext );
1822              
1823             # Collect chords to be printed in the side column.
1824 0         0 $chord = $chord->chord_display;
1825 0         0 push( @chords, $chord );
1826             }
1827             else {
1828 144         238 my $xt0 = $x;
1829 144         213 my $font = $fchord;
1830 144 50       434 if ( $chord ne '' ) {
1831 144         563 my $ch = $chord->chord_display;
1832 144         2273 my $dp = $ch . " ";
1833 144 50       579 if ( $chord->info->is_annotation ) {
    50          
1834 0         0 $font = $fonts->{annotation};
1835 0 0       0 ( $dp = $inlineannots ) =~ s/%[cs]/$ch/g
1836             if $inlinechords;
1837             }
1838             elsif ( $inlinechords ) {
1839 0         0 ( $dp = $inlinechords ) =~ s/%[cs]/$ch/g;
1840             }
1841 144         1507 $xt0 = $pr->text( $dp, $x, $ychord, $font );
1842             }
1843              
1844             # Do not indent chorus labels (issue #81).
1845 144         778 prlabel( $ps, $tag, $x-$opts{indent}, $ytext );
1846 144         270 $tag = "";
1847 144 50       642 if ( $inlinechords ) {
1848 0         0 $x = $pr->text( $phrase, $xt0, $ytext, $ftext );
1849             }
1850             else {
1851 144         1232 my $xt1;
1852 144 50       630 if ( $phrase =~ /^\s+$/ ) {
1853 0         0 $xt1 = $xt0 + length($phrase) * $pr->strwidth(" ",$ftext);
1854             # $xt1 = $pr->text( "n" x length($phrase), $xt0, $ytext, $ftext );
1855             }
1856             else {
1857 144         459 $xt1 = $pr->text( $phrase, $x, $ytext, $ftext );
1858             }
1859 144 50       487 if ( $xt0 > $xt1 ) { # chord is wider
1860             # Do we need to insert a split marker?
1861 0 0 0     0 if ( $i < $n
      0        
      0        
1862             && demarkup($phrase) !~ /\s$/
1863             && demarkup($phrases[$i+1]) !~ /^\s/
1864             # And do we have one?
1865             && ( my $marker = $ps->{'split-marker'} ) ) {
1866              
1867             # Marker has 3 parts: start, repeat, and final.
1868             # final is always printed, last.
1869             # start is printed if there is enough room.
1870             # repeat is printed repeatedly to fill the rest.
1871 0 0       0 $marker = [ $marker, "", "" ]
1872             unless UNIVERSAL::isa( $marker, 'ARRAY' );
1873              
1874             # Reserve space for final.
1875 0         0 my $w = 0;
1876 0         0 $pr->setfont($ftext);
1877 0 0       0 $w = $pr->strwidth($marker->[2]) if $marker->[2];
1878 0         0 $xt0 -= $w;
1879             # start or repeat (if no start).
1880 0   0     0 my $m = $marker->[0] || $marker->[1];
1881 0         0 $x = $xt1;
1882 0 0       0 $x = $xt0 unless $m;
1883 0         0 while ( $x < $xt0 ) {
1884 0         0 $x = $pr->text( $m, $x, $ytext, $ftext );
1885             # After the first, use repeat.
1886 0         0 $m = $marker->[1];
1887 0 0       0 $x = $xt0, last unless $m;
1888             }
1889             # Print final.
1890 0 0       0 if ( $w ) {
1891 0         0 $x = $pr->text( $marker->[2], $x, $ytext, $ftext );
1892             }
1893             }
1894             # Adjust the position for the chord and spit marker width.
1895 0 0       0 $x = $xt0 if $xt0 > $x;
1896             }
1897             else {
1898             # Use lyrics width.
1899 144         473 $x = $xt1;
1900             }
1901             }
1902             }
1903             }
1904              
1905             # Print side column with chords, if any.
1906 72 50       275 $pr->text( join(", ", @chords),
1907             $chordsx, $ychord, $fchord )
1908             if @chords;
1909              
1910 72         312 return;
1911             }
1912              
1913             sub is_bar {
1914 0 0   0 0 0 exists( $_[0]->{class} ) && $_[0]->{class} eq "bar";
1915             }
1916              
1917             sub gridline {
1918 0     0 0 0 my ( $elt, $x, $y, $cellwidth, $barwidth, $margin, $ps, %opts ) = @_;
1919              
1920             # Grid context.
1921              
1922 0         0 my $pr = $ps->{pr};
1923 0         0 my $fonts = $ps->{fonts};
1924              
1925 0   0     0 my $tag = $i_tag // "";
1926 0         0 $i_tag = undef;
1927              
1928             # Use the chords font for the chords, and for the symbols size.
1929 0 0       0 my $fchord = { %{ $fonts->{grid} || $fonts->{chord} } };
  0         0  
1930 0         0 delete($fchord->{background});
1931 0         0 $y -= font_bl($fchord);
1932              
1933 0         0 prlabel( $ps, $tag, $x, $y );
1934              
1935 0         0 $x += $barwidth;
1936 0         0 $cellwidth += $barwidth;
1937              
1938 0   0     0 $elt->{tokens} //= [ {} ];
1939              
1940 0         0 my $firstbar;
1941             my $lastbar;
1942 0         0 foreach my $i ( 0 .. $#{ $elt->{tokens} } ) {
  0         0  
1943 0 0       0 next unless is_bar( $elt->{tokens}->[$i] );
1944 0         0 $lastbar = $i;
1945 0   0     0 $firstbar //= $i;
1946             }
1947              
1948 0         0 my $prevbar = -1;
1949 0         0 my @tokens = @{ $elt->{tokens} };
  0         0  
1950 0         0 my $t;
1951              
1952 0 0       0 if ( $margin->[0] ) {
1953 0         0 $x -= $barwidth;
1954 0 0       0 if ( $elt->{margin} ) {
1955 0         0 my $t = $elt->{margin};
1956 0 0       0 if ( $t->{chords} ) {
1957 0         0 $t->{text} = "";
1958 0         0 for ( 0..$#{ $t->{chords} } ) {
  0         0  
1959 0         0 $t->{text} .= $t->{chords}->[$_]->chord_display . $t->{phrases}->[$_];
1960             }
1961             }
1962 0         0 $pr->text( $t->{text}, $x, $y, $fonts->{comment} );
1963             }
1964 0         0 $x += $margin->[0] * $cellwidth + $barwidth;
1965             }
1966              
1967 0         0 my $ctl = $pr->{ps}->{grids}->{cellbar};
1968 0         0 my $col = $pr->{ps}->{grids}->{symbols}->{color};
1969 0         0 my $needcell = $ctl->{width};
1970              
1971 0         0 state $prevvoltastart;
1972 0         0 my $align;
1973 0 0 0     0 if ( $prevvoltastart && @tokens
      0        
      0        
1974             && $tokens[0]->{class} eq "bar" && $tokens[0]->{align} ) {
1975 0         0 $align = $prevvoltastart;
1976             }
1977 0         0 $prevvoltastart = 0;
1978              
1979 0         0 my $voltastart;
1980 0         0 foreach my $i ( 0 .. $#tokens ) {
1981 0         0 my $token = $tokens[$i];
1982 0         0 my $sz = $fchord->{size};
1983              
1984 0 0       0 if ( $token->{class} eq "bar" ) {
1985 0         0 $x -= $barwidth;
1986 0 0       0 if ( $voltastart ) {
1987 0         0 pr_voltafinish( $voltastart, $y, $x - $voltastart, $sz, $col, $pr );
1988 0         0 $voltastart = 0;
1989             }
1990              
1991 0         0 $t = $token->{symbol};
1992 0         0 if ( 0 ) {
1993             $t = "{" if $t eq "|:";
1994             $t = "}" if $t eq ":|";
1995             $t = "}{" if $t eq ":|:";
1996             }
1997             else {
1998 0 0       0 $t = "|:" if $t eq "{";
1999 0 0       0 $t = ":|" if $t eq "}";
2000 0 0       0 $t = ":|:" if $t eq "}{";
2001             }
2002              
2003 0         0 my $lcr = -1; # left, center, right
2004 0 0       0 $lcr = 0 if $i > $firstbar;
2005 0 0       0 $lcr = 1 if $i == $lastbar;
2006              
2007 0 0       0 if ( $t eq "|" ) {
    0          
    0          
    0          
    0          
    0          
    0          
2008 0 0       0 if ( $token->{volta} ) {
2009 0 0       0 if ( $align ) {
2010 0         0 $x = $align;
2011 0         0 $lcr = 0;
2012             }
2013             $voltastart =
2014 0         0 pr_rptvolta( $x, $y, $lcr, $sz, $col, $pr, $token );
2015 0   0     0 $prevvoltastart ||= $x;
2016             }
2017             else {
2018 0         0 pr_barline( $x, $y, $lcr, $sz, $col, $pr );
2019             }
2020             }
2021             elsif ( $t eq "||" ) {
2022 0         0 pr_dbarline( $x, $y, $lcr, $sz, $col, $pr );
2023             }
2024             elsif ( $t eq "|:" ) {
2025 0         0 pr_rptstart( $x, $y, $lcr, $sz, $col, $pr );
2026             }
2027             elsif ( $t eq ":|" ) {
2028 0         0 pr_rptend( $x, $y, $lcr, $sz, $col, $pr );
2029             }
2030             elsif ( $t eq ":|:" ) {
2031 0         0 pr_rptendstart( $x, $y, $lcr, $sz, $col, $pr );
2032             }
2033             elsif ( $t eq "|." ) {
2034 0         0 pr_endline( $x, $y, $lcr, $sz, $col, $pr );
2035             }
2036             elsif ( $t eq " %" ) { # repeat2Bars
2037 0         0 pr_repeat( $x+$sz/2, $y, 0, $sz, $col, $pr );
2038             }
2039             else {
2040 0         0 die($t); # can't happen
2041             }
2042 0         0 $x += $barwidth;
2043 0         0 $prevbar = $i;
2044 0         0 $needcell = 0;
2045 0         0 next;
2046             }
2047              
2048 0 0       0 if ( $token->{class} eq "repeat2" ) {
2049             # For repeat2Bars, change the next bar line to pseudo-bar.
2050 0         0 my $k = $prevbar + 1;
2051 0   0     0 while ( $k <= $#tokens
2052             && !is_bar($tokens[$k]) ) {
2053 0         0 $k++;
2054             }
2055 0         0 $tokens[$k] = { symbol => " %", class => "bar" };
2056 0         0 $x += $cellwidth;
2057 0         0 $needcell = 0;
2058 0         0 next;
2059             }
2060              
2061             pr_cellline( $x-$barwidth, $y, 0, $sz, $ctl->{width},
2062 0 0       0 $pr->_fgcolor($ctl->{color}), $pr )
2063             if $needcell;
2064 0         0 $needcell = $ctl->{width};
2065              
2066 0 0 0     0 if ( $token->{class} eq "chord" || $token->{class} eq "chords" ) {
    0          
    0          
    0          
    0          
2067 0   0     0 my $tok = $token->{chords} // [ $token->{chord} ];
2068 0         0 my $cellwidth = $cellwidth / @$tok;
2069 0         0 for my $t ( @$tok ) {
2070 0 0       0 $x += $cellwidth, next if $t eq '';
2071 0         0 $t = $t->chord_display;
2072 0         0 $pr->text( $t, $x, $y, $fchord );
2073 0         0 $x += $cellwidth;
2074             }
2075             }
2076             elsif ( exists $token->{chord} ) {
2077             # I'm not sure why not testing for class = chord...
2078             warn("Chord token without class\n")
2079 0 0       0 unless $token->{class} eq "chord";
2080 0         0 my $t = $token->{chord};
2081 0         0 $t = $t->chord_display;
2082 0 0       0 $pr->text( $t, $x, $y, $fchord )
2083             unless $token eq ".";
2084 0         0 $x += $cellwidth;
2085             }
2086             elsif ( $token->{class} eq "slash" ) {
2087 0         0 $pr->text( "/", $x, $y, $fchord );
2088 0         0 $x += $cellwidth;
2089             }
2090             elsif ( $token->{class} eq "space" ) {
2091 0         0 $x += $cellwidth;
2092             }
2093             elsif ( $token->{class} eq "repeat1" ) {
2094 0         0 $t = $token->{symbol};
2095 0         0 my $k = $prevbar + 1;
2096 0   0     0 while ( $k <= $#tokens
2097             && !is_bar($tokens[$k]) ) {
2098 0         0 $k++;
2099             }
2100             pr_repeat( $x + ($k - $prevbar - 1)*$cellwidth/2, $y,
2101 0         0 0, $fchord->{size}, $col, $pr );
2102 0         0 $x += $cellwidth;
2103             }
2104 0 0       0 if ( $x > $ps->{papersize}->[0] ) {
2105             # This should be signalled by the parser.
2106             # warn("PDF: Too few cells for content\n");
2107 0         0 last;
2108             }
2109             }
2110              
2111 0 0 0     0 if ( $margin->[1] && $elt->{comment} ) {
2112 0         0 my $t = $elt->{comment};
2113 0 0       0 if ( $t->{chords} ) {
2114 0         0 $t->{text} = "";
2115 0         0 for ( 0..$#{ $t->{chords} } ) {
  0         0  
2116 0         0 $t->{text} .= $t->{chords}->[$_] . $t->{phrases}->[$_];
2117             }
2118             }
2119 0         0 $pr->text( " " . $t->{text}, $x, $y, $fonts->{comment} );
2120             }
2121             }
2122              
2123             sub pr_cellline {
2124 0     0 0 0 my ( $x, $y, $lcr, $sz, $w, $col, $pr ) = @_;
2125 0         0 $x -= $w / 2 * ($lcr + 1);
2126 0         0 $pr->vline( $x, $y+0.9*$sz, $sz, $w, $col );
2127             }
2128              
2129             sub pr_barline {
2130 0     0 0 0 my ( $x, $y, $lcr, $sz, $col, $pr ) = @_;
2131 0         0 my $w = $sz / 10; # glyph width = $w
2132 0         0 $x -= $w / 2 * ($lcr + 1);
2133 0         0 $pr->vline( $x, $y+0.9*$sz, $sz, $w, $col );
2134             }
2135              
2136             sub pr_dbarline {
2137 0     0 0 0 my ( $x, $y, $lcr, $sz, $col, $pr ) = @_;
2138 0         0 my $w = $sz / 10; # glyph width = 3 * $w
2139 0         0 $x -= 1.5 * $w * ($lcr + 1);
2140 0         0 $pr->vline( $x, $y+0.9*$sz, $sz, $w, $col );
2141 0         0 $x += 2 * $w;
2142 0         0 $pr->vline( $x, $y+0.9*$sz, $sz, $w, $col );
2143             }
2144              
2145             sub pr_rptstart {
2146 0     0 0 0 my ( $x, $y, $lcr, $sz, $col, $pr ) = @_;
2147 0         0 my $w = $sz / 10; # glyph width = 3 * $w
2148 0         0 $x -= 1.5 * $w * ($lcr + 1);
2149 0         0 $pr->vline( $x, $y+0.9*$sz, $sz, $w, $col );
2150 0         0 $x += 2 * $w;
2151 0         0 $y += 0.55 * $sz;
2152 0         0 $pr->line( $x, $y, $x, $y+$w, $w, $col );
2153 0         0 $y -= 0.4 * $sz;
2154 0         0 $pr->line( $x, $y, $x, $y+$w, $w, $col );
2155             }
2156              
2157             sub pr_rptvolta {
2158 0     0 0 0 my ( $x, $y, $lcr, $sz, $symcol, $pr, $token ) = @_;
2159 0         0 my $w = $sz / 10; # glyph width = 3 * $w
2160 0         0 my $col = $pr->{ps}->{grids}->{volta}->{color};
2161 0         0 my $ret = $x -= 1.5 * $w * ($lcr + 1);
2162 0         0 $pr->vline( $x, $y+0.9*$sz, $sz, $w, $col );
2163 0         0 $x += 2 * $w;
2164 0         0 my $font = $pr->{ps}->{fonts}->{grid};
2165 0         0 $pr->setfont($font);
2166 0         0 $pr->text( "" . $token->{volta} . "",
2167             $x-$w/2, $y, $font );
2168 0         0 $ret;
2169             }
2170              
2171             sub pr_voltafinish {
2172 0     0 0 0 my ( $x, $y, $width, $sz, $symcol, $pr ) = @_;
2173 0         0 my $w = $sz / 10; # glyph width = 3 * $w
2174 0         0 my ( $col, $span ) = @{$pr->{ps}->{grids}->{volta}}{qw(color span)};
  0         0  
2175 0         0 $pr->hline( $x, $y+0.9*$sz+$w/4, $width*$span, $w/2, $col );
2176             }
2177              
2178             sub pr_rptend {
2179 0     0 0 0 my ( $x, $y, $lcr, $sz, $col, $pr ) = @_;
2180 0         0 my $w = $sz / 10; # glyph width = 3 * $w
2181 0         0 $x -= 1.5 * $w * ($lcr + 1);
2182 0         0 $pr->vline( $x + 2*$w, $y+0.9*$sz, $sz, $w, $col );
2183 0         0 $y += 0.55 * $sz;
2184 0         0 $pr->line( $x, $y, $x, $y+$w, $w, $col );
2185 0         0 $y -= 0.4 * $sz;
2186 0         0 $pr->line( $x, $y, $x, $y+$w, $w, $col );
2187             }
2188              
2189             sub pr_rptendstart {
2190 0     0 0 0 my ( $x, $y, $lcr, $sz, $col, $pr ) = @_;
2191 0         0 my $w = $sz / 10; # glyph width = 5 * $w
2192 0         0 $x -= 2.5 * $w * ($lcr + 1);
2193 0         0 $pr->vline( $x + 2*$w, $y+0.9*$sz, $sz, $col, , $w );
2194 0         0 $y += 0.55 * $sz;
2195 0         0 $pr->line( $x, $y, $x , $y+$w, $col, , $w );
2196 0         0 $pr->line( $x+4*$w, $y, $x+4*$w, $y+$w, $col, , $w );
2197 0         0 $y -= 0.4 * $sz;
2198 0         0 $pr->line( $x, $y, $x, $y+$w, $col, , $w );
2199 0         0 $pr->line( $x+4*$w, $y, $x+4*$w, $y+$w, $col, , $w );
2200             }
2201              
2202             sub pr_repeat {
2203 0     0 0 0 my ( $x, $y, $lcr, $sz, $col, $pr ) = @_;
2204 0         0 my $w = $sz / 3; # glyph width = 3 * $w
2205 0         0 $x -= 1.5 * $w * ($lcr + 1);
2206 0         0 my $lw = $sz / 10;
2207 0         0 $x -= $w / 2;
2208 0         0 $pr->line( $x, $y+0.2*$sz, $x + $w, $y+0.7*$sz, $lw );
2209 0         0 $pr->line( $x, $y+0.6*$sz, $x + 0.07*$sz , $y+0.7*$sz, $lw );
2210 0         0 $x += $w;
2211 0         0 $pr->line( $x - 0.05*$sz, $y+0.2*$sz, $x + 0.02*$sz, $y+0.3*$sz, $lw );
2212             }
2213              
2214             sub pr_endline {
2215 0     0 0 0 my ( $x, $y, $lcr, $sz, $col, $pr ) = @_;
2216 0         0 my $w = $sz / 10; # glyph width = 2 * $w
2217 0         0 $x -= 0.75 * $w * ($lcr + 1);
2218 0         0 $pr->vline( $x, $y+0.85*$sz, 0.9*$sz, 2*$w );
2219             }
2220              
2221       0 0   sub imageline_vsp {
2222             }
2223              
2224             sub imageline {
2225 0     0 0 0 my ( $elt, $x, $ps, $gety ) = @_;
2226              
2227 0         0 my $opts = $elt->{opts};
2228 0         0 my $pr = $ps->{pr};
2229              
2230 0         0 my $img;
2231 0 0       0 if ( $elt->{uri} =~ /^id=(.+)/ ) {
    0          
    0          
2232 0         0 my $id = $1;
2233 0 0       0 unless ( exists( $assets->{$id} ) ) {
2234 0 0       0 unless ( exists( $config->{assets}->{$id} ) ) {
2235 0         0 return "Unknown asset: id=$id";
2236             }
2237 0         0 my $a = $config->{assets}->{$id};
2238 0 0 0     0 if ( $a->{src} && !$a->{data} ) {
2239 10     10   144 use Image::Info;
  10         26  
  10         92395  
2240 0         0 open( my $fd, '<:raw', $a->{src} );
2241 0 0       0 unless ( $fd ) {
2242 0         0 return $a->{src} . ": $!";
2243             }
2244 0         0 my $data = do { local $/; <$fd> };
  0         0  
  0         0  
2245             # Get info.
2246 0         0 my $info = Image::Info::image_info(\$data);
2247 0 0       0 if ( $info->{error} ) {
2248 0         0 return $info->{error};
2249             }
2250              
2251             # Store in assets.
2252 0   0     0 $assets //= {};
2253             $assets->{$id} =
2254             { data => $data, type => $info->{file_ext},
2255             width => $info->{width}, height => $info->{height},
2256 0         0 };
2257              
2258 0 0       0 if ( $config->{debug}->{images} ) {
2259 0         0 warn("asset[$id] ", length($data), " bytes, ",
2260             "width=$info->{width}, height=$info->{height}",
2261             "\n");
2262             }
2263             }
2264             }
2265 0 0       0 unless ( $assets->{$id}->{data} ) {
2266 0         0 return "Unhandled asset: id=$id";
2267             }
2268             }
2269             elsif ( $elt->{uri} =~ /^chord:(.*)/) {
2270             $img = placeholder->new( $1,
2271             $ps->{dd}->hsp0( undef, $ps ),
2272 0         0 $ps->{dd}->vsp0( undef, $ps ) );
2273             }
2274             elsif ( ! -s $elt->{uri} ) {
2275 0         0 return "$!: " . $elt->{uri};
2276             }
2277              
2278 0 0       0 warn("get_image ", $elt->{uri}, "\n") if $config->{debug}->{images};
2279 0   0     0 $img //= eval { $pr->get_image($elt) };
  0         0  
2280 0 0       0 unless ( $img ) {
2281 0         0 warn($@);
2282 0         0 return "Unhandled image type: " . $elt->{uri};
2283             }
2284              
2285             # Available width and height.
2286 0         0 my $pw;
2287 0 0       0 if ( $ps->{columns} > 1 ) {
2288             $pw = $ps->{columnoffsets}->[1]
2289             - $ps->{columnoffsets}->[0]
2290 0         0 - $ps->{columnspace};
2291             }
2292             else {
2293 0         0 $pw = $ps->{__rightmargin} - $ps->{_leftmargin};
2294             }
2295 0         0 my $ph = $ps->{_margintop} - $ps->{_marginbottom};
2296              
2297 0 0 0     0 if ( $opts->{width} && $opts->{width} =~ /^(\d+(?:\.\d+)?)\%$/ ) {
2298 0         0 $opts->{width} = $1/100 * $pw;
2299             }
2300 0 0 0     0 if ( $opts->{height} && $opts->{height} =~ /^(\d+(?:\.\d+)?)\%$/ ) {
2301 0         0 $opts->{height} = $1/100 * $ph;
2302             }
2303              
2304 0         0 my $scale = 1;
2305             my ( $w, $h ) = ( $opts->{width} || $img->width,
2306 0   0     0 $opts->{height} || $img->height );
      0        
2307              
2308 0 0       0 if ( $config->{debug}->{x1} ) {
2309              
2310             # Current approach: user scale overrides.
2311 0 0       0 if ( defined $opts->{scale} ) {
2312 0   0     0 $scale = $opts->{scale} || 1;
2313             }
2314             else {
2315 0 0       0 if ( $w > $pw ) {
2316 0         0 $scale = $pw / $w;
2317             }
2318 0 0       0 if ( $h*$scale > $ph ) {
2319 0         0 $scale = $ph / $h;
2320             }
2321             }
2322             }
2323             else {
2324              
2325             # Better, but may break things.
2326 0 0       0 if ( $w > $pw ) {
2327 0         0 $scale = $pw / $w;
2328             }
2329 0 0       0 if ( $h*$scale > $ph ) {
2330 0         0 $scale = $ph / $h;
2331             }
2332 0 0       0 if ( $opts->{scale} ) {
2333 0         0 $scale *= $opts->{scale};
2334             }
2335              
2336             }
2337              
2338 0 0       0 warn("Image scale: $scale\n") if $config->{debug}->{images};
2339 0         0 $h *= $scale;
2340 0         0 $w *= $scale;
2341 0 0       0 if ( $opts->{center} ) {
2342 0         0 $x += ($pw - $w) / 2;
2343 0 0       0 warn("Image center: $_[1] -> $x\n") if $config->{debug}->{images};
2344             }
2345              
2346 0         0 my $y = $gety->($h); # may have been changed by checkspace
2347 0 0 0     0 if ( defined ( my $tag = $i_tag // $opts->{label} ) ) {
2348 0         0 $i_tag = undef;
2349 0         0 my $ftext = $ps->{fonts}->{comment};
2350 0         0 my $ytext = $y - font_bl($ftext);
2351 0         0 prlabel( $ps, $tag, $x, $ytext );
2352             }
2353              
2354 0   0     0 my $anchor = $opts->{anchor} //= "float";
2355 0         0 my $ox = $opts->{x};
2356 0         0 my $oy = $opts->{y};
2357              
2358             my $calc = sub {
2359 0     0   0 my ( $l, $r, $t, $b, $mirror ) = @_;
2360 0   0     0 my $_ox = $ox // 0;
2361 0   0     0 my $_oy = $oy // 0;
2362 0         0 $x = $l;
2363 0         0 $y = $t;
2364              
2365 0 0       0 if ( $_ox =~ /^([-+]?[\d.]+)\%$/ ) {
2366 0         0 $ox = $_ox = $1/100 * ($r - $l) - ( $1/100 ) * $w;
2367             }
2368 0 0       0 if ( $_oy =~ /^([-+]?[\d.]+)\%$/ ) {
2369 0         0 $oy = $_oy = $1/100 * ($t - $b) - ( $1/100 ) * $h;
2370             }
2371 0 0       0 if ( $mirror ) {
2372 0 0       0 $x = $r - $w if $_ox =~ /^-/;
2373 0 0       0 $y = $b + $h if $_oy =~ /^-/;
2374             }
2375 0         0 };
2376              
2377 0 0       0 if ( $anchor eq "column" ) {
    0          
    0          
2378             # Relative to the column.
2379 0         0 $calc->( @{$ps}{qw( __leftmargin __rightmargin
  0         0  
2380             __topmargin __bottommargin )}, 0 );
2381             }
2382             elsif ( $anchor eq "page" ) {
2383             # Relative to the page.
2384 0         0 $calc->( @{$ps}{qw( _marginleft _marginright
  0         0  
2385             __topmargin __bottommargin )}, 0 );
2386             }
2387             elsif ( $anchor eq "paper" ) {
2388             # Relative to the paper.
2389 0         0 $calc->( 0, $ps->{papersize}->[0], $ps->{papersize}->[1], 0, 1 );
2390             }
2391             else {
2392             # image is line oriented.
2393 0         0 $calc->( $x, $ps->{__rightmargin}, $y, $ps->{__bottommargin}, 0 );
2394             }
2395              
2396 0 0       0 $x += $ox if defined $ox;
2397 0 0       0 $y -= $oy if defined $oy;
2398 0 0       0 if ( ref($img) eq "placeholder" ) {
2399             warn( sprintf("add_chord %s %.1f %.1f %.1f %.1f (%s x%+.1f y%+.1f)\n",
2400             $img->name, $x, $y, $w, $h,
2401             $anchor,
2402             $ox//0, $oy//0
2403 0 0 0     0 )) if $config->{debug}->{images};
      0        
2404 0         0 local $ps->{diagrams}->{width} = $ps->{diagrams}->{width} * $scale;
2405 0         0 local $ps->{diagrams}->{height} = $ps->{diagrams}->{height} * $scale;
2406 0         0 $ps->{dd}->draw( $ps->{_s}->{chordsinfo}->{$img->name}, $x, $y, $ps );
2407             }
2408             else {
2409             warn( sprintf("add_image %.1f %.1f %.1f %.1f (%s x%+.1f y%+.1f)\n",
2410             $x, $y, $w, $h,
2411             $anchor,
2412             $ox//0, $oy//0
2413 0 0 0     0 )) if $config->{debug}->{images};
      0        
2414              
2415 0   0     0 $pr->add_image( $img, $x, $y, $w, $h, $opts->{border} || 0 );
2416             }
2417 0 0       0 warn("done\n") if $config->{debug}->{images};
2418              
2419 0 0       0 if ( $anchor eq "float" ) {
2420 0   0     0 return $h + ($oy//0);
2421             }
2422 0         0 return 0; # vertical size
2423             }
2424              
2425             sub imagespread {
2426 0     0 0 0 my ( $elt, $x, $y, $ps ) = @_;
2427              
2428 0         0 my $opts = $elt->{opts};
2429 0         0 my $pr = $ps->{pr};
2430              
2431 0 0       0 if ( $elt->{uri} =~ /^id=(.+)/ ) {
    0          
2432             return "Unknown asset: id=$1"
2433 0 0       0 unless exists( $assets->{$1} );
2434             }
2435             elsif ( ! -s $elt->{uri} ) {
2436 0         0 return "$!: " . $elt->{uri};
2437             }
2438              
2439 0 0       0 warn("get_image ", $elt->{uri}, "\n") if $config->{debug}->{images};
2440 0         0 my $img = eval { $pr->get_image($elt) };
  0         0  
2441 0 0       0 unless ( $img ) {
2442 0         0 warn($@);
2443 0         0 return "Unhandled image type: " . $elt->{uri};
2444             }
2445              
2446             # Available width and height.
2447 0         0 my $pw = $ps->{__rightmargin} - $ps->{_leftmargin};
2448 0         0 my $ph = $ps->{_margintop} - $ps->{_marginbottom};
2449              
2450 0         0 my $scale = 1;
2451             my ( $w, $h ) = ( $opts->{width} || $img->width,
2452 0   0     0 $opts->{height} || $img->height );
      0        
2453 0 0       0 if ( defined $opts->{scale} ) {
2454 0   0     0 $scale = $opts->{scale} || 1;
2455             }
2456             else {
2457 0 0       0 if ( $w > $pw ) {
2458 0         0 $scale = $pw / $w;
2459             }
2460 0 0       0 if ( $h*$scale > $ph ) {
2461 0         0 $scale = $ph / $h;
2462             }
2463             }
2464 0 0       0 warn("Image scale: $scale\n") if $config->{debug}->{images};
2465 0         0 $h *= $scale;
2466 0         0 $w *= $scale;
2467 0 0       0 if ( $opts->{center} ) {
2468 0         0 $x += ($pw - $w) / 2;
2469 0 0       0 warn("Image center: $_[1] -> $x\n") if $config->{debug}->{images};
2470             }
2471              
2472 0 0 0     0 if ( defined ( my $tag = $i_tag // $opts->{label} ) ) {
2473 0         0 $i_tag = undef;
2474 0         0 my $ftext = $ps->{fonts}->{comment};
2475 0         0 my $ytext = $y - font_bl($ftext);
2476 0         0 prlabel( $ps, $tag, $x, $ytext );
2477             }
2478              
2479 0 0       0 warn("add_image\n") if $config->{debug}->{images};
2480 0   0     0 $pr->add_image( $img, $x, $y, $w, $h, $opts->{border} || 0 );
2481 0 0       0 warn("done\n") if $config->{debug}->{images};
2482              
2483 0         0 return $h + $elt->{opts}->{spread}; # vertical size
2484             }
2485              
2486             sub tocline {
2487 48     48 0 127 my ( $elt, $x, $y, $ps ) = @_;
2488              
2489 48         97 my $pr = $ps->{pr};
2490 48         81 my $fonts = $ps->{fonts};
2491 48         80 my $y0 = $y;
2492 48         78 my $ftoc = $fonts->{toc};
2493 48         125 $y -= font_bl($ftoc);
2494 48         574 $pr->setfont($ftoc);
2495 48         9674 my $tpl = $elt->{title};
2496 48         97 my $vsp;
2497 48         229 for ( split( /\\n/, $tpl ) ) {
2498 48         242 $ps->{pr}->text( $_, $x, $y );
2499 48 50       170 unless ($vsp) {
2500 48         129 my $p = $elt->{pageno};
2501 48         191 $ps->{pr}->text( $p, $ps->{__rightmargin} - $pr->strwidth($p), $y );
2502 48         213 $vsp = _vsp("toc", $ps);
2503             }
2504 48         167 $y -= $vsp;
2505             }
2506 48         231 my $ann = $pr->{pdfpage}->annotation;
2507 48         35736 $ann->link($elt->{page});
2508             $ann->rect( $ps->{__leftmargin}, $y0 - $ftoc->{size} * $ps->{spacing}->{toc},
2509 48         5764 $ps->{__rightmargin}, $y0 );
2510             }
2511              
2512             sub has_visible_chords {
2513 144     144 0 2001 my ( $elt ) = @_;
2514 144 50       423 if ( $elt->{chords} ) {
2515 144         221 for ( @{ $elt->{chords} } ) {
  144         417  
2516 288 50       658 next if defined;
2517 0         0 warn("Undefined chord in chords: ", ::dump($elt) );
2518             }
2519 144         252 return join( "", @{ $elt->{chords} } ) =~ /\S/;
  144         1670  
2520             }
2521 0         0 return;
2522             }
2523              
2524             sub has_visible_text {
2525 0     0 0 0 my ( $elt ) = @_;
2526 0 0       0 $elt->{phrases} && join( "", @{ $elt->{phrases} } ) =~ /\S/;
  0         0  
2527             }
2528              
2529             sub songline_vsp {
2530 72     72 0 174 my ( $elt, $ps ) = @_;
2531              
2532             # Calculate the vertical span of this songline.
2533 72         164 my $fonts = $ps->{fonts};
2534              
2535 72 50       235 if ( $elt->{type} =~ /^comment/ ) {
2536 0   0     0 my $ftext = $fonts->{$elt->{type}} || $fonts->{comment};
2537 0         0 return $ftext->{size} * $ps->{spacing}->{lyrics};
2538             }
2539 72 50       199 if ( $elt->{type} eq "tabline" ) {
2540 0         0 my $ftext = $fonts->{tab};
2541 0         0 return $ftext->{size} * $ps->{spacing}->{tab};
2542             }
2543              
2544             # Vertical span of the lyrics and chords.
2545             # my $vsp = $fonts->{text}->{size} * $ps->{spacing}->{lyrics};
2546 72         213 my $vsp = text_vsp( $elt, $ps );
2547 72         221 my $csp = $fonts->{chord}->{size} * $ps->{spacing}->{chords};
2548              
2549 72 50 33     332 return $vsp if $lyrics_only || $chordscol;
2550              
2551 72 50 33     924 return $vsp if $suppress_empty_chordsline && ! has_visible_chords($elt);
2552              
2553             # No text printing if no text.
2554 72 50 33     266 $vsp = 0 if $suppress_empty_lyricsline && join( "", @{ $elt->{phrases} } ) !~ /\S/;
  72         1044  
2555              
2556 72 50       221 if ( $inlinechords ) {
2557 0 0       0 $vsp = $csp if $csp > $vsp;
2558             }
2559             else {
2560             # We must show chords above lyrics, so add chords span.
2561 72         617 $vsp += $csp;
2562             }
2563 72         183 return $vsp;
2564             }
2565              
2566             sub _vsp {
2567 184     184   488 my ( $eltype, $ps, $sptype ) = @_;
2568 184   66     687 $sptype ||= $eltype;
2569              
2570             # Calculate the vertical span of this element.
2571              
2572 184         411 my $font = $ps->{fonts}->{$eltype};
2573 184 50       486 confess("Font $eltype has no size!") unless $font->{size};
2574 184         598 $font->{size} * $ps->{spacing}->{$sptype};
2575             }
2576              
2577 16     16 0 69 sub empty_vsp { _vsp( "empty", $_[1] ) }
2578 0     0 0 0 sub grid_vsp { _vsp( "grid", $_[1] ) }
2579 0     0 0 0 sub tab_vsp { _vsp( "tab", $_[1] ) }
2580              
2581             sub toc_vsp {
2582 48     48 0 163 my $vsp = _vsp( "toc", $_[1] );
2583 48         130 my $tpl = $_[0]->{title};
2584 48         94 my $ret = $vsp;
2585 48         212 while ( $tpl =~ /\\n/g ) {
2586 0         0 $ret += $vsp;
2587             }
2588 48         113 return $ret;
2589             }
2590              
2591             sub text_vsp {
2592 72     72 0 148 my ( $elt, $ps ) = @_;
2593              
2594 72 50       241 my $ftext = $ps->{fonts}->{ $elt->{context} eq "chorus"
2595             ? "chorus" : "text" };
2596 72         414 my $layout = Text::Layout->new( $ps->{pr}->{pdf} );
2597 72         3807 $layout->set_font_description( $ftext->{fd} );
2598 72         1202 $layout->set_font_size( $ftext->{size} );
2599             #warn("vsp: ".join( "", @{$elt->{phrases}} )."\n");
2600 72         486 $layout->set_markup( join( "", @{$elt->{phrases}} ) );
  72         459  
2601 72         3810 my $vsp = $layout->get_size->{height} * $ps->{spacing}->{lyrics};
2602             #warn("vsp $vsp \"", $layout->get_text, "\"\n");
2603             # Calculate the vertical span of this line.
2604              
2605 72 50       30683 _vsp( $elt->{context} eq "chorus" ? "chorus" : "text", $ps, "lyrics" );
2606             }
2607              
2608             sub set_columns {
2609 40     40 0 124 my ( $ps, $cols ) = @_;
2610 40         108 my @cols;
2611 40 50       157 if ( ref($cols) eq 'ARRAY' ) {
2612 0         0 @cols = @$cols;
2613 0         0 $cols = @$cols;
2614             }
2615 40 50       156 unless ( $cols ) {
2616 0   0     0 $cols = $ps->{columns} ||= 1;
2617             }
2618             else {
2619 40   50     183 $ps->{columns} = $cols ||= 1;
2620             }
2621              
2622             my $w = $ps->{papersize}->[0]
2623 40         158 - $ps->{_leftmargin} - $ps->{_rightmargin};
2624 40         147 $ps->{columnoffsets} = [ 0 ];
2625              
2626 40 50       161 if ( @cols ) { # columns with explicit widths
2627 0         0 my $stars;
2628 0         0 my $wx = $w; # available
2629 0         0 for ( @cols ) {
2630 0 0 0     0 if ( !$_ || $_ eq '*' ) {
    0          
2631 0         0 $stars++;
2632             }
2633             elsif ( /^(\d+)%$/ ) {
2634 0         0 $_ = $1 * $w / 100; # patch
2635             }
2636             else {
2637 0         0 $wx -= $_; # subtract from avail width
2638             }
2639             }
2640 0 0       0 my $sw = $wx / $stars if $stars;
2641 0         0 my $l = 0;
2642 0         0 for ( @cols ) {
2643 0 0 0     0 if ( !$_ || $_ eq '*' ) {
2644 0         0 $l += $sw;
2645             }
2646             else {
2647 0         0 $l += $_;
2648             }
2649 0         0 push( @{ $ps->{columnoffsets} }, $l );
  0         0  
2650             }
2651             #warn("COL: @{ $ps->{columnoffsets} }\n");
2652 0         0 return;
2653             }
2654              
2655 40 50       170 push( @{ $ps->{columnoffsets} }, $w ), return unless $cols > 1;
  40         168  
2656              
2657 0         0 my $d = ( $w - ( $cols - 1 ) * $ps->{columnspace} ) / $cols;
2658 0         0 $d += $ps->{columnspace};
2659 0         0 for ( 1 .. $cols-1 ) {
2660 0         0 push( @{ $ps->{columnoffsets} }, $_ * $d );
  0         0  
2661             }
2662 0         0 push( @{ $ps->{columnoffsets} }, $w );
  0         0  
2663             #warn("COL: @{ $ps->{columnoffsets} }\n");
2664             }
2665              
2666             sub showlayout {
2667 0     0 0 0 my ( $ps ) = @_;
2668 0         0 my $pr = $ps->{pr};
2669 0         0 my $col = "red";
2670 0         0 my $lw = 0.5;
2671 0         0 my $font = $ps->{fonts}->{grid};
2672              
2673 0         0 my $mr = $ps->{_rightmargin};
2674 0         0 my $ml = $ps->{_leftmargin};
2675              
2676             $pr->rectxy( $ml,
2677             $ps->{marginbottom},
2678             $ps->{papersize}->[0]-$mr,
2679             $ps->{papersize}->[1]-$ps->{margintop},
2680 0         0 $lw, undef, $col);
2681              
2682 0         0 my $fsz = 7;
2683 0         0 my $ptop = $ps->{papersize}->[1]-$ps->{margintop}+$fsz-3;
2684 0         0 $pr->setfont($font,$fsz);
2685 0         0 $pr->text( "$ml",
2686             $ml, $ptop, $font, $fsz );
2687 0         0 my $t = $ps->{papersize}->[0]-$mr;
2688             $pr->text( "$t",
2689 0         0 $ps->{papersize}->[0]-$mr-$pr->strwidth("$mr"),
2690             $ptop, $font, $fsz );
2691 0         0 $t = $ps->{papersize}->[1]-$ps->{margintop};
2692             $pr->text( "$t ",
2693             $ml-$pr->strwidth("$t "),
2694 0         0 $ps->{papersize}->[1]-$ps->{margintop}-2,
2695             $font, $fsz );
2696 0         0 $t = $ps->{marginbottom};
2697             $pr->text( "$t ",
2698             $ml-$pr->strwidth("$t "),
2699 0         0 $ps->{marginbottom}-2,
2700             $font, $fsz );
2701             my @a = ( $ml,
2702             $ps->{papersize}->[1]-$ps->{margintop}+$ps->{headspace},
2703 0         0 $ps->{papersize}->[0]-$ml-$mr,
2704             $lw, $col );
2705 0         0 $pr->hline(@a);
2706 0         0 $t = $a[1];
2707 0         0 $pr->text( "$t ",
2708             $ml-$pr->strwidth("$t "),
2709             $a[1]-2,
2710             $font, $fsz );
2711 0         0 $a[1] = $ps->{marginbottom}-$ps->{footspace};
2712 0         0 $pr->hline(@a);
2713 0         0 $t = $a[1];
2714 0         0 $pr->text( "$t ",
2715             $ml-$pr->strwidth("$t "),
2716             $a[1]-2,
2717             $font, $fsz );
2718              
2719 0         0 my @off = @{ $ps->{columnoffsets} };
  0         0  
2720 0         0 pop(@off);
2721 0 0       0 @off = ( $ps->{chordscolumn} ) if $chordscol;
2722             @a = ( undef,
2723             $ps->{marginbottom},
2724             $ps->{margintop}-$ps->{papersize}->[1]+$ps->{marginbottom},
2725 0         0 $lw, $col );
2726 0         0 foreach my $i ( 0 .. @off-1 ) {
2727 0 0       0 next unless $off[$i];
2728 0         0 $a[0] = $ml + $off[$i];
2729 0         0 $pr->text( "$a[0]",
2730             $a[0] - $pr->strwidth($a[0])/2, $ptop, $font, $fsz );
2731 0         0 $pr->vline(@a);
2732 0         0 $a[0] = $ml + $off[$i] - $ps->{columnspace};
2733 0         0 $pr->text( "$a[0]",
2734             $a[0] - $pr->strwidth($a[0])/2, $ptop, $font, $fsz );
2735 0         0 $pr->vline(@a);
2736 0 0       0 if ( $ps->{_indent} ) {
2737 0         0 $a[0] = $ml + $off[$i] + $ps->{_indent};
2738 0         0 $pr->vline(@a);
2739             }
2740             }
2741 0 0       0 if ( $ps->{_indent} ) {
2742 0         0 $a[0] = $ml + $ps->{_indent};
2743 0         0 $pr->vline(@a);
2744             }
2745             }
2746              
2747             sub config_pdfapi {
2748 11     11 0 3008 my ( $lib, $verbose ) = @_;
2749 11         33 my $pdfapi;
2750              
2751 11         35 my $t = "config";
2752             # Get PDF library.
2753 11 50       69 if ( $ENV{CHORDPRO_PDF_API} ) {
2754 0         0 $t = "CHORDPRO_PDF_API";
2755 0         0 $lib = $ENV{CHORDPRO_PDF_API};
2756             }
2757 11 50       72 if ( $lib ) {
2758 0 0       0 unless ( eval( "require $lib" ) ) {
2759 0         0 die("Missing PDF library $lib ($t)\n");
2760             }
2761 0         0 $pdfapi = $lib;
2762 0 0       0 warn("Using PDF library $lib ($t)\n") if $verbose;
2763             }
2764             else {
2765 11         49 for ( qw( PDF::API2 PDF::Builder ) ) {
2766 11 50       1530 eval "require $_" or next;
2767 11         1753155 $pdfapi = $_;
2768 11 50       65 warn("Using PDF library $_ (detected)\n") if $verbose;
2769 11         42 last;
2770             }
2771             }
2772 11 50       45 die("Missing PDF library\n") unless $pdfapi;
2773 11         76 return $pdfapi;
2774             }
2775              
2776             sub configurator {
2777 8     8 0 41 my ( $cfg ) = @_;
2778              
2779             # From here, we're mainly dealing with the PDF settings.
2780 8         112 my $pdf = $cfg->{pdf};
2781              
2782             # Get PDF library.
2783 8   33     88 $pdfapi //= config_pdfapi( $pdf->{library} );
2784              
2785 8         35 my $fonts = $pdf->{fonts};
2786              
2787             # Apply Chordii command line compatibility.
2788              
2789             # Command line only takes text and chord fonts.
2790 8         33 for my $type ( qw( text chord ) ) {
2791 16         82 for ( $options->{"$type-font"} ) {
2792 16 50       56 next unless $_;
2793 0 0       0 if ( m;/; ) {
2794 0         0 $fonts->{$type}->{file} = $_;
2795             }
2796             else {
2797 0 0       0 die("Config error: \"$_\" is not a built-in font\n")
2798             unless is_corefont($_);
2799 0         0 $fonts->{$type}->{name} = is_corefont($_);
2800             }
2801             }
2802 16         79 for ( $options->{"$type-size"} ) {
2803 16 50       74 $fonts->{$type}->{size} = $_ if $_;
2804             }
2805             }
2806              
2807 8         44 for ( $options->{"page-size"} ) {
2808 8 50       38 $pdf->{papersize} = $_ if $_;
2809             }
2810 8         31 for ( $options->{"vertical-space"} ) {
2811 8 50       38 next unless $_;
2812             $pdf->{spacing}->{lyrics} +=
2813 0         0 $_ / $fonts->{text}->{size};
2814             }
2815 8         55 for ( $options->{"lyrics-only"} ) {
2816 8 50       37 next unless defined $_;
2817             # If set on the command line, it cannot be overridden
2818             # by configs and {controls}.
2819 0         0 $pdf->{"lyrics-only"} = 2 * $_;
2820             }
2821 8         32 for ( $options->{"single-space"} ) {
2822 8 50       33 next unless defined $_;
2823 0         0 $pdf->{"suppress-empty-chords"} = $_;
2824             }
2825 8         32 for ( $options->{"even-pages-number-left"} ) {
2826 8 50       39 next unless defined $_;
2827 0         0 $pdf->{"even-pages-number-left"} = $_;
2828             }
2829              
2830             # Chord grid width.
2831 8 50       37 if ( $options->{'chord-grid-size'} ) {
2832             # Note that this is legacy, so for the chord diagrams only,
2833             $pdf->{diagrams}->{width} =
2834             $pdf->{diagrams}->{height} =
2835             $options->{'chord-grid-size'} /
2836 0         0 @{ $config->{notes}->{sharps} };
  0         0  
2837             }
2838              
2839             # Map papersize name to [ width, height ].
2840 8 50       391 unless ( eval { $pdf->{papersize}->[0] } ) {
  8         154  
2841 8         770 eval "require ${pdfapi}::Resource::PaperSizes";
2842 8         98 my %ps = "${pdfapi}::Resource::PaperSizes"->get_paper_sizes;
2843             die("Unhandled paper size: ", $pdf->{papersize}, "\n")
2844 8 50       415 unless exists $ps{lc $pdf->{papersize}};
2845             $pdf->{papersize} = $ps{lc $pdf->{papersize}}
2846 8         77 }
2847              
2848             # Merge properties for derived fonts.
2849             my $fm = sub {
2850 104     104   238 my ( $font, $def ) = @_;
2851 104         153 for ( keys %{ $fonts->{$def} } ) {
  104         312  
2852 408 100       923 next if /^(?:background|frame)$/;
2853 312   66     1126 $fonts->{$font}->{$_} //= $fonts->{$def}->{$_};
2854             }
2855 8         73 };
2856 8         42 $fm->( qw( subtitle text ) );
2857 8         55 $fm->( qw( chorus text ) );
2858 8         59 $fm->( qw( comment_italic text ) );
2859 8         50 $fm->( qw( comment_box text ) );
2860 8         37 $fm->( qw( comment text ) );
2861 8         44 $fm->( qw( annotation chord ) );
2862 8         43 $fm->( qw( toc text ) );
2863 8         35 $fm->( qw( empty text ) );
2864 8         38 $fm->( qw( grid chord ) );
2865 8         280 $fm->( qw( grid_margin comment ) );
2866 8         79 $fm->( qw( diagram comment ) );
2867 8         53 $fm->( qw( diagram_base comment ) );
2868              
2869             # Default footer is small subtitle.
2870 8   33     121 $fonts->{footer}->{size} //= 0.6 * $fonts->{subtitle}->{size};
2871 8         32 $fm->( qw( footer subtitle ) );
2872              
2873             # This one is fixed.
2874 8         59 $fonts->{chordfingers}->{file} = "ChordProSymbols.ttf";
2875 8         113 $fonts->{chordprosymbols} = $fonts->{chordfingers};
2876             }
2877              
2878             # Get a format string for a given page class and type.
2879             # Page classes have fallbacks.
2880             sub get_format {
2881 126     126 0 283 my ( $ps, $class, $type ) = @_;
2882 126         353 my @classes = qw( first title default );
2883 126         388 for ( my $i = $class; $i < @classes; $i++ ) {
2884 174 100       678 next unless exists($ps->{formats}->{$classes[$i]}->{$type});
2885 126         433 return $ps->{formats}->{$classes[$i]}->{$type};
2886             }
2887 0         0 return;
2888             }
2889              
2890             # Three-part titles.
2891             # Note: baseline printing.
2892             sub tpt {
2893 126     126 0 435 my ( $ps, $class, $type, $rightpage, $x, $y, $s ) = @_;
2894 126         407 my $fmt = get_format( $ps, $class, $type );
2895 126 50       322 return unless $fmt;
2896 126 50 33     601 if ( @$fmt == 3 && ref($fmt->[0]) ne 'ARRAY' ) {
2897 126         295 $fmt = [ $fmt ];
2898             }
2899             # @fmt = ( left-fmt, center-fmt, right-fmt )
2900 126         248 my $pr = $ps->{pr};
2901 126         273 my $font = $ps->{fonts}->{$type};
2902              
2903 126         189 my $havefont;
2904 126         288 my $rm = $ps->{papersize}->[0] - $ps->{_rightmargin};
2905              
2906 126         269 for my $fmt ( @$fmt ) {
2907 126 50       402 if ( @$fmt % 3 ) {
2908 0         0 die("ASSERT: " . scalar(@$fmt)," part format $class $type");
2909             }
2910              
2911 126         377 my @fmt = @$fmt;
2912 126 100       329 @fmt = @fmt[2,1,0] unless $rightpage; # swap
2913              
2914             # Left part. Easiest.
2915 126 100       318 if ( $fmt[0] ) {
2916 5         32 my $t = fmt_subst( $s, $fmt[0] );
2917 5 50       976 if ( $t ne "" ) {
2918 5 50       48 $pr->setfont($font) unless $havefont++;
2919 5         1110 $pr->text( $t, $x, $y );
2920             }
2921             }
2922              
2923             # Center part.
2924 126 100       292 if ( $fmt[1] ) {
2925 80         462 my $t = fmt_subst( $s, $fmt[1] );
2926 80 100       16683 if ( $t ne "" ) {
2927 40 50       647 $pr->setfont($font) unless $havefont++;
2928 40         8038 $pr->text( $t, ($rm+$x-$pr->strwidth($t))/2, $y );
2929             }
2930             }
2931              
2932             # Right part.
2933 126 100       457 if ( $fmt[2] ) {
2934 15         97 my $t = fmt_subst( $s, $fmt[2] );
2935 15 50       2970 if ( $t ne "" ) {
2936 15 100       140 $pr->setfont($font) unless $havefont++;
2937 15         2740 $pr->text( $t, $rm-$pr->strwidth($t), $y );
2938             }
2939             }
2940              
2941 126   100     815 $y -= $font->{size} * ($ps->{spacing}->{$type} || 1);
2942             }
2943              
2944             # Return updated baseline.
2945 126         646 return $y;
2946             }
2947              
2948             sub wrap {
2949 72     72 0 208 my ( $pr, $elt, $x ) = @_;
2950 72         149 my $res = [];
2951 72   50     115 my @chords = @{ $elt->{chords} // [] };
  72         317  
2952 72   50     138 my @phrases = @{ defrag( $elt->{phrases} // [] ) };
  72         344  
2953 72         191 my @rchords;
2954             my @rphrases;
2955 72         204 my $m = $pr->{ps}->{__rightmargin};
2956             #warn("WRAP x=$x rm=$m w=", $m - $x, "\n");
2957              
2958 72         171 while ( @chords ) {
2959 144         269 my $chord = shift(@chords);
2960 144   50     368 my $phrase = shift(@phrases) // "";
2961 144         261 my $ex = "";
2962             #warn("wrap x=$x rm=$m w=", $m - $x, " ch=$chord, ph=$phrase\n");
2963              
2964 144 100 66     509 if ( @rchords && $chord ) {
2965             # Does the chord fit?
2966 72         354 my $c = $chord->chord_display;
2967 72         1206 my $w;
2968 72 50       270 if ( $c =~ /^\*(.+)/ ) {
2969 0         0 $pr->setfont( $pr->{ps}->{fonts}->{annotation} );
2970 0         0 $c = $1;
2971             }
2972             else {
2973 72         364 $pr->setfont( $pr->{ps}->{fonts}->{chord} );
2974             }
2975 72         14312 $w = $pr->strwidth($c);
2976 72 50       13659 if ( $w > $m - $x ) {
2977             # Nope. Move to overflow.
2978 0         0 $ex = $phrase;
2979             }
2980             }
2981              
2982 144 50       405 if ( $ex eq "" ) {
2983             # Do lyrics fit?
2984 144         343 my $font = $pr->{ps}->{fonts}->{text};
2985 144         575 $pr->setfont($font);
2986 144         27432 my $ph;
2987 144         688 ( $ph, $ex ) = $pr->wrap( $phrase, $m - $x );
2988             # If it doesn not fit, it is usually a case a bad luck.
2989             # However, we may be able to move to overflow.
2990 144         460 my $w = $pr->strwidth($ph);
2991 144 50 33     38985 if ( $w > $m - $x && @rchords > 1 ) {
2992 0         0 $ex = $phrase;
2993             }
2994             else {
2995 144         352 push( @rchords, $chord );
2996 144         277 push( @rphrases, $ph );
2997 144         280 $chord = '';
2998             }
2999 144         281 $x += $w;
3000             }
3001              
3002 144 50       577 if ( $ex ne "" ) { # overflow
3003 0 0 0     0 if ( $rphrases[-1] =~ /[[:alpha:]]$/
      0        
3004             && $ex =~ /^[[:alpha:]]/
3005             && $chord ne '' ) {
3006 0         0 $rphrases[-1] .= "-";
3007             }
3008 0         0 unshift( @chords, $chord );
3009 0         0 unshift( @phrases, $ex );
3010 0         0 push( @$res,
3011             { %$elt, chords => [@rchords], phrases => [@rphrases] } );
3012 0         0 $x = $_[2] + $pr->strwidth("x");
3013 0 0       0 $res->[-1]->{indent} = $pr->strwidth("x") if @$res > 1;
3014 0         0 @rchords = ();
3015 0         0 @rphrases = ();
3016             }
3017             }
3018 72         720 push( @$res, { %$elt, chords => \@rchords, phrases => \@rphrases } );
3019 72 50       312 $res->[-1]->{indent} = $pr->strwidth("x") if @$res > 1;
3020 72         272 return $res;
3021             }
3022              
3023             sub wrapsimple {
3024 0     0 0 0 my ( $pr, $text, $x, $font ) = @_;
3025 0 0       0 return ( "", "" ) unless length($text);
3026              
3027 0   0     0 $font ||= $pr->{font};
3028 0         0 $pr->setfont($font);
3029 0         0 $pr->wrap( $text, $pr->{ps}->{__rightmargin} - $x );
3030             }
3031              
3032             my %corefonts =
3033             (
3034             ( map { lc($_) => $_ }
3035             "Times-Roman",
3036             "Times-Bold",
3037             "Times-Italic",
3038             "Times-BoldItalic",
3039             "Helvetica",
3040             "Helvetica-Bold",
3041             "Helvetica-Oblique",
3042             "Helvetica-BoldOblique",
3043             "Courier",
3044             "Courier-Bold",
3045             "Courier-Oblique",
3046             "Courier-BoldOblique",
3047             "ZapfDingbats",
3048             "Georgia",
3049             "Georgia,Bold",
3050             "Georgia,Italic",
3051             "Georgia,BoldItalic",
3052             "Verdana",
3053             "Verdana,Bold",
3054             "Verdana,Italic",
3055             "Verdana,BoldItalic",
3056             "Webdings",
3057             "Wingdings" ),
3058             # For convenience.
3059             "georgia-bold" => "Georgia,Bold",
3060             "georgia-italic" => "Georgia,Italic",
3061             "georgia-bolditalic" => "Georgia,BoldItalic",
3062             "verdana-bold" => "Verdana,Bold",
3063             "verdana-italic" => "Verdana,Italic",
3064             "verdana-bolditalic" => "Verdana,BoldItalic",
3065             );
3066              
3067             sub is_corefont {
3068 680     680 0 2304 $corefonts{lc $_[0]};
3069             }
3070              
3071             # Font handler for SVG embedding.
3072             sub svg_fonthandler {
3073 0     0 0   my ( $ps, $el, $pdf, $style ) = @_;
3074              
3075 0           my $family = $style->{'font-family'};
3076 0   0       my $stl = $style->{'font-style'} // "normal";
3077 0   0       my $weight = $style->{'font-weight'} // "normal";
3078 0   0       my $size = $style->{'font-size'} || 12;
3079 0           my $key = join( "|", $family, $stl, $weight );
3080 0           state $fc = {};
3081              
3082             # As a special case we handle fonts with 'names' like
3083             # pdf.font.foo and map these to the corresponding font
3084             # in the pdf.fonts structure.
3085 0 0         if ( $family =~ /^pdf\.fonts\.(.*)/ ) {
3086 0           my $try = $ps->{fonts}->{$1};
3087 0 0         if ( $try ) {
3088             warn("SVG: Font $family found in config: ",
3089             $try->{_ff}, "\n")
3090 0 0         if $config->{debug}->{svg};
3091             # The font may change during the run, so we do not
3092             # cache it.
3093 0           return $try->{fd}->{font};
3094             }
3095             }
3096              
3097 0     0     local *Text::Layout::FontConfig::_fallback = sub { 0 };
  0            
3098              
3099 0   0       my $font = $fc->{$key} //= do {
3100              
3101 0           my $t;
3102             my $try =
3103 0           eval {
3104 0           $t = Text::Layout::FontConfig->find_font( $family, $stl, $weight );
3105 0           $t->get_font(Text::Layout->new($pdf));
3106             };
3107 0 0         if ( $try ) {
3108             warn("SVG: Font $key found in font config: ",
3109             $t->{loader_data},
3110             "\n")
3111 0 0         if $config->{debug}->{svg};
3112 0           $try;
3113             }
3114             else {
3115 0           return;
3116             }
3117             };
3118              
3119 0           return $font;
3120             }
3121              
3122             # Text handler for SVG embedding.
3123             sub svg_texthandler {
3124 0     0 0   my ( $ps, $el, $xo, $pdf, $style, $text, %opts ) = @_;
3125 0           my @t = split( /([♯♭])/, $text );
3126 0 0         if ( @t == 1 ) {
3127             # Nothing special.
3128 0           $el->set_font( $xo, $style );
3129 0           return $xo->text( $text, %opts );
3130             }
3131              
3132 0           my ( $font, $sz ) = $el->root->fontmanager->find_font($style);
3133 0           my $has_sharp = $font->glyphByUni(ord("♯")) ne ".notdef";
3134 0           my $has_flat = $font->glyphByUni(ord("♭")) ne ".notdef";
3135             # For convenience we assume that either both are available, or missing.
3136              
3137 0 0 0       if ( $has_sharp && $has_flat ) {
3138             # Nothing special.
3139 0           $xo->font( $font, $sz );
3140 0           return $xo->text( $text, %opts );
3141             }
3142              
3143             # Replace the sharp and flat glyphs by glyps from the chordfingers font.
3144 0           my $d = 0;
3145 0           my $this = 0;
3146 0           while ( @t ) {
3147 0           my $text = shift(@t);
3148 0           my $fs = shift(@t);
3149 0 0         $xo->font( $font, $sz ) unless $this eq $font;
3150 0           $d += $xo->text($text);
3151 0           $this = $font;
3152 0 0         next unless $fs;
3153 0           $xo->font( $ps->{fonts}->{chordfingers}->{fd}->{font}, $sz );
3154 0           $this = 0;
3155 0 0         $d += $xo->text( $fs eq '♭' ? '!' : '#' );
3156             }
3157 0           return $d;
3158             }
3159              
3160             sub _dump {
3161 0 0   0     return unless $config->{debug}->{fonts};
3162 0           my ( $ps ) = @_;
3163 0           print STDERR ("== Font family map\n");
3164 0 0         Text::Layout::FontConfig->new->_dump if $verbose;
3165 0           print STDERR ("== Font associations\n");
3166 0           foreach my $f ( sort keys( %{$ps->{fonts}} ) ) {
  0            
3167             printf STDERR ("%-15s %s\n", $f,
3168             eval { $ps->{fonts}->{$f}->{description} } ||
3169             eval { $ps->{fonts}->{$f}->{file} } ||
3170 0   0       eval { "[".$ps->{fonts}->{$f}->{name}."]" } ||
3171             "[]"
3172             );
3173             }
3174             }
3175              
3176             package placeholder;
3177             sub new {
3178 0     0     my $class = shift;
3179 0           my $self = {};
3180 0           $self->{name} = shift;
3181 0           $self->{w} = shift;
3182 0           $self->{h} = shift;
3183 0           bless $self => $class;
3184             }
3185 0     0     sub name { $_[0]->{name} }
3186 0     0     sub width { $_[0]->{w} }
3187 0     0     sub height { $_[0]->{h} }
3188              
3189             1;
3190