App-Music-ChordPro

 view release on metacpan or  search on metacpan

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


    for ( @$content ) {
	next if $_->{type} eq 't';
	my $name = $_->{name};
	if ( $name eq "svg" ) {
	    $indent = "";
	    $self->handle_svg($_);
	    # Adds results to $self->{xoforms}.
	}
	else {
	    # Skip recursively.
	    $self->_dbg( "$name (ignored)" ) unless $name eq "<>"; # top
	    $self->search($_->{content});
	}
    }
}

method handle_svg ( $e ) {

    $self->_dbg( "^ ==== start ", $e->{name}, " ====" );

    my $xo;
    if ( $prog ) {
	$xo = SVGPDF::PAST->new( pdf => $pdf );
    }
    else {
	$xo = $pdf->xo_form;
    }
    push( @$xoforms, { xo => $xo } );

    $self->_dbg("XObject #", scalar(@$xoforms) );
    my $svg = SVGPDF::Element->new
	( name    => $e->{name},
	  atts    => { map { lc($_) => $e->{attrib}->{$_} } keys %{$e->{attrib}} },
	  content => $e->{content},
	  root    => $self,
	);

    # If there are <style> elements, these must be processed first.
    my $cdata = "";
    for ( $svg->get_children ) {
	next unless ref($_) eq "SVGPDF::Style";
	# DDumper($_->get_children) unless scalar($_->get_children) == 1;
	croak("ASSERT: 1 child") unless scalar($_->get_children) == 1;
	for my $t ( $_->get_children ) {
	    croak("# ASSERT: non-text child in style")
	      unless ref($t) eq "SVGPDF::TextElement";
	    $cdata .= $t->content;
	}
    }
    if ( $cdata =~ /\S/ ) {
	$css->read_string($cdata);
    }

    my $atts   = $svg->atts;

    # The viewport, llx lly width height.
    my $vbox   = delete $atts->{viewbox};

    # Width and height are the display size of the viewport.
    # Resolve em, ex and percentages.
    my $vwidth  = delete $atts->{width} // 0;
    my $vheight = delete $atts->{height} // 0;
    for ( $vwidth ) {
	$_ = $svg->u( $_, width => $pagesize->[0],
		      fontsize => $fontsize );
    }
    for ( $vheight ) {
	$_ = $svg->u( $_, width => $pagesize->[1],
		      fontsize => $fontsize );
    }

    delete $atts->{$_} for qw( xmlns:xlink xmlns:svg xmlns version );
    my $style = $svg->css_push($atts);

    # If there is min-width the image must be scaled if the available
    # width is smaller. If the width is larger we render to the
    # available space.
    my $minw = $style->{'min-width'} // 0;
    $minw = $svg->u( $minw, width => $pagesize->[0],
		     fontsize => $fontsize ) if $minw;

    # vertical-align is needed later when the XObject is placed.
    my $valign = $style->{'vertical-align'} // 0;
    $valign = $svg->u( $valign, fontsize => $fontsize ) if $valign;

    my @vb;			# viewBox: llx lly width height
    my @bb;			# bbox:    llx lly urx ury

    # We rely on the <svg> to supply the correct viewBox.
    my $width;			# width of the vbox
    my $height;			# height of the vbox
    if ( $vbox ) {
	@vb     = $svg->getargs($vbox);
	$width  = $svg->u( $vb[2],
			   width => $pagesize->[0],
			   fontsize => $fontsize );
	$height = $svg->u( $vb[3],
			   width => $pagesize->[1],
			   fontsize => $fontsize );
	if ( $minw && $minw > $width ) {
	    $width  = $minw;
	    $vb[2] = $minw / $vheight * $height;
	    $self->_dbg("minw: ", $style->{'min-width'}, " ",
			"width -> $width, \$vb[2] -> $vb[2]\n");
	}
	if ( $valign ) {
	    # Verify valign against the vbox.
	    my $va = sprintf("%.2f", -$valign/$vheight);
	    my $vb = sprintf("%.2f", ($vb[3]+$vb[1])/$vb[3]);
	    warn("Vertical align = $va, but vbox says $vb\n")
	      unless $va eq $vb;
	}
	if ( $vwidth && !$vheight ) {
	    $vheight = $vwidth * $height / $width;
	}
	if ( $vheight && !$vwidth ) {
	    $vwidth = $vheight * $width / $height;
	}
    }
    else {
	# Use to width/height, falling back to pagesize.
	$width  = $svg->u( $vwidth  ||$pagesize->[0],
			   width => $pagesize->[0],
			   fontsize => $fontsize );
	$height = $svg->u( $vheight ||$pagesize->[1],
			   width => $pagesize->[1],
			   fontsize => $fontsize );
	if ( $minw && $minw > $width ) {
	    $width = $minw if $minw > $width;
	    $self->_dbg("minw: ", $style->{'min-width'}, " ",
			"width -> $width");
	}
	@vb     = ( 0, 0, $width, $height );
	if ( $valign ) {
	    $vb[1] = -( $vb[3] + $valign );
	    $self->_dbg("valign: ", $style->{'vertical-align'}, " ",
			"\$vb[1] -> $vb[1]");
	}
	$vbox = sprintf("%.2f %.2f %.2f %.2f (inferred)", @vb);
    }

    $svg->nfi("disproportional vbox/width/height")
      if $vheight &&
      ( (( $width/$height) / ($vwidth/$vheight) > 1.05)
	|| (( $width/$height) / ($vwidth/$vheight) < 0.95) );

    # Get llx lly urx ury bounding box rectangle.
    @bb = $self->vb2bb(@vb);
    $self->_dbg( "vb $vbox => bb %.2f %.2f %.2f %.2f", @bb );
    warn( sprintf("vb $vbox => bb %.2f %.2f %.2f %.2f\n", @bb ))
      if $verbose && !$debug;
    $xo->bbox(@bb);

    if ( my $c = $style->{"background-color"} ) {
	if ( $c ne "none" ) {
	    $xo->fill_color($c);
	    $xo->rectangle(@bb);
	    $xo->fill;
	}
    }

    # Set up result forms.
    $xoforms->[-1] =
      { xo      => $xo,
	# Design (desired) width and height.
	vwidth  => $vwidth  || $vb[2],
	vheight => $vheight || $vb[3],
	# viewBox (SVG coordinates)
	vbox    => [ @vb ],
	width   => $vb[2],
	height  => $vb[3],
	# See e.g. https://oreillymedia.github.io/Using_SVG/extras/ch05-percentages.html
	diag    => sqrt( $vb[2]**2 + $vb[3]**2 ) / sqrt(2),
	# bbox (PDF coordinates)
	bbox    => [ @bb ],
      };
    # Not sure if this is ever needed.
    $xoforms->[-1]->{valign} = $valign if $valign;

#    use DDumper; DDumper( { %{$xoforms->[-1]}, xo => 'XO' } );
    # <svg> coordinates are topleft down, so translate.
    $self->_dbg( "matrix( 1 0 0 -1 0 0)" );
    $xo->matrix( 1, 0, 0, -1, 0, 0 );

    if ( $debug ) {		# show bb
	$xo->save;
	$self->_dbg( "vb rect( %.2f %.2f %.2f %.2f)",
		        $vb[0], $vb[1], $vb[2]+$vb[0], $vb[1]+$vb[3]);
	$xo->rectangle( $vb[0], $vb[1], $vb[2]+$vb[0], $vb[1]+$vb[3]);
	$xo->fill_color("#ffffc0");
	$xo->fill;
	$xo->move(  $vb[0], 0 );
	$xo->hline( $vb[0]+$vb[2]);
	$xo->move( 0, $vb[1] );
	$xo->vline( $vb[1]+$vb[3] );
	$xo->line_width(0.5);
	$xo->stroke_color( "red" );
	$xo->stroke;
	$xo->restore;
    }
    $self->draw_grid( $xo, \@vb ) if $grid;


    # Establish currentColor.
    for ( $css->find("fill") ) {
	next if $_ eq 'none' or $_ eq 'transparent';
	$self->_dbg( "xo fill_color ",
		     $_ eq 'currentColor' ? 'black' : $_,
		     " (initial)");
	$xo->fill_color( $_ eq 'currentColor' ? 'black' : $_ );
    }
    for ( $css->find("stroke") ) {
	next if $_ eq 'none' or $_ eq 'transparent';
	$self->_dbg( "xo stroke_color ",
		     $_ eq 'currentColor' ? 'black' : $_,
		     " (initial)");
	$xo->stroke_color( $_ eq 'currentColor' ? 'black' : $_ );
    }
    $svg->traverse;

    $svg->css_pop;

    $self->_dbg( "==== end ", $e->{name}, " ====" );
}

