From Code to Community: Sponsoring The Perl and Raku Conference 2025 Learn more

#
# XFig Drawing Library
#
# Copyright (c) 2017 D Scott Guthridge <scott_guthridge@rompromity.net>
#
# This program is free software: you can redistribute it and/or modify it under
# the terms of the Artistic License as published by the Perl Foundation, either
# version 2.0 of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful, but WITHOUT
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
# FOR A PARTICULAR PURPOSE. See the Artistic License for more details.
#
# You should have received a copy of the Artistic License along with this
#
our $VERSION = 'v1.0.7';
use strict;
use Carp;
use Image::Info qw(image_info);
#
# RE_REAL regular expression matching a floating point number
#
my $RE_REAL = "(?:(?i)(?:[-+]?)(?:(?=[.]?[0123456789])(?:[0123456789]*)" .
"(?:(?:[.])(?:[0123456789]{0,}))?)(?:(?:[E])(?:(?:[-+]?)" .
"(?:[0123456789]+))|))";
my $DEFAULT_RESOLUTION = 100.0; # dpi
#
# _parseResolution: parse resolution string
# $value: resolution
# $state: state structure
#
# Input may be any of (in increasing preference):
# xres / yres (form 1)
# xyres unit (form 2)
# xres / yres unit (form 3)
#
# Return:
# If the input string is valid, this function updates the
# state structure and returns 1. On error, it returns undef.
#
sub _parseResolution {
my $value = shift;
my $state = shift;
#
# Match against pattern.
#
my $pattern = "\\s*($RE_REAL)" .
"(\\s*[/xX,]?\\s*($RE_REAL))?" .
"(\\s*(dpi|dpcm|dpm))?\\s*";
if (defined($value) && $value =~ m/^${pattern}$/) {
my $x = $1;
my $y = $3;
my $dpi;
#
# If unit given, convert to dpi.
#
if (defined($5)) {
if ($5 eq "dpcm") {
$dpi = 2.54;
} elsif ($5 eq "dpm") {
$dpi = 0.0254;
} else { # "dpi"
$dpi = 1.0;
}
}
#
# Form 1
#
if (!defined($dpi) && defined($y)) {
if ($state->{"best_form"} < 1) {
$state->{"x_resolution"} = $x * $DEFAULT_RESOLUTION;
$state->{"y_resolution"} = $y * $DEFAULT_RESOLUTION;
$state->{"best_form"} = 1;
}
return 1;
}
#
# Form 2
#
if (defined($dpi) && !defined($y)) {
if ($state->{"best_form"} < 2) {
$state->{"x_resolution"} = $x * $dpi;
$state->{"y_resolution"} = $x * $dpi; # y same as x
$state->{"best_form"} = 2;
}
return 1;
}
#
# Form 3
#
if (defined($dpi) && defined($y)) {
if ($state->{"best_form"} < 3) {
$state->{"x_resolution"} = $x * $dpi;
$state->{"y_resolution"} = $y * $dpi;
$state->{"best_form"} = 3;
}
return 1
}
}
return undef;
}
#
# _convertResolution: convert image resolution
# $value: resolution
# $fromImage: true if parsing from Image::Info; false if parameter
#
sub _convertResolution {
my $value = shift;
my $fromImage = shift;
#
# Init state
#
my $state = {
x_resolution => $DEFAULT_RESOLUTION,
y_resolution => $DEFAULT_RESOLUTION,
best_form => 0,
};
#
# Resolution returned from image_info can either be a string or
# a reference to an array of strings, each in one of the forms
# described above in _parseResolution. For example, the resolution
# may be returned as: [ "300 dpi", "1/1" ]. We take the best form
# offered. If the resolution was given explicitly as a parameter
# to Graphics::Fig, it must be a single valid string.
#
if ($fromImage && ref($value) eq "ARRAY") {
foreach my $temp (@{$value}) {
&_parseResolution($temp, $state);
}
} else {
if (!&_parseResolution($value, $state) && !$fromImage) {
croak("picture: error: ${value}: invalid resolution");
}
}
return [ $state->{"x_resolution"}, $state->{"y_resolution"} ];
}
#
# Graphics::Fig::Polyline::convertResolution
# $fig: Fig instance
# $prefix: error message prefix
# $value: angle (degrees)
# $context: parameter context
#
sub convertResolution {
my $fig = shift;
my $prefix = shift;
my $value = shift;
my $context = shift;
return &_convertResolution($value, 0);
}
my @PolylineCommonParameters = (
\%Graphics::Fig::Parameters::UnitsParameter, # must be first
\%Graphics::Fig::Parameters::PositionParameter, # must be second
\%Graphics::Fig::Parameters::ColorParameter,
\%Graphics::Fig::Parameters::DepthParameter,
@Graphics::Fig::Parameters::FillParameters,
\%Graphics::Fig::Parameters::JoinStyleParameter,
@Graphics::Fig::Parameters::LineParameters,
\%Graphics::Fig::Parameters::PointsParameter,
);
#
# Polyline Parameters
#
my %PolylineParameterTemplate = (
positional => {
"@" => [ "points" ],
},
named => [
@PolylineCommonParameters,
@Graphics::Fig::Parameters::ArrowParameters,
\%Graphics::Fig::Parameters::CapStyleParameter,
],
);
#
# Lineto Parameters
#
my %LinetoParameterTemplate = (
positional => {
".." => [ "distance", "heading" ],
"@" => [ "points" ],
},
named => [
@PolylineCommonParameters,
@Graphics::Fig::Parameters::ArrowParameters,
\%Graphics::Fig::Parameters::CapStyleParameter,
{
name => "distance",
convert => \&Graphics::Fig::Parameters::convertLength,
},
{
name => "heading",
convert => \&Graphics::Fig::Parameters::convertAngle,
},
{
name => "detachedLineto",
convert => \&Graphics::Fig::Parameters::convertBool,
aliases => [ "new" ],
}
],
);
#
# Box Parameters
#
my %BoxParameterTemplate = (
positional => {
".." => [ "width", "height" ],
"@" => [ "points" ],
},
named => [
@PolylineCommonParameters,
\%Graphics::Fig::Parameters::CenterParameter,
\%Graphics::Fig::Parameters::CornerRadiusParameter,
{
name => "width",
convert => \&Graphics::Fig::Parameters::convertLength,
},
{
name => "height",
convert => \&Graphics::Fig::Parameters::convertLength,
},
],
);
#
# Polygon Parameters
#
my %PolygonParameterTemplate = (
positional => {
".." => [ "n", "r" ],
"@" => [ "points" ],
},
named => [
@PolylineCommonParameters,
\%Graphics::Fig::Parameters::CenterParameter,
\%Graphics::Fig::Parameters::RotationParameter,
{
name => "n",
convert => \&Graphics::Fig::Parameters::convertInt,
},
{
name => "r",
convert => \&Graphics::Fig::Parameters::convertLength,
aliases => [ "radius" ],
},
],
);
#
# Picture Parameters
#
my %PictureParameterTemplate = (
positional => {
"" => [ ],
"." => [ "filename" ],
".." => [ "filename", "width" ],
"..." => [ "filename", "width", "height" ],
".@" => [ "filename", "points" ],
},
named => [
@PolylineCommonParameters,
\%Graphics::Fig::Parameters::CenterParameter,
{
name => "filename",
},
{
name => "width",
convert => \&Graphics::Fig::Parameters::convertLength,
},
{
name => "height",
convert => \&Graphics::Fig::Parameters::convertLength,
},
{
name => "resolution",
convert => \&convertResolution,
},
],
);
#
# Graphics::Fig::Polyline::new: base constructor
# $proto: prototype
# $parameters: ref to parameter hash
#
sub new {
my $proto = shift;
my $subtype = shift;
my $parameters = shift;
my $self = {
subtype => $subtype,
lineStyle => ${$parameters}{"lineStyle"},
lineThickness => ${$parameters}{"lineThickness"},
penColor => ${$parameters}{"penColor"},
fillColor => ${$parameters}{"fillColor"},
depth => ${$parameters}{"depth"},
areaFill => ${$parameters}{"areaFill"},
styleVal => ${$parameters}{"styleVal"},
joinStyle => ${$parameters}{"joinStyle"},
capStyle => 0,
cornerRadius => 0,
fArrow => undef,
bArrow => undef,
points => [],
};
my $class = ref($proto) || $proto;
bless($self, $class);
return $self;
}
#
# Graphics::Fig::Polyline::polyline constructor
# $proto: prototype
# $fig: parent object
# @parameters: polyline parameters
#
sub polyline {
my $proto = shift;
my $fig = shift;
#
# Parse parameters.
#
my %parameters;
my $stack = ${$fig}{"stack"};
my $tos = ${$stack}[$#{$stack}];
eval {
Graphics::Fig::Parameters::parse($fig, "polyline",
\%PolylineParameterTemplate,
${$tos}{"options"}, \%parameters, @_);
};
if ($@) {
$@ =~ s/ at [^\s]* line \d+\.\n//;
croak("$@");
}
#
# Make sure that at least two points were given.
#
my $temp;
if (!defined($temp = $parameters{"points"}) || scalar(@{$temp} < 2)) {
croak("polyline: error: at least two points must be given");
}
#
# Set remaining parameters.
#
my $self = $proto->new(1, \%parameters);
${$self}{"capStyle"} = $parameters{"capStyle"};
${$self}{"points"} = $parameters{"points"};
Graphics::Fig::Parameters::copyArrowParameters($self, \%parameters);
push(@{${$tos}{"objects"}}, $self);
return $self;
}
#
# Graphics::Fig::Polyline::lineto
# $proto: prototype
# $fig: parent object
# @parameters: polygon parameters
#
sub lineto {
my $proto = shift;
my $fig = shift;
my $self;
#
# Parse parameters.
#
my %parameters;
my $stack = ${$fig}{"stack"};
my $tos = ${$stack}[$#{$stack}];
eval {
Graphics::Fig::Parameters::parse($fig, "lineto",
\%LinetoParameterTemplate,
${$tos}{"options"}, \%parameters, @_);
};
if ($@) {
$@ =~ s/ at [^\s]* line \d+\.\n//;
croak("$@");
}
#
# Check parameters and get the new points.
#
my $newPoints = $parameters{"points"};
if (!defined($newPoints)) {
if (!defined($parameters{"distance"})) {
croak("lineto error: expected distance and heading, or points");
}
if (!defined($parameters{"heading"})) {
croak("lineto error: expected distance and heading, or points");
}
$newPoints = [[
$parameters{"position"}[0] +
$parameters{"distance"} * cos($parameters{"heading"}),
$parameters{"position"}[1] -
$parameters{"distance"} * sin($parameters{"heading"})
]];
} else {
if (defined($parameters{"distance"})) {
croak("lineto error: distance cannot be given with points");
}
if (defined($parameters{"heading"})) {
croak("lineto error: heading cannot be given with points");
}
if (scalar(@{$newPoints}) == 0) {
croak("lineto error: expected at least one point");
}
}
#
# If we have an open lineto object, get the object, curPoints and
# finalPoint.
#
my $curPoints;
my $finalPoint;
if (defined($self = ${$tos}{"openLineto"})) {
$curPoints = ${$self}{"points"};
$finalPoint = ${$curPoints}[$#{$curPoints}];
}
#
# If we don't have an open lineto object, or if any parameter has
# changed from the existing object, construct a new object.
#
my $position = $parameters{"position"};
if (!defined($self) || !defined($finalPoint) ||
$parameters{"detachedLineto"} ||
${$position}[0] != ${$finalPoint}[0] ||
${$position}[1] != ${$finalPoint}[1] ||
${$self}{"lineStyle"} != $parameters{"lineStyle"} ||
${$self}{"lineThickness"} != $parameters{"lineThickness"} ||
${$self}{"penColor"} != $parameters{"penColor"} ||
${$self}{"fillColor"} != $parameters{"fillColor"} ||
${$self}{"depth"} != $parameters{"depth"} ||
${$self}{"areaFill"} != $parameters{"areaFill"} ||
${$self}{"styleVal"} != $parameters{"styleVal"} ||
${$self}{"joinStyle"} != $parameters{"joinStyle"} ||
${$self}{"capStyle"} != $parameters{"capStyle"} ||
Graphics::Fig::Parameters::compareArrowParameters($self,
\%parameters) != 0) {
$self = $proto->new(1, \%parameters);
${$self}{"capStyle"} = $parameters{"capStyle"};
${$self}{"points"} = $parameters{"points"};
Graphics::Fig::Parameters::copyArrowParameters($self, \%parameters);
$curPoints = [ $position ];
${$self}{"points"} = $curPoints;
push(@{${$tos}{"objects"}}, $self);
${$tos}{"openLineto"} = $self;
}
#
# Add the new points and set position to the final point.
#
push(@{$curPoints}, @{$newPoints});
${$tos}{"options"}{"position"} = ${$newPoints}[$#{$newPoints}];
return $self;
}
#
# Graphics::Fig::Polyline::box constructor
# $proto: prototype
# $fig: parent object
# @parameters: box parameters
#
sub box {
my $proto = shift;
my $fig = shift;
#
# Parse parameters.
#
my %parameters;
my $stack = ${$fig}{"stack"};
my $tos = ${$stack}[$#{$stack}];
eval {
Graphics::Fig::Parameters::parse($fig, "box",
\%BoxParameterTemplate,
${$tos}{"options"}, \%parameters, @_);
};
if ($@) {
$@ =~ s/ at [^\s]* line \d+\.\n//;
croak("$@");
}
#
# Construct the object.
#
my $self;
my $cornerRadius = $parameters{"cornerRadius"};
if (defined($cornerRadius) && $cornerRadius != 0) {
$self = $proto->new(4, \%parameters);
${$self}{"cornerRadius"} = $cornerRadius;
} else {
$self = $proto->new(2, \%parameters);
}
#
# Construct the box from two corners.
#
my $temp;
if (defined($temp = $parameters{"points"})) {
my ($x1, $y1, $x2, $y2);
if (defined($parameters{"width"})) {
croak("box: error: width not allowed with points");
}
if (defined($parameters{"height"})) {
croak("box: error: height not allowed with points");
}
if (defined($parameters{"center"})) {
croak("box: error: center not allowed with points");
}
if (scalar(@{$temp}) == 1) {
($x1, $y1) = @{$parameters{"position"}};
($x2, $y2) = @{${$temp}[0]};
} elsif (scalar(@{$temp}) == 2) {
($x1, $y1) = @{${$temp}[0]};
($x2, $y2) = @{${$temp}[1]};
} else {
croak("box: error: expected 1 or 2 points");
}
${$self}{"points"} = [
[ $x1, $y1 ], [ $x2, $y1 ], [ $x2, $y2 ], [ $x1, $y2 ], [ $x1, $y1 ]
];
} elsif (defined(my $width = $parameters{"width"}) &&
defined(my $height = $parameters{"height"})) {
my ($xc, $yc);
if (defined($parameters{"center"})) {
($xc, $yc) = @{$parameters{"center"}};
} else {
($xc, $yc) = @{$parameters{"position"}};
}
my $dx = $width / 2.0;
my $dy = $height / 2.0;
${$self}{"points"} = [
[ $xc - $dx, $yc - $dy ],
[ $xc + $dx, $yc - $dy ],
[ $xc + $dx, $yc + $dy ],
[ $xc - $dx, $yc + $dy ],
[ $xc - $dx, $yc - $dy ]
];
} else {
croak("box: error: expected width and height or 1 or 2 points");
}
push(@{${$tos}{"objects"}}, $self);
return $self;
}
#
# Graphics::Fig::Polyline::polygon constructor
# $proto: prototype
# $fig: parent object
# @parameters: polygon parameters
#
sub polygon {
my $proto = shift;
my $fig = shift;
#
# Parse parameters.
#
my %parameters;
my $stack = ${$fig}{"stack"};
my $tos = ${$stack}[$#{$stack}];
eval {
Graphics::Fig::Parameters::parse($fig, "polygon",
\%PolygonParameterTemplate,
${$tos}{"options"}, \%parameters, @_);
};
if ($@) {
$@ =~ s/ at [^\s]* line \d+\.\n//;
croak("$@");
}
#
# Construct the object.
#
my $self = $proto->new(3, \%parameters);
#
# Regular Polygon
#
my $n;
if (defined($n = $parameters{"n"})) {
my $center;
my $rotation = 0.0;
my $firstPoint;
my $basePoint; # first with center at origin
#
# Minimum n is 3.
#
if ($n < 3) {
croak("polygon: error: n must be at least 3");
}
#
# Find the center.
#
if (defined($parameters{"center"})) {
$center = $parameters{"center"};
} else {
$center = $parameters{"position"};
}
#
# Get the first point.
#
if (defined($parameters{"points"})) {
my $points = $parameters{"points"};
if (scalar(@{$points}) != 1) {
croak("polygon: error: only one point allowed with n");
}
$firstPoint = ${$points}[0];
$basePoint = [ ${$firstPoint}[0] - ${$center}[0],
${$firstPoint}[1] - ${$center}[1] ];
if (defined($parameters{"r"})) {
croak("polygon: error: r not allowed with points");
}
if (defined($parameters{"rotation"})) {
croak("polygon: error: rotation not allowed with points");
}
} else {
my $r;
if (!defined($r = $parameters{"r"})) {
croak("polygon: error: r parameter required");
}
if (defined($parameters{"rotation"})) {
$rotation = $parameters{"rotation"};
}
$basePoint = [ $r * cos($rotation), -$r * sin($rotation) ];
$firstPoint = [ ${$basePoint}[0] + ${$center}[0],
${$basePoint}[1] + ${$center}[1] ];
}
push(@{${$self}{"points"}}, $firstPoint);
for (my $i = 1; $i < $n; ++$i) {
my $c = cos(2 * pi * $i / $n);
my $s = sin(2 * pi * $i / $n);
my $point = [
$c * ${$basePoint}[0] + $s * ${$basePoint}[1] + ${$center}[0],
-$s * ${$basePoint}[0] + $c * ${$basePoint}[1] + ${$center}[1]
];
push(@{${$self}{"points"}}, $point);
}
#
# Polygon from Points
#
} else {
my $points = $parameters{"points"};
if (scalar(@{$points}) < 3) {
croak("polygon: error: expected n or at least 3 points");
}
if (defined($parameters{"r"})) {
croak("polygon: error: r not allowed with points");
}
if (defined($parameters{"rotation"})) {
croak("polygon: error: rotation not allowed with points");
}
@{${$self}{"points"}} = @{$points};
}
#
# Duplicate the first point.
#
{
my $points = ${$self}{"points"};
push(@{$points}, ${$points}[0]);
}
push(@{${$tos}{"objects"}}, $self);
return $self;
}
#
# Graphics::Fig::Polyline::picture constructor
# $proto: prototype
# $fig: parent object
# @parameters: picture parameters
#
sub picture {
my $proto = shift;
my $fig = shift;
#
# Parse parameters.
#
my %parameters;
my $stack = ${$fig}{"stack"};
my $tos = ${$stack}[$#{$stack}];
eval {
Graphics::Fig::Parameters::parse($fig, "pictures",
\%PictureParameterTemplate,
${$tos}{"options"}, \%parameters, @_);
};
if ($@) {
$@ =~ s/ at [^\s]* line \d+\.\n//;
croak("$@");
}
#
# Make sure the filename was given.
#
my $filename = $parameters{"filename"};
if (!defined($filename)) {
croak("picture: error: filename must be given");
}
if ($filename =~ m/\n/) {
croak("picture: error: invalid filename");
}
#
# Construct the object.
#
my $self = $proto->new(5, \%parameters);
${$self}{"filename"} = $filename;
${$self}{"flipped"} = 0;
#
# Construct the bounding box from two corners.
#
my $temp;
if (defined($temp = $parameters{"points"})) {
my ($x1, $y1, $x2, $y2);
if (defined($parameters{"width"})) {
croak("picture: error: width not allowed with points");
}
if (defined($parameters{"height"})) {
croak("picture: error: height not allowed with points");
}
if (defined($parameters{"center"})) {
croak("picture: error: center not allowed with points");
}
if (scalar(@{$temp}) == 1) {
($x1, $y1) = @{$parameters{"position"}};
($x2, $y2) = @{${$temp}[0]};
} elsif (scalar(@{$temp}) == 2) {
($x1, $y1) = @{${$temp}[0]};
($x2, $y2) = @{${$temp}[1]};
} else {
croak("picture: error: expected 1 or 2 points");
}
${$self}{"points"} = [
[ $x1, $y1 ], [ $x2, $y1 ], [ $x2, $y2 ], [ $x1, $y2 ], [ $x1, $y1 ]
];
} else {
#
# Find the center.
#
my ($xc, $yc);
if (defined($parameters{"center"})) {
( $xc, $yc ) = @{$parameters{"center"}};
} else {
( $xc, $yc ) = @{$parameters{"position"}};
}
#
# Find width and height. If the size is not completely specified,
# compute the missing width and height from the image properties.
#
my $width = $parameters{"width"};
my $height = $parameters{"height"};
my $resolution = $parameters{"resolution"};
if (!defined($width) || !defined($height)) {
my $info = image_info($filename);
if (my $error = ${$info}{"error"}) {
croak("picture: error: ${error}");
}
if (!defined($resolution)) {
$resolution = &_convertResolution(${$info}{"resolution"}, 1);
}
die "picture: internal error" unless ref($resolution) eq "ARRAY";
my $nWidth = ${$info}{"width"};
my $nHeight = ${$info}{"height"};
if (!defined($nWidth) || $nWidth <= 0.0 ||
!defined($nHeight) || $nHeight <= 0.0) {
croak("picture: error: cannot determine image size");
}
$nWidth /= ${$resolution}[0];
$nHeight /= ${$resolution}[1];
if (defined($width)) {
$height = $nHeight * $width / $nWidth;
} elsif (defined($height)) {
$width = $nWidth * $height / $nHeight;
} else {
$width = $nWidth;
$height = $nHeight;
}
}
my $dx = $width / 2.0;
my $dy = $height / 2.0;
${$self}{"points"} = [
[ $xc - $dx, $yc - $dy ],
[ $xc + $dx, $yc - $dy ],
[ $xc + $dx, $yc + $dy ],
[ $xc - $dx, $yc + $dy ],
[ $xc - $dx, $yc - $dy ]
];
}
push(@{${$tos}{"objects"}}, $self);
return $self;
}
#
# Graphics::Fig::Polyline::translate
# $self: object
# $parameters: reference to parameter hash
#
sub translate {
my $self = shift;
my $parameters = shift;
@{${$self}{"points"}} = Graphics::Fig::Parameters::translatePoints(
$parameters, @{${$self}{"points"}});
return 1;
}
#
# Graphics::Fig::Polyline::rotate
# $self: object
# $parameters: reference to parameter hash
#
sub rotate {
my $self = shift;
my $parameters = shift;
my $rotation = ${$parameters}{"rotation"};
@{${$self}{"points"}} = Graphics::Fig::Parameters::rotatePoints(
$parameters, @{${$self}{"points"}});
# Change box and arc-box to polygon if rotated to a non right angle.
my $subtype = ${$self}{"subtype"};
if (sin($rotation) * cos($rotation) != 0 &&
($subtype == 2 || $subtype == 4)) {
${$self}{"subtype"} = 3;
}
return 1;
}
#
# Graphics::Fig::Polyline::scale
# $self: object
# $parameters: reference to parameter hash
#
sub scale {
my $self = shift;
my $parameters = shift;
@{${$self}{"points"}} = Graphics::Fig::Parameters::scalePoints(
$parameters, @{${$self}{"points"}});
my $subtype = ${$self}{"subtype"};
if ($subtype == 5) {
my $scale = ${$parameters}{"scale"};
if (${$scale}[0] * ${$scale}[1] < 0) {
${$self}{"flipped"} ^= 1;
}
}
}
#
# Graphics::Fig::Polyline return [[xmin, ymin], [xmax, ymax]]
# $self: object
# $parameters: getbbox parameters
#
sub getbbox {
my $self = shift;
my $parameters = shift;
return Graphics::Fig::Parameters::getbboxFromPoints(@{${$self}{"points"}});
}
#
# Graphics::Fig::Polyline::print
# $self: object
# $fh: reference to output file handle
# $parameters: save parameters
#
sub print {
my $self = shift;
my $fh = shift;
my $parameters = shift;
my $figPerInch = Graphics::Fig::_figPerInch($parameters);
my $subtype = ${$self}{"subtype"};
#
# Print
#
printf $fh ("2 %d %d %.0f %d %d %d -1 %d %.3f %d %d %.0f %d %d %d\n",
$subtype,
${$self}{"lineStyle"},
${$self}{"lineThickness"} * 80.0,
${$self}{"penColor"},
${$self}{"fillColor"},
${$self}{"depth"},
${$self}{"areaFill"},
${$self}{"styleVal"} * 80.0,
${$self}{"joinStyle"},
${$self}{"capStyle"},
${$self}{"cornerRadius"} * 80.0,
defined(${$self}{"fArrow"}) ? 1 : 0,
defined(${$self}{"bArrow"}) ? 1 : 0,
scalar(@{${$self}{"points"}}));
Graphics::Fig::Parameters::printArrowParameters($self, $fh, $parameters);
if ($subtype == 5) {
printf $fh (" %d %s\n", ${$self}{"flipped"}, ${$self}{"filename"});
}
foreach my $point (@{${$self}{"points"}}) {
printf $fh ("\t%.0f %.0f\n",
${$point}[0] * $figPerInch,
${$point}[1] * $figPerInch);
}
}
1;