File Coverage

blib/lib/Data/iRealPro/Output/Imager.pm
Criterion Covered Total %
statement 60 672 8.9
branch 0 376 0.0
condition 0 145 0.0
subroutine 20 51 39.2
pod 0 29 0.0
total 80 1273 6.2


line stmt bran cond sub pod time code
1             #! perl
2              
3             # Data::iRealPro::Output::Imager - produce images for iRealPro songs
4              
5             # Author : Johan Vromans
6             # Created On : Fri Jan 15 19:15:00 2016
7             # Last Modified By: Johan Vromans
8             # Last Modified On: Mon Nov 5 21:33:13 2018
9             # Update Count : 1520
10             # Status : Unknown, Use with caution!
11              
12             ################ Common stuff ################
13              
14 1     1   1018 use strict;
  1         2  
  1         29  
15 1     1   5 use warnings;
  1         2  
  1         26  
16 1     1   5 use Carp;
  1         1  
  1         48  
17 1     1   563 use utf8;
  1         14  
  1         5  
18 1     1   480 use App::Packager;
  1         1609  
  1         5  
19 1     1   644 use FindBin;
  1         1008  
  1         56  
20              
21             package Data::iRealPro::Output::Imager;
22              
23 1     1   7 use parent qw( Data::iRealPro::Output::Base );
  1         13  
  1         5  
24              
25 1     1   634 use Data::Dumper;
  1         5936  
  1         56  
26 1     1   852 use Text::CSV_XS;
  1         17271  
  1         50  
27 1     1   8 use Encode qw( encode_utf8 );
  1         2  
  1         45  
28              
29 1     1   6 use constant FONTSX => 0;
  1         2  
  1         398  
30              
31             sub new {
32 0     0 0   my ( $pkg, $options ) = @_;
33              
34 0           my $self = bless( { variant => "irealpro" }, $pkg );
35              
36             # Get the resource dir.
37 0           $self->{resdir} = $ENV{RESDIR};
38 0   0       $self->{resdir} ||= App::Packager::GetResourcePath();
39 0           $self->{resdir} .= "/";
40 0           $self->{resdir} =~ s;/+$;/;;
41              
42             # Fonts resource dir.
43 0   0       $self->{fontdir} = $ENV{FONTDIR} || $self->{resdir} . "/fonts";
44 0           $self->{fontdir} .= "/";
45 0           $self->{fontdir} =~ s;/+$;/;;
46              
47 0 0         if ( $options->{npp} ) {
48             die( "Unsupported output type for NPP. Please select PNG or JPG.\n")
49 0 0         unless $options->{output} =~ /\.(jpg|png)$/i;
50 0 0         if ( $options->{npp} =~ s/_strict// ) {
51 0           $options->{npp_strict} = 1;
52             }
53 0 0         if ( $options->{npp} =~ s/-$// ) {
54 0           $options->{npp_minor} = '';
55             }
56             else {
57 0           $options->{npp_minor} = 'm';
58             }
59 0 0         $options->{npp} = 'straight' unless $options->{npp} eq 'hand';
60 0           $self->{prefab} = $self->{resdir} . "/drawable-nodpi-v4/";
61             # Quality_H is the 'missing symbol' symbol. It better be there.
62 0 0         if ( -s $self->{prefab} . "quality_h" .
    0          
63             ( $options->{npp} eq 'hand' ? "_hand" : "" ) .
64             ".png" ) {
65             }
66             else {
67 0           die( "NPP Image generation not available" );
68             }
69             warn("Using NPP images from ", $self->{prefab}, "\n")
70 0 0         if $options->{verbose};
71             }
72              
73 0           for ( qw( trace debug verbose output variant transpose toc crop
74             npp npp_minor npp_strict
75             ) ) {
76 0 0         $self->{$_} = $options->{$_} if exists $options->{$_};
77             }
78 0   0       $self->{npp} ||= 0;
79              
80             # Scaling (bitmaps only).
81 0 0 0       if ( $options->{npp} ) {
    0          
82 1     1   8 no warnings 'redefine';
  1         2  
  1         95  
83 0           eval( "sub scale(\$) { \$_[0] };" );
84             }
85             elsif ( $options->{scale} && $options->{scale} =~ /^[\d.]+$/ ) {
86 1     1   6 no warnings 'redefine';
  1         2  
  1         182  
87 0           eval( "sub scale(\$) { " . $options->{scale} . "*\$_[0] };" );
88             }
89 0           return $self;
90             }
91              
92             sub options {
93 0     0 0   my $self = shift;
94 0           [ @{ $self->SUPER::options }, qw( transpose npp ) ];
  0            
95             }
96              
97             # A4 image format.
98 1     1   10 use constant PAGE_WIDTH => 595;
  1         2  
  1         55  
99 1     1   8 use constant PAGE_HEIGHT => 842;
  1         2  
  1         57  
100              
101             # NPP operations are done on a fixed canvas. Eventually, the result
102             # will be scaled or split to match the desired output dimensions.
103              
104 1     1   7 use constant CANVAS_WIDTH => 1920;
  1         2  
  1         43  
105 1     1   6 use constant CANVAS_HEIGHT => 2480;
  1         10  
  1         1189  