sub min( $a, $b ) { $a < $b ? $a : $b }
sub max( $a, $b ) { $a > $b ? $a : $b }

method combine_svg( $forms, %opts ) {
    my $type = $opts{type} // "stacked";
    return $forms if $type eq "none";

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

    }

If no callback function is set, SVGPDF will recognize the 14 standard
PDF corefonts, and aliases C<serif>, C<sans> and C<mono>.

B<IMPORTANT: With the standard corefonts only characters of the
ISO-8859-1 set (Latin-1) can be used. No greek, no chinese, no cyrillic.
You have been warned.>

=head1 LIMITATIONS

The following SVG elements are implemented.

=over 3

=item *

C<svg>, but not nested.

=item *

C<style>, as a child of the outer C<svg>.

Many style attributes are understood, including but not limited to:

color,
stroke, stroke-width, stroke-linecap, stroke-linejoin, stroke-dasharray,
fill, stroke-width, stroke-linecap, stroke-linejoin,
transform (translate, scale, skewX, skewY, rotate, matrix)
font-family, font-style, font-weight, font-size,
text-anchor.

Partially implemented: @font-face (src url data and local file only).

=item *

circle,
ellipse,
g,
image,
line,
path,
polygon,
polyline,
rect,
text and tspan (no white-space styles).

=item *

defs and use,

=back

The following SVG features are partially implemented.

=over 3

=item *

Percentage units. For most "y", "h" or "height" attributes the result
will be the percentage of the viewBox height.

Similar for "x", "w" and "width".

Everything else will result in a percentage of the viewBox diagonal
(according to the specs).

=item *

Embedded SVG elements and preserveAspectRatio.

=item *

Standalone T-path elements.

=back

The following SVG features are not (yet) implemented.

=over 3

=item *

title, desc elements

=back

The following SVG features are not planned to be implemented.

=over 3

=item *

Shades, gradients, patterns and animations.

=item *

Shape rendering attributes.

=item *

Transparency.

=item *

Text paths.

=item *

Clipping and masking.

=back

What is supported, then? Most SVG files generated by any of the
following tools seem to produce good if not perfect results:

=over 3

=item *

C<abc2svg> (ABC music notation tool)

=item *

MathJax, inline and display without tag



( run in 1.476 second using v1.01-cache-2.11-cpan-39bf76dae61 )