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; |