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