106              
107             # Scaling for bitmap graphics to get finer images. Not for NPP.
108 0     0 0   sub scale($) { 2*$_[0] };
109              
110             # Fonts.
111             my $_default_font = "DroidSans.ttf";
112             #my $_default_font = "DroidSansAll.ttf";
113             my $fonts =
114             {
115             titlefont => $_default_font,
116             stitlefont => $_default_font,
117             textfont => $_default_font,
118             markfont => "DroidSans-Bold.ttf",
119             # Normal and condensed versions
120             chordfont => "Myriad-CnSemibold.ttf",
121             chrdfont => "Myriad-UcnSemibold.ttf",
122             # chordfont => "FreeSansBold.ttf",
123             # chrdfont => "FreeSansCn.ttf",
124             musicfont => "Bravura.ttf",
125             muscfont => "BravuraCn.ttf",
126             };
127              
128             # Colors.
129             my $black = "#000000";
130             my $red = "#ff0000";
131             my $blue = "#0000ff";
132              
133             sub process {
134 0     0 0   my ( $self, $u, $options ) = @_;
135             # {{{
136              
137 0           my $plname = $u->{playlist}->{name};
138              
139             # If it is a playlist, assume multiple songs.
140             # With --output this must be either a PDF, or
141             # contain %d or %t.
142 0 0 0       if ( $plname && @{ $u->{playlist}->{songs} } > 1 ) {
  0            
143 0 0 0       if ( $self->{output}
      0        
144             && $self->{output} !~ /\%\d*[dt]/
145             && $self->{output} !~ /\.pdf$/i ) {
146 0           die("Can only generate PDF for playlist\n");
147             }
148             warn( "PLAYLIST: $plname, ",
149 0           scalar(@{ $u->{playlist}->{songs} }), " songs\n" )
150 0 0         if $options->{verbose};
151 0           ( my $t = $plname ) =~ s/[ \/:"]/_/g;
152 0   0       $self->{output} ||= "$t.pdf";
153 0 0         $self->{toc} = 1 unless defined $self->{toc};
154             }
155 0   0       $self->{output} ||= "__new__.pdf";
156              
157 0           ( my $outtype = lc($self->{output}) ) =~ s/^.*\.(.+)$/$1/;
158 0           $self->{outtype} = $outtype;
159              
160 0 0         if ( $outtype eq "pdf" ) {
    0          
161 0 0         if ( eval { require PDF::API2 } ) {
  0            
162 0           $self->{pdf} = PDF::API2->new;
163             # $self->{pdf}->mediabox( 0, PAGE_HEIGHT, PAGE_WIDTH, 0 );
164 0           $self->{pdf}->mediabox( 0, 0, PAGE_WIDTH, PAGE_HEIGHT );
165             }
166             else {
167 0           die( "PDF output requires module PDF::API2 to be installed.\n" );
168             }
169             }
170             elsif ( $outtype =~ /^png|jpg$/ ) {
171 0 0         if ( eval { require Imager } ) {
  0            
172             # ok
173             }
174             else {
175 0           die( "PNG and JPG output require module Imager to be installed.\n" );
176             }
177             }
178             else {
179 0           die( "Unsupported output type for ", $self->{output}, "\n" );
180             }
181              
182 0           my $pageno = 1;
183              
184 0           my $csv;
185             my $csv_fd;
186 0           my $csv_name;
187 0 0 0       if ( $outtype eq "pdf"
188 0           && @{ $u->{playlist}->{songs} } > 1 ) {
189 0           $csv_name = $self->{output};
190 0           $csv_name =~ s/\.pdf$/.csv/i;
191 0           open( $csv_fd, ">:encoding(utf8)", $csv_name );
192 0           $csv = Text::CSV_XS->new( { binary => 1,
193             quote_space => 0,
194             sep_char => ";" } );
195 0           $csv->print( $csv_fd,
196             [ qw( title pages keys composers
197             collections ), "source types" ] );
198 0           $csv_fd->print("\n");
199             }
200              
201             # Process the song(s).
202 0           my $songix;
203             my @book;
204 0           foreach my $song ( @{ $u->{playlist}->{songs} } ) {
  0            
205 0           $songix++;
206             warn( sprintf("Song %3d: %s\n", $songix, $song->{title}) )
207 0 0         if $self->{verbose};
208 0           push( @book, [ $song->{title}, $pageno ] );
209 0           my $mx = $self->decode_song($song);
210              
211 0           $self->{songix} = $songix;
212 0           my $numpages = $self->make_image( $song, $mx );
213              
214 0 0         next unless $csv_fd;
215              
216 0           my $pages = $pageno;
217 0 0         if ( $numpages > 1 ) {
218 0           $pages .= "-" . ( $pageno + $numpages - 1 );
219 0           $pageno += $numpages;
220             }
221             else {
222 0           $pageno++;
223             }
224 0           my $key = $song->{key};
225 0           $key =~ s/-$/m/;
226 0           my $composer = $song->{composer};
227             # $composer = "$2 $1" if $composer =~ /^(.+?) +([^ ]+)$/;
228             $csv->print( $csv_fd,
229             [ $song->{title},
230 0           $pages,
231             $key,
232             $composer,
233             $plname,
234             "Sheet Music",
235             ] );
236 0           $csv_fd->print("\n");
237             }
238              
239 0 0         if ( $outtype eq "pdf" ) {
240 0 0         $pageno += $self->toc( $plname, \@book ) if $self->{toc};
241 0           $self->{pdf}->saveas($self->{output});
242 0 0         warn( "Wrote: ", $self->{output}, "\n" ) if $self->{verbose};
243 0 0         if ( $csv_fd ) {
244 0           $csv_fd->close;
245 0 0         warn( "Wrote: $csv_name\n" ) if $self->{verbose};
246             }
247             }
248             # }}}
249             }
250              
251             sub decode_song {
252 0     0 0   my ( $self, $song ) = @_;
253              
254             # Get the tokens array. This reflects as precisely as possible
255             # the contents of the pure data string.
256 0 0         if ( $self->{debug} ) {
257 0           my $tokens = $song->tokens;
258 0           warn(Dumper($tokens));
259             }
260              
261             # Then create array of cells.
262 0           my $cells = $song->cells;
263 0 0         if ( $self->{debug} ) {
264 0           warn Dumper($cells);
265 0           warn('$DATA = "', $song->{data}, "\";\n");
266             }
267              
268 0           $cells;
269             }
270              
271              
272             my %smufl =
273             ( brace => "\x{e000}",
274             reversedBrace => "\x{e001}",
275             barlineSingle => "\x{e030}",
276             barlineDouble => "\x{e031}",
277             barlineFinal => "\x{e032}",
278             # repeatLeft and Right are too wide. Use a substitute.
279             # repeatLeft => "\x{e040}",
280             # repeatRight => "\x{e041}",
281             # repeatRightLeft => "\x{e042}",
282             repeatLeft => "\x{e000}\x{e043}", # {:
283             repeatRight => "\x{e043}\x{e001}", # :}
284             repeatRightLeft => "\x{e043}\x{e001}\x{e000}\x{e043}", # :}{:
285             repeatDots => "\x{e043}",
286             dalSegno => "\x{e045}",
287             daCapo => "\x{e046}",
288             segno => "\x{e047}",
289             coda => "\x{e048}",
290             timeSig0 => "\x{e080}", # timeSig1, ...etc...
291             flat => "\x{e260}",
292             sharp => "\x{e262}",
293             fermata => "\x{e4c0}",
294             repeat1Bar => "\x{e500}",
295             repeat2Bars => "\x{e501}",
296             repeat4Bars => "\x{e502}",
297             csymDiminished => "\x{e870}",
298             csymHalfDiminished => "\x{e871}",
299             csymAugmented => "\x{e872}",
300             csymMajorSeventh => "\x{e873}",
301             csymMinor => "\x{e874}",
302             );
303              
304             my $numrows = 16;
305             my $numcols = 16;
306              
307 1     1   8 use constant CHORD_NORMAL => 0x00;
  1         2  
  1         62  
308 1     1   7 use constant CHORD_CONDENSED => 0x01;
  1         2  
  1         65  
309 1     1   7 use constant CHORD_ALTERNATIVE => 0x02;
  1         2  
  1         7361  
310              
311             # Generalized formatter for PDF::API2 and Imager.
312             sub make_image {
313             # {{{
314 0     0 0   my ( $self, $song, $cells ) = @_;
315              
316 0           my ( $lm, $tm, $rm, $bm, $dx, $dy );
317              
318 0 0         if ( $self->{npp} ) {
319 0           $lm = 68;
320 0           $rm = 0; # unused
321 0           $tm = 208;
322 0           $dx = ( CANVAS_WIDTH - $lm - 18 ) / 16;
323 0           $dy = 296;
324 0 0         if ( $self->{npp} eq 'hand' ) {
325             $fonts->{titlefont} =
326             $fonts->{stitlefont} =
327             $fonts->{textfont} =
328 0           "Felt-Regular.ttf";
329             }
330              
331             # Calculate the required heigth and create the canvas.
332 0           my $v = $tm; # top margin
333 0           $v += int( ( @$cells + 15 ) / $numcols ) * $dy; # cells
334 0           $v += $cells->[-1]->vs * 121; # extra
335 0 0         $v = CANVAS_HEIGHT if $v < CANVAS_HEIGHT; # minimal
336 0           $self->{im} = Imager->new( xsize => CANVAS_WIDTH,
337             ysize => $v,
338             model => 'rgba',
339             );
340             }
341             else {
342 0           $lm = 40;
343 0           $rm = PAGE_WIDTH - $lm;
344 0           $bm = PAGE_HEIGHT - 50;
345 0           $tm = 172 - 50;
346 0           $dx = ( $rm - $lm ) / $numcols;
347 0           $dy = ( $bm - $tm ) / $numrows;
348              
349 0 0         if ( ! $self->{pdf} ) {
350             $self->{im} =
351 0           Imager->new( xsize => scale(PAGE_WIDTH),
352             ysize => scale(PAGE_HEIGHT),
353             model => 'rgb',
354             );
355             }
356             }
357             die("Imager failure")
358 0 0 0       unless $self->{pdf} || $self->{im};
359              
360             # Create fonts.
361 0           $self->initfonts;
362              
363 0           my $textfont = $self->{textfont};
364 0           my $chordfont = $self->{chordfont};
365 0           my $chrdfont = $self->{chrdfont};
366 0           my $musicfont = $self->{musicfont};
367 0           my $muscfont = $self->{muscfont};
368 0           my $markfont = $self->{markfont};
369              
370 0           my $musicglyphs = $self->{musicglyphs};
371 0           my $musicsize = $self->{musicsize};
372 0           my $chordsize = $self->{chordsize};
373              
374 0 0         if ( $dy < 1.6*$musicsize ) {
375 0           $dy = 1.6*$musicsize;
376             }
377 0           my %missing_glyphs;
378              
379 0           $self->{pages} = 0;
380              
381             # Draw headings for a new page.
382             my $newpage = sub {
383 0     0     $self->newpage;
384              
385 0           my $titlesize = $self->{titlesize};
386 0           my $titlefont = $self->{titlefont};
387 0           my $stitlesize = $self->{stitlesize};
388 0           my $stitlefont = $self->{stitlefont};
389 0           my $ddx = 0.15*$musicsize;
390              
391             my @arg = ( ($lm+$rm)/2-$ddx, $tm-80, $song->{title},
392 0           $titlesize, $titlefont );
393 0 0         @arg[0,1] = ( CANVAS_WIDTH/2, 75 ) if $self->{npp};
394 0           $self->textc(@arg);
395              
396 0 0         if ( $song->{style} ) {
397 0           @arg = ( $lm-$ddx, $tm-50, "(".$song->{style}.")",
398             $stitlesize, $stitlefont );
399 0 0         @arg[0,1] = ( $lm + 10, 136 ) if $self->{npp};
400 0           $self->textl(@arg);
401             }
402              
403 0 0         if ( $song->{composer} ) {
404             # If the composer is two words, assume lastname firstname.
405             # iRealPro swaps them.
406 0           my @t = split( ' ', $song->{composer} );
407 0 0         @t[0,1] = @t[1,0] if @t == 2;
408 0           @arg = ( $rm+$ddx, $tm-50, "@t",
409             $stitlesize, $stitlefont );
410 0 0         @arg[0,1] = ( CANVAS_WIDTH - 60, 134 ) if $self->{npp};
411 0           $self->textr(@arg);
412             }
413              
414 0           };
415              
416 0           my $low; # water mark to crop image
417              
418             # Discard final (trailing?) empty cells for a correct $low.
419             # Note: Empty cells have just a 'vs' element.
420             pop( @$cells )
421 0   0       while $cells->[-1] && keys( %{ $cells->[-1] } ) == 1;
  0            
422              
423             # Process the cells.
424 0           for ( my $i = 0; $i < @$cells; $i++ ) {
425             # {{{
426             # onpage is the cell index relative to the current page.
427             # Note that we do not yet support multi-page songs.
428 0           my $onpage = $i % ( $numrows * $numcols );
429 0 0         if ( !$onpage ) {
430             # First cell on this page, draw headings and such.
431 0           $newpage->();
432 0           $low = 0;
433             }
434              
435             # The current cell.
436 0           my $cell = $cells->[$i];
437              
438             # Cell position on the drawing.
439 0           my $x = $lm + ( $onpage % $numcols ) * $dx;
440 0           my $y = $tm + int( $onpage / $numcols ) * $dy;
441              
442             # Adjust vertical position.
443 0           for ( $cell->vs ) {
444 0 0         next unless $_;
445 0 0         if ( $self->{npp} ) {
446 0           $y += $_*121;
447             }
448             else {
449 0           $y += $_*0.3*$dy;
450             }
451             }
452              
453             # Adjust low water mark.
454 0 0         if ( $y + $dy > $low ) {
455 0           $low = $y + $dy;
456             }
457              
458             #### Cell contents ################
459              
460 0           for ( $cell->lbar ) {
461 0 0         next unless $_;
462              
463 0 0         if ( $self->{npp} ) {
464 0           $self->npp_bar( $x, $y, $_ );
465 0           next;
466             }
467              
468 0 0         my $col = /^repeat(?:Right)?Left$/ ? $red : $black;
469 0           $self->glyphc( $x, $y, $_, undef, $col );
470 0           next;
471             }
472              
473 0           for ( $cell->rbar ) {
474 0 0         next unless $_;
475              
476 0 0         if ( $self->{npp} ) {
477 0           $self->npp_bar( $x+$dx, $y, $_ );
478 0           next;
479             }
480              
481 0           my $col = $black;
482 0 0         if ( /^repeatRight$/ ) {
483 0           $col = $red;
484 0 0 0       if ( ($i+1) % $numcols
      0        
      0        
485             && $i < @$cells-1
486             && $cells->[$i+1]->lbar
487             && $cells->[$i+1]->lbar eq "repeatLeft" ) {
488 0           $cells->[$i+1]->lbar = "repeatRightLeft";
489 0           next;
490             }
491             }
492 0           $self->glyphc( $x+$dx, $y, $_, undef, $col );
493 0           next;
494             }
495              
496 0           for ( $cell->time ) {
497 0 0         next unless $_;
498 0           my ( $t1, $t2 ) = @$_;
499 0 0         if ( $self->{npp} ) {
500 0 0 0       $t2 = "" if $t1 == 12 && $t2 == 8;
501 0           $self->npp_sig( $x, $y, "$t1$t2" );
502 0           next;
503             }
504              
505             my $w = $self->aw( $musicfont, 0.7*$musicsize,
506 0           $musicglyphs->{timeSig0} ) / 2;
507             # Move left half $w for centering, and half $w to get
508             # out of the way.
509 0           my $x = $x - $w - 0.15*$musicsize;
510             # An additinal half $w when double digits are involved.
511 0 0 0       $x -= $w/2 if $t1 > 10 || $t2 > 10;
512              
513             # Transform ordinary digits into music glyphs.
514 0           $w = ord( $musicglyphs->{timeSig0} ) - ord("0");
515 0           $t1 =~ s/(\d)/sprintf( "%c",$w+ord($1) )/ge;
  0            
516 0           $t2 =~ s/(\d)/sprintf( "%c",$w+ord($1) )/ge;
  0            
517              
518 0           $self->textc( $x, $y-0.55*$musicsize, $t1,
519             0.7*$musicsize, $musicfont, $red );
520 0           $self->textc( $x, $y-0.15*$musicsize, $t2,
521             0.7*$musicsize, $musicfont, $red );
522 0           next;
523             }
524              
525 0           for ( $cell->sign ) { # coda, segno, ...
526 0 0         next unless $_;
527 0 0         if ( $self->{npp} ) {
528 0           $self->npp_sign( $x, $y, $_ );
529 0           next;
530             }
531              
532 0           $self->glyphl( $x+0.15*$musicsize, $y-1.05*$musicsize,
533             $_, 0.7*$musicsize, $red );
534 0           next;
535             }
536              
537 0           for ( $cell->text ) {
538 0 0         next unless $_;
539 0           my ( $disp, $t ) = @$_;
540              
541 0           if ( FONTSX ) {
542             if ( $self->{pdf} ) {
543             # $textfont->{' font'} = Font::TTF::Font
544             for ( split( //, $t ) ) {
545             next if $textfont->uniByEnc(ord($_));
546             my $c = ord(substr($t,$i,1));
547             next if $missing_glyphs{$c};
548             $missing_glyphs{$c} = 1;
549             }
550             }
551             if ( $self->{im} ) {
552             my @c = $textfont->has_chars( string => $t );
553             for ( my $i = 0; $i < @c; $i++ ) {
554             next if $c[$i];
555             my $c = ord(substr($t,$i,1));
556             next if $missing_glyphs{$c};
557             $missing_glyphs{$c} = 1;
558             }
559             }
560             }
561              
562 0           my $hack;
563             # Sometimes, THAI PAIYANNOI (U+e2f) is abused as
564             # MUSICAL SYMBOL EIGHTH REST (u+1d13e).
565 0 0         $t =~ s/\x{e2f}/\x{1d13e}/g && $hack++;
566             # Likewise CYRILLIC SMALL LETTER GHE WITH UPTURN (U+491)
567             # -> MUSICAL SYMBOL QUARTER REST (U+1D13D)
568 0 0         $t =~ s/\x{491}/\x{1d13d}/g && $hack++;
569             # Likewise BOX DRAWINGS DOWN SINGLE AND LEFT DOUBLE (U+2555)
570             # -> MUSICAL SYMBOL SIXTEENTH REST (U+1D13F)
571 0 0         $t =~ s/\x{2555}/\x{1d13f}/g && $hack++;
572              
573             # Displacement is 0 .. 74, in steps of 3.
574 0 0         if ($self->{npp} ) {
575 0 0         $self->textl( $x-2, $y + $dy - 27 - ($dy / 74) * $disp, $t,
576             $hack ? 60 : 74, $textfont, $red );
577 0           next;
578             }
579              
580 0           $self->textl( $x+0.15*$musicsize,
581             $y+0.55*$musicsize-($disp/(45/$musicsize)),
582             $t, 0.55*$musicsize, $textfont, $red );
583 0           next;
584             }
585              
586 0           for ( $cell->chord ) { # chords and chordrepeats.
587 0 0         next unless $_;
588 0           my $c = $_;
589 0 0         my $font = $cell->sz ? $chrdfont : $chordfont;
590              
591 0 0         if ( $c =~ /repeat\dBars?/ ) {
592              
593             # npp_strict
594             # both go at the border of this and the next cell
595             # else
596             # 1bar and 2bar not before barline: center in measure
597             # 2bar before barline: center on barline
598              
599 0 0 0       if ( $self->{npp_strict} ) {
    0 0        
600 0           $x += $dx;
601 0 0         if ( $c eq "repeat1Bar" ) {
602 0           $self->npp_repeat( $x, $y, 1 );
603             }
604             else {
605 0           $self->npp_repeat( $x, $y, 2 );
606             }
607             }
608             elsif ( $c eq "repeat1Bar"
609             || ( $c eq "repeat2Bars" && !$cell->rbar )
610             ) {
611              
612             # Find previous bar line.
613 0           my $pb = $i;
614 0           while ( $pb >= 0) {
615 0 0 0       last if $cells->[$pb]->lbar
      0        
616             || ( $pb > 1 && $cells->[$pb-1]->rbar );
617 0           $pb--;
618             }
619             # Find next bar line.
620 0           my $nb = $i;
621 0           while ( $nb < @$cells ) {
622 0 0 0       last if $cells->[$nb]->rbar
      0        
623             || ( $nb+1 < @$cells && $cells->[$nb+1]->lbar );
624 0           $nb++;
625             }
626              
627             # Center between the barlines.
628 0           $x -= ( $i-$pb ) * $dx;
629 0           $x += ( $nb-$pb+1 ) * $dx/2;
630 0 0         if ( $self->{npp} ) {
631 0 0         if ( $c eq "repeat1Bar" ) {
632 0           $self->npp_repeat( $x, $y, 1 );
633             }
634             else {
635 0           $self->npp_repeat( $x, $y, 2 );
636             }
637             }
638             else {
639             $self->textc( $x, ($y-0.3*$musicsize),
640 0           $musicglyphs->{$c}, $chordsize, $musicfont );
641             }
642             }
643              
644             else {
645              
646             # Find next bar line.
647 0           my $nb = $i;
648 0           while ( $nb < @$cells ) {
649 0 0 0       last if $cells->[$nb]->rbar
      0        
650             || ( $nb+1 < @$cells && $cells->[$nb+1]->lbar );
651 0           $nb++;
652             }
653              
654             # Overprint next barline.
655 0           $x += ( $nb-$i+1 ) * $dx;
656 0 0         if ( $self->{npp} ) {
657 0           $self->npp_repeat( $x, $y, 2 );
658             }
659             else {
660             $self->textc( $x, ($y-0.3*$musicsize),
661 0           $musicglyphs->{$c}, $chordsize, $musicfont );
662             }
663             }
664 0           next;
665             }
666              
667 0 0         if ( $c =~ /^repeat(Slash)$/ ) {
668 0 0         if ( $self->{npp} ) {
669 0           $self->npp_slash( $x, $y );
670             }
671             else {
672 0           $self->textl( $x+0.4*$musicsize, $y, "/", $chordsize, $chordfont );
673             }
674 0           next;
675             }
676              
677 0 0         if ( $self->{npp} ) {
678 0 0         $self->npp_chord( $x, $y, $c,
679             $cell->sz ? CHORD_CONDENSED : CHORD_NORMAL );
680             }
681             else {
682 0           $self->chord( $x+0.15*$musicsize, $y, $c, $musicsize, $font );
683             }
684 0           next;
685             }
686              
687 0           for ( $cell->subchord ) {
688 0 0         next unless $_;
689 0 0         if ( $self->{npp} ) {
690 0           $self->npp_chord( $x, $y, $_, CHORD_ALTERNATIVE );
691             }
692             else {
693 0           $self->chord( $x+0.15*$musicsize, $y-$musicsize,
694             $_, 0.7*$chordsize );
695             }
696 0           next;
697             }
698              
699 0           for ( $cell->alt ) { # N1, N2, ... alternatives
700 0 0         next unless defined $_;
701 0           my $n = $_;
702 0 0         if ( $self->{npp} ) {
703 0           $self->npp_ending( $x, $y, $n );
704 0           next;
705             }
706              
707 0 0         $self->textl( $x+0.15*$musicsize, $y-$musicsize, $n . ".",
708             0.55*$musicsize, $textfont, $red ) if $n;
709 0           $self->line( $x+0.1*$musicsize,
710             $y-$musicsize,
711             $x+0.1*$musicsize,
712             $y-1.5*$musicsize, $red );
713 0           $self->line( $x+0.1*$musicsize,
714             $y-1.5*$musicsize,
715             $x+2*$dx,
716             $y-1.5*$musicsize, $red );
717 0           next;
718             }
719              
720 0 0 0       if ( $cell->flags && $cell->flags & 0x01 ) { # invisible END
721 0           next; # suppress.
722 0           my $disp = 0;
723 0 0         if ( $self->{npp} ) {
724 0           $self->textl( $x-2, $y + $dy - 27 - ($dy / 74) * $disp,
725             "END", 60, $textfont, $blue );
726 0           next;
727             }
728 0           $self->textl( $x+0.15*$musicsize,
729             $y+0.55*$musicsize-($disp/(45/$musicsize)),
730             "END", 0.5*$musicsize, $textfont, $blue );
731 0           next;
732             }
733              
734 0           for ( $cell->mark ) {
735 0 0         next unless $_;
736 0 0         if ( $self->{npp} ) {
737 0           $self->npp_mark( $x, $y, lc $_ );
738 0           next;
739             }
740              
741 0           my $t = $_;
742 0 0         $t = "In" if $t eq 'i';
743 0 0         $t = "V" if $t eq 'v';
744 0           $self->textl( $x-0.3*$musicsize, $y-0.9*$musicsize, $t,
745             0.6*$musicsize, $markfont, $red );
746 0           next;
747             }
748              
749 0           next;
750             # }}}
751             }
752              
753 0           if ( FONTSX && %missing_glyphs ) {
754             printf STDERR ( "Missing glyphs:" );
755             printf STDERR ( " U+%04X", $_)
756             foreach sort map { abs($_) } keys %missing_glyphs;
757             print STDERR ("\n");
758             }
759              
760             # Crop excess bottom space.
761 0 0 0       if ( $self->{im} && $self->{crop} && $low ) {
      0        
762 0           $self->{im} = $self->{im}->crop( top => 0, height => scale($low) );
763             }
764              
765 0 0         if ( $self->{outtype} =~ /^png|jpg$/ ) {
766 0           my $did = 0;
767 0 0 0       if ( $self->{npp} && $self->{im}->getheight > CANVAS_HEIGHT ) {
768             # Scale or split oversized pages.
769 0           if ( 1 ) {
770 0 0         warn("Scaling output...\n") if $self->{verbose};
771             $self->{im} =
772 0           $self->{im}->scale( xpixels => CANVAS_WIDTH,
773             ypixels => CANVAS_HEIGHT,
774             type => 'nonprop' );
775             }
776             else {
777             my $x = $self->{im}->getheight;
778             my $y = 0;
779             my $p = 1;
780             while ( $x > 0 ) {
781             my $im = Imager->new( xsize => CANVAS_WIDTH,
782             ysize => CANVAS_HEIGHT,
783             model => 'rgb' );
784             $im->box( filled => 1 );
785             $im->paste( src => $self->{im},
786             src_minx => 0, src_miny => $y,
787             src_maxx => CANVAS_WIDTH,
788             src_maxy => $y + CANVAS_HEIGHT );
789             $x -= CANVAS_HEIGHT;
790             $y += CANVAS_HEIGHT;
791             my $of = $self->{output};
792             $of = sprintf( $of, $self->{songix} ) if $of =~ /\%\d*d/;
793             $of =~ s/\%t/$song->{title}/e;
794             $of =~ s/(\.\w+)$/sprintf("-%02d%s",$p,$1)/e;
795             $im->write( file => encode_utf8($of),
796             type => $self->{outtype} );
797             warn( "Wrote: $of\n" ) if $self->{verbose};
798             $p++;
799             }
800             $did++;
801             }
802             }
803 0 0         unless ( $did ) {
804 0           my $of = $self->{output};
805 0           my $ot = $self->{outtype};
806 0 0         $ot = "jpeg" if $ot eq "jpg";
807 0 0         $of = sprintf( $of, $self->{songix} ) if $of =~ /\%\d*d/;
808 0           $of =~ s/\%t/$song->{title}/e;
  0            
809 0           $self->{im}->write( file => encode_utf8($of),
810             type => $ot );
811 0 0         warn( "Wrote: $of\n" ) if $self->{verbose};
812             }
813             }
814              
815             # Return number of pages actually produced.
816             # This will always be 1 unless generating PDF.
817 0           $song->{pages} = $self->{pages};
818              
819             # }}}
820             }
821              
822             # Draw a chord, with potentially a bass note.
823             sub chord {
824 0     0 0   my ( $self, $x, $y, $c, $size, $font ) = @_;
825 0   0       $font ||= $self->{chordfont};
826 0   0       $size ||= $self->{chordsize};
827 0           $c =~ s/\*(.*?)\*/$1/;
828             # $c =~ s/-/m/;
829 0           my $bass;
830 0 0         if ( $c =~ m;(.*?)/(.*); ) {
831 0           $bass = $2;
832 0           $c = $1;
833             }
834              
835 0           my $one = 0.05*$size;
836 0           $y += $one;
837              
838 0           my @c = split ( //, $c );
839 0           my $root = shift(@c);
840 0 0         $root = " " if $root eq "W";
841 0 0 0       if ( $root eq "N" and $c[0] eq "C" ) {
842 0           shift(@c);
843 0           $x += $self->textl( $x, $y, "N.C.", $size, $self->{chrdfont} );
844             }
845             else {
846 0           $x += $self->textl( $x, $y, $root, 1.2*$size, $font );
847             }
848              
849 0 0         if ( @c ) {
850 0 0         if ( $c[0] eq "b" ) {
    0          
851 0           shift(@c);
852             $self->textl( $x+$one, $y-0.6*$size,
853             $self->{musicglyphs}->{flat},
854 0           $size, $self->{musicfont} );
855             }
856             elsif ( $c[0] eq "#" ) {
857 0           shift(@c);
858             $self->textl( $x+$one, $y-0.7*$size,
859             $self->{musicglyphs}->{sharp},
860 0           0.9*$size, $self->{musicfont} );
861             }
862             }
863              
864 0           while ( @c ) {
865 0           my $c = shift(@c);
866 0 0         if ( $c eq "b" ) {
    0          
    0          
    0          
    0          
    0          
    0          
867 0           $x += $self->glyphl( $x+$one, $y-0.15*$size, "flat", 0.8*$size );
868             }
869             elsif ( $c eq "#" ) {
870 0           $x += $self->glyphl( $x, $y-0.15*$size, "sharp", 0.6*$size );
871             }
872             elsif ( $c =~ /\d/ ) {
873 0           $x += $self->textl( $x, $y+0.1*$size, $c, 0.7*$size, $font );
874             }
875             elsif ( $c eq "^" ) {
876             $x += $self->textl( $x, $y,
877             $self->{musicglyphs}->{csymMajorSeventh},
878 0           0.8*$size, $self->{muscfont} );
879             }
880             elsif ( $c eq "o" ) {
881             $x += $self->textl( $x, $y,
882             $self->{musicglyphs}->{csymDiminished},
883 0           0.8*$size, $self->{muscfont} );
884             }
885             elsif ( $c eq "h" ) {
886             $x += $self->textl( $x, $y,
887             $self->{musicglyphs}->{csymHalfDiminished},
888 0           0.8*$size, $self->{muscfont} );
889             }
890             elsif ( $c eq "-" ) {
891             $x += $self->textl( $x, $y,
892             $self->{musicglyphs}->{csymMinor},
893 0           0.8*$size, $self->{muscfont} );
894             }
895             else {
896             $x += $self->textl( $x, $y+$one+$one, $c,
897 0           0.7*$size, $self->{chrdfont} );
898             }
899             }
900 0 0         return unless $bass;
901 0           my $w = $self->aw( $font, 0.9*$size, "/");
902 0           $x -= $w/3;
903 0           $y += 0.3*$size;
904 0           $self->textl( $x, $y, "/", 0.9*$size, $font );
905 0           $x += $w;
906 0           $y += 0.2*$size;
907 0           $self->chord( $x-$one, $y, $bass, 0.6*$size, $font );
908             }
909              
910             sub font_bl {
911 0     0 0   my ( $font, $size ) = @_;
912 0           $size / ( 1 - $font->descender / $font->ascender );
913             }
914              
915             sub toc {
916 0     0 0   my ( $self, $plname, $book ) = @_;
917              
918 0           my $pages;
919 0           my $textfont = $self->{textfont};
920 0           my $textsize = 10;
921              
922 0           my $lm = 40;
923 0           my $rm = PAGE_WIDTH - $lm;
924 0           my $bm = PAGE_HEIGHT - 50;
925 0           my $tm = 80;
926 0           my $x;
927 0           my $y = PAGE_HEIGHT; # force page break
928 0           my $dy = 1.2 * $textsize;
929 0           my $yb = font_bl( $textfont, $textsize );
930              
931 0           $pages = 0;
932              
933             # Draw headings for a new page.
934             my $newpage = sub {
935 0     0     $self->newpage;
936 0           $pages++;
937 0 0         if ( $pages == 1 ) {
938 0           my $titlesize = $self->{titlesize};
939 0           my $titlefont = $self->{titlefont};
940 0   0       $self->textc( ($lm+$rm)/2, $tm-25,
941             $plname || "Table of Contents",
942             $titlesize, $titlefont );
943             }
944 0           $x = $lm;
945 0           $y = $tm;
946 0           };
947              
948 0           foreach my $b ( @$book ) {
949              
950 0 0         if ( $y > $bm ) {
951 0           $newpage->();
952             }
953 0           $self->textr( $x+25, $y+$yb, $b->[1].".", $textsize, $textfont );
954 0           $self->textl( $x+30, $y+$yb, $b->[0], $textsize, $textfont );
955 0           my $ann = $self->{page}->annotation;
956 0           $ann->link($self->{pdf}->openpage($b->[1]));
957 0           $ann->rect( $x, PAGE_HEIGHT - $y,
958             $x+25+$self->aw( $textfont, $textsize, $b->[0] ),
959             PAGE_HEIGHT - ( $y + $textsize ) );
960 0           $y += $dy;
961             }
962              
963 0           return $pages;
964             }
965              
966             # New page.
967             sub newpage {
968 0     0 0   my ( $self ) = @_;
969 0           $self->{pages}++;
970              
971 0 0         if ( $self->{im} ) {
972             # Start with a white page.
973 0           $self->{im}->box( filled => 1 );
974             }
975              
976 0 0         if ( $self->{pdf} ) {
977 0           $self->{page} = $self->{pdf}->page;
978 0           $self->{text} = $self->{page}->text;
979             }
980             };
981              
982             ################ Low level graphics ################
983              
984             # String width.
985             sub aw {
986 0     0 0   my ( $self, $font, $size, $t ) = @_;
987 0 0         if ( $self->{im} ) {
988 0           my @w = $font->bounding_box( size => $size, string => $t );
989             # ($neg_width,
990             # $global_descent,
991             # $pos_width,
992             # $global_ascent,
993             # $descent,
994             # $ascent,
995             # $advance_width,
996             # $right_bearing)
997 0           return $w[6];
998             }
999 0 0         if ( $self->{pdf} ) {
1000 0           $self->{text}->font( $font, $size );
1001 0           return $self->{text}->advancewidth($t);
1002             }
1003             }
1004              
1005             # Draw text, left aligned.
1006             sub textl {
1007 0     0 0   my ( $self, $x, $y, $t, $size, $font, $col, $lcr ) = @_;
1008 0   0       $size ||= $self->{musicsize};
1009 0   0       $font ||= $self->{textfont};
1010 0   0       $col ||= $black;
1011 0   0       $lcr ||= 'l';
1012              
1013 0           my $w = $self->aw( $font, $size, $t );
1014 0 0         $x -= $w/2 if $lcr eq 'c';
1015 0 0         $x -= $w if $lcr eq 'r';
1016              
1017 0 0         if ( $self->{im} ) {
1018 0           $_ = scale($_) for $x, $y, $size;
1019 0           $self->{im}->string( font => $font, size => $size, aa => 1,
1020             color => $col, x => $x, y => $y, text => $t );
1021             }
1022 0 0         if ( $self->{pdf} ) {
1023 0           for ( $self->{text} ) {
1024 0           $_->translate( $x, PAGE_HEIGHT-$y );
1025 0 0         $_->fillcolor($col) if $col ne $black;
1026 0           $_->text($t);
1027 0 0         $_->fillcolor($black) if $col ne $black;
1028             }
1029             }
1030 0           $w;
1031             };
1032              
1033             # Draw text, centered.
1034             sub textc {
1035 0     0 0   my ( $self, $x, $y, $t, $size, $font, $col, $lcr ) = @_;
1036 0   0       $lcr ||= 'c';
1037 0           $self->textl( $x, $y, $t, $size, $font, $col, $lcr );
1038             };
1039              
1040             # Draw text, right aligned.
1041             sub textr {
1042 0     0 0   my ( $self, $x, $y, $t, $size, $font, $col, $lcr ) = @_;
1043 0   0       $lcr ||= 'r';
1044 0           $self->textl( $x, $y, $t, $size, $font, $col, $lcr );
1045             };
1046              
1047             # Draw music glyph, centered.
1048             sub glyphc {
1049 0     0 0   my ( $self, $x, $y, $smc, $size, $col ) = @_;
1050 0   0       $size ||= $self->{musicsize};
1051 0 0         die("Unknown glyph: $smc") unless exists $self->{musicglyphs}->{$smc};
1052             $self->textc( $x, $y+0.15*$self->{musicsize},
1053             $self->{musicglyphs}->{$smc}, $size,
1054 0           $self->{musicfont}, $col );
1055             };
1056              
1057             # Draw music glyph, left aligned.
1058             sub glyphl {
1059 0     0 0   my ( $self, $x, $y, $smc, $size, $col ) = @_;
1060 0   0       $size ||= $self->{musicsize};
1061 0 0         die("Unknown glyph: $smc") unless exists $self->{musicglyphs}->{$smc};
1062             $self->textl( $x, $y+0.15*$self->{musicsize},
1063             $self->{musicglyphs}->{$smc}, $size,
1064 0           $self->{musicfont}, $col );
1065             };
1066              
1067             # Draw a line.
1068             sub line {
1069 0     0 0   my ( $self, $x1, $y1, $x2, $y2, $col ) = @_;
1070 0   0       $col ||= $black;
1071              
1072 0 0         if ( $self->{im} ) {
1073 0           $_ = scale($_) for $x1, $x2, $y1, $y2;
1074 0           $self->{im}->line( x1 => $x1, y1 => $y1,
1075             x2 => $x2, y2 => $y2,
1076             color => $col );
1077             }
1078 0 0         if ( $self->{pdf} ) {
1079 0           my $gfx = $self->{page}->gfx;
1080 0           $gfx->save;
1081 0           $gfx->strokecolor($col);
1082 0           $gfx->move( $x1, PAGE_HEIGHT-$y1 );
1083 0           $gfx->linewidth(1);
1084 0           $gfx->line( $x2, PAGE_HEIGHT-$y2 );
1085 0           $gfx->stroke;
1086 0           $gfx->restore;
1087             }
1088             }
1089              
1090             my %fontcache;
1091              
1092             # Setup fonts.
1093             sub initfonts {
1094 0     0 0   my ( $self, $size ) = @_;
1095 0   0       $size ||= 20;
1096              
1097             # Make font objects.
1098 0           my @fonts = qw( titlefont stitlefont textfont );
1099             push( @fonts, qw( chordfont chrdfont
1100             musicfont muscfont markfont ) )
1101 0 0         unless $self->{npp};
1102              
1103 0           for ( @fonts ) {
1104 0           my $ff = $self->{fontdir} . $fonts->{$_};
1105 0 0         unless ( -r $ff ) {
1106 0           my $msg = "$ff: $!\n";
1107             $msg .= "(Forgot to set environment variable FONTDIR?)\n"
1108 0 0 0       unless $ENV{FONTDIR} || $App::Packager::PACKAGED;
1109 0           die($msg);
1110             }
1111 0 0         if ( $self->{im} ) {
1112             $self->{$_} =
1113 0 0 0       $fontcache{$ff} ||= Imager::Font->new( file => $ff )
1114             or die( "$_: ", Imager->errstr );
1115             }
1116 0 0         if ( $self->{pdf} ) {
1117 0 0         unless ( $fontcache{$ff} ) {
1118 0 0         unless ( $fontcache{$ff} ) {
1119 0           my $f = $self->{pdf}->ttfont( $ff );
1120 0           $fontcache{$ff} = $f;
1121             }
1122 0           warn( "$ff: ", $fontcache{$ff}->glyphNum, " glyphs\n" )
1123             if FONTSX;
1124             }
1125 0           $self->{$_} = $fontcache{$ff};
1126             }
1127             }
1128              
1129 0           $self->{musicsize} = $size;
1130 0           $self->{chordsize} = $self->{musicsize};
1131 0           $self->{musicglyphs} = \%smufl;
1132 0 0         if ( $self->{npp} ) {
1133 0           $self->{titlesize} = 87;
1134 0           $self->{stitlesize} = 77;
1135             # Text is slightly wider??
1136 0           require Imager::Matrix2d;
1137             # Don't scale the titlefont as well :)
1138 0           my $ff = $self->{fontdir} . $fonts->{textfont};
1139 0           $self->{textfont} = Imager::Font->new( file => $ff);
1140 0           $self->{textfont}->transform(matrix=>Imager::Matrix2d->scale(x=>1.05,y=>1));
1141             }
1142             else {
1143 0           $self->{titlesize} = $self->{musicsize};
1144 0           $self->{stitlesize} = 0.85 * $self->{titlesize};
1145             }
1146              
1147             }
1148              
1149             ################ NPP routines ################
1150              
1151             sub npp_chord {
1152 0     0 0   my ( $self, $x, $y, $c, $flags ) = @_;
1153 0           my ( $root, $quality, $bass ) = $self->xchord($c);
1154              
1155             # Flags: 0x00 normal
1156             # 0x01 condensed
1157             # 0x02 alternate
1158             # 0x03 condensed, alternate
1159             # condensed alternate is the same as alternate
1160              
1161 0 0         if ( $c eq "NC" ) {
1162 0           my $img = $self->getimg("root_nc");
1163 0           $x += 29;
1164 0           $y += 41;
1165 0 0         if ( $flags & CHORD_CONDENSED ) {
1166 0           $img = $img->scale( xscalefactor => 0.7, yscalefactor => 1 );
1167 0           $x -= 31;
1168             }
1169 0 0         if ( $flags & CHORD_ALTERNATIVE ) {
1170             # Need anything?
1171             }
1172 0           $self->{im}->rubthrough( src => $img,
1173             tx => $x, ty => $y );
1174 0           return;
1175             }
1176              
1177 0 0         if ( $c =~ /^(.+)\*(.*)\*(.*)$/ ) {
1178             $self->textl( $x + 85, $y + 168, $2,
1179 0           $self->{stitlesize}, $self->{stitlefont} );
1180 0           $c = $1.$3;
1181             }
1182 0           my $img = $self->chordimg( $c, $flags );
1183 0 0         if ( $flags & CHORD_ALTERNATIVE ) {
1184 0           $self->{im}->rubthrough( src => $img,
1185             tx => $x + 9, ty => $y - 98 );
1186             }
1187             else {
1188 0           $self->{im}->rubthrough( src => $img, tx => $x, ty => $y );
1189 0           return;
1190             }
1191              
1192             }
1193              
1194             sub xchord {
1195 0     0 0   my ( $self, $c ) = @_;
1196 0           my ( $root, $quality, $bass ) = ( "", "", "" );
1197 0           $c = lc($c);
1198 0 0         return ( "nc", "", "" ) if $c eq "nc";
1199              
1200 0 0         if ( $c =~ m;^(.*)/(.+)$; ) {
1201 0           $c = $1;
1202 0           $bass = $2;
1203             }
1204 0 0         if ( $c =~ m;^([a-gw][b#]?)(.*)$; ) {
1205 0           $root = $1;
1206 0           $quality = $2;
1207 0 0         if ( $quality =~ s/^-/m/ ) {
1208 0           $quality .= $self->{npp_minor};
1209             }
1210             }
1211 0           $quality =~ s/\#/x/g;
1212 0           $quality =~ s/\^/v/g;
1213 0           $quality =~ s/\+/p/g;
1214 0           ( $root, $quality, $bass );
1215             }
1216              
1217             my $im_bar;
1218              
1219             sub npp_bar {
1220 0     0 0   my ( $self, $x, $y, $bar ) = @_;
1221 0   0       $bar ||= "bar";
1222              
1223 0   0       $im_bar ||=
1224             {
1225             barlineSingle =>
1226             $self->getimg("single_barline")->scale( scalefactor => 2 ),
1227             barlineDouble =>
1228             $self->getimg("double_barline")->scale( scalefactor => 2 ),
1229             barlineFinal =>
1230             $self->getimg("double_barline_close")->scale( scalefactor => 2 ),
1231             repeatLeft =>
1232             $self->getimg("repeat_barline_open"),
1233             repeatRight =>
1234             $self->getimg("repeat_barline_close"),
1235             };
1236              
1237 0 0 0       $y -= 18 if $bar eq "repeatLeft" || $bar eq "repeatRight";
1238 0 0         $x -= 25 if $bar eq "repeatRight";
1239 0 0         $x -= 1 if $bar eq "repeatLeft";
1240 0 0         $x -= 7 if $bar eq "barlineDouble";
1241 0 0         $x -= 3 if $bar eq "barlineFinal";
1242 0 0         $x -= 9 if $bar eq "barlineSingle";
1243 0           my $w = $im_bar->{$bar}->getwidth;
1244 0           $self->{im}->rubthrough( src => $im_bar->{$bar},
1245             tx => $x - $w/2, ty => $y );
1246             }
1247              
1248             sub npp_sig {
1249 0     0 0   my ( $self, $x, $y, $s ) = @_;
1250 0           $self->{im}->rubthrough( src => $self->getimg("time_signature_$s")
1251             ->scale( xscalefactor => 0.95,
1252             yscalefactor => 1.05 ),
1253             tx => $x-63, ty => $y+12 );
1254             }
1255              
1256             sub npp_mark {
1257 0     0 0   my ( $self, $x, $y, $m ) = @_;
1258 0           $self->{im}->rubthrough( src => $self->getimg("rehearsal_mark_$m"),
1259             tx => $x-55, ty => $y-76 );
1260             }
1261              
1262             sub npp_repeat {
1263 0     0 0   my ( $self, $x, $y, $n ) = @_;
1264 0           my $r = $self->getimg("root_" . ("x" x $n));
1265 0           my $w = $r->getwidth;
1266 0           $self->{im}->rubthrough( src => $r,
1267             tx => $x - $w/2 - 8,
1268             ty => $y + 56 );
1269             }
1270              
1271             sub npp_ending {
1272 0     0 0   my ( $self, $x, $y, $n ) = @_;
1273 0 0         $n = $n == 1 ? "first"
    0          
    0          
1274             : $n == 2 ? "second"
1275             : $n == 3 ? "third"
1276             : "zero";
1277 0           $self->{im}->rubthrough( src => $self->getimg("ending_$n"),
1278             tx => $x, ty => $y - 72 );
1279             }
1280              
1281             sub npp_sign {
1282 0     0 0   my ( $self, $x, $y, $sign ) = @_;
1283 0 0         $x -= 16 if $sign eq "fermata";
1284 0           $self->{im}->rubthrough( src => $self->getimg($sign),
1285             tx => $x + 7, ty => $y - 88 );
1286             }
1287              
1288             sub npp_slash {
1289 0     0 0   my ( $self, $x, $y ) = @_;
1290 0           $self->{im}->rubthrough( src => $self->getimg("root_slash"),
1291             tx => $x, ty => $y );
1292             }
1293              
1294             my %npp_imgcache;
1295              
1296             sub getimg {
1297 0     0 0   my ( $self, $img ) = @_;
1298 0 0         return $npp_imgcache{$img} if $npp_imgcache{$img};
1299              
1300 0           my $if = $self->{prefab} . $img . ".png";
1301              
1302 0 0         if ( $self->{npp} eq "hand" ) {
1303             # iRealPro uses some non-hand symbols even though nice alternatives exist.
1304 0 0 0       unless ( $self->{npp_strict} && $img =~ /rehearsal_mark|root_nc|root_xx/ ) {
1305 0           $if =~ s/\.png/_hand.png/;
1306             }
1307 0 0         unless ( -s $if ) {
1308             # Fallback to non-hand symbols.
1309 0           $if =~ s/_hand\././;
1310             }
1311             }
1312              
1313 0           my $red = 0;
1314 0 0         unless ( -s $if ) {
1315 0           $red = 1;
1316 0           warn("Substituting for \"$img\"\n");
1317 0           $if = $self->{prefab} . "quality_h.png";
1318 0 0         $if =~ s/\.png/_hand.png/ if $self->{npp} eq "hand";
1319             }
1320 0 0         $npp_imgcache{$img} = Imager->new( file => $if )
1321             or die( Imager->errstr );
1322              
1323             # Some symbols are rendered in red.
1324 0   0       $red ||= $img =~ /^ (?:
1325             .*rehearsal_mark_. |
1326             repeat_barline_(?:open|close) |
1327             time_signature_.. |
1328             ending_.* |
1329             coda | fermata | segno
1330             ) $/x;
1331              
1332             # The barline images are white instead of black. Reverse.
1333 0 0         $npp_imgcache{$img}->map( all => [ reverse( 0..255) ], alpha => [] )
1334             if $img =~ /(single|double)_barline/;
1335              
1336             # Colourize if necessary.
1337 0 0         $npp_imgcache{$img}->map( red => [ reverse( 0..255) ] ) if $red;
1338              
1339 0           return $npp_imgcache{$img};
1340             }
1341              
1342             sub chordimg {
1343 0     0 0   my ( $self, $chord, $flags ) = @_;
1344 0           my ( $root, $quality, $bass ) = $self->xchord($chord);
1345              
1346 0   0       my $img = join( "|", "", $root, $quality||"", $bass||"",
      0        
1347             sprintf("%d", $flags), "" );
1348 0 0         return $npp_imgcache{$img} if $npp_imgcache{$img};
1349              
1350 0 0         my $im = Imager->new( xsize => 218,
1351             ysize => 262,
1352             model => 'rgba'
1353             ) or die( Imager->errstr );
1354              
1355 0           my $acc;
1356 0 0         ( $root, $acc ) = ( $1, $2 ) if $root =~ /^([a-gw])([b#x])$/;
1357 0 0         $acc = $acc eq 'b' ? "flat" : "sharp" if $acc;
    0          
1358              
1359 0 0         my $dx = $flags & CHORD_ALTERNATIVE ? 6 : 0;
1360 0 0         my $dy = $flags & CHORD_ALTERNATIVE ? -6 : 0;
1361 0           $im->rubthrough( src => $self->getimg("root_$root"),
1362             tx => 0, ty => 0 );
1363 0 0         $im->rubthrough( src => $self->getimg("root_$acc"),
1364             tx => 0, ty => 0 ) if $acc;
1365 0 0         $im->rubthrough( src => $self->getimg("quality_$quality"),
1366             tx => 84, ty => $dy+80 ) if $quality;
1367              
1368 0 0         if ( $bass ) {
1369              
1370 0           ( $root, $acc ) = $bass =~ /^([a-g])([b#x]?)$/;
1371 0 0         $acc = $acc eq 'b' ? "flat" : "sharp" if $acc;
    0          
1372              
1373 0 0         $dx = $flags & CHORD_CONDENSED ? 10 : 0;
1374 0 0         $dx += 50 if $flags & CHORD_ALTERNATIVE;
1375 0 0         $dy = $flags & CHORD_CONDENSED ? -5 : 0;
1376 0 0         $dy -= 5 if $flags & CHORD_ALTERNATIVE;
1377 0 0         my $sc = $flags & CHORD_CONDENSED ? 0.68 : 0.65;
1378 0 0         my $sc2 = $flags & CHORD_CONDENSED ? 0.58 : 0.55;
1379 0           $im->rubthrough( src => $self->getimg("root_$root")
1380             ->scale( scalefactor => $sc ),
1381             tx => $dx+65, ty => $dy+153 );
1382 0 0         $im->rubthrough( src => $self->getimg("root_$acc")
1383             ->scale( scalefactor => $sc ),
1384             tx => $dx+65, ty => $dy+177 ) if $acc;
1385 0           $im->rubthrough( src => $self->getimg("root_slash")
1386             ->scale( xscalefactor => 0.85,
1387             yscalefactor => $sc2 ),
1388             tx => $dx+0, ty => 142 );
1389             }
1390              
1391 0 0         $im = $im->scale( xscalefactor => 0.7, yscalefactor => 1 ) if $flags & CHORD_CONDENSED;
1392 0 0         $im = $im->scale( xscalefactor => 0.62, yscalefactor => 0.62 ) if $flags & CHORD_ALTERNATIVE;
1393              
1394 0           return $npp_imgcache{$img} = $im;
1395             }
1396              
1397             1;
1398              
1399             =begin experimental
1400              
1401             for ( "mpdfx.pl", "mpng.pl" ) {
1402             open( my $fd, "<", $_ );
1403             my $data = do { local $/; <$fd> };
1404             eval $data or die($@);
1405             }
1406              
1407             =end experimental
1408              
1409             =cut
1410              
1411             1;