The London Perl and Raku Workshop takes place on 26th Oct 2024. If your company depends on Perl, please consider sponsoring and/or attending.
package Polyline;

=head1 NAME

Polyline - A XFig file animator class - Polyline object

=head1 DESCRIPTION

Polyline object - object code in FIG format: 2.
Here are all the attributes of this class:

B<sub_type, line_style, thickness, pen_color, fill_color, depth, pen_style,
area_fill, style_val, join_style, cap_style, radius, forward_arrow,
backward_arrow, npoints, f_arrow_type, f_arrow_style,
f_arrow_thickness, f_arrow_width, f_arrow_height, b_arrow_type, b_arrow_style,
b_arrow_thickness, b_arrow_width, b_arrow_height, xnpoints, ynpoints,
flipped, file, visibility, center_x, center_y>

=head1 FIG ATTRIBUTES

=over

=item sub_type

1: polyline;
2: box;
3: polygon;
4: arc-box;
5: imported-picture bounding-box.

=item line_style

-1: Default;
0: Solid;
1: Dashed;
2: Dotted;
3: Dash-dotted;
4: Dash-double-dotted;
5: Dash-triple-dotted.

=item thickness

80-ths of an inch ("display units")

=item pen_color

-1..31: FIG colors;
32..543 (512 total): user colors.

=item fill_color

-1..31: FIG colors;
32..543 (512 total): user colors.

=item depth

0 ... 999: larger value means object is deeper than (under)
objects with smaller depth

=item pen_style

unused

=item area_fill

fill type

=item style_val

length, in 1/80 inches, of the on/off
dashes for dashed lines, and the distance between the dots, in 1/80 inches,
for dotted lines

=item join_style

0 = Miter (default);
1 = Bevel;
2 = Round.

=item cap_style

0 = Butt (default);
1 = Round;
2 = Projecting.

=item radius

1/80 inch, radius of arc-boxes

=item forward_arrow

0: no forward arrow;
1: on

=item backward_arrow

0: no backward arrow;
1: on

=item npoints

number of points in line

=item f_arrow_type, b_arrow_type

0: Stick-type (default);
1: Closed triangle;
2: Closed with "indented" butt;
3: Closed with "pointed" butt

=item f_arrow_style, b_arrow_style

0: Hollow (filled with white);
1: Filled with pen_color

=item f_arrow_thickness, b_arrow_thickness

1/80 inch

=item f_arrow_width, b_arrow_width

Fig units

=item f_arrow_height, b_arrow_height

Fig units

=item xnpoints, ynpoints

arrays of points coordinates in Fig units

=item flipped

picture orientation:
0: normal;
1: flipped.

=item file

name of picture file to import

=back

=head1 ADDITONNAL ATTRIBUTES

=over

=item visibility

0: hidden;
1: shown

=item center_x, center_y

calculated center (Fig units)

=back

=cut

use strict;
use warnings;

# useful classes
use FigAnim::Utils;

# constructor
sub new {
    my $proto = shift;
    my $class = ref($proto) || $proto;
    my $self = {};
    
    $self->{name} = shift; # object's name in comment
    
    #$self->{object_code} = 2;
    $self->{sub_type} = shift;
    $self->{line_style} = shift;
    $self->{thickness} = shift;
    $self->{pen_color} = shift;
    $self->{fill_color} = shift;
    $self->{depth} = shift;
    $self->{pen_style} = shift;
    $self->{area_fill} = shift;
    $self->{style_val} = shift;
    $self->{join_style} = shift;
    $self->{cap_style} = shift;
    $self->{radius} = shift;
    $self->{forward_arrow} = shift;
    $self->{backward_arrow} = shift;
    $self->{npoints} = shift;
    
    # forward arrow
    $self->{f_arrow_type} = shift;
    $self->{f_arrow_style} = shift;
    $self->{f_arrow_thickness} = shift;
    $self->{f_arrow_width} = shift;
    $self->{f_arrow_height} = shift;
    
    # backward arrow
    $self->{b_arrow_type} = shift;
    $self->{b_arrow_style} = shift;
    $self->{b_arrow_thickness} = shift;
    $self->{b_arrow_width} = shift;
    $self->{b_arrow_height} = shift;
    
    # picture
    $self->{flipped} = shift;
    $self->{file} = shift;
    
    # points
    $self->{xnpoints} = shift;
    $self->{ynpoints} = shift;
    
    # reference to the FigFile
    $self->{fig_file} = shift;
    
    # object's visibility : 0=hidden 1=shown
    $self->{visibility} = 1;
    
    # calculated center
    $self->{center_x} = undef;
    $self->{center_y} = undef;

    # animations
    $self->{animations} = [];

    bless ($self, $class);
    return $self;
}


