Version 9 of can2svg

Updated 2009-08-18 09:20:40 by gpl

What: can2svg

 Where: http://hem.fyristorg.com/matben/download/can2svg.tcl
 Description: Package to translate a canvas to Adobe SVG format.
 Updated: 05/2002
 Contact: mailto:[email protected] (Mats Bengtsson)

FYI: SVG is not an Adobe format, it's a W3C recommendation [L1 ].


Yes, the link is broken. Is this the same file as: http://cvs.rycks.com/cgi-bin/cvsweb/kidistb/can2svg.tcl ??

No, that one is just based on the can2svg.tcl from Mats Bengtsson but it is not the same.


ysk: It is still available on Internet Archive. http://web.archive.org/web/20050414013028/http://hem.fyristorg.com/matben/download/can2svg.tcl


 #  can2svg.tcl ---
 #  
 #      This file provides translation from canvas commands to XML/SVG format.
 #      
 #  Copyright (c) 2002  Mats Bengtsson
 #
 # ########################### USAGE ############################################
 #
 #   NAME
 #      can2svg - translate canvas command to SVG.
 #      
 #   SYNOPSIS
 #      can2svg canvasCmd ?options?
 #           canvasCmd is everything except the widget path name.
 #           
 #      canvas2file widgetPath fileName ?options?
 #           options:   -height
 #                      -width
 #      
 #
 # ########################### CHANGES ##########################################
 #
 #   0.1      first release
 #
 # ########################### TODO #############################################
 # 
 #   handle units (m->mm etc.) 
 #   better support for stipple patterns
 #   how to handle tk editing? DOM?
 #   URI encoding
 #   
 #   ...

 package provide can2svg 0.1

 namespace eval ::can2svg:: {

     namespace export can2svg canvas2file

     variable formatArrowMarker
     variable formatArrowMarkerLast

     # The key into this array is 'arrowMarkerDef_$col_$a_$b_$c', where
     # col is color, and a, b, c are the arrow's shape.
     variable defsArrowMarkerArr

     # Similarly for stipple patterns.
     variable defsStipplePatternArr

     # This shouldn't be hardcoded!
     variable defaultFont {Helvetica 12}

     variable anglesToRadians [expr 3.14159265359/180.0]
     variable grayStipples {gray75 gray50 gray25 gray12}

     # Make 4x4 squares. Perhaps could be improved.
     variable stippleDataArr

     set stippleDataArr(gray75)  \
       {M 0 0 h3 M 0 1 h1 m 1 0 h2 M 0 2 h2 m 1 0 h1 M 0 3 h3}
     set stippleDataArr(gray50)  \
       {M 0 0 h1 m 1 0 h1 M 1 1 h1 m 1 0 h1 \
       M 0 2 h1 m 1 0 h1 M 1 3 h1 m 1 0 h1}
     set stippleDataArr(gray25)  \
       {M 0 0 h1 M 2 1 h1 M 1 2 h1 M 3 3 h1}
     set stippleDataArr(gray12) {M 0 0 h1 M 2 2 h1}

 }

 # ::can2svg::can2svg --
 #
 #       Make xml out of a canvas command, widgetPath removed.
 #       
 # Arguments:
 #       cmd         canvas command without prepending widget path.
 #       args    -usetags    0|all|first|last
 #       
 # Results:
 #   xml data

 proc ::can2svg::can2svg {cmd args} {

     variable defsArrowMarkerArr
     variable defsStipplePatternArr
     variable anglesToRadians
     variable defaultFont
     variable grayStipples

     set nonum_ {[^0-9]}
     set wsp_ {[ ]+}
     set xml ""

     array set argsArr {-usetags all}
     array set argsArr $args

     switch -- [lindex $cmd 0] {

         create {
             set type [lindex $cmd 1]
             set rest [lrange $cmd 2 end]
             regexp -indices -- "-${nonum_}" $rest ind
             set ind1 [lindex $ind 0]
             set coo [string trim [string range $rest 0 [expr $ind1 - 1]]]
             set opts [string range $rest $ind1 end]
             array set optArr $opts

             # Figure out if we've got a spline.
             set haveSpline 0
             if {[info exists optArr(-smooth)] && ($optArr(-smooth) != "0") &&  \
               [info exists optArr(-splinesteps)] && ($optArr(-splinesteps) > 2)} {
                 set haveSpline 1
             }
             if {[info exists optArr(-fill)]} {
                 set fillValue $optArr(-fill)
             } else {
                 set fillValue black
             }
             if {($argsArr(-usetags) != "0") && [info exists optArr(-tags)]} {
                 switch -- $argsArr(-usetags) {
                     all {                        
                         set idAttr [list "id" $optArr(-tags)]
                     }
                     first {
                         set idAttr [list "id" [lindex $optArr(-tags) 0]]
                     }
                     last {
                         set idAttr [list "id" [lindex $optArr(-tags) end]]
                     }
                 }
             } else {
                 set idAttr ""
             }

             # If we need a marker (arrow head) need to make that first.
             if {[info exists optArr(-arrow)]} {
                 if {[info exists optArr(-arrowshape)]} {

                     # Make a key of the arrowshape list into the array.
                     regsub -all -- $wsp_ $optArr(-arrowshape) _ shapeKey
                     set arrowKey ${fillValue}_${shapeKey}
                     set arrowShape $optArr(-arrowshape)
                 } else {
                     set arrowKey ${fillValue}
                     set arrowShape {8 10 3}
                 }
                 if {![info exists defsArrowMarkerArr($arrowKey)]} {
                     set defsArrowMarkerArr($arrowKey)  \
                       [eval {MakeArrowMarker} $arrowShape {$fillValue}]
                     append xml $defsArrowMarkerArr($arrowKey)
                     append xml "\n\t"
                 }
             }

             # If we need a stipple bitmap, need to make that first. Limited!!!
             # Only: gray12, gray25, gray50, gray75
             foreach key {-stipple -outlinestipple} {
                 if {[info exists optArr($key)] &&  \
                   ([lsearch $grayStipples $optArr($key)] >= 0)} {
                     set stipple $optArr($key)
                     if {![info exists defsStipplePatternArr($stipple)]} {
                         set defsStipplePatternArr($stipple)  \
                           [MakeGrayStippleDef $stipple]
                     }
                     append xml $defsStipplePatternArr($stipple)
                     append xml "\n\t"
                 }
             }

             switch -- $type {

                 arc {

                     # Had to do it the hard way! (?)
                     # "Wrong" coordinate system :-(
                     set elem "path"
                     set style [MakeStyle $type $opts]
                     foreach {x1 y1 x2 y2} $coo {}
                     set cx [expr ($x1 + $x2)/2.0]
                     set cy [expr ($y1 + $y2)/2.0]
                     set rx [expr abs($x1 - $x2)/2.0]
                     set ry [expr abs($y1 - $y2)/2.0]
                     set rmin [expr $rx > $ry ? $ry : $rx]

                     # This approximation gives a maximum half pixel error.
                     set deltaPhi [expr 2.0/sqrt($rmin)]
                     set extent [expr $anglesToRadians * $optArr(-extent)]
                     set start [expr $anglesToRadians * $optArr(-start)]
                     set nsteps [expr int(abs($extent)/$deltaPhi) + 2]
                     set delta [expr $extent/$nsteps]
                     set data [format "M %.1f %.1f L"   \
                       [expr $cx + $rx*cos($start)] [expr $cy - $ry*sin($start)]]
                     for {set i 0} {$i <= $nsteps} {incr i} {
                         set phi [expr $start + $i * $delta]
                         append data [format " %.1f %.1f"  \
                           [expr $cx + $rx*cos($phi)] [expr $cy - $ry*sin($phi)]]
                     }
                     if {[info exists optArr(-style)]} {
                         switch -- $optArr(-style) {
                             chord {
                                 append data " Z"
                             }
                             pieslice {
                                 append data [format " %.1f %.1f Z" $cx $cy]
                             }
                         }
                     } else {

                         # Pieslice is the default.
                         append data [format " %.1f %.1f Z" $cx $cy]
                     }
                     set attr [list "d" $data "style" $style]
                     if {[string length $idAttr] > 0} {
                         set attr [concat $attr $idAttr]
                     }
                     set xmlList [MakeXMLList $elem -attrlist $attr]
                 }
                 image - bitmap {
                     set elem "image"
                     set attr [MakeImageAttr $coo $opts]
                     if {[string length $idAttr] > 0} {
                         set attr [concat $attr $idAttr]
                     }
                     set xmlList [MakeXMLList $elem -attrlist $attr]
                 }
                 line {
                     if {$haveSpline} {
                         set elem "path"
                         set style [MakeStyle $type $opts]
                         set data "M [lrange $coo 0 1] Q"
                         set i 4
                         foreach {x y} [lrange $coo 2 end-4] {
                             set x0 [expr ($x + [lindex $coo $i])/2.0]
                             incr i
                             set y0 [expr ($y + [lindex $coo $i])/2.0]
                             incr i
                             append data " $x $y $x0 $y0"                            
                         }
                         append data " [lrange $coo end-3 end]"
                         set attr [list "d" $data "style" $style]
                     } else {
                         set elem "polyline"
                         set style [MakeStyle $type $opts]
                         set attr [list "points" $coo "style" $style]
                     }
                     if {[string length $idAttr] > 0} {
                         set attr [concat $attr $idAttr]
                     }
                     set xmlList [MakeXMLList $elem -attrlist $attr]
                 }
                 oval {
                     foreach {x y w h} [NormalizeRectCoords $coo] {}
                     if {[expr $w == $h]} {
                         set elem "circle"
                         set attr [list  \
                           "cx" [expr $x + $w/2.0]  \
                           "cy" [expr $y + $h/2.0]  \
                           "r"  [expr $w/2.0]]
                     } else {
                         set elem "ellipse"
                         set attr [list  \
                           "cx" [expr $x + $w/2.0]  \
                           "cy" [expr $y + $h/2.0]  \
                           "rx" [expr $w/2.0]       \
                           "ry" [expr $h/2.0]]
                     }
                     set style [MakeStyle $type $opts]
                     lappend attr "style" $style
                     if {[string length $idAttr] > 0} {
                         set attr [concat $attr $idAttr]
                     }
                     set xmlList [MakeXMLList $elem -attrlist $attr]
                 }
                 polygon {
                     if {$haveSpline} {
                         set elem "path"
                         set style [MakeStyle $type $opts]

                         # Translating a closed polygon into a qubic bezier
                         # path is a little bit tricky.
                         set x0 [expr ([lindex $coo end-1] + [lindex $coo 0])/2.0]
                         set y0 [expr ([lindex $coo end] + [lindex $coo 1])/2.0]
                         set data "M $x0 $y0 Q"
                         set i 2
                         foreach {x y} [lrange $coo 0 end-2] {
                             set x1 [expr ($x + [lindex $coo $i])/2.0]
                             incr i
                             set y1 [expr ($y + [lindex $coo $i])/2.0]
                             incr i
                             append data " $x $y $x1 $y1"                            
                         }
                         append data " [lrange $coo end-1 end] $x0 $y0"
                         set attr [list "d" $data "style" $style]
                     } else {
                         set elem "polygon"
                         set style [MakeStyle $type $opts]
                         set attr [list "points" $coo "style" $style]
                     }
                     if {[string length $idAttr] > 0} {
                         set attr [concat $attr $idAttr]
                     }
                     set xmlList [MakeXMLList $elem -attrlist $attr]
                 }
                 rectangle {
                     set elem "rect"
                     set style [MakeStyle $type $opts]

                     # width and height must be non-negative!
                     foreach {x y w h} [NormalizeRectCoords $coo] {}
                     set attr [list "x" $x "y" $y "width" $w "height" $h]
                     lappend attr "style" $style
                     if {[string length $idAttr] > 0} {
                         set attr [concat $attr $idAttr]
                     }
                     set xmlList [MakeXMLList $elem -attrlist $attr]                    
                 }
                 text {
                     set elem "text"
                     set style [MakeStyle $type $opts]
                     set nlines 1
                     if {[info exists optArr(-text)]} {
                         set chdata $optArr(-text)
                         set nlines [expr [regexp -all "\n" $chdata] + 1]
                     } else {
                         set chdata ""
                     }

                     # Figure out the coords of the first baseline.
                     set anchor center
                     if {[info exists optArr(-anchor)]} {
                         set anchor $optArr(-anchor)
                     }                                        
                     if {[info exists optArr(-font)]} {
                         set theFont $optArr(-font)
                     } else {
                         set theFont $defaultFont
                     }
                     set ascent [font metrics $theFont -ascent]
                     set lineSpace [font metrics $theFont -linespace]

                     foreach {xbase ybase}  \
                       [GetTextSVGCoords $coo $anchor $chdata $theFont $nlines] {}

                     set attr [list "x" $xbase "y" $ybase]
                     lappend attr "style" $style
                     if {[string length $idAttr] > 0} {
                         set attr [concat $attr $idAttr]
                     }
                     set dy 0
                     if {$nlines > 1} {

                         # Use the 'tspan' trick here.
                         set subList {}
                         foreach line [split $chdata "\n"] {
                             lappend subList [MakeXMLList "tspan"  \
                               -attrlist [list "x" $xbase "dy" $dy] -chdata $line]
                             set dy $lineSpace
                         }
                         set xmlList [MakeXMLList $elem -attrlist $attr \
                           -subtags $subList]
                     } else {
                         set xmlList [MakeXMLList $elem -attrlist $attr \
                           -chdata $chdata]
                     }
                 }
             }
         }
         move {
             foreach {tag dx dy} [lrange $cmd 1 3] {}
             set attr [list "transform" "translate($dx,$dy)"  \
               "xlink:href" "#$tag"]
             set xmlList [MakeXMLList "use" -attrlist $gattr]
         }
         scale {

         }
     }
     append xml [MakeXML $xmlList]
     return $xml
 }

 # ::can2svg::MakeStyle --
 #
 #       Produce the SVG style attribute from the canvas item options.
 #
 # Arguments:
 #       type        tk canvas widget item type
 #       opts
 #       
 # Results:
 #       The SVG style attribute as a a string.

 proc ::can2svg::MakeStyle {type opts} {

     # Defaults for everything except text.
     if {![string equal $type "text"]} {
         array set styleArr {fill none stroke black}
     }
     set fillCol black

     foreach {key value} $opts {

         switch -- $key {
             -arrow {
                 set arrowValue $value
             }
             -arrowshape {
                 set arrowShape $value
             }
             -capstyle {
                 if {[string equal $value "projecting"]} {
                     set value "square"
                 }
                 if {![string equal $value "butt"]} {
                     set styleArr(stroke-linecap) $value
                 }
             }
             -dash {
                 set dashValue $value
             }
             -dashoffset {
                 if {$value != 0} {
                     set styleArr(stroke-dashoffset) $value
                 }
             }
             -fill {
                 set fillCol $value
                 if {[string equal $type "line"]} {
                     set styleArr(stroke) [MapEmptyToNone $value]
                 } else {
                     set styleArr(fill) [MapEmptyToNone $value]
                 }
             }
             -font {
                 set styleArr(font-family) [lindex $value 0]
                 if {[llength $value] > 1} {
                     set styleArr(font-size) [lindex $value 1]
                 }
                 if {[llength $value] > 2} {
                     set tkstyle [lindex $value 2]
                     switch -- $tkstyle {
                         bold {
                             set styleArr(font-weight) $tkstyle
                         }
                         italic {
                             set styleArr(font-style) $tkstyle
                         }
                         underline {
                             set styleArr(text-decoration) underline
                         }
                         overstrike {
                             set styleArr(text-decoration) overline
                         }
                     }
                 }                

             }
             -joinstyle {
                 set styleArr(stroke-linejoin) $value                
             }
             -outline {
                 set styleArr(stroke) [MapEmptyToNone $value]
             }
             -outlinestipple {
                 set outlineStippleValue $value
             }
             -stipple {
                 set stippleValue $value
             }
             -width {
                 set styleArr(stroke-width) $value
             }
         }
     }

     # If any arrow specify its marker def url key.
     if {[info exists arrowValue]} {
         if {[info exists arrowShape]} {        
             foreach {a b c} $arrowShape {}
             set arrowIdKey "arrowMarkerDef_${fillCol}_${a}_${b}_${c}"
             set arrowIdKeyLast "arrowMarkerLastDef_${fillCol}_${a}_${b}_${c}"
         } else {
             set arrowIdKey "arrowMarkerDef_${fillCol}"
         }
         switch -- $arrowValue {
             first {
                 set styleArr(marker-start) "url(#$arrowIdKey)"
             }
             last {
                 set styleArr(marker-end) "url(#$arrowIdKeyLast)"
             }
             both {
                 set styleArr(marker-start) "url(#$arrowIdKey)"
                 set styleArr(marker-end) "url(#$arrowIdKeyLast)"
             }
         }
     }

     if {[info exists stippleValue]} {

         # Overwrite any existing.
         set styleArr(fill) "url(#tile$stippleValue)"
     }
     if {[info exists outlineStippleValue]} {

         # Overwrite any existing.
         set styleArr(stroke) "url(#tile$stippleValue)"
     }

     # Transform dash value.
     if {[info exists dashValue]} {

         # Two different syntax here.                
         if {[regexp {[\.,\-_ ]} $dashValue]} {

             # .=2 ,=4 -=6 space=4    times stroke width.
             # A space enlarges the... space.
             # Not foolproof!
             regsub -all -- {[^ ]} $dashValue "& " dash
             regsub -all -- "   "  $dash  "12 " dash
             regsub -all -- "  "   $dash  "8 " dash
             regsub -all -- " "    $dash  "4 " dash
             regsub -all -- {\.}   $dash  "2 " dash
             regsub -all -- {,}    $dash  "4 " dash
             regsub -all -- {-}    $dash  "6 " dash                    

             # Multiply with stroke width if > 1.
             if {[info exists styleArr(stroke-width)] &&  \
               ($styleArr(stroke-width) > 1)} {
                 set width $styleArr(stroke-width)
                 set dashOrig $dash
                 set dash {}
                 foreach num $dashOrig {
                     lappend dash [expr int($width * $num)]
                 }
             }
             set styleArr(stroke-dasharray) [string trim $dash]
         } else {
             set styleArr(stroke-dasharray) $value
         }
     }
     if {[string equal $type "polygon"]} {
         set styleArr(fill-rule) "evenodd"
     }

     set style ""
     foreach {key value} [array get styleArr] {
         append style "${key}: ${value}; "
     }
     return [string trim $style]
 }

 # ::can2svg::MakeImageAttr --
 #
 #       Special code is needed to make the attributes for an image item.
 #       
 # Arguments:
 #       elem 
 #       
 # Results:
 #   

 proc ::can2svg::MakeImageAttr {coo opts} {

     array set optArr {-anchor nw}
     array set optArr $opts
     set theImage $optArr(-image)
     set w [image width $theImage]
     set h [image height $theImage]

     # We should make this an URI.
     set theFile [$theImage cget -file]
     set uri [UriFromLocalFile $theFile]
     foreach {x0 y0} $coo {}
     switch -- $optArr(-anchor) {
         nw {
             set x $x0
             set y $y0
         }
         n {
             set x [expr $x0 - $w/2.0]
             set y $y0
         }
         ne {
             set x [expr $x0 - $w]
             set y $y0
         }
         e {
             set x $x0
             set y [expr $y0 - $h/2.0]
         }
         se {
             set x [expr $x0 - $w]
             set y [expr $y0 - $h]
         }
         s {
             set x [expr $x0 - $w/2.0]
             set y [expr $y0 - $h]
         }
         sw {
             set x $x0
             set y [expr $y0 - $h]
         } 
         w {
             set x $x0
             set y [expr $y0 - $h/2.0]
         }
         center {
             set x [expr $x0 - $w/2.0]
             set y [expr $y0 - $h/2.0]
         }
     }
     set attrList [list "x" $x "y" $y "width" $w "height" $h  \
       "xlink:href" $uri]
     return $attrList
 }

 # ::can2svg::GetTextSVGCoords --
 # 
 #       Figure out the baseline coords of the svg text element from
 #       the canvas text item.
 #
 # Arguments:
 #       coo         {x y}
 #       anchor
 #       chdata      character data, newlines included.
 #       
 # Results:
 #       raw xml data of the marker def element.

 proc ::can2svg::GetTextSVGCoords {coo anchor chdata theFont nlines} {

     foreach {x y} $coo {}
     set ascent [font metrics $theFont -ascent]
     set lineSpace [font metrics $theFont -linespace]

     # If not anchored to the west it gets more complicated.
     if {![string match $anchor "*w*"]} {

         # Need to figure out the extent of the text.
         if {$nlines <= 1} {
             set textWidth [font measure $theFont $chdata]
         } else {
             set textWidth 0
             foreach line [split $chdata "\n"] {
                 set lineWidth [font measure $theFont $line]
                 if {$lineWidth > $textWidth} {
                     set textWidth $lineWidth
                 }
             }
         }
     }

     switch -- $anchor {
         nw {
             set xbase $x
             set ybase [expr $y + $ascent]
         }
         w {
             set xbase $x
             set ybase [expr $y - $nlines*$lineSpace/2.0 + $ascent]
         }
         sw {
             set xbase $x
             set ybase [expr $y - $nlines*$lineSpace + $ascent]
         }
         s {
             set xbase [expr $x - $textWidth/2.0]
             set ybase [expr $y - $nlines*$lineSpace + $ascent]
         }
         se {
             set xbase [expr $x - $textWidth]
             set ybase [expr $y - $nlines*$lineSpace + $ascent]
         }
         e {
             set xbase [expr $x - $textWidth]
             set ybase [expr $y - $nlines*$lineSpace/2.0 + $ascent]
         }
         ne {
             set xbase [expr $x - $textWidth]
             set ybase [expr $y + $ascent]
         } 
         n {
             set xbase [expr $x - $textWidth/2.0]
             set ybase [expr $y + $ascent]
         }
         center {
             set xbase [expr $x - $textWidth/2.0]
             set ybase [expr $y - $nlines*$lineSpace/2.0 + $ascent]
         }
     }

     return [list $xbase $ybase]
 }

 # ::can2svg::MakeArrowMarker --
 # 
 #       Make the xml for an arrow marker def element.
 #
 # Arguments:
 #       a           arrows length along its symmetry line
 #       b           arrows total length
 #       c           arrows half width
 #       col         its color
 #       
 # Results:
 #       raw xml data of the marker def elements, both start and last.

 proc ::can2svg::MakeArrowMarker {a b c col} {

     variable formatArrowMarker
     variable formatArrowMarkerLast

     catch {unset formatArrowMarker}

     if {![info exists formatArrowMarker]} {

         # "M 0 c, b 0, a c, b 2*c Z" for the start marker.
         # "M 0 0, b c, 0 2*c, b-a c Z" for the last marker.
         set data "M 0 %s, %s 0, %s %s, %s %s Z"
         set style "fill: %s; stroke: %s;"
         set attr [list "d" $data "style" $style]
         set arrowList [MakeXMLList "path" -attrlist $attr]
         set markerAttr [list "id" %s "markerWidth" %s "markerHeight" %s  \
           "refX" %s "refY" %s "orient" "auto"]
         set defElemList [MakeXMLList "defs" -subtags  \
           [list [MakeXMLList "marker" -attrlist $markerAttr \
           -subtags [list $arrowList] ] ] ]
         set formatArrowMarker [MakeXML $defElemList]

         # ...and the last arrow marker.
         set dataLast "M 0 0, %s %s, 0 %s, %s %s Z"
         set attrLast [list "d" $dataLast "style" $style]
         set arrowLastList [MakeXMLList "path" -attrlist $attrLast]
         set defElemLastList [MakeXMLList "defs" -subtags  \
           [list [MakeXMLList "marker" -attrlist $markerAttr \
           -subtags [list $arrowLastList] ] ] ]
         set formatArrowMarkerLast [MakeXML $defElemLastList]
     }
     set idKey "arrowMarkerDef_${col}_${a}_${b}_${c}"
     set idKeyLast "arrowMarkerLastDef_${col}_${a}_${b}_${c}"

     # Figure out the order of all %s substitutions.
     set markerXML [format $formatArrowMarker $idKey  \
       $b [expr 2*$c] 0 $c  \
       $c $b $a $c $b [expr 2*$c] $col $col]
     set markerLastXML [format $formatArrowMarkerLast $idKeyLast  \
       $b [expr 2*$c] $b $c \
       $b $c [expr 2*$c] [expr $b-$a] $c $col $col]

     return "$markerXML\n\t$markerLastXML"
 }

 # ::can2svg::MakeGrayStippleDef --
 #
 #

 proc ::can2svg::MakeGrayStippleDef {stipple} {

     variable stippleDataArr

     set pathList [MakeXMLList "path" -attrlist  \
       [list "d" $stippleDataArr($stipple) "style" "stroke: black; fill: none;"]]
     set patterAttr [list "id" "tile$stipple" "x" 0 "y" 0 "width" 4 "height" 4 \
       "patternUnits" "userSpaceOnUse"]
     set defElemList [MakeXMLList "defs" -subtags  \
       [list [MakeXMLList "pattern" -attrlist $patterAttr \
       -subtags [list $pathList] ] ] ]

     return [MakeXML $defElemList]
 }

 # ::can2svg::MapEmptyToNone --
 #
 #
 # Arguments:
 #       elem 
 #       
 # Results:
 #   

 proc ::can2svg::MapEmptyToNone {val} {

     if {[string length $val] == 0} {
         return "none"
     } else {
         return $val
     }
 }

 # ::can2svg::NormalizeRectCoords --
 #
 #
 # Arguments:
 #       elem 
 #       
 # Results:
 #   

 proc ::can2svg::NormalizeRectCoords {coo} {

     foreach {x1 y1 x2 y2} $coo {}
     return [list [expr $x2 > $x1 ? $x1 : $x2]  \
       [expr $y2 > $y1 ? $y1 : $y2]  \
       [expr abs($x1-$x2)]  \
       [expr abs($y1-$y2)]]
 }

 # ::can2svg::makedocument --
 #
 #       Adds the prefix and suffix elements to make a complete XML/SVG
 #       document.
 #
 # Arguments:
 #       elem 
 #       
 # Results:
 #   

 proc ::can2svg::makedocument {width height xml} {

     set pre "<?xml version='1.0'?>\n\
       <!DOCTYPE svg PUBLIC \"-//W3C//DTD SVG 1.0//EN\"\
       \"http://www.w3.org/TR/2001/REC-SVG-20010904/DTD/svg10.dtd\">"

     set svgStart "<svg width='$width' height='$height'>"
     set svgEnd "</svg>"
     return "${pre}\n${svgStart}\n${xml}${svgEnd}"
 }

 # ::can2svg::canvas2file --
 #
 #       Takes everything on a canvas widget, translates it to XML/SVG,
 #       and puts it on a file.
 #       
 # Arguments:
 #       wcan        the canvas widget path
 #       path        the file path
 #       args:   -height
 #               -width 
 #       
 # Results:
 #   

 proc ::can2svg::canvas2file {wcan path args} {

     variable defsArrowMarkerArr
     variable defsStipplePatternArr

     # Need to make a fresh start for marker def's.
     catch {unset defsArrowMarkerArr}
     catch {unset defsStipplePatternArr}

     array set argsArr  \
       [list -width [winfo width $wcan] -height [winfo height $wcan]]
     array set argsArr $args

     set fd [open $path w]

     set xml ""
     foreach id [$wcan find all] {
         set type [$wcan type $id]
         set opts [$wcan itemconfigure $id]
         set opcmd {}
         foreach opt $opts {
             set op [lindex $opt 0]
             set val [lindex $opt 4]

             # Empty val's except -fill can be stripped off.
             if {![string equal $op "-fill"] && ([string length $val] == 0)} {
                 continue
             }
             lappend opcmd $op $val
         }
         set co [$wcan coords $id]
         set cmd [concat "create" $type $co $opcmd]
         append xml "\t[can2svg $cmd]\n"        
     }
     puts $fd [makedocument $argsArr(-width) $argsArr(-height) $xml]
     close $fd
 }

 # ::can2svg::MakeXML --
 #
 #       Creates raw xml data from a hierarchical list of xml code.
 #       This proc gets called recursively for each child.
 #       It makes also internal entity replacements on character data.
 #       Mixed elements aren't treated correctly generally.
 #       
 # Arguments:
 #       xmlList     a list of xml code in the format described in the header.
 #       
 # Results:
 #       raw xml data.

 proc ::can2svg::MakeXML {xmlList} {

     # Extract the XML data items.
     foreach {tag attrlist isempty chdata childlist} $xmlList {}
     set rawxml "<$tag"
     foreach {attr value} $attrlist {
         append rawxml " ${attr}='${value}'"
     }
     if {$isempty} {
         append rawxml "/>"
         return $rawxml
     } else {
         append rawxml ">"
     }

     # Call ourselves recursively for each child element. 
     # There is an arbitrary choice here where childs are put before PCDATA.
     foreach child $childlist {
         append rawxml [MakeXML $child]
     }

     # Make standard entity replacements.
     if {[string length $chdata]} {
         append rawxml [XMLCrypt $chdata]
     }
     append rawxml "</$tag>"
     return $rawxml
 }

 # ::can2svg::MakeXMLList --
 #
 #       Build an element list given the tag and the args.
 #
 # Arguments:
 #       tagname:    the name of this element.
 #       args:       
 #           -empty   0|1      Is this an empty tag? If $chdata 
 #                             and $subtags are empty, then whether 
 #                             to make the tag empty or not is decided 
 #                             here. (default: 1)
 #            -attrlist {attr1 value1 attr2 value2 ..}   Vars is a list 
 #                             consisting of attr/value pairs, as shown.
 #            -chdata $chdata   ChData of tag (default: "").
 #            -subtags {$subchilds $subchilds ...} is a list containing xmldata
 #                             of $tagname's subtags. (default: no sub-tags)
 #       
 # Results:
 #       a list suitable for ::can2svg::MakeXML.

 proc ::can2svg::MakeXMLList {tagname args} {

     # Fill in the defaults.
     array set xmlarr {-isempty 1 -attrlist {} -chdata {} -subtags {}}

     # Override the defults with actual values.
     if {[llength $args] > 0} {
         array set xmlarr $args
     }
     if {!(($xmlarr(-chdata) == "") && ($xmlarr(-subtags) == ""))} {
         set xmlarr(-isempty) 0
     }

     # Build sub elements list.
     set sublist {}
     foreach child $xmlarr(-subtags) {
         lappend sublist $child
     }
     set xmlList [list $tagname $xmlarr(-attrlist) $xmlarr(-isempty)  \
       $xmlarr(-chdata) $sublist]
     return $xmlList
 }

 # ::can2svg::XMLCrypt --
 #
 #       Makes standard XML entity replacements.
 #
 # Arguments:
 #       chdata:     character data.
 #       
 # Results:
 #       chdata with XML standard entities replaced.

 proc ::can2svg::XMLCrypt {chdata} {

     foreach from {\& < > {"} {'}}   \
       to {{\&amp;} {\&lt;} {\&gt;} {\&quot;} {\&apos;}} {
         regsub -all $from $chdata $to chdata
     }        
     return $chdata
 }

 # ::can2svg::UriFromLocalFile --
 #
 #       Not foolproof!

 proc ::can2svg::UriFromLocalFile {path} {

     # Windows???  
     # TODO: Encoding?
     if {[string equal $::tcl_platform(platform) "windows"]} {

         # Trim the volume specifier.
         set vol_ {([A-Z]:/|[A-Z]:\\)}
         regexp "${vol_}+(.+)$" $path match x path
     }
     set pathList [split $path "/:\\"]
     set pathJoin [join $pathList /]

     # Any characters not 7bit encode as %xx hex???

     regsub -all -- " " $pathJoin "%20" pathJoin

     return file:///${pathJoin}
 }

 #-------------------------------------------------------------------------------

[ Category Package | Category Graphics | Category Broken Links ]