App-Music-ChordPro

 view release on metacpan or  search on metacpan

Changes  view on Meta::CPAN

    - !Bugfixes
    - Raise requirement for Text::Layout to 0.019.
    - Use only 'name' for chords in the built-in config. 'description'
      cannot be overridden by the user with 'name'.
    - Fix page numbers in CSV.
    - Several fixes for font descriptions and sizes.
    
0.976	2020-08-16

    - !Bugfixes
    - Fix markup defragmentation (#111)
    - Fix page numbers in CSV.
    - Fix problem that --no-toc was not honoured.
    - Fix a2crd crash (#115)

0.975	2020-08-13

    - !ChordPro syntax
    - Support Pango style markup language.
    - Add basic support for annotations.
    - Add directives start/end_of_verse/bridge and short forms.

lib/ChordPro.pm  view on Meta::CPAN

          "encoding=s",
	  "csv!",			# Generates contents CSV
	  "front-matter=s",		# Front matter page(s)
	  "cover=s",			# Cover page(s)
	  "back-matter|back=s",		# Back matter page(s)
	  "filelist=s@",		# List of input files
	  "title=s",			# Title (for books)
	  "subtitle=s",			# Subtitle (for books)
	  "meta=s\%",			# Command line meta data
	  "decapo",			# remove capo
	  "fragment|F",			# partial (incomplete) song
	  "strict!",			# strict conformance

	  ### Experimental ###

	  "progress_callback=s",

	  ### Standard Chordii Options ###

          "about|A+" => \$about,        # About...
          "chord-font|C=s",             # Sets chord font

lib/ChordPro.pm  view on Meta::CPAN

    --about  -A                   About ChordPro...
    --back-matter=FILE            Add back matter pages from PDF document
    --config=JSON  --cfg          Config definitions (multiple)
    --cover=FILE                  Add cover pages from PDF document
    --crd                         Input is text, not ChordPro
    --csv                         (with PDF) Also generate CSV
    --decapo                      Eliminate capo settings
    --diagrams=WHICH		  Prints chord diagrams
    --encoding=ENC                Encoding for input files (UTF-8)
    --filelist=FILE               Reads song file names from FILE
    --fragment -F                 Partial (incomplete) song
    --front-matter=FILE           Add front matter pages from PDF document
    --lyrics-only  -l             Only prints lyrics
    --meta KEY=VALUE              Add meta data
    --output=FILE  -o             Saves the output to FILE
    --[no]strict                  Strict conformance
    --start-page-number=N  -p     Starting page number [1]
    --subtitle=VALUE		  Use with --title for a nice subtitle.
    --title=VALUE		  Adds a nice cover page using this title.
    --toc --notoc -i              Generates/suppresses a table of contents
    --transcode=SYS  -xc          Transcodes to notation system

lib/ChordPro/A2Crd.pm  view on Meta::CPAN

    return '' if $line eq '' ;
    return "{comment: " . $line . "}" ;
}

# Process the lines via the map.
my $infer_titles;
sub maplines {
    my ( $map, $lines ) = @_;
    my @out;
    $infer_titles = $config->{a2crd}->{'infer-titles'}
      && !$options->{fragment};

    # Preamble.
    # Pass empty lines.

    print STDERR  "====== _C =====\n" if $local_debug ;
    print STDERR "MAP: \'$map\' \n" if $local_debug ;

    while ( $map =~ s/^([_C])// ) {
	print STDERR "$1 == @{$lines}[0]\n" if $local_debug ;
	# simply output blank or comment lines at the start of the file

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

    $self->{_currentcolor} = 'black';
    $self->{_currentsize} = 12;
    $self;
}

*html = \&ChordPro::Output::HTML::html;

sub render {
    my ( $self ) = @_;
    my $res = "";
    foreach my $fragment ( @{ $self->{_content} } ) {
	if ( $fragment->{type} eq 'strut' ) {
	    next unless length($fragment->{label}//"");
	    $res .= "<span id=\"".$fragment->{label}."\"/>";
	    next;
	}
	next unless length($fragment->{text});
	my $f = $fragment->{font} || $self->{_currentfont};
	my @c;			# styles
	my @d;			# decorations
	if ( $f->{style} eq "italic" ) {
	    push( @c, q{font-style:italic} );
	}
	if ( $f->{weight} eq "bold" ) {
	    push( @c, q{font-weight:bold} );
	}
	if ( $fragment->{color} && $fragment->{color} ne $self->{_currentcolor} ) {
	    push( @c, join(":","color",$fragment->{color}) );
	}
	if ( $fragment->{size} && $fragment->{size} ne $self->{_currentsize} ) {
	    push( @c, join(":","font-size",$fragment->{size}) );
	}
	if ( $fragment->{bgcolor} ) {
	    push( @c, join(":","background-color",$fragment->{bgcolor}) );
	}
	if ( $fragment->{underline} ) {
	    push( @d, q{underline} );
	}
	if ( $fragment->{strikethrough} ) {
	    push( @d, q{line-through} );
	}
	push( @c, "text-decoration-line:@d" ) if @d;
	my $href = $fragment->{href} // "";
	$res .= "<a href=\"".html($href)."\">" if length($href);
	$res .= "<span$href style=\"" . join(";",@c) . "\">" if @c;
	$res .= html(fq($fragment->{text}));
	$res .= "</span>" if @c;
	$res .= "</a>" if length($href);
    }
    $res;
}

1;

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


# sub new {
#     my ( $pkg, @data ) = @_;
#     my $self = $pkg->SUPER::new;
#     $self;
# }

# sub render {
#     my ( $self ) = @_;
#     my $res = "";
#     foreach my $fragment ( @{ $self->{_content} } ) {
# 	next unless length($fragment->{text});
# 	$res .= $fragment->{text};
#     }
#     $res;
# }


1;
# @todo 
# sub line_rechorus {
#     my ( $lineobject ) = @_;
	    # if ( $rechorus->{quote} ) {

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

    elsif ( $ctl{id} ) {
	my $a = ChordPro::Output::PDF::Song::assets($ctl{id});
	if ( $a && $a->{opts}->{base} ) {
	    $ctl{base} = $a->{opts}->{base};
	}
    }

    return \%ctl;
}

method getimage ($fragment) {
    $fragment->{_img} //= do {
	my $xo;
	if ( $fragment->{id} ) {
	    my $o = ChordPro::Output::PDF::Song::assets($fragment->{id});
	    $xo = $o->{data} if $o;
	    unless ( $o && $xo ) {
		warn("Unknown image ID in <img>: $fragment->{id}\n")
		  unless $fragment->{id} eq "__ERROR__";
		$xo = alert( $fragment->{size} );
	    }
	    $fragment->{design_scale} = $o->{opts}->{scale};
	    if ( $o->{width} && $o->{vwidth} ) {
		$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");

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

			     $y, $font );
	}
	else {
	    $ps->{pr}->text( $label,
			     $x - $ps->{_indent}, $y, $font );
	}
	$y -= $font->{size} * 1.2;
    }
}

# Propagate markup entries over the fragments so that each fragment
# is properly terminated.
sub defrag {
    my ( $frag ) = @_;
    my @stack;
    my @res;

    foreach my $f ( @$frag ) {
	my @a = split( /(<.*?>)/, $f );
	if ( @stack ) {
	    unshift( @a, @stack );
	    @stack = ();
	}
	my @r;
	foreach my $a ( @a ) {
	    if ( $a =~ m;^<\s*/\s*(\w+)(.*)>$; ) {
		my $k = $1;
		#$a =~ s/\b //g;
		#$a =~ s/ \b//g;
		if ( @stack ) {
		    if ( $stack[-1] =~ /^<\s*$k\b/ ) {
			pop(@stack);
		    }
		    else {
			warn("Markup error: \"@$frag\"\n",
			     "  Closing <$k> but $stack[-1] is open\n");
			next;
		    }
		}
		else {
		    warn("Markup error: \"@$frag\"\n",
			 "  Closing <$k> but no markup is open\n");
		    next;
		}
	    }
	    elsif ( $a =~ m;^<\s*(\w+)(.*)>$; ) {
		my $k = $1;
		my $v = $2;
		# Do not push if self-closed.
		push( @stack, "<$k$v>" ) unless $v =~ m;/\s*$;;
	    }
	    push( @r, $a );
	}
	if ( @stack ) {
	    push( @r, map { my $t = $_;
			    $t =~ s;^<\s*(\w+).*;</$1>;;
			    $t; } reverse @stack );
	}
	push( @res, join("", @r ) );
    }
    if ( @stack ) {
	warn("Markup error: \"@$frag\"\n",
	     "  Unclosed markup: @{[ reverse @stack ]}\n" );
    }
    #warn("defrag: ", join('', @res), "\n");
    \@res;
}

sub songline {
    my ( $elt, $x, $ytop, $ps, %opts ) = @_;

    # songline draws text in boxes as follows:
    #
    # +------------------------------
    # |  C   F    G

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

    # can be used. E.g., a font of size 16 with descender -250 and
    # ascender 750 must be drawn at 12 points under $ytop.

    my $pr    = $ps->{pr};
    my $fonts = $ps->{fonts};

    my $type   = $elt->{type};

    my $ftext;
    my $ytext;
    my @phrases = @{ defrag( $elt->{phrases} ) };

    if ( $type =~ /^comment/ ) {
	$ftext = $elt->{font} || $fonts->{$type} || $fonts->{comment};
	$ytext  = $ytop - $pr->font_bl($ftext);
	my $song   = $opts{song};
	$x += $opts{indent} if $opts{indent};
	$x += $elt->{indent} if $elt->{indent};
	pr_label_maybe( $ps, $x, $ytext );
	my $t = $elt->{text};
	if ( $elt->{chords} ) {

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

    my $f = sub {
	my ( $tpl, $p ) = @_;
	my $vsp;
	for my $text ( split( /\\n|\n/, $tpl ) ) {
	    $lines++;
	    # Suppress unclosed markup warnings.
	    local $SIG{__WARN__} = sub{
		CORE::warn(@_) unless "@_" =~ /Unclosed markup/;
	    };
	    # Get the part that fits (hopefully, all) and print.
	    ( $text, my $ex ) = @{ defrag( [ $pr->wrap( $text, $ww ) ] ) };
	    $pr->text( $text, $x, $y );
	    unless ($vsp) {
		$ps->{pr}->text( $p, $ps->{__rightmargin} - $pw, $y );
		$vsp = _vsp("toc", $ps);
		$x += $pr->strwidth( $config->{settings}->{wrapindent} )
		  if $ex ne "";
	    }
	    $y -= $vsp;
	    if ( $ex ne "" ) {
		$text = $ex;

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

    # Return updated baseline.
    return $y;
}

sub wrap {
    my ( $pr, $elt, $x ) = @_;
    return [ $elt ] unless $::config->{settings}->{wraplines};

    my $res = [];
    my @chords  = @{ $elt->{chords} // [] };
    my @phrases = @{ defrag( $elt->{phrases} // [] ) };
    my @rchords;
    my @rphrases;
    my $m = $pr->{ps}->{__rightmargin};
    my $wi = $pr->strwidth( $config->{settings}->{wrapindent},
			    $pr->{ps}->{fonts}->{text} );
    #warn("WRAP x=$x rm=$m w=", $m - $x, "\n");

    while ( @chords ) {
	my $chord  = shift(@chords);
	my $phrase = shift(@phrases) // "";

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


sub new {
    my ( $pkg, @data ) = @_;
    my $self = $pkg->SUPER::new;
    $self;
}

sub render {
    my ( $self ) = @_;
    my $res = "";
    foreach my $fragment ( @{ $self->{_content} } ) {
	next unless length($fragment->{text});
	$res .= fq($fragment->{text});
    }
    $res;
}

1;

lib/ChordPro/Song.pm  view on Meta::CPAN

	    push( @{ $meta->{$_} }, @$v );
	}
	else {
	    $meta->{$_} = $v;
	}
    }

    $xpose = parse_transpose(0);
    $no_transpose = $options->{'no-transpose'};
    $no_substitute = $options->{'no-substitute'};
    my $fragment = $options->{fragment};
    my $target = $config->{settings}->{transcode};
    if ( $target ) {
	unless ( ChordPro::Chords::Parser->have_parser($target) ) {
	    if ( my $file = CP->findres("config/notes/$target.json") ) {
		for ( ChordPro::Config::get_config($file) ) {
		    my $new = $config->hmerge($_);
		    local $config = $new;
		    ChordPro::Chords::Parser->new($new);
		}
	    }

lib/ChordPro/Song.pm  view on Meta::CPAN

		}
		next;
	    }

	    # Currently the ChordPro backend is the only one that
	    # cares about comment lines.
	    # Collect pre-title stuff separately.
	    next unless exists($config->{$backend})
	      && beo( $config->{$backend}, 'comments') eq "retain";

	    if ( exists $self->{title} || $fragment ) {
		$self->add( type => "ignore", text => $_ );
	    }
	    else {
		push( @{ $self->{preamble} }, $_ );
	    }
	    next;
	}

	# Tab content goes literally.
	if ( $in_context eq "tab" ) {

lib/ChordPro/Song.pm  view on Meta::CPAN

		$config->{debug}->{pp} && warn("PRE:  ", $_, "\n");
		$prep->{directive}->($dir);
		$config->{debug}->{pp} && warn("POST: {", $dir, "}\n");
	    }
	    $self->add( type => "ignore",
			text => $_ )
	      unless $self->directive($dir);
	    next;
	}

	if ( /\S/ && !$fragment && !exists $self->{title} ) {
	    do_warn("Missing {title} -- prepare for surprising results");
	    unshift( @$lines, "{title:$_}");
	    $skipcnt++;
	    next;
	}

	if ( $in_context eq "tab" ) {
	    $self->add( type => "tabline", text => $_ );
	    warn("OOPS");
	    next;

lib/ChordPro/Song.pm  view on Meta::CPAN

		    $prev->{phrases}->[0] .= " " . $this->{phrases}->[0];
		}
		else {
		    $self->add( type => "songline", %$this );
		}
	    }
	    else {
		$self->add( type => "songline", $self->decompose($_) );
	    }
	}
	elsif ( exists $self->{title} || $fragment ) {
	    $self->add( type => "empty" );
	}
	else {
	    # Collect pre-title stuff separately.
	    push( @{ $self->{preamble} }, $_ );
	}
    }
    do_warn("Unterminated context in song: $in_context")
      if $in_context;

lib/ChordPro/Songbook.pm  view on Meta::CPAN

    }

    # fs_load sets $opts->{_filesource}.
    $opts->{fail} = "soft";
    my $lines = is_arrayref($filename) ? $filename
      : fs_load( $filename, $opts );
    die( $filename, ": ", $opts->{error}, "\n" ) if $opts->{error};

    # Sense crd input and convert if necessary.
    if ( !(defined($options->{a2crd}) && !$options->{a2crd}) and
	 !$options->{fragment}
	 and any { /\S/ } @$lines	# non-blank lines
	 and $options->{crd} || !any { /^{\s*\w+/ } @$lines ) {
	warn("Converting $filename to ChordPro format\n")
	  if $options->{verbose} || !($options->{a2crd}||$options->{crd});
	require ChordPro::A2Crd;
	$lines = ChordPro::A2Crd::a2crd( { lines => $lines } );
    }

    $opts //= {};

lib/ChordPro/Wx/EditorPanel.pm  view on Meta::CPAN

    my $ctrl = $self->{t_editor};
    my ( $from, $to ) = $ctrl->GetSelection;
    my $have_selection = $from != $to;
    $ctrl->ConvertEOLs(wxSTC_EOL_LF);
    my $text = $have_selection ? $ctrl->GetSelectedText : $ctrl->GetText;

    require ChordPro::A2Crd;
    $::options->{nosysconfig} = 1;
    $::options->{nouserconfig} = 1;
    $::options->{noconfig} = 1;
    $::options->{fragment} = $have_selection;

    # Often text that is pasted from web has additional newlines.
    $text =~ s/^\n+//;
    if ( $text =~ m/(.+\n\n)+/ ) {
	$text =~ s/(.+\n)\n/$1/g;
    }

    my $cho = join
      ( "\n",
	@{ ChordPro::A2Crd::a2crd

t/020_markup.t  view on Meta::CPAN


use strict;
use warnings;
use utf8;

use ChordPro::Testing;
use ChordPro::Output::PDF;

plan tests => 2;

*defrag = \&ChordPro::Output::PDF::Song::defrag;

my $res;

$res = defrag( [ "<i>Comin’ for to carry me ", "home.</i>" ] );

my $xp = [ "<i>Comin’ for to carry me </i>", "<i>home.</i>" ];

is_deeply( $res, $xp, "defrag1");

$res = defrag( [ "<i>Comin’ for to <b>carry me ", "home.</b></i>" ] );

$xp = [ "<i>Comin’ for to <b>carry me </b></i>", "<i><b>home.</b></i>" ];

is_deeply( $res, $xp, "defrag2");

t/700_a2crd.t  view on Meta::CPAN

my $test = 1;

BAIL_OUT("Missing a2crd test data") unless -d "a2crd";

opendir( my $dh, "a2crd" ) || BAIL_OUT("Cannot open a2crd test data");
my @files = grep { /^.+\.crd$/ } readdir($dh);
close($dh);
diag("Testing ", scalar(@files), " crd files");

our $options;
$options->{fragment} = 1;

foreach my $file ( sort @files ) {
    $test++;
    $file = "a2crd/$file";
    #diag("Testing: $file");
    ( my $out = $file ) =~ s/\.crd/.tmp/;
    ( my $ref = $file ) =~ s/\.crd/.cho/;
    @ARGV = ( "--a2crd",
	      "--no-default-configs",
	      "--generate", "ChordPro",

t/710_cho.t  view on Meta::CPAN

my $test = 1;

BAIL_OUT("Missing chordpro test data") unless -d "cho";

opendir( my $dh, "cho" ) || BAIL_OUT("Cannot open chordpro test data");
my @files = grep { /^.+\.cho$/ } readdir($dh);
close($dh);
diag("Testing ", scalar(@files), " chordpro files");

our $options;
#$options->{fragment} = 1;

foreach my $file ( sort @files ) {
    $test++;
    $file = "cho/$file";
    #diag("Testing: $file");
    ( my $out = $file ) =~ s/\.cho/.out/;
    ( my $ref = $file ) =~ s/\.cho/.ref/;
    @ARGV = ( "--no-default-configs",
	      "--generate", "ChordPro",
	      "--backend-option", "expand=1",



( run in 1.540 second using v1.01-cache-2.11-cpan-b16cb0d3907 )