# methods
sub clone {
    my $self = shift;
    my $obj = new Polyline;

    foreach ('name','sub_type','line_style','thickness','pen_color',
	     'fill_color','depth','pen_style','area_fill','style_val',
	     'join_style','cap_style','radius','forward_arrow',
	     'backward_arrow','npoints','f_arrow_type','f_arrow_style',
	     'f_arrow_thickness','f_arrow_width','f_arrow_height',
	     'b_arrow_type','b_arrow_style','b_arrow_thickness',
	     'b_arrow_width','b_arrow_height','flipped','file','fig_file',
	     'visibility','center_x','center_y') {
	$obj->{$_} = $self->{$_};
    }

    $obj->{xnpoints} = [];
    push @{$obj->{xnpoints}}, @{$self->{xnpoints}};

    $obj->{ynpoints} = [];
    push @{$obj->{ynpoints}}, @{$self->{ynpoints}};

    return $obj;
}

sub output {
    my $self = shift;
    return if ($self->{visibility} == 0);
    
    my $fh = shift;
    
    foreach (split(/\n/, $self->{name})) {
	printf $fh "# $_\n";
    }
    
    printf $fh
	"2 %d %d %d %d %d %d %d %d %.3f %d %d %d %d %d %d\n",
	@$self{'sub_type','line_style','thickness','pen_color','fill_color',
	       'depth','pen_style','area_fill','style_val','join_style',
	       'cap_style','radius','forward_arrow','backward_arrow',
	       'npoints'};
    
    if ($self->{forward_arrow}) {
	printf $fh "\t%d %d %.2f %.2f %.2f\n",
	    @$self{'f_arrow_type','f_arrow_style','f_arrow_thickness',
		   'f_arrow_width','f_arrow_height'};
    }
    
    if ($self->{backward_arrow}) {
	printf $fh "\t%d %d %.2f %.2f %.2f\n",
	    @$self{'b_arrow_type','b_arrow_style','b_arrow_thickness',
		   'b_arrow_width','b_arrow_height'};
    }
    
    if ($self->{sub_type} == 5) {
	printf $fh "\t%d %s\n", @$self{'flipped','file'};	
    }
    
    for (0..$self->{npoints}-1) {
	printf $fh "\t" if (($_ % 6) == 0);
	printf $fh " %d %d", $self->{xnpoints}[$_], $self->{ynpoints}[$_];
	printf $fh "\n" if (($_ % 6) == 5);
    }
    printf $fh "\n" if (($self->{npoints} % 6) != 0);
}

sub calculateCenter {
    my $self = shift;
    if (($self->{npoints} > 1) &&
	($self->{xnpoints}[$self->{npoints}-1] == $self->{xnpoints}[0]) &&
	($self->{ynpoints}[$self->{npoints}-1] == $self->{ynpoints}[0])) {
	$self->{center_x} =
	    sprintf("%.0f",
		    eval(join('+',
			      @{$self->{xnpoints}}[0..$self->{npoints}-2]
			      )
			 ) / ($self->{npoints}-1));
	$self->{center_y} =
	    sprintf("%.0f",
		    eval(join('+',
			      @{$self->{ynpoints}}[0..$self->{npoints}-2]
			      )
			 ) / ($self->{npoints}-1));
    } else {
	$self->{center_x} =
	    sprintf("%.0f",
		    eval(join('+',@{$self->{xnpoints}}))/$self->{npoints});
	$self->{center_y} =
	    sprintf("%.0f",
		    eval(join('+',@{$self->{ynpoints}}))/$self->{npoints});
    }
}


