view release on metacpan or search on metacpan
- !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",