File Coverage

blib/lib/Image/SVG/Path.pm
Criterion Covered Total %
statement 242 307 78.8
branch 130 178 73.0
condition 8 15 53.3
subroutine 8 9 88.8
pod 3 6 50.0
total 391 515 75.9


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