File Coverage

blib/lib/Image/SVG/Path.pm
Criterion Covered Total %
statement 227 301 75.4
branch 121 176 68.7
condition 8 15 53.3
subroutine 8 9 88.8
pod 3 6 50.0
total 367 507 72.3


line stmt bran cond sub pod time code
1             package Image::SVG::Path;
2 7     7   415427 use warnings;
  7         64  
  7         211  
3 7     7   32 use strict;
  7         11  
  7         842  
4             require Exporter;
5             our @ISA = qw(Exporter);
6             our @SVG_REGEX = qw/
7             $closepath
8             $curveto
9             $smooth_curveto
10             $drawto_command
11             $drawto_commands
12             $elliptical_arc
13             $horizontal_lineto
14             $lineto
15             $moveto
16             $quadratic_bezier_curveto
17             $smooth_quadratic_bezier_curveto
18             $svg_path
19             $vertical_lineto
20             /;
21              
22             our @FUNCTIONS = qw/extract_path_info reverse_path create_path_string/;
23             our @EXPORT_OK = (@FUNCTIONS, @SVG_REGEX);
24             our %EXPORT_TAGS = (all => \@FUNCTIONS, regex => \@SVG_REGEX);
25             our $VERSION = '0.34';
26              
27 7     7   57 use Carp;
  7         11  
  7         24392  
