App-Music-ChordPro

 view release on metacpan or  search on metacpan

lib/ChordPro/Output/PDF.pm  view on Meta::CPAN

#! perl

package main;

use utf8;
our $config;
our $options;

our $ps;
our $pr;
our $dw;

package ChordPro::Output::PDF;

use strict;
use warnings;
use File::Temp ();
use Ref::Util qw(is_hashref is_arrayref is_coderef);
use Carp;
use ChordPro::Output::Common qw( prep_outlines fmt_subst );
use feature 'signatures';

use ChordPro::Output::PDF::Song;
use ChordPro::Output::PDF::Writer;
use ChordPro::Files;
use ChordPro::Paths;
use ChordPro::Utils;

# Set by Configurator.
our $pdfapi;

use Text::Layout;
use List::Util qw(any);
use Unicode::Collate;

my $verbose = 0;

# For regression testing, run perl with PERL_HASH_SEED set to zero.
# This eliminates the arbitrary order of font definitions and triggers
# us to pinpoint some other data that would otherwise be varying.
my $regtest = defined($ENV{PERL_HASH_SEED}) && $ENV{PERL_HASH_SEED} == 0;

# Convenience.
*generate_song = \&ChordPro::Output::PDF::Song::generate_song;

sub generate_songbook {
    my ( $self, $sb ) = @_;

    return [] unless $sb->{songs}->[0]->{body}
                  || $sb->{songs}->[0]->{source}->{embedding};
    $verbose ||= $options->{verbose};


    $config->unlock;
    $ps = $config->{pdf};
    # use DDP; p $ps->{songbook}, as => "in PDF";
    my $pagectrl = $self->pagectrl;
    $config->lock;

    my $extra_matter = 0;
    if ( $options->{toc} // (@{$sb->{songs}} > 1) ) {
	for ( @{ $::config->{contents} } ) {
	    # Treat ToCs as one.
	    $extra_matter++, last unless $_->{omit};
	}
	$extra_matter++ if $options->{title};
    }
    $extra_matter++ if $pagectrl->{cover} && !$options->{title};
    $extra_matter++ if $pagectrl->{front_matter};
    $extra_matter++ if $pagectrl->{back_matter};
    $extra_matter++ if $options->{csv};

    # $prefill indicates that in 2page mode, a filler page is needed to
    # get the songs properly aligned.
    my $prefill = 0;
    if ( $pagectrl->{align_songs_spread} ) {
	$prefill = 1;
    }
    if ( $pagectrl->{sort_songs} ) {
	sort_songbook( $sb, $pagectrl );
    }
    if ( $pagectrl->{compact_songs} ) {
	$prefill = compact_songbook( $sb, $pagectrl );
	return unless defined $prefill; # cancelled
    }

    progress( phase   => "PDF",
	      index   => 0,
	      total   => scalar(@{$sb->{songs}}) );

    $pr = (__PACKAGE__."::Writer")->new( $ps, $pdfapi );
    warn("Generating PDF ", $options->{output} || "__new__.pdf", "...\n")
      if $options->{verbose};

    my $name = ::runtimeinfo("short");
    $name =~ s/version.*/regression testing/ if $regtest;
    my %info = ( Title => $options->{title} || $sb->{songs}->[0]->{meta}->{title}->[0],
		 Creator => $name );
    while ( my ( $k, $v ) = each %{ $ps->{info} } ) {

lib/ChordPro/Output/PDF.pm  view on Meta::CPAN

    my @cols;
    my $ncols;
    for ( @{ $ctl->{fields} } ) {
	next if $_->{omit};
	push( @cols, $rfc4180->($_->{name}) );
    }
    $ncols = @cols;
    #warn( "CSV: $ncols fields\n" );
    print $fd ( join( $sep, @cols ), "\n" );

    # Extra meta info from command line, for non-song CSV.
    my $xm = $options->{meta} // {};
    unless ( $ctl->{songsonly} ) {
	$csvline->( { %$xm,
		      title     => 'Cover',
		      pagerange => $pagerange->("cover"),
		      sorttitle => 'Cover',
		      artist    => 'ChordPro' } )
	  if $pages_of->{cover};
	$csvline->( { %$xm,
		      title     => 'Front Matter',
		      pagerange => $pagerange->("front"),
		      sorttitle => 'Front Matter',
		      artist    => 'ChordPro' } )
	  if $pages_of->{front};
	$csvline->( { %$xm,
		      title     => "Table of Contents",
		      pagerange => $pagerange->("toc"),
		      sorttitle => "Table of Contents",
		      artist    => 'ChordPro' } )
	  if $pages_of->{toc};
    }

    warn( "CSV: ", scalar(@$book), " songs in book\n")
      if $config->{debug}->{csv};
    for ( my $p = 0; $p < @$book-1; $p++ ) {
	my ( $title, $song ) = @{$book->[$p]};
	my $page = $start_of->{songbook} + $song->{meta}->{tocpage} - 1;
	my $pp = $song->{meta}->{pages};
	my $m = { %{$song->{meta}},
		  pagerange => [ $pagerange->($pp, $page) ] };
	$csvline->($m);
    }

    unless ( $ctl->{songsonly} ) {
	$csvline->( { %$xm,
		      title     => 'Back Matter',
		      pagerange => $pagerange->("back"),
		      sorttitle => 'Back Matter',
		      artist    => 'ChordPro'} )
	  if $pages_of->{back};
    }
    close($fd);
    warn("Generated CSV...\n")
      if  $config->{debug}->{csv} || $options->{verbose};
}

################ ################

sub _dump {
    return unless $config->{debug}->{fonts};
    my ( $ps ) = @_;
    print STDERR ("== Font family map\n");
    Text::Layout::FontConfig->new->_dump if $verbose;
    print STDERR ("== Font associations\n");
    foreach my $f ( sort keys( %{$ps->{fonts}} ) ) {
	printf STDERR ("%-15s  %s\n", $f,
		       eval { $ps->{fonts}->{$f}->{description} } ||
		       eval { $ps->{fonts}->{$f}->{file} } ||
		       eval { "[".$ps->{fonts}->{$f}->{name}."]" } ||
		       "[]"
		      );
    }
}

# Derive new style page controls from old style.
sub pagectrl {
    my ( $self ) = @_;

    # If at this point we still have old style page controls,
    # they were passed via command line and thus override.
    # $config->migrate_songbook_pagectrl;

    my $sb = $config->{pdf}->{songbook};
    my $pagectrl = { dual_pages		 => $sb->{'dual-pages'},
		     align_tocs		 => $sb->{'align-tocs'},
		     align_songs	 => $sb->{'align-songs'},
		     align_songs_spread	 => $sb->{'align-songs-spread'},
		     align_songs_extend	 => $sb->{'align-songs-extend'},
		     sort_songs		 => $sb->{'sort-songs'},
		     compact_songs	 => $sb->{'compact-songs'},
		     cover		 => $sb->{cover},
		     front_matter	 => $sb->{'front-matter'},
		     back_matter	 => $sb->{'back-matter'},
		 };

    unless ( $pagectrl->{dual_pages} ) {
	$pagectrl->{align_songs} = 0;
	$pagectrl->{align_tocs} = 0;
    }
    unless ( $pagectrl->{align_songs} ) {
	$pagectrl->{$_} = 0
	  for qw( align_songs_spread align_songs_extend compact_songs);
    }
    for ( qw( cover front_matter back_matter ) ) {
	$pagectrl->{$_} = undef unless is_true($pagectrl->{$_});
    }
    if ( $config->{debug}->{pagectrl} ) {
	use DDP; p $pagectrl, as => "pagectrl";
    }
    return $pagectrl;
}

sub pagectrl_msg {
    my ( $pagectrl ) = @_;
    my $msg = $pagectrl->{dual_pages} ? "dual" : "single";
    if ( $pagectrl->{align_tocs} ) {
	$msg .= ", align_tocs";
	$msg .= "_song" if $pagectrl->{align_tocs} eq "song";
    }
    if ( $pagectrl->{align_songs} ) {
	$msg .= ", align_songs";
	$msg .= ", extend" if $pagectrl->{align_songs_extend};
	$msg .= ", spread" if $pagectrl->{align_songs_spread};
    }
    $msg .= ", " . $pagectrl->{sort_songs} if $pagectrl->{sort_songs};

    return $msg;
}

lib/ChordPro/Output/PDF.pm  view on Meta::CPAN

		$fragment->{design_scale} ||= 1;
		$fragment->{design_scale} *= $o->{vwidth}/$o->{width};
	    }
	}
	elsif ( $fragment->{builtin} ) {
	    my $i = $fragment->{builtin};
	    if ( $i =~ /^alert(?:\(([\d.]+)\))?$/ ) {
		$xo = alert( $1 || $fragment->{size} );
	    }
	    else {
		warn("Unknown builtin image in <img>: $i\n");
		$xo = alert( $fragment->{size} );
	    }
	}
	elsif ( $fragment->{chord} ) {
	    my $info = ChordPro::Chords::known_chord($fragment->{chord});
	    unless ( $info ) {
		warn("Unknown chord in <img>: $fragment->{chord}\n");
		$xo = alert( $fragment->{size} );
	    }
	    else {
		my $type = $fragment->{instrument} // $config->{instrument}->{type};
		my $p = ChordPro::Output::PDF::diagrammer($type);
		$xo = $p->diagram_xo($info);
	    }
	}
	$xo // $self->SUPER::getimage($fragment) // alert( $fragment->{size} );
    };
}

