File Coverage

blib/lib/SVG/SVG2zinc/Conversions.pm
Criterion Covered Total %
statement 15 409 3.6
branch 0 194 0.0
condition 0 33 0.0
subroutine 5 28 17.8
pod 0 23 0.0
total 20 687 2.9


line stmt bran cond sub pod time code
1             package SVG::SVG2zinc::Conversions;
2              
3 1     1   458402 use Math::Trig;
  1         279624  
  1         198  
4 1     1   964 use Math::Bezier::Convert;
  1         3  
  1         50  
5 1     1   5 use strict;
  1         1  
  1         25  
6 1     1   4 use Carp;
  1         2  
  1         58  
7              
8 1     1   6 use vars qw( $VERSION @ISA @EXPORT );
  1         2  
  1         6459  
9              
10             ($VERSION) = sprintf("%d.%02d", q$Revision: 1.10 $ =~ /(\d+)\.(\d+)/);
11              
12             @ISA = qw( Exporter );
13              
14             @EXPORT = qw( InitConv
15             removeComment convertOpacity
16             createNamedFont
17             defineNamedGradient namedGradient namedGradientDef existsGradient
18             extractGradientTypeAndStops addTransparencyToGradient
19             colorConvert
20             pathPoints points
21             cleanName
22             float2int sizesConvert sizeConvert
23             transform
24             );
25              
26             # some variables to be initialized at the beginning
27              
28             my ($warnProc, $lineNumProc); # two proc
29             my %fonts; # a hashtable to identify all used fonts
30             my %gradients;
31              
32             sub InitConv {
33 0     0 0   ($warnProc, $lineNumProc) = @_;
34 0           %fonts = ();
35 0           %gradients = ();
36 0           return 1;
37             }
38              
39             sub myWarn{
40 0     0 0   &{$warnProc}(@_);
  0            
41             }
42              
43             ### remove SVG comments in the form /* */ in $str
44             ### returns the string without these comments
45             sub removeComment {
46 0     0 0   my ($str) = @_;
47             # my $strOrig = $str;
48 0 0         return "" unless defined $str;
49              
50 0           while ($str =~ s|(.*)(/\*.*\*/){1}?|$1|) {
51             # print "begin='$str'\n";
52             }
53             # print "'$strOrig' => '$str'\n";
54 0           $str =~ s/^\s*// ;
55 0           return $str;
56             }
57              
58             ## returns an opacity value between 0 and 1
59             ## returns 1 if the argument is undefined
60             sub convertOpacity {
61 0     0 0   my ($opacity) = @_;
62 0 0         $opacity = 1 unless defined $opacity;
63 0 0         $opacity = 0 if $opacity<0;
64 0 0         $opacity = 1 if $opacity>1;
65 0           return $opacity;
66             }
67              
68              
69             ######################################################################################
70             # fontes management
71             ######################################################################################
72              
73             # the following hashtable is used to maps SVG font names to X font names
74             # BUG: obvioulsy this hashtable should be defined in the system or at
75             # least as a configuration file or in the SVG2zinc parser parameters
76             my %fontsMapping =
77             ( 'comicsansms' => "comic sans ms",
78             # 'helvetica' => "arial", # "verdana",
79             'arialmt' => "arial",
80             );
81              
82             sub createNamedFont {
83 0     0 0   my ($fullFamily, $size, $weight) = @_;
84 0 0         $fullFamily = "verdana" if $fullFamily eq "";
85 0           my $family = lc($fullFamily);
86              
87 0 0         $weight = "normal" unless $weight; ## valeur par défaut
88              
89 0 0         if ( $size =~ /(.*)pt/ ) {
    0          
90             ## size in points
91 0           $size = $1;
92             } elsif ( $size =~ /(\d*(.\d*)?)\s*$/ ) {
93             ## size in pixel
94             ## BUG: generates a bug in TkZinc when render != 0 (TBC)
95 0           $size = -$1;
96             }
97              
98 0           $size = &float2int($size); # I round the font size, at least until we have vectorial font in Tk::Zinc
99            
100 0 0         if ( $family =~ /(\w*)-bold/ ) {
101 0           $family = $1;
102 0           $weight = "bold"; # this might be in contradiction with the wieght defined in SVG (??)
103             } else {
104 0           $weight = "medium";
105             }
106 0 0         $family = $fontsMapping{$family} if defined $fontsMapping{$family};
107             # print "FontFamily: '$fullFamily' => '$family'\n";
108            
109 0           my $fontKey = join "_", ($family, $size, $weight);
110 0 0         if (!defined $fonts{$fontKey}) {
111 0           $fonts{$fontKey} = $fontKey;
112 0           print "In createNamedFont, a new font: $fontKey\n";
113 0           return ($fontKey, "->fontCreate('$fontKey', -family => \"$family\", -size => $size, -weight => \"$weight\");");
114             } else {
115 0           return ($fontKey,"");
116             }
117            
118             } # end of createNamedFont
119              
120             ######################################################################################
121             # gradients management
122             ######################################################################################
123             # my %gradients;
124              
125             ## Check if the new gradient does not already exists (with another name)
126             ## In this case, the hash is extended with an "auto-reference"
127             ## $gradients{newName} = "oldName"
128             ## and the function returns 0
129             ## Otherwise, add an entry in the hastable
130             ## $gradients{newName} = "newDefinition"
131             ## and returns 1
132             sub defineNamedGradient {
133 0     0 0   my ($newGname, $newGradDef) = @_;
134 0           my $prevEqGrad;
135 0           $newGradDef =~ s/^\s*(.*\S)\s*$/$1/ ; # removing trailing/leading blank
136 0           $newGradDef =~ s/\s*\|\s*/ \| /g ; # inserting blanks around the |
137 0           $newGradDef =~ s/\s\s+/ /g; # removing multiple occurence of blanks
138             # print "CLEANED grad='$newGradDef'\n";
139 0           foreach my $gname (keys %gradients) {
140 0 0         if ($gradients{$gname} eq $newGradDef) {
141             ## such a gradient already exist with another name
142 0           $gradients{$newGname} = $gname;
143             # print "GRADIENT: $newGname == $gname\n";
144              
145             # $res .= "\n###### $newGname => $gname"; ###
146              
147 0           return 0;
148             }
149             }
150             ## there is no identical gradient with another name
151             ## we add the definition in the hashtable
152 0           $gradients{$newGname} = $newGradDef;
153 0           return $newGradDef;
154             }
155              
156             ## returns the name of a gradient, by following if necessary
157             ## "auto-references" in the hashtable
158             sub namedGradient {
159 0     0 0   my ($gname) = @_;
160 0           my $def = $gradients{$gname};
161 0 0         return $gname unless defined $def;
162             ## to avoid looping if the hashtable is buggy:
163 0 0 0       return $gname if !defined $gradients{$def} or $def eq $gradients{$def};
164 0           return &namedGradient($gradients{$gname});
165             }
166              
167             ## returns the definition associated to a named gradient, following if necessary
168             ## "auto-references" in the hashtable
169             sub namedGradientDef {
170 0     0 0   my ($gname) = @_;
171 0           my $def = $gradients{$gname};
172 0 0         return "" unless defined $def;
173             ## to avoid looping if the hashtable is buggy:
174 0 0 0       return $def if !defined $gradients{$def} or $def eq $gradients{$def};
175 0           return $gradients{&namedGradient($gradients{$gname})};
176             }
177              
178             # returns 1 if the named has an associated gradient
179             sub existsGradient {
180 0     0 0   my ($gname) = @_;
181 0 0         if (defined $gradients{$gname}) {return 1} else {return 0};
  0            
  0            
182             }
183              
184             ## this function returns both the radial type with its parameters AND
185             ## a list of stops characteristics as defined in TkZinc
186             ## usage: ($radialType, @stops) = &extractGradientTypeAndStops();
187             ## this func assumes that DOES exist
188             sub extractGradientTypeAndStops {
189 0     0 0   my ($namedGradient) = @_;
190 0           my $gradDef = &namedGradientDef($namedGradient);
191 0           my @defElements = split (/\s*\|\s*/ , $gradDef);
192 0           my $gradientType;
193 0           $gradientType = shift @defElements;
194 0           return ($gradientType, @defElements);
195             }
196              
197             ## combines the opacity to every parts of a named gradient
198             ## if some parts of the gradients are themselves partly transparent, they are combined
199             ## if $opacity is 1, returns directly $gname
200             ## else returns a new definition of a gradient
201             sub addTransparencyToGradient {
202 0     0 0   my ($gname,$opacity) = @_;
203 0 0         return $gname if $opacity == 100;
204 0 0         &myWarn ("ATTG: ERROR $gname\n"), return $gname if !&namedGradientDef($gname); ## this cas is certainly an error in the SVG source file!
205 0           my ($gradientType, @stops) = &extractGradientTypeAndStops($gname);
206              
207 0           my @newStops;
208 0           foreach my $stop (@stops) {
209 0           my $newStop="";
210 0 0         if ($stop =~ /^([^\s;]+)\s*;\s*(\d+)\s*(\d*)\s*$/ # red;45 50 or red;45
    0          
    0          
211             ) {
212 0           my ($color,$trans,$pos) = ($1,$2,$3);
213             # print "$stop => '$color','$trans','$pos'\n";
214 0           my $newtransp = &float2int($trans*$opacity/100);
215 0 0         if ($pos) {
216 0           $newStop="$color;$newtransp $pos";
217             } else {
218 0           $newStop="$color;$newtransp";
219             }
220             } elsif ($stop =~ /^(\S+)\s+(\d+)$/) { # red 50
221 0           my ($color,$pos) = ($1,$2);
222             # print "$stop => '$color','$pos'\n";
223 0           my $newtransp = &float2int($opacity);
224 0           $newStop="$color;$newtransp $pos";
225             } elsif ($stop =~ /^(\S+)$/) {
226 0           my ($color) = ($1);
227             # print "$stop => '$color'\n";
228 0           my $newtransp = &float2int($opacity);
229 0           $newStop="$color;$newtransp";
230             } else {
231 0           &myWarn ("In addTransparencyToGradient: bad gradient Elements: '$stop'\n");
232             }
233 0           push @newStops, $newStop;
234             }
235 0           return ( $gradientType . " | " . join (" | ", @newStops));
236             } # end of addTransparencyToGradient
237              
238              
239             ######################################################################################
240             # color conversion
241             ######################################################################################
242             # a hash table to define non-X SVG colors
243             # THX to Lemort for bug report and correction!
244             my %color2color = ('lime' => 'green',
245             'Lime' => 'green',
246             'crimson' => '#DC143C',
247             'Crimson' => '#DC143C',
248             'aqua' => '#00ffff',
249             'Aqua' => '#00ffff',
250             'fuschia' => '#ff00ff',
251             'Fuschia' => '#ff00ff',
252             'fuchsia' => '#ff00ff',
253             'Fuchsia' => '#ff00ff',
254             'indigo' => '#4b0082',
255             'Indigo' => '#4b0082',
256             'olive' => '#808000',
257             'Olive' => '#808000',
258             'silver' => '#c0c0c0',
259             'Silver' => '#c0c0c0',
260             'teal' => '#008080',
261             'Teal' => '#008080',
262             'green' => '#008000',
263             'Green' => '#008000',
264             'grey' => '#808080',
265             'Grey' => '#808080',
266             'gray' => '#808080',
267             'Gray' => '#808080',
268             'maroon' => '#800000',
269             'Maroon' => '#800000',
270             'purple' => '#800080',
271             'Purple' => '#800080',
272             );
273              
274             #### BUG: this is certainly only a partial implementation!!
275             sub colorConvert {
276 0     0 0   my ($color) = @_;
277 0 0         if ($color =~ /^\s*none/m) {
    0          
    0          
    0          
278 0           return 'none';
279             } elsif ($color =~ /rgb\(\s*(.+)\s*\)/ ) {
280             ## color like "rgb(...)"
281 0           my $rgbs = $1;
282 0 0         if ($rgbs =~ /([\d.]*)%\s*,\s*([\d.]*)%\s*,\s*([\d.]*)%/ ) {
    0          
283             ## color like "rgb(1.2% , 45%,67.%)"
284 0           my ($r,$g,$b) = ($1,$2,$3);
285 0           $color = sprintf ("#%02x%02x%02x",
286             sprintf ("%.0f",2.55*$r),
287             sprintf ("%.0f",2.55*$g),
288             sprintf ("%.0f",2.55*$b));
289 0           return $color;
290             } elsif ($rgbs =~ /(\d*)\s*,\s*(\d*)\s*,\s*(\d*)/ ) {
291             ## color like "rgb(255, 45,67)"
292 0           my ($r,$g,$b) = ($1,$2,$3);
293 0           $color = sprintf "#%02x%02x%02x", $r,$g,$b;
294 0           return $color;
295             } else {
296 0           &myWarn ("Unknown rgb color coding: $color\n");
297             }
298             } elsif ($color =~ /^url\(\#(.+)\)/ ) {
299             ## color like "url(#monGradient)"
300 0           $color = $1;
301 0           my $res = &namedGradient($color);
302 0           return $res; #&namedGradient($1);
303             } elsif ( $color =~ /\#([0-9a-fA-F]{3}?)$/ ) {
304             ## color like #fc1 => #ffcc11
305 0           $color =~ s/([0-9a-fA-F])/$1$1/g ;
306             # on doubling the digiys, because Tk does not do it properly
307 0           return $color;
308             } else {
309             ## named colors!
310             ## except those in the %color2color, all other should be defined in the
311             ## standard rgb.txt file
312 0           my $converted = $color2color{lc($color)}; # THX to Lemort for bug report!
313 0 0         if (defined $converted) {
314 0           return $converted;
315             } else {
316 0           return $color;
317             }
318             }
319             } # end of colorConvert
320              
321             ######################################################################################
322             # path points commands conversion
323             ######################################################################################
324              
325              
326             # &pathPoints (\%attrs)
327             # returns a boolean and a list of table references
328             # - the boolean is true is the path has more than one contour or if it must be closed
329             # - every table referecne pints to a table of strings, each string describing coordinates
330             # possible BUG: in Tk::Zinc when a curve has more than one contour, they are all closed
331             # how is it in SVG?
332             sub pathPoints {
333 0     0 0   my ($ref_attrs) = @_;
334 0           my $str = $ref_attrs->{d};
335             # print "#### In PathPoints : $str\n";
336 0           my ($x,$y) = (0,0); # current values
337 0           my $closed = 1;
338 0           my $atLeastOneZ=0; # true if at least one z/Z command. The curve must then be closed
339 0           my @fullRes;
340             my @res ;
341 0           my ($firstX, $firstY); # for memorizing the first point for a 'm' command after a 'z'!
342 0           my ($prevContrlx,$prevContrly); # useful for the s/S commande
343              
344             # I use now a repetitive search on the same string, without allocating
345             # a $last string for the string end; with very long list of points, such
346             # as iceland.svg, we can gain 30% in this function and about 3s over 30s
347 0           while ( $str =~ m/\s*([aAmMzZvVhHlLcCsSqQtT])\s*([^aAmMzZvVhHlLcCsSqQtT]*)\s*/g ) {
348 0           my ($command, $args)=($1,$2);
349 0 0         &myWarn ("!!!! Ill-formed path command: '", substr($str,pos($str), 40), "...'\n") unless defined $command ;
350             # print "Command=$command args=$args x=$x y=$y\n";
351 0 0 0       if ($command eq "M") { ## moveto absolute
    0          
    0          
352 0 0         if (!$closed) {
353             ## creating a new contour
354 0           push @fullRes, [ @res ];
355 0           $atLeastOneZ = 1;
356 0           @res = ();
357             }
358 0           my @points = &splitPoints($args);
359 0           ($prevContrlx,$prevContrly) = (undef,undef);
360 0           $firstX = $points[0];
361 0           $firstY = $points[1];
362 0           while (@points) {
363 0           $x = shift @points;
364 0           $y = shift @points;
365 0           push @res , "[$x, $y]";
366             }
367 0           next;
368             } elsif ($command eq "m") { ## moveto relative
369 0 0         if (!$closed) {
370             ## creating a new contour
371 0           push @fullRes, [ @res ];
372 0           $atLeastOneZ = 1;
373 0           @res = ();
374             }
375 0           my @dxy = &splitPoints($args);
376 0           $firstX = $x+$dxy[0];
377 0           $firstY = $y+$dxy[1];
378             # print "m command: $args => @dxy ,$x,$y\n";
379 0           while (@dxy) {
380             ## trying to minimize the number of operation
381             ## to speed a bit this loop
382 0           $x += shift @dxy;
383 0           $y += shift @dxy;
384 0           push @res, "[$x, $y]";
385             }
386 0           next;
387             } elsif ($command eq 'z' or $command eq 'Z') {
388 0           push @fullRes, [ @res ];
389 0           $closed = 1;
390 0           $atLeastOneZ = 1;
391 0           @res = ();
392 0           $x=$firstX;
393 0           $y=$firstY;
394 0           next;
395             }
396             # as a command will/should follow, the curve is no more closed
397 0           $closed = 0;
398 0 0 0       if ($command eq "V") { ## vertival lineto absolute
    0 0        
    0 0        
    0 0        
    0 0        
    0          
    0          
    0          
    0          
    0          
    0          
399 0           ($y) = $args =~ /(\S+)/m ; ## XXXX what about multiple y !?
400 0           push @res , "[$x, $y]";
401             } elsif ($command eq "v") { ## vertical lineto relative
402 0           my ($dy) = $args =~ /(\S+)/m ; ## XXXX what about multiple dy !?
403 0           $y += $dy;
404 0           push @res , "[$x, $y]";
405             } elsif ($command eq "H") { ## horizontal lineto absolute
406 0           ($x) = $args =~ /(\S+)/m ; ## XXXX what about multiple x !?
407 0           push @res , "[$x, $y]";
408             } elsif ($command eq "h") { ## horizontal lineto relative
409 0           my ($dx) = $args =~ /(\S+)/m ; ## XXXX what about multiple dx !?
410 0           $x += $dx;
411 0           push @res , "[$x, $y]";
412             } elsif ($command eq "L") { ## lineto absolute
413 0           my @points = &splitPoints($args);
414 0           while (@points) {
415 0           $x = shift @points;
416 0           $y = shift @points;
417 0           push @res , "[$x, $y]";
418             }
419             } elsif ($command eq "l") { ## lineto relative
420             ### thioscommand can have more than one point as arguments
421 0           my @points = &splitPoints($args);
422             # for (my $i = 0; $i < $#points; $i+=2)
423             # is not quicker than the following while
424 0           while (@points) {
425             ## trying to minimize the number of operation
426             ## to speed a bit this loop
427 0           $x += shift @points;
428 0           $y += shift @points;
429 0           push @res , "[$x, $y]";
430             }
431             } elsif ($command eq "C" or $command eq "c") { ## cubic bezier
432 0 0         &myWarn ("$command command in a path must not be the first one") ,last
433             if (scalar @res < 1);
434 0           my @points = &splitPoints($args);
435 0           while (@points) {
436 0 0         &myWarn ("$command command must have 6 coordinates x N times") ,last
437             if (scalar @points < 6);
438 0           my $x1 = shift @points;
439 0           my $y1 = shift @points;
440 0           $prevContrlx = shift @points;
441 0           $prevContrly = shift @points;
442 0           my $xf = shift @points;
443 0           my $yf = shift @points;
444 0 0         if ($command eq "c") { $x1+=$x; $y1+=$y; $prevContrlx+=$x; $prevContrly+=$y; $xf+=$x; $yf+=$y}
  0            
  0            
  0            
  0            
  0            
  0            
445 0           push @res, ( "[$x1, $y1, 'c'], [$prevContrlx, $prevContrly, 'c'], [$xf, $yf]");
446 0           $x=$xf;
447 0           $y=$yf;
448             }
449             } elsif ($command eq "S" or $command eq "s") { ## cubic bezier with opposite last control point
450 0 0         &myWarn ("$command command in a path must not be the first one") ,last
451             if (scalar @res < 1);
452             # print "$command command : $args\n";
453 0           my @points = &splitPoints($args);
454 0 0         if ($command eq "s") {
455 0           for (my $i=0; $i <= $#points; $i += 2) {
456 0           $points[$i] += $x;
457             }
458 0           for (my $i=1; $i <= $#points; $i += 2) {
459 0           $points[$i] += $y;
460             }
461             }
462 0           while (@points) {
463 0 0         &myWarn ("$command command must have 4 coordinates x N times; skipping @points") ,last
464             if (scalar @points < 4);
465 0 0         my $x1 = (defined $prevContrlx) ? $prevContrlx : $x;
466 0           $x1 = 2*$x-$x1;
467 0 0         my $y1 = (defined $prevContrly) ? $prevContrly : $y;
468 0           $y1 = 2*$y-$y1;
469 0           $prevContrlx = shift @points;
470 0           $prevContrly = shift @points;
471 0           $x = shift @points;
472 0           $y = shift @points;
473 0           push @res, ( "[$x1, $y1, 'c'], [$prevContrlx, $prevContrly, 'c'], [$x, $y]");
474             }
475              
476              
477             } elsif ($command eq "Q" or $command eq "q") { ## quadratic bezier
478 0 0         &myWarn ("$command command in a path must not be the first one") ,last
479             if (scalar @res < 1);
480 0           my @points = &splitPoints($args);
481 0 0         if ($command eq "q") {
482 0           for (my $i=0; $i <= $#points; $i += 2) {
483 0           $points[$i] += $x;
484             }
485 0           for (my $i=1; $i <= $#points; $i += 2) {
486 0           $points[$i] += $y;
487             }
488             }
489 0           while (@points) {
490 0 0         &myWarn ("$command command must have 4 coordinates x N times") ,last
491             if (scalar @points < 4);
492 0           $prevContrlx = shift @points;
493 0           $prevContrly = shift @points;
494            
495 0           my $last_x = $x;
496 0           my $last_y = $y;
497              
498 0           $x = shift @points;
499 0           $y = shift @points;
500              
501             # the following code has been provided by Lemort@intuilab.com
502 0           my @coordsToConvert = ($last_x,$last_y, $prevContrlx, $prevContrly,$x,$y);
503 0           my @convertCoords = Math::Bezier::Convert::quadratic_to_cubic(@coordsToConvert);
504             # removing the first point, already present
505 0           splice(@convertCoords, 0, 2);
506            
507 0           while (@convertCoords) {
508 0           my ($ctrl1_x, $ctrl1_y) = splice(@convertCoords, 0, 2);
509 0           my ($ctrl2_x, $ctrl2_y) = splice(@convertCoords, 0, 2);
510 0           my ($pt_x, $pt_y) = splice(@convertCoords, 0, 2);
511              
512 0           push @res, ("[$ctrl1_x, $ctrl1_y, 'c'], [$ctrl2_x, $ctrl2_y, 'c'], [$pt_x, $pt_y]");
513             }
514              
515             }
516              
517             } elsif ($command eq "T" or $command eq "t") { ## quadratic bezier with opposite last control point?!
518 0 0         &myWarn ("$command command in a path must not be the first one") ,last
519             if (scalar @res < 1);
520 0           my @points = &splitPoints($args);
521              
522 0 0         if ($command eq "t") {
523 0           for (my $i=0; $i <= $#points; $i += 2) {
524 0           $points[$i] += $x;
525             }
526 0           for (my $i=1; $i <= $#points; $i += 2) {
527 0           $points[$i] += $y;
528             }
529             }
530 0           while (@points) {
531 0 0         &myWarn ("$command command must have 2 coordinates x N times") ,last
532             if (scalar @points < 2);
533 0 0         my $x1 = (defined $prevContrlx) ? $prevContrlx : $x;
534 0           $prevContrlx = 2*$x-$x1;
535 0 0         my $y1 = (defined $prevContrly) ? $prevContrly : $y;
536 0           $prevContrly = 2*$y-$y1;
537            
538 0           my $last_x = $x;
539 0           my $last_y = $y;
540              
541 0           $x = shift @points;
542 0           $y = shift @points;
543              
544             # the following code has been provided by Lemort@intuilab.com
545 0           my @coordsToConvert = ($last_x,$last_y, $prevContrlx, $prevContrly,$x,$y);
546 0           my @convertCoords = Math::Bezier::Convert::quadratic_to_cubic(@coordsToConvert);
547             # removing the first point, already present
548 0           splice(@convertCoords, 0, 2);
549            
550 0           while (@convertCoords) {
551 0           my ($ctrl1_x, $ctrl1_y) = splice(@convertCoords, 0, 2);
552 0           my ($ctrl2_x, $ctrl2_y) = splice(@convertCoords, 0, 2);
553 0           my ($pt_x, $pt_y) = splice(@convertCoords, 0, 2);
554              
555 0           push @res, ("[$ctrl1_x, $ctrl1_y, 'c'], [$ctrl2_x, $ctrl2_y, 'c'], [$pt_x, $pt_y]");
556             }
557              
558             }
559             } elsif ($command eq 'a' or $command eq 'A') {
560 0           my @points = &splitPoints($args);
561 0           while (@points) {
562 0 0         &myWarn ("bad $command command parameters: @points\n") if (scalar @points < 7);
563             # print "($x,$y) $command command: @points\n";
564 0 0         if ($command eq 'a') {
565 0           $points[5] += $x;
566 0           $points[6] += $y;
567             }
568             # print "($x,$y) $command command: @points\n";
569 0           my @coords = &arcPathCommand ( $x,$y, @points[0..6] );
570 0           push @res, @coords;
571 0           $x = $points[5];
572 0           $y = $points[6];
573 0 0         last if (scalar @points == 7);
574 0           @points = @points[7..$#points]; ### XXX à tester!
575             }
576             } else {
577 0           &myWarn ("!!! bad path command: $command\n");
578             }
579             }
580 0 0         if (@res) {
581 0           return ( $atLeastOneZ, [@res], @fullRes);
582 0           } else { return ( $atLeastOneZ, @fullRes) }
583             } # end of pathPoints
584              
585              
586              
587              
588             # this function can be called many many times; so it has been "optimized"
589             # even if a bit less readable
590             sub splitPoints {
591 0     0 0   $_ = shift;
592             ### adding a space before every dash (-) when the dash preceeds by a digit
593 0           s/(\d)-/$1 -/g;
594             ### adding a space before à dot (.) when more than one real are not separated;
595             ### e.g.: '2.3.45.6.' becomes '2.3 .45 .5'
596 0           while ( scalar s/\.(\d+)\.(\d+)/\.$1 \.$2/) {
597             }
598 0           return split ( /[\s,]+/ );
599             }
600              
601              
602              
603             sub arcPathCommand {
604 0     0 0   my ($x1,$y1, $rx,$ry, $x_rot, $large_arc_flag,$sweep_flag, $x2,$y2) = @_;
605 0 0 0       return ($x2,$y2) if ($rx == 0 and $ry == 0);
606 0 0         $rx = -$rx if $rx < 0;
607 0 0         $ry = -$ry if $ry < 0;
608              
609             # computing the center
610 0           my $phi = deg2rad($x_rot);
611              
612             # compute x1' and y1' (formula F.6.5.1)
613 0           my $deltaX = ($x1-$x2)/2;
614 0           my $deltaY = ($y1-$y2)/2;
615 0           my $xp1 = cos($phi)*$deltaX + sin($phi)*$deltaY;
616 0           my $yp1 = -sin($phi)*$deltaX + cos($phi)*$deltaY;
617             # print "xp1,yp1= $xp1 , $yp1\n";
618              
619             # the radius_check has been suggested by lemort@intuilab.com
620             # checking that radius are correct
621 0           my $radius_check = ($xp1/$rx)**2 + ($yp1/$ry)**2;
622            
623 0 0         if ($radius_check > 1) {
624 0           $rx *= sqrt($radius_check);
625 0           $ry *= sqrt($radius_check);
626             }
627              
628             # compute the sign: (formula F.6.5.2)
629 0           my $sign = 1;
630 0 0         $sign = -1 if $large_arc_flag eq $sweep_flag;
631             # compute the big square root (formula F.6.5.2)
632             # print "denominator: ", ( ($rx*$ry)**2 - ($rx*$yp1)**2 - ($ry*$xp1)**2 ),"\n";
633 0           my $bigsqroot = (
634             abs( ($rx*$ry)**2 - ($rx*$yp1)**2 - ($ry*$xp1)**2 ) ### ABS ?!?!
635             /
636             ( ($rx*$yp1)**2 + ($ry*$xp1)**2 )
637             );
638             # computing c'x and c'y (formula F.6.5.2)
639 0           $bigsqroot = $sign * sqrt ($bigsqroot);
640 0           my $cpx = $bigsqroot * ($rx*$yp1/$ry);
641 0           my $cpy = $bigsqroot * (- $ry*$xp1/$rx);
642              
643             # compute cx and cy (formula F.6.5.3)
644 0           my $middleX = ($x1+$x2)/2;
645 0           my $middleY = ($y1+$y2)/2;
646 0           my $cx = cos($phi)*$cpx - sin($phi)*$cpy + $middleX;
647 0           my $cy = sin($phi)*$cpx + cos($phi)*$cpy + $middleY;
648              
649             # computing theta1 (formula F.6.5.5)
650 0           my $XX = ($xp1-$cpx)/$rx;
651 0           my $YY = ($yp1-$cpy)/$ry;
652 0           my $theta1 = rad2deg (&vectorProduct ( 1,0,
653             $XX,$YY));
654             # computing dTheta (formula F.6.5.6)
655 0           my $dTheta = rad2deg (&vectorProduct ( $XX,$YY,
656             (-$xp1-$cpx)/$rx,(-$yp1-$cpy)/$ry ));
657             # Next To be implemented!!
658             # printf "cx,cy=%d,%d\ttheta1,dtheta=%d,%d\trx,ry=%d,%d\n",$cx,$cy,$theta1,$dTheta,$rx,$ry;
659 0 0 0       if (!$sweep_flag and $dTheta>0) {
660 0           $dTheta-=360;
661             }
662 0 0 0       if ($sweep_flag and $dTheta<0) {
663 0           $dTheta+=360;
664             }
665 0           return join (",", &computeArcPoints($cx,$cy,$rx,$ry,
666             $phi,deg2rad($theta1),deg2rad($dTheta))), "\n";
667             }
668              
669             sub computeArcPoints {
670 0     0 0   my ($cx,$cy,$rx,$ry,$phi,$theta1,$dTheta) = @_;
671 0           my $Nrad = 3.14/18;
672 0           my $N = &float2int(abs($dTheta/$Nrad));
673 0           my $cosPhi = cos($phi);
674 0           my $sinPhi = sin($phi);
675             # print "N,dTheta: $N,$dTheta\n";
676 0           my $dd = $dTheta/$N;
677 0           my @res;
678 0           for (my $i=0; $i<=$N; $i++) {
679 0           my $a = $theta1 + $dd*$i;
680 0           my $xp = $rx*cos($a);
681 0           my $yp = $ry*sin($a);
682 0           my $x1 = $cosPhi*$xp - $sinPhi*$yp + $cx;
683 0           my $y1 = $sinPhi*$xp + $cosPhi*$yp + $cy;
684 0           push @res, "[$x1, $y1]";
685             }
686 0           return @res;
687             }
688              
689             ## vectorial product
690             sub vectorProduct {
691 0     0 0   my ($x1,$y1, $x2,$y2) = @_;
692 0           my $sign = 1;
693 0 0         $sign = -1 if ($x1*$y2 - $y1*$x2) < 0;
694              
695 0           return $sign * acos ( ($x1*$x2 + $y1*$y2)
696             /
697             sqrt ( ($x1**2 + $y1**2) * ($x2**2 + $y2**2) )
698             );
699             }
700              
701             ######################################################################################
702             # points conversions for polygone / polyline
703             ######################################################################################
704              
705             # &points (\%attrs)
706             # converts the string, value of an attribute points
707             # to a string of coordinate list for Tk::Zinc
708             sub points {
709 0     0 0   my ($ref_attrs) = @_;
710 0           my $str = $ref_attrs->{points};
711             # suppressing leading and trailing blanks:
712 0           ($str) = $str =~ /^\s* # leading blanks
713             (.*\S) #
714             \s*$ # trailing blanks
715             /x;
716              
717 0           $str =~ s/([^,])[\s]+([^,])/$1,$2/g ; # replacing blanks separators by a comma
718 0           return $str;
719             }
720              
721             ######################################################################################
722             # cleaning an id to make it usable as a TkZinc Tag
723             ######################################################################################
724              
725             ## the following function cleans an id, ie modifies it so that it
726             ## follows the TkZinc tag conventions.
727             ## BUG: the cleanning is far from being complete
728             sub cleanName {
729 0     0 0   my $id = shift;
730             # to avoid numeric ids
731 0 0         if ($id =~ /^\d+$/) {
732             # &myWarn ("id: $id start with digits\n");
733 0           $id = "id_".$id;
734             }
735             # to avoid any dots in a tag
736 0 0         if ($id =~ /\./) {
737             # &myWarn ("id: $id contains dots\n");
738 0           $id =~ s/\./_/g ;
739             }
740 0           return $id;
741             }
742              
743             ################################################################################
744             # size conversions
745             ################################################################################
746              
747             ## get a list of "size" attributes as listed in @attrs (e.g.: x y width height...)
748             ## - convert all in pixel
749             ## - return 0 for attributes listed in @attrs and not available in %{$ref_attrs}
750             sub sizesConvert {
751 0     0 0   my ($ref_attrs,@attrs) = @_;
752 0           my %attrs = %{$ref_attrs};
  0            
753 0           my @res;
754 0           foreach my $attr (@attrs) {
755 0           my $value;
756 0 0         if (!defined ($value = $attrs{$attr}) ) {
757 0           push @res,0;
758             # print "!!!! undefined attr: $attr\n";
759             } else {
760 0           push @res,&sizeConvert ($value);
761             }
762             }
763 0           return @res;
764             } # end of sizesConvert
765              
766             # currently, to simplify this code, I suppose the screen is 100dpi!
767             # at least the generated code is currently independant from the host
768             # where is is supposed to run
769             # maybe this should be enhanced
770             sub sizeConvert {
771 0     0 0   my ($value) = @_;
772 0 0         if ($value =~ /(.*)cm/) {
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
773 0           return $1 * 40; ## approximative pixel / cm
774             } elsif ($value =~ /(.*)mm/) {
775 0           return $1 * 4; ## approximative pixel / mm
776             } elsif ($value =~ /(\d+)px/) {
777 0           return $1; ## exact! pixel / pixel
778             } elsif ($value =~ /(.*)in/) {
779 0           return &float2int($1 * 100); ## approximative pixel / inch
780             } elsif ($value =~ /(.*)pt/) {
781 0           return &float2int($1 * 100 / 72); ## approximative pixel / pt (a pt = 1in/72)
782             } elsif ($value =~ /(.*)pc/) {
783 0           return &float2int($1 * 100 / 6); ## (a pica = 1in/6)
784             } elsif ($value =~ /(.*)%/) {
785 0           return $1/100; ## useful for coordinates using %
786             ## in lienar gradient (x1,x2,y2,y2)
787             } elsif ($value =~ /(.*)em/) { # not yet implemented
788 0           &myWarn ("em unit not yet implemented in sizes");
789 0           return $value;
790             } elsif ($value =~ /(.*)ex/) { # not yet implemented
791 0           &myWarn ("ex unit not yet implemented in sizes");
792 0           return $value;
793             } else {
794 0           return $value;
795             }
796             } # end of sizeConvert
797              
798              
799             sub float2int {
800 0     0 0   return sprintf ("%.0f",$_[0]);
801             }
802              
803              
804             # process a string describing transformations
805             # returns a list of string describing transformations
806             # to be applied to Tk::Zinc item Id
807             sub transform {
808 0     0 0   my ($id, $str) = @_;
809 0 0         return () if !defined $str;
810 0 0         &myWarn ("!!! Need an Id for applying a transformation\n"), return () if !defined $id;
811 0           my @fullTrans;
812 0           while ($str =~ m/\s*(\w+)\s*\(([^\)]*)\)\s*/g) {
813 0           my ($trans, $params) = ($1,$2);
814 0           my @params = split (/[\s,]+/, $params);
815 0 0         if ($trans eq 'translate') {
    0          
    0          
    0          
    0          
    0          
816 0 0         $params[1] = 0 if scalar @params == 1; ## the 2nd paramter defaults to 0
817 0           my $translation = "->translate($id," . join (",",@params) . ");" ;
818 0           push @fullTrans, $translation;
819             } elsif ($trans eq 'rotate') {
820 0           $params[0] = deg2rad($params[0]);
821 0           my $rotation = "->rotate($id," . join (",",@params) . ");";
822 0           push @fullTrans, $rotation;
823             } elsif ($trans eq 'scale') {
824 0 0         $params[1] = $params[0] if scalar @params == 1; ## the 2nd scale parameter defaults to the 1st
825 0           my $scale = "->scale($id," . join (",",@params) . ");";
826 0           push @fullTrans,$scale;
827             } elsif ($trans eq 'matrix') {
828 0           my $matrixParams = join ',',@params;
829 0           my $matrix = "->tset($id, $matrixParams);";
830 0           push @fullTrans, $matrix;
831             } elsif ($trans eq 'skewX'){
832 0           my $skewX = "->skew($id, " . deg2rad($params[0]) . ",0);";
833             # print "skewX=$skewX\n";
834 0           push @fullTrans, $skewX;
835             } elsif ($trans eq 'skewY'){
836 0           my $skewY = "->skew($id, 0," . deg2rad($params[0]) . ");";
837             # print "skewY=$skewY\n";
838 0           push @fullTrans, $skewY;
839             } else {
840 0           &myWarn ("!!! Unknown transformation '$trans'\n");
841             }
842             # $str = $rest;
843             }
844 0           return reverse @fullTrans;
845             } # end of transform
846              
847             1;