App-Music-ChordPro

 view release on metacpan or  search on metacpan

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

#! perl

use v5.26;
use Object::Pad;
use utf8;

my $dcache;			# cache core grids
my $pdf = "";			# for cache flush

class ChordPro::Output::PDF::StringDiagram;

field $pr	:param;

field $config;
field $ps;

field $gw;			# width of a cell, pt
field $gh;			# height of a cell, pt
field $lw;			# fraction of cell width
field $nutwidth;		# width (in linewidth) of the top nut
field $nw;			# extra width for the top nut, pt
field $vc;			# cells, vertical
field $strings;			# number of strings
field $hc;			# cells, horizontal (= strings)
field $dot;			# dot size, fraction of cell width
field $bsz;			# barre size, fraction of dot
field $bstyle;			# barre style ("line", "arc")
field $fsh;			# show fingers (0, 1, "below")
field $fg;			# foreground color
field $bg;			# background color
field $fbp;			# fret base position ("left", "right")
field $fbt;			# fret base text ("%s" is default)

ADJUST {
    $config	  = $::config;
    $ps		  = $pr->{ps};
    $strings	  = $config->diagram_strings;
    my $ctl	  = $ps->{diagrams};
    $gw		  = $ctl->{width} || 6;
    $gh		  = $ctl->{height} || 6;
    $lw		  = ($ctl->{linewidth} || 0.10) * $gw;
    $nutwidth     = $ctl->{nutwidth} || 1;
    $nw		  = ($nutwidth-1) * $lw;
    $vc		  = $ctl->{vcells} || 4;
    $hc		  = $strings;
    $dot	  = $ctl->{dotsize} * ( $gh < $gw ? $gh : $gw );
    $bsz	  = $ctl->{barwidth} * $dot;
    $bstyle	  = $ctl->{barstyle} || "line";
    $fsh	  = $ctl->{fingers} || 0;
    $fbp	  = $ctl->{fretbaseposition} || "left";
    $fbt	  = $ctl->{fretbasetext} || "%s";
    $dcache = {} if $pr->{pdf} ne $pdf;
    $pdf          = $pr->{pdf};
}

use constant DIAG_DEBUG => 0;

# The vertical space the diagram requires.
method vsp0( $elt, $dummy = 0 ) {
    $ps->{fonts}->{diagram}->{size} * $ps->{spacing}->{diagramchords}
      + $nutwidth * $lw + 0.40 * $gw
      + $vc * $gh
      + ( $fsh eq "below" ? $ps->{fonts}->{diagram}->{size} : 0 )
      ;
}

# The advance height.
method vsp1( $elt, $dummy = 0 ) {
    $ps->{diagrams}->{vspace} * $gh;
}

# The vertical space the diagram requires, including advance height.
method vsp( $elt, $dummy = 0 ) {
    $self->vsp0($elt) + $self->vsp1($elt);
}

# The horizontal space the diagram requires.
method hsp0( $elt, $dummy = 0 ) {
    ($strings - 1) * $gw;
}

# The advance width.
method hsp1( $elt, $dummy = 0 ) {
    $ps->{diagrams}->{hspace} * $gw;
}

# The horizontal space the diagram requires, including advance width.
method hsp( $elt, $dummy = 0 ) {
    $self->hsp0($elt) + $self->hsp1($elt);
}

# The actual draw method.
method draw( $info, $x, $y, $dummy=0 ) {
    return unless $info;

    my $font = $ps->{fonts}->{diagram};

    my $xo = $self->diagram_xo($info);
    my @bb = $xo->bbox;
    warn("BB [ @bb ] $x $y\n") if DIAG_DEBUG;
    $pr->{pdfgfx}->object( $xo, $x,

			   $y - ($font->{size} * $ps->{spacing}->{diagramchords} + $dot + $lw) );

    # Draw name.
    my $w = $gw * ($strings - 1);
    $pr->setfont($font);
    my $name = $info->chord_display;
    $name = "<span color='$fg'>$name</span>"
      if $info->{diagram};
    $pr->text( $name, $x + ($w - $pr->strwidth($name))/2,
	       $y-$pr->font_bl($font));#+$font->{fd}->{ascender}/1000 );
}