# animation methods
sub setAttributeValue {
    my $self = shift;
    my @anim = (0, $self->{name}, shift, 0, shift, shift);
    push @{$self->{fig_file}->{animations}}, \@anim;
}

sub setPenColor {
    my $self = shift;
    my $time = shift;
    my $color = $FigAnim::Utils::colors_codes{shift};
    if (defined $color) {
	my @anim = (0, $self->{name}, $time, 0, 'pen_color', $color);
	push @{$self->{fig_file}->{animations}}, \@anim;
    }
}

sub setFillColor {
    my $self = shift;
    my $time = shift;
    my $color = $FigAnim::Utils::colors_codes{shift};
    if (defined $color) {
	my @anim = (0, $self->{name}, $time, 0, 'fill_color', $color);
	push @{$self->{fig_file}->{animations}}, \@anim;
    }
}

sub hide {
    my $self = shift;
    my @anim = (0, $self->{name}, shift, 0, 'visibility', 0);
    push @{$self->{fig_file}->{animations}}, \@anim;
}

sub show {
    my $self = shift;
    my @anim = (0, $self->{name}, shift, 0, 'visibility', 1);
    push @{$self->{fig_file}->{animations}}, \@anim;
}

sub changeThickness {
    my $self = shift;
    my @anim = (1, $self->{name}, shift, shift, int shift);
    push @{$self->{fig_file}->{animations}}, \@anim;
}

sub changeFillIntensity {
    my $self = shift;
    my @anim = (2, $self->{name}, shift, shift, int shift);
    $anim[4] = 0 if ($anim[4] < 0);
    $anim[4] = 20 if ($anim[4] > 20);
    push @{$self->{fig_file}->{animations}}, \@anim;
}

sub translate {
    my $self = shift;
    my @anim = (12, $self->{name}, shift, shift, shift, shift, shift);
    $anim[6] = '' if (!(defined $anim[6]));
    push @{$self->{fig_file}->{animations}}, \@anim;
}

sub rotate {
    my $self = shift;
    my @anim = (22, $self->{name}, shift, shift, shift, shift, shift, shift);
    if (!((defined $anim[5]) && (defined $anim[6]))) {
	$anim[5] = $self->{center_x};
	$anim[6] = $self->{center_y};
    }
    $anim[7] = '' if (!(defined $anim[7]));
    push @{$self->{fig_file}->{animations}}, \@anim;
}

sub scale {
    my $self = shift;
    my @anim = (32, $self->{name}, shift, shift, shift, shift, shift);
    if (!((defined $anim[5]) && (defined $anim[6]))) {
	$anim[5] = $self->{center_x};
	$anim[6] = $self->{center_y};
    }
    push @{$self->{fig_file}->{animations}}, \@anim;
}


