1#  svg2can.tcl ---
2#
3#      This file provides translation from canvas commands to XML/SVG format.
4#
5#  Copyright (c) 2004-2007  Mats Bengtsson
6#
7#  This file is distributed under BSD style license.
8#
9# $Id: svg2can.tcl,v 1.42 2008-02-06 13:57:24 matben Exp $
10#
11# ########################### USAGE ############################################
12#
13#   NAME
14#      svg2can - translate XML/SVG to canvas command.
15#
16#   SYNOPSIS
17#      svg2can::parsesvgdocument xmllist
18#      svg2can::parseelement xmllist
19#
20#
21# ########################### CHANGES ##########################################
22#
23#   0.1      first release
24#   0.2      starting support for tkpath package
25#
26# ########################### TODO #############################################
27#
28#       A lot...
29#
30# ########################### INTERNALS ########################################
31#
32# The whole parse tree is stored as a hierarchy of lists as:
33#
34#       xmllist = {tag attrlist isempty cdata {child1 child2 ...}}
35
36# We need URN decoding for the file path in images. From my whiteboard code.
37
38package require uriencode
39
40package provide svg2can 1.0
41
42namespace eval svg2can {
43
44    variable confopts
45    array set confopts {
46	-foreignobjecthandler ""
47	-httphandler          ""
48	-imagehandler         ""
49	-imagehandlerex       ""
50    }
51
52    variable textAnchorMap
53    array set textAnchorMap {
54	start   w
55	middle  c
56	end     e
57    }
58
59    variable fontWeightMap
60    array set fontWeightMap {
61	normal    normal
62	bold      bold
63	bolder    bold
64	lighter   normal
65	100       normal
66	200       normal
67	300       normal
68	400       normal
69	500       normal
70	600       bold
71	700       bold
72	800       bold
73	900       bold
74    }
75
76    # We need to have a temporary tag for doing transformations.
77    variable tmptag _tmp_transform
78    variable pi 3.14159265359
79    variable degrees2Radians [expr {2*$pi/360.0}]
80    variable systemFont
81
82    switch -- $::tcl_platform(platform) {
83	unix {
84	    set systemFont {Helvetica 10}
85	    if {[package vcompare [info tclversion] 8.3] == 1} {
86		if {[string equal [tk windowingsystem] "aqua"]} {
87		    set systemFont system
88		}
89	    }
90	}
91	windows {
92	    set systemFont system
93	}
94    }
95
96    variable priv
97    set priv(havetkpath) 0
98    if {![catch {package require tkpath 0.2.8}]} {
99	set priv(havetkpath) 1
100    }
101
102    # We don't want it now.
103    set priv(havetkpath) 0
104
105    variable chache
106    variable cache_key ""
107}
108
109# svg2can::config --
110#
111#       Processes the configuration options.
112
113proc svg2can::config {args} {
114    variable confopts
115
116    set options [lsort [array names confopts -*]]
117    set usage [join $options ", "]
118    if {[llength $args] == 0} {
119	set result {}
120	foreach name $options {
121	    lappend result $name $confopts($name)
122	}
123	return $result
124    }
125    regsub -all -- - $options {} options
126    set pat ^-([join $options |])$
127    if {[llength $args] == 1} {
128	set flag [lindex $args 0]
129	if {[regexp -- $pat $flag]} {
130	    return $confopts($flag)
131	} else {
132	    return -code error "Unknown option $flag, must be: $usage"
133	}
134    } else {
135	foreach {flag value} $args {
136	    if {[regexp -- $pat $flag]} {
137		set confopts($flag) $value
138	    } else {
139		return -code error "Unknown option $flag, must be: $usage"
140	    }
141	}
142    }
143}
144
145# svg2can::cache_* --
146#
147#       A few routines to handle the caching of images and gradients.
148#       Useful for garbage collection. Cache stuff per key which is typically
149#       a widget path, and then do:
150#       svg2can::cache_set_key $w
151#       bind $w <Destroy> +[list svg2can::cache_free $w]
152#       This works only if parsing svg docs in one shot.
153
154proc svg2can::cache_set_key {key} {
155    variable cache_key
156    set cache_key $key
157}
158
159proc svg2can::cache_get_key {} {
160    variable cache_key
161    return $cache_key
162}
163
164proc svg2can::cache_get {$key} {
165    variable cache
166    if {[info exists cache($key)]} {
167	return $cache($key)
168    } else {
169	return [list]
170    }
171}
172
173proc svg2can::cache_add {type token} {
174    variable cache
175    variable cache_key
176    lappend cache($cache_key) [list $type $token]
177}
178
179proc svg2can::cache_free {key} {
180    variable cache
181
182    if {![info exists cache($key)]} {
183	return
184    }
185    foreach spec $cache($key) {
186	set type [lindex $spec 0]
187	set token [lindex $spec 1]
188	switch -- $type {
189	    image {
190		image delete $token
191	    }
192	    gradient {
193		::tkpath::gradient delete $token
194	    }
195	}
196    }
197    set cache($key) [list]
198}
199
200proc svg2can::cache_reset {key} {
201    variable cache
202    set cache($key) [list]
203}
204
205# svg2can::parsesvgdocument --
206#
207#
208# Arguments:
209#       xmllist     the parsed document as a xml list
210#       args        configuration options
211#          -httphandler
212#    	   -imagehandler
213#
214# Results:
215#       a list of canvas commands without the widgetPath
216
217proc svg2can::parsesvgdocument {xmllist args} {
218    variable confopts
219    variable priv
220
221    array set argsA [array get confopts]
222    array set argsA $args
223    set paropts [array get argsA]
224
225    set ans {}
226    foreach c [getchildren $xmllist] {
227	if {$priv(havetkpath)} {
228	    set ans [concat $ans [ParseElemRecursiveEx $c $paropts {}]]
229	} else {
230	    set ans [concat $ans [ParseElemRecursive $c $paropts {}]]
231	}
232    }
233    return $ans
234}
235
236# svg2can::parseelement --
237#
238#       External interface for parsing a single element.
239#
240# Arguments:
241#       xmllist     the elements xml list
242#       args        configuration options
243#          -httphandler
244#    	   -imagehandler
245#    	   -imagehandlerex
246#
247# Results:
248#       a list of canvas commands without the widgetPath
249
250proc svg2can::parseelement {xmllist args} {
251    variable confopts
252    variable priv
253
254    array set argsA [array get confopts]
255    array set argsA $args
256    set paropts [array get argsA]
257    if {$priv(havetkpath)} {
258	return [ParseElemRecursiveEx $xmllist $paropts {}]
259    } else {
260	return [ParseElemRecursive $xmllist $paropts {}]
261    }
262}
263
264# svg2can::ParseElemRecursive --
265#
266#       Parses element for internal usage.
267#
268# Arguments:
269#       xmllist     the elements xml list
270#       paropts     parse options
271#       transformL
272#       args        list of attributes from any enclosing element (g).
273#
274# Results:
275#       a list of canvas commands without the widgetPath
276
277proc svg2can::ParseElemRecursive {xmllist paropts transformL args} {
278
279    set cmdList [list]
280    set tag [gettag $xmllist]
281
282    # Handle any tranform attribute; may be recursive, so keep a list.
283    set transformL [concat $transformL [ParseTransformAttr [getattr $xmllist]]]
284
285    switch -- $tag {
286	circle - ellipse - image - line - polyline - polygon - rect - path - text {
287	    set func [string totitle $tag]
288	    set cmdL [eval {Parse${func} $xmllist $paropts $transformL} $args]
289	    set cmdList [concat $cmdList $cmdL]
290	}
291	a - g {
292
293	    # Need to collect the attributes for the g element since
294	    # the child elements inherit them. g elements may be nested!
295	    # Must parse any style to the actual attribute names.
296	    array set attrA $args
297	    array set attrA [getattr $xmllist]
298	    unset -nocomplain attrA(id)
299	    if {[info exists attrA(style)]} {
300		array set attrA [StyleAttrToList $attrA(style)]
301	    }
302	    foreach c [getchildren $xmllist] {
303		set cmdList [concat $cmdList [eval {
304		    ParseElemRecursive $c $paropts $transformL
305		} [array get attrA]]]
306	    }
307	}
308	foreignObject {
309	    array set parseArr $paropts
310	    if {[string length $parseArr(-foreignobjecthandler)]} {
311		set elem [uplevel #0 $parseArr(-foreignobjecthandler) \
312		  [list $xmllist $paropts $transformL] $args]
313		if {$elem != ""} {
314		    set cmdList [concat $cmdList $elem]
315		}
316	    }
317	}
318	use - defs - marker - symbol {
319	    # todo
320	}
321    }
322    return $cmdList
323}
324
325# svg2can::ParseElemRecursiveEx --
326#
327#       Same for tkpath...
328#
329# Arguments:
330#       transAttr   this is a list of transform attributes
331
332proc svg2can::ParseElemRecursiveEx {xmllist paropts transAttr args} {
333
334    set cmdList [list]
335    set tag [gettag $xmllist]
336
337    switch -- $tag {
338	circle - ellipse - image - line - polyline - polygon - rect - path - text {
339	    set func [string totitle $tag]
340	    set cmd [eval {Parse${func}Ex $xmllist $paropts $transAttr} $args]
341	    if {[llength $cmd]} {
342		lappend cmdList $cmd
343	    }
344	}
345	a - g {
346
347	    # Need to collect the attributes for the g element since
348	    # the child elements inherit them. g elements may be nested!
349	    # Must parse any style to the actual attribute names.
350	    array set attrA $args
351	    array set attrA [getattr $xmllist]
352	    unset -nocomplain attrA(id)
353	    if {[info exists attrA(style)]} {
354		array set attrA [StyleAttrToList $attrA(style)]
355	    }
356	    if {[info exists attrA(transform)]} {
357		eval {lappend transAttr} [TransformAttrToList $attrA(transform)]
358		unset attrA(transform)
359	    }
360	    foreach c [getchildren $xmllist] {
361		set cmdList [concat $cmdList [eval {
362		    ParseElemRecursiveEx $c $paropts $transAttr
363		} [array get attrA]]]
364	    }
365	}
366	linearGradient {
367	    CreateLinearGradient $xmllist
368	}
369	radialGradient {
370	    CreateRadialGradient $xmllist
371	}
372	foreignObject {
373	    array set parseArr $paropts
374	    if {[string length $parseArr(-foreignobjecthandler)]} {
375		set elem [uplevel #0 $parseArr(-foreignobjecthandler) \
376		  [list $xmllist $paropts $transformL] $args]
377		if {$elem != ""} {
378		    set cmdList [concat $cmdList $elem]
379		}
380	    }
381	}
382	defs {
383	    eval {ParseDefs $xmllist $paropts $transAttr} $args
384	}
385	use - marker - symbol {
386	    # todo
387	}
388    }
389    return $cmdList
390}
391
392proc svg2can::ParseDefs {xmllist paropts transAttr args} {
393
394    # @@@ Only gradients so far.
395
396    foreach c [getchildren $xmllist] {
397	set tag [gettag $c]
398
399	switch -- $tag {
400	    linearGradient {
401		CreateLinearGradient $c
402	    }
403	    radialGradient {
404		CreateRadialGradient $c
405	    }
406	}
407    }
408}
409
410# svg2can::ParseCircle, ParseEllipse, ParseLine, ParseRect, ParsePath,
411#   ParsePolyline, ParsePolygon, ParseImage --
412#
413#       Makes the necessary canvas commands needed to reproduce the
414#       svg element.
415#
416# Arguments:
417#       xmllist
418#       paropts     parse options
419#       transformL
420#       args        list of attributes from any enclosing element (g).
421#
422# Results:
423#       list of canvas create command without the widgetPath.
424
425proc svg2can::ParseCircle {xmllist paropts transformL args} {
426    variable tmptag
427
428    set opts {}
429    set presAttr {}
430    set cx 0
431    set cy 0
432    set r 0
433    array set attrA $args
434    array set attrA [getattr $xmllist]
435
436    # We need to have a temporary tag for doing transformations.
437    set tags {}
438    if {[llength $transformL]} {
439	lappend tags $tmptag
440    }
441
442    foreach {key value} [array get attrA] {
443	switch -- $key {
444	    cx - cy - r {
445		set $key [parseLength $value]
446	    }
447	    id {
448		set tags [concat $tags $value]
449	    }
450	    style {
451		set opts [StyleToOpts oval [StyleAttrToList $value]]
452	    }
453	    default {
454		# Valid itemoptions will be sorted out below.
455		lappend presAttr $key $value
456	    }
457	}
458    }
459    lappend opts -tags $tags
460    set coords [list [expr {$cx - $r}] [expr {$cy - $r}] \
461      [expr {$cx + $r}] [expr {$cy + $r}]]
462    set opts [MergePresentationAttr oval $opts $presAttr]
463    set cmdList [list [concat create oval $coords $opts]]
464
465    return [AddAnyTransformCmds $cmdList $transformL]
466}
467
468proc svg2can::ParseCircleEx {xmllist paropts transAttr args} {
469
470    set opts {}
471    set cx 0
472    set cy 0
473    set presAttr {}
474    array set attrA $args
475    array set attrA [getattr $xmllist]
476
477    foreach {key value} [array get attrA] {
478	switch -- $key {
479	    cx - cy {
480		set $key [parseLength $value]
481	    }
482	    id {
483		lappend opts -tags $value
484	    }
485	    style {
486		eval {lappend opts} [StyleToOptsEx [StyleAttrToList $value]]
487	    }
488	    transform {
489		eval {lappend transAttr} [TransformAttrToList $value]
490	    }
491	    default {
492		lappend presAttr $key $value
493	    }
494	}
495    }
496    if {[llength $transAttr]} {
497	lappend opts -matrix [TransformAttrListToMatrix $transAttr]
498    }
499    set opts [StrokeFillDefaults [MergePresentationAttrEx $opts $presAttr]]
500    return [concat create circle $cx $cy $opts]
501}
502
503proc svg2can::ParseEllipse {xmllist paropts transformL args} {
504    variable tmptag
505
506    set opts {}
507    set presAttr {}
508    set cx 0
509    set cy 0
510    set rx 0
511    set ry 0
512    array set attrA $args
513    array set attrA [getattr $xmllist]
514    set tags {}
515    if {[llength $transformL]} {
516	lappend tags $tmptag
517    }
518
519    foreach {key value} [array get attrA] {
520
521	switch -- $key {
522	    cx - cy - rx - ry {
523		set $key [parseLength $value]
524	    }
525	    id {
526		set tags [concat $tags $value]
527	    }
528	    style {
529		set opts [StyleToOpts oval [StyleAttrToList $value]]
530	    }
531	    default {
532		lappend presAttr $key $value
533	    }
534	}
535    }
536    lappend opts -tags $tags
537    set coords [list [expr {$cx - $rx}] [expr {$cy - $ry}] \
538      [expr {$cx + $rx}] [expr {$cy + $ry}]]
539    set opts [MergePresentationAttr oval $opts $presAttr]
540    set cmdList [list [concat create oval $coords $opts]]
541
542    return [AddAnyTransformCmds $cmdList $transformL]
543}
544
545proc svg2can::ParseEllipseEx {xmllist paropts transAttr args} {
546
547    set opts {}
548    set cx 0
549    set cy 0
550    set presAttr {}
551    array set attrA $args
552    array set attrA [getattr $xmllist]
553
554    foreach {key value} [array get attrA] {
555	switch -- $key {
556	    cx - cy {
557		set $key [parseLength $value]
558	    }
559	    id {
560		lappend opts -tags $value
561	    }
562	    style {
563		eval {lappend opts} [StyleToOptsEx [StyleAttrToList $value]]
564	    }
565	    transform {
566		eval {lappend transAttr} [TransformAttrToList $value]
567	    }
568	    default {
569		lappend presAttr $key $value
570	    }
571	}
572    }
573    if {[llength $transAttr]} {
574	lappend opts -matrix [TransformAttrListToMatrix $transAttr]
575    }
576    set opts [StrokeFillDefaults [MergePresentationAttrEx $opts $presAttr]]
577    return [concat create ellipse $cx $cy $opts]
578}
579
580proc svg2can::ParseImage {xmllist paropts transformL args} {
581    variable tmptag
582
583    set x 0
584    set y 0
585    set presAttr {}
586    set photo {}
587    array set attrA $args
588    array set attrA [getattr $xmllist]
589    array set paroptsA $paropts
590    set tags {}
591    if {[llength $transformL]} {
592	lappend tags $tmptag
593    }
594
595    foreach {key value} [array get attrA] {
596	switch -- $key {
597	    x - y - height - width {
598		# The canvas image item does not have width and height.
599		# These are REQUIRED in SVG.
600		set $key [parseLength $value]
601	    }
602	    id {
603		set tags [concat $tags $value]
604	    }
605	    style {
606		set opts [StyleToOpts image [StyleAttrToList $value]]
607	    }
608	    xlink:href {
609		set xlinkhref $value
610	    }
611	    default {
612		lappend presAttr $key $value
613	    }
614	}
615    }
616    lappend opts -tags $tags -anchor nw
617    set opts [MergePresentationAttr image $opts $presAttr]
618
619    if {[string length $paroptsA(-imagehandlerex)]} {
620	uplevel #0 $paroptsA(-imagehandlerex) [list $xmllist $opts]
621	return
622    }
623
624    # Handle the xlink:href attribute.
625    if {[info exists xlinkhref]} {
626
627	switch -glob -- $xlinkhref {
628	    file:/* {
629		set path [::uri::urn::unquote $xlinkhref]
630		set path [string map {file:/// /} $path]
631		if {[string length $paroptsA(-imagehandler)]} {
632		    set cmd [concat create image $x $y $opts]
633		    lappend cmd -file $path -height $height -width $width
634		    set photo [uplevel #0 $paroptsA(-imagehandler) [list $cmd]]
635		    lappend opts -image $photo
636		} else {
637		    if {[string tolower [file extension $path]] eq ".gif"} {
638			set photo [image create photo -file $path -format gif]
639			cache_add image $photo
640		    } else {
641			set photo [image create photo -file $path]
642			cache_add image $photo
643		    }
644		    lappend opts -image $photo
645		}
646	    }
647	    http:/* {
648		if {[string length $paroptsA(-httphandler)]} {
649		    set cmd [concat create image $x $y $opts]
650		    lappend cmd -url $xlinkhref  -height $height -width $width
651		    uplevel #0 $paroptsA(-httphandler) [list $cmd]
652		}
653		return
654	    }
655	    default {
656		return
657	    }
658	}
659    }
660    set cmd [concat create image $x $y $opts]
661    set cmdList [list $cmd]
662
663    return [AddAnyTransformCmds $cmdList $transformL]
664}
665
666proc svg2can::ParseImageEx {xmllist paropts transAttr args} {
667
668    set x 0
669    set y 0
670    set width  0
671    set height 0
672    set opts {}
673    set presAttr {}
674    array set attrA $args
675    array set attrA [getattr $xmllist]
676    array set paroptsA $paropts
677
678    foreach {key value} [array get attrA] {
679	switch -- $key {
680	    x - y {
681		set $key [parseLength $value]
682	    }
683	    height - width {
684		# A value of 0 disables rendering in SVG.
685		# tkpath uses 0 for using natural sizes.
686		if {$value == 0.0} {
687		    return
688		}
689		set $key [parseLength $value]
690	    }
691	    id {
692		lappend opts -tags $value
693	    }
694	    style {
695		eval {lappend opts} [StyleToOptsEx [StyleAttrToList $value]]
696	    }
697	    transform {
698		eval {lappend transAttr} [TransformAttrToList $value]
699	    }
700	    xlink:href {
701		set xlinkhref $value
702	    }
703	    default {
704		lappend presAttr $key $value
705	    }
706	}
707    }
708    lappend opts -width $width -height $height
709    if {[llength $transAttr]} {
710	lappend opts -matrix [TransformAttrListToMatrix $transAttr]
711    }
712    if {[string length $paroptsA(-imagehandlerex)]} {
713	uplevel #0 $paroptsA(-imagehandlerex) [list $xmllist $opts]
714	return
715    }
716
717    # Handle the xlink:href attribute.
718    if {[info exists xlinkhref]} {
719
720	switch -glob -- $xlinkhref {
721	    file:/* {
722		set path [::uri::urn::unquote $xlinkhref]
723		set path [string map {file:/// /} $path]
724		if {[string length $paroptsA(-imagehandler)]} {
725		    set cmd [concat create image $x $y $opts]
726		    lappend cmd -file $path -height $height -width $width
727		    set photo [uplevel #0 $paroptsA(-imagehandler) [list $cmd]]
728		    lappend opts -image $photo
729		} else {
730		    if {[string tolower [file extension $path]] eq ".gif"} {
731			set photo [image create photo -file $path -format gif]
732			cache_add image $photo
733		    } else {
734			set photo [image create photo -file $path]
735			cache_add image $photo
736		    }
737		    lappend opts -image $photo
738		}
739	    }
740	    http:/* {
741		if {[string length $paroptsA(-httphandler)]} {
742		    set cmd [concat create image $x $y $opts]
743		    lappend cmd -url $xlinkhref -height $height -width $width
744		    uplevel #0 $paroptsA(-httphandler) [list $cmd]
745		}
746		return
747	    }
748	    default {
749		return
750	    }
751	}
752    }
753
754    set opts [MergePresentationAttrEx $opts $presAttr]
755    return [concat create pimage $x $y $opts]
756}
757
758proc svg2can::ParseLine {xmllist paropts transformL args} {
759    variable tmptag
760
761    set opts {}
762    set coords {0 0 0 0}
763    set presAttr {}
764    array set attrA $args
765    array set attrA [getattr $xmllist]
766    set tags {}
767    if {[llength $transformL]} {
768	lappend tags $tmptag
769    }
770
771    foreach {key value} [array get attrA] {
772
773	switch -- $key {
774	    id {
775		set tags [concat $tags $value]
776	    }
777	    style {
778		set opts [StyleToOpts line [StyleAttrToList $value]]
779	    }
780	    x1 {
781		lset coords 0 [parseLength $value]
782	    }
783	    y1 {
784		lset coords 1 [parseLength $value]
785	    }
786	    x2 {
787		lset coords 2 [parseLength $value]
788	    }
789	    y2 {
790		lset coords 3 [parseLength $value]
791	    }
792	    default {
793		lappend presAttr $key $value
794	    }
795	}
796    }
797    lappend opts -tags $tags
798    set opts [MergePresentationAttr line $opts $presAttr]
799    set cmdList [list [concat create line $coords $opts]]
800
801    return [AddAnyTransformCmds $cmdList $transformL]
802}
803
804proc svg2can::ParseLineEx {xmllist paropts transAttr args} {
805
806    set x1 0
807    set y1 0
808    set x2 0
809    set y2 0
810    set opts {}
811    set presAttr {}
812    array set attrA $args
813    array set attrA [getattr $xmllist]
814
815    foreach {key value} [array get attrA] {
816	switch -- $key {
817	    x1 - y1 - x2 - y2 {
818		set $key [parseLength $value]
819	    }
820	    id {
821		lappend opts -tags $value
822	    }
823	    style {
824		eval {lappend opts} [StyleToOptsEx [StyleAttrToList $value]]
825	    }
826	    transform {
827		eval {lappend transAttr} [TransformAttrToList $value]
828	    }
829	    default {
830		lappend presAttr $key $value
831	    }
832	}
833    }
834    if {[llength $transAttr]} {
835	lappend opts -matrix [TransformAttrListToMatrix $transAttr]
836    }
837    set opts [StrokeFillDefaults [MergePresentationAttrEx $opts $presAttr] 1]
838    return [concat create pline $x1 $y1 $x2 $y2 $opts]
839}
840
841proc svg2can::ParsePath {xmllist paropts transformL args} {
842    variable tmptag
843
844    set cmdList {}
845    set opts {}
846    set presAttr {}
847    set path {}
848    set styleList {}
849    set lineopts {}
850    set polygonopts {}
851    array set attrA $args
852    array set attrA [getattr $xmllist]
853    set tags {}
854    if {[llength $transformL]} {
855	lappend tags $tmptag
856    }
857
858    foreach {key value} [array get attrA] {
859
860	switch -- $key {
861	    d {
862		set path $value
863	    }
864	    id {
865		set tags [concat $tags $value]
866	    }
867	    style {
868		# Need to parse separately for each canvas item since different
869		# default values.
870		set lineopts    [StyleToOpts line    [StyleAttrToList $value]]
871		set polygonopts [StyleToOpts polygon [StyleAttrToList $value]]
872	    }
873	    default {
874		lappend presAttr $key $value
875	    }
876	}
877    }
878
879    # The resulting canvas items are typically lines and polygons.
880    # Since the style parsing is different keep separate copies.
881    lappend lineopts    -tags $tags
882    lappend polygonopts -tags $tags
883    set lineopts    [MergePresentationAttr line    $lineopts    $presAttr]
884    set polygonopts [MergePresentationAttr polygon $polygonopts $presAttr]
885
886    # Parse the actual path data.
887    set co {}
888    set cantype line
889    set itemopts {}
890
891    regsub -all -- {([a-zA-Z])([0-9])} $path {\1 \2} path
892    regsub -all -- {([0-9])([a-zA-Z])} $path {\1 \2} path
893    set path [string map {- " -"} $path]
894    set path [string map {, " "} $path]
895
896    set i 0
897    set len  [llength $path]
898    set len1 [expr {$len - 1}]
899    set len2 [expr {$len - 2}]
900    set len4 [expr {$len - 4}]
901    set len6 [expr {$len - 6}]
902
903    # 'i' is the index into the path list; points to the command (character).
904
905    while {$i < $len} {
906	set elem [lindex $path $i]
907	set isabsolute 1
908	if {[string is lower $elem]} {
909	    set isabsolute 0
910	}
911
912	switch -glob -- $elem {
913	    A - a {
914		# Not part of Tiny SVG.
915		incr i
916		foreach {rx ry phi fa fs x y} [lrange $path $i [expr {$i + 6}]] break
917		if {!$isabsolute} {
918		    set x [expr {$cpx + $x}]
919		    set y [expr {$cpy + $y}]
920
921		}
922		set arcpars \
923		  [EllipticArcParameters $cpx $cpy $rx $ry $phi $fa $fs $x $y]
924
925		# Handle special cases.
926		switch -- $arcpars {
927		    skip {
928			# Empty
929		    }
930		    lineto {
931			lappend co [lindex $path [expr {$i + 5}]] \
932			  [lindex $path [expr {$i + 6}]]
933		    }
934		    default {
935
936			# Need to end any previous path.
937			if {[llength $co] > 2} {
938			    set opts [concat [set ${cantype}opts] $itemopts]
939			    lappend cmdList [concat create $cantype $co $opts]
940			}
941
942			# Cannot handle rotations.
943			foreach {cx cy rx ry theta delta phi} $arcpars break
944			set box [list [expr {$cx-$rx}] [expr {$cy-$ry}] \
945			  [expr {$cx+$rx}] [expr {$cy+$ry}]]
946			set itemopts [list -start $theta -extent $delta]
947
948			# Try to interpret any subsequent data as a
949			# -style chord | pieslice.
950			# Z: chord; float float Z: pieslice.
951			set ia [expr {$i + 7}]
952			set ib [expr {$i + 10}]
953
954			if {[regexp -nocase {z} [lrange $path $ia $ia]]} {
955			    lappend itemopts -style chord
956			    incr i 1
957			} elseif {[regexp -nocase {l +([-0-9\.]+) +([-0-9\.]+) +z} \
958			  [lrange $path $ia $ib] m mx my] &&  \
959			  [expr {hypot($mx-$cx, $my-$cy)}] < 4.0} {
960			    lappend itemopts -style pieslice
961			    incr i 4
962			} else {
963			    lappend itemopts -style arc
964			}
965			set opts [concat $polygonopts $itemopts]
966			lappend cmdList [concat create arc $box $opts]
967			set co {}
968			set itemopts {}
969		    }
970		}
971		incr i 6
972	    }
973	    C - c {
974		# We could have a sequence of pairs of points here...
975		# Approximate by quadratic bezier.
976		# There are three options here:
977		# C (p1 p2 p3) (p4 p5 p6)...           finalize item
978		# C (p1 p2 p3) S (p4 p5)...            let S trigger below
979		# C p1 p2 p3 anything else             finalize here
980		while {![regexp {[a-zA-Z]} [lindex $path [expr {$i+1}]]] && \
981		  ($i < $len6)} {
982		    set co [list $cpx $cpy]
983		    if {$isabsolute} {
984			lappend co [lindex $path [incr i]] [lindex $path [incr i]]
985			lappend co [lindex $path [incr i]] [lindex $path [incr i]]
986			lappend co [lindex $path [incr i]] [lindex $path [incr i]]
987			set cpx [lindex $co end-1]
988			set cpy [lindex $co end]
989		    } else {
990			PathAddRelative $path co i cpx cpy
991			PathAddRelative $path co i cpx cpy
992			PathAddRelative $path co i cpx cpy
993		    }
994
995		    # Do not finalize item if S instruction.
996		    if {![string equal -nocase [lindex $path [expr {$i+1}]] "S"]} {
997			lappend itemopts -smooth 1
998			set opts [concat $lineopts $itemopts]
999			lappend cmdList [concat create line $co $opts]
1000			set co {}
1001			set itemopts {}
1002		    }
1003		}
1004		incr i
1005	    }
1006	    H {
1007		while {![regexp {[a-zA-Z]} [lindex $path [expr {$i+1}]]] && \
1008		  ($i < $len1)} {
1009		    lappend co [lindex $path [incr i]] $cpy
1010		}
1011		incr i
1012	    }
1013	    h {
1014		while {![regexp {[a-zA-Z]} [lindex $path [expr {$i+1}]]] && \
1015		  ($i < $len1)} {
1016		    lappend co [expr {$cpx + [lindex $path [incr i]]}] $cpy
1017		}
1018		incr i
1019	    }
1020	    L - {[0-9]+} - {-[0-9]+} {
1021		while {![regexp {[a-zA-Z]} [lindex $path [expr {$i+1}]]] && \
1022		  ($i < $len2)} {
1023		    lappend co [lindex $path [incr i]] [lindex $path [incr i]]
1024		}
1025		incr i
1026	    }
1027	    l {
1028		while {![regexp {[a-zA-Z]} [lindex $path [expr {$i+1}]]] && \
1029		  ($i < $len2)} {
1030		    lappend co [expr {$cpx + [lindex $path [incr i]]}] \
1031		      [expr {$cpy + [lindex $path [incr i]]}]
1032		}
1033		incr i
1034	    }
1035	    M - m {
1036		# Make a fresh canvas item and finalize any previous command.
1037		if {[llength $co]} {
1038		    set opts [concat [set ${cantype}opts] $itemopts]
1039		    lappend cmdList [concat create $cantype $co $opts]
1040		}
1041		if {!$isabsolute && [info exists cpx]} {
1042		    set co [list  \
1043		      [expr {$cpx + [lindex $path [incr i]]}]
1044		      [expr {$cpy + [lindex $path [incr i]]}]]
1045		} else {
1046		    set co [list [lindex $path [incr i]] [lindex $path [incr i]]]
1047		}
1048		set itemopts {}
1049		incr i
1050	    }
1051	    Q - q {
1052		# There are three options here:
1053		# Q p1 p2 p3 p4...           finalize item
1054		# Q p1 p2 T p3...            let T trigger below
1055		# Q p1 p2 anything else      finalize here
1056
1057		# We may have a sequence of pairs of points following the Q.
1058		# Make a fresh item for each.
1059		while {![regexp {[a-zA-Z]} [lindex $path [expr {$i+1}]]] && \
1060		  ($i < $len4)} {
1061		    set co [list $cpx $cpy]
1062		    if {$isabsolute} {
1063			lappend co [lindex $path [incr i]] [lindex $path [incr i]]
1064			lappend co [lindex $path [incr i]] [lindex $path [incr i]]
1065			set cpx [lindex $co end-1]
1066			set cpy [lindex $co end]
1067		    } else {
1068			PathAddRelative $path co i cpx cpy
1069			PathAddRelative $path co i cpx cpy
1070		    }
1071
1072		    # Do not finalize item if T instruction.
1073		    if {![string equal -nocase [lindex $path [expr {$i+1}]] "T"]} {
1074			lappend itemopts -smooth 1
1075			set opts [concat $lineopts $itemopts]
1076			lappend cmdList [concat create line $co $opts]
1077			set co {}
1078			set itemopts {}
1079		    }
1080		}
1081		incr i
1082	    }
1083	    S - s {
1084		# Must annihilate last point added and use its mirror instead.
1085		while {![regexp {[a-zA-Z]} [lindex $path [expr {$i+1}]]] && \
1086		  ($i < $len4)} {
1087
1088		    # Control point from mirroring.
1089		    set ctrlpx [expr {2 * $cpx - [lindex $co end-3]}]
1090		    set ctrlpy [expr {2 * $cpy - [lindex $co end-2]}]
1091		    lset co end-1 $ctrlpx
1092		    lset co end $ctrlpy
1093		    if {$isabsolute} {
1094			lappend co [lindex $path [incr i]] [lindex $path [incr i]]
1095			lappend co [lindex $path [incr i]] [lindex $path [incr i]]
1096			set cpx [lindex $co end-1]
1097			set cpy [lindex $co end]
1098		    } else {
1099			PathAddRelative $path co i cpx cpy
1100			PathAddRelative $path co i cpx cpy
1101		    }
1102		}
1103
1104		# Finalize item.
1105		lappend itemopts -smooth 1
1106		set dx [expr {[lindex $co 0] - [lindex $co end-1]}]
1107		set dy [expr {[lindex $co 1] - [lindex $co end]}]
1108
1109		# Check endpoints to see if closed polygon.
1110		# Remove first AND end points if closed!
1111		if {[expr {hypot($dx, $dy)}] < 0.5} {
1112		    set opts [concat $polygonopts $itemopts]
1113		    set co [lrange $co 2 end-2]
1114		    lappend cmdList [concat create polygon $co $opts]
1115		} else {
1116		    set opts [concat $lineopts $itemopts]
1117		    lappend cmdList [concat create line $co $opts]
1118		}
1119		set co {}
1120		set itemopts {}
1121		incr i
1122	    }
1123	    T - t {
1124		# Must annihilate last point added and use its mirror instead.
1125		while {![regexp {[a-zA-Z]} [lindex $path [expr {$i+1}]]] && \
1126		  ($i < $len2)} {
1127
1128		    # Control point from mirroring.
1129		    set ctrlpx [expr {2 * $cpx - [lindex $co end-3]}]
1130		    set ctrlpy [expr {2 * $cpy - [lindex $co end-2]}]
1131		    lset co end-1 $ctrlpx
1132		    lset co end $ctrlpy
1133		    if {$isabsolute} {
1134			lappend co [lindex $path [incr i]] [lindex $path [incr i]]
1135			set cpx [lindex $co end-1]
1136			set cpy [lindex $co end]
1137		    } else {
1138			PathAddRelative $path co i cpx cpy
1139		    }
1140		}
1141
1142		# Finalize item.
1143		lappend itemopts -smooth 1
1144		set dx [expr {[lindex $co 0] - [lindex $co end-1]}]
1145		set dy [expr {[lindex $co 1] - [lindex $co end]}]
1146
1147		# Check endpoints to see if closed polygon.
1148		# Remove first AND end points if closed!
1149		if {[expr {hypot($dx, $dy)}] < 0.5} {
1150		    set opts [concat $polygonopts $itemopts]
1151		    set co [lrange $co 2 end-2]
1152		    lappend cmdList [concat create polygon $co $opts]
1153		} else {
1154		    set opts [concat $lineopts $itemopts]
1155		    lappend cmdList [concat create line $co $opts]
1156		}
1157		set co {}
1158		set itemopts {}
1159		incr i
1160	    }
1161	    V {
1162		while {![regexp {[a-zA-Z]} [lindex $path [expr {$i+1}]]] && \
1163		  ($i < $len1)} {
1164		    lappend co $cpx [lindex $path [incr i]]
1165		}
1166		incr i
1167	    }
1168	    v {
1169		while {![regexp {[a-zA-Z]} [lindex $path [expr {$i+1}]]] && \
1170		  ($i < $len1)} {
1171		    lappend co $cpx [expr {$cpy + [lindex $path [incr i]]}]
1172		}
1173		incr i
1174	    }
1175	    Z - z {
1176		if {[llength $co]} {
1177		    set opts [concat $polygonopts $itemopts]
1178		    lappend cmdList [concat create polygon $co $opts]
1179		}
1180		set cantype line
1181		set itemopts {}
1182		incr i
1183		set co {}
1184	    }
1185	    default {
1186		# ?
1187		incr i
1188	    }
1189	}   ;# End switch.
1190
1191	# Keep track of the pens current point.
1192	if {[llength $co]} {
1193	    set cpx [lindex $co end-1]
1194	    set cpy [lindex $co end]
1195	}
1196    }   ;# End while loop.
1197
1198    # Finalize the last element if any.
1199    if {[llength $co]} {
1200	set opts [concat [set ${cantype}opts] $itemopts]
1201	lappend cmdList [concat create $cantype $co $opts]
1202    }
1203    return [AddAnyTransformCmds $cmdList $transformL]
1204}
1205
1206proc svg2can::ParsePathEx {xmllist paropts transAttr args} {
1207
1208    set opts {}
1209    set presAttr {}
1210    set path {}
1211    array set attrA $args
1212    array set attrA [getattr $xmllist]
1213
1214    foreach {key value} [array get attrA] {
1215	switch -- $key {
1216	    d {
1217		set path [parsePathAttr $value]
1218	    }
1219	    id {
1220		lappend opts -tags $value
1221	    }
1222	    style {
1223		eval {lappend opts} [StyleToOptsEx [StyleAttrToList $value]]
1224	    }
1225	    transform {
1226		eval {lappend transAttr} [TransformAttrToList $value]
1227	    }
1228	    default {
1229		lappend presAttr $key $value
1230	    }
1231	}
1232    }
1233    if {[llength $transAttr]} {
1234	lappend opts -matrix [TransformAttrListToMatrix $transAttr]
1235    }
1236    set opts [StrokeFillDefaults [MergePresentationAttrEx $opts $presAttr]]
1237    return [concat create path [list $path] $opts]
1238}
1239
1240# Handle different defaults for fill and stroke.
1241
1242proc svg2can::StrokeFillDefaults {opts {noFill 0}} {
1243
1244    array set optsA $opts
1245    if {!$noFill && ![info exists optsA(-fill)]} {
1246	set optsA(-fill) black
1247    }
1248    if {[info exists optsA(-fillgradient)]} {
1249	unset -nocomplain optsA(-fill)
1250    }
1251    if {![info exists optsA(-stroke)]} {
1252	set optsA(-stroke) {}
1253    }
1254    return [array get optsA]
1255}
1256
1257proc svg2can::ParsePolyline {xmllist paropts transformL args} {
1258    variable tmptag
1259
1260    set coords {}
1261    set opts {}
1262    set presAttr {}
1263    array set attrA $args
1264    array set attrA [getattr $xmllist]
1265    set tags {}
1266    if {[llength $transformL]} {
1267	lappend tags $tmptag
1268    }
1269
1270    foreach {key value} [array get attrA] {
1271
1272	switch -- $key {
1273	    points {
1274		set coords [PointsToList $value]
1275	    }
1276	    id {
1277		set tags [concat $tags $value]
1278	    }
1279	    style {
1280		set opts [StyleToOpts line [StyleAttrToList $value]]
1281	    }
1282	    default {
1283		lappend presAttr $key $value
1284	    }
1285	}
1286    }
1287    lappend opts -tags $tags
1288    set opts [MergePresentationAttr line $opts $presAttr]
1289    set cmdList [list [concat create line $coords $opts]]
1290
1291    return [AddAnyTransformCmds $cmdList $transformL]
1292}
1293
1294proc svg2can::ParsePolylineEx {xmllist paropts transAttr args} {
1295
1296    set opts {}
1297    set points {0 0}
1298    set presAttr {}
1299    array set attrA $args
1300    array set attrA [getattr $xmllist]
1301
1302    foreach {key value} [array get attrA] {
1303	switch -- $key {
1304	    points {
1305		set points [PointsToList $value]
1306	    }
1307	    id {
1308		lappend opts -tags $value
1309	    }
1310	    style {
1311		eval {lappend opts} [StyleToOptsEx [StyleAttrToList $value]]
1312	    }
1313	    transform {
1314		eval {lappend transAttr} [TransformAttrToList $value]
1315	    }
1316	    default {
1317		lappend presAttr $key $value
1318	    }
1319	}
1320    }
1321    if {[llength $transAttr]} {
1322	lappend opts -matrix [TransformAttrListToMatrix $transAttr]
1323    }
1324    set opts [StrokeFillDefaults [MergePresentationAttrEx $opts $presAttr]]
1325    return [concat create polyline $points $opts]
1326}
1327
1328proc svg2can::ParsePolygon {xmllist paropts transformL args} {
1329    variable tmptag
1330
1331    set coords {}
1332    set opts {}
1333    set presAttr {}
1334    array set attrA $args
1335    array set attrA [getattr $xmllist]
1336    set tags {}
1337    if {[llength $transformL]} {
1338	lappend tags $tmptag
1339    }
1340
1341    foreach {key value} [array get attrA] {
1342
1343	switch -- $key {
1344	    points {
1345		set coords [PointsToList $value]
1346	    }
1347	    id {
1348		set tags [concat $tags $value]
1349	    }
1350	    style {
1351		set opts [StyleToOpts polygon [StyleAttrToList $value]]
1352	    }
1353	    default {
1354		lappend presAttr $key $value
1355	    }
1356	}
1357    }
1358    lappend opts -tags $tags
1359    set opts [MergePresentationAttr polygon $opts $presAttr]
1360    set cmdList [list [concat create polygon $coords $opts]]
1361
1362    return [AddAnyTransformCmds $cmdList $transformL]
1363}
1364
1365proc svg2can::ParsePolygonEx {xmllist paropts transAttr args} {
1366
1367    set opts {}
1368    set points {0 0}
1369    set presAttr {}
1370    array set attrA $args
1371    array set attrA [getattr $xmllist]
1372
1373    foreach {key value} [array get attrA] {
1374	switch -- $key {
1375	    points {
1376		set points [PointsToList $value]
1377	    }
1378	    id {
1379		lappend opts -tags $value
1380	    }
1381	    style {
1382		eval {lappend opts} [StyleToOptsEx [StyleAttrToList $value]]
1383	    }
1384	    transform {
1385		eval {lappend transAttr} [TransformAttrToList $value]
1386	    }
1387	    default {
1388		lappend presAttr $key $value
1389	    }
1390	}
1391    }
1392    if {[llength $transAttr]} {
1393	lappend opts -matrix [TransformAttrListToMatrix $transAttr]
1394    }
1395    set opts [StrokeFillDefaults [MergePresentationAttrEx $opts $presAttr]]
1396    return [concat create ppolygon $points $opts]
1397}
1398
1399proc svg2can::ParseRect {xmllist paropts transformL args} {
1400    variable tmptag
1401
1402    set opts {}
1403    set coords {0 0 0 0}
1404    set presAttr {}
1405    array set attrA $args
1406    array set attrA [getattr $xmllist]
1407    set tags {}
1408    if {[llength $transformL]} {
1409	lappend tags $tmptag
1410    }
1411
1412    foreach {key value} [array get attrA] {
1413
1414	switch -- $key {
1415	    id {
1416		set tags [concat $tags $value]
1417	    }
1418	    rx - ry {
1419		# unsupported :-(
1420	    }
1421	    style {
1422		set opts [StyleToOpts rectangle [StyleAttrToList $value]]
1423	    }
1424	    x - y - width - height {
1425		set $key [parseLength $value]
1426	    }
1427	    default {
1428		lappend presAttr $key $value
1429	    }
1430	}
1431    }
1432    if {[info exists x]} {
1433	lset coords 0 $x
1434    }
1435    if {[info exists y]} {
1436	lset coords 1 $y
1437    }
1438    if {[info exists width]} {
1439	lset coords 2 [expr {[lindex $coords 0] + $width}]
1440    }
1441    if {[info exists height]} {
1442	lset coords 3 [expr {[lindex $coords 1] + $height}]
1443    }
1444    lappend opts -tags $tags
1445    set opts [MergePresentationAttr rectangle $opts $presAttr]
1446    set cmdList [list [concat create rectangle $coords $opts]]
1447
1448    return [AddAnyTransformCmds $cmdList $transformL]
1449}
1450
1451proc svg2can::ParseRectEx {xmllist paropts transAttr args} {
1452
1453    set opts {}
1454    set x 0
1455    set y 0
1456    set width  0
1457    set height 0
1458    set presAttr {}
1459    array set attrA $args
1460    array set attrA [getattr $xmllist]
1461
1462    foreach {key value} [array get attrA] {
1463	switch -- $key {
1464	    x - y - width - height {
1465		set $key [parseLength $value]
1466	    }
1467	    id {
1468		lappend opts -tags $value
1469	    }
1470	    style {
1471		eval {lappend opts} [StyleToOptsEx [StyleAttrToList $value]]
1472	    }
1473	    transform {
1474		eval {lappend transAttr} [TransformAttrToList $value]
1475	    }
1476	    default {
1477		lappend presAttr $key $value
1478	    }
1479	}
1480    }
1481    if {[llength $transAttr]} {
1482	lappend opts -matrix [TransformAttrListToMatrix $transAttr]
1483    }
1484    set x2 [expr {$x + $width}]
1485    set y2 [expr {$y + $height}]
1486    set opts [StrokeFillDefaults [MergePresentationAttrEx $opts $presAttr]]
1487    return [concat create prect $x $y $x2 $y2 $opts]
1488}
1489
1490# svg2can::ParseText --
1491#
1492#       Takes a text element and returns a list of canvas create text commands.
1493#       Assuming that chdata is not mixed with elements, we should now have
1494#       either chdata OR more elements (tspan).
1495
1496proc svg2can::ParseText {xmllist paropts transformL args} {
1497    set x 0
1498    set y 0
1499    set xAttr 0
1500    set yAttr 0
1501    set cmdList [ParseTspan $xmllist $transformL x y xAttr yAttr {}]
1502    return $cmdList
1503}
1504
1505proc svg2can::ParseTextEx {xmllist paropts transAttr args} {
1506    return [eval {ParseText $xmllist $paropts {}} $args]
1507}
1508
1509# svg2can::ParseTspan --
1510#
1511#       Takes a tspan or text element and returns a list of canvas
1512#       create text commands.
1513
1514proc svg2can::ParseTspan {xmllist transformL xVar yVar xAttrVar yAttrVar opts} {
1515    variable tmptag
1516    variable systemFont
1517    upvar $xVar x
1518    upvar $yVar y
1519    upvar $xAttrVar xAttr
1520    upvar $yAttrVar yAttr
1521
1522    # Nested tspan elements do not inherit x, y, dx, or dy attributes set.
1523    # Sibling tspan elements do inherit x, y attributes.
1524    # Keep two separate sets of x and y; (x,y) and (xAttr,yAttr):
1525    # (x,y)
1526
1527    # Inherit opts.
1528    array set optsA $opts
1529    array set optsA [ParseTextAttr $xmllist xAttr yAttr baselineShift]
1530
1531    set tag [gettag $xmllist]
1532    set childList [getchildren $xmllist]
1533    set cmdList {}
1534    if {[string equal $tag "text"]} {
1535	set x $xAttr
1536	set y $yAttr
1537    }
1538
1539    if {[llength $childList]} {
1540
1541	# Nested tspan elements do not inherit x, y set via attributes.
1542	if {[string equal $tag "tspan"]} {
1543	    set xAttr $x
1544	    set yAttr $y
1545	}
1546	set opts [array get optsA]
1547	foreach c $childList {
1548
1549	    switch -- [gettag $c] {
1550		tspan {
1551		    set cmdList [concat $cmdList \
1552		      [ParseTspan $c $transformL x y xAttr yAttr $opts]]
1553		}
1554		default {
1555		    # empty
1556		}
1557	    }
1558	}
1559    } else {
1560	set str [getcdata $xmllist]
1561	set optsA(-text) $str
1562	if {[llength $transformL]} {
1563	    lappend optsA(-tags) $tmptag
1564	}
1565	set opts [array get optsA]
1566	set theFont $systemFont
1567	if {[info exists optsA(-font)]} {
1568	    set theFont $optsA(-font)
1569	}
1570
1571	# Need to adjust the text position so that the baseline matches y.
1572	# nw to baseline
1573	set ascent [font metrics $theFont -ascent]
1574	set cmdList [list [concat create text  \
1575	  $xAttr [expr {$yAttr - $ascent + $baselineShift}] $opts]]
1576	set cmdList [AddAnyTransformCmds $cmdList $transformL]
1577
1578	# Each text insert moves both the running coordinate sets.
1579	# newlines???
1580	set deltax [font measure $theFont $str]
1581	set x     [expr {$x + $deltax}]
1582	set xAttr [expr {$xAttr + $deltax}]
1583    }
1584    return $cmdList
1585}
1586
1587# svg2can::ParseTextAttr --
1588#
1589#       Parses the attributes in xmllist and returns the translated canvas
1590#       option list.
1591
1592proc svg2can::ParseTextAttr {xmllist xVar yVar baselineShiftVar} {
1593    variable systemFont
1594    upvar $xVar x
1595    upvar $yVar y
1596    upvar $baselineShiftVar baselineShift
1597
1598    # svg defaults to start with y being the baseline while tk default is c.
1599    #set opts {-anchor sw}
1600    # Anchor nw is simplest when newlines.
1601    set opts {-anchor nw}
1602    set presAttr {}
1603    set baselineShift 0
1604
1605    foreach {key value} [getattr $xmllist] {
1606
1607	switch -- $key {
1608	    baseline-shift {
1609		set baselineShiftSet $value
1610	    }
1611	    dx {
1612		set x [expr {$x + $value}]
1613	    }
1614	    dy {
1615		set y [expr {$y + $value}]
1616	    }
1617	    id {
1618		lappend opts -tags $value
1619	    }
1620	    style {
1621		set opts [concat $opts \
1622		  [StyleToOpts text [StyleAttrToList $value]]]
1623	    }
1624	    x - y {
1625		set $key $value
1626	    }
1627	    default {
1628		lappend presAttr $key $value
1629	    }
1630	}
1631    }
1632    array set optsA $opts
1633    set theFont $systemFont
1634    if {[info exists optsA(-font)]} {
1635	set theFont $optsA(-font)
1636    }
1637    if {[info exists baselineShiftSet]} {
1638	set baselineShift [BaselineShiftToDy $baselineShiftSet $theFont]
1639    }
1640    return [MergePresentationAttr text $opts $presAttr]
1641}
1642
1643# svg2can::AttrToCoords --
1644#
1645#       Returns coords from SVG attributes.
1646#
1647# Arguments:
1648#       type        SVG type
1649#       attr        list of geometry attributes
1650#
1651# Results:
1652#       list of coordinates
1653
1654proc svg2can::AttrToCoords {type attrlist} {
1655
1656    # Defaults.
1657    array set attr {
1658	cx      0
1659	cy      0
1660	height  0
1661	r       0
1662	rx      0
1663	ry      0
1664	width   0
1665	x       0
1666	x1      0
1667	x2      0
1668	y       0
1669	y1      0
1670	y2      0
1671    }
1672    array set attr $attrlist
1673
1674    switch -- $type {
1675	circle {
1676	    set coords [list  \
1677	      [expr {$attr(cx) - $attr(r)}] [expr {$attr(cy) - $attr(r)}] \
1678	      [expr {$attr(cx) + $attr(r)}] [expr {$attr(cy) + $attr(r)}]]
1679	}
1680	ellipse {
1681	    set coords [list  \
1682	      [expr {$attr(cx) - $attr(rx)}] [expr {$attr(cy) - $attr(ry)}] \
1683	      [expr {$attr(cx) + $attr(rx)}] [expr {$attr(cy) + $attr(ry)}]]
1684	}
1685	image {
1686	    set coords [list $attr(x) $attr(y)]
1687	}
1688	line {
1689	    set coords [list $attr(x1) $attr(y1) $attr(x2) $attr(y2)]
1690	}
1691	path {
1692	    # empty
1693	}
1694	polygon {
1695	    set coords [PointsToList $attr(points)]
1696	}
1697	polyline {
1698	    set coords [PointsToList $attr(points)]
1699	}
1700	rect {
1701	    set coords [list $attr(x) $attr(y) \
1702	      [expr {$attr(x) + $attr(width)}] [expr {$attr(y) + $attr(height)}]]
1703	}
1704	text {
1705	    set coords [list $attr(x) $attr(y)]
1706	}
1707    }
1708    return $coords
1709}
1710
1711# @@@ There is a lot TODO here!
1712
1713proc svg2can::CreateLinearGradient {xmllist} {
1714    variable gradientIDToToken
1715
1716    set x1 0
1717    set y1 0
1718    set x2 1
1719    set y2 0
1720    set method pad
1721    set units bbox
1722    set stops {}
1723
1724    # We first need to find out if any xlink:href attribute since:
1725    # Any 'linearGradient' attributes which are defined on the
1726    # referenced element which are not defined on this element are
1727    # inherited by this element.
1728    set attr [getattr $xmllist]
1729    set idx [lsearch -exact $attr xlink:href]
1730    if {$idx >= 0 && [expr {$idx % 2 == 0}]} {
1731	set value [lindex $attr [incr idx]]
1732	if {![string match {\#*} $value]} {
1733	    return -code error "unrecognized gradient uri \"$value\""
1734	}
1735	set uri [string range $value 1 end]
1736	if {![info exists gradientIDToToken($uri)]} {
1737	    return -code error "unrecognized gradient uri \"$value\""
1738	}
1739	set hreftoken $gradientIDToToken($uri)
1740	set units  [::tkpath::gradient cget $hreftoken -units]
1741	set method [::tkpath::gradient cget $hreftoken -method]
1742	set hrefstops [::tkpath::gradient cget $hreftoken -stops]
1743	foreach {x1 y1 x2 y2} \
1744	  [::tkpath::gradient cget $hreftoken -lineartransition] { break }
1745    }
1746
1747    foreach {key value} $attr {
1748	switch -- $key {
1749	    x1 - y1 - x2 - y2 {
1750		set $key [parseUnaryOrPercentage $value]
1751	    }
1752	    id {
1753		set id $value
1754	    }
1755	    gradientUnits {
1756		set units [string map \
1757		  {objectBoundingBox bbox userSpaceOnUse userspace} $value]
1758	    }
1759	    spreadMethod {
1760		set method $value
1761	    }
1762	}
1763    }
1764    if {![info exists id]} {
1765	return
1766    }
1767
1768    # If this element has no defined gradient stops, and the referenced element
1769    # does, then this element inherits the gradient stop from the referenced
1770    # element.
1771    set stops [ParseGradientStops $xmllist]
1772    if {$stops eq {}} {
1773	if {[info exists hrefstops]} {
1774	    set stops $hrefstops
1775	}
1776    }
1777    set token [::tkpath::gradient create linear -method $method -units $units \
1778      -lineartransition [list $x1 $y1 $x2 $y2] -stops $stops]
1779    set gradientIDToToken($id) $token
1780    cache_add gradient $token
1781}
1782
1783proc svg2can::CreateRadialGradient {xmllist} {
1784    variable gradientIDToToken
1785
1786    set cx 0.5
1787    set cy 0.5
1788    set r  0.5
1789    set fx 0.5
1790    set fy 0.5
1791    set method pad
1792    set units bbox
1793    set stops {}
1794
1795    # We first need to find out if any xlink:href attribute since:
1796    # Any 'linearGradient' attributes which are defined on the
1797    # referenced element which are not defined on this element are
1798    # inherited by this element.
1799    set attr [getattr $xmllist]
1800    set idx [lsearch -exact $attr xlink:href]
1801    if {$idx >= 0 && [expr {$idx % 2 == 0}]} {
1802	set value [lindex $attr [incr idx]]
1803	if {![string match {\#*} $value]} {
1804	    return -code error "unrecognized gradient uri \"$value\""
1805	}
1806	set uri [string range $value 1 end]
1807	if {![info exists gradientIDToToken($uri)]} {
1808	    return -code error "unrecognized gradient uri \"$value\""
1809	}
1810	set hreftoken $gradientIDToToken($uri)
1811	set units  [::tkpath::gradient cget $hreftoken -units]
1812	set method [::tkpath::gradient cget $hreftoken -method]
1813	set hrefstops [::tkpath::gradient cget $hreftoken -stops]
1814	set transL [::tkpath::gradient cget $hreftoken -radialtransition]
1815	set cx [lindex $transL 0]
1816	set cy [lindex $transL 1]
1817	if {[llength $transL] > 2} {
1818	    set r [lindex $transL 2]
1819	    if {[llength $transL] == 5} {
1820		set fx [lindex $transL 3]
1821		set fy [lindex $transL 4]
1822	    }
1823	}
1824    }
1825
1826    foreach {key value} [getattr $xmllist] {
1827	switch -- $key {
1828	    cx - cy - r - fx - fy {
1829		set $key [parseUnaryOrPercentage $value]
1830	    }
1831	    id {
1832		set id $value
1833	    }
1834	    gradientUnits {
1835		set units [string map \
1836		  {objectBoundingBox bbox userSpaceOnUse userspace} $value]
1837	    }
1838	    spreadMethod {
1839		set method $value
1840	    }
1841	}
1842    }
1843    if {![info exists id]} {
1844	return
1845    }
1846    # If this element has no defined gradient stops, and the referenced element
1847    # does, then this element inherits the gradient stop from the referenced
1848    # element.
1849    set stops [ParseGradientStops $xmllist]
1850    if {$stops eq {}} {
1851	if {[info exists hrefstops]} {
1852	    set stops $hrefstops
1853	}
1854    }
1855    set token [::tkpath::gradient create radial -method $method -units $units \
1856      -radialtransition [list $cx $cy $r $fx $fy] -stops $stops]
1857    set gradientIDToToken($id) $token
1858    cache_add gradient $token
1859}
1860
1861proc svg2can::ParseGradientStops {xmllist} {
1862
1863    set stops {}
1864
1865    foreach stopE [getchildren $xmllist] {
1866	if {[gettag $stopE] eq "stop"} {
1867	    set opts {}
1868	    set offset 0
1869	    set color black
1870	    set opacity 1
1871
1872	    foreach {key value} [getattr $stopE] {
1873		switch -- $key {
1874		    offset {
1875			set offset [parseUnaryOrPercentage $value]
1876		    }
1877		    stop-color {
1878			set color [parseColor $value]
1879		    }
1880		    stop-opacity {
1881			set opacity $value
1882		    }
1883		    style {
1884			set opts [StopsStyleToStopSpec [StyleAttrToList $value]]
1885		    }
1886		}
1887	    }
1888
1889	    # Style takes precedence.
1890	    array set stopA [list color $color opacity $opacity]
1891	    array set stopA $opts
1892	    lappend stops [list $offset $stopA(color) $stopA(opacity)]
1893	}
1894    }
1895    return $stops
1896}
1897
1898proc svg2can::parseUnaryOrPercentage {offset} {
1899    if {[string is double -strict $offset]} {
1900	return $offset
1901    } elseif {[regexp {(.+)%} $offset - percent]} {
1902	return [expr {$percent/100.0}]
1903    }
1904}
1905
1906# svg2can::parseColor --
1907#
1908#       Takes a SVG color definition and turns it into a Tk color.
1909#
1910# Arguments:
1911#       color       SVG color
1912#
1913# Results:
1914#       tk color
1915
1916proc svg2can::parseColor {color} {
1917
1918    if {[regexp {rgb\(([0-9]{1,3})%, *([0-9]{1,3})%, *([0-9]{1,3})%\)}  \
1919      $color - r g b]} {
1920	set col "#"
1921	foreach c [list $r $g $b] {
1922	    append col [format %02x [expr {round(2.55 * $c)}]]
1923	}
1924    } elseif {[regexp {rgb\(([0-9]{1,3}), *([0-9]{1,3}), *([0-9]{1,3})\)}  \
1925      $color - r g b]} {
1926	set col "#"
1927	foreach c [list $r $g $b] {
1928	    append col [format %02x $c]
1929	}
1930    } else {
1931	set col [MapNoneToEmpty $color]
1932    }
1933    return $col
1934}
1935
1936proc svg2can::parseFillToList {value} {
1937    variable gradientIDToToken
1938
1939    if {[regexp {url\(#(.+)\)} $value - id]} {
1940	#puts "\t id=$id"
1941	if {[info exists gradientIDToToken($id)]} {
1942	    #puts "\t gradientIDToToken=$gradientIDToToken($id)"
1943	    return [list -fill $gradientIDToToken($id)]
1944	} else {
1945	    puts "--------> missing gradientIDToToken id=$id"
1946	    return [list -fill black]
1947	}
1948    } else {
1949	return [list -fill [parseColor $value]]
1950    }
1951}
1952
1953proc svg2can::parseLength {length} {
1954    if {[string is double -strict $length]} {
1955	return $length
1956    }
1957    # SVG is using: px, pt, mm, cm, em, ex, in, %.
1958    # @@@ Incomplete!
1959    set length [string map {px ""  pt p  mm m  cm c  in i} $length]
1960    return [winfo fpixels . $length]
1961}
1962
1963proc svg2can::parsePathAttr {path} {
1964    regsub -all -- {([a-zA-Z])([0-9])} $path {\1 \2} path
1965    regsub -all -- {([0-9])([a-zA-Z])} $path {\1 \2} path
1966    regsub -all -- {([a-zA-Z])([a-zA-Z])} $path {\1 \2} path
1967    return [string map {- " -"  , " "} $path]
1968}
1969
1970# svg2can::StyleToOpts --
1971#
1972#       Takes the style attribute as a list and parses it into
1973#       resonable canvas drawing options.
1974#       Discards all attributes that don't map to an item option.
1975#
1976# Arguments:
1977#       type        tk canvas item type
1978#       styleList
1979#
1980# Results:
1981#       list of canvas options
1982
1983proc svg2can::StyleToOpts {type styleList args} {
1984
1985    variable textAnchorMap
1986
1987    array set argsA {
1988	-setdefaults 1
1989	-origfont    {Helvetica 12}
1990    }
1991    array set argsA $args
1992
1993    # SVG and canvas have different defaults.
1994    if {$argsA(-setdefaults)} {
1995	switch -- $type {
1996	    oval - polygon - rectangle {
1997		array set optsA {-fill black -outline ""}
1998	    }
1999	    line {
2000		array set optsA {-fill black}
2001	    }
2002	}
2003    }
2004
2005    set fontSpec $argsA(-origfont)
2006    set haveFont 0
2007
2008    foreach {key value} $styleList {
2009
2010	switch -- $key {
2011	    fill {
2012		switch -- $type {
2013		    arc - oval - polygon - rectangle - text {
2014			set optsA(-fill) [parseColor $value]
2015		    }
2016		}
2017	    }
2018	    font-family {
2019		lset fontSpec 0 $value
2020		set haveFont 1
2021	    }
2022	    font-size {
2023
2024		# Use pixels instead of points.
2025		if {[regexp {([0-9\.]+)pt} $value match pts]} {
2026		    set pix [expr {int($pts * [tk scaling] + 0.01)}]
2027		    lset fontSpec 1 "-$pix"
2028		} elseif {[regexp {([0-9\.]+)px} $value match pix]} {
2029		    lset fontSpec 1 [expr {int(-$pix)}]
2030		} else {
2031		    lset fontSpec 1 [expr {int(-$value)}]
2032		}
2033		set haveFont 1
2034	    }
2035	    font-style {
2036		switch -- $value {
2037		    italic {
2038			lappend fontSpec italic
2039		    }
2040		}
2041		set haveFont 1
2042	    }
2043	    font-weight {
2044		switch -- $value {
2045		    bold {
2046			lappend fontSpec bold
2047		    }
2048		}
2049		set haveFont 1
2050	    }
2051	    marker-end {
2052		set optsA(-arrow) last
2053	    }
2054	    marker-start {
2055		set optsA(-arrow) first
2056	    }
2057	    stroke {
2058		switch -- $type {
2059		    arc - oval - polygon - rectangle {
2060			set optsA(-outline) [parseColor $value]
2061		    }
2062		    line {
2063			set optsA(-fill) [parseColor $value]
2064		    }
2065		}
2066	    }
2067	    stroke-dasharray {
2068		set dash [split $value ,]
2069		if {[expr {[llength $dash]%2 == 1}]} {
2070		    set dash [concat $dash $dash]
2071		}
2072	    }
2073	    stroke-linecap {
2074		# canvas: butt (D), projecting , round
2075		# svg:    butt (D), square, round
2076		if {[string equal $value "square"]} {
2077		    set optsA(-capstyle) "projecting"
2078		}
2079		if {![string equal $value "butt"]} {
2080		    set optsA(-capstyle) $value
2081		}
2082	    }
2083	    stroke-linejoin {
2084		set optsA(-joinstyle) $value
2085	    }
2086	    stroke-miterlimit {
2087		# empty
2088	    }
2089	    stroke-opacity {
2090		if {[expr {$value == 0}]} {
2091
2092		}
2093	    }
2094	    stroke-width {
2095		if {![string equal $type "text"]} {
2096		    set optsA(-width) $value
2097		}
2098	    }
2099	    text-anchor {
2100		set optsA(-anchor) $textAnchorMap($value)
2101	    }
2102	    text-decoration {
2103		switch -- $value {
2104		    line-through {
2105			lappend fontSpec overstrike
2106		    }
2107		    underline {
2108			lappend fontSpec underline
2109		    }
2110		}
2111		set haveFont 1
2112	    }
2113	}
2114    }
2115    if {$haveFont} {
2116	set optsA(-font) $fontSpec
2117    }
2118    return [array get optsA]
2119}
2120
2121proc svg2can::StyleToOptsEx {styleList args} {
2122
2123    foreach {key value} $styleList {
2124	switch -- $key {
2125	    fill {
2126		foreach {name val} [parseFillToList $value] { break }
2127		set optsA($name) $val
2128	    }
2129	    opacity {
2130		# @@@ This is much more complicated than this for groups!
2131		set optsA(-fillopacity) $value
2132		set optsA(-strokeopacity) $value
2133	    }
2134	    stroke {
2135		set optsA(-$key) [parseColor $value]
2136	    }
2137	    stroke-dasharray {
2138		if {$value eq "none"} {
2139		    set optsA(-strokedasharray) {}
2140		} else {
2141		    set dash [split $value ,]
2142		    set optsA(-strokedasharray) $dash
2143		}
2144	    }
2145	    fill-opacity - stroke-linejoin - stroke-miterlimit - stroke-opacity {
2146		set name [string map {"-" ""} $key]
2147		set optsA(-$name) $value
2148	    }
2149	    stroke-linecap {
2150		# svg:    butt (D), square, round
2151		# canvas: butt (D), projecting , round
2152		if {$value eq "square"} {
2153		    set value "projecting"
2154		}
2155		set name [string map {"-" ""} $key]
2156		set optsA(-$name) $value
2157	    }
2158	    stroke-width {
2159		set name [string map {"-" ""} $key]
2160		set optsA(-$name) [parseLength $value]
2161	    }
2162	    r - rx - ry - width - height {
2163		set optsA(-$key) [parseLength $value]
2164	    }
2165	}
2166    }
2167    return [array get optsA]
2168}
2169
2170# svg2can::StopsStyleToStopSpec --
2171#
2172#       Takes the stop style attribute as a list and parses it into
2173#       a flat array for the gradient stopSpec: {offset color ?opacity?}
2174
2175proc svg2can::StopsStyleToStopSpec {styleList} {
2176
2177    foreach {key value} $styleList {
2178	switch -- $key {
2179	    stop-color {
2180		set optsA(color) [parseColor $value]
2181	    }
2182	    stop-opacity {
2183		set optsA(opacity) $value
2184	    }
2185	}
2186    }
2187    return [array get optsA]
2188}
2189
2190# svg2can::EllipticArcParameters --
2191#
2192#       Conversion from endpoint to center parameterization.
2193#       From: http://www.w3.org/TR/2003/REC-SVG11-20030114
2194
2195proc svg2can::EllipticArcParameters {x1 y1 rx ry phi fa fs x2 y2} {
2196    variable pi
2197
2198    # NOTE: direction of angles are opposite for Tk and SVG!
2199
2200    # F.6.2 Out-of-range parameters
2201    if {($x1 == $x2) && ($y1 == $y2)} {
2202	return skip
2203    }
2204    if {[expr {$rx == 0}] || [expr {$ry == 0}]} {
2205	return lineto
2206    }
2207    set rx [expr {abs($rx)}]
2208    set ry [expr {abs($ry)}]
2209    set phi [expr {fmod($phi, 360) * $pi/180.0}]
2210    if {$fa != 0} {
2211	set fa 1
2212    }
2213    if {$fs != 0} {
2214	set fs 1
2215    }
2216
2217    # F.6.5 Conversion from endpoint to center parameterization
2218    set dx [expr {($x1-$x2)/2.0}]
2219    set dy [expr {($y1-$y2)/2.0}]
2220    set x1prime [expr {cos($phi) * $dx + sin($phi) * $dy}]
2221    set y1prime [expr {-sin($phi) * $dx + cos($phi) * $dy}]
2222
2223    # F.6.6 Correction of out-of-range radii
2224    set rx [expr {abs($rx)}]
2225    set ry [expr {abs($ry)}]
2226    set x1prime2 [expr {$x1prime * $x1prime}]
2227    set y1prime2 [expr {$y1prime * $y1prime}]
2228    set rx2 [expr {$rx * $rx}]
2229    set ry2 [expr {$ry * $ry}]
2230    set lambda [expr {$x1prime2/$rx2 + $y1prime2/$ry2}]
2231    if {$lambda > 1.0} {
2232	set rx [expr {sqrt($lambda) * $rx}]
2233	set ry [expr {sqrt($lambda) * $ry}]
2234	set rx2 [expr {$rx * $rx}]
2235	set ry2 [expr {$ry * $ry}]
2236    }
2237
2238    # Compute cx' and cy'
2239    set sign [expr {$fa == $fs ? -1 : 1}]
2240    set square [expr {($rx2 * $ry2 - $rx2 * $y1prime2 - $ry2 * $x1prime2) /  \
2241      ($rx2 * $y1prime2 + $ry2 * $x1prime2)}]
2242    set root [expr {sqrt(abs($square))}]
2243    set cxprime [expr {$sign * $root * $rx * $y1prime/$ry}]
2244    set cyprime [expr {-$sign * $root * $ry * $x1prime/$rx}]
2245
2246    # Compute cx and cy from cx' and cy'
2247    set cx [expr {$cxprime * cos($phi) - $cyprime * sin($phi) + ($x1 + $x2)/2.0}]
2248    set cy [expr {$cxprime * sin($phi) + $cyprime * cos($phi) + ($y1 + $y2)/2.0}]
2249
2250    # Compute start angle and extent
2251    set ux [expr {($x1prime - $cxprime)/double($rx)}]
2252    set uy [expr {($y1prime - $cyprime)/double($ry)}]
2253    set vx [expr {(-$x1prime - $cxprime)/double($rx)}]
2254    set vy [expr {(-$y1prime - $cyprime)/double($ry)}]
2255
2256    set sign [expr {$uy > 0 ? 1 : -1}]
2257    set theta [expr {$sign * acos( $ux/hypot($ux, $uy) )}]
2258
2259    set sign [expr {$ux * $vy - $uy * $vx > 0 ? 1 : -1}]
2260    set delta [expr {$sign * acos( ($ux * $vx + $uy * $vy) /  \
2261      (hypot($ux, $uy) * hypot($vx, $vy)) )}]
2262
2263    # To degrees
2264    set theta [expr {$theta * 180.0/$pi}]
2265    set delta [expr {$delta * 180.0/$pi}]
2266    #set delta [expr {fmod($delta, 360)}]
2267    set phi   [expr {fmod($phi, 360)}]
2268
2269    if {($fs == 0) && ($delta > 0)} {
2270	set delta [expr {$delta - 360}]
2271    } elseif {($fs ==1) && ($delta < 0)} {
2272	set delta [expr {$delta + 360}]
2273    }
2274
2275    # NOTE: direction of angles are opposite for Tk and SVG!
2276    set theta [expr {-1*$theta}]
2277    set delta [expr {-1*$delta}]
2278
2279    return [list $cx $cy $rx $ry $theta $delta $phi]
2280}
2281
2282# svg2can::MergePresentationAttr --
2283#
2284#       Let the style attribute override the presentation attributes.
2285
2286proc svg2can::MergePresentationAttr {type opts presAttr} {
2287
2288    if {[llength $presAttr]} {
2289	array set optsA [StyleToOpts $type $presAttr]
2290	array set optsA $opts
2291	set opts [array get optsA]
2292    }
2293    return $opts
2294}
2295
2296proc svg2can::MergePresentationAttrEx {opts presAttr} {
2297
2298    if {[llength $presAttr]} {
2299	array set optsA [StyleToOptsEx $presAttr]
2300	array set optsA $opts
2301	set opts [array get optsA]
2302    }
2303    return $opts
2304}
2305
2306proc svg2can::StyleAttrToList {style} {
2307    return [split [string trim [string map {" " ""} $style] \;] :\;]
2308}
2309
2310proc svg2can::BaselineShiftToDy {baselineshift fontSpec} {
2311
2312    set linespace [font metrics $fontSpec -linespace]
2313
2314    switch -regexp -- $baselineshift {
2315	sub {
2316	    set dy [expr {0.8 * $linespace}]
2317	}
2318	super {
2319	    set dy [expr {-0.8 * $linespace}]
2320	}
2321	{-?[0-9]+%} {
2322	    set dy [expr {0.01 * $linespace * [string trimright $baselineshift %]}]
2323	}
2324	default {
2325	    # 0.5em ?
2326	    set dy $baselineshift
2327	}
2328    }
2329    return $dy
2330}
2331
2332# svg2can::PathAddRelative --
2333#
2334#       Utility function to add a relative point from the path to the
2335#       coordinate list. Updates iVar, and the current point.
2336
2337proc svg2can::PathAddRelative {path coVar iVar cpxVar cpyVar} {
2338    upvar $coVar  co
2339    upvar $iVar   i
2340    upvar $cpxVar cpx
2341    upvar $cpyVar cpy
2342
2343    set newx [expr {$cpx + [lindex $path [incr i]]}]
2344    set newy [expr {$cpy + [lindex $path [incr i]]}]
2345    lappend co $newx $newy
2346    set cpx $newx
2347    set cpy $newy
2348}
2349
2350proc svg2can::PointsToList {points} {
2351    return [string map {, " "} $points]
2352}
2353
2354# svg2can::ParseTransformAttr --
2355#
2356#       Parse the svg syntax for the transform attribute to a simple tcl
2357#       list.
2358
2359proc svg2can::ParseTransformAttr {attrlist} {
2360    set cmd ""
2361    set idx [lsearch -exact $attrlist "transform"]
2362    if {$idx >= 0} {
2363	set cmd [TransformAttrToList [lindex $attrlist [incr idx]]]
2364    }
2365    return $cmd
2366}
2367
2368proc svg2can::TransformAttrToList {cmd} {
2369    regsub -all -- {\( *([-0-9.]+) *\)} $cmd { \1} cmd
2370    regsub -all -- {\( *([-0-9.]+)[ ,]+([-0-9.]+) *\)} $cmd { {\1 \2}} cmd
2371    regsub -all -- {\( *([-0-9.]+)[ ,]+([-0-9.]+)[ ,]+([-0-9.]+) *\)} \
2372      $cmd { {\1 \2 \3}} cmd
2373    regsub -all -- {,} $cmd {} cmd
2374    return $cmd
2375}
2376
2377# svg2can::TransformAttrListToMatrix --
2378#
2379#       Processes a SVG transform attribute to a transformation matrix.
2380#       Used by tkpath only.
2381#
2382#       | a c tx |
2383#       | b d ty |
2384#       | 0 0 1  |
2385#
2386#       linear form : {a b c d tx ty}
2387
2388proc svg2can::TransformAttrListToMatrix {transform} {
2389    variable degrees2Radians
2390
2391    # @@@ I don't have 100% control of multiplication order!
2392    set i 0
2393
2394    foreach {op value} $transform {
2395
2396	switch -- $op {
2397	    matrix {
2398		set m([incr i]) $value
2399	    }
2400	    rotate {
2401		set phi [lindex $value 0]
2402		set cosPhi  [expr {cos($degrees2Radians*$phi)}]
2403		set sinPhi  [expr {sin($degrees2Radians*$phi)}]
2404		set msinPhi [expr {-1.0*$sinPhi}]
2405		if {[llength $value] == 1} {
2406		    set m([incr i])  \
2407		      [list $cosPhi $sinPhi $msinPhi $cosPhi 0 0]
2408		} else {
2409		    set cx [lindex $value 1]
2410		    set cy [lindex $value 2]
2411		    set m([incr i]) [list $cosPhi $sinPhi $msinPhi $cosPhi \
2412		      [expr {-$cx*$cosPhi + $cy*$sinPhi + $cx}] \
2413		      [expr {-$cx*$sinPhi - $cy*$cosPhi + $cy}]]
2414		}
2415	    }
2416	    scale {
2417		set sx [lindex $value 0]
2418		if {[llength $value] > 1} {
2419		    set sy [lindex $value 1]
2420		} else {
2421		    set sy $sx
2422		}
2423		set m([incr i]) [list $sx 0 0 $sy 0 0]
2424	    }
2425	    skewx {
2426		set tana [expr {tan($degrees2Radians*[lindex $value 0])}]
2427		set m([incr i]) [list 1 0 $tana 1 0 0]
2428	    }
2429	    skewy {
2430		set tana [expr {tan($degrees2Radians*[lindex $value 0])}]
2431		set m([incr i]) [list 1 $tana 0 1 0 0]
2432	    }
2433	    translate {
2434		set tx [lindex $value 0]
2435		if {[llength $value] > 1} {
2436		    set ty [lindex $value 1]
2437		} else {
2438		    set ty 0
2439		}
2440		set m([incr i]) [list 1 0 0 1 $tx $ty]
2441	    }
2442	}
2443    }
2444    if {$i == 1} {
2445	# This is the most common case.
2446	set mat $m($i)
2447    } else {
2448	set mat {1 0 0 1 0 0}
2449	foreach i [lsort -integer [array names m]] {
2450	    set mat [MMult $mat $m($i)]
2451	}
2452    }
2453    foreach {a b c d tx ty} $mat { break }
2454    return [list [list $a $c] [list $b $d] [list $tx $ty]]
2455}
2456
2457proc svg2can::MMult {m1 m2} {
2458    foreach {a1 b1 c1 d1 tx1 ty1} $m1 { break }
2459    foreach {a2 b2 c2 d2 tx2 ty2} $m2 { break }
2460    return [list \
2461      [expr {$a1*$a2  + $c1*$b2}]        \
2462      [expr {$b1*$a2  + $d1*$b2}]        \
2463      [expr {$a1*$c2  + $c1*$d2}]        \
2464      [expr {$b1*$c2  + $d1*$d2}]        \
2465      [expr {$a1*$tx2 + $c1*$ty2 + $tx1}] \
2466      [expr {$b1*$tx2 + $d1*$ty2 + $ty1}]]
2467}
2468
2469# svg2can::CreateTransformCanvasCmdList --
2470#
2471#       Takes a parsed list of transform attributes and turns them
2472#       into a sequence of canvas commands.
2473#       Standard items only which miss a matrix option.
2474
2475proc svg2can::CreateTransformCanvasCmdList {tag transformL} {
2476
2477    set cmdList {}
2478    foreach {key argument} $transformL {
2479
2480	switch -- $key {
2481	    translate {
2482		lappend cmdList [concat [list move $tag] $argument]
2483	    }
2484	    scale {
2485
2486		switch -- [llength $argument] {
2487		    1 {
2488			set xScale $argument
2489			set yScale $argument
2490		    }
2491		    2 {
2492			foreach {xScale yScale} $argument break
2493		    }
2494		    default {
2495			set xScale 1.0
2496			set yScale 1.0
2497		    }
2498		}
2499		lappend cmdList [list scale $tag 0 0 $xScale $yScale]
2500	    }
2501	}
2502    }
2503    return $cmdList
2504}
2505
2506proc svg2can::AddAnyTransformCmds {cmdList transformL} {
2507    variable tmptag
2508
2509    if {[llength $transformL]} {
2510	set cmdList [concat $cmdList \
2511	  [CreateTransformCanvasCmdList $tmptag $transformL]]
2512	lappend cmdList [list dtag $tmptag]
2513    }
2514    return $cmdList
2515}
2516
2517proc svg2can::MapNoneToEmpty {val} {
2518
2519    if {[string equal $val "none"]} {
2520	return
2521    } else {
2522	return $val
2523    }
2524}
2525
2526proc svg2can::FlattenList {hilist} {
2527
2528    set flatlist {}
2529    FlatListRecursive $hilist flatlist
2530    return $flatlist
2531}
2532
2533proc svg2can::FlatListRecursive {hilist flatlistVar} {
2534    upvar $flatlistVar flatlist
2535
2536    if {[string equal [lindex $hilist 0] "create"]} {
2537	set flatlist [list $hilist]
2538    } else {
2539	foreach c $hilist {
2540	    if {[string equal [lindex $c 0] "create"]} {
2541		lappend flatlist $c
2542	    } else {
2543		FlatListRecursive $c flatlist
2544	    }
2545	}
2546    }
2547}
2548
2549# svg2can::gettag, getattr, getcdata, getchildren --
2550#
2551#       Accesor functions to the specific things in a xmllist.
2552
2553proc svg2can::gettag {xmllist} {
2554    return [lindex $xmllist 0]
2555}
2556
2557proc svg2can::getattr {xmllist} {
2558    return [lindex $xmllist 1]
2559}
2560
2561proc svg2can::getcdata {xmllist} {
2562    return [lindex $xmllist 3]
2563}
2564
2565proc svg2can::getchildren {xmllist} {
2566    return [lindex $xmllist 4]
2567}
2568
2569proc svg2can::_DrawSVG {fileName w} {
2570    set fd [open $fileName r]
2571    set xml [read $fd]
2572    close $fd
2573    set xmllist [tinydom::documentElement [tinydom::parse $xml]]
2574    set cmdList [svg2can::parsesvgdocument $xmllist]
2575    foreach c $cmdList {
2576	puts $c
2577	eval $w $c
2578    }
2579}
2580
2581# Tests...
2582if {0} {
2583    # load /Users/matben/C/cvs/tkpath/macosx/build/tkpath0.2.1.dylib
2584    package require svg2can
2585    toplevel .t
2586    pack [canvas .t.c -width 600 -height 500]
2587    set i 0
2588
2589    set xml([incr i]) {<polyline points='400 10 10 10 10 400' \
2590      style='stroke: #000000; stroke-width: 1.0; fill: none;'/>}
2591
2592    # Text
2593    set xml([incr i]) {<text x='10.0' y='20.0' \
2594      style='stroke-width: 0; font-family: Helvetica; font-size: 12; \
2595      fill: #000000;' id='std text t001'>\
2596      <tspan>Start</tspan><tspan>Mid</tspan><tspan>End</tspan></text>}
2597    set xml([incr i]) {<text x='10.0' y='40.0' \
2598      style='stroke-width: 0; font-family: Helvetica; font-size: 12; \
2599      fill: #000000;' id='std text t002'>One\
2600      straight text data</text>}
2601    set xml([incr i]) {<text x='10.0' y='60.0' \
2602      style='stroke-width: 0; font-family: Helvetica; font-size: 12; \
2603      fill: #000000;' id='std text t003'>\
2604      <tspan>Online</tspan><tspan dy='6'>dy=6</tspan><tspan dy='-6'>End</tspan></text>}
2605    set xml([incr i]) {<text x='10.0' y='90.0' \
2606      style='stroke-width: 0; font-family: Helvetica; font-size: 16; \
2607      fill: #000000;' id='std text t004'>\
2608      <tspan>First</tspan>\
2609      <tspan dy='10'>Online (dy=10)</tspan>\
2610      <tspan><tspan>Nested</tspan></tspan><tspan>End</tspan></text>}
2611
2612    # Paths
2613    set xml([incr i]) {<path d='M 200 100 L 300 100 300 200 200 200 Z' \
2614      style='fill-rule: evenodd; fill: none; stroke: black; stroke-width: 1.0;\
2615      stroke-linejoin: round;' id='std poly t005'/>}
2616    set xml([incr i]) {<path d='M 30 100 Q 80 30 100 100 130 65 200 80' \
2617      style='fill-rule: evenodd; fill: none; stroke: #af5da8; stroke-width: 4.0;\
2618      stroke-linejoin: round;' id='std poly t006'/>}
2619    set xml([incr i]) {<polyline points='30 100,80 30,100 100,130 65,200 80' \
2620      style='fill: none; stroke: red;'/>}
2621    set xml([incr i])  {<path d='M 10 200 H 100 200 v20h 10'\
2622    style='fill-rule: evenodd; fill: none; stroke: black; stroke-width: 2.0;\
2623      stroke-linejoin: round;' id='std t008'/>}
2624    set xml([incr i])  {<path d='M 20 200 V 300 310 h 10 v 10'\
2625      style='fill-rule: evenodd; fill: none; stroke: blue; stroke-width: 2.0;\
2626      stroke-linejoin: round;' id='std t008'/>}
2627    set xml([incr i]) {<path d='M 30 100 Q 80 30 100 100 T 200 80' \
2628      style='fill: none; stroke: green; stroke-width: 2.0;' id='t006'/>}
2629    set xml([incr i]) {<path d='M 30 200 Q 80 130 100 200 T 150 180 200 180 250 180 300 180' \
2630      style='fill: none; stroke: gray50; stroke-width: 2.0;' id='t006'/>}
2631    set xml([incr i]) {<path d='M 30 300 Q 80 230 100 300 t 50 0 50 0 50 0 50 0' \
2632      style='fill: none; stroke: gray50; stroke-width: 1.0;' id='std poly t006'/>}
2633    set xml([incr i]) {<path d="M100,200 C100,100 250,100 250,200 \
2634      S400,300 400,200" style='fill: none; stroke: black'/>}
2635
2636    set xml([incr i]) {<path d="M 125 75 A 100 50 0 0 0 225 125" \
2637      style='fill: none; stroke: blue; stroke-width: 2.0;'/>}
2638    set xml([incr i]) {<path d="M 125 75 A 100 50 0 0 1 225 125" \
2639      style='fill: none; stroke: red; stroke-width: 2.0;'/>}
2640    set xml([incr i]) {<path d="M 125 75 A 100 50 0 1 0 225 125" \
2641      style='fill: none; stroke: green; stroke-width: 2.0;'/>}
2642    set xml([incr i]) {<path d="M 125 75 A 100 50 0 1 1 225 125" \
2643      style='fill: none; stroke: gray50; stroke-width: 2.0;'/>}
2644
2645    # g
2646    set xml([incr i]) {<g fill="none" stroke="red" stroke-width="3" > \
2647      <line x1="300" y1="10" x2="350" y2="10" /> \
2648      <line x1="300" y1="10" x2="300" y2="50" /> \
2649      </g>}
2650
2651    # translate
2652    set xml([incr i]) {<rect id="t0012" x="10" y="10" width="20" height="20" \
2653      style="stroke: yellow; fill: none; stroke-width: 2.0;" \
2654      transform="translate(200,200)"/>}
2655    set xml([incr i]) {<rect id="t0013" x="10" y="10" width="20" height="20" \
2656      style="stroke: yellow; fill: none; stroke-width: 2.0;" transform="scale(4)"/>}
2657    set xml([incr i]) {<circle id="t0013" cx="10" cy="10" r="20" \
2658      style="stroke: yellow; fill: none; stroke-width: 2.0;"\
2659      transform="translate(200,300)"/>}
2660    set xml([incr i]) {<text x='10.0' y='40.0' transform="translate(200,300)" \
2661      style='font-family: Helvetica; font-size: 24; \
2662      fill: #000000;'>Translated Text</text>}
2663
2664    # tkpath tests...
2665    set xml([incr i]) {<rect x='10' y='10' height='15' width='20' \
2666      transform='translate(30, 20) scale(2)' style='fill: gray;'/>}
2667    set xml([incr i]) {<rect x='10' y='10' height='15' width='20' \
2668      transform='scale(2) translate(30, 20)' style='fill: black;'/>}
2669
2670    set xml([incr i]) {<g transform='translate(30, 20)'> \
2671      <g transform='scale(2)'> \
2672      <rect x='10' y='10' height='15' width='20' style='stroke: blue; fill: none;'/> \
2673      </g> \
2674      </g>}
2675
2676    .t.c create path "M 30 20 h 100 M 30 20 v 100"
2677    .t.c create prect 10 10 30 25 -stroke {} -fill gray
2678
2679    foreach i [lsort -integer [array names xml]] {
2680	set xmllist [tinydom::documentElement [tinydom::parse $xml($i)]]
2681	set cmdList [svg2can::parseelement $xmllist]
2682	foreach c $cmdList {
2683	    puts $c
2684	    eval .t.c $c
2685	}
2686    }
2687
2688}
2689
2690#-------------------------------------------------------------------------------
2691