Plot functionality using Hihcharts
[phpmyadmin/ammaryasirr.git] / js / canvg / canvg.js
blob83c91b45fc311461717d7bf1feb54354b064b7fa
1 /*
2  * canvg.js - Javascript SVG parser and renderer on Canvas
3  * MIT Licensed 
4  * Gabe Lerner (gabelerner@gmail.com)
5  * http://code.google.com/p/canvg/
6  *
7  * Requires: rgbcolor.js - http://www.phpied.com/rgb-color-parser-in-javascript/
8  */
9 if(!window.console) {
10         window.console = {};
11         window.console.log = function(str) {};
12         window.console.dir = function(str) {};
15 // <3 IE
16 if(!Array.indexOf){
17         Array.prototype.indexOf = function(obj){
18                 for(var i=0; i<this.length; i++){
19                         if(this[i]==obj){
20                                 return i;
21                         }
22                 }
23                 return -1;
24         }
27 (function(){
28         // canvg(target, s)
29         // empty parameters: replace all 'svg' elements on page with 'canvas' elements
30         // target: canvas element or the id of a canvas element
31         // s: svg string or url to svg file
32         // opts: optional hash of options
33         //               ignoreMouse: true => ignore mouse events
34         //               ignoreAnimation: true => ignore animations
35         //               ignoreDimensions: true => does not try to resize canvas
36         //               ignoreClear: true => does not clear canvas
37         //               offsetX: int => draws at a x offset
38         //               offsetY: int => draws at a y offset
39         //               scaleWidth: int => scales horizontally to width
40         //               scaleHeight: int => scales vertically to height
41         //               renderCallback: function => will call the function after the first render is completed
42         //               forceRedraw: function => will call the function on every frame, if it returns true, will redraw
43         this.canvg = function (target, s, opts) {
44                 // no parameters
45                 if (target == null && s == null && opts == null) {
46                         var svgTags = document.getElementsByTagName('svg');
47                         for (var i=0; i<svgTags.length; i++) {
48                                 var svgTag = svgTags[i];
49                                 var c = document.createElement('canvas');
50                                 c.width = svgTag.clientWidth;
51                                 c.height = svgTag.clientHeight;
52                                 svgTag.parentNode.insertBefore(c, svgTag);
53                                 svgTag.parentNode.removeChild(svgTag);
54                                 var div = document.createElement('div');
55                                 div.appendChild(svgTag);
56                                 canvg(c, div.innerHTML);
57                         }
58                         return;
59                 }       
60         
61                 if (typeof target == 'string') {
62                         target = document.getElementById(target);
63                 }
64                 
65                 // reuse class per canvas
66                 var svg;
67                 if (target.svg == null) {
68                         svg = build();
69                         target.svg = svg;
70                 }
71                 else {
72                         svg = target.svg;
73                         svg.stop();
74                 }
75                 svg.opts = opts;
76                 
77                 var ctx = target.getContext('2d');
78                 if (s.substr(0,1) == '<') {
79                         // load from xml string
80                         svg.loadXml(ctx, s);
81                 }
82                 else {
83                         // load from url
84                         svg.load(ctx, s);
85                 }
86         }
88         function build() {
89                 var svg = { };
90                 
91                 svg.FRAMERATE = 30;
92                 
93                 // globals
94                 svg.init = function(ctx) {
95                         svg.Definitions = {};
96                         svg.Styles = {};
97                         svg.Animations = [];
98                         svg.Images = [];
99                         svg.ctx = ctx;
100                         svg.ViewPort = new (function () {
101                                 this.viewPorts = [];
102                                 this.SetCurrent = function(width, height) { this.viewPorts.push({ width: width, height: height }); }
103                                 this.RemoveCurrent = function() { this.viewPorts.pop(); }
104                                 this.Current = function() { return this.viewPorts[this.viewPorts.length - 1]; }
105                                 this.width = function() { return this.Current().width; }
106                                 this.height = function() { return this.Current().height; }
107                                 this.ComputeSize = function(d) {
108                                         if (d != null && typeof(d) == 'number') return d;
109                                         if (d == 'x') return this.width();
110                                         if (d == 'y') return this.height();
111                                         return Math.sqrt(Math.pow(this.width(), 2) + Math.pow(this.height(), 2)) / Math.sqrt(2);                        
112                                 }
113                         });
114                 }
115                 svg.init();
116                 
117                 // images loaded
118                 svg.ImagesLoaded = function() { 
119                         for (var i=0; i<svg.Images.length; i++) {
120                                 if (!svg.Images[i].loaded) return false;
121                         }
122                         return true;
123                 }
125                 // trim
126                 svg.trim = function(s) { return s.replace(/^\s+|\s+$/g, ''); }
127                 
128                 // compress spaces
129                 svg.compressSpaces = function(s) { return s.replace(/[\s\r\t\n]+/gm,' '); }
130                 
131                 // ajax
132                 svg.ajax = function(url) {
133                         var AJAX;
134                         if(window.XMLHttpRequest){AJAX=new XMLHttpRequest();}
135                         else{AJAX=new ActiveXObject('Microsoft.XMLHTTP');}
136                         if(AJAX){
137                            AJAX.open('GET',url,false);
138                            AJAX.send(null);
139                            return AJAX.responseText;
140                         }
141                         return null;
142                 } 
143                 
144                 // parse xml
145                 svg.parseXml = function(xml) {
146                         if (window.DOMParser)
147                         {
148                                 var parser = new DOMParser();
149                                 return parser.parseFromString(xml, 'text/xml');
150                         }
151                         else 
152                         {
153                                 xml = xml.replace(/<!DOCTYPE svg[^>]*>/, '');
154                                 var xmlDoc = new ActiveXObject('Microsoft.XMLDOM');
155                                 xmlDoc.async = 'false';
156                                 xmlDoc.loadXML(xml); 
157                                 return xmlDoc;
158                         }               
159                 }
160                 
161                 svg.Property = function(name, value) {
162                         this.name = name;
163                         this.value = value;
164                         
165                         this.hasValue = function() {
166                                 return (this.value != null && this.value != '');
167                         }
168                                                         
169                         // return the numerical value of the property
170                         this.numValue = function() {
171                                 if (!this.hasValue()) return 0;
172                                 
173                                 var n = parseFloat(this.value);
174                                 if ((this.value + '').match(/%$/)) {
175                                         n = n / 100.0;
176                                 }
177                                 return n;
178                         }
179                         
180                         this.valueOrDefault = function(def) {
181                                 if (this.hasValue()) return this.value;
182                                 return def;
183                         }
184                         
185                         this.numValueOrDefault = function(def) {
186                                 if (this.hasValue()) return this.numValue();
187                                 return def;
188                         }
189                         
190                         /* EXTENSIONS */
191                         var that = this;
192                         
193                         // color extensions
194                         this.Color = {
195                                 // augment the current color value with the opacity
196                                 addOpacity: function(opacity) {
197                                         var newValue = that.value;
198                                         if (opacity != null && opacity != '') {
199                                                 var color = new RGBColor(that.value);
200                                                 if (color.ok) {
201                                                         newValue = 'rgba(' + color.r + ', ' + color.g + ', ' + color.b + ', ' + opacity + ')';
202                                                 }
203                                         }
204                                         return new svg.Property(that.name, newValue);
205                                 }
206                         }
207                         
208                         // definition extensions
209                         this.Definition = {
210                                 // get the definition from the definitions table
211                                 getDefinition: function() {
212                                         var name = that.value.replace(/^(url\()?#([^\)]+)\)?$/, '$2');
213                                         return svg.Definitions[name];
214                                 },
215                                 
216                                 isUrl: function() {
217                                         return that.value.indexOf('url(') == 0
218                                 },
219                                 
220                                 getFillStyle: function(e) {
221                                         var def = this.getDefinition();
222                                         
223                                         // gradient
224                                         if (def != null && def.createGradient) {
225                                                 return def.createGradient(svg.ctx, e);
226                                         }
227                                         
228                                         // pattern
229                                         if (def != null && def.createPattern) {
230                                                 return def.createPattern(svg.ctx, e);
231                                         }
232                                         
233                                         return null;
234                                 }
235                         }
236                         
237                         // length extensions
238                         this.Length = {
239                                 DPI: function(viewPort) {
240                                         return 96.0; // TODO: compute?
241                                 },
242                                 
243                                 EM: function(viewPort) {
244                                         var em = 12;
245                                         
246                                         var fontSize = new svg.Property('fontSize', svg.Font.Parse(svg.ctx.font).fontSize);
247                                         if (fontSize.hasValue()) em = fontSize.Length.toPixels(viewPort);
248                                         
249                                         return em;
250                                 },
251                         
252                                 // get the length as pixels
253                                 toPixels: function(viewPort) {
254                                         if (!that.hasValue()) return 0;
255                                         var s = that.value+'';
256                                         if (s.match(/em$/)) return that.numValue() * this.EM(viewPort);
257                                         if (s.match(/ex$/)) return that.numValue() * this.EM(viewPort) / 2.0;
258                                         if (s.match(/px$/)) return that.numValue();
259                                         if (s.match(/pt$/)) return that.numValue() * 1.25;
260                                         if (s.match(/pc$/)) return that.numValue() * 15;
261                                         if (s.match(/cm$/)) return that.numValue() * this.DPI(viewPort) / 2.54;
262                                         if (s.match(/mm$/)) return that.numValue() * this.DPI(viewPort) / 25.4;
263                                         if (s.match(/in$/)) return that.numValue() * this.DPI(viewPort);
264                                         if (s.match(/%$/)) return that.numValue() * svg.ViewPort.ComputeSize(viewPort);
265                                         return that.numValue();
266                                 }
267                         }
268                         
269                         // time extensions
270                         this.Time = {
271                                 // get the time as milliseconds
272                                 toMilliseconds: function() {
273                                         if (!that.hasValue()) return 0;
274                                         var s = that.value+'';
275                                         if (s.match(/s$/)) return that.numValue() * 1000;
276                                         if (s.match(/ms$/)) return that.numValue();
277                                         return that.numValue();
278                                 }
279                         }
280                         
281                         // angle extensions
282                         this.Angle = {
283                                 // get the angle as radians
284                                 toRadians: function() {
285                                         if (!that.hasValue()) return 0;
286                                         var s = that.value+'';
287                                         if (s.match(/deg$/)) return that.numValue() * (Math.PI / 180.0);
288                                         if (s.match(/grad$/)) return that.numValue() * (Math.PI / 200.0);
289                                         if (s.match(/rad$/)) return that.numValue();
290                                         return that.numValue() * (Math.PI / 180.0);
291                                 }
292                         }
293                 }
294                 
295                 // fonts
296                 svg.Font = new (function() {
297                         this.Styles = ['normal','italic','oblique','inherit'];
298                         this.Variants = ['normal','small-caps','inherit'];
299                         this.Weights = ['normal','bold','bolder','lighter','100','200','300','400','500','600','700','800','900','inherit'];
300                         
301                         this.CreateFont = function(fontStyle, fontVariant, fontWeight, fontSize, fontFamily, inherit) { 
302                                 var f = inherit != null ? this.Parse(inherit) : this.CreateFont('', '', '', '', '', svg.ctx.font);
303                                 return { 
304                                         fontFamily: fontFamily || f.fontFamily, 
305                                         fontSize: fontSize || f.fontSize, 
306                                         fontStyle: fontStyle || f.fontStyle, 
307                                         fontWeight: fontWeight || f.fontWeight, 
308                                         fontVariant: fontVariant || f.fontVariant,
309                                         toString: function () { return [this.fontStyle, this.fontVariant, this.fontWeight, this.fontSize, this.fontFamily].join(' ') } 
310                                 } 
311                         }
312                         
313                         var that = this;
314                         this.Parse = function(s) {
315                                 var f = {};
316                                 var d = svg.trim(svg.compressSpaces(s || '')).split(' ');
317                                 var set = { fontSize: false, fontStyle: false, fontWeight: false, fontVariant: false }
318                                 var ff = '';
319                                 for (var i=0; i<d.length; i++) {
320                                         if (!set.fontStyle && that.Styles.indexOf(d[i]) != -1) { if (d[i] != 'inherit') f.fontStyle = d[i]; set.fontStyle = true; }
321                                         else if (!set.fontVariant && that.Variants.indexOf(d[i]) != -1) { if (d[i] != 'inherit') f.fontVariant = d[i]; set.fontStyle = set.fontVariant = true;  }
322                                         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; }
323                                         else if (!set.fontSize) { if (d[i] != 'inherit') f.fontSize = d[i].split('/')[0]; set.fontStyle = set.fontVariant = set.fontWeight = set.fontSize = true; }
324                                         else { if (d[i] != 'inherit') ff += d[i]; }
325                                 } if (ff != '') f.fontFamily = ff;
326                                 return f;
327                         }
328                 });
329                 
330                 // points and paths
331                 svg.ToNumberArray = function(s) {
332                         var a = svg.trim(svg.compressSpaces((s || '').replace(/,/g, ' '))).split(' ');
333                         for (var i=0; i<a.length; i++) {
334                                 a[i] = parseFloat(a[i]);
335                         }
336                         return a;
337                 }               
338                 svg.Point = function(x, y) {
339                         this.x = x;
340                         this.y = y;
341                         
342                         this.angleTo = function(p) {
343                                 return Math.atan2(p.y - this.y, p.x - this.x);
344                         }
345                         
346                         this.applyTransform = function(v) {
347                                 var xp = this.x * v[0] + this.y * v[2] + v[4];
348                                 var yp = this.x * v[1] + this.y * v[3] + v[5];
349                                 this.x = xp;
350                                 this.y = yp;
351                         }
352                 }
353                 svg.CreatePoint = function(s) {
354                         var a = svg.ToNumberArray(s);
355                         return new svg.Point(a[0], a[1]);
356                 }
357                 svg.CreatePath = function(s) {
358                         var a = svg.ToNumberArray(s);
359                         var path = [];
360                         for (var i=0; i<a.length; i+=2) {
361                                 path.push(new svg.Point(a[i], a[i+1]));
362                         }
363                         return path;
364                 }
365                 
366                 // bounding box
367                 svg.BoundingBox = function(x1, y1, x2, y2) { // pass in initial points if you want
368                         this.x1 = Number.NaN;
369                         this.y1 = Number.NaN;
370                         this.x2 = Number.NaN;
371                         this.y2 = Number.NaN;
372                         
373                         this.x = function() { return this.x1; }
374                         this.y = function() { return this.y1; }
375                         this.width = function() { return this.x2 - this.x1; }
376                         this.height = function() { return this.y2 - this.y1; }
377                         
378                         this.addPoint = function(x, y) {        
379                                 if (x != null) {
380                                         if (isNaN(this.x1) || isNaN(this.x2)) {
381                                                 this.x1 = x;
382                                                 this.x2 = x;
383                                         }
384                                         if (x < this.x1) this.x1 = x;
385                                         if (x > this.x2) this.x2 = x;
386                                 }
387                         
388                                 if (y != null) {
389                                         if (isNaN(this.y1) || isNaN(this.y2)) {
390                                                 this.y1 = y;
391                                                 this.y2 = y;
392                                         }
393                                         if (y < this.y1) this.y1 = y;
394                                         if (y > this.y2) this.y2 = y;
395                                 }
396                         }                       
397                         this.addX = function(x) { this.addPoint(x, null); }
398                         this.addY = function(y) { this.addPoint(null, y); }
399                         
400                         this.addBoundingBox = function(bb) {
401                                 this.addPoint(bb.x1, bb.y1);
402                                 this.addPoint(bb.x2, bb.y2);
403                         }
404                         
405                         this.addQuadraticCurve = function(p0x, p0y, p1x, p1y, p2x, p2y) {
406                                 var cp1x = p0x + 2/3 * (p1x - p0x); // CP1 = QP0 + 2/3 *(QP1-QP0)
407                                 var cp1y = p0y + 2/3 * (p1y - p0y); // CP1 = QP0 + 2/3 *(QP1-QP0)
408                                 var cp2x = cp1x + 1/3 * (p2x - p0x); // CP2 = CP1 + 1/3 *(QP2-QP0)
409                                 var cp2y = cp1y + 1/3 * (p2y - p0y); // CP2 = CP1 + 1/3 *(QP2-QP0)
410                                 this.addBezierCurve(p0x, p0y, cp1x, cp2x, cp1y, cp2y, p2x, p2y);
411                         }
412                         
413                         this.addBezierCurve = function(p0x, p0y, p1x, p1y, p2x, p2y, p3x, p3y) {
414                                 // from http://blog.hackers-cafe.net/2009/06/how-to-calculate-bezier-curves-bounding.html
415                                 var p0 = [p0x, p0y], p1 = [p1x, p1y], p2 = [p2x, p2y], p3 = [p3x, p3y];
416                                 this.addPoint(p0[0], p0[1]);
417                                 this.addPoint(p3[0], p3[1]);
418                                 
419                                 for (i=0; i<=1; i++) {
420                                         var f = function(t) { 
421                                                 return Math.pow(1-t, 3) * p0[i]
422                                                 + 3 * Math.pow(1-t, 2) * t * p1[i]
423                                                 + 3 * (1-t) * Math.pow(t, 2) * p2[i]
424                                                 + Math.pow(t, 3) * p3[i];
425                                         }
426                                         
427                                         var b = 6 * p0[i] - 12 * p1[i] + 6 * p2[i];
428                                         var a = -3 * p0[i] + 9 * p1[i] - 9 * p2[i] + 3 * p3[i];
429                                         var c = 3 * p1[i] - 3 * p0[i];
430                                         
431                                         if (a == 0) {
432                                                 if (b == 0) continue;
433                                                 var t = -c / b;
434                                                 if (0 < t && t < 1) {
435                                                         if (i == 0) this.addX(f(t));
436                                                         if (i == 1) this.addY(f(t));
437                                                 }
438                                                 continue;
439                                         }
440                                         
441                                         var b2ac = Math.pow(b, 2) - 4 * c * a;
442                                         if (b2ac < 0) continue;
443                                         var t1 = (-b + Math.sqrt(b2ac)) / (2 * a);
444                                         if (0 < t1 && t1 < 1) {
445                                                 if (i == 0) this.addX(f(t1));
446                                                 if (i == 1) this.addY(f(t1));
447                                         }
448                                         var t2 = (-b - Math.sqrt(b2ac)) / (2 * a);
449                                         if (0 < t2 && t2 < 1) {
450                                                 if (i == 0) this.addX(f(t2));
451                                                 if (i == 1) this.addY(f(t2));
452                                         }
453                                 }
454                         }
455                         
456                         this.isPointInBox = function(x, y) {
457                                 return (this.x1 <= x && x <= this.x2 && this.y1 <= y && y <= this.y2);
458                         }
459                         
460                         this.addPoint(x1, y1);
461                         this.addPoint(x2, y2);
462                 }
463                 
464                 // transforms
465                 svg.Transform = function(v) {   
466                         var that = this;
467                         this.Type = {}
468                 
469                         // translate
470                         this.Type.translate = function(s) {
471                                 this.p = svg.CreatePoint(s);                    
472                                 this.apply = function(ctx) {
473                                         ctx.translate(this.p.x || 0.0, this.p.y || 0.0);
474                                 }
475                                 this.applyToPoint = function(p) {
476                                         p.applyTransform([1, 0, 0, 1, this.p.x || 0.0, this.p.y || 0.0]);
477                                 }
478                         }
479                         
480                         // rotate
481                         this.Type.rotate = function(s) {
482                                 var a = svg.ToNumberArray(s);
483                                 this.angle = new svg.Property('angle', a[0]);
484                                 this.cx = a[1] || 0;
485                                 this.cy = a[2] || 0;
486                                 this.apply = function(ctx) {
487                                         ctx.translate(this.cx, this.cy);
488                                         ctx.rotate(this.angle.Angle.toRadians());
489                                         ctx.translate(-this.cx, -this.cy);
490                                 }
491                                 this.applyToPoint = function(p) {
492                                         var a = this.angle.Angle.toRadians();
493                                         p.applyTransform([1, 0, 0, 1, this.p.x || 0.0, this.p.y || 0.0]);
494                                         p.applyTransform([Math.cos(a), Math.sin(a), -Math.sin(a), Math.cos(a), 0, 0]);
495                                         p.applyTransform([1, 0, 0, 1, -this.p.x || 0.0, -this.p.y || 0.0]);
496                                 }                       
497                         }
498                         
499                         this.Type.scale = function(s) {
500                                 this.p = svg.CreatePoint(s);
501                                 this.apply = function(ctx) {
502                                         ctx.scale(this.p.x || 1.0, this.p.y || this.p.x || 1.0);
503                                 }
504                                 this.applyToPoint = function(p) {
505                                         p.applyTransform([this.p.x || 0.0, 0, 0, this.p.y || 0.0, 0, 0]);
506                                 }                               
507                         }
508                         
509                         this.Type.matrix = function(s) {
510                                 this.m = svg.ToNumberArray(s);
511                                 this.apply = function(ctx) {
512                                         ctx.transform(this.m[0], this.m[1], this.m[2], this.m[3], this.m[4], this.m[5]);
513                                 }
514                                 this.applyToPoint = function(p) {
515                                         p.applyTransform(this.m);
516                                 }                                       
517                         }
518                         
519                         this.Type.SkewBase = function(s) {
520                                 this.base = that.Type.matrix;
521                                 this.base(s);
522                                 this.angle = new svg.Property('angle', s);
523                         }
524                         this.Type.SkewBase.prototype = new this.Type.matrix;
525                         
526                         this.Type.skewX = function(s) {
527                                 this.base = that.Type.SkewBase;
528                                 this.base(s);
529                                 this.m = [1, 0, Math.tan(this.angle.Angle.toRadians()), 1, 0, 0];
530                         }
531                         this.Type.skewX.prototype = new this.Type.SkewBase;
532                         
533                         this.Type.skewY = function(s) {
534                                 this.base = that.Type.SkewBase;
535                                 this.base(s);
536                                 this.m = [1, Math.tan(this.angle.Angle.toRadians()), 0, 1, 0, 0];
537                         }
538                         this.Type.skewY.prototype = new this.Type.SkewBase;
539                 
540                         this.transforms = [];
541                         
542                         this.apply = function(ctx) {
543                                 for (var i=0; i<this.transforms.length; i++) {
544                                         this.transforms[i].apply(ctx);
545                                 }
546                         }
547                         
548                         this.applyToPoint = function(p) {
549                                 for (var i=0; i<this.transforms.length; i++) {
550                                         this.transforms[i].applyToPoint(p);
551                                 }
552                         }
553                         
554                         var data = v.split(/\s(?=[a-z])/);
555                         for (var i=0; i<data.length; i++) {
556                                 var type = data[i].split('(')[0];
557                                 var s = data[i].split('(')[1].replace(')','');
558                                 var transform = new this.Type[type](s);
559                                 this.transforms.push(transform);
560                         }
561                 }
562                 
563                 // aspect ratio
564                 svg.AspectRatio = function(ctx, aspectRatio, width, desiredWidth, height, desiredHeight, minX, minY, refX, refY) {
565                         // aspect ratio - http://www.w3.org/TR/SVG/coords.html#PreserveAspectRatioAttribute
566                         aspectRatio = svg.compressSpaces(aspectRatio);
567                         aspectRatio = aspectRatio.replace(/^defer\s/,''); // ignore defer
568                         var align = aspectRatio.split(' ')[0] || 'xMidYMid';
569                         var meetOrSlice = aspectRatio.split(' ')[1] || 'meet';                                  
570         
571                         // calculate scale
572                         var scaleX = width / desiredWidth;
573                         var scaleY = height / desiredHeight;
574                         var scaleMin = Math.min(scaleX, scaleY);
575                         var scaleMax = Math.max(scaleX, scaleY);
576                         if (meetOrSlice == 'meet') { desiredWidth *= scaleMin; desiredHeight *= scaleMin; }
577                         if (meetOrSlice == 'slice') { desiredWidth *= scaleMax; desiredHeight *= scaleMax; }    
578                         
579                         refX = new svg.Property('refX', refX);
580                         refY = new svg.Property('refY', refY);
581                         if (refX.hasValue() && refY.hasValue()) {                               
582                                 ctx.translate(-scaleMin * refX.Length.toPixels('x'), -scaleMin * refY.Length.toPixels('y'));
583                         } 
584                         else {                                  
585                                 // align
586                                 if (align.match(/^xMid/) && ((meetOrSlice == 'meet' && scaleMin == scaleY) || (meetOrSlice == 'slice' && scaleMax == scaleY))) ctx.translate(width / 2.0 - desiredWidth / 2.0, 0); 
587                                 if (align.match(/YMid$/) && ((meetOrSlice == 'meet' && scaleMin == scaleX) || (meetOrSlice == 'slice' && scaleMax == scaleX))) ctx.translate(0, height / 2.0 - desiredHeight / 2.0); 
588                                 if (align.match(/^xMax/) && ((meetOrSlice == 'meet' && scaleMin == scaleY) || (meetOrSlice == 'slice' && scaleMax == scaleY))) ctx.translate(width - desiredWidth, 0); 
589                                 if (align.match(/YMax$/) && ((meetOrSlice == 'meet' && scaleMin == scaleX) || (meetOrSlice == 'slice' && scaleMax == scaleX))) ctx.translate(0, height - desiredHeight); 
590                         }
591                         
592                         // scale
593                         if (align == 'none') ctx.scale(scaleX, scaleY);
594                         else if (meetOrSlice == 'meet') ctx.scale(scaleMin, scaleMin); 
595                         else if (meetOrSlice == 'slice') ctx.scale(scaleMax, scaleMax);         
596                         
597                         // translate
598                         ctx.translate(minX == null ? 0 : -minX, minY == null ? 0 : -minY);                      
599                 }
600                 
601                 // elements
602                 svg.Element = {}
603                 
604                 svg.Element.ElementBase = function(node) {      
605                         this.attributes = {};
606                         this.styles = {};
607                         this.children = [];
608                         
609                         // get or create attribute
610                         this.attribute = function(name, createIfNotExists) {
611                                 var a = this.attributes[name];
612                                 if (a != null) return a;
613                                                         
614                                 a = new svg.Property(name, '');
615                                 if (createIfNotExists == true) this.attributes[name] = a;
616                                 return a;
617                         }
618                         
619                         // get or create style
620                         this.style = function(name, createIfNotExists) {
621                                 var s = this.styles[name];
622                                 if (s != null) return s;
623                                 
624                                 var a = this.attribute(name);
625                                 if (a != null && a.hasValue()) {
626                                         return a;
627                                 }
628                                         
629                                 s = new svg.Property(name, '');
630                                 if (createIfNotExists == true) this.styles[name] = s;
631                                 return s;
632                         }
633                         
634                         // base render
635                         this.render = function(ctx) {
636                                 // don't render display=none
637                                 if (this.attribute('display').value == 'none') return;
638                         
639                                 ctx.save();
640                                 this.setContext(ctx);
641                                 this.renderChildren(ctx);
642                                 this.clearContext(ctx);
643                                 ctx.restore();
644                         }
645                         
646                         // base set context
647                         this.setContext = function(ctx) {
648                                 // OVERRIDE ME!
649                         }
650                         
651                         // base clear context
652                         this.clearContext = function(ctx) {
653                                 // OVERRIDE ME!
654                         }                       
655                         
656                         // base render children
657                         this.renderChildren = function(ctx) {
658                                 for (var i=0; i<this.children.length; i++) {
659                                         this.children[i].render(ctx);
660                                 }
661                         }
662                         
663                         this.addChild = function(childNode, create) {
664                                 var child = childNode;
665                                 if (create) child = svg.CreateElement(childNode);
666                                 child.parent = this;
667                                 this.children.push(child);                      
668                         }
669                                 
670                         if (node != null && node.nodeType == 1) { //ELEMENT_NODE
671                                 // add children
672                                 for (var i=0; i<node.childNodes.length; i++) {
673                                         var childNode = node.childNodes[i];
674                                         if (childNode.nodeType == 1) this.addChild(childNode, true); //ELEMENT_NODE
675                                 }
676                                 
677                                 // add attributes
678                                 for (var i=0; i<node.attributes.length; i++) {
679                                         var attribute = node.attributes[i];
680                                         this.attributes[attribute.nodeName] = new svg.Property(attribute.nodeName, attribute.nodeValue);
681                                 }
682                                                                                 
683                                 // add tag styles
684                                 var styles = svg.Styles[this.type];
685                                 if (styles != null) {
686                                         for (var name in styles) {
687                                                 this.styles[name] = styles[name];
688                                         }
689                                 }                                       
690                                 
691                                 // add class styles
692                                 if (this.attribute('class').hasValue()) {
693                                         var classes = svg.compressSpaces(this.attribute('class').value).split(' ');
694                                         for (var j=0; j<classes.length; j++) {
695                                                 styles = svg.Styles['.'+classes[j]];
696                                                 if (styles != null) {
697                                                         for (var name in styles) {
698                                                                 this.styles[name] = styles[name];
699                                                         }
700                                                 }
701                                         }
702                                 }
703                                 
704                                 // add inline styles
705                                 if (this.attribute('style').hasValue()) {
706                                         var styles = this.attribute('style').value.split(';');
707                                         for (var i=0; i<styles.length; i++) {
708                                                 if (svg.trim(styles[i]) != '') {
709                                                         var style = styles[i].split(':');
710                                                         var name = svg.trim(style[0]);
711                                                         var value = svg.trim(style[1]);
712                                                         this.styles[name] = new svg.Property(name, value);
713                                                 }
714                                         }
715                                 }       
717                                 // add id
718                                 if (this.attribute('id').hasValue()) {
719                                         if (svg.Definitions[this.attribute('id').value] == null) {
720                                                 svg.Definitions[this.attribute('id').value] = this;
721                                         }
722                                 }
723                         }
724                 }
725                 
726                 svg.Element.RenderedElementBase = function(node) {
727                         this.base = svg.Element.ElementBase;
728                         this.base(node);
729                         
730                         this.setContext = function(ctx) {
731                                 // fill
732                                 if (this.style('fill').Definition.isUrl()) {
733                                         var fs = this.style('fill').Definition.getFillStyle(this);
734                                         if (fs != null) ctx.fillStyle = fs;
735                                 }
736                                 else if (this.style('fill').hasValue()) {
737                                         var fillStyle = this.style('fill');
738                                         if (this.style('fill-opacity').hasValue()) fillStyle = fillStyle.Color.addOpacity(this.style('fill-opacity').value);
739                                         ctx.fillStyle = (fillStyle.value == 'none' ? 'rgba(0,0,0,0)' : fillStyle.value);
740                                 }
741                                                                         
742                                 // stroke
743                                 if (this.style('stroke').Definition.isUrl()) {
744                                         var fs = this.style('stroke').Definition.getFillStyle(this);
745                                         if (fs != null) ctx.strokeStyle = fs;
746                                 }
747                                 else if (this.style('stroke').hasValue()) {
748                                         var strokeStyle = this.style('stroke');
749                                         if (this.style('stroke-opacity').hasValue()) strokeStyle = strokeStyle.Color.addOpacity(this.style('stroke-opacity').value);
750                                         ctx.strokeStyle = (strokeStyle.value == 'none' ? 'rgba(0,0,0,0)' : strokeStyle.value);
751                                 }
752                                 if (this.style('stroke-width').hasValue()) ctx.lineWidth = this.style('stroke-width').Length.toPixels();
753                                 if (this.style('stroke-linecap').hasValue()) ctx.lineCap = this.style('stroke-linecap').value;
754                                 if (this.style('stroke-linejoin').hasValue()) ctx.lineJoin = this.style('stroke-linejoin').value;
755                                 if (this.style('stroke-miterlimit').hasValue()) ctx.miterLimit = this.style('stroke-miterlimit').value;
757                                 // font
758                                 if (typeof(ctx.font) != 'undefined') {
759                                         ctx.font = svg.Font.CreateFont( 
760                                                 this.style('font-style').value, 
761                                                 this.style('font-variant').value, 
762                                                 this.style('font-weight').value, 
763                                                 this.style('font-size').hasValue() ? this.style('font-size').Length.toPixels() + 'px' : '', 
764                                                 this.style('font-family').value).toString();
765                                 }
766                                 
767                                 // transform
768                                 if (this.attribute('transform').hasValue()) { 
769                                         var transform = new svg.Transform(this.attribute('transform').value);
770                                         transform.apply(ctx);
771                                 }
772                                 
773                                 // clip
774                                 if (this.attribute('clip-path').hasValue()) {
775                                         var clip = this.attribute('clip-path').Definition.getDefinition();
776                                         if (clip != null) clip.apply(ctx);
777                                 }
778                                 
779                                 // opacity
780                                 if (this.style('opacity').hasValue()) {
781                                         ctx.globalAlpha = this.style('opacity').numValue();
782                                 }
783                         }               
784                 }
785                 svg.Element.RenderedElementBase.prototype = new svg.Element.ElementBase;
786                 
787                 svg.Element.PathElementBase = function(node) {
788                         this.base = svg.Element.RenderedElementBase;
789                         this.base(node);
790                         
791                         this.path = function(ctx) {
792                                 if (ctx != null) ctx.beginPath();
793                                 return new svg.BoundingBox();
794                         }
795                         
796                         this.renderChildren = function(ctx) {
797                                 this.path(ctx);
798                                 svg.Mouse.checkPath(this, ctx);
799                                 if (ctx.fillStyle != '') ctx.fill();
800                                 if (ctx.strokeStyle != '') ctx.stroke();
801                                 
802                                 var markers = this.getMarkers();
803                                 if (markers != null) {
804                                         if (this.attribute('marker-start').Definition.isUrl()) {
805                                                 var marker = this.attribute('marker-start').Definition.getDefinition();
806                                                 marker.render(ctx, markers[0][0], markers[0][1]);
807                                         }
808                                         if (this.attribute('marker-mid').Definition.isUrl()) {
809                                                 var marker = this.attribute('marker-mid').Definition.getDefinition();
810                                                 for (var i=1;i<markers.length-1;i++) {
811                                                         marker.render(ctx, markers[i][0], markers[i][1]);
812                                                 }
813                                         }
814                                         if (this.attribute('marker-end').Definition.isUrl()) {
815                                                 var marker = this.attribute('marker-end').Definition.getDefinition();
816                                                 marker.render(ctx, markers[markers.length-1][0], markers[markers.length-1][1]);
817                                         }
818                                 }                                       
819                         }
820                         
821                         this.getBoundingBox = function() {
822                                 return this.path();
823                         }
824                         
825                         this.getMarkers = function() {
826                                 return null;
827                         }
828                 }
829                 svg.Element.PathElementBase.prototype = new svg.Element.RenderedElementBase;
830                 
831                 // svg element
832                 svg.Element.svg = function(node) {
833                         this.base = svg.Element.RenderedElementBase;
834                         this.base(node);
835                         
836                         this.baseClearContext = this.clearContext;
837                         this.clearContext = function(ctx) {
838                                 this.baseClearContext(ctx);
839                                 svg.ViewPort.RemoveCurrent();
840                         }
841                         
842                         this.baseSetContext = this.setContext;
843                         this.setContext = function(ctx) {
844                                 // initial values
845                                 ctx.strokeStyle = 'rgba(0,0,0,0)';
846                                 ctx.lineCap = 'butt';
847                                 ctx.lineJoin = 'miter';
848                                 ctx.miterLimit = 4;                     
849                         
850                                 this.baseSetContext(ctx);
851                                 
852                                 // create new view port
853                                 if (this.attribute('x').hasValue() && this.attribute('y').hasValue()) {
854                                         ctx.translate(this.attribute('x').Length.toPixels('x'), this.attribute('y').Length.toPixels('y'));
855                                 }
856                                 
857                                 var width = svg.ViewPort.width();
858                                 var height = svg.ViewPort.height();
859                                 if (this.attribute('width').hasValue() && this.attribute('height').hasValue()) {
860                                         width = this.attribute('width').Length.toPixels('x');
861                                         height = this.attribute('height').Length.toPixels('y');
862                                         
863                                         var x = 0;
864                                         var y = 0;
865                                         if (this.attribute('refX').hasValue() && this.attribute('refY').hasValue()) {
866                                                 x = -this.attribute('refX').Length.toPixels('x');
867                                                 y = -this.attribute('refY').Length.toPixels('y');
868                                         }
869                                         
870                                         ctx.beginPath();
871                                         ctx.moveTo(x, y);
872                                         ctx.lineTo(width, y);
873                                         ctx.lineTo(width, height);
874                                         ctx.lineTo(x, height);
875                                         ctx.closePath();
876                                         ctx.clip();
877                                 }
878                                 svg.ViewPort.SetCurrent(width, height); 
879                                                 
880                                 // viewbox
881                                 if (this.attribute('viewBox').hasValue()) {                             
882                                         var viewBox = svg.ToNumberArray(this.attribute('viewBox').value);
883                                         var minX = viewBox[0];
884                                         var minY = viewBox[1];
885                                         width = viewBox[2];
886                                         height = viewBox[3];
887                                         
888                                         svg.AspectRatio(ctx,
889                                                                         this.attribute('preserveAspectRatio').value, 
890                                                                         svg.ViewPort.width(), 
891                                                                         width,
892                                                                         svg.ViewPort.height(),
893                                                                         height,
894                                                                         minX,
895                                                                         minY,
896                                                                         this.attribute('refX').value,
897                                                                         this.attribute('refY').value);
898                                                                                 
899                                         svg.ViewPort.RemoveCurrent();   
900                                         svg.ViewPort.SetCurrent(viewBox[2], viewBox[3]);                                                
901                                 }                               
902                         }
903                 }
904                 svg.Element.svg.prototype = new svg.Element.RenderedElementBase;
906                 // rect element
907                 svg.Element.rect = function(node) {
908                         this.base = svg.Element.PathElementBase;
909                         this.base(node);
910                         
911                         this.path = function(ctx) {
912                                 var x = this.attribute('x').Length.toPixels('x');
913                                 var y = this.attribute('y').Length.toPixels('y');
914                                 var width = this.attribute('width').Length.toPixels('x');
915                                 var height = this.attribute('height').Length.toPixels('y');
916                                 var rx = this.attribute('rx').Length.toPixels('x');
917                                 var ry = this.attribute('ry').Length.toPixels('y');
918                                 if (this.attribute('rx').hasValue() && !this.attribute('ry').hasValue()) ry = rx;
919                                 if (this.attribute('ry').hasValue() && !this.attribute('rx').hasValue()) rx = ry;
920                                 
921                                 if (ctx != null) {
922                                         ctx.beginPath();
923                                         ctx.moveTo(x + rx, y);
924                                         ctx.lineTo(x + width - rx, y);
925                                         ctx.quadraticCurveTo(x + width, y, x + width, y + ry)
926                                         ctx.lineTo(x + width, y + height - ry);
927                                         ctx.quadraticCurveTo(x + width, y + height, x + width - rx, y + height)
928                                         ctx.lineTo(x + rx, y + height);
929                                         ctx.quadraticCurveTo(x, y + height, x, y + height - ry)
930                                         ctx.lineTo(x, y + ry);
931                                         ctx.quadraticCurveTo(x, y, x + rx, y)
932                                         ctx.closePath();
933                                 }
934                                 
935                                 return new svg.BoundingBox(x, y, x + width, y + height);
936                         }
937                 }
938                 svg.Element.rect.prototype = new svg.Element.PathElementBase;
939                 
940                 // circle element
941                 svg.Element.circle = function(node) {
942                         this.base = svg.Element.PathElementBase;
943                         this.base(node);
944                         
945                         this.path = function(ctx) {
946                                 var cx = this.attribute('cx').Length.toPixels('x');
947                                 var cy = this.attribute('cy').Length.toPixels('y');
948                                 var r = this.attribute('r').Length.toPixels();
949                         
950                                 if (ctx != null) {
951                                         ctx.beginPath();
952                                         ctx.arc(cx, cy, r, 0, Math.PI * 2, true); 
953                                         ctx.closePath();
954                                 }
955                                 
956                                 return new svg.BoundingBox(cx - r, cy - r, cx + r, cy + r);
957                         }
958                 }
959                 svg.Element.circle.prototype = new svg.Element.PathElementBase; 
961                 // ellipse element
962                 svg.Element.ellipse = function(node) {
963                         this.base = svg.Element.PathElementBase;
964                         this.base(node);
965                         
966                         this.path = function(ctx) {
967                                 var KAPPA = 4 * ((Math.sqrt(2) - 1) / 3);
968                                 var rx = this.attribute('rx').Length.toPixels('x');
969                                 var ry = this.attribute('ry').Length.toPixels('y');
970                                 var cx = this.attribute('cx').Length.toPixels('x');
971                                 var cy = this.attribute('cy').Length.toPixels('y');
972                                 
973                                 if (ctx != null) {
974                                         ctx.beginPath();
975                                         ctx.moveTo(cx, cy - ry);
976                                         ctx.bezierCurveTo(cx + (KAPPA * rx), cy - ry,  cx + rx, cy - (KAPPA * ry), cx + rx, cy);
977                                         ctx.bezierCurveTo(cx + rx, cy + (KAPPA * ry), cx + (KAPPA * rx), cy + ry, cx, cy + ry);
978                                         ctx.bezierCurveTo(cx - (KAPPA * rx), cy + ry, cx - rx, cy + (KAPPA * ry), cx - rx, cy);
979                                         ctx.bezierCurveTo(cx - rx, cy - (KAPPA * ry), cx - (KAPPA * rx), cy - ry, cx, cy - ry);
980                                         ctx.closePath();
981                                 }
982                                 
983                                 return new svg.BoundingBox(cx - rx, cy - ry, cx + rx, cy + ry);
984                         }
985                 }
986                 svg.Element.ellipse.prototype = new svg.Element.PathElementBase;                        
987                 
988                 // line element
989                 svg.Element.line = function(node) {
990                         this.base = svg.Element.PathElementBase;
991                         this.base(node);
992                         
993                         this.getPoints = function() {
994                                 return [
995                                         new svg.Point(this.attribute('x1').Length.toPixels('x'), this.attribute('y1').Length.toPixels('y')),
996                                         new svg.Point(this.attribute('x2').Length.toPixels('x'), this.attribute('y2').Length.toPixels('y'))];
997                         }
998                                                                 
999                         this.path = function(ctx) {
1000                                 var points = this.getPoints();
1001                                 
1002                                 if (ctx != null) {
1003                                         ctx.beginPath();
1004                                         ctx.moveTo(points[0].x, points[0].y);
1005                                         ctx.lineTo(points[1].x, points[1].y);
1006                                 }
1007                                 
1008                                 return new svg.BoundingBox(points[0].x, points[0].y, points[1].x, points[1].y);
1009                         }
1010                         
1011                         this.getMarkers = function() {
1012                                 var points = this.getPoints();  
1013                                 var a = points[0].angleTo(points[1]);
1014                                 return [[points[0], a], [points[1], a]];
1015                         }
1016                 }
1017                 svg.Element.line.prototype = new svg.Element.PathElementBase;           
1018                                 
1019                 // polyline element
1020                 svg.Element.polyline = function(node) {
1021                         this.base = svg.Element.PathElementBase;
1022                         this.base(node);
1023                         
1024                         this.points = svg.CreatePath(this.attribute('points').value);
1025                         this.path = function(ctx) {
1026                                 var bb = new svg.BoundingBox(this.points[0].x, this.points[0].y);
1027                                 if (ctx != null) {
1028                                         ctx.beginPath();
1029                                         ctx.moveTo(this.points[0].x, this.points[0].y);
1030                                 }
1031                                 for (var i=1; i<this.points.length; i++) {
1032                                         bb.addPoint(this.points[i].x, this.points[i].y);
1033                                         if (ctx != null) ctx.lineTo(this.points[i].x, this.points[i].y);
1034                                 }
1035                                 return bb;
1036                         }
1037                         
1038                         this.getMarkers = function() {
1039                                 var markers = [];
1040                                 for (var i=0; i<this.points.length - 1; i++) {
1041                                         markers.push([this.points[i], this.points[i].angleTo(this.points[i+1])]);
1042                                 }
1043                                 markers.push([this.points[this.points.length-1], markers[markers.length-1][1]]);
1044                                 return markers;
1045                         }                       
1046                 }
1047                 svg.Element.polyline.prototype = new svg.Element.PathElementBase;                               
1048                                 
1049                 // polygon element
1050                 svg.Element.polygon = function(node) {
1051                         this.base = svg.Element.polyline;
1052                         this.base(node);
1053                         
1054                         this.basePath = this.path;
1055                         this.path = function(ctx) {
1056                                 var bb = this.basePath(ctx);
1057                                 if (ctx != null) {
1058                                         ctx.lineTo(this.points[0].x, this.points[0].y);
1059                                         ctx.closePath();
1060                                 }
1061                                 return bb;
1062                         }
1063                 }
1064                 svg.Element.polygon.prototype = new svg.Element.polyline;
1066                 // path element
1067                 svg.Element.path = function(node) {
1068                         this.base = svg.Element.PathElementBase;
1069                         this.base(node);
1070                                         
1071                         var d = this.attribute('d').value;
1072                         // TODO: floating points, convert to real lexer based on http://www.w3.org/TR/SVG11/paths.html#PathDataBNF
1073                         d = d.replace(/,/gm,' '); // get rid of all commas
1074                         d = d.replace(/([MmZzLlHhVvCcSsQqTtAa])([MmZzLlHhVvCcSsQqTtAa])/gm,'$1 $2'); // separate commands from commands
1075                         d = d.replace(/([MmZzLlHhVvCcSsQqTtAa])([MmZzLlHhVvCcSsQqTtAa])/gm,'$1 $2'); // separate commands from commands
1076                         d = d.replace(/([MmZzLlHhVvCcSsQqTtAa])([^\s])/gm,'$1 $2'); // separate commands from points
1077                         d = d.replace(/([^\s])([MmZzLlHhVvCcSsQqTtAa])/gm,'$1 $2'); // separate commands from points
1078                         d = d.replace(/([0-9])([+\-])/gm,'$1 $2'); // separate digits when no comma
1079                         d = d.replace(/(\.[0-9]*)(\.)/gm,'$1 $2'); // separate digits when no comma
1080                         d = d.replace(/([Aa](\s+[0-9]+){3})\s+([01])\s*([01])/gm,'$1 $3 $4 '); // shorthand elliptical arc path syntax
1081                         d = svg.compressSpaces(d); // compress multiple spaces
1082                         d = svg.trim(d);
1083                         this.PathParser = new (function(d) {
1084                                 this.tokens = d.split(' ');
1085                                 
1086                                 this.reset = function() {
1087                                         this.i = -1;
1088                                         this.command = '';
1089                                         this.previousCommand = '';
1090                                         this.start = new svg.Point(0, 0);
1091                                         this.control = new svg.Point(0, 0);
1092                                         this.current = new svg.Point(0, 0);
1093                                         this.points = [];
1094                                         this.angles = [];
1095                                 }
1096                                                                 
1097                                 this.isEnd = function() {
1098                                         return this.i >= this.tokens.length - 1;
1099                                 }
1100                                 
1101                                 this.isCommandOrEnd = function() {
1102                                         if (this.isEnd()) return true;
1103                                         return this.tokens[this.i + 1].match(/[A-Za-z]/) != null;
1104                                 }
1105                                 
1106                                 this.isRelativeCommand = function() {
1107                                         return this.command == this.command.toLowerCase();
1108                                 }
1109                                                         
1110                                 this.getToken = function() {
1111                                         this.i = this.i + 1;
1112                                         return this.tokens[this.i];
1113                                 }
1114                                 
1115                                 this.getScalar = function() {
1116                                         return parseFloat(this.getToken());
1117                                 }
1118                                 
1119                                 this.nextCommand = function() {
1120                                         this.previousCommand = this.command;
1121                                         this.command = this.getToken();
1122                                 }                               
1123                                 
1124                                 this.getPoint = function() {
1125                                         var p = new svg.Point(this.getScalar(), this.getScalar());
1126                                         return this.makeAbsolute(p);
1127                                 }
1128                                 
1129                                 this.getAsControlPoint = function() {
1130                                         var p = this.getPoint();
1131                                         this.control = p;
1132                                         return p;
1133                                 }
1134                                 
1135                                 this.getAsCurrentPoint = function() {
1136                                         var p = this.getPoint();
1137                                         this.current = p;
1138                                         return p;       
1139                                 }
1140                                 
1141                                 this.getReflectedControlPoint = function() {
1142                                         if (this.previousCommand.toLowerCase() != 'c' && this.previousCommand.toLowerCase() != 's') {
1143                                                 return this.current;
1144                                         }
1145                                         
1146                                         // reflect point
1147                                         var p = new svg.Point(2 * this.current.x - this.control.x, 2 * this.current.y - this.control.y);                                        
1148                                         return p;
1149                                 }
1150                                 
1151                                 this.makeAbsolute = function(p) {
1152                                         if (this.isRelativeCommand()) {
1153                                                 p.x = this.current.x + p.x;
1154                                                 p.y = this.current.y + p.y;
1155                                         }
1156                                         return p;
1157                                 }
1158                                 
1159                                 this.addMarker = function(p, from) {
1160                                         this.addMarkerAngle(p, from == null ? null : from.angleTo(p));
1161                                 }
1162                                 
1163                                 this.addMarkerAngle = function(p, a) {
1164                                         this.points.push(p);
1165                                         this.angles.push(a);
1166                                 }                               
1167                                 
1168                                 this.getMarkerPoints = function() { return this.points; }
1169                                 this.getMarkerAngles = function() {
1170                                         for (var i=0; i<this.angles.length; i++) {
1171                                                 if (this.angles[i] == null) {
1172                                                         for (var j=i+1; j<this.angles.length; j++) {
1173                                                                 if (this.angles[j] != null) {
1174                                                                         this.angles[i] = this.angles[j];
1175                                                                         break;
1176                                                                 }
1177                                                         }
1178                                                 }
1179                                         }
1180                                         return this.angles;
1181                                 }
1182                         })(d);
1184                         this.path = function(ctx) {
1185                                 var pp = this.PathParser;
1186                                 pp.reset();
1188                                 var bb = new svg.BoundingBox();
1189                                 
1190                                 if(this.attribute('visibility').value=='hidden') return;
1191                                 
1192                                 if (ctx != null) ctx.beginPath();
1193                                 while (!pp.isEnd()) {
1194                                         pp.nextCommand();
1195                                         switch (pp.command.toUpperCase()) {
1196                                         case 'M':
1197                                                 var p = pp.getAsCurrentPoint();
1198                                                 pp.addMarker(p);
1199                                                 bb.addPoint(p.x, p.y);
1200                                                 if (ctx != null) ctx.moveTo(p.x, p.y);
1201                                                 pp.start = pp.current;
1202                                                 while (!pp.isCommandOrEnd()) {
1203                                                         var p = pp.getAsCurrentPoint();
1204                                                         pp.addMarker(p);
1205                                                         bb.addPoint(p.x, p.y);
1206                                                         if (ctx != null) ctx.lineTo(p.x, p.y);
1207                                                 }
1208                                                 break;
1209                                         case 'L':
1210                                                 while (!pp.isCommandOrEnd()) {
1211                                                         var c = pp.current;
1212                                                         var p = pp.getAsCurrentPoint();
1213                                                         pp.addMarker(p, c);
1214                                                         bb.addPoint(p.x, p.y);
1215                                                         if (ctx != null) ctx.lineTo(p.x, p.y);
1216                                                 }
1217                                                 break;
1218                                         case 'H':
1219                                                 while (!pp.isCommandOrEnd()) {
1220                                                         var newP = new svg.Point((pp.isRelativeCommand() ? pp.current.x : 0) + pp.getScalar(), pp.current.y);
1221                                                         pp.addMarker(newP, pp.current);
1222                                                         pp.current = newP;
1223                                                         bb.addPoint(pp.current.x, pp.current.y);
1224                                                         if (ctx != null) ctx.lineTo(pp.current.x, pp.current.y);
1225                                                 }
1226                                                 break;
1227                                         case 'V':
1228                                                 while (!pp.isCommandOrEnd()) {
1229                                                         var newP = new svg.Point(pp.current.x, (pp.isRelativeCommand() ? pp.current.y : 0) + pp.getScalar());
1230                                                         pp.addMarker(newP, pp.current);
1231                                                         pp.current = newP;
1232                                                         bb.addPoint(pp.current.x, pp.current.y);
1233                                                         if (ctx != null) ctx.lineTo(pp.current.x, pp.current.y);
1234                                                 }
1235                                                 break;
1236                                         case 'C':
1237                                                 while (!pp.isCommandOrEnd()) {
1238                                                         var curr = pp.current;
1239                                                         var p1 = pp.getPoint();
1240                                                         var cntrl = pp.getAsControlPoint();
1241                                                         var cp = pp.getAsCurrentPoint();
1242                                                         pp.addMarker(cp, cntrl);
1243                                                         bb.addBezierCurve(curr.x, curr.y, p1.x, p1.y, cntrl.x, cntrl.y, cp.x, cp.y);
1244                                                         if (ctx != null) ctx.bezierCurveTo(p1.x, p1.y, cntrl.x, cntrl.y, cp.x, cp.y);
1245                                                 }
1246                                                 break;
1247                                         case 'S':
1248                                                 while (!pp.isCommandOrEnd()) {
1249                                                         var curr = pp.current;
1250                                                         var p1 = pp.getReflectedControlPoint();
1251                                                         var cntrl = pp.getAsControlPoint();
1252                                                         var cp = pp.getAsCurrentPoint();
1253                                                         pp.addMarker(cp, cntrl);
1254                                                         bb.addBezierCurve(curr.x, curr.y, p1.x, p1.y, cntrl.x, cntrl.y, cp.x, cp.y);
1255                                                         if (ctx != null) ctx.bezierCurveTo(p1.x, p1.y, cntrl.x, cntrl.y, cp.x, cp.y);
1256                                                 }
1257                                                 break;
1258                                         case 'Q':
1259                                                 while (!pp.isCommandOrEnd()) {
1260                                                         var curr = pp.current;
1261                                                         var cntrl = pp.getAsControlPoint();
1262                                                         var cp = pp.getAsCurrentPoint();
1263                                                         pp.addMarker(cp, cntrl);
1264                                                         bb.addQuadraticCurve(curr.x, curr.y, cntrl.x, cntrl.y, cp.x, cp.y);
1265                                                         if (ctx != null) ctx.quadraticCurveTo(cntrl.x, cntrl.y, cp.x, cp.y);
1266                                                 }
1267                                                 break;
1268                                         case 'T':
1269                                                 while (!pp.isCommandOrEnd()) {
1270                                                         var curr = pp.current;
1271                                                         var cntrl = pp.getReflectedControlPoint();
1272                                                         pp.control = cntrl;
1273                                                         var cp = pp.getAsCurrentPoint();
1274                                                         pp.addMarker(cp, cntrl);
1275                                                         bb.addQuadraticCurve(curr.x, curr.y, cntrl.x, cntrl.y, cp.x, cp.y);
1276                                                         if (ctx != null) ctx.quadraticCurveTo(cntrl.x, cntrl.y, cp.x, cp.y);
1277                                                 }
1278                                                 break;
1279                                         case 'A':
1280                                                 while (!pp.isCommandOrEnd()) {
1281                                                     var curr = pp.current;
1282                                                         var rx = pp.getScalar();
1283                                                         var ry = pp.getScalar();
1284                                                         var xAxisRotation = pp.getScalar() * (Math.PI / 180.0);
1285                                                         var largeArcFlag = pp.getScalar();
1286                                                         var sweepFlag = pp.getScalar();
1287                                                         var cp = pp.getAsCurrentPoint();
1289                                                         // Conversion from endpoint to center parameterization
1290                                                         // http://www.w3.org/TR/SVG11/implnote.html#ArcImplementationNotes
1291                                                         // x1', y1'
1292                                                         var currp = new svg.Point(
1293                                                                 Math.cos(xAxisRotation) * (curr.x - cp.x) / 2.0 + Math.sin(xAxisRotation) * (curr.y - cp.y) / 2.0,
1294                                                                 -Math.sin(xAxisRotation) * (curr.x - cp.x) / 2.0 + Math.cos(xAxisRotation) * (curr.y - cp.y) / 2.0
1295                                                         );
1296                                                         // adjust radii
1297                                                         var l = Math.pow(currp.x,2)/Math.pow(rx,2)+Math.pow(currp.y,2)/Math.pow(ry,2);
1298                                                         if (l > 1) {
1299                                                                 rx *= Math.sqrt(l);
1300                                                                 ry *= Math.sqrt(l);
1301                                                         }
1302                                                         // cx', cy'
1303                                                         var s = (largeArcFlag == sweepFlag ? -1 : 1) * Math.sqrt(
1304                                                                 ((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))) /
1305                                                                 (Math.pow(rx,2)*Math.pow(currp.y,2)+Math.pow(ry,2)*Math.pow(currp.x,2))
1306                                                         );
1307                                                         if (isNaN(s)) s = 0;
1308                                                         var cpp = new svg.Point(s * rx * currp.y / ry, s * -ry * currp.x / rx);
1309                                                         // cx, cy
1310                                                         var centp = new svg.Point(
1311                                                                 (curr.x + cp.x) / 2.0 + Math.cos(xAxisRotation) * cpp.x - Math.sin(xAxisRotation) * cpp.y,
1312                                                                 (curr.y + cp.y) / 2.0 + Math.sin(xAxisRotation) * cpp.x + Math.cos(xAxisRotation) * cpp.y
1313                                                         );
1314                                                         // vector magnitude
1315                                                         var m = function(v) { return Math.sqrt(Math.pow(v[0],2) + Math.pow(v[1],2)); }
1316                                                         // ratio between two vectors
1317                                                         var r = function(u, v) { return (u[0]*v[0]+u[1]*v[1]) / (m(u)*m(v)) }
1318                                                         // angle between two vectors
1319                                                         var a = function(u, v) { return (u[0]*v[1] < u[1]*v[0] ? -1 : 1) * Math.acos(r(u,v)); }
1320                                                         // initial angle
1321                                                         var a1 = a([1,0], [(currp.x-cpp.x)/rx,(currp.y-cpp.y)/ry]);
1322                                                         // angle delta
1323                                                         var u = [(currp.x-cpp.x)/rx,(currp.y-cpp.y)/ry];
1324                                                         var v = [(-currp.x-cpp.x)/rx,(-currp.y-cpp.y)/ry];
1325                                                         var ad = a(u, v);
1326                                                         if (r(u,v) <= -1) ad = Math.PI;
1327                                                         if (r(u,v) >= 1) ad = 0;
1329                                                         if (sweepFlag == 0 && ad > 0) ad = ad - 2 * Math.PI;
1330                                                         if (sweepFlag == 1 && ad < 0) ad = ad + 2 * Math.PI;
1332                                                         // for markers
1333                                                         var halfWay = new svg.Point(
1334                                                                 centp.x - rx * Math.cos((a1 + ad) / 2),
1335                                                                 centp.y - ry * Math.sin((a1 + ad) / 2)
1336                                                         );
1337                                                         pp.addMarkerAngle(halfWay, (a1 + ad) / 2 + (sweepFlag == 0 ? 1 : -1) * Math.PI / 2);
1338                                                         pp.addMarkerAngle(cp, ad + (sweepFlag == 0 ? 1 : -1) * Math.PI / 2);
1340                                                         bb.addPoint(cp.x, cp.y); // TODO: this is too naive, make it better
1341                                                         if (ctx != null) {
1342                                                                 var r = rx > ry ? rx : ry;
1343                                                                 var sx = rx > ry ? 1 : rx / ry;
1344                                                                 var sy = rx > ry ? ry / rx : 1;
1346                                                                 ctx.translate(centp.x, centp.y);
1347                                                                 ctx.rotate(xAxisRotation);
1348                                                                 ctx.scale(sx, sy);
1349                                                                 ctx.arc(0, 0, r, a1, a1 + ad, 1 - sweepFlag);
1350                                                                 ctx.scale(1/sx, 1/sy);
1351                                                                 ctx.rotate(-xAxisRotation);
1352                                                                 ctx.translate(-centp.x, -centp.y);
1353                                                         }
1354                                                 }
1355                                                 break;
1356                                         case 'Z':
1357                                                 if (ctx != null) ctx.closePath();
1358                                                 pp.current = pp.start;
1359                                         }
1360                                 }
1362                                 return bb;
1363                         }
1365                         this.getMarkers = function() {
1366                                 var points = this.PathParser.getMarkerPoints();
1367                                 var angles = this.PathParser.getMarkerAngles();
1368                                 
1369                                 var markers = [];
1370                                 for (var i=0; i<points.length; i++) {
1371                                         markers.push([points[i], angles[i]]);
1372                                 }
1373                                 return markers;
1374                         }
1375                 }
1376                 svg.Element.path.prototype = new svg.Element.PathElementBase;
1377                 
1378                 // pattern element
1379                 svg.Element.pattern = function(node) {
1380                         this.base = svg.Element.ElementBase;
1381                         this.base(node);
1382                         
1383                         this.createPattern = function(ctx, element) {
1384                                 // render me using a temporary svg element
1385                                 var tempSvg = new svg.Element.svg();
1386                                 tempSvg.attributes['viewBox'] = new svg.Property('viewBox', this.attribute('viewBox').value);
1387                                 tempSvg.attributes['x'] = new svg.Property('x', this.attribute('x').value);
1388                                 tempSvg.attributes['y'] = new svg.Property('y', this.attribute('y').value);
1389                                 tempSvg.attributes['width'] = new svg.Property('width', this.attribute('width').value);
1390                                 tempSvg.attributes['height'] = new svg.Property('height', this.attribute('height').value);
1391                                 tempSvg.children = this.children;
1392                                 
1393                                 var c = document.createElement('canvas');
1394                                 c.width = this.attribute('width').Length.toPixels();
1395                                 c.height = this.attribute('height').Length.toPixels();
1396                                 tempSvg.render(c.getContext('2d'));             
1397                                 return ctx.createPattern(c, 'repeat');
1398                         }
1399                 }
1400                 svg.Element.pattern.prototype = new svg.Element.ElementBase;
1401                 
1402                 // marker element
1403                 svg.Element.marker = function(node) {
1404                         this.base = svg.Element.ElementBase;
1405                         this.base(node);
1406                         
1407                         this.baseRender = this.render;
1408                         this.render = function(ctx, point, angle) {
1409                                 ctx.translate(point.x, point.y);
1410                                 if (this.attribute('orient').valueOrDefault('auto') == 'auto') ctx.rotate(angle);
1411                                 if (this.attribute('markerUnits').valueOrDefault('strokeWidth') == 'strokeWidth') ctx.scale(ctx.lineWidth, ctx.lineWidth);
1412                                 ctx.save();
1413                                                         
1414                                 // render me using a temporary svg element
1415                                 var tempSvg = new svg.Element.svg();
1416                                 tempSvg.attributes['viewBox'] = new svg.Property('viewBox', this.attribute('viewBox').value);
1417                                 tempSvg.attributes['refX'] = new svg.Property('refX', this.attribute('refX').value);
1418                                 tempSvg.attributes['refY'] = new svg.Property('refY', this.attribute('refY').value);
1419                                 tempSvg.attributes['width'] = new svg.Property('width', this.attribute('markerWidth').value);
1420                                 tempSvg.attributes['height'] = new svg.Property('height', this.attribute('markerHeight').value);
1421                                 tempSvg.attributes['fill'] = new svg.Property('fill', this.attribute('fill').valueOrDefault('black'));
1422                                 tempSvg.attributes['stroke'] = new svg.Property('stroke', this.attribute('stroke').valueOrDefault('none'));
1423                                 tempSvg.children = this.children;
1424                                 tempSvg.render(ctx);
1425                                 
1426                                 ctx.restore();
1427                                 if (this.attribute('markerUnits').valueOrDefault('strokeWidth') == 'strokeWidth') ctx.scale(1/ctx.lineWidth, 1/ctx.lineWidth);
1428                                 if (this.attribute('orient').valueOrDefault('auto') == 'auto') ctx.rotate(-angle);
1429                                 ctx.translate(-point.x, -point.y);
1430                         }
1431                 }
1432                 svg.Element.marker.prototype = new svg.Element.ElementBase;
1433                 
1434                 // definitions element
1435                 svg.Element.defs = function(node) {
1436                         this.base = svg.Element.ElementBase;
1437                         this.base(node);        
1438                         
1439                         this.render = function(ctx) {
1440                                 // NOOP
1441                         }
1442                 }
1443                 svg.Element.defs.prototype = new svg.Element.ElementBase;
1444                 
1445                 // base for gradients
1446                 svg.Element.GradientBase = function(node) {
1447                         this.base = svg.Element.ElementBase;
1448                         this.base(node);
1449                         
1450                         this.gradientUnits = this.attribute('gradientUnits').valueOrDefault('objectBoundingBox');
1451                         
1452                         this.stops = [];                        
1453                         for (var i=0; i<this.children.length; i++) {
1454                                 var child = this.children[i];
1455                                 this.stops.push(child);
1456                         }       
1457                         
1458                         this.getGradient = function() {
1459                                 // OVERRIDE ME!
1460                         }                       
1462                         this.createGradient = function(ctx, element) {
1463                                 var stopsContainer = this;
1464                                 if (this.attribute('xlink:href').hasValue()) {
1465                                         stopsContainer = this.attribute('xlink:href').Definition.getDefinition();
1466                                 }
1467                         
1468                                 var g = this.getGradient(ctx, element);
1469                                 for (var i=0; i<stopsContainer.stops.length; i++) {
1470                                         g.addColorStop(stopsContainer.stops[i].offset, stopsContainer.stops[i].color);
1471                                 }
1472                                 return g;                               
1473                         }
1474                 }
1475                 svg.Element.GradientBase.prototype = new svg.Element.ElementBase;
1476                 
1477                 // linear gradient element
1478                 svg.Element.linearGradient = function(node) {
1479                         this.base = svg.Element.GradientBase;
1480                         this.base(node);
1481                         
1482                         this.getGradient = function(ctx, element) {
1483                                 var bb = element.getBoundingBox();
1484                                 
1485                                 var x1 = (this.gradientUnits == 'objectBoundingBox' 
1486                                         ? bb.x() + bb.width() * this.attribute('x1').numValue() 
1487                                         : this.attribute('x1').Length.toPixels('x'));
1488                                 var y1 = (this.gradientUnits == 'objectBoundingBox' 
1489                                         ? bb.y() + bb.height() * this.attribute('y1').numValue()
1490                                         : this.attribute('y1').Length.toPixels('y'));
1491                                 var x2 = (this.gradientUnits == 'objectBoundingBox' 
1492                                         ? bb.x() + bb.width() * this.attribute('x2').numValue()
1493                                         : this.attribute('x2').Length.toPixels('x'));
1494                                 var y2 = (this.gradientUnits == 'objectBoundingBox' 
1495                                         ? bb.y() + bb.height() * this.attribute('y2').numValue()
1496                                         : this.attribute('y2').Length.toPixels('y'));
1497                                 
1498                                 var p1 = new svg.Point(x1, y1);
1499                                 var p2 = new svg.Point(x2, y2);
1500                                 if (this.attribute('gradientTransform').hasValue()) { 
1501                                         var transform = new svg.Transform(this.attribute('gradientTransform').value);
1502                                         transform.applyToPoint(p1);
1503                                         transform.applyToPoint(p2);
1504                                 }
1505                                 
1506                                 return ctx.createLinearGradient(p1.x, p1.y, p2.x, p2.y);
1507                         }
1508                 }
1509                 svg.Element.linearGradient.prototype = new svg.Element.GradientBase;
1510                 
1511                 // radial gradient element
1512                 svg.Element.radialGradient = function(node) {
1513                         this.base = svg.Element.GradientBase;
1514                         this.base(node);
1515                         
1516                         this.getGradient = function(ctx, element) {
1517                                 var bb = element.getBoundingBox();
1518                                 
1519                                 var cx = (this.gradientUnits == 'objectBoundingBox' 
1520                                         ? bb.x() + bb.width() * this.attribute('cx').numValue() 
1521                                         : this.attribute('cx').Length.toPixels('x'));
1522                                 var cy = (this.gradientUnits == 'objectBoundingBox' 
1523                                         ? bb.y() + bb.height() * this.attribute('cy').numValue() 
1524                                         : this.attribute('cy').Length.toPixels('y'));
1525                                 
1526                                 var fx = cx;
1527                                 var fy = cy;
1528                                 if (this.attribute('fx').hasValue()) {
1529                                         fx = (this.gradientUnits == 'objectBoundingBox' 
1530                                         ? bb.x() + bb.width() * this.attribute('fx').numValue() 
1531                                         : this.attribute('fx').Length.toPixels('x'));
1532                                 }
1533                                 if (this.attribute('fy').hasValue()) {
1534                                         fy = (this.gradientUnits == 'objectBoundingBox' 
1535                                         ? bb.y() + bb.height() * this.attribute('fy').numValue() 
1536                                         : this.attribute('fy').Length.toPixels('y'));
1537                                 }
1538                                 
1539                                 var r = (this.gradientUnits == 'objectBoundingBox' 
1540                                         ? (bb.width() + bb.height()) / 2.0 * this.attribute('r').numValue()
1541                                         : this.attribute('r').Length.toPixels());
1542                                 
1543                                 var c = new svg.Point(cx, cy);
1544                                 var f = new svg.Point(fx, fy);
1545                                 if (this.attribute('gradientTransform').hasValue()) { 
1546                                         var transform = new svg.Transform(this.attribute('gradientTransform').value);
1547                                         transform.applyToPoint(c);
1548                                         transform.applyToPoint(f);
1549                                         
1550                                         for (var i=0; i<transform.transforms.length; i++) {
1551                                                 // average the scaling part of the transform, apply to radius
1552                                                 var scale1 = transform.transforms[i].m[0];
1553                                                 var scale2 = transform.transforms[i].m[3];
1554                                                 r = r * ((scale1 + scale2) / 2.0);
1555                                         }
1556                                 }                               
1557                                 
1558                                 return ctx.createRadialGradient(f.x, f.y, 0, c.x, c.y, r);
1559                         }
1560                 }
1561                 svg.Element.radialGradient.prototype = new svg.Element.GradientBase;
1562                 
1563                 // gradient stop element
1564                 svg.Element.stop = function(node) {
1565                         this.base = svg.Element.ElementBase;
1566                         this.base(node);
1567                         
1568                         this.offset = this.attribute('offset').numValue();
1569                         
1570                         var stopColor = this.style('stop-color');
1571                         if (this.style('stop-opacity').hasValue()) stopColor = stopColor.Color.addOpacity(this.style('stop-opacity').value);
1572                         this.color = stopColor.value;
1573                 }
1574                 svg.Element.stop.prototype = new svg.Element.ElementBase;
1575                 
1576                 // animation base element
1577                 svg.Element.AnimateBase = function(node) {
1578                         this.base = svg.Element.ElementBase;
1579                         this.base(node);
1580                         
1581                         svg.Animations.push(this);
1582                         
1583                         this.duration = 0.0;
1584                         this.begin = this.attribute('begin').Time.toMilliseconds();
1585                         this.maxDuration = this.begin + this.attribute('dur').Time.toMilliseconds();
1586                         
1587                         this.getProperty = function() {
1588                                 var attributeType = this.attribute('attributeType').value;
1589                                 var attributeName = this.attribute('attributeName').value;
1590                                 
1591                                 if (attributeType == 'CSS') {
1592                                         return this.parent.style(attributeName, true);
1593                                 }
1594                                 return this.parent.attribute(attributeName, true);                      
1595                         };
1596                         
1597                         this.initialValue = null;
1598                         this.removed = false;                   
1600                         this.calcValue = function() {
1601                                 // OVERRIDE ME!
1602                                 return '';
1603                         }
1604                         
1605                         this.update = function(delta) { 
1606                                 // set initial value
1607                                 if (this.initialValue == null) {
1608                                         this.initialValue = this.getProperty().value;
1609                                 }
1610                         
1611                                 // if we're past the end time
1612                                 if (this.duration > this.maxDuration) {
1613                                         // loop for indefinitely repeating animations
1614                                         if (this.attribute('repeatCount').value == 'indefinite') {
1615                                                 this.duration = 0.0
1616                                         }
1617                                         else if (this.attribute('fill').valueOrDefault('remove') == 'remove' && !this.removed) {
1618                                                 this.removed = true;
1619                                                 this.getProperty().value = this.initialValue;
1620                                                 return true;
1621                                         }
1622                                         else {
1623                                                 return false; // no updates made
1624                                         }
1625                                 }                       
1626                                 this.duration = this.duration + delta;
1627                         
1628                                 // if we're past the begin time
1629                                 var updated = false;
1630                                 if (this.begin < this.duration) {
1631                                         var newValue = this.calcValue(); // tween
1632                                         
1633                                         if (this.attribute('type').hasValue()) {
1634                                                 // for transform, etc.
1635                                                 var type = this.attribute('type').value;
1636                                                 newValue = type + '(' + newValue + ')';
1637                                         }
1638                                         
1639                                         this.getProperty().value = newValue;
1640                                         updated = true;
1641                                 }
1642                                 
1643                                 return updated;
1644                         }
1645                         
1646                         // fraction of duration we've covered
1647                         this.progress = function() {
1648                                 return ((this.duration - this.begin) / (this.maxDuration - this.begin));
1649                         }                       
1650                 }
1651                 svg.Element.AnimateBase.prototype = new svg.Element.ElementBase;
1652                 
1653                 // animate element
1654                 svg.Element.animate = function(node) {
1655                         this.base = svg.Element.AnimateBase;
1656                         this.base(node);
1657                         
1658                         this.calcValue = function() {
1659                                 var from = this.attribute('from').numValue();
1660                                 var to = this.attribute('to').numValue();
1661                                 
1662                                 // tween value linearly
1663                                 return from + (to - from) * this.progress(); 
1664                         };
1665                 }
1666                 svg.Element.animate.prototype = new svg.Element.AnimateBase;
1667                         
1668                 // animate color element
1669                 svg.Element.animateColor = function(node) {
1670                         this.base = svg.Element.AnimateBase;
1671                         this.base(node);
1673                         this.calcValue = function() {
1674                                 var from = new RGBColor(this.attribute('from').value);
1675                                 var to = new RGBColor(this.attribute('to').value);
1676                                 
1677                                 if (from.ok && to.ok) {
1678                                         // tween color linearly
1679                                         var r = from.r + (to.r - from.r) * this.progress();
1680                                         var g = from.g + (to.g - from.g) * this.progress();
1681                                         var b = from.b + (to.b - from.b) * this.progress();
1682                                         return 'rgb('+parseInt(r,10)+','+parseInt(g,10)+','+parseInt(b,10)+')';
1683                                 }
1684                                 return this.attribute('from').value;
1685                         };
1686                 }
1687                 svg.Element.animateColor.prototype = new svg.Element.AnimateBase;
1688                 
1689                 // animate transform element
1690                 svg.Element.animateTransform = function(node) {
1691                         this.base = svg.Element.animate;
1692                         this.base(node);
1693                 }
1694                 svg.Element.animateTransform.prototype = new svg.Element.animate;
1695                 
1696                 // text element
1697                 svg.Element.text = function(node) {
1698                         this.base = svg.Element.RenderedElementBase;
1699                         this.base(node);
1700                         
1701                         if (node != null) {
1702                                 // add children
1703                                 this.children = [];
1704                                 for (var i=0; i<node.childNodes.length; i++) {
1705                                         var childNode = node.childNodes[i];
1706                                         if (childNode.nodeType == 1) { // capture tspan and tref nodes
1707                                                 this.addChild(childNode, true);
1708                                         }
1709                                         else if (childNode.nodeType == 3) { // capture text
1710                                                 this.addChild(new svg.Element.tspan(childNode), false);
1711                                         }
1712                                 }
1713                         }
1714                         
1715                         this.baseSetContext = this.setContext;
1716                         this.setContext = function(ctx) {
1717                                 this.baseSetContext(ctx);
1718                                 if (this.attribute('text-anchor').hasValue()) {
1719                                         var textAnchor = this.attribute('text-anchor').value;
1720                                         ctx.textAlign = textAnchor == 'middle' ? 'center' : textAnchor;
1721                                 }
1722                                 if (this.attribute('alignment-baseline').hasValue()) ctx.textBaseline = this.attribute('alignment-baseline').value;
1723                         }
1724                         
1725                         this.renderChildren = function(ctx) {
1726                                 if(this.attribute('visibility').value=='hidden') return;
1727                                 
1728                                 var x = this.attribute('x').Length.toPixels('x');
1729                                 var y = this.attribute('y').Length.toPixels('y');
1730                                 
1731                                 for (var i=0; i<this.children.length; i++) {
1732                                         var child = this.children[i];
1733                                 
1734                                         if (child.attribute('x').hasValue()) {
1735                                                 child.x = child.attribute('x').Length.toPixels('x');
1736                                         }
1737                                         else {
1738                                                 if (child.attribute('dx').hasValue()) x += child.attribute('dx').Length.toPixels('x');
1739                                                 child.x = x;
1740                                                 x += child.measureText(ctx);
1741                                         }
1742                                         
1743                                         if (child.attribute('y').hasValue()) {
1744                                                 child.y = child.attribute('y').Length.toPixels('y');
1745                                         }
1746                                         else {
1747                                                 if (child.attribute('dy').hasValue()) y += child.attribute('dy').Length.toPixels('y');
1748                                                 child.y = y;
1749                                         }       
1750                                         
1751                                         child.render(ctx);
1752                                 }
1753                         }
1754                 }
1755                 svg.Element.text.prototype = new svg.Element.RenderedElementBase;
1756                 
1757                 // text base
1758                 svg.Element.TextElementBase = function(node) {
1759                         this.base = svg.Element.RenderedElementBase;
1760                         this.base(node);
1761                         
1762                         this.renderChildren = function(ctx) {
1763                                 ctx.fillText(svg.compressSpaces(this.getText()), this.x, this.y);
1764                         }
1765                         
1766                         this.getText = function() {
1767                                 // OVERRIDE ME
1768                         }
1769                         
1770                         this.measureText = function(ctx) {
1771                                 var textToMeasure = svg.compressSpaces(this.getText());
1772                                 if (!ctx.measureText) return textToMeasure.length * 10;
1773                                 return ctx.measureText(textToMeasure).width;
1774                         }
1775                 }
1776                 svg.Element.TextElementBase.prototype = new svg.Element.RenderedElementBase;
1777                 
1778                 // tspan 
1779                 svg.Element.tspan = function(node) {
1780                         this.base = svg.Element.TextElementBase;
1781                         this.base(node);
1782                         
1783                         //                                                               TEXT                     ELEMENT
1784                         this.text = node.nodeType == 3 ? node.nodeValue : node.childNodes[0].nodeValue;
1785                         this.getText = function() {
1786                                 return this.text;
1787                         }
1788                 }
1789                 svg.Element.tspan.prototype = new svg.Element.TextElementBase;
1790                 
1791                 // tref
1792                 svg.Element.tref = function(node) {
1793                         this.base = svg.Element.TextElementBase;
1794                         this.base(node);
1795                         
1796                         this.getText = function() {
1797                                 var element = this.attribute('xlink:href').Definition.getDefinition();
1798                                 if (element != null) return element.children[0].getText();
1799                         }
1800                 }
1801                 svg.Element.tref.prototype = new svg.Element.TextElementBase;           
1802                 
1803                 // a element
1804                 svg.Element.a = function(node) {
1805                         this.base = svg.Element.TextElementBase;
1806                         this.base(node);
1807                         
1808                         this.hasText = true;
1809                         for (var i=0; i<node.childNodes.length; i++) {
1810                                 if (node.childNodes[i].nodeType != 3) this.hasText = false;
1811                         }
1812                         
1813                         // this might contain text
1814                         this.text = this.hasText ? node.childNodes[0].nodeValue : '';
1815                         this.getText = function() {
1816                                 return this.text;
1817                         }               
1819                         this.baseRenderChildren = this.renderChildren;
1820                         this.renderChildren = function(ctx) {
1821                                 if (this.hasText) {
1822                                         // render as text element
1823                                         this.baseRenderChildren(ctx);
1824                                         var fontSize = new svg.Property('fontSize', svg.Font.Parse(svg.ctx.font).fontSize);
1825                                         svg.Mouse.checkBoundingBox(this, new svg.BoundingBox(this.x, this.y - fontSize.Length.toPixels('y'), this.x + this.measureText(ctx), this.y));                                  
1826                                 }
1827                                 else {
1828                                         // render as temporary group
1829                                         var g = new svg.Element.g();
1830                                         g.children = this.children;
1831                                         g.parent = this;
1832                                         g.render(ctx);
1833                                 }
1834                         }
1835                         
1836                         this.onclick = function() {
1837                                 window.open(this.attribute('xlink:href').value);
1838                         }
1839                         
1840                         this.onmousemove = function() {
1841                                 svg.ctx.canvas.style.cursor = 'pointer';
1842                         }
1843                 }
1844                 svg.Element.a.prototype = new svg.Element.TextElementBase;              
1845                 
1846                 // image element
1847                 svg.Element.image = function(node) {
1848                         this.base = svg.Element.RenderedElementBase;
1849                         this.base(node);
1850                         
1851                         svg.Images.push(this);
1852                         this.img = document.createElement('img');
1853                         this.loaded = false;
1854                         var that = this;
1855                         this.img.onload = function() { that.loaded = true; }
1856                         this.img.src = this.attribute('xlink:href').value;
1857                         
1858                         this.renderChildren = function(ctx) {
1859                                 var x = this.attribute('x').Length.toPixels('x');
1860                                 var y = this.attribute('y').Length.toPixels('y');
1861                                 
1862                                 var width = this.attribute('width').Length.toPixels('x');
1863                                 var height = this.attribute('height').Length.toPixels('y');                     
1864                                 if (width == 0 || height == 0) return;
1865                         
1866                                 ctx.save();
1867                                 ctx.translate(x, y);
1868                                 svg.AspectRatio(ctx,
1869                                                                 this.attribute('preserveAspectRatio').value,
1870                                                                 width,
1871                                                                 this.img.width,
1872                                                                 height,
1873                                                                 this.img.height,
1874                                                                 0,
1875                                                                 0);     
1876                                 ctx.drawImage(this.img, 0, 0);                  
1877                                 ctx.restore();
1878                         }
1879                 }
1880                 svg.Element.image.prototype = new svg.Element.RenderedElementBase;
1881                 
1882                 // group element
1883                 svg.Element.g = function(node) {
1884                         this.base = svg.Element.RenderedElementBase;
1885                         this.base(node);
1886                         
1887                         this.getBoundingBox = function() {
1888                                 var bb = new svg.BoundingBox();
1889                                 for (var i=0; i<this.children.length; i++) {
1890                                         bb.addBoundingBox(this.children[i].getBoundingBox());
1891                                 }
1892                                 return bb;
1893                         };
1894                 }
1895                 svg.Element.g.prototype = new svg.Element.RenderedElementBase;
1897                 // symbol element
1898                 svg.Element.symbol = function(node) {
1899                         this.base = svg.Element.RenderedElementBase;
1900                         this.base(node);
1901                         
1902                         this.baseSetContext = this.setContext;
1903                         this.setContext = function(ctx) {               
1904                                 this.baseSetContext(ctx);
1905                                 
1906                                 // viewbox
1907                                 if (this.attribute('viewBox').hasValue()) {                             
1908                                         var viewBox = svg.ToNumberArray(this.attribute('viewBox').value);
1909                                         var minX = viewBox[0];
1910                                         var minY = viewBox[1];
1911                                         width = viewBox[2];
1912                                         height = viewBox[3];
1913                                         
1914                                         svg.AspectRatio(ctx,
1915                                                                         this.attribute('preserveAspectRatio').value, 
1916                                                                         this.attribute('width').Length.toPixels('x'),
1917                                                                         width,
1918                                                                         this.attribute('height').Length.toPixels('y'),
1919                                                                         height,
1920                                                                         minX,
1921                                                                         minY);
1923                                         svg.ViewPort.SetCurrent(viewBox[2], viewBox[3]);                                                
1924                                 }
1925                         }                       
1926                 }
1927                 svg.Element.symbol.prototype = new svg.Element.RenderedElementBase;             
1928                         
1929                 // style element
1930                 svg.Element.style = function(node) { 
1931                         this.base = svg.Element.ElementBase;
1932                         this.base(node);
1933                         
1934                         var css = node.childNodes[0].nodeValue;
1935                         css = css.replace(/(\/\*([^*]|[\r\n]|(\*+([^*\/]|[\r\n])))*\*+\/)|(\/\/.*)/gm, ''); // remove comments
1936                         css = svg.compressSpaces(css); // replace whitespace
1937                         var cssDefs = css.split('}');
1938                         for (var i=0; i<cssDefs.length; i++) {
1939                                 if (svg.trim(cssDefs[i]) != '') {
1940                                         var cssDef = cssDefs[i].split('{');
1941                                         var cssClasses = cssDef[0].split(',');
1942                                         var cssProps = cssDef[1].split(';');
1943                                         for (var j=0; j<cssClasses.length; j++) {
1944                                                 var cssClass = svg.trim(cssClasses[j]);
1945                                                 if (cssClass != '') {
1946                                                         var props = {};
1947                                                         for (var k=0; k<cssProps.length; k++) {
1948                                                                 var prop = cssProps[k].split(':');
1949                                                                 var name = prop[0];
1950                                                                 var value = prop[1];
1951                                                                 if (name != null && value != null) {
1952                                                                         props[svg.trim(prop[0])] = new svg.Property(svg.trim(prop[0]), svg.trim(prop[1]));
1953                                                                 }
1954                                                         }
1955                                                         svg.Styles[cssClass] = props;
1956                                                 }
1957                                         }
1958                                 }
1959                         }
1960                 }
1961                 svg.Element.style.prototype = new svg.Element.ElementBase;
1962                 
1963                 // use element 
1964                 svg.Element.use = function(node) {
1965                         this.base = svg.Element.RenderedElementBase;
1966                         this.base(node);
1967                         
1968                         this.baseSetContext = this.setContext;
1969                         this.setContext = function(ctx) {
1970                                 this.baseSetContext(ctx);
1971                                 if (this.attribute('x').hasValue()) ctx.translate(this.attribute('x').Length.toPixels('x'), 0);
1972                                 if (this.attribute('y').hasValue()) ctx.translate(0, this.attribute('y').Length.toPixels('y'));
1973                         }
1974                         
1975                         this.getDefinition = function() {
1976                                 var element = this.attribute('xlink:href').Definition.getDefinition();
1977                                 if (this.attribute('width').hasValue()) element.attribute('width', true).value = this.attribute('width').value;
1978                                 if (this.attribute('height').hasValue()) element.attribute('height', true).value = this.attribute('height').value;
1979                                 return element;
1980                         }
1981                         
1982                         this.path = function(ctx) {
1983                                 var element = this.getDefinition();
1984                                 if (element != null) element.path(ctx);
1985                         }
1986                         
1987                         this.renderChildren = function(ctx) {
1988                                 var element = this.getDefinition();
1989                                 if (element != null) element.render(ctx);
1990                         }
1991                 }
1992                 svg.Element.use.prototype = new svg.Element.RenderedElementBase;
1993                 
1994                 // clip element
1995                 svg.Element.clipPath = function(node) {
1996                         this.base = svg.Element.ElementBase;
1997                         this.base(node);
1998                         
1999                         this.apply = function(ctx) {
2000                                 for (var i=0; i<this.children.length; i++) {
2001                                         if (this.children[i].path) {
2002                                                 this.children[i].path(ctx);
2003                                                 ctx.clip();
2004                                         }
2005                                 }
2006                         }
2007                 }
2008                 svg.Element.clipPath.prototype = new svg.Element.ElementBase;
2010                 // title element, do nothing
2011                 svg.Element.title = function(node) {
2012                 }
2013                 svg.Element.title.prototype = new svg.Element.ElementBase;
2015                 // desc element, do nothing
2016                 svg.Element.desc = function(node) {
2017                 }
2018                 svg.Element.desc.prototype = new svg.Element.ElementBase;               
2019                 
2020                 svg.Element.MISSING = function(node) {
2021                         console.log('ERROR: Element \'' + node.nodeName + '\' not yet implemented.');
2022                 }
2023                 svg.Element.MISSING.prototype = new svg.Element.ElementBase;
2024                 
2025                 // element factory
2026                 svg.CreateElement = function(node) {    
2027                         var className = node.nodeName.replace(/^[^:]+:/,'');
2028                         var e = null;
2029                         if (typeof(svg.Element[className]) != 'undefined') {
2030                                 e = new svg.Element[className](node);
2031                         }
2032                         else {
2033                                 e = new svg.Element.MISSING(node);
2034                         }
2036                         e.type = node.nodeName;
2037                         return e;
2038                 }
2039                                 
2040                 // load from url
2041                 svg.load = function(ctx, url) {
2042                         svg.loadXml(ctx, svg.ajax(url));
2043                 }
2044                 
2045                 // load from xml
2046                 svg.loadXml = function(ctx, xml) {
2047                         svg.init(ctx);
2048                         
2049                         var mapXY = function(p) {
2050                                 var e = ctx.canvas;
2051                                 while (e) {
2052                                         p.x -= e.offsetLeft;
2053                                         p.y -= e.offsetTop;
2054                                         e = e.offsetParent;
2055                                 }
2056                                 if (window.scrollX) p.x += window.scrollX;
2057                                 if (window.scrollY) p.y += window.scrollY;
2058                                 return p;
2059                         }
2060                         
2061                         // bind mouse
2062                         if (svg.opts == null || svg.opts['ignoreMouse'] != true) {
2063                                 ctx.canvas.onclick = function(e) {
2064                                         var p = mapXY(new svg.Point(e != null ? e.clientX : event.clientX, e != null ? e.clientY : event.clientY));
2065                                         svg.Mouse.onclick(p.x, p.y);
2066                                 };
2067                                 ctx.canvas.onmousemove = function(e) {
2068                                         var p = mapXY(new svg.Point(e != null ? e.clientX : event.clientX, e != null ? e.clientY : event.clientY));
2069                                         svg.Mouse.onmousemove(p.x, p.y);
2070                                 };
2071                         }
2072                 
2073                         var dom = svg.parseXml(xml);
2074                         var e = svg.CreateElement(dom.documentElement);
2075                                         
2076                         // render loop
2077                         var isFirstRender = true;
2078                         var draw = function() {
2079                                 if (svg.opts == null || svg.opts['ignoreDimensions'] != true) {
2080                                         // set canvas size
2081                                         if (e.style('width').hasValue()) {
2082                                                 ctx.canvas.width = e.style('width').Length.toPixels(ctx.canvas.parentNode.clientWidth);
2083                                         }
2084                                         if (e.style('height').hasValue()) {
2085                                                 ctx.canvas.height = e.style('height').Length.toPixels(ctx.canvas.parentNode.clientHeight);
2086                                         }
2087                                 }
2088                                 svg.ViewPort.SetCurrent(ctx.canvas.clientWidth, ctx.canvas.clientHeight);               
2089                                 
2090                                 if (svg.opts != null && svg.opts['offsetX'] != null) e.attribute('x', true).value = svg.opts['offsetX'];
2091                                 if (svg.opts != null && svg.opts['offsetY'] != null) e.attribute('y', true).value = svg.opts['offsetY'];
2092                                 if (svg.opts != null && svg.opts['scaleWidth'] != null && svg.opts['scaleHeight'] != null) {
2093                                         e.attribute('width', true).value = svg.opts['scaleWidth'];
2094                                         e.attribute('height', true).value = svg.opts['scaleHeight'];                    
2095                                         e.attribute('viewBox', true).value = '0 0 ' + ctx.canvas.clientWidth + ' ' + ctx.canvas.clientHeight;
2096                                         e.attribute('preserveAspectRatio', true).value = 'none';
2097                                 }
2098                         
2099                                 // clear and render
2100                                 if (svg.opts == null || svg.opts['ignoreClear'] != true) {
2101                                         ctx.clearRect(0, 0, ctx.canvas.clientWidth, ctx.canvas.clientHeight);
2102                                 }
2103                                 e.render(ctx);
2104                                 if (isFirstRender) {
2105                                         isFirstRender = false;
2106                                         if (svg.opts != null && typeof(svg.opts['renderCallback']) == 'function') svg.opts['renderCallback']();
2107                                 }                       
2108                         }
2109                         
2110                         var waitingForImages = true;
2111                         if (svg.ImagesLoaded()) {
2112                                 waitingForImages = false;
2113                                 draw();
2114                         }
2115                         svg.intervalID = setInterval(function() { 
2116                                 var needUpdate = false;
2117                                 
2118                                 if (waitingForImages && svg.ImagesLoaded()) {
2119                                         waitingForImages = false;
2120                                         needUpdate = true;
2121                                 }
2122                         
2123                                 // need update from mouse events?
2124                                 if (svg.opts == null || svg.opts['ignoreMouse'] != true) {
2125                                         needUpdate = needUpdate | svg.Mouse.hasEvents();
2126                                 }
2127                         
2128                                 // need update from animations?
2129                                 if (svg.opts == null || svg.opts['ignoreAnimation'] != true) {
2130                                         for (var i=0; i<svg.Animations.length; i++) {
2131                                                 needUpdate = needUpdate | svg.Animations[i].update(1000 / svg.FRAMERATE);
2132                                         }
2133                                 }
2134                                 
2135                                 // need update from redraw?
2136                                 if (svg.opts != null && typeof(svg.opts['forceRedraw']) == 'function') {
2137                                         if (svg.opts['forceRedraw']() == true) needUpdate = true;
2138                                 }
2139                                 
2140                                 // render if needed
2141                                 if (needUpdate) {
2142                                         draw();                         
2143                                         svg.Mouse.runEvents(); // run and clear our events
2144                                 }
2145                         }, 1000 / svg.FRAMERATE);
2146                 }
2147                 
2148                 svg.stop = function() {
2149                         if (svg.intervalID) {
2150                                 clearInterval(svg.intervalID);
2151                         }
2152                 }
2153                 
2154                 svg.Mouse = new (function() {
2155                         this.events = [];
2156                         this.hasEvents = function() { return this.events.length != 0; }
2157                 
2158                         this.onclick = function(x, y) {
2159                                 this.events.push({ type: 'onclick', x: x, y: y, 
2160                                         run: function(e) { if (e.onclick) e.onclick(); }
2161                                 });
2162                         }
2163                         
2164                         this.onmousemove = function(x, y) {
2165                                 this.events.push({ type: 'onmousemove', x: x, y: y,
2166                                         run: function(e) { if (e.onmousemove) e.onmousemove(); }
2167                                 });
2168                         }                       
2169                         
2170                         this.eventElements = [];
2171                         
2172                         this.checkPath = function(element, ctx) {
2173                                 for (var i=0; i<this.events.length; i++) {
2174                                         var e = this.events[i];
2175                                         if (ctx.isPointInPath && ctx.isPointInPath(e.x, e.y)) this.eventElements[i] = element;
2176                                 }
2177                         }
2178                         
2179                         this.checkBoundingBox = function(element, bb) {
2180                                 for (var i=0; i<this.events.length; i++) {
2181                                         var e = this.events[i];
2182                                         if (bb.isPointInBox(e.x, e.y)) this.eventElements[i] = element;
2183                                 }                       
2184                         }
2185                         
2186                         this.runEvents = function() {
2187                                 svg.ctx.canvas.style.cursor = '';
2188                                 
2189                                 for (var i=0; i<this.events.length; i++) {
2190                                         var e = this.events[i];
2191                                         var element = this.eventElements[i];
2192                                         while (element) {
2193                                                 e.run(element);
2194                                                 element = element.parent;
2195                                         }
2196                                 }               
2197                         
2198                                 // done running, clear
2199                                 this.events = []; 
2200                                 this.eventElements = [];
2201                         }
2202                 });
2203                 
2204                 return svg;
2205         }
2206 })();
2208 if (CanvasRenderingContext2D) {
2209         CanvasRenderingContext2D.prototype.drawSvg = function(s, dx, dy, dw, dh) {
2210                 canvg(this.canvas, s, { 
2211                         ignoreMouse: true, 
2212                         ignoreAnimation: true, 
2213                         ignoreDimensions: true, 
2214                         ignoreClear: true, 
2215                         offsetX: dx, 
2216                         offsetY: dy, 
2217                         scaleWidth: dw, 
2218                         scaleHeight: dh
2219                 });
2220         }