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