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 )