File Coverage

blib/lib/SVGPDF/Contrib/PathExtract.pm
Criterion Covered Total %
statement 205 307 66.7
branch 102 178 57.3
condition 4 15 26.6
subroutine 7 9 77.7
pod 0 6 0.0
total 318 515 61.7


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