28              
29             # These are the fields in the "arc" hash which is returned when an "A"
30             # command is processed.
31              
32             my @arc_fields = qw/rx ry x_axis_rotation large_arc_flag sweep_flag x y/;
33              
34             # Return "relative" or "absolute" depending on whether the command is
35             # upper or lower case.
36              
37             sub position_type
38             {
39 201     201 0 296 my ($curve_type) = @_;
40 201 100       352 if (lc $curve_type eq $curve_type) {
    50          
41 84         136 return "relative";
42             }
43             elsif (uc $curve_type eq $curve_type) {
44 117         216 return "absolute";
45             }
46             else {
47 0         0 croak "I don't know what to do with '$curve_type'";
48             }
49             }
50              
51             sub add_coords
52             {
53 30     30 0 40 my ($first_ref, $to_add_ref) = @_;
54 30         49 $first_ref->[0] += $to_add_ref->[0];
55 30         44 $first_ref->[1] += $to_add_ref->[1];
56             }
57              
58             sub reverse_path
59             {
60 0     0 1 0 my ($path) = @_;
61 0         0 my $me = 'reverse_path';
62 0 0       0 if (! $path) {
63 0         0 croak "$me: no input";
64             }
65 0         0 my @values = extract_path_info ($path, {
66             no_smooth => 1,
67             absolute => 1,
68             });
69 0 0       0 if (! @values) {
70 0         0 return '';
71             }
72 0         0 my @rvalues;
73 0         0 my $end_point = $values[0]->{point};
74 0         0 for my $value (@values[1..$#values]) {
75 0         0 my $element = {};
76 0         0 $element->{type} = $value->{type};
77             # print "$element->{type}\n";
78 0 0       0 if ($value->{type} eq 'cubic-bezier') {
79 0         0 $element->{control1} = $value->{control2};
80 0         0 $element->{control2} = $value->{control1};
81 0         0 $element->{end} = $end_point;
82 0         0 $end_point = $value->{end};
83             }
84             else {
85 0         0 croak "Can't handle path element type '$value->{type}'";
86             }
87 0         0 unshift @rvalues, $element;
88             }
89 0         0 my $moveto = {
90             type => 'moveto',
91             point => $end_point,
92             };
93 0         0 unshift @rvalues, $moveto;
94 0         0 my $rpath = create_path_string (\@rvalues);
95 0         0 return $rpath;
96             }
97              
98             sub create_path_string
99             {
100 1     1 1 263 my ($info_ref) = @_;
101 1         3 my $path = '';
102 1         3 for my $element (@$info_ref) {
103 31         38 my $t = $element->{type};
104             # print "$t\n";
105 31 100       67 if ($t eq 'moveto') {
    50          
    100          
    100          
    100          
    100          
    50          
106 2         3 my @p = @{$element->{point}};
  2         5  
107 2         17 $path .= sprintf ("M%f,%f ", @p);
108             }
109             elsif ($t eq 'cubic-bezier') {
110 0         0 my @c1 = @{$element->{control1}};
  0         0  
111 0         0 my @c2 = @{$element->{control2}};
  0         0  
112 0         0 my @e = @{$element->{end}};
  0         0  
113 0         0 $path .= sprintf ("C%f,%f %f,%f %f,%f ", @c1, @c2, @e);
114             }
115             elsif ($t eq 'closepath') {
116 2         4 $path .= "Z";
117             }
118             elsif ($t eq 'vertical-line-to') {
119 14         37 $path .= sprintf ("V%f ", $element->{y});
120             }
121             elsif ($t eq 'horizontal-line-to') {
122 8         26 $path .= sprintf ("H%f ", $element->{x});
123             }
124             elsif ($t eq 'line-to') {
125 4         4 $path .= sprintf ("L%f,%f ", @{$element->{point}});
  4         18  
126             }
127             elsif ($t eq 'arc') {
128 1         3 my @f = map {sprintf ("%f", $element->{$_})} @arc_fields;
  7         22  
129 1         5 $path .= "A ". join (',', @f) . " ";
130             }
131             else {
132 0         0 croak "Don't know how to deal with type '$t'";
133             }
134             }
135 1         3 return $path;
136             }
137              
138             # Match the e or E in an exponent.
139              
140             my $e = qr/[eE]/;
141              
142             # This whitespace regex is from the SVG grammar,
143             # https://www.w3.org/TR/SVG/paths.html#PathDataBNF.
144              
145             my $wsp = qr/[\x20\x09\x0D\x0A]/;
146              
147             # The latter commented-out part of this regex fixes a backtracking
148             # problem caused by numbers like 123-234 which are supposed to be
149             # parsed as two numbers "123" and "-234", as if containing a
150             # comma. The regular expression blows up and cannot handle this
151             # format. However, adding this final part slows the module down by a
152             # factor of about 25%, so they are commented out.
153              
154             my $comma_wsp = qr/$wsp+|$wsp*,$wsp*/;#|(?<=[0-9])(?=-)/;
155              
156             # The following regular expression splits the path into pieces. Note
157             # this only splits on '-' or '+' when not preceeded by 'e'. This
158             # regular expression is not following the SVG grammar, it is going our
159             # own way.
160              
161             # Regular expressions to match numbers
162              
163             # Digit sequence
164              
165             my $ds = qr/[0-9]+/;
166              
167             my $sign = qr/[\+\-]/;
168              
169             # Fractional constant
170              
171             my $fc = qr/$ds?\.$ds/;
172              
173             my $exponent = qr/$e$sign?$ds/x;
174              
175             # Floating point constant
176              
177             my $fpc = qr/
178             $fc
179             $exponent?
180             |
181             $ds
182             $exponent
183             /x;
184              
185              
186             # Non-negative number. $floating_point_constant needs to go before
187             # $ds, otherwise it matches the shorter $ds every time.
188              
189             my $nnn = qr/
190             $fpc
191             |
192             $ds
193             /x;
194              
195             my $number = qr/$sign?$nnn/;
196              
197             my $pair = qr/$number$comma_wsp?$number/;
198              
199             my $pairs = qr/(?:$pair$wsp)*$pair/;
200              
201             my $numbers = qr/(?:$number$wsp)*$number/;
202              
203             # Quadratic bezier curve
204              
205             my $qarg = qr/$pair$comma_wsp?$pair/;
206              
207             our $quadratic_bezier_curveto =
208             qr/
209             ([Qq])
210             $wsp*
211             (
212             (?:$qarg $comma_wsp?)*
213             $qarg
214             )
215             /x;
216              
217             our $smooth_quadratic_bezier_curveto =
218             qr/
219             ([Tt])
220             $wsp*
221             (
222             (?:$pair $comma_wsp?)*
223             $pair
224             )
225             /x;
226              
227             # Cubic bezier curve
228              
229             my $sarg = qr/$pair$comma_wsp?$pair/;
230              
231             our $smooth_curveto = qr/
232             ([Ss])
233             $wsp*
234             (
235             (?:
236             $sarg
237             $comma_wsp
238             )*
239             $sarg
240             )
241             /x;
242              
243             my $carg = qr/(?:$pair $comma_wsp?){2} $pair/x;
244              
245             our $curveto = qr/
246             ([Cc])
247             $wsp*
248             (
249             (?:$carg $comma_wsp)*
250             $carg
251             )
252             /x;
253              
254             my $flag = qr/[01]/;
255              
256             my $eaa = qr/
257             $nnn
258             $comma_wsp?
259             $nnn
260             $comma_wsp?
261             $number
262             $comma_wsp
263             $flag
264             $comma_wsp?
265             $flag
266             $comma_wsp?
267             $pair
268             /x;
269              
270             our $elliptical_arc = qr/([Aa]) $wsp* ((?:$eaa $comma_wsp?)* $eaa)/x;
271              
272             our $vertical_lineto = qr/([Vv]) $wsp* ($numbers)/x;
273              
274             our $horizontal_lineto = qr/([Hh]) $wsp* ($numbers)/x;
275              
276             our $lineto = qr/([Ll]) $wsp* ($pairs)/x;
277              
278             our $closepath = qr/([Zz])/;
279              
280             our $moveto = qr/
281             ([Mm]) $wsp* ($pairs)
282             /x;
283              
284             our $drawto_command = qr/
285             (
286             $closepath
287             |
288             $lineto
289             |
290             $horizontal_lineto
291             |
292             $vertical_lineto
293             |
294             $curveto
295             |
296             $smooth_curveto
297             |
298             $quadratic_bezier_curveto
299             |
300             $smooth_quadratic_bezier_curveto
301             |
302             $elliptical_arc
303             )
304             /x;
305              
306             our $drawto_commands = qr/
307             (?:$drawto_command $wsp)*
308             $drawto_command
309             /x;
310             our $mdc_group = qr/
311             $moveto
312             $wsp*
313             $drawto_commands
314             /x;
315              
316             my $mdc_groups = qr/
317             $mdc_group+
318             /x;
319              
320             our $moveto_drawto_command_groups = $mdc_groups;
321              
322             our $svg_path = qr/
323             $wsp*
324             $mdc_groups?
325             $wsp*
326             /x;
327              
328             # Old regex.
329              
330             #my $number_re = qr/(?:[\+\-0-9.]|$e)+/i;
331              
332             # This is where we depart from the SVG grammar and go our own way.
333              
334             my $numbers_re = qr/(?:$number|$comma_wsp+)*/;
335              
336             sub extract_path_info
337             {
338 63     63 1 65925 my ($path, $options_ref) = @_;
339             # Error/message reporting thing. Not sure why I did this now.
340 63         94 my $me = 'extract_path_info';
341 63 50       132 if (! $path) {
342 0         0 croak "$me: no input";
343             }
344             # Create an empty options so that we don't have to
345             # keep testing whether the "options" string is defined or not
346             # before trying to read a hash value from it.
347 63 100       116 if ($options_ref) {
348 31 50       77 if (ref $options_ref ne 'HASH') {
349 0         0 croak "$me: second argument should be a hash reference";
350             }
351             }
352             else {
353 32         47 $options_ref = {};
354             }
355 63 50       117 if (! wantarray) {
356 0         0 croak "$me: extract_path_info returns an array of values";
357             }
358 63         106 my $verbose = $options_ref->{verbose};
359 63 100       100 if ($verbose) {
360 2         21 print "$me: I am trying to split up '$path'.\n";
361             }
362 63         83 my @path_info;
363 63         372 my @path = split /([cslqtahvzm])/i, $path;
364 63 50 33     628 if ( $path[0] !~ /^$wsp*$/ || $path[1] !~ /[Mm]/ ) {
365 0         0 croak "No moveto at start of path '$path'";
366             }
367 63         107 shift @path;
368 63         92 my $path_pos=0;
369 63         70 my @curves;
370 63         131 while ( $path_pos < scalar @path ) {
371 201         299 my $command = $path[$path_pos];
372 201         266 my $values = $path[$path_pos+1];
373 201 100       315 if (! defined $values) {
374 28         45 $values = '';
375             }
376 201         301 my $original = "${command}${values}";
377 201 50       12500 if ($original !~ /$moveto|$drawto_command/x) {
378 0         0 warn "Cannot parse '$original' using moveto/drawto_command regex";
379             }
380 201         965 $values=~s/^$wsp*//;
381 201         508 push @curves, [$command, $values, $original];
382 201         499 $path_pos+=2;
383             }
384 63         102 for my $curve_data (@curves) {
385 201         365 my ($command, $values) = @$curve_data;
386             # print "$curve\n";
387 201         1712 my @numbers = ($values =~ /($number)/g);
388             # Remove leading plus signs to keep the same behaviour as
389             # before.
390 201         329 @numbers = map {s/^\+//; $_} @numbers;
  576         663  
  576         928  
391             # print "@numbers\n";
392 201 100       339 if ($verbose) {
393 12         104 printf "$me: Extracted %d numbers: %s\n", scalar (@numbers),
394             join (" ! ", @numbers);
395             }
396 201 100       828 if (uc $command eq 'C') {
    100          
    100          
    100          
    100          
    100          
    100          
    100          
    100          
    50          
397 9         15 my $expect_numbers = 6;
398 9 50       18 if (@numbers % $expect_numbers != 0) {
399 0         0 croak "$me: Wrong number of values for a C curve " .
400             scalar @numbers . " in '$path'";
401             }
402 9         22 my $position = position_type ($command);
403 9         28 for (my $i = 0; $i < @numbers / $expect_numbers; $i++) {
404 26         34 my $offset = $expect_numbers * $i;
405 26         50 my @control1 = @numbers[$offset + 0, $offset + 1];
406 26         44 my @control2 = @numbers[$offset + 2, $offset + 3];
407 26         41 my @end = @numbers[$offset + 4, $offset + 5];
408             # Put each of these abbreviated things into the list
409             # as a separate path.
410 26         119 push @path_info, {
411             type => 'cubic-bezier',
412             name => 'curveto',
413             position => $position,
414             control1 => \@control1,
415             control2 => \@control2,
416             end => \@end,
417             svg_key => $command,
418             };
419             }
420             }
421             elsif (uc $command eq 'S') {
422 7         10 my $expect_numbers = 4;
423 7 50       17 if (@numbers % $expect_numbers != 0) {
424 0         0 croak "$me: Wrong number of values for an S curve " .
425             scalar @numbers . " in '$path'";
426             }
427 7         13 my $position = position_type ($command);
428 7         24 for (my $i = 0; $i < @numbers / $expect_numbers; $i++) {
429 8         13 my $offset = $expect_numbers * $i;
430 8         20 my @control2 = @numbers[$offset + 0, $offset + 1];
431 8         14 my @end = @numbers[$offset + 2, $offset + 3];
432 8         46 push @path_info, {
433             type => 'smooth-cubic-bezier',
434             name => 'shorthand/smooth curveto',
435             position => $position,
436             control2 => \@control2,
437             end => \@end,
438             svg_key => $command,
439             };
440             }
441             }
442             elsif (uc $command eq 'L') {
443 29         36 my $expect_numbers = 2;
444             # Maintain this check here, even though it's duplicated
445             # inside build_lineto, because it's specific to the lineto
446 29 50       59 if (@numbers % $expect_numbers != 0) {
447 0         0 croak "Odd number of values for an L command " .
448             scalar (@numbers) . " in '$path'";
449             }
450 29         44 my $position = position_type ($command);
451 29         55 push @path_info, build_lineto ($position, @numbers);
452             }
453             elsif (uc $command eq 'Z') {
454 33 50       61 if (@numbers > 0) {
455 0         0 croak "Wrong number of values for a Z command " .
456             scalar @numbers . " in '$path'";
457             }
458 33         60 my $position = position_type ($command);
459 33         119 push @path_info, {
460             type => 'closepath',
461             name => 'closepath',
462             position => $position,
463             svg_key => $command,
464             }
465             }
466             elsif (uc $command eq 'Q') {
467 6         8 my $expect_numbers = 4;
468 6 50       17 if (@numbers % $expect_numbers != 0) {
469 0         0 croak "Wrong number of values for a Q command " .
470             scalar @numbers . " in '$path'";
471             }
472 6         12 my $position = position_type ($command);
473 6         22 for (my $i = 0; $i < @numbers / $expect_numbers; $i++) {
474 7         12 my $o = $expect_numbers * $i;
475 7         47 push @path_info, {
476             type => 'quadratic-bezier',
477             name => 'quadratic Bézier curveto',
478             position => $position,
479             control => [@numbers[$o, $o + 1]],
480             end => [@numbers[$o + 2, $o + 3]],
481             svg_key => $command,
482             }
483             }
484             }
485             elsif (uc $command eq 'T') {
486 4         8 my $expect_numbers = 2;
487 4 50       10 if (@numbers % $expect_numbers != 0) {
488 0         0 croak "$me: Wrong number of values for an T command " .
489             scalar @numbers . " in '$path'";
490             }
491 4         9 my $position = position_type ($command);
492 4         17 for (my $i = 0; $i < @numbers / $expect_numbers; $i++) {
493 5         9 my $o = $expect_numbers * $i;
494 5         30 push @path_info, {
495             type => 'smooth-quadratic-bezier',
496             name => 'Shorthand/smooth quadratic Bézier curveto',
497             position => $position,
498             end => [@numbers[$o, $o + 1]],
499             svg_key => $command,
500             }
501             }
502             }
503             elsif (uc $command eq 'H') {
504 12         19 my $position = position_type ($command);
505 12         28 for (my $i = 0; $i < @numbers; $i++) {
506 13         57 push @path_info, {
507             type => 'horizontal-line-to',
508             name => 'horizontal lineto',
509             position => $position,
510             x => $numbers[$i],
511             svg_key => $command,
512             };
513             }
514             }
515             elsif (uc $command eq 'V') {
516 21         30 my $position = position_type ($command);
517 21         43 for (my $i = 0; $i < @numbers; $i++) {
518 22         152 push @path_info, {
519             type => 'vertical-line-to',
520             name => 'vertical lineto',
521             position => $position,
522             y => $numbers[$i],
523             svg_key => $command,
524             };
525             }
526             }
527             elsif (uc $command eq 'A') {
528 8         18 my $position = position_type ($command);
529 8         10 my $expect_numbers = 7;
530 8 50       34 if (@numbers % $expect_numbers != 0) {
531 0         0 croak "$me: Need 7 parameters for arc";
532             }
533 8         32 for (my $i = 0; $i < @numbers / $expect_numbers; $i++) {
534 9         13 my $o = $expect_numbers * $i;
535 9         11 my %arc;
536 9         48 $arc{svg_key} = $command;
537 9         15 $arc{type} = 'arc';
538 9         12 $arc{name} = 'elliptical arc';
539 9         14 $arc{position} = $position;
540 9         51 @arc{@arc_fields} = @numbers[$o .. $o + 6];
541 9         41 push @path_info, \%arc;
542             }
543             }
544             elsif (uc $command eq 'M') {
545 72         90 my $expect_numbers = 2;
546 72         125 my $position = position_type ($command);
547 72 50       147 if (@numbers < $expect_numbers) {
548 0         0 croak "$me: Need at least $expect_numbers numbers for move to";
549             }
550 72 50       147 if (@numbers % $expect_numbers != 0) {
551 0         0 croak "$me: Odd number of values for an M command " .
552             scalar (@numbers) . " in '$path'";
553             }
554 72         309 push @path_info, {
555             type => 'moveto',
556             name => 'moveto',
557             position => $position,
558             point => [@numbers[0,1]],
559             svg_key => $command,
560             };
561             # M can be followed by implicit line-to commands, so
562             # consume these.
563 72 100       201 if (@numbers > $expect_numbers) {
564 9         20 my @implicit_lineto = splice @numbers, $expect_numbers;
565 9         21 push @path_info, build_lineto ($position, @implicit_lineto);
566             }
567             }
568             else {
569 0         0 croak "I don't know what to do with a curve type '$command'";
570             }
571             }
572              
573             # Now sort it out if the user wants to get rid of the absolute
574             # paths etc.
575            
576 63         108 my $absolute = $options_ref->{absolute};
577 63   100     196 my $no_smooth = $options_ref->{no_shortcuts} || $options_ref->{no_smooth};
578 63 100       98 if ($absolute) {
579 7 100       18 if ($verbose) {
580 2         13 print "Making all coordinates absolute.\n";
581             }
582 7         16 my @abs_pos = (0, 0);
583 7         10 my @start_drawing;
584             my $previous;
585 7         10 my $begin_drawing = 1; ##This will get updated after
586 7         14 for my $element (@path_info) {
587 73 100       222 if ($element->{type} eq 'moveto') {
    100          
    100          
    100          
    50          
    100          
    100          
    100          
    100          
    50          
588 13         16 $begin_drawing = 1;
589 13 100       24 if ($element->{position} eq 'relative') {
590 5         7 my $ip = $options_ref->{initial_position};
591 5 50       11 if ($ip) {
592 0 0 0     0 if (ref $ip ne 'ARRAY' ||
593             scalar @$ip != 2) {
594 0         0 croak "$me: The initial position supplied doesn't look like a pair of coordinates";
595             }
596 0         0 add_coords ($element->{point}, $ip);
597             }
598             else {
599 5         9 add_coords ($element->{point}, \@abs_pos);
600             }
601             }
602 13         15 @abs_pos = @{$element->{point}};
  13         29  
603             }
604             elsif ($element->{type} eq 'line-to') {
605 18 100       34 if ($element->{position} eq 'relative') {
606 17         27 add_coords ($element->{point}, \@abs_pos);
607             }
608 18 100       26 if ($begin_drawing) {
609 6 100       11 if ($verbose) {
610 1         15 printf "Beginning drawing at [%.4f, %.4f]\n", @abs_pos;
611             }
612 6         9 $begin_drawing = 0;
613 6         12 @start_drawing = @abs_pos;
614             }
615 18         21 @abs_pos = @{$element->{point}};
  18         29  
616             }
617             elsif ($element->{type} eq 'horizontal-line-to') {
618 10 50       16 if ($element->{position} eq 'relative') {
619 10         15 $element->{x} += $abs_pos[0];
620             }
621 10 50       15 if ($begin_drawing) {
622 0 0       0 if ($verbose) {
623 0         0 printf "Beginning drawing at [%.4f, %.4f]\n", @abs_pos;
624             }
625 0         0 $begin_drawing = 0;
626 0         0 @start_drawing = @abs_pos;
627             }
628 10         10 $abs_pos[0] = $element->{x};
629             }
630             elsif ($element->{type} eq 'vertical-line-to') {
631 18 50       26 if ($element->{position} eq 'relative') {
632 18         25 $element->{y} += $abs_pos[1];
633             }
634 18 100       30 if ($begin_drawing) {
635 4 100       6 if ($verbose) {
636 2         29 printf "Beginning drawing at [%.4f, %.4f]\n", @abs_pos;
637             }
638 4         9 $begin_drawing = 0;
639 4         9 @start_drawing = @abs_pos;
640             }
641 18         20 $abs_pos[1] = $element->{y};
642             }
643             elsif ($element->{type} eq 'cubic-bezier') {
644 0 0       0 if ($element->{position} eq 'relative') {
645 0         0 add_coords ($element->{control1}, \@abs_pos);
646 0         0 add_coords ($element->{control2}, \@abs_pos);
647 0         0 add_coords ($element->{end}, \@abs_pos);
648             }
649 0 0       0 if ($begin_drawing) {
650 0 0       0 if ($verbose) {
651 0         0 printf "Beginning drawing at [%.4f, %.4f]\n", @abs_pos;
652             }
653 0         0 $begin_drawing = 0;
654 0         0 @start_drawing = @abs_pos;
655             }
656 0         0 @abs_pos = @{$element->{end}};
  0         0  
657             }
658             elsif ($element->{type} eq 'smooth-cubic-bezier') {
659 2 50       5 if ($element->{position} eq 'relative') {
660 2         6 add_coords ($element->{control2}, \@abs_pos);
661 2         4 add_coords ($element->{end}, \@abs_pos);
662             }
663 2 50       4 if ($no_smooth) {
664 2         3 $element->{type} = 'cubic-bezier';
665 2         2 $element->{svg_key} = 'C';
666 2 100 66     11 if ($previous && $previous->{type} eq 'cubic-bezier') {
667             $element->{control1} = [
668             2 * $abs_pos[0] - $previous->{control2}->[0],
669 1         4 2 * $abs_pos[1] - $previous->{control2}->[1],
670             ];
671             } else {
672 1         2 $element->{control1} = [@abs_pos];
673             }
674             }
675 2 100       5 if ($begin_drawing) {
676 1 50       2 if ($verbose) {
677 0         0 printf "Beginning drawing at [%.4f, %.4f]\n", @abs_pos;
678             }
679 1         2 $begin_drawing = 0;
680 1         2 @start_drawing = @abs_pos;
681             }
682 2         3 @abs_pos = @{$element->{end}};
  2         5  
683             }
684             elsif ($element->{type} eq 'quadratic-bezier') {
685 1 50       4 if ($element->{position} eq 'relative') {
686 1         2 add_coords ($element->{control}, \@abs_pos);
687 1         3 add_coords ($element->{end}, \@abs_pos);
688             }
689 1 50       3 if ($begin_drawing) {
690 1 50       2 if ($verbose) {
691 0         0 printf "Beginning drawing at [%.4f, %.4f]\n", @abs_pos;
692             }
693 1         1 $begin_drawing = 0;
694 1         3 @start_drawing = @abs_pos;
695             }
696 1         2 @abs_pos = @{$element->{end}};
  1         3  
697             }
698             elsif ($element->{type} eq 'smooth-quadratic-bezier') {
699 2 50       5 if ($element->{position} eq 'relative') {
700 2         5 add_coords ($element->{end}, \@abs_pos);
701             }
702 2 50       6 if ($no_smooth) {
703 2         4 $element->{type} = 'quadratic-bezier';
704 2         3 $element->{svg_key} = 'Q';
705 2 100 66     9 if ($previous && $previous->{type} eq 'quadratic-bezier') {
706             $element->{control} = [
707             2 * $abs_pos[0] - $previous->{control}->[0],
708 1         3 2 * $abs_pos[1] - $previous->{control}->[1],
709             ];
710             } else {
711 1         3 $element->{control} = [@abs_pos];
712             }
713             }
714 2 100       8 if ($begin_drawing) {
715 1 50       3 if ($verbose) {
716 0         0 printf "Beginning drawing at [%.4f, %.4f]\n", @abs_pos;
717             }
718 1         2 $begin_drawing = 0;
719 1         2 @start_drawing = @abs_pos;
720             }
721 2         3 @abs_pos = @{$element->{end}};
  2         5  
722             }
723             elsif ($element->{type} eq 'arc') {
724              
725 1 50       4 if ($element->{position} eq 'relative') {
726 1         3 $element->{x} += $abs_pos[0];
727 1         2 $element->{y} += $abs_pos[1];
728             }
729 1 50       2 if ($begin_drawing) {
730 0 0       0 if ($verbose) {
731 0         0 printf "Beginning drawing at [%.4f, %.4f]\n", @abs_pos;
732             }
733 0         0 $begin_drawing = 0;
734 0         0 @start_drawing = @abs_pos;
735             }
736 1         3 @abs_pos = ($element->{x}, $element->{y});
737             }
738             elsif ($element->{type} eq 'closepath') {
739 8 100       18 if ($verbose) {
740 2         22 printf "Closing drawing shape to [%.4f, %.4f]\n", @start_drawing;
741             }
742 8         16 @abs_pos = @start_drawing;
743 8         10 $begin_drawing = 1;
744             }
745 73         86 $element->{position} = 'absolute';
746 73 50       101 if (! $element->{svg_key}) {
747 0         0 die "No SVG key";
748             }
749 73         95 $element->{svg_key} = uc $element->{svg_key};
750 73         84 $previous = $element;
751             }
752             }
753 63         327 return @path_info;
754             }
755              
756             # Given a current position and an array of coordinates, use the
757             # coordinates to build up line-to elements until the coordinates are
758             # exhausted. Before entering this, it should have been checked that
759             # there is an even number of coordinates.
760              
761             sub build_lineto
762             {
763 38     38 0 80 my ($position, @coords) = @_;
764 38         51 my @path_info = ();
765 38         45 my $n_coords = scalar (@coords);
766 38 50       72 if ($n_coords % 2 != 0) {
767             # This trap should never be reached, since we should always
768             # check before entering this routine. However, we keep it for
769             # safety.
770 0         0 croak "Odd number of coordinates in lineto";
771             }
772 38         108 while (my ($x, $y) = splice @coords, 0, 2) {
773 54 100       334 push @path_info, {
774             type => 'line-to',
775             name => 'lineto',
776             position => $position,
777             point => [$x, $y],
778             end => [$x, $y],
779             svg_key => ($position eq 'absolute' ? 'L' : 'l'),
780             };
781             }
782 38         100 return @path_info;
783             }
784              
785             1;