# outputs a SVG element
sub outputSVG {
    my $self = shift;
    my $fh = shift;

    my ($colors) = @_;
    
    foreach (split(/\n/, $self->{name})) {
	print $fh "<!-- $_ -->\n";
    }

    # converts values in SVG-CSS

    my $style = "style=\"";

    if ($self->{sub_type} == 1) { # polyline

	if ($self->{npoints} == 1) { # one point -> square in SVG

	    $style .= "stroke-width: 0; ";

	    # pen_color
	    $style .=
		"fill: " .
		ConvertSVG::pen_fill_colors_to_rgb($self->{pen_color},
						   $colors);

	    # end of style
	    $style .= "\"";

	    # thickness
	    my $thickness = ConvertSVG::thickness_to_value($self->{thickness});

	    # top, left
	    my $x = "x=\"" .
		int($self->{xnpoints}[0] - ($thickness / 2)) .
		"\"";
	    my $y = "y=\"" .
		int($self->{ynpoints}[0] - ($thickness / 2)) .
		"\"";

	    # width, height
	    my $width = "width=\"" . $thickness . "\"";
	    my $height = "height=\"" . $thickness . "\"";

	    print $fh "<rect $x $y $width $height $style />\n";

	} else { # two points at least

	    # converts arrows into SVG paths

	    my $f_arrow_name = "";
	    my $b_arrow_name = "";

	    if ($self->{forward_arrow} == 1) {
		$_ = $self->{name};
		s/\W//g;
		$f_arrow_name = $_ . "_f_arrow";
		my $f_arrow =
		    ConvertSVG::arrows_to_markers($f_arrow_name,
						  1,
						  "auto",
						  $self->{f_arrow_type},
						  $self->{f_arrow_style},
						  $self->{f_arrow_thickness},
						  $self->{f_arrow_width},
						  $self->{f_arrow_height},
						  $self->{pen_color},
						  $colors);
		print $fh $f_arrow;
		$style .=
		    "marker-end: url(#" . $f_arrow_name . "); ";
	    }

	    if ($self->{backward_arrow} == 1) {
		$_ = $self->{name};
		s/\W//g;
		$b_arrow_name = $_ . "_b_arrow";
		my $b_arrow =
		    ConvertSVG::arrows_to_markers($b_arrow_name,
						  0,
						  "auto",
						  $self->{b_arrow_type},
						  $self->{b_arrow_style},
						  $self->{b_arrow_thickness},
						  $self->{b_arrow_width},
						  $self->{b_arrow_height},
						  $self->{pen_color},
						  $colors);
		print $fh $b_arrow;
		$style .=
		    "marker-start: url(#" . $b_arrow_name . "); ";
	    }

	    # line_style, style_val, pen_color
	    $style .= ConvertSVG::line_style_to_stroke($self->{line_style},
						       $self->{style_val},
						       $self->{pen_color},
						       $colors) . "; ";
	    # thickness
	    $style .= ConvertSVG::thickness_to_stroke($self->{thickness}) . "; ";
	    
	    # fill_color, area_fill
	    $style .= ConvertSVG::area_fill_to_fill($self->{area_fill},
						    $self->{fill_color},
						    $colors) . "; ";

	    
	    # join_style
	    $style .= ConvertSVG::join_style_to_linejoin($self->{join_style}) . "; ";
	    # cap_style
	    $style .= ConvertSVG::cap_style_to_linecap($self->{cap_style});
	    
	    # end of style
	    $style .= "\"";
	    
	    # points
	    my $points = "points=\"";
	    for (0 .. $self->{npoints}-1) {
		$points .= $self->{xnpoints}[$_] . " " . $self->{ynpoints}[$_];
		if ($_ < $self->{npoints}-1) { $points .= ", "; }
	    }
	    $points .= "\"";
	    
	    print $fh "<polyline $points $style";

	    if ($#{$self->{animations}} >= 0) { # SVG + SMIL
		print $fh ">\n";
		for (my $i = 0; $i <= $#{$self->{animations}}; $i++) {
		    print $fh "\t";
		    my $smil = ConvertSMIL::smil($self->{animations}[$i]);
		    print $fh $smil;
		    print $fh "\n";
		}
		print $fh "</polyline>\n\n";
	    } else { # SVG only
		print $fh " />\n\n";
	    }
	}

    } elsif ($self->{sub_type} == 2) { # box

	# line_style, style_val, pen_color
	$style .= ConvertSVG::line_style_to_stroke($self->{line_style},
						   $self->{style_val},
						   $self->{pen_color},
						   $colors) . "; ";
	# thickness
	$style .= ConvertSVG::thickness_to_stroke($self->{thickness}) . "; ";
	# fill_color, area_fill
	$style .= ConvertSVG::area_fill_to_fill($self->{area_fill},
						$self->{fill_color},
						$colors) . "; ";
	# join_style
	$style .= ConvertSVG::join_style_to_linejoin($self->{join_style}) . "; ";
	# cap_style
	$style .= ConvertSVG::cap_style_to_linecap($self->{cap_style});

	# end of style
	$style .= "\"";

	my $top = ConvertSVG::min($self->{ynpoints}[0],
				  $self->{ynpoints}[1],
				  $self->{ynpoints}[2],
				  $self->{ynpoints}[3]);
	my $left = ConvertSVG::min($self->{xnpoints}[0],
				   $self->{xnpoints}[1],
				   $self->{xnpoints}[2],
				   $self->{xnpoints}[3]);
	my $bottom = ConvertSVG::max($self->{ynpoints}[0],
				     $self->{ynpoints}[1],
				     $self->{ynpoints}[2],
				     $self->{ynpoints}[3]);
	my $right = ConvertSVG::max($self->{xnpoints}[0],
				    $self->{xnpoints}[1],
				    $self->{xnpoints}[2],
				    $self->{xnpoints}[3]);

	my $x = "x=\"" . $left . "\"";
	my $y = "y=\"" . $top . "\"";

	my $width = "width=\"" . ($right - $left) . "\"";
	my $height = "height=\"" . ($bottom - $top) . "\"";

	print $fh "<rect $x $y $width $height $style";

	if ($#{$self->{animations}} >= 0) { # SVG + SMIL
	    print $fh ">\n";
	    for (my $i = 0; $i <= $#{$self->{animations}}; $i++) {
		print $fh "\t";
		my $smil = ConvertSMIL::smil($self->{animations}[$i]);
		print $fh $smil;
		print $fh "\n";
	    }
	    print $fh "</rect>\n\n";
	} else { # SVG only
	    print $fh " />\n\n";
	}

    } elsif ($self->{sub_type} == 3) { # polygon

	# line_style, style_val, pen_color
	$style .= ConvertSVG::line_style_to_stroke($self->{line_style},
						   $self->{style_val},
						   $self->{pen_color},
						   $colors) . "; ";
	# thickness
	$style .= ConvertSVG::thickness_to_stroke($self->{thickness}) . "; ";

	# fill_color, area_fill
	$style .= ConvertSVG::area_fill_to_fill($self->{area_fill},
						$self->{fill_color},
						$colors) . "; ";
	# join_style
	$style .= ConvertSVG::join_style_to_linejoin($self->{join_style}) . "; ";
	# cap_style
	$style .= ConvertSVG::cap_style_to_linecap($self->{cap_style});

	# end of style
	$style .= "\"";

	# points
	my $points = "points=\"";
	for (0 .. $self->{npoints}-1) {
	    $points .= $self->{xnpoints}[$_];
	    $points .= " ";
	    $points .= $self->{ynpoints}[$_];
	    if ($_ < $self->{npoints}-1) { $points .= ", "; }
	}
	$points .= "\"";

	print $fh "<polygon $points $style";

	if ($#{$self->{animations}} >= 0) { # SVG + SMIL
	    print $fh ">\n";
	    for (my $i = 0; $i <= $#{$self->{animations}}; $i++) {
		print $fh "\t";
		my $smil = ConvertSMIL::smil($self->{animations}[$i]);
		print $fh $smil;
		print $fh "\n";
	    }
	    print $fh "</polygon>\n\n";
	} else { # SVG only
	    print $fh " />\n\n";
	}

    } elsif ($self->{sub_type} == 4) { # arc-box

	# line_style, style_val, pen_color
	$style .= ConvertSVG::line_style_to_stroke($self->{line_style},
						   $self->{style_val},
						   $self->{pen_color},
						   $colors) . "; ";
	# thickness
	$style .= ConvertSVG::thickness_to_stroke($self->{thickness}) . "; ";
	# fill_color, area_fill
	$style .= ConvertSVG::area_fill_to_fill($self->{area_fill},
						$self->{fill_color},
						$colors) . "; ";
	# join_style
	$style .= ConvertSVG::join_style_to_linejoin($self->{join_style}) . "; ";
	# cap_style
	$style .= ConvertSVG::cap_style_to_linecap($self->{cap_style});

	# end of style
	$style .= "\"";

	my $top = ConvertSVG::min($self->{ynpoints}[0],
				  $self->{ynpoints}[1],
				  $self->{ynpoints}[2],
				  $self->{ynpoints}[3]);
	my $left = ConvertSVG::min($self->{xnpoints}[0],
				   $self->{xnpoints}[1],
				   $self->{xnpoints}[2],
				   $self->{xnpoints}[3]);
	my $bottom = ConvertSVG::max($self->{ynpoints}[0],
				     $self->{ynpoints}[1],
				     $self->{ynpoints}[2],
				     $self->{ynpoints}[3]);
	my $right = ConvertSVG::max($self->{xnpoints}[0],
				    $self->{xnpoints}[1],
				    $self->{xnpoints}[2],
				    $self->{xnpoints}[3]);

	my $x = "x=\"" . $left . "\"";
	my $y = "y=\"" . $top . "\"";

	my $width = "width=\"" . ($right - $left) . "\"";
	my $height = "height=\"" . ($bottom - $top) . "\"";

	my $rx = "rx=\"" . $self->{radius} * 15 . "\"";
	my $ry = "ry=\"" . $self->{radius} * 15 . "\"";

	print $fh "<rect $x $y $width $height $rx $ry $style";

	if ($#{$self->{animations}} >= 0) { # SVG + SMIL
	    print $fh ">\n";
	    for (my $i = 0; $i <= $#{$self->{animations}}; $i++) {
		print $fh "\t";
		my $smil = ConvertSMIL::smil($self->{animations}[$i]);
		print $fh $smil;
		print $fh "\n";
	    }
	    print $fh "</rect>\n\n";
	} else { # SVG only
	    print $fh " />\n\n";
	}

    } elsif ($self->{sub_type} == 5) { # imported-picture bounding-box

	# end of style
	$style .= "\"";

	my $top = ConvertSVG::min($self->{ynpoints}[0],
				  $self->{ynpoints}[1],
				  $self->{ynpoints}[2],
				  $self->{ynpoints}[3]);
	my $left = ConvertSVG::min($self->{xnpoints}[0],
				   $self->{xnpoints}[1],
				   $self->{xnpoints}[2],
				   $self->{xnpoints}[3]);
	my $bottom = ConvertSVG::max($self->{ynpoints}[0],
				     $self->{ynpoints}[1],
				     $self->{ynpoints}[2],
				     $self->{ynpoints}[3]);
	my $right = ConvertSVG::max($self->{xnpoints}[0],
				    $self->{xnpoints}[1],
				    $self->{xnpoints}[2],
				    $self->{xnpoints}[3]);

	my $x = "x=\"" . $left . "\"";
	my $y = "y=\"" . $top . "\"";

	my $width = "width=\"" . ($right - $left) . "\"";
	my $height = "height=\"" . ($bottom - $top) . "\"";

	# file
	my $xlink = "xlink:href=\"" . @$self{'file'} . "\"";

	# flipped
	my $transform = "";
	if ($self->{flipped} == 1) {
	    $transform =
		"transform=\"" .
		"translate(" .
		($self->{xnpoints}[2] - $self->{xnpoints}[0]) .
		", 0), " .
		"rotate(90, " .
		$self->{xnpoints}[0] . ", " .
		$self->{ynpoints}[0] . ")\"";
	}

	print $fh "<image $xlink $x $y $width $height $transform";

	if ($#{$self->{animations}} >= 0) { # SVG + SMIL
	    print $fh ">\n";
	    for (my $i = 0; $i <= $#{$self->{animations}}; $i++) {
		print $fh "\t";
		my $smil = ConvertSMIL::smil($self->{animations}[$i]);
		print $fh $smil;
		print $fh "\n";
	    }
	    print $fh "</image>\n\n";
	} else { # SVG only
	    print $fh " />\n\n";
	}
    }
}


1;