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

use strict;
use Carp;
my %imap = (
m => [qw/x y/], # move to
z => [], # close path
l => [qw/x y/], # line to
h => ['x'], # horz line
v => ['y'], # vert line
c => [qw/x1 y1 x2 y2 x y/], # Curveto Cubic Bezier
s => [qw/x1 y1 x y/], # Smooth Cubic Bezier
q => [qw/x1 y1 x y/], # Curceto Quadratic Bézier
t => [qw/x y/], # Smooth Quadratic Bézier
a => [qw/rx ry xr l s x y/], # Eliptical Arc Curve
);
sub new
{
my ($proto, %opts) = @_;
return $proto->SUPER::new(%opts);
}
sub serialise
{
my ($self) = @_;
my $lastmode = '';
my $result = '';
foreach my $inst ($self->instructions) {
my %inst = %{$inst};
$result .= ' ' if length($result);
my $rel = $inst{'rel'};
my $mode = $inst{'mode'};
my @c = @inst{@{$imap{$mode}}};
$mode = uc($mode) if not $rel;
$result .= $mode.' ' if $mode ne $lastmode;
$result .= join(' ', @c) if @c;
$lastmode = $mode;
}
$result =~ s/\s+$//;
return $result;
}
sub deserialise
{
my ($self, $path) = @_;
$path =~ s/(\+|-)/ $1/g;
$path =~ s/([MmZzLlHhVvCcSsQqTtAa])/ $1 /g;
$path =~ s/,/ /g;
$path =~ s/^\s+//;
$path =~ s/\s+$//;
my @path = split(/\s+/, $path);
my @inst;
my $mode;
for(my $i = 0; $i <= $#path; $i++) {
my $s = $path[$i];
next if not defined($s);
if(defined($imap{lc($s)})) {
$mode = $s;
if(not @{$imap{lc($mode)}}) {
# Make sure blind instructions are added
push @inst, _instruction($mode);
} else {
next;
}
} elsif($s =~ /^\-*\d+\.*\d*$/) {
# Data for current mode
my $length = @{$imap{lc($mode)}};
my $end = $i + $length;
# Next i if no co-ords to gather
next if not $end;
# Add the instruction
push @inst, _instruction($mode, @path[$i..$end]);
# Inplicit lineto after moveto
$mode = 'l' if($mode eq 'm');
$mode = 'L' if($mode eq 'M');
$i += $length - 1;
} else {
die "Error in path, unexpected instruction '$s' - ".join(', ', keys(%imap))."\n";
}
}
$self->{'path'} = \@inst;
return $self;
}
sub _instruction
{
my ($mode, @c) = @_;
my $reletive = ($mode =~ /A-Z/) ? 1 : 0;
$mode = lc($mode);
my %inst = ( mode => $mode, rel => $reletive );
@inst{@{$imap{$mode}}} = @c if @c;
return \%inst;
}
sub instructions
{
my ($self) = @_;
return @{$self->{'path'}};
}
return 1;