# Returns the complete diagram as an xo. This includes the core grid,
# finger/fret positions, open and muted string indicators.
# The bounding box includes space form the open and muted string indicators
# and dots on the first and last strings, even when absent.
# The bbox includes basefret and fingers (below) if present.
# Origin is top left of the grid.
# Note that the chord name is not part of the diagram.

method diagram_xo( $info ) {
    return unless $info;
    $fg = $info->{diagram} // $config->{pdf}->{theme}->{foreground};
    $bg = $config->{pdf}->{theme}->{background};

    # Set default options for safety if they have not already been set
    $fg = "black" if $fg eq "none";
    $bg = "white" if $bg eq "none";

    my $x = 0;
    my $w = $gw * ($strings - 1);
    my $baselabeloffset = $info->{baselabeloffset} || 0;
    my $basefretno = $info->{base} + $baselabeloffset;
    my $basefrettext="";	# for base label
    my $basefont;		# for base label
    my $basesize;		# for base label

    # Get the core grid.
    my $xg = $self->grid_xo;
    my @xgbb = $xg->bbox;

    my $xo = $pdf->xo_form;
    my @bb = ( 0,
	       0.77 * $dot + 2*$lw,
	       $w + $dot/2,
	       $xgbb[3] );

    if ( $basefretno > 1 ) {
	$basefont = $ps->{fonts}->{diagram_base}->{fd}->{font};
	$basesize = $gh/0.85;
        my $basefretformat = $fbt;
        $basefretformat = '%s' unless $basefretformat =~ /^[^%]*\%s[^%]*$/;
        $basefrettext = sprintf($basefretformat, $basefretno);

        if ( $fbp eq "left" ) {
            $bb[0] -= $basefont->width("xx$basefrettext") * $basesize;
        }
        else {
            #fret base position on "right" side
            $bb[0] -= $dot/2;
            $bb[2] += $basefont->width("xx$basefrettext") * $basesize;
        }
    }
    else {
	$bb[0] -= $dot/2;
    }
    if ( $fsh eq "below" && $info->{fingers} ) {
	$bb[3] -= $gh + $lw;
    }
    $xo->bbox(@bb);
    $xo->line_width($lw);
    $xo->stroke_color($fg);
    $xo->fill_color($fg);

    if ( DIAG_DEBUG ) {
	# Draw the grid.
	$xo->save;
	$xo->fill_color('yellow');
	$xo->rectangle($xo->bbox)->fill;
	$xo->object( $xg, 0, 0, 1 );
	$xo->fill_color('red');
	my $lw = $lw/2;
	$xo->rectangle( -$lw, -$lw, $lw, $lw )->fill;
	$xo->restore;

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

		$xo->line_width($lw+0.2);
	    }

	    foreach ( sort keys %$bar ) {
		my @bi = @{ $bar->{$_} };
		# $bi array description = [finger, fret, first_string, last_string].

		if ( $bi[-2] == $bi[-1] ) { # not a bar
		    delete $bar->{$_};
		    next;
		}

		if ( $bstyle eq "line" ) {
		    # Print the bar line.
		    $x = $bi[2]*$gw;
		    $xo->move( $x, -$nw -$bi[1]*$gh+$gh/2 );
		    $xo->hline( $x+($bi[3]-$bi[2])*$gw);
		}
		else {
		    # Print arcs for barre
		    my $arcw = (($bi[3]-$bi[2])*$gw + 0.7*$gw)/2;
		    my $arch = 0.4*$gw;
		    my $arcy = -$nw -$bi[1]*$gh +$gh+0.25*$gh;
		    my $arcx = $bi[2]*$gw - (0.7*$gw)/2;

		    if ( $bi[1] == 1 ) {
			# Bar is on the first fret so bar arcs
			# must be drawn above the nut.
			$arcy += $nw;
		    }

		    # Draw first arc.
		    $xo->move( $arcx, $arcy );
		    $xo->arc( $arcx+$arcw, $arcy, $arcw, $arch, 180, 0 );

		    # Draw second arc a little higher, this is
		    # a fast way to have narrower corners look at the arc edge.
		    $xo->move( $arcx, $arcy-0.8 );
		    $xo->arc( $arcx+$arcw, $arcy-0.8, $arcw, $arch, 180, 0 );
		}

		$xo->stroke;
		$xo->fill;
	    }
	    $xo->stroke->restore;
	}
    }

    my $oflo;			# to detect out of range frets

    # Color of the dots and numbers.
    my $fbg = "";		# numbers
    my $ffg = $fg;		# dots
    # The numbercolor property of the chordfingers is used for the
    # color of the dot numbers.
    my $fcf = $ps->{fonts}->{chordfingers};
    $fbg = $pr->_bgcolor($fcf->{numbercolor});
    $ffg = $pr->_bgcolor($fcf->{color});

    if ( $fsh ne "below" ) {
        # However, if none we should really use "background" color.
        $fbg = $bg if $fbg eq "none";
    }
    else {
        # However, for "below" case if none or numbercolor equals background color we should really use "foreground".
        $fbg = $fg if ( $fbg eq "none") || ( $fbg eq $bg );
    }

    $x = -$gw;
    for my $sx ( 0 .. $strings-1 ) {
	$x += $gw;
	my $fret = $info->{frets}->[$sx];
	my $fing = -1;
	$fing = $fingers->[$sx] // -1 if $fingers;

	# For bars in "line" style, only the first and last finger.
	if ( $fing && $bar->{$fing} && $bstyle eq "line" ) {
	    next unless $sx == $bar->{$fing}->[2] || $sx == $bar->{$fing}->[3];
	}

	if ( $fret > 0 ) {
	    if ( $fret > $vc && !$oflo++ ) {
		warn("Diagram $info->{name}: ",
		     "Fret position $fret exceeds diagram size $vc\n");
		next;
	    }
	    $xo->fill_color($ffg);
	    $xo->circle( $x, -$nw - ($fret-0.5)*$gh, $dot/2 )->fill;

	}
	elsif ( $fret < 0 ) {
	    $xo->move( $x - $dot/3, 0.77 * $dot + $lw );
	    $xo->line( $x + $dot/3, 0.1 * $gh + $lw );
	    $xo->move( $x + $dot/3, 0.77 * $dot  + $lw );
	    $xo->line( $x - $dot/3, 0.1 * $gh + $lw );
	    $xo->stroke;
	}
	elsif ( $info->{base} > 0 ) {
	    $xo->circle( $x, 3.5*$gh/10 + $lw, $dot/3 )->stroke;
	}
    }

    # Show the fingers, if any.
    if ( $fingers && @$fingers ) {
	my ( $font, $size );
	$font = "chordfingers";
	$size = $dot;
	if ( $fsh eq "below" ) {
	    $size = $ps->{fonts}->{$font}->{size} // "00";
	    $size = $dot if $size <= 0;
	}
	$font = $ps->{fonts}->{$font}->{fd}->{font};
        warn("XXX ", $font->{' data'}->{fontname}, " $size\n") if DIAG_DEBUG;

	$x = -$gw;
	my $did = 0;
	for my $sx ( 0 .. $strings-1 ) {
            #when "below", chord fingers should be always drawn and not take into account the dot color
            last if ( $fsh ne "below" ) && ( $fbg eq $ffg );
	    $x += $gw;
	    my $fret = $info->{frets}->[$sx];
	    next unless $fret > 0;
	    my $fing = uc $fingers->[$sx];
	    next unless $fing =~ /^[1-9A-Z]$/;



( run in 0.786 second using v1.01-cache-2.11-cpan-f56aa216473 )