懒羊羊
2023-08-30 1ac2bc1590406d9babec036e154d8d08f34a6aa1
提交 | 用户 | 时间
1ac2bc 1 /**
2  * @license A class to parse color values
3  * @author Stoyan Stefanov <sstoo@gmail.com>
4  * @link   http://www.phpied.com/rgb-color-parser-in-javascript/
5  * Use it if you like it
6  *
7  */
8 function RGBColor(color_string)
9 {
10     this.ok = false;
11
12     // strip any leading #
13     if (color_string.charAt(0) == '#') { // remove # if any
14         color_string = color_string.substr(1,6);
15     }
16
17     color_string = color_string.replace(/ /g,'');
18     color_string = color_string.toLowerCase();
19
20     // before getting into regexps, try simple matches
21     // and overwrite the input
22     var simple_colors = {
23         aliceblue: 'f0f8ff',
24         antiquewhite: 'faebd7',
25         aqua: '00ffff',
26         aquamarine: '7fffd4',
27         azure: 'f0ffff',
28         beige: 'f5f5dc',
29         bisque: 'ffe4c4',
30         black: '000000',
31         blanchedalmond: 'ffebcd',
32         blue: '0000ff',
33         blueviolet: '8a2be2',
34         brown: 'a52a2a',
35         burlywood: 'deb887',
36         cadetblue: '5f9ea0',
37         chartreuse: '7fff00',
38         chocolate: 'd2691e',
39         coral: 'ff7f50',
40         cornflowerblue: '6495ed',
41         cornsilk: 'fff8dc',
42         crimson: 'dc143c',
43         cyan: '00ffff',
44         darkblue: '00008b',
45         darkcyan: '008b8b',
46         darkgoldenrod: 'b8860b',
47         darkgray: 'a9a9a9',
48         darkgreen: '006400',
49         darkkhaki: 'bdb76b',
50         darkmagenta: '8b008b',
51         darkolivegreen: '556b2f',
52         darkorange: 'ff8c00',
53         darkorchid: '9932cc',
54         darkred: '8b0000',
55         darksalmon: 'e9967a',
56         darkseagreen: '8fbc8f',
57         darkslateblue: '483d8b',
58         darkslategray: '2f4f4f',
59         darkturquoise: '00ced1',
60         darkviolet: '9400d3',
61         deeppink: 'ff1493',
62         deepskyblue: '00bfff',
63         dimgray: '696969',
64         dodgerblue: '1e90ff',
65         feldspar: 'd19275',
66         firebrick: 'b22222',
67         floralwhite: 'fffaf0',
68         forestgreen: '228b22',
69         fuchsia: 'ff00ff',
70         gainsboro: 'dcdcdc',
71         ghostwhite: 'f8f8ff',
72         gold: 'ffd700',
73         goldenrod: 'daa520',
74         gray: '808080',
75         green: '008000',
76         greenyellow: 'adff2f',
77         honeydew: 'f0fff0',
78         hotpink: 'ff69b4',
79         indianred : 'cd5c5c',
80         indigo : '4b0082',
81         ivory: 'fffff0',
82         khaki: 'f0e68c',
83         lavender: 'e6e6fa',
84         lavenderblush: 'fff0f5',
85         lawngreen: '7cfc00',
86         lemonchiffon: 'fffacd',
87         lightblue: 'add8e6',
88         lightcoral: 'f08080',
89         lightcyan: 'e0ffff',
90         lightgoldenrodyellow: 'fafad2',
91         lightgrey: 'd3d3d3',
92         lightgreen: '90ee90',
93         lightpink: 'ffb6c1',
94         lightsalmon: 'ffa07a',
95         lightseagreen: '20b2aa',
96         lightskyblue: '87cefa',
97         lightslateblue: '8470ff',
98         lightslategray: '778899',
99         lightsteelblue: 'b0c4de',
100         lightyellow: 'ffffe0',
101         lime: '00ff00',
102         limegreen: '32cd32',
103         linen: 'faf0e6',
104         magenta: 'ff00ff',
105         maroon: '800000',
106         mediumaquamarine: '66cdaa',
107         mediumblue: '0000cd',
108         mediumorchid: 'ba55d3',
109         mediumpurple: '9370d8',
110         mediumseagreen: '3cb371',
111         mediumslateblue: '7b68ee',
112         mediumspringgreen: '00fa9a',
113         mediumturquoise: '48d1cc',
114         mediumvioletred: 'c71585',
115         midnightblue: '191970',
116         mintcream: 'f5fffa',
117         mistyrose: 'ffe4e1',
118         moccasin: 'ffe4b5',
119         navajowhite: 'ffdead',
120         navy: '000080',
121         oldlace: 'fdf5e6',
122         olive: '808000',
123         olivedrab: '6b8e23',
124         orange: 'ffa500',
125         orangered: 'ff4500',
126         orchid: 'da70d6',
127         palegoldenrod: 'eee8aa',
128         palegreen: '98fb98',
129         paleturquoise: 'afeeee',
130         palevioletred: 'd87093',
131         papayawhip: 'ffefd5',
132         peachpuff: 'ffdab9',
133         peru: 'cd853f',
134         pink: 'ffc0cb',
135         plum: 'dda0dd',
136         powderblue: 'b0e0e6',
137         purple: '800080',
138         red: 'ff0000',
139         rosybrown: 'bc8f8f',
140         royalblue: '4169e1',
141         saddlebrown: '8b4513',
142         salmon: 'fa8072',
143         sandybrown: 'f4a460',
144         seagreen: '2e8b57',
145         seashell: 'fff5ee',
146         sienna: 'a0522d',
147         silver: 'c0c0c0',
148         skyblue: '87ceeb',
149         slateblue: '6a5acd',
150         slategray: '708090',
151         snow: 'fffafa',
152         springgreen: '00ff7f',
153         steelblue: '4682b4',
154         tan: 'd2b48c',
155         teal: '008080',
156         thistle: 'd8bfd8',
157         tomato: 'ff6347',
158         turquoise: '40e0d0',
159         violet: 'ee82ee',
160         violetred: 'd02090',
161         wheat: 'f5deb3',
162         white: 'ffffff',
163         whitesmoke: 'f5f5f5',
164         yellow: 'ffff00',
165         yellowgreen: '9acd32'
166     };
167     for (var key in simple_colors) {
168         if (color_string == key) {
169             color_string = simple_colors[key];
170         }
171     }
172     // emd of simple type-in colors
173
174     // array of color definition objects
175     var color_defs = [
176         {
177             re: /^rgb\((\d{1,3}),\s*(\d{1,3}),\s*(\d{1,3})\)$/,
178             example: ['rgb(123, 234, 45)', 'rgb(255,234,245)'],
179             process: function (bits){
180                 return [
181                     parseInt(bits[1]),
182                     parseInt(bits[2]),
183                     parseInt(bits[3])
184                 ];
185             }
186         },
187         {
188             re: /^(\w{2})(\w{2})(\w{2})$/,
189             example: ['#00ff00', '336699'],
190             process: function (bits){
191                 return [
192                     parseInt(bits[1], 16),
193                     parseInt(bits[2], 16),
194                     parseInt(bits[3], 16)
195                 ];
196             }
197         },
198         {
199             re: /^(\w{1})(\w{1})(\w{1})$/,
200             example: ['#fb0', 'f0f'],
201             process: function (bits){
202                 return [
203                     parseInt(bits[1] + bits[1], 16),
204                     parseInt(bits[2] + bits[2], 16),
205                     parseInt(bits[3] + bits[3], 16)
206                 ];
207             }
208         }
209     ];
210
211     // search through the definitions to find a match
212     for (var i = 0; i < color_defs.length; i++) {
213         var re = color_defs[i].re;
214         var processor = color_defs[i].process;
215         var bits = re.exec(color_string);
216         if (bits) {
217             channels = processor(bits);
218             this.r = channels[0];
219             this.g = channels[1];
220             this.b = channels[2];
221             this.ok = true;
222         }
223
224     }
225
226     // validate/cleanup values
227     this.r = (this.r < 0 || isNaN(this.r)) ? 0 : ((this.r > 255) ? 255 : this.r);
228     this.g = (this.g < 0 || isNaN(this.g)) ? 0 : ((this.g > 255) ? 255 : this.g);
229     this.b = (this.b < 0 || isNaN(this.b)) ? 0 : ((this.b > 255) ? 255 : this.b);
230
231     // some getters
232     this.toRGB = function () {
233         return 'rgb(' + this.r + ', ' + this.g + ', ' + this.b + ')';
234     }
235     this.toHex = function () {
236         var r = this.r.toString(16);
237         var g = this.g.toString(16);
238         var b = this.b.toString(16);
239         if (r.length == 1) r = '0' + r;
240         if (g.length == 1) g = '0' + g;
241         if (b.length == 1) b = '0' + b;
242         return '#' + r + g + b;
243     }
244
245     // help
246     this.getHelpXML = function () {
247
248         var examples = new Array();
249         // add regexps
250         for (var i = 0; i < color_defs.length; i++) {
251             var example = color_defs[i].example;
252             for (var j = 0; j < example.length; j++) {
253                 examples[examples.length] = example[j];
254             }
255         }
256         // add type-in colors
257         for (var sc in simple_colors) {
258             examples[examples.length] = sc;
259         }
260
261         var xml = document.createElement('ul');
262         xml.setAttribute('id', 'rgbcolor-examples');
263         for (var i = 0; i < examples.length; i++) {
264             try {
265                 var list_item = document.createElement('li');
266                 var list_color = new RGBColor(examples[i]);
267                 var example_div = document.createElement('div');
268                 example_div.style.cssText =
269                         'margin: 3px; '
270                         + 'border: 1px solid black; '
271                         + 'background:' + list_color.toHex() + '; '
272                         + 'color:' + list_color.toHex()
273                 ;
274                 example_div.appendChild(document.createTextNode('test'));
275                 var list_item_value = document.createTextNode(
276                     ' ' + examples[i] + ' -> ' + list_color.toRGB() + ' -> ' + list_color.toHex()
277                 );
278                 list_item.appendChild(example_div);
279                 list_item.appendChild(list_item_value);
280                 xml.appendChild(list_item);
281
282             } catch(e){}
283         }
284         return xml;
285
286     }
287
288 }
289
290 /**
291  * @license canvg.js - Javascript SVG parser and renderer on Canvas
292  * MIT Licensed 
293  * Gabe Lerner (gabelerner@gmail.com)
294  * http://code.google.com/p/canvg/
295  *
296  * Requires: rgbcolor.js - http://www.phpied.com/rgb-color-parser-in-javascript/
297  *
298  */
299 if(!window.console) {
300     window.console = {};
301     window.console.log = function(str) {};
302     window.console.dir = function(str) {};
303 }
304
305 if(!Array.prototype.indexOf){
306     Array.prototype.indexOf = function(obj){
307         for(var i=0; i<this.length; i++){
308             if(this[i]==obj){
309                 return i;
310             }
311         }
312         return -1;
313     }
314 }
315
316 (function(){
317     // canvg(target, s)
318     // empty parameters: replace all 'svg' elements on page with 'canvas' elements
319     // target: canvas element or the id of a canvas element
320     // s: svg string, url to svg file, or xml document
321     // opts: optional hash of options
322     //         ignoreMouse: true => ignore mouse events
323     //         ignoreAnimation: true => ignore animations
324     //         ignoreDimensions: true => does not try to resize canvas
325     //         ignoreClear: true => does not clear canvas
326     //         offsetX: int => draws at a x offset
327     //         offsetY: int => draws at a y offset
328     //         scaleWidth: int => scales horizontally to width
329     //         scaleHeight: int => scales vertically to height
330     //         renderCallback: function => will call the function after the first render is completed
331     //         forceRedraw: function => will call the function on every frame, if it returns true, will redraw
332     this.canvg = function (target, s, opts) {
333         // no parameters
334         if (target == null && s == null && opts == null) {
335             var svgTags = document.getElementsByTagName('svg');
336             for (var i=0; i<svgTags.length; i++) {
337                 var svgTag = svgTags[i];
338                 var c = document.createElement('canvas');
339                 c.width = svgTag.clientWidth;
340                 c.height = svgTag.clientHeight;
341                 svgTag.parentNode.insertBefore(c, svgTag);
342                 svgTag.parentNode.removeChild(svgTag);
343                 var div = document.createElement('div');
344                 div.appendChild(svgTag);
345                 canvg(c, div.innerHTML);
346             }
347             return;
348         }    
349         opts = opts || {};
350     
351         if (typeof target == 'string') {
352             target = document.getElementById(target);
353         }
354         
355         // reuse class per canvas
356         var svg;
357         if (target.svg == null) {
358             svg = build();
359             target.svg = svg;
360         }
361         else {
362             svg = target.svg;
363             svg.stop();
364         }
365         svg.opts = opts;
366         
367         var ctx = target.getContext('2d');
368         if (typeof(s.documentElement) != 'undefined') {
369             // load from xml doc
370             svg.loadXmlDoc(ctx, s);
371         }
372         else if (s.substr(0,1) == '<') {
373             // load from xml string
374             svg.loadXml(ctx, s);
375         }
376         else {
377             // load from url
378             svg.load(ctx, s);
379         }
380     }
381
382     function build() {
383         var svg = { };
384         
385         svg.FRAMERATE = 30;
386         svg.MAX_VIRTUAL_PIXELS = 30000;
387         
388         // globals
389         svg.init = function(ctx) {
390             svg.Definitions = {};
391             svg.Styles = {};
392             svg.Animations = [];
393             svg.Images = [];
394             svg.ctx = ctx;
395             svg.ViewPort = new (function () {
396                 this.viewPorts = [];
397                 this.Clear = function() { this.viewPorts = []; }
398                 this.SetCurrent = function(width, height) { this.viewPorts.push({ width: width, height: height }); }
399                 this.RemoveCurrent = function() { this.viewPorts.pop(); }
400                 this.Current = function() { return this.viewPorts[this.viewPorts.length - 1]; }
401                 this.width = function() { return this.Current().width; }
402                 this.height = function() { return this.Current().height; }
403                 this.ComputeSize = function(d) {
404                     if (d != null && typeof(d) == 'number') return d;
405                     if (d == 'x') return this.width();
406                     if (d == 'y') return this.height();
407                     return Math.sqrt(Math.pow(this.width(), 2) + Math.pow(this.height(), 2)) / Math.sqrt(2);            
408                 }
409             });
410         }
411         svg.init();
412         
413         // images loaded
414         svg.ImagesLoaded = function() { 
415             for (var i=0; i<svg.Images.length; i++) {
416                 if (!svg.Images[i].loaded) return false;
417             }
418             return true;
419         }
420
421         // trim
422         svg.trim = function(s) { return s.replace(/^\s+|\s+$/g, ''); }
423         
424         // compress spaces
425         svg.compressSpaces = function(s) { return s.replace(/[\s\r\t\n]+/gm,' '); }
426         
427         // ajax
428         svg.ajax = function(url) {
429             var AJAX;
430             if(window.XMLHttpRequest){AJAX=new XMLHttpRequest();}
431             else{AJAX=new ActiveXObject('Microsoft.XMLHTTP');}
432             if(AJAX){
433                AJAX.open('GET',url,false);
434                AJAX.send(null);
435                return AJAX.responseText;
436             }
437             return null;
438         } 
439         
440         // parse xml
441         svg.parseXml = function(xml) {
442             if (window.DOMParser)
443             {
444                 var parser = new DOMParser();
445                 return parser.parseFromString(xml, 'text/xml');
446             }
447             else 
448             {
449                 xml = xml.replace(/<!DOCTYPE svg[^>]*>/, '');
450                 var xmlDoc = new ActiveXObject('Microsoft.XMLDOM');
451                 xmlDoc.async = 'false';
452                 xmlDoc.loadXML(xml); 
453                 return xmlDoc;
454             }        
455         }
456         
457         svg.Property = function(name, value) {
458             this.name = name;
459             this.value = value;
460             
461             this.hasValue = function() {
462                 return (this.value != null && this.value !== '');
463             }
464                             
465             // return the numerical value of the property
466             this.numValue = function() {
467                 if (!this.hasValue()) return 0;
468                 
469                 var n = parseFloat(this.value);
470                 if ((this.value + '').match(/%$/)) {
471                     n = n / 100.0;
472                 }
473                 return n;
474             }
475             
476             this.valueOrDefault = function(def) {
477                 if (this.hasValue()) return this.value;
478                 return def;
479             }
480             
481             this.numValueOrDefault = function(def) {
482                 if (this.hasValue()) return this.numValue();
483                 return def;
484             }
485             
486             /* EXTENSIONS */
487             var that = this;
488             
489             // color extensions
490             this.Color = {
491                 // augment the current color value with the opacity
492                 addOpacity: function(opacity) {
493                     var newValue = that.value;
494                     if (opacity != null && opacity != '') {
495                         var color = new RGBColor(that.value);
496                         if (color.ok) {
497                             newValue = 'rgba(' + color.r + ', ' + color.g + ', ' + color.b + ', ' + opacity + ')';
498                         }
499                     }
500                     return new svg.Property(that.name, newValue);
501                 }
502             }
503             
504             // definition extensions
505             this.Definition = {
506                 // get the definition from the definitions table
507                 getDefinition: function() {
508                     var name = that.value.replace(/^(url\()?#([^\)]+)\)?$/, '$2');
509                     return svg.Definitions[name];
510                 },
511                 
512                 isUrl: function() {
513                     return that.value.indexOf('url(') == 0
514                 },
515                 
516                 getFillStyle: function(e) {
517                     var def = this.getDefinition();
518                     
519                     // gradient
520                     if (def != null && def.createGradient) {
521                         return def.createGradient(svg.ctx, e);
522                     }
523                     
524                     // pattern
525                     if (def != null && def.createPattern) {
526                         return def.createPattern(svg.ctx, e);
527                     }
528                     
529                     return null;
530                 }
531             }
532             
533             // length extensions
534             this.Length = {
535                 DPI: function(viewPort) {
536                     return 96.0; // TODO: compute?
537                 },
538                 
539                 EM: function(viewPort) {
540                     var em = 12;
541                     
542                     var fontSize = new svg.Property('fontSize', svg.Font.Parse(svg.ctx.font).fontSize);
543                     if (fontSize.hasValue()) em = fontSize.Length.toPixels(viewPort);
544                     
545                     return em;
546                 },
547             
548                 // get the length as pixels
549                 toPixels: function(viewPort) {
550                     if (!that.hasValue()) return 0;
551                     var s = that.value+'';
552                     if (s.match(/em$/)) return that.numValue() * this.EM(viewPort);
553                     if (s.match(/ex$/)) return that.numValue() * this.EM(viewPort) / 2.0;
554                     if (s.match(/px$/)) return that.numValue();
555                     if (s.match(/pt$/)) return that.numValue() * 1.25;
556                     if (s.match(/pc$/)) return that.numValue() * 15;
557                     if (s.match(/cm$/)) return that.numValue() * this.DPI(viewPort) / 2.54;
558                     if (s.match(/mm$/)) return that.numValue() * this.DPI(viewPort) / 25.4;
559                     if (s.match(/in$/)) return that.numValue() * this.DPI(viewPort);
560                     if (s.match(/%$/)) return that.numValue() * svg.ViewPort.ComputeSize(viewPort);
561                     return that.numValue();
562                 }
563             }
564             
565             // time extensions
566             this.Time = {
567                 // get the time as milliseconds
568                 toMilliseconds: function() {
569                     if (!that.hasValue()) return 0;
570                     var s = that.value+'';
571                     if (s.match(/s$/)) return that.numValue() * 1000;
572                     if (s.match(/ms$/)) return that.numValue();
573                     return that.numValue();
574                 }
575             }
576             
577             // angle extensions
578             this.Angle = {
579                 // get the angle as radians
580                 toRadians: function() {
581                     if (!that.hasValue()) return 0;
582                     var s = that.value+'';
583                     if (s.match(/deg$/)) return that.numValue() * (Math.PI / 180.0);
584                     if (s.match(/grad$/)) return that.numValue() * (Math.PI / 200.0);
585                     if (s.match(/rad$/)) return that.numValue();
586                     return that.numValue() * (Math.PI / 180.0);
587                 }
588             }
589         }
590         
591         // fonts
592         svg.Font = new (function() {
593             this.Styles = ['normal','italic','oblique','inherit'];
594             this.Variants = ['normal','small-caps','inherit'];
595             this.Weights = ['normal','bold','bolder','lighter','100','200','300','400','500','600','700','800','900','inherit'];
596             
597             this.CreateFont = function(fontStyle, fontVariant, fontWeight, fontSize, fontFamily, inherit) { 
598                 var f = inherit != null ? this.Parse(inherit) : this.CreateFont('', '', '', '', '', svg.ctx.font);
599                 return { 
600                     fontFamily: fontFamily || f.fontFamily, 
601                     fontSize: fontSize || f.fontSize, 
602                     fontStyle: fontStyle || f.fontStyle, 
603                     fontWeight: fontWeight || f.fontWeight, 
604                     fontVariant: fontVariant || f.fontVariant,
605                     toString: function () { return [this.fontStyle, this.fontVariant, this.fontWeight, this.fontSize, this.fontFamily].join(' ') } 
606                 } 
607             }
608             
609             var that = this;
610             this.Parse = function(s) {
611                 var f = {};
612                 var d = svg.trim(svg.compressSpaces(s || '')).split(' ');
613                 var set = { fontSize: false, fontStyle: false, fontWeight: false, fontVariant: false }
614                 var ff = '';
615                 for (var i=0; i<d.length; i++) {
616                     if (!set.fontStyle && that.Styles.indexOf(d[i]) != -1) { if (d[i] != 'inherit') f.fontStyle = d[i]; set.fontStyle = true; }
617                     else if (!set.fontVariant && that.Variants.indexOf(d[i]) != -1) { if (d[i] != 'inherit') f.fontVariant = d[i]; set.fontStyle = set.fontVariant = true;    }
618                     else if (!set.fontWeight && that.Weights.indexOf(d[i]) != -1) {    if (d[i] != 'inherit') f.fontWeight = d[i]; set.fontStyle = set.fontVariant = set.fontWeight = true; }
619                     else if (!set.fontSize) { if (d[i] != 'inherit') f.fontSize = d[i].split('/')[0]; set.fontStyle = set.fontVariant = set.fontWeight = set.fontSize = true; }
620                     else { if (d[i] != 'inherit') ff += d[i]; }
621                 } if (ff != '') f.fontFamily = ff;
622                 return f;
623             }
624         });
625         
626         // points and paths
627         svg.ToNumberArray = function(s) {
628             var a = svg.trim(svg.compressSpaces((s || '').replace(/,/g, ' '))).split(' ');
629             for (var i=0; i<a.length; i++) {
630                 a[i] = parseFloat(a[i]);
631             }
632             return a;
633         }        
634         svg.Point = function(x, y) {
635             this.x = x;
636             this.y = y;
637             
638             this.angleTo = function(p) {
639                 return Math.atan2(p.y - this.y, p.x - this.x);
640             }
641             
642             this.applyTransform = function(v) {
643                 var xp = this.x * v[0] + this.y * v[2] + v[4];
644                 var yp = this.x * v[1] + this.y * v[3] + v[5];
645                 this.x = xp;
646                 this.y = yp;
647             }
648         }
649         svg.CreatePoint = function(s) {
650             var a = svg.ToNumberArray(s);
651             return new svg.Point(a[0], a[1]);
652         }
653         svg.CreatePath = function(s) {
654             var a = svg.ToNumberArray(s);
655             var path = [];
656             for (var i=0; i<a.length; i+=2) {
657                 path.push(new svg.Point(a[i], a[i+1]));
658             }
659             return path;
660         }
661         
662         // bounding box
663         svg.BoundingBox = function(x1, y1, x2, y2) { // pass in initial points if you want
664             this.x1 = Number.NaN;
665             this.y1 = Number.NaN;
666             this.x2 = Number.NaN;
667             this.y2 = Number.NaN;
668             
669             this.x = function() { return this.x1; }
670             this.y = function() { return this.y1; }
671             this.width = function() { return this.x2 - this.x1; }
672             this.height = function() { return this.y2 - this.y1; }
673             
674             this.addPoint = function(x, y) {    
675                 if (x != null) {
676                     if (isNaN(this.x1) || isNaN(this.x2)) {
677                         this.x1 = x;
678                         this.x2 = x;
679                     }
680                     if (x < this.x1) this.x1 = x;
681                     if (x > this.x2) this.x2 = x;
682                 }
683             
684                 if (y != null) {
685                     if (isNaN(this.y1) || isNaN(this.y2)) {
686                         this.y1 = y;
687                         this.y2 = y;
688                     }
689                     if (y < this.y1) this.y1 = y;
690                     if (y > this.y2) this.y2 = y;
691                 }
692             }            
693             this.addX = function(x) { this.addPoint(x, null); }
694             this.addY = function(y) { this.addPoint(null, y); }
695             
696             this.addBoundingBox = function(bb) {
697                 this.addPoint(bb.x1, bb.y1);
698                 this.addPoint(bb.x2, bb.y2);
699             }
700             
701             this.addQuadraticCurve = function(p0x, p0y, p1x, p1y, p2x, p2y) {
702                 var cp1x = p0x + 2/3 * (p1x - p0x); // CP1 = QP0 + 2/3 *(QP1-QP0)
703                 var cp1y = p0y + 2/3 * (p1y - p0y); // CP1 = QP0 + 2/3 *(QP1-QP0)
704                 var cp2x = cp1x + 1/3 * (p2x - p0x); // CP2 = CP1 + 1/3 *(QP2-QP0)
705                 var cp2y = cp1y + 1/3 * (p2y - p0y); // CP2 = CP1 + 1/3 *(QP2-QP0)
706                 this.addBezierCurve(p0x, p0y, cp1x, cp2x, cp1y,    cp2y, p2x, p2y);
707             }
708             
709             this.addBezierCurve = function(p0x, p0y, p1x, p1y, p2x, p2y, p3x, p3y) {
710                 // from http://blog.hackers-cafe.net/2009/06/how-to-calculate-bezier-curves-bounding.html
711                 var p0 = [p0x, p0y], p1 = [p1x, p1y], p2 = [p2x, p2y], p3 = [p3x, p3y];
712                 this.addPoint(p0[0], p0[1]);
713                 this.addPoint(p3[0], p3[1]);
714                 
715                 for (i=0; i<=1; i++) {
716                     var f = function(t) { 
717                         return Math.pow(1-t, 3) * p0[i]
718                         + 3 * Math.pow(1-t, 2) * t * p1[i]
719                         + 3 * (1-t) * Math.pow(t, 2) * p2[i]
720                         + Math.pow(t, 3) * p3[i];
721                     }
722                     
723                     var b = 6 * p0[i] - 12 * p1[i] + 6 * p2[i];
724                     var a = -3 * p0[i] + 9 * p1[i] - 9 * p2[i] + 3 * p3[i];
725                     var c = 3 * p1[i] - 3 * p0[i];
726                     
727                     if (a == 0) {
728                         if (b == 0) continue;
729                         var t = -c / b;
730                         if (0 < t && t < 1) {
731                             if (i == 0) this.addX(f(t));
732                             if (i == 1) this.addY(f(t));
733                         }
734                         continue;
735                     }
736                     
737                     var b2ac = Math.pow(b, 2) - 4 * c * a;
738                     if (b2ac < 0) continue;
739                     var t1 = (-b + Math.sqrt(b2ac)) / (2 * a);
740                     if (0 < t1 && t1 < 1) {
741                         if (i == 0) this.addX(f(t1));
742                         if (i == 1) this.addY(f(t1));
743                     }
744                     var t2 = (-b - Math.sqrt(b2ac)) / (2 * a);
745                     if (0 < t2 && t2 < 1) {
746                         if (i == 0) this.addX(f(t2));
747                         if (i == 1) this.addY(f(t2));
748                     }
749                 }
750             }
751             
752             this.isPointInBox = function(x, y) {
753                 return (this.x1 <= x && x <= this.x2 && this.y1 <= y && y <= this.y2);
754             }
755             
756             this.addPoint(x1, y1);
757             this.addPoint(x2, y2);
758         }
759         
760         // transforms
761         svg.Transform = function(v) {    
762             var that = this;
763             this.Type = {}
764         
765             // translate
766             this.Type.translate = function(s) {
767                 this.p = svg.CreatePoint(s);            
768                 this.apply = function(ctx) {
769                     ctx.translate(this.p.x || 0.0, this.p.y || 0.0);
770                 }
771                 this.applyToPoint = function(p) {
772                     p.applyTransform([1, 0, 0, 1, this.p.x || 0.0, this.p.y || 0.0]);
773                 }
774             }
775             
776             // rotate
777             this.Type.rotate = function(s) {
778                 var a = svg.ToNumberArray(s);
779                 this.angle = new svg.Property('angle', a[0]);
780                 this.cx = a[1] || 0;
781                 this.cy = a[2] || 0;
782                 this.apply = function(ctx) {
783                     ctx.translate(this.cx, this.cy);
784                     ctx.rotate(this.angle.Angle.toRadians());
785                     ctx.translate(-this.cx, -this.cy);
786                 }
787                 this.applyToPoint = function(p) {
788                     var a = this.angle.Angle.toRadians();
789                     p.applyTransform([1, 0, 0, 1, this.p.x || 0.0, this.p.y || 0.0]);
790                     p.applyTransform([Math.cos(a), Math.sin(a), -Math.sin(a), Math.cos(a), 0, 0]);
791                     p.applyTransform([1, 0, 0, 1, -this.p.x || 0.0, -this.p.y || 0.0]);
792                 }            
793             }
794             
795             this.Type.scale = function(s) {
796                 this.p = svg.CreatePoint(s);
797                 this.apply = function(ctx) {
798                     ctx.scale(this.p.x || 1.0, this.p.y || this.p.x || 1.0);
799                 }
800                 this.applyToPoint = function(p) {
801                     p.applyTransform([this.p.x || 0.0, 0, 0, this.p.y || 0.0, 0, 0]);
802                 }                
803             }
804             
805             this.Type.matrix = function(s) {
806                 this.m = svg.ToNumberArray(s);
807                 this.apply = function(ctx) {
808                     ctx.transform(this.m[0], this.m[1], this.m[2], this.m[3], this.m[4], this.m[5]);
809                 }
810                 this.applyToPoint = function(p) {
811                     p.applyTransform(this.m);
812                 }                    
813             }
814             
815             this.Type.SkewBase = function(s) {
816                 this.base = that.Type.matrix;
817                 this.base(s);
818                 this.angle = new svg.Property('angle', s);
819             }
820             this.Type.SkewBase.prototype = new this.Type.matrix;
821             
822             this.Type.skewX = function(s) {
823                 this.base = that.Type.SkewBase;
824                 this.base(s);
825                 this.m = [1, 0, Math.tan(this.angle.Angle.toRadians()), 1, 0, 0];
826             }
827             this.Type.skewX.prototype = new this.Type.SkewBase;
828             
829             this.Type.skewY = function(s) {
830                 this.base = that.Type.SkewBase;
831                 this.base(s);
832                 this.m = [1, Math.tan(this.angle.Angle.toRadians()), 0, 1, 0, 0];
833             }
834             this.Type.skewY.prototype = new this.Type.SkewBase;
835         
836             this.transforms = [];
837             
838             this.apply = function(ctx) {
839                 for (var i=0; i<this.transforms.length; i++) {
840                     this.transforms[i].apply(ctx);
841                 }
842             }
843             
844             this.applyToPoint = function(p) {
845                 for (var i=0; i<this.transforms.length; i++) {
846                     this.transforms[i].applyToPoint(p);
847                 }
848             }
849             
850             var data = svg.trim(svg.compressSpaces(v)).split(/\s(?=[a-z])/);
851             for (var i=0; i<data.length; i++) {
852                 var type = data[i].split('(')[0];
853                 var s = data[i].split('(')[1].replace(')','');
854                 var transform = new this.Type[type](s);
855                 this.transforms.push(transform);
856             }
857         }
858         
859         // aspect ratio
860         svg.AspectRatio = function(ctx, aspectRatio, width, desiredWidth, height, desiredHeight, minX, minY, refX, refY) {
861             // aspect ratio - http://www.w3.org/TR/SVG/coords.html#PreserveAspectRatioAttribute
862             aspectRatio = svg.compressSpaces(aspectRatio);
863             aspectRatio = aspectRatio.replace(/^defer\s/,''); // ignore defer
864             var align = aspectRatio.split(' ')[0] || 'xMidYMid';
865             var meetOrSlice = aspectRatio.split(' ')[1] || 'meet';                    
866     
867             // calculate scale
868             var scaleX = width / desiredWidth;
869             var scaleY = height / desiredHeight;
870             var scaleMin = Math.min(scaleX, scaleY);
871             var scaleMax = Math.max(scaleX, scaleY);
872             if (meetOrSlice == 'meet') { desiredWidth *= scaleMin; desiredHeight *= scaleMin; }
873             if (meetOrSlice == 'slice') { desiredWidth *= scaleMax; desiredHeight *= scaleMax; }    
874             
875             refX = new svg.Property('refX', refX);
876             refY = new svg.Property('refY', refY);
877             if (refX.hasValue() && refY.hasValue()) {                
878                 ctx.translate(-scaleMin * refX.Length.toPixels('x'), -scaleMin * refY.Length.toPixels('y'));
879             } 
880             else {                    
881                 // align
882                 if (align.match(/^xMid/) && ((meetOrSlice == 'meet' && scaleMin == scaleY) || (meetOrSlice == 'slice' && scaleMax == scaleY))) ctx.translate(width / 2.0 - desiredWidth / 2.0, 0); 
883                 if (align.match(/YMid$/) && ((meetOrSlice == 'meet' && scaleMin == scaleX) || (meetOrSlice == 'slice' && scaleMax == scaleX))) ctx.translate(0, height / 2.0 - desiredHeight / 2.0); 
884                 if (align.match(/^xMax/) && ((meetOrSlice == 'meet' && scaleMin == scaleY) || (meetOrSlice == 'slice' && scaleMax == scaleY))) ctx.translate(width - desiredWidth, 0); 
885                 if (align.match(/YMax$/) && ((meetOrSlice == 'meet' && scaleMin == scaleX) || (meetOrSlice == 'slice' && scaleMax == scaleX))) ctx.translate(0, height - desiredHeight); 
886             }
887             
888             // scale
889             if (align == 'none') ctx.scale(scaleX, scaleY);
890             else if (meetOrSlice == 'meet') ctx.scale(scaleMin, scaleMin); 
891             else if (meetOrSlice == 'slice') ctx.scale(scaleMax, scaleMax);     
892             
893             // translate
894             ctx.translate(minX == null ? 0 : -minX, minY == null ? 0 : -minY);            
895         }
896         
897         // elements
898         svg.Element = {}
899         
900         svg.Element.ElementBase = function(node) {    
901             this.attributes = {};
902             this.styles = {};
903             this.children = [];
904             
905             // get or create attribute
906             this.attribute = function(name, createIfNotExists) {
907                 var a = this.attributes[name];
908                 if (a != null) return a;
909                             
910                 a = new svg.Property(name, '');
911                 if (createIfNotExists == true) this.attributes[name] = a;
912                 return a;
913             }
914             
915             // get or create style, crawls up node tree
916             this.style = function(name, createIfNotExists) {
917                 var s = this.styles[name];
918                 if (s != null) return s;
919                 
920                 var a = this.attribute(name);
921                 if (a != null && a.hasValue()) {
922                     return a;
923                 }
924                 
925                 var p = this.parent;
926                 if (p != null) {
927                     var ps = p.style(name);
928                     if (ps != null && ps.hasValue()) {
929                         return ps;
930                     }
931                 }
932                     
933                 s = new svg.Property(name, '');
934                 if (createIfNotExists == true) this.styles[name] = s;
935                 return s;
936             }
937             
938             // base render
939             this.render = function(ctx) {
940                 // don't render display=none
941                 if (this.style('display').value == 'none') return;
942                 
943                 // don't render visibility=hidden
944                 if (this.attribute('visibility').value == 'hidden') return;
945             
946                 ctx.save();
947                     this.setContext(ctx);
948                         // mask
949                         if (this.attribute('mask').hasValue()) {
950                             var mask = this.attribute('mask').Definition.getDefinition();
951                             if (mask != null) mask.apply(ctx, this);
952                         }
953                         else if (this.style('filter').hasValue()) {
954                             var filter = this.style('filter').Definition.getDefinition();
955                             if (filter != null) filter.apply(ctx, this);
956                         }
957                         else this.renderChildren(ctx);                
958                     this.clearContext(ctx);
959                 ctx.restore();
960             }
961             
962             // base set context
963             this.setContext = function(ctx) {
964                 // OVERRIDE ME!
965             }
966             
967             // base clear context
968             this.clearContext = function(ctx) {
969                 // OVERRIDE ME!
970             }            
971             
972             // base render children
973             this.renderChildren = function(ctx) {
974                 for (var i=0; i<this.children.length; i++) {
975                     this.children[i].render(ctx);
976                 }
977             }
978             
979             this.addChild = function(childNode, create) {
980                 var child = childNode;
981                 if (create) child = svg.CreateElement(childNode);
982                 child.parent = this;
983                 this.children.push(child);            
984             }
985                 
986             if (node != null && node.nodeType == 1) { //ELEMENT_NODE
987                 // add children
988                 for (var i=0; i<node.childNodes.length; i++) {
989                     var childNode = node.childNodes[i];
990                     if (childNode.nodeType == 1) this.addChild(childNode, true); //ELEMENT_NODE
991                 }
992                 
993                 // add attributes
994                 for (var i=0; i<node.attributes.length; i++) {
995                     var attribute = node.attributes[i];
996                     this.attributes[attribute.nodeName] = new svg.Property(attribute.nodeName, attribute.nodeValue);
997                 }
998                                         
999                 // add tag styles
1000                 var styles = svg.Styles[node.nodeName];
1001                 if (styles != null) {
1002                     for (var name in styles) {
1003                         this.styles[name] = styles[name];
1004                     }
1005                 }                    
1006                 
1007                 // add class styles
1008                 if (this.attribute('class').hasValue()) {
1009                     var classes = svg.compressSpaces(this.attribute('class').value).split(' ');
1010                     for (var j=0; j<classes.length; j++) {
1011                         styles = svg.Styles['.'+classes[j]];
1012                         if (styles != null) {
1013                             for (var name in styles) {
1014                                 this.styles[name] = styles[name];
1015                             }
1016                         }
1017                         styles = svg.Styles[node.nodeName+'.'+classes[j]];
1018                         if (styles != null) {
1019                             for (var name in styles) {
1020                                 this.styles[name] = styles[name];
1021                             }
1022                         }
1023                     }
1024                 }
1025                 
1026                 // add inline styles
1027                 if (this.attribute('style').hasValue()) {
1028                     var styles = this.attribute('style').value.split(';');
1029                     for (var i=0; i<styles.length; i++) {
1030                         if (svg.trim(styles[i]) != '') {
1031                             var style = styles[i].split(':');
1032                             var name = svg.trim(style[0]);
1033                             var value = svg.trim(style[1]);
1034                             this.styles[name] = new svg.Property(name, value);
1035                         }
1036                     }
1037                 }    
1038
1039                 // add id
1040                 if (this.attribute('id').hasValue()) {
1041                     if (svg.Definitions[this.attribute('id').value] == null) {
1042                         svg.Definitions[this.attribute('id').value] = this;
1043                     }
1044                 }
1045             }
1046         }
1047         
1048         svg.Element.RenderedElementBase = function(node) {
1049             this.base = svg.Element.ElementBase;
1050             this.base(node);
1051             
1052             this.setContext = function(ctx) {
1053                 // fill
1054                 if (this.style('fill').Definition.isUrl()) {
1055                     var fs = this.style('fill').Definition.getFillStyle(this);
1056                     if (fs != null) ctx.fillStyle = fs;
1057                 }
1058                 else if (this.style('fill').hasValue()) {
1059                     var fillStyle = this.style('fill');
1060                     if (this.style('fill-opacity').hasValue()) fillStyle = fillStyle.Color.addOpacity(this.style('fill-opacity').value);
1061                     ctx.fillStyle = (fillStyle.value == 'none' ? 'rgba(0,0,0,0)' : fillStyle.value);
1062                 }
1063                                     
1064                 // stroke
1065                 if (this.style('stroke').Definition.isUrl()) {
1066                     var fs = this.style('stroke').Definition.getFillStyle(this);
1067                     if (fs != null) ctx.strokeStyle = fs;
1068                 }
1069                 else if (this.style('stroke').hasValue()) {
1070                     var strokeStyle = this.style('stroke');
1071                     if (this.style('stroke-opacity').hasValue()) strokeStyle = strokeStyle.Color.addOpacity(this.style('stroke-opacity').value);
1072                     ctx.strokeStyle = (strokeStyle.value == 'none' ? 'rgba(0,0,0,0)' : strokeStyle.value);
1073                 }
1074                 if (this.style('stroke-width').hasValue()) ctx.lineWidth = this.style('stroke-width').Length.toPixels();
1075                 if (this.style('stroke-linecap').hasValue()) ctx.lineCap = this.style('stroke-linecap').value;
1076                 if (this.style('stroke-linejoin').hasValue()) ctx.lineJoin = this.style('stroke-linejoin').value;
1077                 if (this.style('stroke-miterlimit').hasValue()) ctx.miterLimit = this.style('stroke-miterlimit').value;
1078
1079                 // font
1080                 if (typeof(ctx.font) != 'undefined') {
1081                     ctx.font = svg.Font.CreateFont( 
1082                         this.style('font-style').value, 
1083                         this.style('font-variant').value, 
1084                         this.style('font-weight').value, 
1085                         this.style('font-size').hasValue() ? this.style('font-size').Length.toPixels() + 'px' : '', 
1086                         this.style('font-family').value).toString();
1087                 }
1088                 
1089                 // transform
1090                 if (this.attribute('transform').hasValue()) { 
1091                     var transform = new svg.Transform(this.attribute('transform').value);
1092                     transform.apply(ctx);
1093                 }
1094                 
1095                 // clip
1096                 if (this.attribute('clip-path').hasValue()) {
1097                     var clip = this.attribute('clip-path').Definition.getDefinition();
1098                     if (clip != null) clip.apply(ctx);
1099                 }
1100                 
1101                 // opacity
1102                 if (this.style('opacity').hasValue()) {
1103                     ctx.globalAlpha = this.style('opacity').numValue();
1104                 }
1105             }        
1106         }
1107         svg.Element.RenderedElementBase.prototype = new svg.Element.ElementBase;
1108         
1109         svg.Element.PathElementBase = function(node) {
1110             this.base = svg.Element.RenderedElementBase;
1111             this.base(node);
1112             
1113             this.path = function(ctx) {
1114                 if (ctx != null) ctx.beginPath();
1115                 return new svg.BoundingBox();
1116             }
1117             
1118             this.renderChildren = function(ctx) {
1119                 this.path(ctx);
1120                 svg.Mouse.checkPath(this, ctx);
1121                 if (ctx.fillStyle != '') ctx.fill();
1122                 if (ctx.strokeStyle != '') ctx.stroke();
1123                 
1124                 var markers = this.getMarkers();
1125                 if (markers != null) {
1126                     if (this.style('marker-start').Definition.isUrl()) {
1127                         var marker = this.style('marker-start').Definition.getDefinition();
1128                         marker.render(ctx, markers[0][0], markers[0][1]);
1129                     }
1130                     if (this.style('marker-mid').Definition.isUrl()) {
1131                         var marker = this.style('marker-mid').Definition.getDefinition();
1132                         for (var i=1;i<markers.length-1;i++) {
1133                             marker.render(ctx, markers[i][0], markers[i][1]);
1134                         }
1135                     }
1136                     if (this.style('marker-end').Definition.isUrl()) {
1137                         var marker = this.style('marker-end').Definition.getDefinition();
1138                         marker.render(ctx, markers[markers.length-1][0], markers[markers.length-1][1]);
1139                     }
1140                 }                    
1141             }
1142             
1143             this.getBoundingBox = function() {
1144                 return this.path();
1145             }
1146             
1147             this.getMarkers = function() {
1148                 return null;
1149             }
1150         }
1151         svg.Element.PathElementBase.prototype = new svg.Element.RenderedElementBase;
1152         
1153         // svg element
1154         svg.Element.svg = function(node) {
1155             this.base = svg.Element.RenderedElementBase;
1156             this.base(node);
1157             
1158             this.baseClearContext = this.clearContext;
1159             this.clearContext = function(ctx) {
1160                 this.baseClearContext(ctx);
1161                 svg.ViewPort.RemoveCurrent();
1162             }
1163             
1164             this.baseSetContext = this.setContext;
1165             this.setContext = function(ctx) {
1166                 // initial values
1167                 ctx.strokeStyle = 'rgba(0,0,0,0)';
1168                 ctx.lineCap = 'butt';
1169                 ctx.lineJoin = 'miter';
1170                 ctx.miterLimit = 4;            
1171             
1172                 this.baseSetContext(ctx);
1173                 
1174                 // create new view port
1175                 if (this.attribute('x').hasValue() && this.attribute('y').hasValue()) {
1176                     ctx.translate(this.attribute('x').Length.toPixels('x'), this.attribute('y').Length.toPixels('y'));
1177                 }
1178                 
1179                 var width = svg.ViewPort.width();
1180                 var height = svg.ViewPort.height();
1181                 if (typeof(this.root) == 'undefined' && this.attribute('width').hasValue() && this.attribute('height').hasValue()) {
1182                     width = this.attribute('width').Length.toPixels('x');
1183                     height = this.attribute('height').Length.toPixels('y');
1184                     
1185                     var x = 0;
1186                     var y = 0;
1187                     if (this.attribute('refX').hasValue() && this.attribute('refY').hasValue()) {
1188                         x = -this.attribute('refX').Length.toPixels('x');
1189                         y = -this.attribute('refY').Length.toPixels('y');
1190                     }
1191                     
1192                     ctx.beginPath();
1193                     ctx.moveTo(x, y);
1194                     ctx.lineTo(width, y);
1195                     ctx.lineTo(width, height);
1196                     ctx.lineTo(x, height);
1197                     ctx.closePath();
1198                     ctx.clip();
1199                 }
1200                 svg.ViewPort.SetCurrent(width, height);    
1201                         
1202                 // viewbox
1203                 if (this.attribute('viewBox').hasValue()) {                
1204                     var viewBox = svg.ToNumberArray(this.attribute('viewBox').value);
1205                     var minX = viewBox[0];
1206                     var minY = viewBox[1];
1207                     width = viewBox[2];
1208                     height = viewBox[3];
1209                     
1210                     svg.AspectRatio(ctx,
1211                                     this.attribute('preserveAspectRatio').value, 
1212                                     svg.ViewPort.width(), 
1213                                     width,
1214                                     svg.ViewPort.height(),
1215                                     height,
1216                                     minX,
1217                                     minY,
1218                                     this.attribute('refX').value,
1219                                     this.attribute('refY').value);
1220                                         
1221                     svg.ViewPort.RemoveCurrent();    
1222                     svg.ViewPort.SetCurrent(viewBox[2], viewBox[3]);                        
1223                 }                
1224             }
1225         }
1226         svg.Element.svg.prototype = new svg.Element.RenderedElementBase;
1227
1228         // rect element
1229         svg.Element.rect = function(node) {
1230             this.base = svg.Element.PathElementBase;
1231             this.base(node);
1232             
1233             this.path = function(ctx) {
1234                 var x = this.attribute('x').Length.toPixels('x');
1235                 var y = this.attribute('y').Length.toPixels('y');
1236                 var width = this.attribute('width').Length.toPixels('x');
1237                 var height = this.attribute('height').Length.toPixels('y');
1238                 var rx = this.attribute('rx').Length.toPixels('x');
1239                 var ry = this.attribute('ry').Length.toPixels('y');
1240                 if (this.attribute('rx').hasValue() && !this.attribute('ry').hasValue()) ry = rx;
1241                 if (this.attribute('ry').hasValue() && !this.attribute('rx').hasValue()) rx = ry;
1242                 
1243                 if (ctx != null) {
1244                     ctx.beginPath();
1245                     ctx.moveTo(x + rx, y);
1246                     ctx.lineTo(x + width - rx, y);
1247                     ctx.quadraticCurveTo(x + width, y, x + width, y + ry)
1248                     ctx.lineTo(x + width, y + height - ry);
1249                     ctx.quadraticCurveTo(x + width, y + height, x + width - rx, y + height)
1250                     ctx.lineTo(x + rx, y + height);
1251                     ctx.quadraticCurveTo(x, y + height, x, y + height - ry)
1252                     ctx.lineTo(x, y + ry);
1253                     ctx.quadraticCurveTo(x, y, x + rx, y)
1254                     ctx.closePath();
1255                 }
1256                 
1257                 return new svg.BoundingBox(x, y, x + width, y + height);
1258             }
1259         }
1260         svg.Element.rect.prototype = new svg.Element.PathElementBase;
1261         
1262         // circle element
1263         svg.Element.circle = function(node) {
1264             this.base = svg.Element.PathElementBase;
1265             this.base(node);
1266             
1267             this.path = function(ctx) {
1268                 var cx = this.attribute('cx').Length.toPixels('x');
1269                 var cy = this.attribute('cy').Length.toPixels('y');
1270                 var r = this.attribute('r').Length.toPixels();
1271             
1272                 if (ctx != null) {
1273                     ctx.beginPath();
1274                     ctx.arc(cx, cy, r, 0, Math.PI * 2, true); 
1275                     ctx.closePath();
1276                 }
1277                 
1278                 return new svg.BoundingBox(cx - r, cy - r, cx + r, cy + r);
1279             }
1280         }
1281         svg.Element.circle.prototype = new svg.Element.PathElementBase;    
1282
1283         // ellipse element
1284         svg.Element.ellipse = function(node) {
1285             this.base = svg.Element.PathElementBase;
1286             this.base(node);
1287             
1288             this.path = function(ctx) {
1289                 var KAPPA = 4 * ((Math.sqrt(2) - 1) / 3);
1290                 var rx = this.attribute('rx').Length.toPixels('x');
1291                 var ry = this.attribute('ry').Length.toPixels('y');
1292                 var cx = this.attribute('cx').Length.toPixels('x');
1293                 var cy = this.attribute('cy').Length.toPixels('y');
1294                 
1295                 if (ctx != null) {
1296                     ctx.beginPath();
1297                     ctx.moveTo(cx, cy - ry);
1298                     ctx.bezierCurveTo(cx + (KAPPA * rx), cy - ry,  cx + rx, cy - (KAPPA * ry), cx + rx, cy);
1299                     ctx.bezierCurveTo(cx + rx, cy + (KAPPA * ry), cx + (KAPPA * rx), cy + ry, cx, cy + ry);
1300                     ctx.bezierCurveTo(cx - (KAPPA * rx), cy + ry, cx - rx, cy + (KAPPA * ry), cx - rx, cy);
1301                     ctx.bezierCurveTo(cx - rx, cy - (KAPPA * ry), cx - (KAPPA * rx), cy - ry, cx, cy - ry);
1302                     ctx.closePath();
1303                 }
1304                 
1305                 return new svg.BoundingBox(cx - rx, cy - ry, cx + rx, cy + ry);
1306             }
1307         }
1308         svg.Element.ellipse.prototype = new svg.Element.PathElementBase;            
1309         
1310         // line element
1311         svg.Element.line = function(node) {
1312             this.base = svg.Element.PathElementBase;
1313             this.base(node);
1314             
1315             this.getPoints = function() {
1316                 return [
1317                     new svg.Point(this.attribute('x1').Length.toPixels('x'), this.attribute('y1').Length.toPixels('y')),
1318                     new svg.Point(this.attribute('x2').Length.toPixels('x'), this.attribute('y2').Length.toPixels('y'))];
1319             }
1320                                 
1321             this.path = function(ctx) {
1322                 var points = this.getPoints();
1323                 
1324                 if (ctx != null) {
1325                     ctx.beginPath();
1326                     ctx.moveTo(points[0].x, points[0].y);
1327                     ctx.lineTo(points[1].x, points[1].y);
1328                 }
1329                 
1330                 return new svg.BoundingBox(points[0].x, points[0].y, points[1].x, points[1].y);
1331             }
1332             
1333             this.getMarkers = function() {
1334                 var points = this.getPoints();    
1335                 var a = points[0].angleTo(points[1]);
1336                 return [[points[0], a], [points[1], a]];
1337             }
1338         }
1339         svg.Element.line.prototype = new svg.Element.PathElementBase;        
1340                 
1341         // polyline element
1342         svg.Element.polyline = function(node) {
1343             this.base = svg.Element.PathElementBase;
1344             this.base(node);
1345             
1346             this.points = svg.CreatePath(this.attribute('points').value);
1347             this.path = function(ctx) {
1348                 var bb = new svg.BoundingBox(this.points[0].x, this.points[0].y);
1349                 if (ctx != null) {
1350                     ctx.beginPath();
1351                     ctx.moveTo(this.points[0].x, this.points[0].y);
1352                 }
1353                 for (var i=1; i<this.points.length; i++) {
1354                     bb.addPoint(this.points[i].x, this.points[i].y);
1355                     if (ctx != null) ctx.lineTo(this.points[i].x, this.points[i].y);
1356                 }
1357                 return bb;
1358             }
1359             
1360             this.getMarkers = function() {
1361                 var markers = [];
1362                 for (var i=0; i<this.points.length - 1; i++) {
1363                     markers.push([this.points[i], this.points[i].angleTo(this.points[i+1])]);
1364                 }
1365                 markers.push([this.points[this.points.length-1], markers[markers.length-1][1]]);
1366                 return markers;
1367             }            
1368         }
1369         svg.Element.polyline.prototype = new svg.Element.PathElementBase;                
1370                 
1371         // polygon element
1372         svg.Element.polygon = function(node) {
1373             this.base = svg.Element.polyline;
1374             this.base(node);
1375             
1376             this.basePath = this.path;
1377             this.path = function(ctx) {
1378                 var bb = this.basePath(ctx);
1379                 if (ctx != null) {
1380                     ctx.lineTo(this.points[0].x, this.points[0].y);
1381                     ctx.closePath();
1382                 }
1383                 return bb;
1384             }
1385         }
1386         svg.Element.polygon.prototype = new svg.Element.polyline;
1387
1388         // path element
1389         svg.Element.path = function(node) {
1390             this.base = svg.Element.PathElementBase;
1391             this.base(node);
1392                     
1393             var d = this.attribute('d').value;
1394             // TODO: convert to real lexer based on http://www.w3.org/TR/SVG11/paths.html#PathDataBNF
1395             d = d.replace(/,/gm,' '); // get rid of all commas
1396             d = d.replace(/([MmZzLlHhVvCcSsQqTtAa])([MmZzLlHhVvCcSsQqTtAa])/gm,'$1 $2'); // separate commands from commands
1397             d = d.replace(/([MmZzLlHhVvCcSsQqTtAa])([MmZzLlHhVvCcSsQqTtAa])/gm,'$1 $2'); // separate commands from commands
1398             d = d.replace(/([MmZzLlHhVvCcSsQqTtAa])([^\s])/gm,'$1 $2'); // separate commands from points
1399             d = d.replace(/([^\s])([MmZzLlHhVvCcSsQqTtAa])/gm,'$1 $2'); // separate commands from points
1400             d = d.replace(/([0-9])([+\-])/gm,'$1 $2'); // separate digits when no comma
1401             d = d.replace(/(\.[0-9]*)(\.)/gm,'$1 $2'); // separate digits when no comma
1402             d = d.replace(/([Aa](\s+[0-9]+){3})\s+([01])\s*([01])/gm,'$1 $3 $4 '); // shorthand elliptical arc path syntax
1403             d = svg.compressSpaces(d); // compress multiple spaces
1404             d = svg.trim(d);
1405             this.PathParser = new (function(d) {
1406                 this.tokens = d.split(' ');
1407                 
1408                 this.reset = function() {
1409                     this.i = -1;
1410                     this.command = '';
1411                     this.previousCommand = '';
1412                     this.start = new svg.Point(0, 0);
1413                     this.control = new svg.Point(0, 0);
1414                     this.current = new svg.Point(0, 0);
1415                     this.points = [];
1416                     this.angles = [];
1417                 }
1418                                 
1419                 this.isEnd = function() {
1420                     return this.i >= this.tokens.length - 1;
1421                 }
1422                 
1423                 this.isCommandOrEnd = function() {
1424                     if (this.isEnd()) return true;
1425                     return this.tokens[this.i + 1].match(/^[A-Za-z]$/) != null;
1426                 }
1427                 
1428                 this.isRelativeCommand = function() {
1429                     return this.command == this.command.toLowerCase();
1430                 }
1431                             
1432                 this.getToken = function() {
1433                     this.i = this.i + 1;
1434                     return this.tokens[this.i];
1435                 }
1436                 
1437                 this.getScalar = function() {
1438                     return parseFloat(this.getToken());
1439                 }
1440                 
1441                 this.nextCommand = function() {
1442                     this.previousCommand = this.command;
1443                     this.command = this.getToken();
1444                 }                
1445                 
1446                 this.getPoint = function() {
1447                     var p = new svg.Point(this.getScalar(), this.getScalar());
1448                     return this.makeAbsolute(p);
1449                 }
1450                 
1451                 this.getAsControlPoint = function() {
1452                     var p = this.getPoint();
1453                     this.control = p;
1454                     return p;
1455                 }
1456                 
1457                 this.getAsCurrentPoint = function() {
1458                     var p = this.getPoint();
1459                     this.current = p;
1460                     return p;    
1461                 }
1462                 
1463                 this.getReflectedControlPoint = function() {
1464                     if (this.previousCommand.toLowerCase() != 'c' && this.previousCommand.toLowerCase() != 's') {
1465                         return this.current;
1466                     }
1467                     
1468                     // reflect point
1469                     var p = new svg.Point(2 * this.current.x - this.control.x, 2 * this.current.y - this.control.y);                    
1470                     return p;
1471                 }
1472                 
1473                 this.makeAbsolute = function(p) {
1474                     if (this.isRelativeCommand()) {
1475                         p.x = this.current.x + p.x;
1476                         p.y = this.current.y + p.y;
1477                     }
1478                     return p;
1479                 }
1480                 
1481                 this.addMarker = function(p, from, priorTo) {
1482                     // if the last angle isn't filled in because we didn't have this point yet ...
1483                     if (priorTo != null && this.angles.length > 0 && this.angles[this.angles.length-1] == null) {
1484                         this.angles[this.angles.length-1] = this.points[this.points.length-1].angleTo(priorTo);
1485                     }
1486                     this.addMarkerAngle(p, from == null ? null : from.angleTo(p));
1487                 }
1488                 
1489                 this.addMarkerAngle = function(p, a) {
1490                     this.points.push(p);
1491                     this.angles.push(a);
1492                 }                
1493                 
1494                 this.getMarkerPoints = function() { return this.points; }
1495                 this.getMarkerAngles = function() {
1496                     for (var i=0; i<this.angles.length; i++) {
1497                         if (this.angles[i] == null) {
1498                             for (var j=i+1; j<this.angles.length; j++) {
1499                                 if (this.angles[j] != null) {
1500                                     this.angles[i] = this.angles[j];
1501                                     break;
1502                                 }
1503                             }
1504                         }
1505                     }
1506                     return this.angles;
1507                 }
1508             })(d);
1509
1510             this.path = function(ctx) {
1511                 var pp = this.PathParser;
1512                 pp.reset();
1513
1514                 var bb = new svg.BoundingBox();
1515                 if (ctx != null) ctx.beginPath();
1516                 while (!pp.isEnd()) {
1517                     pp.nextCommand();
1518                     switch (pp.command.toUpperCase()) {
1519                     case 'M':
1520                         var p = pp.getAsCurrentPoint();
1521                         pp.addMarker(p);
1522                         bb.addPoint(p.x, p.y);
1523                         if (ctx != null) ctx.moveTo(p.x, p.y);
1524                         pp.start = pp.current;
1525                         while (!pp.isCommandOrEnd()) {
1526                             var p = pp.getAsCurrentPoint();
1527                             pp.addMarker(p, pp.start);
1528                             bb.addPoint(p.x, p.y);
1529                             if (ctx != null) ctx.lineTo(p.x, p.y);
1530                         }
1531                         break;
1532                     case 'L':
1533                         while (!pp.isCommandOrEnd()) {
1534                             var c = pp.current;
1535                             var p = pp.getAsCurrentPoint();
1536                             pp.addMarker(p, c);
1537                             bb.addPoint(p.x, p.y);
1538                             if (ctx != null) ctx.lineTo(p.x, p.y);
1539                         }
1540                         break;
1541                     case 'H':
1542                         while (!pp.isCommandOrEnd()) {
1543                             var newP = new svg.Point((pp.isRelativeCommand() ? pp.current.x : 0) + pp.getScalar(), pp.current.y);
1544                             pp.addMarker(newP, pp.current);
1545                             pp.current = newP;
1546                             bb.addPoint(pp.current.x, pp.current.y);
1547                             if (ctx != null) ctx.lineTo(pp.current.x, pp.current.y);
1548                         }
1549                         break;
1550                     case 'V':
1551                         while (!pp.isCommandOrEnd()) {
1552                             var newP = new svg.Point(pp.current.x, (pp.isRelativeCommand() ? pp.current.y : 0) + pp.getScalar());
1553                             pp.addMarker(newP, pp.current);
1554                             pp.current = newP;
1555                             bb.addPoint(pp.current.x, pp.current.y);
1556                             if (ctx != null) ctx.lineTo(pp.current.x, pp.current.y);
1557                         }
1558                         break;
1559                     case 'C':
1560                         while (!pp.isCommandOrEnd()) {
1561                             var curr = pp.current;
1562                             var p1 = pp.getPoint();
1563                             var cntrl = pp.getAsControlPoint();
1564                             var cp = pp.getAsCurrentPoint();
1565                             pp.addMarker(cp, cntrl, p1);
1566                             bb.addBezierCurve(curr.x, curr.y, p1.x, p1.y, cntrl.x, cntrl.y, cp.x, cp.y);
1567                             if (ctx != null) ctx.bezierCurveTo(p1.x, p1.y, cntrl.x, cntrl.y, cp.x, cp.y);
1568                         }
1569                         break;
1570                     case 'S':
1571                         while (!pp.isCommandOrEnd()) {
1572                             var curr = pp.current;
1573                             var p1 = pp.getReflectedControlPoint();
1574                             var cntrl = pp.getAsControlPoint();
1575                             var cp = pp.getAsCurrentPoint();
1576                             pp.addMarker(cp, cntrl, p1);
1577                             bb.addBezierCurve(curr.x, curr.y, p1.x, p1.y, cntrl.x, cntrl.y, cp.x, cp.y);
1578                             if (ctx != null) ctx.bezierCurveTo(p1.x, p1.y, cntrl.x, cntrl.y, cp.x, cp.y);
1579                         }
1580                         break;
1581                     case 'Q':
1582                         while (!pp.isCommandOrEnd()) {
1583                             var curr = pp.current;
1584                             var cntrl = pp.getAsControlPoint();
1585                             var cp = pp.getAsCurrentPoint();
1586                             pp.addMarker(cp, cntrl, cntrl);
1587                             bb.addQuadraticCurve(curr.x, curr.y, cntrl.x, cntrl.y, cp.x, cp.y);
1588                             if (ctx != null) ctx.quadraticCurveTo(cntrl.x, cntrl.y, cp.x, cp.y);
1589                         }
1590                         break;
1591                     case 'T':
1592                         while (!pp.isCommandOrEnd()) {
1593                             var curr = pp.current;
1594                             var cntrl = pp.getReflectedControlPoint();
1595                             pp.control = cntrl;
1596                             var cp = pp.getAsCurrentPoint();
1597                             pp.addMarker(cp, cntrl, cntrl);
1598                             bb.addQuadraticCurve(curr.x, curr.y, cntrl.x, cntrl.y, cp.x, cp.y);
1599                             if (ctx != null) ctx.quadraticCurveTo(cntrl.x, cntrl.y, cp.x, cp.y);
1600                         }
1601                         break;
1602                     case 'A':
1603                         while (!pp.isCommandOrEnd()) {
1604                             var curr = pp.current;
1605                             var rx = pp.getScalar();
1606                             var ry = pp.getScalar();
1607                             var xAxisRotation = pp.getScalar() * (Math.PI / 180.0);
1608                             var largeArcFlag = pp.getScalar();
1609                             var sweepFlag = pp.getScalar();
1610                             var cp = pp.getAsCurrentPoint();
1611
1612                             // Conversion from endpoint to center parameterization
1613                             // http://www.w3.org/TR/SVG11/implnote.html#ArcImplementationNotes
1614                             // x1', y1'
1615                             var currp = new svg.Point(
1616                                 Math.cos(xAxisRotation) * (curr.x - cp.x) / 2.0 + Math.sin(xAxisRotation) * (curr.y - cp.y) / 2.0,
1617                                 -Math.sin(xAxisRotation) * (curr.x - cp.x) / 2.0 + Math.cos(xAxisRotation) * (curr.y - cp.y) / 2.0
1618                             );
1619                             // adjust radii
1620                             var l = Math.pow(currp.x,2)/Math.pow(rx,2)+Math.pow(currp.y,2)/Math.pow(ry,2);
1621                             if (l > 1) {
1622                                 rx *= Math.sqrt(l);
1623                                 ry *= Math.sqrt(l);
1624                             }
1625                             // cx', cy'
1626                             var s = (largeArcFlag == sweepFlag ? -1 : 1) * Math.sqrt(
1627                                 ((Math.pow(rx,2)*Math.pow(ry,2))-(Math.pow(rx,2)*Math.pow(currp.y,2))-(Math.pow(ry,2)*Math.pow(currp.x,2))) /
1628                                 (Math.pow(rx,2)*Math.pow(currp.y,2)+Math.pow(ry,2)*Math.pow(currp.x,2))
1629                             );
1630                             if (isNaN(s)) s = 0;
1631                             var cpp = new svg.Point(s * rx * currp.y / ry, s * -ry * currp.x / rx);
1632                             // cx, cy
1633                             var centp = new svg.Point(
1634                                 (curr.x + cp.x) / 2.0 + Math.cos(xAxisRotation) * cpp.x - Math.sin(xAxisRotation) * cpp.y,
1635                                 (curr.y + cp.y) / 2.0 + Math.sin(xAxisRotation) * cpp.x + Math.cos(xAxisRotation) * cpp.y
1636                             );
1637                             // vector magnitude
1638                             var m = function(v) { return Math.sqrt(Math.pow(v[0],2) + Math.pow(v[1],2)); }
1639                             // ratio between two vectors
1640                             var r = function(u, v) { return (u[0]*v[0]+u[1]*v[1]) / (m(u)*m(v)) }
1641                             // angle between two vectors
1642                             var a = function(u, v) { return (u[0]*v[1] < u[1]*v[0] ? -1 : 1) * Math.acos(r(u,v)); }
1643                             // initial angle
1644                             var a1 = a([1,0], [(currp.x-cpp.x)/rx,(currp.y-cpp.y)/ry]);
1645                             // angle delta
1646                             var u = [(currp.x-cpp.x)/rx,(currp.y-cpp.y)/ry];
1647                             var v = [(-currp.x-cpp.x)/rx,(-currp.y-cpp.y)/ry];
1648                             var ad = a(u, v);
1649                             if (r(u,v) <= -1) ad = Math.PI;
1650                             if (r(u,v) >= 1) ad = 0;
1651
1652                             if (sweepFlag == 0 && ad > 0) ad = ad - 2 * Math.PI;
1653                             if (sweepFlag == 1 && ad < 0) ad = ad + 2 * Math.PI;
1654
1655                             // for markers
1656                             var halfWay = new svg.Point(
1657                                 centp.x - rx * Math.cos((a1 + ad) / 2),
1658                                 centp.y - ry * Math.sin((a1 + ad) / 2)
1659                             );
1660                             pp.addMarkerAngle(halfWay, (a1 + ad) / 2 + (sweepFlag == 0 ? 1 : -1) * Math.PI / 2);
1661                             pp.addMarkerAngle(cp, ad + (sweepFlag == 0 ? 1 : -1) * Math.PI / 2);
1662
1663                             bb.addPoint(cp.x, cp.y); // TODO: this is too naive, make it better
1664                             if (ctx != null) {
1665                                 var r = rx > ry ? rx : ry;
1666                                 var sx = rx > ry ? 1 : rx / ry;
1667                                 var sy = rx > ry ? ry / rx : 1;
1668
1669                                 ctx.translate(centp.x, centp.y);
1670                                 ctx.rotate(xAxisRotation);
1671                                 ctx.scale(sx, sy);
1672                                 ctx.arc(0, 0, r, a1, a1 + ad, 1 - sweepFlag);
1673                                 ctx.scale(1/sx, 1/sy);
1674                                 ctx.rotate(-xAxisRotation);
1675                                 ctx.translate(-centp.x, -centp.y);
1676                             }
1677                         }
1678                         break;
1679                     case 'Z':
1680                         if (ctx != null) ctx.closePath();
1681                         pp.current = pp.start;
1682                     }
1683                 }
1684
1685                 return bb;
1686             }
1687
1688             this.getMarkers = function() {
1689                 var points = this.PathParser.getMarkerPoints();
1690                 var angles = this.PathParser.getMarkerAngles();
1691                 
1692                 var markers = [];
1693                 for (var i=0; i<points.length; i++) {
1694                     markers.push([points[i], angles[i]]);
1695                 }
1696                 return markers;
1697             }
1698         }
1699         svg.Element.path.prototype = new svg.Element.PathElementBase;
1700         
1701         // pattern element
1702         svg.Element.pattern = function(node) {
1703             this.base = svg.Element.ElementBase;
1704             this.base(node);
1705             
1706             this.createPattern = function(ctx, element) {
1707                 // render me using a temporary svg element
1708                 var tempSvg = new svg.Element.svg();
1709                 tempSvg.attributes['viewBox'] = new svg.Property('viewBox', this.attribute('viewBox').value);
1710                 tempSvg.attributes['x'] = new svg.Property('x', this.attribute('x').value);
1711                 tempSvg.attributes['y'] = new svg.Property('y', this.attribute('y').value);
1712                 tempSvg.attributes['width'] = new svg.Property('width', this.attribute('width').value);
1713                 tempSvg.attributes['height'] = new svg.Property('height', this.attribute('height').value);
1714                 tempSvg.children = this.children;
1715                 
1716                 var c = document.createElement('canvas');
1717                 c.width = this.attribute('width').Length.toPixels('x');
1718                 c.height = this.attribute('height').Length.toPixels('y');
1719                 tempSvg.render(c.getContext('2d'));        
1720                 return ctx.createPattern(c, 'repeat');
1721             }
1722         }
1723         svg.Element.pattern.prototype = new svg.Element.ElementBase;
1724         
1725         // marker element
1726         svg.Element.marker = function(node) {
1727             this.base = svg.Element.ElementBase;
1728             this.base(node);
1729             
1730             this.baseRender = this.render;
1731             this.render = function(ctx, point, angle) {
1732                 ctx.translate(point.x, point.y);
1733                 if (this.attribute('orient').valueOrDefault('auto') == 'auto') ctx.rotate(angle);
1734                 if (this.attribute('markerUnits').valueOrDefault('strokeWidth') == 'strokeWidth') ctx.scale(ctx.lineWidth, ctx.lineWidth);
1735                 ctx.save();
1736                             
1737                 // render me using a temporary svg element
1738                 var tempSvg = new svg.Element.svg();
1739                 tempSvg.attributes['viewBox'] = new svg.Property('viewBox', this.attribute('viewBox').value);
1740                 tempSvg.attributes['refX'] = new svg.Property('refX', this.attribute('refX').value);
1741                 tempSvg.attributes['refY'] = new svg.Property('refY', this.attribute('refY').value);
1742                 tempSvg.attributes['width'] = new svg.Property('width', this.attribute('markerWidth').value);
1743                 tempSvg.attributes['height'] = new svg.Property('height', this.attribute('markerHeight').value);
1744                 tempSvg.attributes['fill'] = new svg.Property('fill', this.attribute('fill').valueOrDefault('black'));
1745                 tempSvg.attributes['stroke'] = new svg.Property('stroke', this.attribute('stroke').valueOrDefault('none'));
1746                 tempSvg.children = this.children;
1747                 tempSvg.render(ctx);
1748                 
1749                 ctx.restore();
1750                 if (this.attribute('markerUnits').valueOrDefault('strokeWidth') == 'strokeWidth') ctx.scale(1/ctx.lineWidth, 1/ctx.lineWidth);
1751                 if (this.attribute('orient').valueOrDefault('auto') == 'auto') ctx.rotate(-angle);
1752                 ctx.translate(-point.x, -point.y);
1753             }
1754         }
1755         svg.Element.marker.prototype = new svg.Element.ElementBase;
1756         
1757         // definitions element
1758         svg.Element.defs = function(node) {
1759             this.base = svg.Element.ElementBase;
1760             this.base(node);    
1761             
1762             this.render = function(ctx) {
1763                 // NOOP
1764             }
1765         }
1766         svg.Element.defs.prototype = new svg.Element.ElementBase;
1767         
1768         // base for gradients
1769         svg.Element.GradientBase = function(node) {
1770             this.base = svg.Element.ElementBase;
1771             this.base(node);
1772             
1773             this.gradientUnits = this.attribute('gradientUnits').valueOrDefault('objectBoundingBox');
1774             
1775             this.stops = [];            
1776             for (var i=0; i<this.children.length; i++) {
1777                 var child = this.children[i];
1778                 this.stops.push(child);
1779             }    
1780             
1781             this.getGradient = function() {
1782                 // OVERRIDE ME!
1783             }            
1784
1785             this.createGradient = function(ctx, element) {
1786                 var stopsContainer = this;
1787                 if (this.attribute('xlink:href').hasValue()) {
1788                     stopsContainer = this.attribute('xlink:href').Definition.getDefinition();
1789                 }
1790             
1791                 var g = this.getGradient(ctx, element);
1792                 for (var i=0; i<stopsContainer.stops.length; i++) {
1793                     g.addColorStop(stopsContainer.stops[i].offset, stopsContainer.stops[i].color);
1794                 }
1795                 
1796                 if (this.attribute('gradientTransform').hasValue()) {
1797                     // render as transformed pattern on temporary canvas
1798                     var rootView = svg.ViewPort.viewPorts[0];
1799                     
1800                     var rect = new svg.Element.rect();
1801                     rect.attributes['x'] = new svg.Property('x', -svg.MAX_VIRTUAL_PIXELS/3.0);
1802                     rect.attributes['y'] = new svg.Property('y', -svg.MAX_VIRTUAL_PIXELS/3.0);
1803                     rect.attributes['width'] = new svg.Property('width', svg.MAX_VIRTUAL_PIXELS);
1804                     rect.attributes['height'] = new svg.Property('height', svg.MAX_VIRTUAL_PIXELS);
1805                     
1806                     var group = new svg.Element.g();
1807                     group.attributes['transform'] = new svg.Property('transform', this.attribute('gradientTransform').value);
1808                     group.children = [ rect ];
1809                     
1810                     var tempSvg = new svg.Element.svg();
1811                     tempSvg.attributes['x'] = new svg.Property('x', 0);
1812                     tempSvg.attributes['y'] = new svg.Property('y', 0);
1813                     tempSvg.attributes['width'] = new svg.Property('width', rootView.width);
1814                     tempSvg.attributes['height'] = new svg.Property('height', rootView.height);
1815                     tempSvg.children = [ group ];
1816                     
1817                     var c = document.createElement('canvas');
1818                     c.width = rootView.width;
1819                     c.height = rootView.height;
1820                     var tempCtx = c.getContext('2d');
1821                     tempCtx.fillStyle = g;
1822                     tempSvg.render(tempCtx);        
1823                     return tempCtx.createPattern(c, 'no-repeat');
1824                 }
1825                 
1826                 return g;                
1827             }
1828         }
1829         svg.Element.GradientBase.prototype = new svg.Element.ElementBase;
1830         
1831         // linear gradient element
1832         svg.Element.linearGradient = function(node) {
1833             this.base = svg.Element.GradientBase;
1834             this.base(node);
1835             
1836             this.getGradient = function(ctx, element) {
1837                 var bb = element.getBoundingBox();
1838                 
1839                 var x1 = (this.gradientUnits == 'objectBoundingBox' 
1840                     ? bb.x() + bb.width() * this.attribute('x1').numValue() 
1841                     : this.attribute('x1').Length.toPixels('x'));
1842                 var y1 = (this.gradientUnits == 'objectBoundingBox' 
1843                     ? bb.y() + bb.height() * this.attribute('y1').numValue()
1844                     : this.attribute('y1').Length.toPixels('y'));
1845                 var x2 = (this.gradientUnits == 'objectBoundingBox' 
1846                     ? bb.x() + bb.width() * this.attribute('x2').numValue()
1847                     : this.attribute('x2').Length.toPixels('x'));
1848                 var y2 = (this.gradientUnits == 'objectBoundingBox' 
1849                     ? bb.y() + bb.height() * this.attribute('y2').numValue()
1850                     : this.attribute('y2').Length.toPixels('y'));
1851
1852                 return ctx.createLinearGradient(x1, y1, x2, y2);
1853             }
1854         }
1855         svg.Element.linearGradient.prototype = new svg.Element.GradientBase;
1856         
1857         // radial gradient element
1858         svg.Element.radialGradient = function(node) {
1859             this.base = svg.Element.GradientBase;
1860             this.base(node);
1861             
1862             this.getGradient = function(ctx, element) {
1863                 var bb = element.getBoundingBox();
1864                 
1865                 var cx = (this.gradientUnits == 'objectBoundingBox' 
1866                     ? bb.x() + bb.width() * this.attribute('cx').numValue() 
1867                     : this.attribute('cx').Length.toPixels('x'));
1868                 var cy = (this.gradientUnits == 'objectBoundingBox' 
1869                     ? bb.y() + bb.height() * this.attribute('cy').numValue() 
1870                     : this.attribute('cy').Length.toPixels('y'));
1871                 
1872                 var fx = cx;
1873                 var fy = cy;
1874                 if (this.attribute('fx').hasValue()) {
1875                     fx = (this.gradientUnits == 'objectBoundingBox' 
1876                     ? bb.x() + bb.width() * this.attribute('fx').numValue() 
1877                     : this.attribute('fx').Length.toPixels('x'));
1878                 }
1879                 if (this.attribute('fy').hasValue()) {
1880                     fy = (this.gradientUnits == 'objectBoundingBox' 
1881                     ? bb.y() + bb.height() * this.attribute('fy').numValue() 
1882                     : this.attribute('fy').Length.toPixels('y'));
1883                 }
1884                 
1885                 var r = (this.gradientUnits == 'objectBoundingBox' 
1886                     ? (bb.width() + bb.height()) / 2.0 * this.attribute('r').numValue()
1887                     : this.attribute('r').Length.toPixels());
1888                 
1889                 return ctx.createRadialGradient(fx, fy, 0, cx, cy, r);
1890             }
1891         }
1892         svg.Element.radialGradient.prototype = new svg.Element.GradientBase;
1893         
1894         // gradient stop element
1895         svg.Element.stop = function(node) {
1896             this.base = svg.Element.ElementBase;
1897             this.base(node);
1898             
1899             this.offset = this.attribute('offset').numValue();
1900             
1901             var stopColor = this.style('stop-color');
1902             if (this.style('stop-opacity').hasValue()) stopColor = stopColor.Color.addOpacity(this.style('stop-opacity').value);
1903             this.color = stopColor.value;
1904         }
1905         svg.Element.stop.prototype = new svg.Element.ElementBase;
1906         
1907         // animation base element
1908         svg.Element.AnimateBase = function(node) {
1909             this.base = svg.Element.ElementBase;
1910             this.base(node);
1911             
1912             svg.Animations.push(this);
1913             
1914             this.duration = 0.0;
1915             this.begin = this.attribute('begin').Time.toMilliseconds();
1916             this.maxDuration = this.begin + this.attribute('dur').Time.toMilliseconds();
1917             
1918             this.getProperty = function() {
1919                 var attributeType = this.attribute('attributeType').value;
1920                 var attributeName = this.attribute('attributeName').value;
1921                 
1922                 if (attributeType == 'CSS') {
1923                     return this.parent.style(attributeName, true);
1924                 }
1925                 return this.parent.attribute(attributeName, true);            
1926             };
1927             
1928             this.initialValue = null;
1929             this.removed = false;            
1930
1931             this.calcValue = function() {
1932                 // OVERRIDE ME!
1933                 return '';
1934             }
1935             
1936             this.update = function(delta) {    
1937                 // set initial value
1938                 if (this.initialValue == null) {
1939                     this.initialValue = this.getProperty().value;
1940                 }
1941             
1942                 // if we're past the end time
1943                 if (this.duration > this.maxDuration) {
1944                     // loop for indefinitely repeating animations
1945                     if (this.attribute('repeatCount').value == 'indefinite') {
1946                         this.duration = 0.0
1947                     }
1948                     else if (this.attribute('fill').valueOrDefault('remove') == 'remove' && !this.removed) {
1949                         this.removed = true;
1950                         this.getProperty().value = this.initialValue;
1951                         return true;
1952                     }
1953                     else {
1954                         return false; // no updates made
1955                     }
1956                 }            
1957                 this.duration = this.duration + delta;
1958             
1959                 // if we're past the begin time
1960                 var updated = false;
1961                 if (this.begin < this.duration) {
1962                     var newValue = this.calcValue(); // tween
1963                     
1964                     if (this.attribute('type').hasValue()) {
1965                         // for transform, etc.
1966                         var type = this.attribute('type').value;
1967                         newValue = type + '(' + newValue + ')';
1968                     }
1969                     
1970                     this.getProperty().value = newValue;
1971                     updated = true;
1972                 }
1973                 
1974                 return updated;
1975             }
1976             
1977             // fraction of duration we've covered
1978             this.progress = function() {
1979                 return ((this.duration - this.begin) / (this.maxDuration - this.begin));
1980             }            
1981         }
1982         svg.Element.AnimateBase.prototype = new svg.Element.ElementBase;
1983         
1984         // animate element
1985         svg.Element.animate = function(node) {
1986             this.base = svg.Element.AnimateBase;
1987             this.base(node);
1988             
1989             this.calcValue = function() {
1990                 var from = this.attribute('from').numValue();
1991                 var to = this.attribute('to').numValue();
1992                 
1993                 // tween value linearly
1994                 return from + (to - from) * this.progress(); 
1995             };
1996         }
1997         svg.Element.animate.prototype = new svg.Element.AnimateBase;
1998             
1999         // animate color element
2000         svg.Element.animateColor = function(node) {
2001             this.base = svg.Element.AnimateBase;
2002             this.base(node);
2003
2004             this.calcValue = function() {
2005                 var from = new RGBColor(this.attribute('from').value);
2006                 var to = new RGBColor(this.attribute('to').value);
2007                 
2008                 if (from.ok && to.ok) {
2009                     // tween color linearly
2010                     var r = from.r + (to.r - from.r) * this.progress();
2011                     var g = from.g + (to.g - from.g) * this.progress();
2012                     var b = from.b + (to.b - from.b) * this.progress();
2013                     return 'rgb('+parseInt(r,10)+','+parseInt(g,10)+','+parseInt(b,10)+')';
2014                 }
2015                 return this.attribute('from').value;
2016             };
2017         }
2018         svg.Element.animateColor.prototype = new svg.Element.AnimateBase;
2019         
2020         // animate transform element
2021         svg.Element.animateTransform = function(node) {
2022             this.base = svg.Element.animate;
2023             this.base(node);
2024         }
2025         svg.Element.animateTransform.prototype = new svg.Element.animate;
2026         
2027         // font element
2028         svg.Element.font = function(node) {
2029             this.base = svg.Element.ElementBase;
2030             this.base(node);
2031
2032             this.horizAdvX = this.attribute('horiz-adv-x').numValue();            
2033             
2034             this.isRTL = false;
2035             this.isArabic = false;
2036             this.fontFace = null;
2037             this.missingGlyph = null;
2038             this.glyphs = [];            
2039             for (var i=0; i<this.children.length; i++) {
2040                 var child = this.children[i];
2041                 if (child.type == 'font-face') {
2042                     this.fontFace = child;
2043                     if (child.style('font-family').hasValue()) {
2044                         svg.Definitions[child.style('font-family').value] = this;
2045                     }
2046                 }
2047                 else if (child.type == 'missing-glyph') this.missingGlyph = child;
2048                 else if (child.type == 'glyph') {
2049                     if (child.arabicForm != '') {
2050                         this.isRTL = true;
2051                         this.isArabic = true;
2052                         if (typeof(this.glyphs[child.unicode]) == 'undefined') this.glyphs[child.unicode] = [];
2053                         this.glyphs[child.unicode][child.arabicForm] = child;
2054                     }
2055                     else {
2056                         this.glyphs[child.unicode] = child;
2057                     }
2058                 }
2059             }    
2060         }
2061         svg.Element.font.prototype = new svg.Element.ElementBase;
2062         
2063         // font-face element
2064         svg.Element.fontface = function(node) {
2065             this.base = svg.Element.ElementBase;
2066             this.base(node);    
2067             
2068             this.ascent = this.attribute('ascent').value;
2069             this.descent = this.attribute('descent').value;
2070             this.unitsPerEm = this.attribute('units-per-em').numValue();                
2071         }
2072         svg.Element.fontface.prototype = new svg.Element.ElementBase;
2073         
2074         // missing-glyph element
2075         svg.Element.missingglyph = function(node) {
2076             this.base = svg.Element.path;
2077             this.base(node);    
2078             
2079             this.horizAdvX = 0;
2080         }
2081         svg.Element.missingglyph.prototype = new svg.Element.path;
2082         
2083         // glyph element
2084         svg.Element.glyph = function(node) {
2085             this.base = svg.Element.path;
2086             this.base(node);    
2087             
2088             this.horizAdvX = this.attribute('horiz-adv-x').numValue();
2089             this.unicode = this.attribute('unicode').value;
2090             this.arabicForm = this.attribute('arabic-form').value;
2091         }
2092         svg.Element.glyph.prototype = new svg.Element.path;
2093         
2094         // text element
2095         svg.Element.text = function(node) {
2096             this.base = svg.Element.RenderedElementBase;
2097             this.base(node);
2098             
2099             if (node != null) {
2100                 // add children
2101                 this.children = [];
2102                 for (var i=0; i<node.childNodes.length; i++) {
2103                     var childNode = node.childNodes[i];
2104                     if (childNode.nodeType == 1) { // capture tspan and tref nodes
2105                         this.addChild(childNode, true);
2106                     }
2107                     else if (childNode.nodeType == 3) { // capture text
2108                         this.addChild(new svg.Element.tspan(childNode), false);
2109                     }
2110                 }
2111             }
2112             
2113             this.baseSetContext = this.setContext;
2114             this.setContext = function(ctx) {
2115                 this.baseSetContext(ctx);
2116                 if (this.style('dominant-baseline').hasValue()) ctx.textBaseline = this.style('dominant-baseline').value;
2117                 if (this.style('alignment-baseline').hasValue()) ctx.textBaseline = this.style('alignment-baseline').value;
2118             }
2119             
2120             this.renderChildren = function(ctx) {
2121                 var textAnchor = this.style('text-anchor').valueOrDefault('start');
2122                 var x = this.attribute('x').Length.toPixels('x');
2123                 var y = this.attribute('y').Length.toPixels('y');
2124                 for (var i=0; i<this.children.length; i++) {
2125                     var child = this.children[i];
2126                 
2127                     if (child.attribute('x').hasValue()) {
2128                         child.x = child.attribute('x').Length.toPixels('x');
2129                     }
2130                     else {
2131                         if (child.attribute('dx').hasValue()) x += child.attribute('dx').Length.toPixels('x');
2132                         child.x = x;
2133                     }
2134                     
2135                     var childLength = child.measureText(ctx);
2136                     if (textAnchor != 'start' && (i==0 || child.attribute('x').hasValue())) { // new group?
2137                         // loop through rest of children
2138                         var groupLength = childLength;
2139                         for (var j=i+1; j<this.children.length; j++) {
2140                             var childInGroup = this.children[j];
2141                             if (childInGroup.attribute('x').hasValue()) break; // new group
2142                             groupLength += childInGroup.measureText(ctx);
2143                         }
2144                         child.x -= (textAnchor == 'end' ? groupLength : groupLength / 2.0);
2145                     }
2146                     x = child.x + childLength;
2147                     
2148                     if (child.attribute('y').hasValue()) {
2149                         child.y = child.attribute('y').Length.toPixels('y');
2150                     }
2151                     else {
2152                         if (child.attribute('dy').hasValue()) y += child.attribute('dy').Length.toPixels('y');
2153                         child.y = y;
2154                     }    
2155                     y = child.y;
2156                     
2157                     child.render(ctx);
2158                 }
2159             }
2160         }
2161         svg.Element.text.prototype = new svg.Element.RenderedElementBase;
2162         
2163         // text base
2164         svg.Element.TextElementBase = function(node) {
2165             this.base = svg.Element.RenderedElementBase;
2166             this.base(node);
2167             
2168             this.getGlyph = function(font, text, i) {
2169                 var c = text[i];
2170                 var glyph = null;
2171                 if (font.isArabic) {
2172                     var arabicForm = 'isolated';
2173                     if ((i==0 || text[i-1]==' ') && i<text.length-2 && text[i+1]!=' ') arabicForm = 'terminal'; 
2174                     if (i>0 && text[i-1]!=' ' && i<text.length-2 && text[i+1]!=' ') arabicForm = 'medial';
2175                     if (i>0 && text[i-1]!=' ' && (i == text.length-1 || text[i+1]==' ')) arabicForm = 'initial';
2176                     if (typeof(font.glyphs[c]) != 'undefined') {
2177                         glyph = font.glyphs[c][arabicForm];
2178                         if (glyph == null && font.glyphs[c].type == 'glyph') glyph = font.glyphs[c];
2179                     }
2180                 }
2181                 else {
2182                     glyph = font.glyphs[c];
2183                 }
2184                 if (glyph == null) glyph = font.missingGlyph;
2185                 return glyph;
2186             }
2187             
2188             this.renderChildren = function(ctx) {
2189                 var customFont = this.parent.style('font-family').Definition.getDefinition();
2190                 if (customFont != null) {
2191                     var fontSize = this.parent.style('font-size').numValueOrDefault(svg.Font.Parse(svg.ctx.font).fontSize);
2192                     var fontStyle = this.parent.style('font-style').valueOrDefault(svg.Font.Parse(svg.ctx.font).fontStyle);
2193                     var text = this.getText();
2194                     if (customFont.isRTL) text = text.split("").reverse().join("");
2195                     
2196                     var dx = svg.ToNumberArray(this.parent.attribute('dx').value);
2197                     for (var i=0; i<text.length; i++) {
2198                         var glyph = this.getGlyph(customFont, text, i);
2199                         var scale = fontSize / customFont.fontFace.unitsPerEm;
2200                         ctx.translate(this.x, this.y);
2201                         ctx.scale(scale, -scale);
2202                         var lw = ctx.lineWidth;
2203                         ctx.lineWidth = ctx.lineWidth * customFont.fontFace.unitsPerEm / fontSize;
2204                         if (fontStyle == 'italic') ctx.transform(1, 0, .4, 1, 0, 0);
2205                         glyph.render(ctx);
2206                         if (fontStyle == 'italic') ctx.transform(1, 0, -.4, 1, 0, 0);
2207                         ctx.lineWidth = lw;
2208                         ctx.scale(1/scale, -1/scale);
2209                         ctx.translate(-this.x, -this.y);    
2210                         
2211                         this.x += fontSize * (glyph.horizAdvX || customFont.horizAdvX) / customFont.fontFace.unitsPerEm;
2212                         if (typeof(dx[i]) != 'undefined' && !isNaN(dx[i])) {
2213                             this.x += dx[i];
2214                         }
2215                     }
2216                     return;
2217                 }
2218             
2219                 if (ctx.strokeStyle != '') ctx.strokeText(svg.compressSpaces(this.getText()), this.x, this.y);
2220                 if (ctx.fillStyle != '') ctx.fillText(svg.compressSpaces(this.getText()), this.x, this.y);
2221             }
2222             
2223             this.getText = function() {
2224                 // OVERRIDE ME
2225             }
2226             
2227             this.measureText = function(ctx) {
2228                 var customFont = this.parent.style('font-family').Definition.getDefinition();
2229                 if (customFont != null) {
2230                     var fontSize = this.parent.style('font-size').numValueOrDefault(svg.Font.Parse(svg.ctx.font).fontSize);
2231                     var measure = 0;
2232                     var text = this.getText();
2233                     if (customFont.isRTL) text = text.split("").reverse().join("");
2234                     var dx = svg.ToNumberArray(this.parent.attribute('dx').value);
2235                     for (var i=0; i<text.length; i++) {
2236                         var glyph = this.getGlyph(customFont, text, i);
2237                         measure += (glyph.horizAdvX || customFont.horizAdvX) * fontSize / customFont.fontFace.unitsPerEm;
2238                         if (typeof(dx[i]) != 'undefined' && !isNaN(dx[i])) {
2239                             measure += dx[i];
2240                         }
2241                     }
2242                     return measure;
2243                 }
2244             
2245                 var textToMeasure = svg.compressSpaces(this.getText());
2246                 if (!ctx.measureText) return textToMeasure.length * 10;
2247                 
2248                 ctx.save();
2249                 this.setContext(ctx);
2250                 var width = ctx.measureText(textToMeasure).width;
2251                 ctx.restore();
2252                 return width;
2253             }
2254         }
2255         svg.Element.TextElementBase.prototype = new svg.Element.RenderedElementBase;
2256         
2257         // tspan 
2258         svg.Element.tspan = function(node) {
2259             this.base = svg.Element.TextElementBase;
2260             this.base(node);
2261             
2262             this.text = node.nodeType == 3 ? node.nodeValue : // text
2263                         node.childNodes.length > 0 ? node.childNodes[0].nodeValue : // element
2264                         node.text;
2265             this.getText = function() {
2266                 return this.text;
2267             }
2268         }
2269         svg.Element.tspan.prototype = new svg.Element.TextElementBase;
2270         
2271         // tref
2272         svg.Element.tref = function(node) {
2273             this.base = svg.Element.TextElementBase;
2274             this.base(node);
2275             
2276             this.getText = function() {
2277                 var element = this.attribute('xlink:href').Definition.getDefinition();
2278                 if (element != null) return element.children[0].getText();
2279             }
2280         }
2281         svg.Element.tref.prototype = new svg.Element.TextElementBase;        
2282         
2283         // a element
2284         svg.Element.a = function(node) {
2285             this.base = svg.Element.TextElementBase;
2286             this.base(node);
2287             
2288             this.hasText = true;
2289             for (var i=0; i<node.childNodes.length; i++) {
2290                 if (node.childNodes[i].nodeType != 3) this.hasText = false;
2291             }
2292             
2293             // this might contain text
2294             this.text = this.hasText ? node.childNodes[0].nodeValue : '';
2295             this.getText = function() {
2296                 return this.text;
2297             }        
2298
2299             this.baseRenderChildren = this.renderChildren;
2300             this.renderChildren = function(ctx) {
2301                 if (this.hasText) {
2302                     // render as text element
2303                     this.baseRenderChildren(ctx);
2304                     var fontSize = new svg.Property('fontSize', svg.Font.Parse(svg.ctx.font).fontSize);
2305                     svg.Mouse.checkBoundingBox(this, new svg.BoundingBox(this.x, this.y - fontSize.Length.toPixels('y'), this.x + this.measureText(ctx), this.y));                    
2306                 }
2307                 else {
2308                     // render as temporary group
2309                     var g = new svg.Element.g();
2310                     g.children = this.children;
2311                     g.parent = this;
2312                     g.render(ctx);
2313                 }
2314             }
2315             
2316             this.onclick = function() {
2317                 window.open(this.attribute('xlink:href').value);
2318             }
2319             
2320             this.onmousemove = function() {
2321                 svg.ctx.canvas.style.cursor = 'pointer';
2322             }
2323         }
2324         svg.Element.a.prototype = new svg.Element.TextElementBase;        
2325         
2326         // image element
2327         svg.Element.image = function(node) {
2328             this.base = svg.Element.RenderedElementBase;
2329             this.base(node);
2330             
2331             svg.Images.push(this);
2332             this.img = document.createElement('img');
2333             this.loaded = false;
2334             var that = this;
2335             this.img.onload = function() { that.loaded = true; }
2336             this.img.src = this.attribute('xlink:href').value;
2337             
2338             this.renderChildren = function(ctx) {
2339                 var x = this.attribute('x').Length.toPixels('x');
2340                 var y = this.attribute('y').Length.toPixels('y');
2341                 
2342                 var width = this.attribute('width').Length.toPixels('x');
2343                 var height = this.attribute('height').Length.toPixels('y');            
2344                 if (width == 0 || height == 0) return;
2345             
2346                 ctx.save();
2347                 ctx.translate(x, y);
2348                 svg.AspectRatio(ctx,
2349                                 this.attribute('preserveAspectRatio').value,
2350                                 width,
2351                                 this.img.width,
2352                                 height,
2353                                 this.img.height,
2354                                 0,
2355                                 0);    
2356                 ctx.drawImage(this.img, 0, 0);            
2357                 ctx.restore();
2358             }
2359         }
2360         svg.Element.image.prototype = new svg.Element.RenderedElementBase;
2361         
2362         // group element
2363         svg.Element.g = function(node) {
2364             this.base = svg.Element.RenderedElementBase;
2365             this.base(node);
2366             
2367             this.getBoundingBox = function() {
2368                 var bb = new svg.BoundingBox();
2369                 for (var i=0; i<this.children.length; i++) {
2370                     bb.addBoundingBox(this.children[i].getBoundingBox());
2371                 }
2372                 return bb;
2373             };
2374         }
2375         svg.Element.g.prototype = new svg.Element.RenderedElementBase;
2376
2377         // symbol element
2378         svg.Element.symbol = function(node) {
2379             this.base = svg.Element.RenderedElementBase;
2380             this.base(node);
2381             
2382             this.baseSetContext = this.setContext;
2383             this.setContext = function(ctx) {        
2384                 this.baseSetContext(ctx);
2385                 
2386                 // viewbox
2387                 if (this.attribute('viewBox').hasValue()) {                
2388                     var viewBox = svg.ToNumberArray(this.attribute('viewBox').value);
2389                     var minX = viewBox[0];
2390                     var minY = viewBox[1];
2391                     width = viewBox[2];
2392                     height = viewBox[3];
2393                     
2394                     svg.AspectRatio(ctx,
2395                                     this.attribute('preserveAspectRatio').value, 
2396                                     this.attribute('width').Length.toPixels('x'),
2397                                     width,
2398                                     this.attribute('height').Length.toPixels('y'),
2399                                     height,
2400                                     minX,
2401                                     minY);
2402
2403                     svg.ViewPort.SetCurrent(viewBox[2], viewBox[3]);                        
2404                 }
2405             }            
2406         }
2407         svg.Element.symbol.prototype = new svg.Element.RenderedElementBase;        
2408             
2409         // style element
2410         svg.Element.style = function(node) { 
2411             this.base = svg.Element.ElementBase;
2412             this.base(node);
2413             
2414             // text, or spaces then CDATA
2415             var css = node.childNodes[0].nodeValue + (node.childNodes.length > 1 ? node.childNodes[1].nodeValue : '');
2416             css = css.replace(/(\/\*([^*]|[\r\n]|(\*+([^*\/]|[\r\n])))*\*+\/)|(^[\s]*\/\/.*)/gm, ''); // remove comments
2417             css = svg.compressSpaces(css); // replace whitespace
2418             var cssDefs = css.split('}');
2419             for (var i=0; i<cssDefs.length; i++) {
2420                 if (svg.trim(cssDefs[i]) != '') {
2421                     var cssDef = cssDefs[i].split('{');
2422                     var cssClasses = cssDef[0].split(',');
2423                     var cssProps = cssDef[1].split(';');
2424                     for (var j=0; j<cssClasses.length; j++) {
2425                         var cssClass = svg.trim(cssClasses[j]);
2426                         if (cssClass != '') {
2427                             var props = {};
2428                             for (var k=0; k<cssProps.length; k++) {
2429                                 var prop = cssProps[k].indexOf(':');
2430                                 var name = cssProps[k].substr(0, prop);
2431                                 var value = cssProps[k].substr(prop + 1, cssProps[k].length - prop);
2432                                 if (name != null && value != null) {
2433                                     props[svg.trim(name)] = new svg.Property(svg.trim(name), svg.trim(value));
2434                                 }
2435                             }
2436                             svg.Styles[cssClass] = props;
2437                             if (cssClass == '@font-face') {
2438                                 var fontFamily = props['font-family'].value.replace(/"/g,'');
2439                                 var srcs = props['src'].value.split(',');
2440                                 for (var s=0; s<srcs.length; s++) {
2441                                     if (srcs[s].indexOf('format("svg")') > 0) {
2442                                         var urlStart = srcs[s].indexOf('url');
2443                                         var urlEnd = srcs[s].indexOf(')', urlStart);
2444                                         var url = srcs[s].substr(urlStart + 5, urlEnd - urlStart - 6);
2445                                         var doc = svg.parseXml(svg.ajax(url));
2446                                         var fonts = doc.getElementsByTagName('font');
2447                                         for (var f=0; f<fonts.length; f++) {
2448                                             var font = svg.CreateElement(fonts[f]);
2449                                             svg.Definitions[fontFamily] = font;
2450                                         }
2451                                     }
2452                                 }
2453                             }
2454                         }
2455                     }
2456                 }
2457             }
2458         }
2459         svg.Element.style.prototype = new svg.Element.ElementBase;
2460         
2461         // use element 
2462         svg.Element.use = function(node) {
2463             this.base = svg.Element.RenderedElementBase;
2464             this.base(node);
2465             
2466             this.baseSetContext = this.setContext;
2467             this.setContext = function(ctx) {
2468                 this.baseSetContext(ctx);
2469                 if (this.attribute('x').hasValue()) ctx.translate(this.attribute('x').Length.toPixels('x'), 0);
2470                 if (this.attribute('y').hasValue()) ctx.translate(0, this.attribute('y').Length.toPixels('y'));
2471             }
2472             
2473             this.getDefinition = function() {
2474                 var element = this.attribute('xlink:href').Definition.getDefinition();
2475                 if (this.attribute('width').hasValue()) element.attribute('width', true).value = this.attribute('width').value;
2476                 if (this.attribute('height').hasValue()) element.attribute('height', true).value = this.attribute('height').value;
2477                 return element;
2478             }
2479             
2480             this.path = function(ctx) {
2481                 var element = this.getDefinition();
2482                 if (element != null) element.path(ctx);
2483             }
2484             
2485             this.renderChildren = function(ctx) {
2486                 var element = this.getDefinition();
2487                 if (element != null) element.render(ctx);
2488             }
2489         }
2490         svg.Element.use.prototype = new svg.Element.RenderedElementBase;
2491         
2492         // mask element
2493         svg.Element.mask = function(node) {
2494             this.base = svg.Element.ElementBase;
2495             this.base(node);
2496                         
2497             this.apply = function(ctx, element) {
2498                 // render as temp svg    
2499                 var x = this.attribute('x').Length.toPixels('x');
2500                 var y = this.attribute('y').Length.toPixels('y');
2501                 var width = this.attribute('width').Length.toPixels('x');
2502                 var height = this.attribute('height').Length.toPixels('y');
2503                 
2504                 // temporarily remove mask to avoid recursion
2505                 var mask = element.attribute('mask').value;
2506                 element.attribute('mask').value = '';
2507                 
2508                     var cMask = document.createElement('canvas');
2509                     cMask.width = x + width;
2510                     cMask.height = y + height;
2511                     var maskCtx = cMask.getContext('2d');
2512                     this.renderChildren(maskCtx);
2513                 
2514                     var c = document.createElement('canvas');
2515                     c.width = x + width;
2516                     c.height = y + height;
2517                     var tempCtx = c.getContext('2d');
2518                     element.render(tempCtx);
2519                     tempCtx.globalCompositeOperation = 'destination-in';
2520                     tempCtx.fillStyle = maskCtx.createPattern(cMask, 'no-repeat');
2521                     tempCtx.fillRect(0, 0, x + width, y + height);
2522                     
2523                     ctx.fillStyle = tempCtx.createPattern(c, 'no-repeat');
2524                     ctx.fillRect(0, 0, x + width, y + height);
2525                     
2526                 // reassign mask
2527                 element.attribute('mask').value = mask;    
2528             }
2529             
2530             this.render = function(ctx) {
2531                 // NO RENDER
2532             }
2533         }
2534         svg.Element.mask.prototype = new svg.Element.ElementBase;
2535         
2536         // clip element
2537         svg.Element.clipPath = function(node) {
2538             this.base = svg.Element.ElementBase;
2539             this.base(node);
2540             
2541             this.apply = function(ctx) {
2542                 for (var i=0; i<this.children.length; i++) {
2543                     if (this.children[i].path) {
2544                         this.children[i].path(ctx);
2545                         ctx.clip();
2546                     }
2547                 }
2548             }
2549             
2550             this.render = function(ctx) {
2551                 // NO RENDER
2552             }
2553         }
2554         svg.Element.clipPath.prototype = new svg.Element.ElementBase;
2555
2556         // filters
2557         svg.Element.filter = function(node) {
2558             this.base = svg.Element.ElementBase;
2559             this.base(node);
2560                         
2561             this.apply = function(ctx, element) {
2562                 // render as temp svg    
2563                 var bb = element.getBoundingBox();
2564                 var x = this.attribute('x').Length.toPixels('x');
2565                 var y = this.attribute('y').Length.toPixels('y');
2566                 if (x == 0 || y == 0) {
2567                     x = bb.x1;
2568                     y = bb.y1;
2569                 }
2570                 var width = this.attribute('width').Length.toPixels('x');
2571                 var height = this.attribute('height').Length.toPixels('y');
2572                 if (width == 0 || height == 0) {
2573                     width = bb.width();
2574                     height = bb.height();
2575                 }
2576                 
2577                 // temporarily remove filter to avoid recursion
2578                 var filter = element.style('filter').value;
2579                 element.style('filter').value = '';
2580                 
2581                 // max filter distance
2582                 var extraPercent = .20;
2583                 var px = extraPercent * width;
2584                 var py = extraPercent * height;
2585                 
2586                 var c = document.createElement('canvas');
2587                 c.width = width + 2*px;
2588                 c.height = height + 2*py;
2589                 var tempCtx = c.getContext('2d');
2590                 tempCtx.translate(-x + px, -y + py);
2591                 element.render(tempCtx);
2592             
2593                 // apply filters
2594                 for (var i=0; i<this.children.length; i++) {
2595                     this.children[i].apply(tempCtx, 0, 0, width + 2*px, height + 2*py);
2596                 }
2597                 
2598                 // render on me
2599                 ctx.drawImage(c, 0, 0, width + 2*px, height + 2*py, x - px, y - py, width + 2*px, height + 2*py);
2600                 
2601                 // reassign filter
2602                 element.style('filter', true).value = filter;    
2603             }
2604             
2605             this.render = function(ctx) {
2606                 // NO RENDER
2607             }        
2608         }
2609         svg.Element.filter.prototype = new svg.Element.ElementBase;
2610         
2611         svg.Element.feGaussianBlur = function(node) {
2612             this.base = svg.Element.ElementBase;
2613             this.base(node);    
2614             
2615             function make_fgauss(sigma) {
2616                 sigma = Math.max(sigma, 0.01);                  
2617                 var len = Math.ceil(sigma * 4.0) + 1;                     
2618                 mask = [];                               
2619                 for (var i = 0; i < len; i++) {                             
2620                     mask[i] = Math.exp(-0.5 * (i / sigma) * (i / sigma));                                           
2621                 }                                                           
2622                 return mask; 
2623             }
2624             
2625             function normalize(mask) {
2626                 var sum = 0;
2627                 for (var i = 1; i < mask.length; i++) {
2628                     sum += Math.abs(mask[i]);
2629                 }
2630                 sum = 2 * sum + Math.abs(mask[0]);
2631                 for (var i = 0; i < mask.length; i++) {
2632                     mask[i] /= sum;
2633                 }
2634                 return mask;
2635             }
2636             
2637             function convolve_even(src, dst, mask, width, height) {
2638               for (var y = 0; y < height; y++) {
2639                 for (var x = 0; x < width; x++) {
2640                   var a = imGet(src, x, y, width, height, 3)/255;
2641                   for (var rgba = 0; rgba < 4; rgba++) {                      
2642                       var sum = mask[0] * (a==0?255:imGet(src, x, y, width, height, rgba)) * (a==0||rgba==3?1:a);
2643                       for (var i = 1; i < mask.length; i++) {
2644                         var a1 = imGet(src, Math.max(x-i,0), y, width, height, 3)/255;
2645                         var a2 = imGet(src, Math.min(x+i, width-1), y, width, height, 3)/255;
2646                         sum += mask[i] * 
2647                           ((a1==0?255:imGet(src, Math.max(x-i,0), y, width, height, rgba)) * (a1==0||rgba==3?1:a1) + 
2648                            (a2==0?255:imGet(src, Math.min(x+i, width-1), y, width, height, rgba)) * (a2==0||rgba==3?1:a2));
2649                       }
2650                       imSet(dst, y, x, height, width, rgba, sum);
2651                   }              
2652                 }
2653               }
2654             }        
2655
2656             function imGet(img, x, y, width, height, rgba) {
2657                 return img[y*width*4 + x*4 + rgba];
2658             }
2659             
2660             function imSet(img, x, y, width, height, rgba, val) {
2661                 img[y*width*4 + x*4 + rgba] = val;
2662             }
2663                         
2664             function blur(ctx, width, height, sigma)
2665             {
2666                 var srcData = ctx.getImageData(0, 0, width, height);
2667                 var mask = make_fgauss(sigma);
2668                 mask = normalize(mask);
2669                 tmp = [];
2670                 convolve_even(srcData.data, tmp, mask, width, height);
2671                 convolve_even(tmp, srcData.data, mask, height, width);
2672                 ctx.clearRect(0, 0, width, height);
2673                 ctx.putImageData(srcData, 0, 0);
2674             }            
2675         
2676             this.apply = function(ctx, x, y, width, height) {
2677                 // assuming x==0 && y==0 for now
2678                 blur(ctx, width, height, this.attribute('stdDeviation').numValue());
2679             }
2680         }
2681         svg.Element.filter.prototype = new svg.Element.feGaussianBlur;
2682         
2683         // title element, do nothing
2684         svg.Element.title = function(node) {
2685         }
2686         svg.Element.title.prototype = new svg.Element.ElementBase;
2687
2688         // desc element, do nothing
2689         svg.Element.desc = function(node) {
2690         }
2691         svg.Element.desc.prototype = new svg.Element.ElementBase;        
2692         
2693         svg.Element.MISSING = function(node) {
2694             console.log('ERROR: Element \'' + node.nodeName + '\' not yet implemented.');
2695         }
2696         svg.Element.MISSING.prototype = new svg.Element.ElementBase;
2697         
2698         // element factory
2699         svg.CreateElement = function(node) {    
2700             var className = node.nodeName.replace(/^[^:]+:/,''); // remove namespace
2701             className = className.replace(/\-/g,''); // remove dashes
2702             var e = null;
2703             if (typeof(svg.Element[className]) != 'undefined') {
2704                 e = new svg.Element[className](node);
2705             }
2706             else {
2707                 e = new svg.Element.MISSING(node);
2708             }
2709
2710             e.type = node.nodeName;
2711             return e;
2712         }
2713                 
2714         // load from url
2715         svg.load = function(ctx, url) {
2716             svg.loadXml(ctx, svg.ajax(url));
2717         }
2718         
2719         // load from xml
2720         svg.loadXml = function(ctx, xml) {
2721             svg.loadXmlDoc(ctx, svg.parseXml(xml));
2722         }
2723         
2724         svg.loadXmlDoc = function(ctx, dom) {
2725             svg.init(ctx);
2726             
2727             var mapXY = function(p) {
2728                 var e = ctx.canvas;
2729                 while (e) {
2730                     p.x -= e.offsetLeft;
2731                     p.y -= e.offsetTop;
2732                     e = e.offsetParent;
2733                 }
2734                 if (window.scrollX) p.x += window.scrollX;
2735                 if (window.scrollY) p.y += window.scrollY;
2736                 return p;
2737             }
2738             
2739             // bind mouse
2740             if (svg.opts['ignoreMouse'] != true) {
2741                 ctx.canvas.onclick = function(e) {
2742                     var p = mapXY(new svg.Point(e != null ? e.clientX : event.clientX, e != null ? e.clientY : event.clientY));
2743                     svg.Mouse.onclick(p.x, p.y);
2744                 };
2745                 ctx.canvas.onmousemove = function(e) {
2746                     var p = mapXY(new svg.Point(e != null ? e.clientX : event.clientX, e != null ? e.clientY : event.clientY));
2747                     svg.Mouse.onmousemove(p.x, p.y);
2748                 };
2749             }
2750         
2751             var e = svg.CreateElement(dom.documentElement);
2752             e.root = true;
2753                     
2754             // render loop
2755             var isFirstRender = true;
2756             var draw = function() {
2757                 svg.ViewPort.Clear();
2758                 if (ctx.canvas.parentNode) svg.ViewPort.SetCurrent(ctx.canvas.parentNode.clientWidth, ctx.canvas.parentNode.clientHeight);
2759             
2760                 if (svg.opts['ignoreDimensions'] != true) {
2761                     // set canvas size
2762                     if (e.style('width').hasValue()) {
2763                         ctx.canvas.width = e.style('width').Length.toPixels('x');
2764                         ctx.canvas.style.width = ctx.canvas.width + 'px';
2765                     }
2766                     if (e.style('height').hasValue()) {
2767                         ctx.canvas.height = e.style('height').Length.toPixels('y');
2768                         ctx.canvas.style.height = ctx.canvas.height + 'px';
2769                     }
2770                 }
2771                 var cWidth = ctx.canvas.clientWidth || ctx.canvas.width;
2772                 var cHeight = ctx.canvas.clientHeight || ctx.canvas.height;
2773                 svg.ViewPort.SetCurrent(cWidth, cHeight);        
2774                 
2775                 if (svg.opts != null && svg.opts['offsetX'] != null) e.attribute('x', true).value = svg.opts['offsetX'];
2776                 if (svg.opts != null && svg.opts['offsetY'] != null) e.attribute('y', true).value = svg.opts['offsetY'];
2777                 if (svg.opts != null && svg.opts['scaleWidth'] != null && svg.opts['scaleHeight'] != null) {
2778                     var xRatio = 1, yRatio = 1;
2779                     if (e.attribute('width').hasValue()) xRatio = e.attribute('width').Length.toPixels('x') / svg.opts['scaleWidth'];
2780                     if (e.attribute('height').hasValue()) yRatio = e.attribute('height').Length.toPixels('y') / svg.opts['scaleHeight'];
2781                 
2782                     e.attribute('width', true).value = svg.opts['scaleWidth'];
2783                     e.attribute('height', true).value = svg.opts['scaleHeight'];            
2784                     e.attribute('viewBox', true).value = '0 0 ' + (cWidth * xRatio) + ' ' + (cHeight * yRatio);
2785                     e.attribute('preserveAspectRatio', true).value = 'none';
2786                 }
2787             
2788                 // clear and render
2789                 if (svg.opts['ignoreClear'] != true) {
2790                     ctx.clearRect(0, 0, cWidth, cHeight);
2791                 }
2792                 e.render(ctx);
2793                 if (isFirstRender) {
2794                     isFirstRender = false;
2795                     if (svg.opts != null && typeof(svg.opts['renderCallback']) == 'function') svg.opts['renderCallback']();
2796                 }            
2797             }
2798             
2799             var waitingForImages = true;
2800             if (svg.ImagesLoaded()) {
2801                 waitingForImages = false;
2802                 draw();
2803             }
2804             svg.intervalID = setInterval(function() { 
2805                 var needUpdate = false;
2806                 
2807                 if (waitingForImages && svg.ImagesLoaded()) {
2808                     waitingForImages = false;
2809                     needUpdate = true;
2810                 }
2811             
2812                 // need update from mouse events?
2813                 if (svg.opts['ignoreMouse'] != true) {
2814                     needUpdate = needUpdate | svg.Mouse.hasEvents();
2815                 }
2816             
2817                 // need update from animations?
2818                 if (svg.opts['ignoreAnimation'] != true) {
2819                     for (var i=0; i<svg.Animations.length; i++) {
2820                         needUpdate = needUpdate | svg.Animations[i].update(1000 / svg.FRAMERATE);
2821                     }
2822                 }
2823                 
2824                 // need update from redraw?
2825                 if (svg.opts != null && typeof(svg.opts['forceRedraw']) == 'function') {
2826                     if (svg.opts['forceRedraw']() == true) needUpdate = true;
2827                 }
2828                 
2829                 // render if needed
2830                 if (needUpdate) {
2831                     draw();                
2832                     svg.Mouse.runEvents(); // run and clear our events
2833                 }
2834             }, 1000 / svg.FRAMERATE);
2835         }
2836         
2837         svg.stop = function() {
2838             if (svg.intervalID) {
2839                 clearInterval(svg.intervalID);
2840             }
2841         }
2842         
2843         svg.Mouse = new (function() {
2844             this.events = [];
2845             this.hasEvents = function() { return this.events.length != 0; }
2846         
2847             this.onclick = function(x, y) {
2848                 this.events.push({ type: 'onclick', x: x, y: y, 
2849                     run: function(e) { if (e.onclick) e.onclick(); }
2850                 });
2851             }
2852             
2853             this.onmousemove = function(x, y) {
2854                 this.events.push({ type: 'onmousemove', x: x, y: y,
2855                     run: function(e) { if (e.onmousemove) e.onmousemove(); }
2856                 });
2857             }            
2858             
2859             this.eventElements = [];
2860             
2861             this.checkPath = function(element, ctx) {
2862                 for (var i=0; i<this.events.length; i++) {
2863                     var e = this.events[i];
2864                     if (ctx.isPointInPath && ctx.isPointInPath(e.x, e.y)) this.eventElements[i] = element;
2865                 }
2866             }
2867             
2868             this.checkBoundingBox = function(element, bb) {
2869                 for (var i=0; i<this.events.length; i++) {
2870                     var e = this.events[i];
2871                     if (bb.isPointInBox(e.x, e.y)) this.eventElements[i] = element;
2872                 }            
2873             }
2874             
2875             this.runEvents = function() {
2876                 svg.ctx.canvas.style.cursor = '';
2877                 
2878                 for (var i=0; i<this.events.length; i++) {
2879                     var e = this.events[i];
2880                     var element = this.eventElements[i];
2881                     while (element) {
2882                         e.run(element);
2883                         element = element.parent;
2884                     }
2885                 }        
2886             
2887                 // done running, clear
2888                 this.events = []; 
2889                 this.eventElements = [];
2890             }
2891         });
2892         
2893         return svg;
2894     }
2895 })();
2896
2897 if (CanvasRenderingContext2D) {
2898     CanvasRenderingContext2D.prototype.drawSvg = function(s, dx, dy, dw, dh) {
2899         canvg(this.canvas, s, { 
2900             ignoreMouse: true, 
2901             ignoreAnimation: true, 
2902             ignoreDimensions: true, 
2903             ignoreClear: true, 
2904             offsetX: dx, 
2905             offsetY: dy, 
2906             scaleWidth: dw, 
2907             scaleHeight: dh
2908         });
2909     }
2910 }/**
2911  * @license Highcharts JS v3.0.6 (2013-10-04)
2912  * CanVGRenderer Extension module
2913  *
2914  * (c) 2011-2012 Torstein Hønsi, Erik Olsson
2915  *
2916  * License: www.highcharts.com/license
2917  */
2918
2919 // JSLint options:
2920 /*global Highcharts */
2921
2922 (function (Highcharts) { // encapsulate
2923     var UNDEFINED,
2924         DIV = 'div',
2925         ABSOLUTE = 'absolute',
2926         RELATIVE = 'relative',
2927         HIDDEN = 'hidden',
2928         VISIBLE = 'visible',
2929         PX = 'px',
2930         css = Highcharts.css,
2931         CanVGRenderer = Highcharts.CanVGRenderer,
2932         SVGRenderer = Highcharts.SVGRenderer,
2933         extend = Highcharts.extend,
2934         merge = Highcharts.merge,
2935         addEvent = Highcharts.addEvent,
2936         createElement = Highcharts.createElement,
2937         discardElement = Highcharts.discardElement;
2938
2939     // Extend CanVG renderer on demand, inherit from SVGRenderer
2940     extend(CanVGRenderer.prototype, SVGRenderer.prototype);
2941
2942     // Add additional functionality:
2943     extend(CanVGRenderer.prototype, {
2944         create: function (chart, container, chartWidth, chartHeight) {
2945             this.setContainer(container, chartWidth, chartHeight);
2946             this.configure(chart);
2947         },
2948         setContainer: function (container, chartWidth, chartHeight) {
2949             var containerStyle = container.style,
2950                 containerParent = container.parentNode,
2951                 containerLeft = containerStyle.left,
2952                 containerTop = containerStyle.top,
2953                 containerOffsetWidth = container.offsetWidth,
2954                 containerOffsetHeight = container.offsetHeight,
2955                 canvas,
2956                 initialHiddenStyle = { visibility: HIDDEN, position: ABSOLUTE };
2957
2958             this.init.apply(this, [container, chartWidth, chartHeight]);
2959
2960             // add the canvas above it
2961             canvas = createElement('canvas', {
2962                 width: containerOffsetWidth,
2963                 height: containerOffsetHeight
2964             }, {
2965                 position: RELATIVE,
2966                 left: containerLeft,
2967                 top: containerTop
2968             }, container);
2969             this.canvas = canvas;
2970
2971             // Create the tooltip line and div, they are placed as siblings to
2972             // the container (and as direct childs to the div specified in the html page)
2973             this.ttLine = createElement(DIV, null, initialHiddenStyle, containerParent);
2974             this.ttDiv = createElement(DIV, null, initialHiddenStyle, containerParent);
2975             this.ttTimer = UNDEFINED;
2976
2977             // Move away the svg node to a new div inside the container's parent so we can hide it.
2978             var hiddenSvg = createElement(DIV, {
2979                 width: containerOffsetWidth,
2980                 height: containerOffsetHeight
2981             }, {
2982                 visibility: HIDDEN,
2983                 left: containerLeft,
2984                 top: containerTop
2985             }, containerParent);
2986             this.hiddenSvg = hiddenSvg;
2987             hiddenSvg.appendChild(this.box);
2988         },
2989
2990         /**
2991          * Configures the renderer with the chart. Attach a listener to the event tooltipRefresh.
2992          **/
2993         configure: function (chart) {
2994             var renderer = this,
2995                 options = chart.options.tooltip,
2996                 borderWidth = options.borderWidth,
2997                 tooltipDiv = renderer.ttDiv,
2998                 tooltipDivStyle = options.style,
2999                 tooltipLine = renderer.ttLine,
3000                 padding = parseInt(tooltipDivStyle.padding, 10);
3001
3002             // Add border styling from options to the style
3003             tooltipDivStyle = merge(tooltipDivStyle, {
3004                 padding: padding + PX,
3005                 'background-color': options.backgroundColor,
3006                 'border-style': 'solid',
3007                 'border-width': borderWidth + PX,
3008                 'border-radius': options.borderRadius + PX
3009             });
3010
3011             // Optionally add shadow
3012             if (options.shadow) {
3013                 tooltipDivStyle = merge(tooltipDivStyle, {
3014                     'box-shadow': '1px 1px 3px gray', // w3c
3015                     '-webkit-box-shadow': '1px 1px 3px gray' // webkit
3016                 });
3017             }
3018             css(tooltipDiv, tooltipDivStyle);
3019
3020             // Set simple style on the line
3021             css(tooltipLine, {
3022                 'border-left': '1px solid darkgray'
3023             });
3024
3025             // This event is triggered when a new tooltip should be shown
3026             addEvent(chart, 'tooltipRefresh', function (args) {
3027                 var chartContainer = chart.container,
3028                     offsetLeft = chartContainer.offsetLeft,
3029                     offsetTop = chartContainer.offsetTop,
3030                     position;
3031
3032                 // Set the content of the tooltip
3033                 tooltipDiv.innerHTML = args.text;
3034
3035                 // Compute the best position for the tooltip based on the divs size and container size.
3036                 position = chart.tooltip.getPosition(tooltipDiv.offsetWidth, tooltipDiv.offsetHeight, {plotX: args.x, plotY: args.y});
3037
3038                 css(tooltipDiv, {
3039                     visibility: VISIBLE,
3040                     left: position.x + PX,
3041                     top: position.y + PX,
3042                     'border-color': args.borderColor
3043                 });
3044
3045                 // Position the tooltip line
3046                 css(tooltipLine, {
3047                     visibility: VISIBLE,
3048                     left: offsetLeft + args.x + PX,
3049                     top: offsetTop + chart.plotTop + PX,
3050                     height: chart.plotHeight  + PX
3051                 });
3052
3053                 // This timeout hides the tooltip after 3 seconds
3054                 // First clear any existing timer
3055                 if (renderer.ttTimer !== UNDEFINED) {
3056                     clearTimeout(renderer.ttTimer);
3057                 }
3058
3059                 // Start a new timer that hides tooltip and line
3060                 renderer.ttTimer = setTimeout(function () {
3061                     css(tooltipDiv, { visibility: HIDDEN });
3062                     css(tooltipLine, { visibility: HIDDEN });
3063                 }, 3000);
3064             });
3065         },
3066
3067         /**
3068          * Extend SVGRenderer.destroy to also destroy the elements added by CanVGRenderer.
3069          */
3070         destroy: function () {
3071             var renderer = this;
3072
3073             // Remove the canvas
3074             discardElement(renderer.canvas);
3075
3076             // Kill the timer
3077             if (renderer.ttTimer !== UNDEFINED) {
3078                 clearTimeout(renderer.ttTimer);
3079             }
3080
3081             // Remove the divs for tooltip and line
3082             discardElement(renderer.ttLine);
3083             discardElement(renderer.ttDiv);
3084             discardElement(renderer.hiddenSvg);
3085
3086             // Continue with base class
3087             return SVGRenderer.prototype.destroy.apply(renderer);
3088         },
3089
3090         /**
3091          * Take a color and return it if it's a string, do not make it a gradient even if it is a
3092          * gradient. Currently canvg cannot render gradients (turns out black),
3093          * see: http://code.google.com/p/canvg/issues/detail?id=104
3094          *
3095          * @param {Object} color The color or config object
3096          */
3097         color: function (color, elem, prop) {
3098             if (color && color.linearGradient) {
3099                 // Pick the end color and forward to base implementation
3100                 color = color.stops[color.stops.length - 1][1];
3101             }
3102             return SVGRenderer.prototype.color.call(this, color, elem, prop);
3103         },
3104
3105         /**
3106          * Draws the SVG on the canvas or adds a draw invokation to the deferred list.
3107          */
3108         draw: function () {
3109             var renderer = this;
3110             window.canvg(renderer.canvas, renderer.hiddenSvg.innerHTML);
3111         }
3112     });
3113 }(Highcharts));