File Coverage

lib/ChordPro/lib/SVGPDF/Contrib/PathExtract.pm
Criterion Covered Total %
statement 9 307 2.9
branch 0 178 0.0
condition 0 15 0.0
subroutine 3 9 33.3
pod 0 6 0.0
total 12 515 2.3


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 1     1   6 use warnings;
  1         3  
  1         47  
10 1     1   8 use strict;
  1         2  
  1         152  
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 1     1   7 use Carp;
  1         3  
  1         4488  
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 0     0 0   my ($curve_type) = @_;
48 0 0         if (lc $curve_type eq $curve_type) {
    0          
49 0           return "relative";
50             }
51             elsif (uc $curve_type eq $curve_type) {
52 0           return "absolute";
53             }
54             else {
55 0           croak "I don't know what to do with '$curve_type'";
56             }
57             }
58              
59             sub add_coords
60             {
61 0     0 0   my ($first_ref, $to_add_ref) = @_;
62 0           $first_ref->[0] += $to_add_ref->[0];
63 0           $first_ref->[1] += $to_add_ref->[1];
64             }
65              
66             sub reverse_path
67             {
68 0     0 0   my ($path) = @_;
69 0           my $me = 'reverse_path';
70 0 0         if (! $path) {
71 0           croak "$me: no input";
72             }
73 0           my @values = extract_path_info ($path, {
74             no_smooth => 1,
75             absolute => 1,
76             });
77 0 0         if (! @values) {
78 0           return '';
79             }
80 0           my @rvalues;
81 0           my $end_point = $values[0]->{point};
82 0           for my $value (@values[1..$#values]) {
83 0           my $element = {};
84 0           $element->{type} = $value->{type};
85             # print "$element->{type}\n";
86 0 0         if ($value->{type} eq 'cubic-bezier') {
87 0           $element->{control1} = $value->{control2};
88 0           $element->{control2} = $value->{control1};
89 0           $element->{end} = $end_point;
90 0           $end_point = $value->{end};
91             }
92             else {
93 0           croak "Can't handle path element type '$value->{type}'";
94             }
95 0           unshift @rvalues, $element;
96             }
97 0           my $moveto = {
98             type => 'moveto',
99             point => $end_point,
100             };
101 0           unshift @rvalues, $moveto;
102 0           my $rpath = create_path_string (\@rvalues);
103 0           return $rpath;
104             }
105              
106             sub create_path_string
107             {
108 0     0 0   my ($info_ref) = @_;
109 0           my $path = '';
110 0           for my $element (@$info_ref) {
111 0           my $t = $element->{type};
112             # print "$t\n";
113 0 0         if ($t eq 'moveto') {
    0          
    0          
    0          
    0          
    0          
    0          
114 0           my @p = @{$element->{point}};
  0            
115 0           $path .= sprintf ("M%f,%f ", @p);
116             }
117             elsif ($t eq 'cubic-bezier') {
118 0           my @c1 = @{$element->{control1}};
  0            
119 0           my @c2 = @{$element->{control2}};
  0            
120 0           my @e = @{$element->{end}};
  0            
121 0           $path .= sprintf ("C%f,%f %f,%f %f,%f ", @c1, @c2, @e);
122             }
123             elsif ($t eq 'closepath') {
124 0           $path .= "Z";
125             }
126             elsif ($t eq 'vertical-line-to') {
127 0           $path .= sprintf ("V%f ", $element->{y});
128             }
129             elsif ($t eq 'horizontal-line-to') {
130 0           $path .= sprintf ("H%f ", $element->{x});
131             }
132             elsif ($t eq 'line-to') {
133 0           $path .= sprintf ("L%f,%f ", @{$element->{point}});
  0            
134             }
135             elsif ($t eq 'arc') {
136 0           my @f = map {sprintf ("%f", $element->{$_})} @arc_fields;
  0            
137 0           $path .= "A ". join (',', @f) . " ";
138             }
139             else {
140 0           croak "Don't know how to deal with type '$t'";
141             }
142             }
143 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 0     0 0   my ($path, $options_ref) = @_;
350             # Error/message reporting thing. Not sure why I did this now.
351 0           my $me = 'extract_path_info';
352 0 0         if (! $path) {
353 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 0 0         if ($options_ref) {
359 0 0         if (ref $options_ref ne 'HASH') {
360 0           croak "$me: second argument should be a hash reference";
361             }
362             }
363             else {
364 0           $options_ref = {};
365             }
366 0 0         if (! wantarray) {
367 0           croak "$me: extract_path_info returns an array of values";
368             }
369 0           my $verbose = $options_ref->{verbose};
370 0 0         if ($verbose) {
371 0           print "$me: I am trying to split up '$path'.\n";
372             }
373 0           my @path_info;
374 0           my @path = split /([cslqtahvzm])/i, $path;
375 0 0 0       if ( $path[0] !~ /^$wsp*$/ || $path[1] !~ /[Mm]/ ) {
376 0           croak "No moveto at start of path '$path'";
377             }
378 0           shift @path;
379 0           my $path_pos=0;
380 0           my @curves;
381 0           while ($path_pos < scalar @path) {
382 0           my $command = $path[$path_pos];
383 0           my $values = $path[$path_pos+1];
384 0 0         if (! defined $values) {
385 0           $values = '';
386             }
387 0           my $original = "${command}${values}";
388 0 0         if ($original !~ /$moveto|$drawto_command/x) {
389 0           warn "Cannot parse '$original' using moveto/drawto_command regex";
390             }
391 0           $values=~s/^$wsp*//;
392 0           push @curves, [$command, $values, $original];
393 0           $path_pos+=2;
394             }
395 0           for my $curve_data (@curves) {
396 0           my ($command, $values) = @$curve_data;
397 0           my $ucc = uc $command;
398 0           my @numbers;
399 0 0         if ($ucc eq 'A') {
400 0           @numbers = ($values =~ /$eaa/g);
401             }
402             else {
403 0           @numbers = ($values =~ /($number)/g);
404             }
405             # Remove leading plus signs to keep the same behaviour as
406             # before.
407 0           @numbers = map {s/^\+//; $_} @numbers;
  0            
  0            
408 0 0         if ($verbose) {
409 0           printf "$me: Extracted %d numbers: %s\n", scalar (@numbers),
410             join (" ! ", @numbers);
411             }
412 0 0         if ($ucc eq 'C') {
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
413 0           my $expect_numbers = 6;
414 0 0         if (@numbers % $expect_numbers != 0) {
415 0           croak "$me: Wrong number of values for a C curve " .
416             scalar @numbers . " in '$path'";
417             }
418 0           my $position = position_type ($command);
419 0           for (my $i = 0; $i < @numbers / $expect_numbers; $i++) {
420 0           my $offset = $expect_numbers * $i;
421 0           my @control1 = @numbers[$offset + 0, $offset + 1];
422 0           my @control2 = @numbers[$offset + 2, $offset + 3];
423 0           my @end = @numbers[$offset + 4, $offset + 5];
424             # Put each of these abbreviated things into the list
425             # as a separate path.
426 0           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 0           my $expect_numbers = 4;
439 0 0         if (@numbers % $expect_numbers != 0) {
440 0           croak "$me: Wrong number of values for an S curve " .
441             scalar @numbers . " in '$path'";
442             }
443 0           my $position = position_type ($command);
444 0           for (my $i = 0; $i < @numbers / $expect_numbers; $i++) {
445 0           my $offset = $expect_numbers * $i;
446 0           my @control2 = @numbers[$offset + 0, $offset + 1];
447 0           my @end = @numbers[$offset + 2, $offset + 3];
448 0           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 0           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 0 0         if (@numbers % $expect_numbers != 0) {
463 0           croak "Odd number of values for an L command " .
464             scalar (@numbers) . " in '$path'";
465             }
466 0           my $position = position_type ($command);
467 0           push @path_info, build_lineto ($position, @numbers);
468             }
469             elsif ($ucc eq 'Z') {
470 0 0         if (@numbers > 0) {
471 0           croak "Wrong number of values for a Z command " .
472             scalar @numbers . " in '$path'";
473             }
474 0           my $position = position_type ($command);
475 0           push @path_info, {
476             type => 'closepath',
477             name => 'closepath',
478             position => $position,
479             svg_key => $command,
480             }
481             }
482             elsif ($ucc eq 'Q') {
483 0           my $expect_numbers = 4;
484 0 0         if (@numbers % $expect_numbers != 0) {
485 0           croak "Wrong number of values for a Q command " .
486             scalar @numbers . " in '$path'";
487             }
488 0           my $position = position_type ($command);
489 0           for (my $i = 0; $i < @numbers / $expect_numbers; $i++) {
490 0           my $o = $expect_numbers * $i;
491 0           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 0           my $expect_numbers = 2;
503 0 0         if (@numbers % $expect_numbers != 0) {
504 0           croak "$me: Wrong number of values for an T command " .
505             scalar @numbers . " in '$path'";
506             }
507 0           my $position = position_type ($command);
508 0           for (my $i = 0; $i < @numbers / $expect_numbers; $i++) {
509 0           my $o = $expect_numbers * $i;
510 0           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 0           my $position = position_type ($command);
521 0           for (my $i = 0; $i < @numbers; $i++) {
522 0           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 0           my $position = position_type ($command);
533 0           for (my $i = 0; $i < @numbers; $i++) {
534 0           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 0           my $position = position_type ($command);
545 0           my $expect_numbers = 7;
546 0 0         if (@numbers % $expect_numbers != 0) {
547 0           my $n = scalar (@numbers);
548 0           croak "$me: Need multiple of 7 parameters for arc, got $n (@numbers)";
549             }
550 0           for (my $i = 0; $i < @numbers / $expect_numbers; $i++) {
551 0           my $o = $expect_numbers * $i;
552 0           my %arc;
553 0           $arc{svg_key} = $command;
554 0           $arc{type} = 'arc';
555 0           $arc{name} = 'elliptical arc';
556 0           $arc{position} = $position;
557 0           @arc{@arc_fields} = @numbers[$o .. $o + 6];
558 0           push @path_info, \%arc;
559             }
560             }
561             elsif ($ucc eq 'M') {
562 0           my $expect_numbers = 2;
563 0           my $position = position_type ($command);
564 0 0         if (@numbers < $expect_numbers) {
565 0           croak "$me: Need at least $expect_numbers numbers for move to";
566             }
567 0 0         if (@numbers % $expect_numbers != 0) {
568 0           croak "$me: Odd number of values for an M command " .
569             scalar (@numbers) . " in '$path'";
570             }
571 0           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 0 0         if (@numbers > $expect_numbers) {
581 0           my @implicit_lineto = splice @numbers, $expect_numbers;
582 0           push @path_info, build_lineto ($position, @implicit_lineto);
583             }
584             }
585             else {
586 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 0           my $absolute = $options_ref->{absolute};
594 0   0       my $no_smooth = $options_ref->{no_shortcuts} || $options_ref->{no_smooth};
595 0 0         if ($absolute) {
596 0 0         if ($verbose) {
597 0           print "Making all coordinates absolute.\n";
598             }
599 0           my @abs_pos = (0, 0);
600 0           my @start_drawing;
601             my $previous;
602 0           my $begin_drawing = 1; ##This will get updated after
603 0           for my $element (@path_info) {
604 0 0         if ($element->{type} eq 'moveto') {
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
605 0           $begin_drawing = 1;
606 0 0         if ($element->{position} eq 'relative') {
607 0           my $ip = $options_ref->{initial_position};
608 0 0         if ($ip) {
609 0 0 0       if (ref $ip ne 'ARRAY' ||
610             scalar @$ip != 2) {
611 0           croak "$me: The initial position supplied doesn't look like a pair of coordinates";
612             }
613 0           add_coords ($element->{point}, $ip);
614             }
615             else {
616 0           add_coords ($element->{point}, \@abs_pos);
617             }
618             }
619 0           @abs_pos = @{$element->{point}};
  0            
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 0           @start_drawing = @abs_pos;
629             }
630             elsif ($element->{type} eq 'line-to') {
631 0 0         if ($element->{position} eq 'relative') {
632 0           add_coords ($element->{point}, \@abs_pos);
633             }
634 0 0         if ($begin_drawing) {
635 0 0         if ($verbose) {
636 0           printf "Beginning drawing at [%.4f, %.4f]\n", @abs_pos;
637             }
638 0           $begin_drawing = 0;
639 0           @start_drawing = @abs_pos;
640             }
641 0           @abs_pos = @{$element->{point}};
  0            
642             }
643             elsif ($element->{type} eq 'horizontal-line-to') {
644 0 0         if ($element->{position} eq 'relative') {
645 0           $element->{x} += $abs_pos[0];
646             }
647 0 0         if ($begin_drawing) {
648 0 0         if ($verbose) {
649 0           printf "Beginning drawing at [%.4f, %.4f]\n", @abs_pos;
650             }
651 0           $begin_drawing = 0;
652 0           @start_drawing = @abs_pos;
653             }
654 0           $abs_pos[0] = $element->{x};
655             }
656             elsif ($element->{type} eq 'vertical-line-to') {
657 0 0         if ($element->{position} eq 'relative') {
658 0           $element->{y} += $abs_pos[1];
659             }
660 0 0         if ($begin_drawing) {
661 0 0         if ($verbose) {
662 0           printf "Beginning drawing at [%.4f, %.4f]\n", @abs_pos;
663             }
664 0           $begin_drawing = 0;
665 0           @start_drawing = @abs_pos;
666             }
667 0           $abs_pos[1] = $element->{y};
668             }
669             elsif ($element->{type} eq 'cubic-bezier') {
670 0 0         if ($element->{position} eq 'relative') {
671 0           add_coords ($element->{control1}, \@abs_pos);
672 0           add_coords ($element->{control2}, \@abs_pos);
673 0           add_coords ($element->{end}, \@abs_pos);
674             }
675 0 0         if ($begin_drawing) {
676 0 0         if ($verbose) {
677 0           printf "Beginning drawing at [%.4f, %.4f]\n", @abs_pos;
678             }
679 0           $begin_drawing = 0;
680 0           @start_drawing = @abs_pos;
681             }
682 0           @abs_pos = @{$element->{end}};
  0            
683             }
684             elsif ($element->{type} eq 'smooth-cubic-bezier') {
685 0 0         if ($element->{position} eq 'relative') {
686 0           add_coords ($element->{control2}, \@abs_pos);
687 0           add_coords ($element->{end}, \@abs_pos);
688             }
689 0 0         if ($no_smooth) {
690 0           $element->{type} = 'cubic-bezier';
691 0           $element->{svg_key} = 'C';
692 0 0 0       if ($previous && $previous->{type} eq 'cubic-bezier') {
693             $element->{control1} = [
694             2 * $abs_pos[0] - $previous->{control2}->[0],
695 0           2 * $abs_pos[1] - $previous->{control2}->[1],
696             ];
697             } else {
698 0           $element->{control1} = [@abs_pos];
699             }
700             }
701 0 0         if ($begin_drawing) {
702 0 0         if ($verbose) {
703 0           printf "Beginning drawing at [%.4f, %.4f]\n", @abs_pos;
704             }
705 0           $begin_drawing = 0;
706 0           @start_drawing = @abs_pos;
707             }
708 0           @abs_pos = @{$element->{end}};
  0            
709             }
710             elsif ($element->{type} eq 'quadratic-bezier') {
711 0 0         if ($element->{position} eq 'relative') {
712 0           add_coords ($element->{control}, \@abs_pos);
713 0           add_coords ($element->{end}, \@abs_pos);
714             }
715 0 0         if ($begin_drawing) {
716 0 0         if ($verbose) {
717 0           printf "Beginning drawing at [%.4f, %.4f]\n", @abs_pos;
718             }
719 0           $begin_drawing = 0;
720 0           @start_drawing = @abs_pos;
721             }
722 0           @abs_pos = @{$element->{end}};
  0            
723             }
724             elsif ($element->{type} eq 'smooth-quadratic-bezier') {
725 0 0         if ($element->{position} eq 'relative') {
726 0           add_coords ($element->{end}, \@abs_pos);
727             }
728 0 0         if ($no_smooth) {
729 0           $element->{type} = 'quadratic-bezier';
730 0           $element->{svg_key} = 'Q';
731 0 0 0       if ($previous && $previous->{type} eq 'quadratic-bezier') {
732             $element->{control} = [
733             2 * $abs_pos[0] - $previous->{control}->[0],
734 0           2 * $abs_pos[1] - $previous->{control}->[1],
735             ];
736             } else {
737 0           $element->{control} = [@abs_pos];
738             }
739             }
740 0 0         if ($begin_drawing) {
741 0 0         if ($verbose) {
742 0           printf "Beginning drawing at [%.4f, %.4f]\n", @abs_pos;
743             }
744 0           $begin_drawing = 0;
745 0           @start_drawing = @abs_pos;
746             }
747 0           @abs_pos = @{$element->{end}};
  0            
748             }
749             elsif ($element->{type} eq 'arc') {
750              
751 0 0         if ($element->{position} eq 'relative') {
752 0           $element->{x} += $abs_pos[0];
753 0           $element->{y} += $abs_pos[1];
754             }
755 0 0         if ($begin_drawing) {
756 0 0         if ($verbose) {
757 0           printf "Beginning drawing at [%.4f, %.4f]\n", @abs_pos;
758             }
759 0           $begin_drawing = 0;
760 0           @start_drawing = @abs_pos;
761             }
762 0           @abs_pos = ($element->{x}, $element->{y});
763             }
764             elsif ($element->{type} eq 'closepath') {
765 0 0         if ($verbose) {
766 0           printf "Closing drawing shape to [%.4f, %.4f]\n", @start_drawing;
767             }
768 0           @abs_pos = @start_drawing;
769 0           $begin_drawing = 1;
770             }
771 0           $element->{position} = 'absolute';
772 0 0         if (! $element->{svg_key}) {
773 0           die "No SVG key";
774             }
775 0           $element->{svg_key} = uc $element->{svg_key};
776 0           $previous = $element;
777             }
778             }
779 0           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 0     0 0   my ($position, @coords) = @_;
790 0           my @path_info = ();
791 0           my $n_coords = scalar (@coords);
792 0 0         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           croak "Odd number of coordinates in lineto";
797             }
798 0           while (my ($x, $y) = splice @coords, 0, 2) {
799 0 0         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 0           return @path_info;
809             }
810              
811             1;