sub alert ($size) {
    my $scale = $size/20;
    my $xo = $pr->{pdf}->xo_form;
    $xo->bbox( 0, -18*$scale, 20*$scale, 0 );
    $xo->matrix( $scale, 0, 0, -$scale, 0, 0 );
    $xo->line_width(2)->line_join(1);
    $xo->stroke_color("red");
    $xo->fill_color("red");
    $xo->move( 1, 17 )->polyline( 19, 17, 10, 1 )->close->stroke;
    $xo->rectangle( 9, 13, 11, 15 );
    $xo->move( 9, 12 )->polyline( 8.5, 7, 11.5, 7, 11, 12 )->close->fill;
    return $xo;
}

class TextLayoutSymbolElement :does(Text::Layout::ElementRole);

use ChordPro::Utils qw(parse_kv);
use ChordPro::Symbols;

field $glyphs;

BUILD {
    $glyphs = ChordPro::Symbols::symbols();
};

method parse( $ctx, $k, $v ) {
    my $kv = parse_kv($v);
    my $res =
      { %$ctx,
	type => "text",
	font => Text::Layout::FontConfig->from_string("ChordProSymbols"),
      };

    while ( ( $k,$v) = each(%$kv) ) {
	$res->{$k} = $v, next
	  if $k =~ /^(size|color|bgcolor|href)$/;
	$res->{text} = $glyphs->{$k}, next if defined $glyphs->{$k};
	warn("Unknown attribute in <sym>: $k (ignored)\n");
    }

    return $res;
}

# These methods must be defined for the role, but will not be used.
method render( $hash, $gfx, $x, $y ) {}
method bbox( $hash ) {}

1;



( run in 0.811 second using v1.01-cache-2.11-cpan-5735350b133 )