File Coverage

blib/lib/SVG/SVG2zinc.pm
Criterion Covered Total %
statement 4 6 66.6
branch n/a
condition n/a
subroutine 2 2 100.0
pod n/a
total 6 8 75.0


line stmt bran cond sub pod time code
1             package SVG::SVG2zinc;
2              
3             #
4             # convertisseur SVG->TkZinc
5             #
6             # Copyright 2002-2003
7             # Centre d'Études de la Navigation Aérienne
8             #
9             # Author: Christophe Mertz
10             # previously
11             # with many helps from
12             # Alexandre Lemort
13             # Celine Schlienger
14             # Stéphane Chatty
15             #
16             # $Id: SVG2zinc.pm,v 1.42 2004/05/01 09:19:32 mertz Exp $
17             #############################################################################
18             #
19             # this is the main module of the a converter from SVG file
20             # to either perl script/module (an eventually other scripting language)
21             # It is also usable to display SVG graphic file in Tk::Zinc
22              
23             #############################################################################
24             # limitations are now listed in the POD at the end of this file
25             #############################################################################
26              
27 1     1   26484 use strict;
  1         2  
  1         38  
28 1     1   405 use XML::Parser;
  0            
  0            
29             use Carp;
30             use Math::Trig;
31             use Tk::PNG;
32             use Tk::JPEG;
33             use English;
34             use File::Basename;
35              
36             use SVG::SVG2zinc::Conversions;
37              
38             use vars qw($VERSION $REVISION @ISA @EXPORT);
39             @EXPORT = qw( parsefile findINC );
40              
41             $REVISION = q$Revision: 1.42 $ ;
42             $VERSION = "0.10";
43              
44             # to suppress some stupid warning usefull for debugging only
45             my $warn=0;
46              
47             my $verbose;
48              
49             my $current_group;
50             my @prev_groups = ();
51             my %current_context;
52             my @prev_contexts = ();
53              
54             my $itemCount = 0;
55             my $effectiveItemCount = 0; # to know if some groups are empty (cf &defs et &defs_)
56             my $prefix; # prefix used in tags associated to generated items
57             my $colorSep = ";";
58              
59             sub InitVars {
60             @prev_groups = ();
61             %current_context = ();
62             @prev_contexts = ();
63            
64             $itemCount = 0;
65             $effectiveItemCount = 0;
66             $colorSep = ";";
67             }
68              
69             # This hash table indicates all non-implemented extensions
70             # Normaly, the href extension is the only implemented extension listed in the SVG entity
71             # The hash-value associated to a not implemented etension is 0
72             # The hash-value is then set to 1 when an warning message has been printed once
73             my %notImplementedExtensionPrefix;
74              
75              
76             # events on "graphics and container elements"
77             my @EVENT_ON_GRAPHICS = qw/onfocusin onfocusout onactivate onclick
78             onmousedown onmouseup onmouseover onmousemove onmouseout onload/ ;
79             # events on "Document-level event attributes"
80             my @EVENT_ON_DOC = qw /onunload onabort onerror onresize onscroll onzoom/;
81             # events "Animation event attributes"
82             my @EVENT_ON_ANIM = qw /onbegin onend onrepeat/ ;
83              
84             my %EVENT_ON_GRAPHICS = map { $_ => 1 } @EVENT_ON_GRAPHICS;
85             my %EVENT_ON_DOC = map { $_ => 1 } @EVENT_ON_DOC;
86             my %EVENT_ON_ANIM = map { $_ => 1 } @EVENT_ON_ANIM;
87              
88              
89             ### @STYLE_ATTRS and %STYLE_ATTRS are "constant" array and hash used in
90             #### &analyze_style , &analyze_text_style , &groupContext , &attrs_implemented
91             my @STYLE_ATTRS = qw(class style display fill fill-opacity fill-rule stroke
92             stroke-width stroke-opacity opacity font-size font-family
93             font-weight stroke-linejoin stroke-linecap
94             ) ;
95             my %STYLE_ATTRS = map { $_ => 1 } @STYLE_ATTRS;
96              
97             #### not implemented / not implementable attributes
98             #### these attributes will generate only limited warning
99             #### used in &attrs_implemented
100             my @STYLE_ATTRS_NYI = qw (stroke-miterlimit stroke-dasharray
101             gradientUnits gradientTransform spreadMethod
102             clip-rule clip-path
103             name
104             ) ; # what is the foolish name?
105             my %STYLE_ATTRS_NYI = map { $_ => 1 } @STYLE_ATTRS_NYI;
106              
107             #### not yet implemented tags (to avoid many many error messages)
108             #### this list could be used to clearly distinguishe TAGS
109             #### not yet implemented or not implementable.
110             #### This list is curently not used! consider it as a piece of comment!
111             my @NO_YET_IMPLEMENTED_TAGS = qw ( midPointStop
112             filter feColorMatrix feComponentTransfer feFuncA
113             );
114              
115             my $fileDir; ## in fact this could be a part of an url
116             ## but we currently only get files in the some directories
117              
118             my $backend; ## the backend used to produce/interpret perl or tcl or whatever...
119              
120             my $expat;
121             sub parsefile {
122             my ($svgfile, $backendName, %args) = @_;
123              
124             # some init
125             &InitVars;
126             $fileDir = dirname($svgfile)."/";
127            
128             # the group where to create items, defaulted to 1
129             # $current_group = defined $args{-group} ? $args{-group} : 1;
130              
131             # verbosity, defaulted to 0
132             $verbose = defined $args{-verbose} ? $args{-verbose}: 0;
133              
134             # init of the prefix used to prefix tags. defaulted to the empty string
135             $prefix = defined $args{-prefix} ? $args{-prefix} : "";
136             delete $args{-prefix}; # this option is not propagated to Backend
137              
138             # print "The prefix is '$prefix'\n";
139             # should we treat XML namespace?
140             my $namespace = defined $args{-namespace} ? $args{-namespace} : 0;
141             delete $args{-namespace}; # this option is not propagated to Backend
142            
143             ## init of some global variables used by Conversions.pm
144             &SVG::SVG2zinc::Conversions::InitConv(\&myWarn, \¤t_line);
145              
146            
147             my $filename;
148             if ($filename = &findINC($backendName.".pm")) {
149             # print " loading $filename\n";
150             eval {require "$filename"};
151             } elsif ($filename = &findINC("SVG/SVG2zinc/Backend",$backendName.".pm")) {
152             # print " loading $filename\n";
153             eval {require "$filename"};
154             $backendName = "SVG::SVG2zinc::Backend::$backendName";
155             } else {
156             die "unable to find Backend $backendName in perl path @INC";
157             }
158             if ($@) {
159             die "while loading Backend $backendName:\n$@\n";
160             }
161              
162             $backend=$backendName->new(-in => $svgfile, %args);
163              
164             $current_group = $backend->_topgroup;
165             $backend->fileHeader;
166             my $parser = new XML::Parser(
167             Style => 'SVG2zinc',
168             Namespaces => $namespace, # well this works for dia shape dtd!
169             Pkg => 'SVG::SVG2zinc',
170             ErrorContext => 3,
171             );
172             $parser->setHandlers(Char => \&Char,
173             Init => \&Init,
174             Final => \&Final,
175             XMLDecl => \&XMLDecl,
176             );
177             my $svg=$parser->parsefile($svgfile);
178             $backend->fileTail;
179             &print_warning_for_not_implemented_attr;
180             } # end of parsefile
181              
182             ## as it seems that some svg files are using differencies between dtd 1.0 and 1.1
183             ## we need to know which version of the dtd we are using (defaulted to 1.0)
184             my $dtdVersion;
185             sub XMLDecl {
186             my ($parser, $Version, $Encoding, $Standalone) = @_;
187             # $Standalone = '_undef' unless defined $Standalone;
188             # print "XMLDecl: $parser, $Version, $Encoding, $Standalone\n";
189             if (defined $Version) {
190             $dtdVersion = $Version;
191             } else {
192             $dtdVersion = 1.0;
193             }
194             } # end of XMLDecl
195              
196              
197              
198             # the svg tags are translated in group items.
199             # If the SVG tag contains both width and height properties
200             # they will be reported in the generated group as tags :
201             # 'height=xxx' 'width=xxx'
202             sub svg {
203             my ($parser, $elementname, %attrs) = @_;
204             %attrs = &expandAttributes ($elementname,%attrs);
205             my ($name,$natural) = &name ($elementname, $attrs{id});
206             delete $attrs{xmlns}; # this attribute is mandatory, but useless for SVG2zinc
207              
208             my ($width,$height)=&sizesConvert( \%attrs , qw (width height)); #! this defines the Zinc size!
209             # case when the width or height is defined in %
210             # the % refers to the size of an including document
211             undef $width if defined $attrs{width} and $attrs{width} =~ /%/ ;
212             undef $height if defined $attrs{height} and $attrs{height}=~ /%/ ;
213             # print "WIDTH,HEIGHT = $width $height\n";
214             my $widthHeightTags="";
215             if (defined $width and defined $height) {
216             $widthHeightTags = ", 'width=" . &float2int($width) .
217             "', 'height=" . &float2int($height) . "'";
218             }
219             if (!@prev_contexts) { # we are in the very top svg group!
220             $widthHeightTags .= ", 'svg_top'";
221             }
222             my $res = "->add('group',$current_group, -tags => [$name$widthHeightTags], -priority => 10";
223             unshift @prev_contexts, \%current_context;
224             my $prop;
225             ($prop, %current_context) = &groupContext ($name, %attrs);
226             $res .= $prop . ");";
227            
228             unshift @prev_groups, $current_group;
229             $current_group = $name;
230            
231             foreach my $attr (keys %attrs) {
232             if ($attr =~ /^xmlns:(.+)/ ) {
233             my $extensionPrefix = $1;
234             # this xlink extension is only partly implemented
235             # (ie. when the url refers an image file in the same directory than the SVG file)
236             next if ($extensionPrefix eq 'xlink');
237             print "$extensionPrefix is not implemented\n";
238             $notImplementedExtensionPrefix{$extensionPrefix} = 0;
239             }
240             }
241            
242             &attrs_implemented ( 'svg', $name, [qw ( id width height viewBox preserveAspectRatio
243             xmlns),
244             # the following attributes are not currently implementable
245             qw ( enable-background overflow )
246             ], %attrs );
247             &stackPort($name, $width,$height, $attrs{viewBox}, $attrs{preserveAspectRatio});
248             &display ($res);
249             } # end of svg
250              
251             my @portStack;
252             sub stackPort {
253             # my ($name, $width,$height,$viewbox,$aspectRatio)=@_;
254             unshift @portStack, [ @_ ];
255             }
256              
257             ## to treat the viewbox, preserveAspectRatio attributes
258             ## of the svg, symbol, image, foreignObject... entities
259             sub viewPortTransforms {
260             my $portRef = shift @portStack;
261             my ($name, $width,$height,$viewbox,$aspectRatio)=@{$portRef};
262             $viewbox = "" unless defined $viewbox;
263             $aspectRatio = "" unless defined $aspectRatio;
264             $width = "" unless defined $width;
265             $height = "" unless defined $height;
266             # print "In $name: width=$width height=$height viewbox=$viewbox aspectRatio=$aspectRatio\n";
267             if ($viewbox and $width and $height ) {
268             my $expr = "->adaptViewport($name, $width,$height, '$viewbox', '$aspectRatio');";
269             # print "Expr = $expr\n";
270             &display($expr);
271             # if (!$aspectRatio or $aspectRatio eq "none") {
272             # my $translateX = $minx;
273             # my $translateY = $miny;
274             # my $scaleX= $width / ($portWidth - $minx);
275             # my $scaleY= $height / ($portHeight - $miny);
276             # @transfs = ("->translate($name, $translateX, $translateY);",
277             # "->scale($name, $scaleX, $scaleY);");
278             # &display(@transfs);
279             }
280             }
281              
282              
283             sub svg_ {
284             my ($parser, $elementname) = @_;
285             print "############ End of $elementname:\n" if $verbose;
286             &viewPortTransforms;
287             $current_group = shift @prev_groups;
288             %current_context = %{shift @prev_contexts};
289             }
290              
291             # just to avoid useless warning messages
292             sub desc {}
293             sub desc_ { }
294              
295             # just to avoid useless warning messages
296             sub title {}
297             sub title_ { }
298              
299             # just to avoid useless warning messages in svg tests suites
300             sub Paragraph {}
301             sub Paragraph_ { }
302              
303             ## return either the id of the object or a name of the form '____<$counter>'
304             ## the returned named includes single quotes!
305             ## it also increments two counters:
306             ## - the itemCount used for naming any item
307             ## - the effectiveItemCount for counting graphic items only
308             ## This counter is used at the end of a defs to see if a group
309             ## must be saved, or if the group is just empty
310             sub name {
311             my ($type, $id) = @_;
312             print "############ In $type:\n" if $verbose;
313             $itemCount++;
314             $effectiveItemCount++ if (defined $id and
315             $type ne 'defs' and
316             $type ne 'switch' and
317             $type ne 'g' and
318             $type ne 'svg' and
319             $type !~ /Gradient/
320             );
321             if (defined $id) {
322             $id = &cleanName ($id);
323             return ("'$id'", 1);
324             } else {
325             return ("'" . $prefix . "__$type"."__$itemCount'",0);
326             }
327             } # end of name
328              
329             sub g {
330             my ($parser, $elementname, %attrs) = @_;
331             %attrs = &expandAttributes ($elementname,%attrs);
332             my ($name,$natural) = &name ($elementname, $attrs{id});
333             # print "Group: $name\n";
334             my $res = "->add('group',$current_group, -tags => [$name], -priority => 10";
335             unshift @prev_groups, $current_group;
336             $current_group = $name;
337             unshift @prev_contexts, \%current_context;
338             my $prop;
339             # print "GROUP1 context: ", (%attrs),"\n";
340             ($prop, %current_context) = &groupContext ($name, %attrs);
341             # print "g1:",join(" ",( %current_context )), "\n";
342             $res .= $prop . ");";
343             &attrs_implemented ( 'g', $name, [qw ( id transform ) , @EVENT_ON_GRAPHICS ], %attrs ); ### les styles attrs sont à traiter à part!
344             &display ($res,
345             &transform($name, $attrs{transform}));
346             &treatGroupEvent ($name, %attrs);
347             } # end of g
348              
349             ## returns true if the parameter is an EVENT_ON_GRAPHICS (ie. applies only to group-like tags)
350             sub isGroupEvent {
351             my ($attr) = @_;
352             return $EVENT_ON_GRAPHICS{$attr} or 0;
353             }
354              
355             ## should bing callbacks to group, depending on events and scripts...
356             ## not yet implemented
357             sub treatGroupEvent {
358             my ($objname, %attr) = (@_);
359             foreach my $event (@EVENT_ON_GRAPHICS) {
360             my $value = $attr{$event};
361             next unless defined $value;
362             # print "## $objname HAS EVENT $event = $value\n";
363             # XXX what should I do here?
364             }
365             }
366              
367             sub groupContext {
368             my ($name, %attrs) = @_;
369             my %childrenContext;
370             my $prop = "";
371             foreach my $attr (keys %attrs) {
372             my $value = $attrs{$attr};
373             # print "IN $name : $attr := $value\n";
374             if (!defined $value) {
375             &myWarn ("!! Undefined value for attribute $attr in group $name !?");
376             next;
377             } elsif (&isGroupEvent ($attr)) {
378             next;
379             }
380             $value = &removeComment($value);
381             if ($attr eq 'opacity') { # attributes to apply directly to the group
382             $value = &convertOpacity ($value);
383             $prop = sprintf ", -alpha => %i", &float2int($value * 100);
384             } elsif ($attr eq 'id' or $attr eq 'transform') { # attributes treated before!
385             next;
386             } elsif ($attr eq 'display' and $value eq 'none') {
387             ## beware: the visibility attribut is inheritated but can be modidied by a child
388             ## I put it in the %childrenContext and children will manage it
389             $prop .= ", -visible => 0, -sensitive => 0";
390             &myWarn ("!! The following group is not visible: $name !?\n");
391             } elsif (&isAnExtensionAttr($attr)) {
392             next;
393             } elsif ($attr eq 'viewBox' or $attr eq 'preserveAspectRatio' or $attr eq 'height' or $attr eq 'width') {
394             ### hack which works fine for managing the viewport!!
395             } elsif (!defined $STYLE_ATTRS{$attr}) { # this attribute is not implemented!
396             if (defined $STYLE_ATTRS_NYI{$attr}) {
397             ¬_implemented_attr($attr);
398             } else {
399             &myWarn ("!!! Unimplemented attribute '$attr' (='$value') in group $name\n");
400             }
401             next;
402             } else { # all other attributes will be applied to children
403             $childrenContext{$attr} = $value;
404             }
405             }
406             print "children context: ", join (", ", (%childrenContext)) , "\n" if $verbose;
407             return ($prop, %childrenContext);
408             } # end of groupContext
409            
410              
411             sub g_ {
412             my ($parser, $elementname) = @_;
413             print "############ End of $elementname:\n" if $verbose;
414             $current_group = shift @prev_groups;
415             %current_context = %{shift @prev_contexts};
416             }
417              
418             ## A switch is implemented as a group.
419             ## BUG: In fact, we should select either the first if the tag is implemented
420             ## or the secund sub-tag if not.
421             ## In practice, the first sub-tag is not implemented in standard SVG, so we
422             ## we forget it and take the second one.
423             ## A problem will appear if the first tag is implemented, because, in this case
424             ## we will instanciantes both the first and second
425             sub switch {
426             my ($parser, $elementname, %attrs) = @_;
427             %attrs = &expandAttributes ($elementname,%attrs);
428             my ($name) = &name ($elementname, $attrs{id});
429             $name =~ s/\'//g;
430             $attrs{id} = $name;
431             &g($parser, $elementname, %attrs);
432             } # end of switch
433              
434             sub switch_ {
435             # my ($parser, $elementname) = @_;
436             &g_;
437             }
438              
439             # a clipath is a not-visible groupe whose items define a clipping area
440             # usable with the clip-path attribute
441             # BUG: currently, the clipping is not implemented, but at least clipping
442             ## items are put in a invisible sub-group and are not displayed
443             sub clipPath {
444             my ($parser, $elementname, %attrs) = @_;
445             %attrs = &expandAttributes ($elementname,%attrs);
446             my ($name,$natural) = &name ($elementname, $attrs{id});
447             print "In clippath $name NYI\n";
448             ## should we verify that the clippath has an Id?
449             my $res = "->add('group',$current_group, -tags => [$name, '__clipPath'], -priority => 10, -atomic => 1, -visible => 0";
450             unshift @prev_groups, $current_group;
451             $current_group = $name;
452             unshift @prev_contexts, \%current_context;
453             my $prop;
454             ($prop, %current_context) = &groupContext ($name, %attrs);
455             $res .= $prop . ");";
456             # &attrs_implemented ( 'g', $name, [qw ( id transform ) , @EVENT_ON_GRAPHICS ], %attrs ); ### les styles attrs sont à traiter à part!
457             &display ($res, &transform($name, $attrs{transform}));
458             # &treatGroupEvent ($name, %attrs);
459             } # end of clippath
460              
461             sub clipPath_ {
462             my ($parser, $elementname) = @_;
463             print "############ End of $elementname:\n" if $verbose;
464             $current_group = shift @prev_groups;
465             %current_context = %{shift @prev_contexts};
466             } # end of clippath_
467              
468             # a symbol is a non-visible group which will be instancianted (cloned)
469             # latter in a tag
470             sub symbol {
471             my ($parser, $elementname, %attrs) = @_;
472             %attrs = &expandAttributes ($elementname,%attrs);
473             my ($name,$natural) = &name ($elementname, $attrs{id});
474             ## should we verify that the clippath has an Id?
475             ## should we verify that is defined inside a tag?
476             my $res = "->add('group',$current_group, -tags => [$name], -priority => 10, -atomic => 1, -visible => 0";
477             unshift @prev_groups, $current_group;
478             $current_group = $name;
479             unshift @prev_contexts, \%current_context;
480             my $prop;
481             ($prop, %current_context) = &groupContext ($name, %attrs);
482             $res .= $prop . ");";
483             # &attrs_implemented ( 'g', $name, [qw ( id transform ) , @EVENT_ON_GRAPHICS ], %attrs ); ### les styles attrs sont à traiter à part!
484             &display ($res, &transform($name, $attrs{transform}));
485             # &treatGroupEvent ($name, %attrs);
486             } # end of symbol
487              
488             sub symbol_ {
489             my ($parser, $elementname) = @_;
490             print "############ End of $elementname:\n" if $verbose;
491             $current_group = shift @prev_groups;
492             %current_context = %{shift @prev_contexts};
493             } # end of symbol_
494              
495             # this will clone and make visible either symbols or other items based on the Id refered by the xlink:href attribute
496             sub use {
497             my ($parser, $elementname, %attrs) = @_;
498             %attrs = &expandAttributes ($elementname,%attrs);
499             my ($name,$natural) = &name ($elementname, $attrs{id});
500             # my @attrs = %attrs; print "############ Start of $elementname: @attrs\n" if $verbose;
501             my $ref = $attrs{'xlink:href'};
502             if (!defined $ref) {
503             &myWarn ("!! $elementname must have a xlink:href attribute\n");
504             return;
505             }
506             $ref =~ s/\#//;
507             my $cleanedId = &cleanName($ref); # to make the name zinc compliant
508             my $res = "->clone('$cleanedId', -visible => 1, -tags => [$name, 'cloned_$cleanedId']";
509             $res .= &analyze_style (\%attrs);
510             $res .=");";
511             my ($x,$y,$width,$height) = ($attrs{x},$attrs{y},$attrs{width},$attrs{height});
512             my @transforms = "->chggroup($name, $current_group);";
513             if (defined $x) {
514             push @transforms, "->translate($name, $x,$y);";
515             }
516             &display ($res,@transforms);
517             }
518              
519             { ## start of defs block to share $res and other variables between many functions
520              
521             ## XXX: BUG this code DOES NOT allow recursive defs! (this is also probably a bug in the SVG file)
522             my $defsCounter = 0;
523             my $insideGradient = 0; ## should never exceed 1!
524             my $res; # the current gradient/object being defined
525             my $defsId; # the group id containing items to be cloned
526             # this group will be deleted later if it is empty
527            
528             my $effectiveItem;
529             ## a will generate the creation of an invisible group in Tk::Zinc
530             ## to be cloned latter in a tag
531             ## This group can be potentialy empty and in this cas it would be better
532             ## not to create it, or at least delete it latter if it is empty
533             sub defs {
534             my ($parser, $elementname, %attrs) = @_;
535             %attrs = &expandAttributes ($elementname,%attrs);
536             ($defsId) = &name ($elementname, $attrs{id});
537             $defsId =~ s/\'//g;
538             $attrs{id} = $defsId;
539             &g($parser, $elementname, %attrs);
540             &display("->itemconfigure('$defsId', -visible => 0);");
541             $defsCounter++;
542             $effectiveItem = $effectiveItemCount;
543             print "############ $elementname: $defsId\n" if $verbose;
544             }
545              
546             sub defs_ {
547             my ($parser, $elementname) = @_;
548             $defsCounter++;
549             # print "end of defs $defsId:", $effectiveItemCount , $effectiveItem, "\n";
550             &g_;
551             if ($effectiveItemCount == $effectiveItem) {
552             &display ("->remove('$defsId');");
553             }
554             }
555              
556              
557             ######################################################################
558             ### CSS : Cascading Style Sheet
559             ######################################################################
560             { ### css
561             my @styles;
562             my %classes;
563             my %elementClasses;
564             my %idClasses;
565             my $in_css=0;
566             sub nextStyle {
567             my $text = shift;
568             push @styles,$text;
569             # print "Style: $text\n";
570             }
571              
572             ## returns a reference to a hash-table defining pair of (attribut value) describing
573             ## a CSS style associated to a Class
574             ## returns undef if such class is not defined
575             sub getClass {
576             my $class = shift;
577             my $ref_styles = $classes{$class};
578             # print "in getClass: $class ",%classes, "\n";
579             # my %styles = %{$ref_styles}; print "in getClass: $class ", (%styles), "\n";
580             return ($ref_styles);
581             }
582              
583             ## returns a reference to a hash-table defining pair of (attribut value) describing
584             ## a CSS style associated to an element type
585             ## returns undef if such element type is not defined
586             sub getElementClass {
587             my $element = shift;
588             my $ref_styles = $elementClasses{$element};
589             # my %styles = %{$ref_styles};
590             # print "in getElementClass: $element ", (%styles), "\n";
591             return ($ref_styles);
592             }
593              
594             ## returns a reference to a hash-table defining pair of (attribut value) describing
595             ## a CSS style associated to an Id
596             ## returns undef if such class is not defined
597             sub getIdClass {
598             my $id = shift;
599             my $ref_styles = $idClasses{$id};
600             # my %styles = %{$ref_styles};
601             # print "in getIdClass: $id ", (%styles), "\n";
602             return ($ref_styles);
603             }
604              
605             sub style {
606             my ($parser, $elementname, %attrs) = @_;
607             if ($attrs{type} eq "text/css") {
608             $in_css=1;
609             }
610             } # end of style
611              
612             sub style_ {
613             my ($parser, $elementname) = @_;
614             my $str = "";
615             foreach my $s (@styles) {
616             $s = &removeComment($s);
617             $s =~ s/\s(\s+)//g ; # removing blocks of blanks
618             $str .= " " . $s;
619             }
620             # print "in style_: $str\n";
621             while ($str) {
622             # print "remaning str in style_: $str\n";
623             if ($str =~ /^\s*\.(\S+)\s*\{\s*([^\}]*)\}\s*(.*)/ ) {
624             # class styling
625             my ($name,$value) = ($1,$2);
626             $str = $3;
627             # $value =~ s/\s+$//;
628             print "STYLE of class: '$name' => '$value'\n";
629             ## and now do something!
630             my %style = &expandStyle($value);
631             $classes{$1} = \%style;
632             } elsif ( $str =~ /^\s*\#([^\{]+)\s*\{\s*([^\}]*)\}\s*(.*)/ ) {
633             my ($ids,$value) = ($1,$2);
634             $str = $3;
635             print "STYLE of ids: '$ids' => '$value'\n";
636             ## and now do something!
637             } elsif ( $str =~ /^\s*\[([^\{]+)\]\s*\{\s*([^\}]*)\}\s*(.*)/ ) {
638             my ($attr_val,$value) = ($1,$2);
639             $str = $3;
640             print "STYLE of attr_values: '$attr_val' => '$value'\n";
641             ## and now do something!
642             } elsif ( $str =~ /^\s*\@font-face\s*\{\s*[^\}]*\}\s*(.*)/ ) {
643             print "STYLE of font-face", substr($str, 0, 100),"....\n";
644             $str = $1;
645             } elsif ( $str =~ /^\s*([^\s\{]+)\s*\{\s*([^\}]*)\}\s*(.*)/ ) {
646             my ($name,$value) = ($1,$2);
647             $str = $3;
648             print "STYLE of tags: '$name' => '$value'\n";
649             ## and now do something... NYI
650             } else {
651             &myWarn ("unknown style : $str\nskipping this style");
652             return;
653             }
654             }
655             $in_css=0;
656             @styles=();
657             } # end of style_
658              
659             } ### end of css
660              
661             ######################################################################
662             ### gradients
663             ######################################################################
664              
665             my $gname;
666             my @stops;
667             my @inheritedStops;
668             my $angle;
669             my $center;
670              
671             sub radialGradient {
672             my ($parser, $elementname, %attrs) = @_;
673             %attrs = &expandAttributes ($elementname,%attrs);
674             my ($name,$natural) = &name ($elementname, $attrs{id});
675             &myWarn ("!! $elementname must have an id\n") unless $natural;
676             $gname = substr ($name,1,-1); # remove quote (') at the very beginning and end of $name
677             $insideGradient ++;
678             &myWarn ("Gradient '$gname' definition inside a previous gradient definition. This is bug in svg source\n")
679             unless $insideGradient == 1;
680             $res="->gname(";
681             @stops = ();
682             @inheritedStops = ();
683             if (defined $attrs{'xlink:href'}) {
684             my $unused;
685             my $link = delete $attrs{'xlink:href'};
686             if ($link =~ /^\#(.+)$/) {
687             $link = $1;
688             ($unused, @inheritedStops) = &extractGradientTypeAndStops ($link);
689             } else {
690             # BUG??: we only treat internal links like #gradientName
691             carp "bad link towards a gradient: $link";
692             }
693             }
694             my ($fx,$fy,$cx,$cy, $r) = &sizesConvert( \%attrs , qw (fx fy cx cy r));
695             # BUG: a serious limitation is that TkZinc (3.2.6i) does not support
696             # the cx, cy and r parameters
697              
698             if (defined $cx and $cx == $fx) { delete $attrs{cx}; } # to avoid needless warning of &attrs_implemented
699             if (defined $cy and $cy == $fy) { delete $attrs{cy}; } # to avoid needless warning of &attrs_implemented
700             &attrs_implemented ( 'radialGradient', $name, [qw ( id fx fy)], %attrs );
701              
702             $fx = &float2int(($fx -0.5) * 100);
703             $fy = &float2int(($fy -0.5) * 100);
704             $center = "$fx $fy";
705             }
706              
707             sub radialGradient_ {
708             $insideGradient --;
709             if (!@stops) {
710             if (@inheritedStops) {
711             @stops = @inheritedStops;
712             } else {
713             carp ("Bad gradient def: nor stops, neither xlink;href");
714             }
715             }
716             my $gradientDef = "=radial $center | " . join (" | ", @stops);
717             $res .= "\"" . $gradientDef . "\", \"$gname\");"; ### BUG: limits: x y!
718             # si il faut appliquer une transparence sur un gradient on est très embêté!
719             &defineNamedGradient($gname, $gradientDef) ;
720             # print "RADIAL='$res'\n";
721             &display($res) ;
722             @stops = ();
723             }
724              
725             sub linearGradient {
726             my ($parser, $elementname, %attrs) = @_;
727             %attrs = &expandAttributes ($elementname,%attrs);
728             my ($name,$natural) = &name ($elementname, $attrs{id});
729             &myWarn ("!! $elementname must have an id\n") unless $natural;
730             $gname = substr ($name,1,-1); # remove quote (') at the very beginning and end of $name
731             $insideGradient ++;
732             &myWarn ("Gradient '$gname' definition inside a previous gradient Definition. This will bug\n")
733             unless $insideGradient == 1;
734             $res="->gname(";
735             @stops = ();
736             @inheritedStops = ();
737             if (defined $attrs{'xlink:href'}) {
738             my $unused;
739             my $link = delete $attrs{'xlink:href'};
740             if ($link =~ /^\#(.+)$/) {
741             $link = $1;
742             ($unused, @inheritedStops) = &extractGradientTypeAndStops ($link);
743             } else {
744             # BUG??: we only treat internal links like #gradientName
745             carp "bad link towards a gradient: $link";
746             }
747             }
748             &attrs_implemented ( 'linearGradient', $name, [qw ( x1 x2 y1 y2 id )], %attrs );
749             my ($x1,$x2,$y1,$y2) = &sizesConvert( \%attrs , qw (x1 x2 y1 y2));
750             if ( ($y2 - $y1) or ($x2 - $x1) ) {
751             my $atan = - rad2deg (atan2 ($y2-$y1,$x2-$x1));
752             $angle = &float2int($atan);
753             } else {
754             $angle = 0;
755             };
756             # print "ANGLE = $angle\n";
757             }
758              
759             sub linearGradient_ {
760             $insideGradient --;
761             if (!@stops) {
762             if (@inheritedStops) {
763             @stops = @inheritedStops;
764             } else {
765             carp ("Bad gradient def: nor stops, neither xlink;href");
766             }
767             }
768             my $gradientDef = "=axial $angle | " . join (" | ", @stops);
769             $res .= "\"" . $gradientDef . "\", \"$gname\");";
770             # si il faut appliquer une transparence sur un gradient on est très embêté!
771             &defineNamedGradient($gname, $gradientDef) ;
772             &display($res) ;
773             }
774              
775             sub stop {
776             my ($parser, $elementname, %attrs) = @_;
777             %attrs = &expandAttributes ($elementname,%attrs);
778             # my ($name,$natural) = &name ($elementname, $attrs{id}); # no name is needed!
779             &myWarn ("$elementname should be defined inside or \n") unless $insideGradient;
780              
781             my $style = delete $attrs{'style'};
782             if (defined $style) {
783             my %keyvalues = &expandStyle($style);
784             %attrs = (%attrs , %keyvalues);
785             }
786             my $offset = $attrs{'offset'};
787             my $color = $attrs{'stop-color'};
788             if (!defined $color) {
789             &myWarn ("!! Undefined stop-color in a \n");
790             } elsif (!defined $offset) {
791             &myWarn ("!! Undefined offset in a \n");
792             } else {
793             if ($offset =~ /([\.\d]+)%/){
794             $offset = &float2int($1);
795             # } elsif ($offset =~ /^([.\d]+)$/) {
796             # $offset = &float2int($1*100);
797             } else {
798             $offset = &float2int($offset*100);
799             }
800             my $color=&colorConvert($color);
801             if (defined (my $stopOpacity = $attrs{'stop-opacity'})) {
802             $stopOpacity = &float2int($stopOpacity*100);
803             push @stops, "$color$colorSep$stopOpacity $offset";
804             } else {
805             push @stops, "$color $offset";
806             }
807             }
808             } # end of stop
809              
810             } # end of gradient closure
811              
812              
813             my %convertFormat = (
814             'jpg' => 'jpeg',
815             'jpeg' => 'jpeg',
816             'png' => 'png',
817             );
818              
819             sub image {
820             my ($parser, $elementname, %attrs) = @_;
821             %attrs = &expandAttributes ($elementname,%attrs);
822             my ($name,$natural) = &name ($elementname, $attrs{id});
823             # &myWarn ("!! $elementname must have an id\n") unless $natural;
824              
825             my $group = $current_group;
826             my @RES;
827             if (my $opacity = $attrs{'opacity'}) {
828             # creating an intermediate group for managing the transparency
829             # BUG: we could used the attribute -color := white:$opacity
830             $opacity = &convertOpacity ($opacity);
831             if ($opacity != 1) {
832             ## on crée un groupe pour gérer la transparence
833             my $opacity = &float2int(100 * $opacity);
834             my $newgroup = substr ($name,0,-1) . "transparency'";
835             push @RES , "->add('group', $current_group, -alpha => $opacity, -tags => [ $newgroup ], -priority => 10);\n";
836             $group = $newgroup;
837             }
838             }
839             my $res = "";
840             my $ref = "";
841             if ($ref = $attrs{'xlink:href'}) {
842              
843             if ($ref =~ /^data:image\/(\w+);base64,(.+)/) {
844              
845             # this code has been provided by A. Lemort from Intuilab
846             # for uuencoded inline image
847             my $format = $1;
848             my $data = $2;
849             # print ("data:image: '", substr($ref,0,30), "....' format=$format\n");
850             $ref = "data:image/$format;base64"; # $ref is used later in a tag of the icon
851             $format = $convertFormat{lc($format)};
852             $res .= "->add('icon',$group, -image => ->Photo(-data => '$data', -format => '$format')";
853             } elsif ($ref =~ /^data:;base64,(.+)/) {
854             ## the following piece of code works more or less ?!
855             ## BUG: there is a pb with scaling (ex: data-svg/vero_data/propal_crea_boutons.svg)
856             my $data = $1;
857             # print ("data:; '", substr($ref,0,30), "....' NO format!\n");
858             $ref = "data:;base64"; # $ref is used later in a tag of the icon
859             $res .= "->add('icon',$group, -image => ->Photo(-data => '$data')";
860             } else {
861             # It's a file
862             # print "Including image : $fileDir$ref\n";
863             if (open REF, "$fileDir$ref") {
864             close REF;
865             # print "group='$group' ref='$ref' filedir=$fileDir\n";
866             $res .= "->add('icon',$group, -image => ->Photo('$ref', -file => '$fileDir$ref')";
867             } else {
868             &myWarn ("When parsing the image '$name': no such file: '" . substr ("$fileDir$ref", 0,50) . "'\n") ;
869             return;
870             }
871             }
872             } else {
873             &myWarn ("Unable to parse the image '$name'") ;
874             return;
875             }
876              
877             $res .= ", -tags => [$name, '$ref'], -composescale => 1, -composerotation => 1, -priority => 10);";
878             push @RES, $res ;
879              
880             my ($x, $y, $width, $height) = &sizesConvert ( \%attrs , qw (x y width height));
881             if ($width == 0 or $height == 0) {
882             &myWarn ("Skipping a 0 sized image: '$name' size is $width x $height\n");
883             } elsif ($width < 0 or $height < 0) {
884             &myWarn ("Error in the size of the image '$name' : $width x $height\n");
885             } else {
886             push @RES, "->adaptViewport($name, $width,$height);";
887             }
888             if ($x or $y) {
889             push @RES, "->translate($name, $x,$y);";
890             }
891            
892             &attrs_implemented ( 'image', $name, [qw ( x y width height id )], %attrs );
893             &display (@RES,
894             &transform($name, $attrs{transform}) );
895             } # end of image
896              
897              
898             sub line {
899             my ($parser, $elementname, %attrs) = @_;
900             %attrs = &expandAttributes ($elementname,%attrs);
901             my ($name,$natural) = &name ($elementname, $attrs{id});
902             my $res = "->add('curve',$current_group,[$attrs{x1},$attrs{y1},$attrs{x2},$attrs{y2}], -priority => 10";
903             $res .= ", -tags => ['line'";
904             $res .= ", $name" if ($natural or $attrs{transform});
905             $res .= "]";
906             $res .= &analyze_style (\%attrs);
907             $res .=");";
908             &attrs_implemented ( 'line', $name, [qw (x1 y1 x2 y2 style id transform )], %attrs );
909             &display ($res,
910             &transform($name, $attrs{transform}) );
911             } # end of line
912              
913              
914             sub Char {
915             my ($expat, $text) = @_;
916             return if !defined $text;
917             my $type = ($expat->context)[-1];
918             return if !defined $type;
919             chomp $text;
920             return if (!$text && ($text ne "0")); # empty text!
921             # $text =~ s/([\x80-\xff])/sprintf "#x%X;", ord $1/eg;
922             # $text =~ s/([\t\n])/sprintf "#%d;", ord $1/eg;
923             # print "$type: $text\n";
924             if ($type eq 'tspan' or $type eq 'text') {
925             # print "[$text]\n";
926             &nextText ($text);
927             } elsif ($type eq 'style') {
928             &nextStyle ($text);
929             }
930             } # end of char
931              
932              
933             ## this lexical block allows &text, &nextTetx, &tspan, and &text_ to share common variables
934             {
935             my $res;
936             my @transforms;
937             my @texts;
938             sub text {
939             my ($parser, $elementname, %attrs) = @_;
940             %attrs = &expandAttributes ($elementname,%attrs);
941             my ($name,$natural) = &name ($elementname, $attrs{id});
942             my ($x,$y)=&sizesConvert( \%attrs , qw (x y));
943             $res = "->add('text',$current_group, -position => [$x,$y], -tags => ['text'";
944             $res .= ", $name" if ($natural or $attrs{transform});
945             $res .= "], -anchor => 'sw'"; ## XXX est-ce bien la bonne ancre?
946             ## XXX BUG? je ne suis pas sur que la ligne suivante soit indispensable?!
947             $res .= &analyze_text_style (\%attrs);
948             @texts = ();
949             @transforms = reverse &transform($name, $attrs{transform});
950             &attrs_implemented ( 'text', $name, [qw (x y id transform text-anchor font-family font-size)], %attrs );
951             } # end of text
952              
953             sub nextText {
954             my $txt = shift;
955             push @texts,$txt;
956             }
957              
958              
959             ## BUG: tags can be used to modiofy many graphics attributs of the part of the text
960             ## such as colors, fonte, size and position...
961             ## this is currently hard to implement as in Tk::Zinc a text item can only have one color, one size...
962             sub tspan {
963             my ($expat, $elementname, %attrs) = @_;
964             # my @attrs = %attrs; print "TSPAN: $elementname @attrs\n";
965             $res .= &analyze_text_style (\%attrs);
966             } # end of tspan
967              
968             sub text_ {
969             my ($parser, $elementname, @rest) = @_;
970             # my $text = join ('+++',@texts); print "TEXT_ : $text\n";
971             for (my $i=0 ; $i <= $#texts ; $i++) {
972             $texts[$i] =~ s/\'/\\'/g ; #'
973             }
974             my $theText = join ('', @texts);
975             $res .= ", -text => '$theText', -priority => 10);";
976             &display ($res, @transforms);
977             } # end of test_
978              
979             } ## end of text lexical block
980              
981             sub polyline {
982             my ($parser, $elementname, %attrs) = @_;
983             %attrs = &expandAttributes ($elementname,%attrs);
984             my ($name,$natural) = &name ($elementname, $attrs{id});
985             my $res = "->add('curve',$current_group,[" . &points(\%attrs);
986             $res .= "], -tags => ['polyline'";
987             $res .= ", $name" if ($natural or $attrs{transform});
988             $res .= "], -priority => 10";
989             $res .= &analyze_style (\%attrs);
990             $res .=");";
991             &attrs_implemented ( 'polyline', $name, [qw (points style transform id )], %attrs );
992             &display ($res,
993             &transform($name, $attrs{transform}) );
994             }
995              
996             sub rect {
997             my ($parser, $elementname, %attrs) = @_;
998             %attrs = &expandAttributes ($elementname,%attrs);
999             my ($name,$natural) = &name ($elementname, $attrs{id});
1000             my ($x,$y,$width,$height)=&sizesConvert( \%attrs , qw (x y width height));
1001             my $res = "->add('rectangle',$current_group,[$x,$y,"
1002             .($x+$width).",".($y+$height)."], -tags => ['rect'";
1003             $res .= ", $name" if ($natural or $attrs{transform});
1004             $res .= "], -priority => 10";
1005             # by default, rectangles are filled (cf example svg/use02_p87.svg
1006             # from svg specifs). The value is set here, and can be overidden later
1007             # in the &analyze_style
1008             $res .= ", -filled => 1" unless defined $attrs{fill} and $attrs{fill} eq 'none';
1009             delete $attrs{'stroke-linejoin'}; ## BUG: due to TkZinc limitation: no -joinstyle for rectangle
1010             $res .= &analyze_style (\%attrs);
1011             $res .=");";
1012             &attrs_implemented ( 'rect', $name, [qw (id x y width height style transform )], %attrs );
1013             &display ($res,
1014             &transform($name, $attrs{transform}) );
1015             }
1016              
1017              
1018             sub ellipse {
1019             my ($parser, $elementname, %attrs) = @_;
1020             %attrs = &expandAttributes ($elementname,%attrs);
1021             my ($name,$natural) = &name ($elementname, $attrs{id});
1022             my ($cx,$cy,$rx,$ry)=&sizesConvert( \%attrs , qw (cx cy rx ry));
1023             my $res = "->add('arc',$current_group,[". ($cx-$rx) . ", ". ($cy-$ry) .
1024             ", " . ($cx+$rx) . ", ". ($cy+$ry) . "], -tags => ['ellipse',";
1025             $res .= ", $name" if ($natural or $attrs{transform});
1026             $res .= "], -priority => 10";
1027             # by default, ellipses are filled
1028             # from svg specifs). The value is set here, and can be overidden later
1029             # in the &analyze_style
1030             $res .= ", -filled => 1" unless defined $attrs{fill} and $attrs{fill} eq 'none';
1031             delete $attrs{'stroke-linejoin'}; ## BUG: due to TkZinc limitation: no -joinstyle for arc
1032             $res .= &analyze_style (\%attrs);
1033             $res .=");";
1034             &attrs_implemented ( 'ellipse', $name, [qw (cx cy rx ry style transform id )], %attrs );
1035             &display ($res,
1036             &transform($name, $attrs{transform}) );
1037             }
1038              
1039             sub circle {
1040             my ($parser, $elementname, %attrs) = @_;
1041             %attrs = &expandAttributes ($elementname,%attrs);
1042             my ($name,$natural) = &name ($elementname, $attrs{id});
1043             my ($cx,$cy,$r)=&sizesConvert( \%attrs , qw (cx cy r));
1044             my $res = "->add('arc',$current_group,[". ($cx-$r) . ", ". ($cy-$r) .
1045             ", " . ($cx+$r) . ", ". ($cy+$r) . "], -tags => ['circle'";
1046             $res .= ", $name" if ($natural or $attrs{transform});
1047             $res .= "], -priority => 10";
1048             # by default, circles are filled
1049             # from svg specifs). The value is set here, and can be overidden later
1050             # in the &analyze_style
1051             $res .= ", -filled => 1" unless defined $attrs{fill} and $attrs{fill} eq 'none';
1052             $res .= &analyze_style (\%attrs);
1053             $res .=");";
1054             delete $attrs{'stroke-linejoin'}; ## BUG: due to TkZinc limitation: no -joinstyle for arc
1055             &attrs_implemented ( 'circle', $name, [qw ( cx cy r transform id )], %attrs );
1056             &display ($res,
1057             &transform($name, $attrs{transform}) );
1058             }
1059              
1060              
1061             sub polygon {
1062             my ($parser, $elementname, %attrs) = @_;
1063             %attrs = &expandAttributes ($elementname,%attrs);
1064             my ($name,$natural) = &name ($elementname, $attrs{id});
1065             my $res = "->add('curve',$current_group,[" . &points(\%attrs);
1066             $res .= "], -closed => 1, -tags => ['polygon'";
1067             $res .= ", $name" if ($natural or $attrs{transform});
1068             $res .= "], -priority => 10";
1069             # by default, polygones are filled
1070             # from svg specifs). The value is set here, and can be overidden later
1071             # in the &analyze_style
1072             $res .= ", -filled => 1" unless defined $attrs{fill} and $attrs{fill} eq 'none';
1073             $res .= &analyze_style (\%attrs);
1074             $res .= ");";
1075             &attrs_implemented ( 'polygone', $name, [qw ( points style transform id )], %attrs );
1076             &display ($res,
1077             &transform($name, $attrs{transform}) );
1078             }
1079              
1080              
1081             sub path {
1082             my ($parser, $elementname, %attrs) = @_;
1083             %attrs = &expandAttributes ($elementname,%attrs);
1084             # my @attrs=%attrs; print "PATH attr=@attrs\n";
1085             my ($name,$natural) = &name ($elementname, $attrs{id});
1086             my $res = "->add('curve',$current_group,[";
1087             my ($closed, @listOfListpoints) = &pathPoints(\%attrs);
1088             my $refPoints = shift @listOfListpoints;
1089             $res .= join (", ", @{$refPoints});
1090             $res .= "], -tags => [$name], -priority => 10";
1091             # by default, paths are filled (cf exemple other-samples/logo_intuilab_illustrator.svg)
1092             # The value is set here, and can be overidden later
1093             # in the &analyze_style
1094             $res .= ", -filled => 1" unless defined $attrs{fill} and $attrs{fill} eq 'none';
1095             if ( defined $attrs{'fill-rule'} ) {
1096             $res .= ", -fillrule => 'nonzero'" unless $attrs{'fill-rule'} eq 'evenodd';
1097             delete $attrs{'fill-rule'};
1098             }
1099             $res .= ", -closed => $closed";
1100             $res .= &analyze_style (\%attrs);
1101             $res .= ");";
1102             # and process other contours
1103             my @contours = ();
1104             foreach my $refPoints (@listOfListpoints) {
1105             my @points = @{$refPoints};
1106             # print "AN OTHER CONTOUR: @points\n";
1107             my $contour = "->contour($name, 'add', 0, [";
1108             $contour .= join (", ", @points);
1109             $contour .= "]);";
1110             push @contours, $contour;
1111             }
1112             &attrs_implemented ( 'path', $name, [qw ( d style stroke-linejoin stroke-linecap transform id)], %attrs );
1113             &display ($res, @contours,
1114             &transform($name, $attrs{transform}) );
1115             } # end of path
1116              
1117              
1118              
1119              
1120             sub expandAttributes {
1121             my ($elementName, %rawAttrs) = @_;
1122             my (%styleKeyValues, %classKeyValues, %elementKeyValues, %idKeyValues);
1123             my $style = delete $rawAttrs{'style'};
1124             if (defined $style) {
1125             %styleKeyValues = &expandStyle($style);
1126             }
1127             my $class = delete $rawAttrs{'class'};
1128             if (defined $class) { ## for the css
1129             my $ref_styles = &getClass($class);
1130             if (defined $ref_styles) {
1131             %classKeyValues = %{$ref_styles};
1132             } else {
1133             &myWarn ("class attribute refers an illegal style: '$class'\n");
1134             }
1135             }
1136             my $ref_styles = &getElementClass($elementName);
1137             if (defined $ref_styles) {
1138             %elementKeyValues = %{$ref_styles};
1139             }
1140             my $id = $rawAttrs{id};
1141             if (defined $id) {
1142             my $ref_style = &getIdClass($id);
1143             if (defined $ref_style) {
1144             %idKeyValues = %{$ref_styles};
1145             }
1146             }
1147             return (%rawAttrs, %elementKeyValues, %classKeyValues, %styleKeyValues, %idKeyValues); ## the order is important!
1148             }
1149              
1150             ### CM 19/1/03: This function could be really simplified (CM 09/09/3 why??? I do not remember!)
1151             ## analyze styles attached to an item (non text item) and on any of its groups
1152             sub analyze_style {
1153             my ($ref_attr) = @_;
1154             my %ref_attr = %{$ref_attr};
1155             my %attrs = ( %current_context , %ref_attr );
1156             # print "analyze_style:",join(" ",( %attrs )), "\n";
1157             my %directkeyvalues;
1158             foreach my $attr (@STYLE_ATTRS) {
1159             my $value = $attrs{$attr};
1160             if (defined $value) {
1161             # print ("$attr := ", $value, "\n");
1162             $directkeyvalues{$attr} = &removeComment($value);
1163             }
1164             }
1165             return &analyze_style_hash (\%directkeyvalues);
1166             } # end of analyze_style;
1167              
1168              
1169             ## analyze styles attached to a text item and on any of its groups
1170             sub analyze_text_style {
1171             my ($ref_attr) = @_;
1172             my %attrs = ( %current_context , %{$ref_attr} );
1173             my $res = "";
1174             my $style = delete $attrs{'style'};
1175             if (defined $style) {
1176             my %keyvalues = &expandStyle($style);
1177             $res = &analyze_text_style_hash (\%keyvalues);
1178             }
1179             my %directkeyvalues;
1180             foreach my $attr (@STYLE_ATTRS) {
1181             my $value = $attrs{$attr};
1182             if (defined $value) {
1183             # print ("Analyzetext_style: $attr := ", $value, "\n");
1184             $directkeyvalues{$attr} = &removeComment($value);
1185             }
1186             }
1187             $res .= &analyze_text_style_hash (\%directkeyvalues);
1188             return $res;
1189             } # end of analyze_text_style;
1190              
1191              
1192             ## expanses the attribute = "prop:val;prop2:val2" in a hashtable like {prop => val, prop2 => val2, ...}
1193             ## and return this hash (BUG: may be it should return a reference!)
1194             sub expandStyle {
1195             my ($style) = @_;
1196             return () unless defined $style;
1197             my %keyvalues;
1198             $style = &removeComment ($style);
1199             foreach my $keyvalue ( split ( /\s*;\s*/ , $style) ) {
1200             my ($key, $value) = $keyvalue =~ /(.*)\s*:\s*(.*)/ ;
1201             # print "Style: key = $key value=$value\n";
1202             $keyvalues{$key} = $value;
1203             }
1204             return %keyvalues;
1205             } # end of expandStyle
1206              
1207              
1208             ## Analyze attributes contained in the hashtable given as ref
1209             ## This hashtable {attribut =>value...} must contain all
1210             ## attributes to analyze
1211             ## returns a string containing the TkZinc attributes
1212             sub analyze_style_hash {
1213             my ($ref_keyvalues) = @_;
1214             my %keyvalues = %{$ref_keyvalues};
1215             my $res = "";
1216             my $opacity = &convertOpacity(delete $keyvalues{'opacity'});
1217              
1218             ## we are treating now the stroke color and its transparency
1219             my $stroke = delete $keyvalues{'stroke'};
1220             my $strokeOpacity = delete $keyvalues{'stroke-opacity'};
1221             $strokeOpacity = 1 if !defined $strokeOpacity;
1222             $strokeOpacity = &float2int( &convertOpacity($strokeOpacity)*$opacity*100);
1223             if (defined $stroke) {
1224             my $color = &colorConvert($stroke);
1225             # print "stroke=$stroke <=> '$color'\n";
1226             if ($color eq "none") {
1227             $res .= ", -linewidth => 0";
1228             delete $keyvalues{'stroke-width'};
1229             } elsif ( $strokeOpacity != 100 ) {
1230             if ( &existsGradient($color) ) {
1231             # so, apply a transparency to a Tk::Zinc named gradient
1232             my $newColor = &addTransparencyToGradient($color,$strokeOpacity);
1233             $res .= ", -linecolor => \"$newColor\", -filled => 1";
1234             } else {
1235             $res .= ", -linecolor => \"$color$colorSep$strokeOpacity\"";
1236             }
1237             } else {
1238             $res .= ", -linecolor => \"$color\"";
1239             }
1240             } elsif ( $strokeOpacity != 1 ) { # no stroke color, but opacity
1241             ## what should I do?!
1242             }
1243            
1244             ## we are treating now the fill color and its transparency
1245             my $fill = delete $keyvalues{'fill'};
1246             my $fillOpacity = delete $keyvalues{'fill-opacity'};
1247             $fillOpacity = 1 if !defined $fillOpacity;
1248             $fillOpacity = &float2int( &convertOpacity($fillOpacity)*$opacity*100);
1249             delete $keyvalues{'fill-opacity'};
1250             if (defined $fill) {
1251             my $color = &colorConvert($fill);
1252             if ($color eq "none") {
1253             $res .= ", -filled => 0";
1254             delete $keyvalues{'fill-opacity'};
1255             } elsif ( $fillOpacity != 100 ) {
1256             # print "fillOpacity=$fillOpacity\n";
1257             if ( &existsGradient($color) ) {
1258             # so, apply a transparency to a Tk::Zinc named gradient
1259             my $newColor = &addTransparencyToGradient($color,$fillOpacity);
1260             $res .= ", -fillcolor => \"$newColor\", -filled => 1";
1261             ## we must define the contour color, else it will be defaulted to black
1262             $res .= ", -linecolor => \"$newColor\"," unless defined $stroke;
1263             } else {
1264             $res .= ", -fillcolor => \"$color$colorSep$fillOpacity\", -filled => 1";
1265             ## we must define the contour color, else it will be defaulted to black
1266             $res .= ", -linecolor => \"$color$colorSep$fillOpacity\"," unless defined $stroke;
1267             }
1268             } else {
1269             $res .= ", -fillcolor => \"$color\", -filled =>1";
1270             ## we must define the contour color, else it will be defaulted to black
1271             $res .= ", -linecolor => \"$color\"" unless defined $stroke;
1272             }
1273             }
1274              
1275             # all other attributes now
1276             foreach my $key (sort keys %keyvalues) {
1277             my $value = $keyvalues{$key};
1278             next if (!defined $value);
1279             # print "KEY=$key VALUE=$value\n";
1280             if ($key eq 'stroke-width') {
1281             if ( defined $keyvalues{stroke} and $keyvalues{stroke} eq 'none' ) {
1282             delete $keyvalues{stroke};
1283             next;
1284             }
1285             $value = &sizeConvert($value);
1286             if ($value == 0 and $dtdVersion eq "1.0") {
1287             $value = 0.1; # BUG? a widht of 0 is the smallest possible width in SVG 1.0 [true or false?]
1288             }
1289             $res .= ", -linewidth => $value";
1290             } elsif ($key eq 'display') {
1291             if ($value eq 'none') {
1292             $res .= ", -visible => 0, -sensitive => 0";
1293             }
1294             ## We do not treat the other possible values for display as defined in CSS2?!
1295             } elsif ($key eq 'visibility') {
1296             ## BUG? if a "not-visible" group contains a visible graphic element
1297             ## this element WILL NOT be visible in TkZinc , but should be visible in SVG!!
1298             ## Cf specif svg p. 284
1299             if ($value eq 'hidden' or $value eq 'collapse') {
1300             $res .= ", -visible => 0";
1301             }
1302             ## We do not treat the other possible values for display as defined in CSS2?!
1303             } elsif ($key eq 'stroke-linecap') {
1304             if ($value eq 'butt' or $value eq 'round') {
1305             $res .= ", -capstyle => \"$value\"";
1306             } elsif ($value eq 'square') {
1307             $res .= ", -capstyle => \"projecting\"";
1308             } else {
1309             &myWarn ("!! bad value for $key style : $value\n");
1310             }
1311             } elsif ($key eq 'stroke-linejoin') {
1312             ($value) = $value =~ /(\w+)/ ; ## pour enlever d'eventuel blancs
1313             $res .= ", -joinstyle => \"$value\"";
1314             } elsif ($key eq 'fill-rule') {
1315             ### this attributes is for shape only and is analyzed in &path
1316             } elsif ($key eq 'font-size') {
1317             ### this attributes is for text only and is analyzed in &analyze_text_style_hash
1318             } else {
1319             &myWarn ("Unknown Style (in analyze_style_hash): $key (value is $value)\n") if $warn;
1320             }
1321             }
1322             return $res;
1323             } # end of analyze_style_hash
1324              
1325              
1326             ## We do not treat yet relative size of text e.g. : font-size = %120
1327             sub analyze_text_style_hash {
1328             my ($ref_keyvalues) = @_;
1329             my %keyvalues = %{$ref_keyvalues};
1330             # print "analyze_text_style_hash: ", %keyvalues,"\n";
1331             my $res = "";
1332             my $opacity = &convertOpacity($keyvalues{opacity});
1333             delete $keyvalues{'opacity'};
1334              
1335             my $fontFamily="";
1336             my $fontSize ="";
1337             my $fontWeight ="";
1338             foreach my $key (sort keys %keyvalues) {
1339             my $value = $keyvalues{$key};
1340             # print "$key ==>> $value\n";
1341             next if (!defined $value); # in this case, the SVG code is invalide (TBC)
1342             if ($key eq 'text-anchor') {
1343             if ($value eq 'start') {
1344             $res .= ", -alignment => 'left'";
1345             } elsif ($value eq 'end') {
1346             $res .= ", -alignment => 'right'";
1347             } elsif ($value eq 'middle') {
1348             $res .= ", -alignment => 'center'"}
1349             } elsif ($key eq 'display') {
1350             if ($value eq 'none') {
1351             $res .= ", -visible => 0, -sensitive => 0";
1352             }
1353             ## We do not treat the other possible values for display as defined in CSS2?!
1354             } elsif ($key eq 'visibility') {
1355             ## BUG? if a "not-visible" group contains a visible graphic element
1356             ## this element WILL NOT be visible in TkZinc , but should be visible in SVG!!
1357             ## Cf specif svg p. 284
1358             if ($value eq 'hidden' or $value eq 'collapse') {
1359             $res .= ", -visible => 0";
1360             }
1361             ## We do not treat the other possible values for display as defined in CSS2?!
1362             } elsif ($key eq 'font-family') {
1363             $value =~ s/\'//g; # on removing quotes around the fonte name
1364             $fontFamily = $value;
1365             # print "font-family ==>> $fontFamily\n";
1366             } elsif ($key eq 'font-size') {
1367             $fontSize = $value;
1368             } elsif ($key eq 'font-weight') {
1369             $fontWeight = $value;
1370             # print "font-weight ==>> $fontWeight\n";
1371             } elsif ($key eq 'fill') {
1372             my $fillOpacity;
1373             my $color = &colorConvert($value);
1374             if ($color eq 'none') {
1375             # $res .= ", -filled => 0"; # this is the default value in Tk::Zinc
1376             } elsif ( ($fillOpacity = $keyvalues{'fill-opacity'} or $opacity != 1) ) {
1377             $fillOpacity = &convertOpacity($fillOpacity) * $opacity;
1378             delete $keyvalues{'fill-opacity'};
1379             if ( &existsGradient($color) ) {
1380             # so, apply a transparency to a Tk::Zinc named gradient
1381             my $newColor = &addTransparencyToGradient($color,$fillOpacity);
1382             $res .= ", -color => \"$newColor\"";
1383             } else {
1384             $res .= ", -color => \"$color$colorSep$fillOpacity\"";
1385             }
1386             } else {
1387             $res .= ", -color => \"$color\"";
1388             }
1389             } else {
1390             &myWarn ("Unknown Style of text: $key (value is $value)\n") if $warn;
1391             }
1392             }
1393             if ($fontFamily or $fontSize or $fontWeight) {
1394             ## to be extended to all other fonts definition parameters
1395             ## NB: fontWeight is not used yet!
1396             my ($fontKey,$code) = &createNamedFont ($fontFamily, $fontSize, "");
1397             &display($code) if $code;
1398             $res .= ", -font => \"$fontKey\"";
1399             }
1400             return $res;
1401             } # end of analyze_text_style_hash
1402              
1403              
1404              
1405              
1406             ## print warnings for all used attributes unkonwn or not implemented
1407             sub attrs_implemented {
1408             my ($type, $name, $ref_attrs_implemented, %attrs) = @_;
1409             my %attrs_implemented;
1410             foreach my $attr (@{$ref_attrs_implemented}) {
1411             $attrs_implemented{$attr}=1;
1412             }
1413             my %expandStyle = &expandStyle ($attrs{style});
1414             my %attributes = ( %expandStyle, %attrs);
1415             foreach my $attr ( keys %attributes ) {
1416             # print "attr: $attr $attributes{$attr}\n";
1417             if (!&isAnExtensionAttr($attr) and
1418             !defined $STYLE_ATTRS{$attr} and
1419             !defined $attrs_implemented{$attr}) {
1420             if (defined $STYLE_ATTRS_NYI{$attr}) {
1421             ¬_implemented_attr($attr);
1422             } else {
1423             &myWarn ("!!! Unimplemented attribute '$attr' (='$attributes{$attr}') in '$type' $name\n");
1424             }
1425             }
1426             }
1427             } # end of attrs_implemented
1428              
1429             # These hashes contain the number of usage of not implemented attributes and
1430             # the lines on svg source files where a not implemented attributes is used
1431             # so that they can be displayed by the sub &print_warning_for_not_implemented_attr
1432             my %not_implemented_attr;
1433             my %not_implemented_attr_lines;
1434             sub not_implemented_attr {
1435             my ($attr) = @_;
1436             $not_implemented_attr{$attr}++;
1437             if (defined $not_implemented_attr_lines{$attr}) {
1438             push @{$not_implemented_attr_lines{$attr}},¤t_line;
1439             } else {
1440             $not_implemented_attr_lines{$attr} = [¤t_line];
1441             }
1442             }
1443              
1444             sub print_warning_for_not_implemented_attr {
1445             foreach my $k (sort keys %not_implemented_attr) {
1446             print "not implemented/implementable attribute '$k' was used $not_implemented_attr{$k} times in lines ";
1447             my @lines;
1448             if ($not_implemented_attr{$k} > 20) {
1449             @lines = @{$not_implemented_attr_lines{$k}}[0..19];
1450             print join (", ",@lines) ,"...\n";
1451             } else {
1452             @lines = @{$not_implemented_attr_lines{$k}};
1453             print join (", ",@lines) ,"...\n";
1454             }
1455             }
1456             }
1457              
1458              
1459             # print a warning for the first use of an attribute of a non-implemented extension to SVG
1460             # return :
1461             # - true if the attribute belong to an extension of SVG
1462             # - false if its supposed to be a standard SVG attribute (or a non-existing attribute)
1463             sub isAnExtensionAttr {
1464             my ($attr) = @_;
1465             if ( $attr =~ /^(.+):.+/ ) {
1466             my $prefix = $1;
1467             if (defined $notImplementedExtensionPrefix{$prefix} and
1468             $notImplementedExtensionPrefix{$prefix} == 0) {
1469             &myWarn ("!! XML EXTENSION '$prefix' IS NOT IMPLEMENTED\n");
1470             # we set the value to 1 so that the next time we will not prnt another message
1471             $notImplementedExtensionPrefix{$prefix} = 1;
1472             }
1473             return 1;
1474             } else {
1475             return 0;
1476             }
1477             } # end of isAnExtensionAttr
1478              
1479             {
1480             my $inMetadata=0;
1481             sub metadata {
1482             $inMetadata++;
1483             }
1484             sub _metadata {
1485             $inMetadata--;
1486             }
1487              
1488             sub inMetadata {
1489             return $inMetadata;
1490             }
1491             }
1492              
1493             sub notYetImplemented {
1494             my ($elementname) = @_;
1495             &myWarn ("####### $elementname: Not Yet Implemented\n");
1496             }
1497              
1498             {
1499             my $expat;
1500             sub Init {
1501             $expat = shift;
1502             }
1503             sub Final {
1504             undef $expat;
1505             }
1506              
1507             ## takes 1 arg : 'message'
1508             sub myWarn {
1509             my ($mess) = @_;
1510             if (defined $expat) {
1511             print STDOUT ("at ", $expat->current_line, ": $mess");
1512             } else {
1513             print STDOUT $mess;
1514             }
1515             }
1516              
1517             sub current_line {
1518             if (defined $expat) {
1519             return $expat->current_line;
1520             } else {
1521             return "_undef_";
1522             }
1523             }
1524             }
1525              
1526             sub display {
1527             my (@res) = @_;
1528             $backend->treatLines(@res);
1529             }
1530              
1531             sub findINC
1532             {
1533             my $file = join('/',@_);
1534             my $dir;
1535             $file =~ s,::,/,g;
1536             foreach $dir (@INC)
1537             {
1538             my $path;
1539             return $path if (-e ($path = "$dir/$file"));
1540             }
1541             return undef;
1542             }
1543              
1544              
1545             ###################################################################
1546             ### this a slightly different implementation of the subs style as defined in XML::Parser
1547             ### Differences are :
1548             # - when an error occure in a callback, the error is handled and a warning is
1549             # printed with the line number of the SVG source file
1550             # - namespace can be used (this is usefull for example to treat the SVG included in dia data files)
1551             #
1552              
1553             package XML::Parser::SVG2zinc;
1554             $XML::Parser::Built_In_Styles{'SVG2zinc'} = 1;
1555              
1556              
1557             sub Start {
1558             no strict 'refs';
1559             my $expat = shift;
1560             my $tag = shift;
1561             my $ns = $expat->namespace($tag);
1562             # print "tag=$tag , ns=",$ns||" ", "\n";
1563             if (!defined $ns || $ns =~ /\/svg$/) {
1564             ## the tag is a SVG tag
1565             ## BUG: we should also get some tags of XML standard used by
1566             ## the SVG standard. Exemple: xlink:href
1567             my $sub = $expat->{Pkg} . "::$tag";
1568             # print "Sub=$sub\n";
1569             if (defined &$sub) {
1570             eval { &$sub($expat, $tag, @_) };
1571             if ($@) {
1572             $expat->xpcarp("An Error occured while evaluationg $tag {...} :\n$@");
1573             }
1574             } elsif (&SVG::SVG2zinc::inMetadata) {
1575             # we do othing, unless tags were treated before!
1576             }
1577             else {
1578             ## skipping the tag if it is part of not implemented extension
1579             my ($extension) = $tag =~ /(\w+):.*/;
1580             return if defined $extension && defined $notImplementedExtensionPrefix{$extension};
1581             warn "## Unimplemented SVG tag: $tag\n";
1582             }
1583             }
1584             }
1585              
1586             sub End {
1587             no strict 'refs';
1588             my $expat = shift;
1589             my $tag = shift;
1590             my $ns = $expat->namespace($tag);
1591             if (!defined $ns || $ns =~ /\/svg$/) {
1592             my $sub = $expat->{Pkg} . "::${tag}_";
1593             ## the tag is a SVG tag
1594             if (defined &$sub) {
1595             eval { &$sub($expat, $tag) };
1596             if ($@) {
1597             $expat->xpcarp("An Error occured while evaluationg ${tag}_ {...}) :\n$@");
1598             }
1599             } else {
1600             # the following error message is not usefull, as there were already
1601             # an error message at the opening tag
1602             # warn "## Unimplemented SVG tag: ${tag}_\n";
1603             }
1604             }
1605             }
1606              
1607              
1608              
1609             ###################################################################
1610              
1611              
1612             1;
1613              
1614             __END__