amcharts.rb 3.1.1.3 → 3.2.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,2842 @@
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
+ (function(){
10
+ // canvg(target, s)
11
+ // empty parameters: replace all 'svg' elements on page with 'canvas' elements
12
+ // target: canvas element or the id of a canvas element
13
+ // s: svg string, url to svg file, or xml document
14
+ // opts: optional hash of options
15
+ // ignoreMouse: true => ignore mouse events
16
+ // ignoreAnimation: true => ignore animations
17
+ // ignoreDimensions: true => does not try to resize canvas
18
+ // ignoreClear: true => does not clear canvas
19
+ // offsetX: int => draws at a x offset
20
+ // offsetY: int => draws at a y offset
21
+ // scaleWidth: int => scales horizontally to width
22
+ // scaleHeight: int => scales vertically to height
23
+ // renderCallback: function => will call the function after the first render is completed
24
+ // forceRedraw: function => will call the function on every frame, if it returns true, will redraw
25
+ this.canvg = function (target, s, opts) {
26
+ // no parameters
27
+ if (target == null && s == null && opts == null) {
28
+ var svgTags = document.getElementsByTagName('svg');
29
+ for (var i=0; i<svgTags.length; i++) {
30
+ var svgTag = svgTags[i];
31
+ var c = document.createElement('canvas');
32
+ c.width = svgTag.clientWidth;
33
+ c.height = svgTag.clientHeight;
34
+ svgTag.parentNode.insertBefore(c, svgTag);
35
+ svgTag.parentNode.removeChild(svgTag);
36
+ var div = document.createElement('div');
37
+ div.appendChild(svgTag);
38
+ canvg(c, div.innerHTML);
39
+ }
40
+ return;
41
+ }
42
+ opts = opts || {};
43
+
44
+ if (typeof target == 'string') {
45
+ target = document.getElementById(target);
46
+ }
47
+
48
+ // store class on canvas
49
+ if (target.svg != null) target.svg.stop();
50
+ var svg = build();
51
+ // on i.e. 8 for flash canvas, we can't assign the property so check for it
52
+ if (!(target.childNodes.length == 1 && target.childNodes[0].nodeName == 'OBJECT')) target.svg = svg;
53
+ svg.opts = opts;
54
+
55
+ var ctx = target.getContext('2d');
56
+ if (typeof(s.documentElement) != 'undefined') {
57
+ // load from xml doc
58
+ svg.loadXmlDoc(ctx, s);
59
+ }
60
+ else if (s.substr(0,1) == '<') {
61
+ // load from xml string
62
+ svg.loadXml(ctx, s);
63
+ }
64
+ else {
65
+ // load from url
66
+ svg.load(ctx, s);
67
+ }
68
+ }
69
+
70
+ function build() {
71
+ var svg = { };
72
+
73
+ svg.FRAMERATE = 30;
74
+ svg.MAX_VIRTUAL_PIXELS = 30000;
75
+
76
+ // globals
77
+ svg.init = function(ctx) {
78
+ var uniqueId = 0;
79
+ svg.UniqueId = function () { uniqueId++; return 'canvg' + uniqueId; };
80
+ svg.Definitions = {};
81
+ svg.Styles = {};
82
+ svg.Animations = [];
83
+ svg.Images = [];
84
+ svg.ctx = ctx;
85
+ svg.ViewPort = new (function () {
86
+ this.viewPorts = [];
87
+ this.Clear = function() { this.viewPorts = []; }
88
+ this.SetCurrent = function(width, height) { this.viewPorts.push({ width: width, height: height }); }
89
+ this.RemoveCurrent = function() { this.viewPorts.pop(); }
90
+ this.Current = function() { return this.viewPorts[this.viewPorts.length - 1]; }
91
+ this.width = function() { return this.Current().width; }
92
+ this.height = function() { return this.Current().height; }
93
+ this.ComputeSize = function(d) {
94
+ if (d != null && typeof(d) == 'number') return d;
95
+ if (d == 'x') return this.width();
96
+ if (d == 'y') return this.height();
97
+ return Math.sqrt(Math.pow(this.width(), 2) + Math.pow(this.height(), 2)) / Math.sqrt(2);
98
+ }
99
+ });
100
+ }
101
+ svg.init();
102
+
103
+ // images loaded
104
+ svg.ImagesLoaded = function() {
105
+ for (var i=0; i<svg.Images.length; i++) {
106
+ if (!svg.Images[i].loaded) return false;
107
+ }
108
+ return true;
109
+ }
110
+
111
+ // trim
112
+ svg.trim = function(s) { return s.replace(/^\s+|\s+$/g, ''); }
113
+
114
+ // compress spaces
115
+ svg.compressSpaces = function(s) { return s.replace(/[\s\r\t\n]+/gm,' '); }
116
+
117
+ // ajax
118
+ svg.ajax = function(url) {
119
+ var AJAX;
120
+ if(window.XMLHttpRequest){AJAX=new XMLHttpRequest();}
121
+ else{AJAX=new ActiveXObject('Microsoft.XMLHTTP');}
122
+ if(AJAX){
123
+ AJAX.open('GET',url,false);
124
+ AJAX.send(null);
125
+ return AJAX.responseText;
126
+ }
127
+ return null;
128
+ }
129
+
130
+ // parse xml
131
+
132
+ svg.parseXml = function(xml) {
133
+ if (window.DOMParser)
134
+ {
135
+ var parser = new DOMParser();
136
+ return parser.parseFromString(xml, 'text/xml');
137
+ }
138
+ else
139
+ {
140
+ xml = xml.replace(/<!DOCTYPE svg[^>]*>/, '');
141
+ var xmlDoc = new ActiveXObject('Microsoft.XMLDOM');
142
+ xmlDoc.async = 'false';
143
+ xmlDoc.loadXML(xml);
144
+ return xmlDoc;
145
+ }
146
+ }
147
+
148
+ svg.Property = function(name, value) {
149
+ this.name = name;
150
+ this.value = value;
151
+ }
152
+ svg.Property.prototype.getValue = function() {
153
+ return this.value;
154
+ }
155
+
156
+ svg.Property.prototype.hasValue = function() {
157
+ return (this.value != null && this.value !== '');
158
+ }
159
+
160
+ // return the numerical value of the property
161
+ svg.Property.prototype.numValue = function() {
162
+ if (!this.hasValue()) return 0;
163
+
164
+ var n = parseFloat(this.value);
165
+ if ((this.value + '').match(/%$/)) {
166
+ n = n / 100.0;
167
+ }
168
+ return n;
169
+ }
170
+
171
+ svg.Property.prototype.valueOrDefault = function(def) {
172
+ if (this.hasValue()) return this.value;
173
+ return def;
174
+ }
175
+
176
+ svg.Property.prototype.numValueOrDefault = function(def) {
177
+ if (this.hasValue()) return this.numValue();
178
+ return def;
179
+ }
180
+
181
+ // color extensions
182
+ // augment the current color value with the opacity
183
+ svg.Property.prototype.addOpacity = function(opacity) {
184
+ var newValue = this.value;
185
+ if (opacity != null && opacity != '' && typeof(this.value)=='string') { // can only add opacity to colors, not patterns
186
+ var color = new RGBColor(this.value);
187
+ if (color.ok) {
188
+ newValue = 'rgba(' + color.r + ', ' + color.g + ', ' + color.b + ', ' + opacity + ')';
189
+ }
190
+ }
191
+ return new svg.Property(this.name, newValue);
192
+ }
193
+
194
+ // definition extensions
195
+ // get the definition from the definitions table
196
+ svg.Property.prototype.getDefinition = function() {
197
+ var name = this.value.match(/#([^\)'"]+)/);
198
+ if (name) { name = name[1]; }
199
+ if (!name) { name = this.value; }
200
+ return svg.Definitions[name];
201
+ }
202
+
203
+ svg.Property.prototype.isUrlDefinition = function() {
204
+ return this.value.indexOf('url(') == 0
205
+ }
206
+
207
+ svg.Property.prototype.getFillStyleDefinition = function(e, opacityProp) {
208
+ var def = this.getDefinition();
209
+
210
+ // gradient
211
+ if (def != null && def.createGradient) {
212
+ return def.createGradient(svg.ctx, e, opacityProp);
213
+ }
214
+
215
+ // pattern
216
+ if (def != null && def.createPattern) {
217
+ if (def.getHrefAttribute().hasValue()) {
218
+ var pt = def.attribute('patternTransform');
219
+ def = def.getHrefAttribute().getDefinition();
220
+ if (pt.hasValue()) { def.attribute('patternTransform', true).value = pt.value; }
221
+ }
222
+ return def.createPattern(svg.ctx, e);
223
+ }
224
+
225
+ return null;
226
+ }
227
+
228
+ // length extensions
229
+ svg.Property.prototype.getDPI = function(viewPort) {
230
+ return 96.0; // TODO: compute?
231
+ }
232
+
233
+ svg.Property.prototype.getEM = function(viewPort) {
234
+ var em = 12;
235
+
236
+ var fontSize = new svg.Property('fontSize', svg.Font.Parse(svg.ctx.font).fontSize);
237
+ if (fontSize.hasValue()) em = fontSize.toPixels(viewPort);
238
+
239
+ return em;
240
+ }
241
+
242
+ svg.Property.prototype.getUnits = function() {
243
+ var s = this.value+'';
244
+ return s.replace(/[0-9\.\-]/g,'');
245
+ }
246
+
247
+ // get the length as pixels
248
+ svg.Property.prototype.toPixels = function(viewPort, processPercent) {
249
+ if (!this.hasValue()) return 0;
250
+ var s = this.value+'';
251
+ if (s.match(/em$/)) return this.numValue() * this.getEM(viewPort);
252
+ if (s.match(/ex$/)) return this.numValue() * this.getEM(viewPort) / 2.0;
253
+ if (s.match(/px$/)) return this.numValue();
254
+ if (s.match(/pt$/)) return this.numValue() * this.getDPI(viewPort) * (1.0 / 72.0);
255
+ if (s.match(/pc$/)) return this.numValue() * 15;
256
+ if (s.match(/cm$/)) return this.numValue() * this.getDPI(viewPort) / 2.54;
257
+ if (s.match(/mm$/)) return this.numValue() * this.getDPI(viewPort) / 25.4;
258
+ if (s.match(/in$/)) return this.numValue() * this.getDPI(viewPort);
259
+ if (s.match(/%$/)) return this.numValue() * svg.ViewPort.ComputeSize(viewPort);
260
+ var n = this.numValue();
261
+ if (processPercent && n < 1.0) return n * svg.ViewPort.ComputeSize(viewPort);
262
+ return n;
263
+ }
264
+
265
+ // time extensions
266
+ // get the time as milliseconds
267
+ svg.Property.prototype.toMilliseconds = function() {
268
+ if (!this.hasValue()) return 0;
269
+ var s = this.value+'';
270
+ if (s.match(/s$/)) return this.numValue() * 1000;
271
+ if (s.match(/ms$/)) return this.numValue();
272
+ return this.numValue();
273
+ }
274
+
275
+ // angle extensions
276
+ // get the angle as radians
277
+ svg.Property.prototype.toRadians = function() {
278
+ if (!this.hasValue()) return 0;
279
+ var s = this.value+'';
280
+ if (s.match(/deg$/)) return this.numValue() * (Math.PI / 180.0);
281
+ if (s.match(/grad$/)) return this.numValue() * (Math.PI / 200.0);
282
+ if (s.match(/rad$/)) return this.numValue();
283
+ return this.numValue() * (Math.PI / 180.0);
284
+ }
285
+
286
+ // fonts
287
+ svg.Font = new (function() {
288
+ this.Styles = 'normal|italic|oblique|inherit';
289
+ this.Variants = 'normal|small-caps|inherit';
290
+ this.Weights = 'normal|bold|bolder|lighter|100|200|300|400|500|600|700|800|900|inherit';
291
+
292
+ this.CreateFont = function(fontStyle, fontVariant, fontWeight, fontSize, fontFamily, inherit) {
293
+ var f = inherit != null ? this.Parse(inherit) : this.CreateFont('', '', '', '', '', svg.ctx.font);
294
+ return {
295
+ fontFamily: fontFamily || f.fontFamily,
296
+ fontSize: fontSize || f.fontSize,
297
+ fontStyle: fontStyle || f.fontStyle,
298
+ fontWeight: fontWeight || f.fontWeight,
299
+ fontVariant: fontVariant || f.fontVariant,
300
+ toString: function () { return [this.fontStyle, this.fontVariant, this.fontWeight, this.fontSize, this.fontFamily].join(' ') }
301
+ }
302
+ }
303
+
304
+ var that = this;
305
+ this.Parse = function(s) {
306
+ var f = {};
307
+ var d = svg.trim(svg.compressSpaces(s || '')).split(' ');
308
+ var set = { fontSize: false, fontStyle: false, fontWeight: false, fontVariant: false }
309
+ var ff = '';
310
+ for (var i=0; i<d.length; i++) {
311
+ if (!set.fontStyle && that.Styles.indexOf(d[i]) != -1) { if (d[i] != 'inherit') f.fontStyle = d[i]; set.fontStyle = true; }
312
+ else if (!set.fontVariant && that.Variants.indexOf(d[i]) != -1) { if (d[i] != 'inherit') f.fontVariant = d[i]; set.fontStyle = set.fontVariant = true; }
313
+ 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; }
314
+ else if (!set.fontSize) { if (d[i] != 'inherit') f.fontSize = d[i].split('/')[0]; set.fontStyle = set.fontVariant = set.fontWeight = set.fontSize = true; }
315
+ else { if (d[i] != 'inherit') ff += d[i]; }
316
+ } if (ff != '') f.fontFamily = ff;
317
+ return f;
318
+ }
319
+ });
320
+
321
+ // points and paths
322
+ svg.ToNumberArray = function(s) {
323
+ var a = svg.trim(svg.compressSpaces((s || '').replace(/,/g, ' '))).split(' ');
324
+ for (var i=0; i<a.length; i++) {
325
+ a[i] = parseFloat(a[i]);
326
+ }
327
+ return a;
328
+ }
329
+ svg.Point = function(x, y) {
330
+ this.x = x;
331
+ this.y = y;
332
+ }
333
+ svg.Point.prototype.angleTo = function(p) {
334
+ return Math.atan2(p.y - this.y, p.x - this.x);
335
+ }
336
+
337
+ svg.Point.prototype.applyTransform = function(v) {
338
+ var xp = this.x * v[0] + this.y * v[2] + v[4];
339
+ var yp = this.x * v[1] + this.y * v[3] + v[5];
340
+ this.x = xp;
341
+ this.y = yp;
342
+ }
343
+
344
+ svg.CreatePoint = function(s) {
345
+ var a = svg.ToNumberArray(s);
346
+ return new svg.Point(a[0], a[1]);
347
+ }
348
+ svg.CreatePath = function(s) {
349
+ var a = svg.ToNumberArray(s);
350
+ var path = [];
351
+ for (var i=0; i<a.length; i+=2) {
352
+ path.push(new svg.Point(a[i], a[i+1]));
353
+ }
354
+ return path;
355
+ }
356
+
357
+ // bounding box
358
+ svg.BoundingBox = function(x1, y1, x2, y2) { // pass in initial points if you want
359
+ this.x1 = Number.NaN;
360
+ this.y1 = Number.NaN;
361
+ this.x2 = Number.NaN;
362
+ this.y2 = Number.NaN;
363
+
364
+ this.x = function() { return this.x1; }
365
+ this.y = function() { return this.y1; }
366
+ this.width = function() { return this.x2 - this.x1; }
367
+ this.height = function() { return this.y2 - this.y1; }
368
+
369
+ this.addPoint = function(x, y) {
370
+ if (x != null) {
371
+ if (isNaN(this.x1) || isNaN(this.x2)) {
372
+ this.x1 = x;
373
+ this.x2 = x;
374
+ }
375
+ if (x < this.x1) this.x1 = x;
376
+ if (x > this.x2) this.x2 = x;
377
+ }
378
+
379
+ if (y != null) {
380
+ if (isNaN(this.y1) || isNaN(this.y2)) {
381
+ this.y1 = y;
382
+ this.y2 = y;
383
+ }
384
+ if (y < this.y1) this.y1 = y;
385
+ if (y > this.y2) this.y2 = y;
386
+ }
387
+ }
388
+ this.addX = function(x) { this.addPoint(x, null); }
389
+ this.addY = function(y) { this.addPoint(null, y); }
390
+
391
+ this.addBoundingBox = function(bb) {
392
+ this.addPoint(bb.x1, bb.y1);
393
+ this.addPoint(bb.x2, bb.y2);
394
+ }
395
+
396
+ this.addQuadraticCurve = function(p0x, p0y, p1x, p1y, p2x, p2y) {
397
+ var cp1x = p0x + 2/3 * (p1x - p0x); // CP1 = QP0 + 2/3 *(QP1-QP0)
398
+ var cp1y = p0y + 2/3 * (p1y - p0y); // CP1 = QP0 + 2/3 *(QP1-QP0)
399
+ var cp2x = cp1x + 1/3 * (p2x - p0x); // CP2 = CP1 + 1/3 *(QP2-QP0)
400
+ var cp2y = cp1y + 1/3 * (p2y - p0y); // CP2 = CP1 + 1/3 *(QP2-QP0)
401
+ this.addBezierCurve(p0x, p0y, cp1x, cp2x, cp1y, cp2y, p2x, p2y);
402
+ }
403
+
404
+ this.addBezierCurve = function(p0x, p0y, p1x, p1y, p2x, p2y, p3x, p3y) {
405
+ // from http://blog.hackers-cafe.net/2009/06/how-to-calculate-bezier-curves-bounding.html
406
+ var p0 = [p0x, p0y], p1 = [p1x, p1y], p2 = [p2x, p2y], p3 = [p3x, p3y];
407
+ this.addPoint(p0[0], p0[1]);
408
+ this.addPoint(p3[0], p3[1]);
409
+
410
+ for (i=0; i<=1; i++) {
411
+ var f = function(t) {
412
+ return Math.pow(1-t, 3) * p0[i]
413
+ + 3 * Math.pow(1-t, 2) * t * p1[i]
414
+ + 3 * (1-t) * Math.pow(t, 2) * p2[i]
415
+ + Math.pow(t, 3) * p3[i];
416
+ }
417
+
418
+ var b = 6 * p0[i] - 12 * p1[i] + 6 * p2[i];
419
+ var a = -3 * p0[i] + 9 * p1[i] - 9 * p2[i] + 3 * p3[i];
420
+ var c = 3 * p1[i] - 3 * p0[i];
421
+
422
+ if (a == 0) {
423
+ if (b == 0) continue;
424
+ var t = -c / b;
425
+ if (0 < t && t < 1) {
426
+ if (i == 0) this.addX(f(t));
427
+ if (i == 1) this.addY(f(t));
428
+ }
429
+ continue;
430
+ }
431
+
432
+ var b2ac = Math.pow(b, 2) - 4 * c * a;
433
+ if (b2ac < 0) continue;
434
+ var t1 = (-b + Math.sqrt(b2ac)) / (2 * a);
435
+ if (0 < t1 && t1 < 1) {
436
+ if (i == 0) this.addX(f(t1));
437
+ if (i == 1) this.addY(f(t1));
438
+ }
439
+ var t2 = (-b - Math.sqrt(b2ac)) / (2 * a);
440
+ if (0 < t2 && t2 < 1) {
441
+ if (i == 0) this.addX(f(t2));
442
+ if (i == 1) this.addY(f(t2));
443
+ }
444
+ }
445
+ }
446
+
447
+ this.isPointInBox = function(x, y) {
448
+ return (this.x1 <= x && x <= this.x2 && this.y1 <= y && y <= this.y2);
449
+ }
450
+
451
+ this.addPoint(x1, y1);
452
+ this.addPoint(x2, y2);
453
+ }
454
+
455
+ // transforms
456
+ svg.Transform = function(v) {
457
+ var that = this;
458
+ this.Type = {}
459
+
460
+ // translate
461
+ this.Type.translate = function(s) {
462
+ this.p = svg.CreatePoint(s);
463
+ this.apply = function(ctx) {
464
+ ctx.translate(this.p.x || 0.0, this.p.y || 0.0);
465
+ }
466
+ this.unapply = function(ctx) {
467
+ ctx.translate(-1.0 * this.p.x || 0.0, -1.0 * this.p.y || 0.0);
468
+ }
469
+ this.applyToPoint = function(p) {
470
+ p.applyTransform([1, 0, 0, 1, this.p.x || 0.0, this.p.y || 0.0]);
471
+ }
472
+ }
473
+
474
+ // rotate
475
+ this.Type.rotate = function(s) {
476
+ var a = svg.ToNumberArray(s);
477
+ this.angle = new svg.Property('angle', a[0]);
478
+ this.cx = a[1] || 0;
479
+ this.cy = a[2] || 0;
480
+ this.apply = function(ctx) {
481
+ ctx.translate(this.cx, this.cy);
482
+ ctx.rotate(this.angle.toRadians());
483
+ ctx.translate(-this.cx, -this.cy);
484
+ }
485
+ this.unapply = function(ctx) {
486
+ ctx.translate(this.cx, this.cy);
487
+ ctx.rotate(-1.0 * this.angle.toRadians());
488
+ ctx.translate(-this.cx, -this.cy);
489
+ }
490
+ this.applyToPoint = function(p) {
491
+ var a = this.angle.toRadians();
492
+ p.applyTransform([1, 0, 0, 1, this.p.x || 0.0, this.p.y || 0.0]);
493
+ p.applyTransform([Math.cos(a), Math.sin(a), -Math.sin(a), Math.cos(a), 0, 0]);
494
+ p.applyTransform([1, 0, 0, 1, -this.p.x || 0.0, -this.p.y || 0.0]);
495
+ }
496
+ }
497
+
498
+ this.Type.scale = function(s) {
499
+ this.p = svg.CreatePoint(s);
500
+ this.apply = function(ctx) {
501
+ ctx.scale(this.p.x || 1.0, this.p.y || this.p.x || 1.0);
502
+ }
503
+ this.unapply = function(ctx) {
504
+ ctx.scale(1.0 / this.p.x || 1.0, 1.0 / this.p.y || this.p.x || 1.0);
505
+ }
506
+ this.applyToPoint = function(p) {
507
+ p.applyTransform([this.p.x || 0.0, 0, 0, this.p.y || 0.0, 0, 0]);
508
+ }
509
+ }
510
+
511
+ this.Type.matrix = function(s) {
512
+ this.m = svg.ToNumberArray(s);
513
+ this.apply = function(ctx) {
514
+ ctx.transform(this.m[0], this.m[1], this.m[2], this.m[3], this.m[4], this.m[5]);
515
+ }
516
+ this.applyToPoint = function(p) {
517
+ p.applyTransform(this.m);
518
+ }
519
+ }
520
+
521
+ this.Type.SkewBase = function(s) {
522
+ this.base = that.Type.matrix;
523
+ this.base(s);
524
+ this.angle = new svg.Property('angle', s);
525
+ }
526
+ this.Type.SkewBase.prototype = new this.Type.matrix;
527
+
528
+ this.Type.skewX = function(s) {
529
+ this.base = that.Type.SkewBase;
530
+ this.base(s);
531
+ this.m = [1, 0, Math.tan(this.angle.toRadians()), 1, 0, 0];
532
+ }
533
+ this.Type.skewX.prototype = new this.Type.SkewBase;
534
+
535
+ this.Type.skewY = function(s) {
536
+ this.base = that.Type.SkewBase;
537
+ this.base(s);
538
+ this.m = [1, Math.tan(this.angle.toRadians()), 0, 1, 0, 0];
539
+ }
540
+ this.Type.skewY.prototype = new this.Type.SkewBase;
541
+
542
+ this.transforms = [];
543
+
544
+ this.apply = function(ctx) {
545
+ for (var i=0; i<this.transforms.length; i++) {
546
+ this.transforms[i].apply(ctx);
547
+ }
548
+ }
549
+
550
+ this.unapply = function(ctx) {
551
+ for (var i=this.transforms.length-1; i>=0; i--) {
552
+ this.transforms[i].unapply(ctx);
553
+ }
554
+ }
555
+
556
+ this.applyToPoint = function(p) {
557
+ for (var i=0; i<this.transforms.length; i++) {
558
+ this.transforms[i].applyToPoint(p);
559
+ }
560
+ }
561
+
562
+ var data = svg.trim(svg.compressSpaces(v)).replace(/\)(\s?,\s?)/g,') ').split(/\s(?=[a-z])/);
563
+ for (var i=0; i<data.length; i++) {
564
+ var type = svg.trim(data[i].split('(')[0]);
565
+ var s = data[i].split('(')[1].replace(')','');
566
+ var transform = new this.Type[type](s);
567
+ transform.type = type;
568
+ this.transforms.push(transform);
569
+ }
570
+ }
571
+
572
+ // aspect ratio
573
+ svg.AspectRatio = function(ctx, aspectRatio, width, desiredWidth, height, desiredHeight, minX, minY, refX, refY) {
574
+ // aspect ratio - http://www.w3.org/TR/SVG/coords.html#PreserveAspectRatioAttribute
575
+ aspectRatio = svg.compressSpaces(aspectRatio);
576
+ aspectRatio = aspectRatio.replace(/^defer\s/,''); // ignore defer
577
+ var align = aspectRatio.split(' ')[0] || 'xMidYMid';
578
+ var meetOrSlice = aspectRatio.split(' ')[1] || 'meet';
579
+
580
+ // calculate scale
581
+ var scaleX = width / desiredWidth;
582
+ var scaleY = height / desiredHeight;
583
+ var scaleMin = Math.min(scaleX, scaleY);
584
+ var scaleMax = Math.max(scaleX, scaleY);
585
+ if (meetOrSlice == 'meet') { desiredWidth *= scaleMin; desiredHeight *= scaleMin; }
586
+ if (meetOrSlice == 'slice') { desiredWidth *= scaleMax; desiredHeight *= scaleMax; }
587
+
588
+ refX = new svg.Property('refX', refX);
589
+ refY = new svg.Property('refY', refY);
590
+ if (refX.hasValue() && refY.hasValue()) {
591
+ ctx.translate(-scaleMin * refX.toPixels('x'), -scaleMin * refY.toPixels('y'));
592
+ }
593
+ else {
594
+ // align
595
+ if (align.match(/^xMid/) && ((meetOrSlice == 'meet' && scaleMin == scaleY) || (meetOrSlice == 'slice' && scaleMax == scaleY))) ctx.translate(width / 2.0 - desiredWidth / 2.0, 0);
596
+ if (align.match(/YMid$/) && ((meetOrSlice == 'meet' && scaleMin == scaleX) || (meetOrSlice == 'slice' && scaleMax == scaleX))) ctx.translate(0, height / 2.0 - desiredHeight / 2.0);
597
+ if (align.match(/^xMax/) && ((meetOrSlice == 'meet' && scaleMin == scaleY) || (meetOrSlice == 'slice' && scaleMax == scaleY))) ctx.translate(width - desiredWidth, 0);
598
+ if (align.match(/YMax$/) && ((meetOrSlice == 'meet' && scaleMin == scaleX) || (meetOrSlice == 'slice' && scaleMax == scaleX))) ctx.translate(0, height - desiredHeight);
599
+ }
600
+
601
+ // scale
602
+ if (align == 'none') ctx.scale(scaleX, scaleY);
603
+ else if (meetOrSlice == 'meet') ctx.scale(scaleMin, scaleMin);
604
+ else if (meetOrSlice == 'slice') ctx.scale(scaleMax, scaleMax);
605
+
606
+ // translate
607
+ ctx.translate(minX == null ? 0 : -minX, minY == null ? 0 : -minY);
608
+ }
609
+
610
+ // elements
611
+ svg.Element = {}
612
+
613
+ svg.EmptyProperty = new svg.Property('EMPTY', '');
614
+
615
+ svg.Element.ElementBase = function(node) {
616
+ this.attributes = {};
617
+ this.styles = {};
618
+ this.children = [];
619
+
620
+ // get or create attribute
621
+ this.attribute = function(name, createIfNotExists) {
622
+ var a = this.attributes[name];
623
+ if (a != null) return a;
624
+
625
+ if (createIfNotExists == true) { a = new svg.Property(name, ''); this.attributes[name] = a; }
626
+ return a || svg.EmptyProperty;
627
+ }
628
+
629
+ this.getHrefAttribute = function() {
630
+ for (var a in this.attributes) {
631
+ if (a.match(/:href$/)) {
632
+ return this.attributes[a];
633
+ }
634
+ }
635
+ return svg.EmptyProperty;
636
+ }
637
+
638
+ // get or create style, crawls up node tree
639
+ this.style = function(name, createIfNotExists) {
640
+ var s = this.styles[name];
641
+ if (s != null) return s;
642
+
643
+ var a = this.attribute(name);
644
+ if (a != null && a.hasValue()) {
645
+ this.styles[name] = a; // move up to me to cache
646
+ return a;
647
+ }
648
+
649
+ var p = this.parent;
650
+ if (p != null) {
651
+ var ps = p.style(name);
652
+ if (ps != null && ps.hasValue()) {
653
+ return ps;
654
+ }
655
+ }
656
+
657
+ if (createIfNotExists == true) { s = new svg.Property(name, ''); this.styles[name] = s; }
658
+ return s || svg.EmptyProperty;
659
+ }
660
+
661
+ // base render
662
+ this.render = function(ctx) {
663
+ // don't render display=none
664
+ if (this.style('display').value == 'none') return;
665
+
666
+ // don't render visibility=hidden
667
+ if (this.attribute('visibility').value == 'hidden') return;
668
+
669
+ ctx.save();
670
+ if (this.attribute('mask').hasValue()) { // mask
671
+ var mask = this.attribute('mask').getDefinition();
672
+ if (mask != null) mask.apply(ctx, this);
673
+ }
674
+ else if (this.style('filter').hasValue()) { // filter
675
+ var filter = this.style('filter').getDefinition();
676
+ if (filter != null) filter.apply(ctx, this);
677
+ }
678
+ else {
679
+ this.setContext(ctx);
680
+ this.renderChildren(ctx);
681
+ this.clearContext(ctx);
682
+ }
683
+ ctx.restore();
684
+ }
685
+
686
+ // base set context
687
+ this.setContext = function(ctx) {
688
+ // OVERRIDE ME!
689
+ }
690
+
691
+ // base clear context
692
+ this.clearContext = function(ctx) {
693
+ // OVERRIDE ME!
694
+ }
695
+
696
+ // base render children
697
+ this.renderChildren = function(ctx) {
698
+ for (var i=0; i<this.children.length; i++) {
699
+ this.children[i].render(ctx);
700
+ }
701
+ }
702
+
703
+ this.addChild = function(childNode, create) {
704
+ var child = childNode;
705
+ if (create) child = svg.CreateElement(childNode);
706
+ child.parent = this;
707
+ this.children.push(child);
708
+ }
709
+
710
+ if (node != null && node.nodeType == 1) { //ELEMENT_NODE
711
+ // add children
712
+ for (var i=0; i<node.childNodes.length; i++) {
713
+ var childNode = node.childNodes[i];
714
+ if (childNode.nodeType == 1) this.addChild(childNode, true); //ELEMENT_NODE
715
+ if (this.captureTextNodes && childNode.nodeType == 3) {
716
+ var text = childNode.nodeValue || childNode.text || '';
717
+ if (svg.trim(svg.compressSpaces(text)) != '') {
718
+ this.addChild(new svg.Element.tspan(childNode), false); // TEXT_NODE
719
+ }
720
+ }
721
+ }
722
+
723
+ // add attributes
724
+ for (var i=0; i<node.attributes.length; i++) {
725
+ var attribute = node.attributes[i];
726
+ this.attributes[attribute.nodeName] = new svg.Property(attribute.nodeName, attribute.nodeValue);
727
+ }
728
+
729
+ // add tag styles
730
+ var styles = svg.Styles[node.nodeName];
731
+ if (styles != null) {
732
+ for (var name in styles) {
733
+ this.styles[name] = styles[name];
734
+ }
735
+ }
736
+
737
+ // add class styles
738
+ if (this.attribute('class').hasValue()) {
739
+ var classes = svg.compressSpaces(this.attribute('class').value).split(' ');
740
+ for (var j=0; j<classes.length; j++) {
741
+ styles = svg.Styles['.'+classes[j]];
742
+ if (styles != null) {
743
+ for (var name in styles) {
744
+ this.styles[name] = styles[name];
745
+ }
746
+ }
747
+ styles = svg.Styles[node.nodeName+'.'+classes[j]];
748
+ if (styles != null) {
749
+ for (var name in styles) {
750
+ this.styles[name] = styles[name];
751
+ }
752
+ }
753
+ }
754
+ }
755
+
756
+ // add id styles
757
+ if (this.attribute('id').hasValue()) {
758
+ var styles = svg.Styles['#' + this.attribute('id').value];
759
+ if (styles != null) {
760
+ for (var name in styles) {
761
+ this.styles[name] = styles[name];
762
+ }
763
+ }
764
+ }
765
+
766
+ // add inline styles
767
+ if (this.attribute('style').hasValue()) {
768
+ var styles = this.attribute('style').value.split(';');
769
+ for (var i=0; i<styles.length; i++) {
770
+ if (svg.trim(styles[i]) != '') {
771
+ var style = styles[i].split(':');
772
+ var name = svg.trim(style[0]);
773
+ var value = svg.trim(style[1]);
774
+ this.styles[name] = new svg.Property(name, value);
775
+ }
776
+ }
777
+ }
778
+
779
+ // add id
780
+ if (this.attribute('id').hasValue()) {
781
+ if (svg.Definitions[this.attribute('id').value] == null) {
782
+ svg.Definitions[this.attribute('id').value] = this;
783
+ }
784
+ }
785
+ }
786
+ }
787
+
788
+ svg.Element.RenderedElementBase = function(node) {
789
+ this.base = svg.Element.ElementBase;
790
+ this.base(node);
791
+
792
+ this.setContext = function(ctx) {
793
+ // fill
794
+ if (this.style('fill').isUrlDefinition()) {
795
+ var fs = this.style('fill').getFillStyleDefinition(this, this.style('fill-opacity'));
796
+ if (fs != null) ctx.fillStyle = fs;
797
+ }
798
+ else if (this.style('fill').hasValue()) {
799
+ var fillStyle = this.style('fill');
800
+ if (fillStyle.value == 'currentColor') fillStyle.value = this.style('color').value;
801
+ ctx.fillStyle = (fillStyle.value == 'none' ? 'rgba(0,0,0,0)' : fillStyle.value);
802
+ }
803
+ if (this.style('fill-opacity').hasValue()) {
804
+ var fillStyle = new svg.Property('fill', ctx.fillStyle);
805
+ fillStyle = fillStyle.addOpacity(this.style('fill-opacity').value);
806
+ ctx.fillStyle = fillStyle.value;
807
+ }
808
+
809
+ // stroke
810
+ if (this.style('stroke').isUrlDefinition()) {
811
+ var fs = this.style('stroke').getFillStyleDefinition(this, this.style('stroke-opacity'));
812
+ if (fs != null) ctx.strokeStyle = fs;
813
+ }
814
+ else if (this.style('stroke').hasValue()) {
815
+ var strokeStyle = this.style('stroke');
816
+ if (strokeStyle.value == 'currentColor') strokeStyle.value = this.style('color').value;
817
+ ctx.strokeStyle = (strokeStyle.value == 'none' ? 'rgba(0,0,0,0)' : strokeStyle.value);
818
+ }
819
+ if (this.style('stroke-opacity').hasValue()) {
820
+ var strokeStyle = new svg.Property('stroke', ctx.strokeStyle);
821
+ strokeStyle = strokeStyle.addOpacity(this.style('stroke-opacity').value);
822
+ ctx.strokeStyle = strokeStyle.value;
823
+ }
824
+ if (this.style('stroke-width').hasValue()) {
825
+ var newLineWidth = this.style('stroke-width').toPixels();
826
+ ctx.lineWidth = newLineWidth == 0 ? 0.001 : newLineWidth; // browsers don't respect 0
827
+ }
828
+ if (this.style('stroke-linecap').hasValue()) ctx.lineCap = this.style('stroke-linecap').value;
829
+ if (this.style('stroke-linejoin').hasValue()) ctx.lineJoin = this.style('stroke-linejoin').value;
830
+ if (this.style('stroke-miterlimit').hasValue()) ctx.miterLimit = this.style('stroke-miterlimit').value;
831
+ if (this.style('stroke-dasharray').hasValue()) {
832
+ var gaps = svg.ToNumberArray(this.style('stroke-dasharray').value);
833
+ if (typeof(ctx.setLineDash) != 'undefined') { ctx.setLineDash(gaps); }
834
+ else if (typeof(ctx.webkitLineDash) != 'undefined') { ctx.webkitLineDash = gaps; }
835
+ else if (typeof(ctx.mozDash ) != 'undefined') { ctx.mozDash = gaps; }
836
+
837
+ var offset = this.style('stroke-dashoffset').numValueOrDefault(1);
838
+ if (typeof(ctx.lineDashOffset) != 'undefined') { ctx.lineDashOffset = offset; }
839
+ else if (typeof(ctx.webkitLineDashOffset) != 'undefined') { ctx.webkitLineDashOffset = offset; }
840
+ else if (typeof(ctx.mozDashOffset) != 'undefined') { ctx.mozDashOffset = offset; }
841
+ }
842
+
843
+ // font
844
+ if (typeof(ctx.font) != 'undefined') {
845
+ ctx.font = svg.Font.CreateFont(
846
+ this.style('font-style').value,
847
+ this.style('font-variant').value,
848
+ this.style('font-weight').value,
849
+ this.style('font-size').hasValue() ? this.style('font-size').toPixels() + 'px' : '',
850
+ this.style('font-family').value).toString();
851
+ }
852
+
853
+ // transform
854
+ if (this.attribute('transform').hasValue()) {
855
+ var transform = new svg.Transform(this.attribute('transform').value);
856
+ transform.apply(ctx);
857
+ }
858
+
859
+ // clip
860
+ if (this.style('clip-path').hasValue()) {
861
+ var clip = this.style('clip-path').getDefinition();
862
+ if (clip != null) clip.apply(ctx);
863
+ }
864
+
865
+ // opacity
866
+ if (this.style('opacity').hasValue()) {
867
+ ctx.globalAlpha = this.style('opacity').numValue();
868
+ }
869
+ }
870
+ }
871
+ svg.Element.RenderedElementBase.prototype = new svg.Element.ElementBase;
872
+
873
+ svg.Element.PathElementBase = function(node) {
874
+ this.base = svg.Element.RenderedElementBase;
875
+ this.base(node);
876
+
877
+ this.path = function(ctx) {
878
+ if (ctx != null) ctx.beginPath();
879
+ return new svg.BoundingBox();
880
+ }
881
+
882
+ this.renderChildren = function(ctx) {
883
+ this.path(ctx);
884
+ svg.Mouse.checkPath(this, ctx);
885
+ if (ctx.fillStyle != '') {
886
+ if (this.attribute('fill-rule').hasValue()) { ctx.fill(this.attribute('fill-rule').value); }
887
+ else { ctx.fill(); }
888
+ }
889
+ if (ctx.strokeStyle != '') ctx.stroke();
890
+
891
+ var markers = this.getMarkers();
892
+ if (markers != null) {
893
+ if (this.style('marker-start').isUrlDefinition()) {
894
+ var marker = this.style('marker-start').getDefinition();
895
+ marker.render(ctx, markers[0][0], markers[0][1]);
896
+ }
897
+ if (this.style('marker-mid').isUrlDefinition()) {
898
+ var marker = this.style('marker-mid').getDefinition();
899
+ for (var i=1;i<markers.length-1;i++) {
900
+ marker.render(ctx, markers[i][0], markers[i][1]);
901
+ }
902
+ }
903
+ if (this.style('marker-end').isUrlDefinition()) {
904
+ var marker = this.style('marker-end').getDefinition();
905
+ marker.render(ctx, markers[markers.length-1][0], markers[markers.length-1][1]);
906
+ }
907
+ }
908
+ }
909
+
910
+ this.getBoundingBox = function() {
911
+ return this.path();
912
+ }
913
+
914
+ this.getMarkers = function() {
915
+ return null;
916
+ }
917
+ }
918
+ svg.Element.PathElementBase.prototype = new svg.Element.RenderedElementBase;
919
+
920
+ // svg element
921
+ svg.Element.svg = function(node) {
922
+ this.base = svg.Element.RenderedElementBase;
923
+ this.base(node);
924
+
925
+ this.baseClearContext = this.clearContext;
926
+ this.clearContext = function(ctx) {
927
+ this.baseClearContext(ctx);
928
+ svg.ViewPort.RemoveCurrent();
929
+ }
930
+
931
+ this.baseSetContext = this.setContext;
932
+ this.setContext = function(ctx) {
933
+ // initial values
934
+ ctx.strokeStyle = 'rgba(0,0,0,0)';
935
+ ctx.lineCap = 'butt';
936
+ ctx.lineJoin = 'miter';
937
+ ctx.miterLimit = 4;
938
+
939
+ this.baseSetContext(ctx);
940
+
941
+ // create new view port
942
+ if (!this.attribute('x').hasValue()) this.attribute('x', true).value = 0;
943
+ if (!this.attribute('y').hasValue()) this.attribute('y', true).value = 0;
944
+ ctx.translate(this.attribute('x').toPixels('x'), this.attribute('y').toPixels('y'));
945
+
946
+ var width = svg.ViewPort.width();
947
+ var height = svg.ViewPort.height();
948
+
949
+ if (!this.attribute('width').hasValue()) this.attribute('width', true).value = '100%';
950
+ if (!this.attribute('height').hasValue()) this.attribute('height', true).value = '100%';
951
+ if (typeof(this.root) == 'undefined') {
952
+ width = this.attribute('width').toPixels('x');
953
+ height = this.attribute('height').toPixels('y');
954
+
955
+ var x = 0;
956
+ var y = 0;
957
+ if (this.attribute('refX').hasValue() && this.attribute('refY').hasValue()) {
958
+ x = -this.attribute('refX').toPixels('x');
959
+ y = -this.attribute('refY').toPixels('y');
960
+ }
961
+
962
+ ctx.beginPath();
963
+ ctx.moveTo(x, y);
964
+ ctx.lineTo(width, y);
965
+ ctx.lineTo(width, height);
966
+ ctx.lineTo(x, height);
967
+ ctx.closePath();
968
+ ctx.clip();
969
+ }
970
+ svg.ViewPort.SetCurrent(width, height);
971
+
972
+ // viewbox
973
+ if (this.attribute('viewBox').hasValue()) {
974
+ var viewBox = svg.ToNumberArray(this.attribute('viewBox').value);
975
+ var minX = viewBox[0];
976
+ var minY = viewBox[1];
977
+ width = viewBox[2];
978
+ height = viewBox[3];
979
+
980
+ svg.AspectRatio(ctx,
981
+ this.attribute('preserveAspectRatio').value,
982
+ svg.ViewPort.width(),
983
+ width,
984
+ svg.ViewPort.height(),
985
+ height,
986
+ minX,
987
+ minY,
988
+ this.attribute('refX').value,
989
+ this.attribute('refY').value);
990
+
991
+ svg.ViewPort.RemoveCurrent();
992
+ svg.ViewPort.SetCurrent(viewBox[2], viewBox[3]);
993
+ }
994
+ }
995
+ }
996
+ svg.Element.svg.prototype = new svg.Element.RenderedElementBase;
997
+
998
+ // rect element
999
+ svg.Element.rect = function(node) {
1000
+ this.base = svg.Element.PathElementBase;
1001
+ this.base(node);
1002
+
1003
+ this.path = function(ctx) {
1004
+ var x = this.attribute('x').toPixels('x');
1005
+ var y = this.attribute('y').toPixels('y');
1006
+ var width = this.attribute('width').toPixels('x');
1007
+ var height = this.attribute('height').toPixels('y');
1008
+ var rx = this.attribute('rx').toPixels('x');
1009
+ var ry = this.attribute('ry').toPixels('y');
1010
+ if (this.attribute('rx').hasValue() && !this.attribute('ry').hasValue()) ry = rx;
1011
+ if (this.attribute('ry').hasValue() && !this.attribute('rx').hasValue()) rx = ry;
1012
+ rx = Math.min(rx, width / 2.0);
1013
+ ry = Math.min(ry, height / 2.0);
1014
+ if (ctx != null) {
1015
+ ctx.beginPath();
1016
+ ctx.moveTo(x + rx, y);
1017
+ ctx.lineTo(x + width - rx, y);
1018
+ ctx.quadraticCurveTo(x + width, y, x + width, y + ry)
1019
+ ctx.lineTo(x + width, y + height - ry);
1020
+ ctx.quadraticCurveTo(x + width, y + height, x + width - rx, y + height)
1021
+ ctx.lineTo(x + rx, y + height);
1022
+ ctx.quadraticCurveTo(x, y + height, x, y + height - ry)
1023
+ ctx.lineTo(x, y + ry);
1024
+ ctx.quadraticCurveTo(x, y, x + rx, y)
1025
+ ctx.closePath();
1026
+ }
1027
+
1028
+ return new svg.BoundingBox(x, y, x + width, y + height);
1029
+ }
1030
+ }
1031
+ svg.Element.rect.prototype = new svg.Element.PathElementBase;
1032
+
1033
+ // circle element
1034
+ svg.Element.circle = function(node) {
1035
+ this.base = svg.Element.PathElementBase;
1036
+ this.base(node);
1037
+
1038
+ this.path = function(ctx) {
1039
+ var cx = this.attribute('cx').toPixels('x');
1040
+ var cy = this.attribute('cy').toPixels('y');
1041
+ var r = this.attribute('r').toPixels();
1042
+
1043
+ if (ctx != null) {
1044
+ ctx.beginPath();
1045
+ ctx.arc(cx, cy, r, 0, Math.PI * 2, true);
1046
+ ctx.closePath();
1047
+ }
1048
+
1049
+ return new svg.BoundingBox(cx - r, cy - r, cx + r, cy + r);
1050
+ }
1051
+ }
1052
+ svg.Element.circle.prototype = new svg.Element.PathElementBase;
1053
+
1054
+ // ellipse element
1055
+ svg.Element.ellipse = function(node) {
1056
+ this.base = svg.Element.PathElementBase;
1057
+ this.base(node);
1058
+
1059
+ this.path = function(ctx) {
1060
+ var KAPPA = 4 * ((Math.sqrt(2) - 1) / 3);
1061
+ var rx = this.attribute('rx').toPixels('x');
1062
+ var ry = this.attribute('ry').toPixels('y');
1063
+ var cx = this.attribute('cx').toPixels('x');
1064
+ var cy = this.attribute('cy').toPixels('y');
1065
+
1066
+ if (ctx != null) {
1067
+ ctx.beginPath();
1068
+ ctx.moveTo(cx, cy - ry);
1069
+ ctx.bezierCurveTo(cx + (KAPPA * rx), cy - ry, cx + rx, cy - (KAPPA * ry), cx + rx, cy);
1070
+ ctx.bezierCurveTo(cx + rx, cy + (KAPPA * ry), cx + (KAPPA * rx), cy + ry, cx, cy + ry);
1071
+ ctx.bezierCurveTo(cx - (KAPPA * rx), cy + ry, cx - rx, cy + (KAPPA * ry), cx - rx, cy);
1072
+ ctx.bezierCurveTo(cx - rx, cy - (KAPPA * ry), cx - (KAPPA * rx), cy - ry, cx, cy - ry);
1073
+ ctx.closePath();
1074
+ }
1075
+
1076
+ return new svg.BoundingBox(cx - rx, cy - ry, cx + rx, cy + ry);
1077
+ }
1078
+ }
1079
+ svg.Element.ellipse.prototype = new svg.Element.PathElementBase;
1080
+
1081
+ // line element
1082
+ svg.Element.line = function(node) {
1083
+ this.base = svg.Element.PathElementBase;
1084
+ this.base(node);
1085
+
1086
+ this.getPoints = function() {
1087
+ return [
1088
+ new svg.Point(this.attribute('x1').toPixels('x'), this.attribute('y1').toPixels('y')),
1089
+ new svg.Point(this.attribute('x2').toPixels('x'), this.attribute('y2').toPixels('y'))];
1090
+ }
1091
+
1092
+ this.path = function(ctx) {
1093
+ var points = this.getPoints();
1094
+
1095
+ if (ctx != null) {
1096
+ ctx.beginPath();
1097
+ ctx.moveTo(points[0].x, points[0].y);
1098
+ ctx.lineTo(points[1].x, points[1].y);
1099
+ }
1100
+
1101
+ return new svg.BoundingBox(points[0].x, points[0].y, points[1].x, points[1].y);
1102
+ }
1103
+
1104
+ this.getMarkers = function() {
1105
+ var points = this.getPoints();
1106
+ var a = points[0].angleTo(points[1]);
1107
+ return [[points[0], a], [points[1], a]];
1108
+ }
1109
+ }
1110
+ svg.Element.line.prototype = new svg.Element.PathElementBase;
1111
+
1112
+ // polyline element
1113
+ svg.Element.polyline = function(node) {
1114
+ this.base = svg.Element.PathElementBase;
1115
+ this.base(node);
1116
+
1117
+ this.points = svg.CreatePath(this.attribute('points').value);
1118
+ this.path = function(ctx) {
1119
+ var bb = new svg.BoundingBox(this.points[0].x, this.points[0].y);
1120
+ if (ctx != null) {
1121
+ ctx.beginPath();
1122
+ ctx.moveTo(this.points[0].x, this.points[0].y);
1123
+ }
1124
+ for (var i=1; i<this.points.length; i++) {
1125
+ bb.addPoint(this.points[i].x, this.points[i].y);
1126
+ if (ctx != null) ctx.lineTo(this.points[i].x, this.points[i].y);
1127
+ }
1128
+ return bb;
1129
+ }
1130
+
1131
+ this.getMarkers = function() {
1132
+ var markers = [];
1133
+ for (var i=0; i<this.points.length - 1; i++) {
1134
+ markers.push([this.points[i], this.points[i].angleTo(this.points[i+1])]);
1135
+ }
1136
+ markers.push([this.points[this.points.length-1], markers[markers.length-1][1]]);
1137
+ return markers;
1138
+ }
1139
+ }
1140
+ svg.Element.polyline.prototype = new svg.Element.PathElementBase;
1141
+
1142
+ // polygon element
1143
+ svg.Element.polygon = function(node) {
1144
+ this.base = svg.Element.polyline;
1145
+ this.base(node);
1146
+
1147
+ this.basePath = this.path;
1148
+ this.path = function(ctx) {
1149
+ var bb = this.basePath(ctx);
1150
+ if (ctx != null) {
1151
+ ctx.lineTo(this.points[0].x, this.points[0].y);
1152
+ ctx.closePath();
1153
+ }
1154
+ return bb;
1155
+ }
1156
+ }
1157
+ svg.Element.polygon.prototype = new svg.Element.polyline;
1158
+
1159
+ // path element
1160
+ svg.Element.path = function(node) {
1161
+ this.base = svg.Element.PathElementBase;
1162
+ this.base(node);
1163
+
1164
+ var d = this.attribute('d').value;
1165
+ // TODO: convert to real lexer based on http://www.w3.org/TR/SVG11/paths.html#PathDataBNF
1166
+ d = d.replace(/,/gm,' '); // get rid of all commas
1167
+ d = d.replace(/([MmZzLlHhVvCcSsQqTtAa])([MmZzLlHhVvCcSsQqTtAa])/gm,'$1 $2'); // separate commands from commands
1168
+ d = d.replace(/([MmZzLlHhVvCcSsQqTtAa])([MmZzLlHhVvCcSsQqTtAa])/gm,'$1 $2'); // separate commands from commands
1169
+ d = d.replace(/([MmZzLlHhVvCcSsQqTtAa])([^\s])/gm,'$1 $2'); // separate commands from points
1170
+ d = d.replace(/([^\s])([MmZzLlHhVvCcSsQqTtAa])/gm,'$1 $2'); // separate commands from points
1171
+ d = d.replace(/([0-9])([+\-])/gm,'$1 $2'); // separate digits when no comma
1172
+ d = d.replace(/(\.[0-9]*)(\.)/gm,'$1 $2'); // separate digits when no comma
1173
+ d = d.replace(/([Aa](\s+[0-9]+){3})\s+([01])\s*([01])/gm,'$1 $3 $4 '); // shorthand elliptical arc path syntax
1174
+ d = svg.compressSpaces(d); // compress multiple spaces
1175
+ d = svg.trim(d);
1176
+ this.PathParser = new (function(d) {
1177
+ this.tokens = d.split(' ');
1178
+
1179
+ this.reset = function() {
1180
+ this.i = -1;
1181
+ this.command = '';
1182
+ this.previousCommand = '';
1183
+ this.start = new svg.Point(0, 0);
1184
+ this.control = new svg.Point(0, 0);
1185
+ this.current = new svg.Point(0, 0);
1186
+ this.points = [];
1187
+ this.angles = [];
1188
+ }
1189
+
1190
+ this.isEnd = function() {
1191
+ return this.i >= this.tokens.length - 1;
1192
+ }
1193
+
1194
+ this.isCommandOrEnd = function() {
1195
+ if (this.isEnd()) return true;
1196
+ return this.tokens[this.i + 1].match(/^[A-Za-z]$/) != null;
1197
+ }
1198
+
1199
+ this.isRelativeCommand = function() {
1200
+ switch(this.command)
1201
+ {
1202
+ case 'm':
1203
+ case 'l':
1204
+ case 'h':
1205
+ case 'v':
1206
+ case 'c':
1207
+ case 's':
1208
+ case 'q':
1209
+ case 't':
1210
+ case 'a':
1211
+ case 'z':
1212
+ return true;
1213
+ break;
1214
+ }
1215
+ return false;
1216
+ }
1217
+
1218
+ this.getToken = function() {
1219
+ this.i++;
1220
+ return this.tokens[this.i];
1221
+ }
1222
+
1223
+ this.getScalar = function() {
1224
+ return parseFloat(this.getToken());
1225
+ }
1226
+
1227
+ this.nextCommand = function() {
1228
+ this.previousCommand = this.command;
1229
+ this.command = this.getToken();
1230
+ }
1231
+
1232
+ this.getPoint = function() {
1233
+ var p = new svg.Point(this.getScalar(), this.getScalar());
1234
+ return this.makeAbsolute(p);
1235
+ }
1236
+
1237
+ this.getAsControlPoint = function() {
1238
+ var p = this.getPoint();
1239
+ this.control = p;
1240
+ return p;
1241
+ }
1242
+
1243
+ this.getAsCurrentPoint = function() {
1244
+ var p = this.getPoint();
1245
+ this.current = p;
1246
+ return p;
1247
+ }
1248
+
1249
+ this.getReflectedControlPoint = function() {
1250
+ if (this.previousCommand.toLowerCase() != 'c' &&
1251
+ this.previousCommand.toLowerCase() != 's' &&
1252
+ this.previousCommand.toLowerCase() != 'q' &&
1253
+ this.previousCommand.toLowerCase() != 't' ){
1254
+ return this.current;
1255
+ }
1256
+
1257
+ // reflect point
1258
+ var p = new svg.Point(2 * this.current.x - this.control.x, 2 * this.current.y - this.control.y);
1259
+ return p;
1260
+ }
1261
+
1262
+ this.makeAbsolute = function(p) {
1263
+ if (this.isRelativeCommand()) {
1264
+ p.x += this.current.x;
1265
+ p.y += this.current.y;
1266
+ }
1267
+ return p;
1268
+ }
1269
+
1270
+ this.addMarker = function(p, from, priorTo) {
1271
+ // if the last angle isn't filled in because we didn't have this point yet ...
1272
+ if (priorTo != null && this.angles.length > 0 && this.angles[this.angles.length-1] == null) {
1273
+ this.angles[this.angles.length-1] = this.points[this.points.length-1].angleTo(priorTo);
1274
+ }
1275
+ this.addMarkerAngle(p, from == null ? null : from.angleTo(p));
1276
+ }
1277
+
1278
+ this.addMarkerAngle = function(p, a) {
1279
+ this.points.push(p);
1280
+ this.angles.push(a);
1281
+ }
1282
+
1283
+ this.getMarkerPoints = function() { return this.points; }
1284
+ this.getMarkerAngles = function() {
1285
+ for (var i=0; i<this.angles.length; i++) {
1286
+ if (this.angles[i] == null) {
1287
+ for (var j=i+1; j<this.angles.length; j++) {
1288
+ if (this.angles[j] != null) {
1289
+ this.angles[i] = this.angles[j];
1290
+ break;
1291
+ }
1292
+ }
1293
+ }
1294
+ }
1295
+ return this.angles;
1296
+ }
1297
+ })(d);
1298
+
1299
+ this.path = function(ctx) {
1300
+ var pp = this.PathParser;
1301
+ pp.reset();
1302
+
1303
+ var bb = new svg.BoundingBox();
1304
+ if (ctx != null) ctx.beginPath();
1305
+ while (!pp.isEnd()) {
1306
+ pp.nextCommand();
1307
+ switch (pp.command) {
1308
+ case 'M':
1309
+ case 'm':
1310
+ var p = pp.getAsCurrentPoint();
1311
+ pp.addMarker(p);
1312
+ bb.addPoint(p.x, p.y);
1313
+ if (ctx != null) ctx.moveTo(p.x, p.y);
1314
+ pp.start = pp.current;
1315
+ while (!pp.isCommandOrEnd()) {
1316
+ var p = pp.getAsCurrentPoint();
1317
+ pp.addMarker(p, pp.start);
1318
+ bb.addPoint(p.x, p.y);
1319
+ if (ctx != null) ctx.lineTo(p.x, p.y);
1320
+ }
1321
+ break;
1322
+ case 'L':
1323
+ case 'l':
1324
+ while (!pp.isCommandOrEnd()) {
1325
+ var c = pp.current;
1326
+ var p = pp.getAsCurrentPoint();
1327
+ pp.addMarker(p, c);
1328
+ bb.addPoint(p.x, p.y);
1329
+ if (ctx != null) ctx.lineTo(p.x, p.y);
1330
+ }
1331
+ break;
1332
+ case 'H':
1333
+ case 'h':
1334
+ while (!pp.isCommandOrEnd()) {
1335
+ var newP = new svg.Point((pp.isRelativeCommand() ? pp.current.x : 0) + pp.getScalar(), pp.current.y);
1336
+ pp.addMarker(newP, pp.current);
1337
+ pp.current = newP;
1338
+ bb.addPoint(pp.current.x, pp.current.y);
1339
+ if (ctx != null) ctx.lineTo(pp.current.x, pp.current.y);
1340
+ }
1341
+ break;
1342
+ case 'V':
1343
+ case 'v':
1344
+ while (!pp.isCommandOrEnd()) {
1345
+ var newP = new svg.Point(pp.current.x, (pp.isRelativeCommand() ? pp.current.y : 0) + pp.getScalar());
1346
+ pp.addMarker(newP, pp.current);
1347
+ pp.current = newP;
1348
+ bb.addPoint(pp.current.x, pp.current.y);
1349
+ if (ctx != null) ctx.lineTo(pp.current.x, pp.current.y);
1350
+ }
1351
+ break;
1352
+ case 'C':
1353
+ case 'c':
1354
+ while (!pp.isCommandOrEnd()) {
1355
+ var curr = pp.current;
1356
+ var p1 = pp.getPoint();
1357
+ var cntrl = pp.getAsControlPoint();
1358
+ var cp = pp.getAsCurrentPoint();
1359
+ pp.addMarker(cp, cntrl, p1);
1360
+ bb.addBezierCurve(curr.x, curr.y, p1.x, p1.y, cntrl.x, cntrl.y, cp.x, cp.y);
1361
+ if (ctx != null) ctx.bezierCurveTo(p1.x, p1.y, cntrl.x, cntrl.y, cp.x, cp.y);
1362
+ }
1363
+ break;
1364
+ case 'S':
1365
+ case 's':
1366
+ while (!pp.isCommandOrEnd()) {
1367
+ var curr = pp.current;
1368
+ var p1 = pp.getReflectedControlPoint();
1369
+ var cntrl = pp.getAsControlPoint();
1370
+ var cp = pp.getAsCurrentPoint();
1371
+ pp.addMarker(cp, cntrl, p1);
1372
+ bb.addBezierCurve(curr.x, curr.y, p1.x, p1.y, cntrl.x, cntrl.y, cp.x, cp.y);
1373
+ if (ctx != null) ctx.bezierCurveTo(p1.x, p1.y, cntrl.x, cntrl.y, cp.x, cp.y);
1374
+ }
1375
+ break;
1376
+ case 'Q':
1377
+ case 'q':
1378
+ while (!pp.isCommandOrEnd()) {
1379
+ var curr = pp.current;
1380
+ var cntrl = pp.getAsControlPoint();
1381
+ var cp = pp.getAsCurrentPoint();
1382
+ pp.addMarker(cp, cntrl, cntrl);
1383
+ bb.addQuadraticCurve(curr.x, curr.y, cntrl.x, cntrl.y, cp.x, cp.y);
1384
+ if (ctx != null) ctx.quadraticCurveTo(cntrl.x, cntrl.y, cp.x, cp.y);
1385
+ }
1386
+ break;
1387
+ case 'T':
1388
+ case 't':
1389
+ while (!pp.isCommandOrEnd()) {
1390
+ var curr = pp.current;
1391
+ var cntrl = pp.getReflectedControlPoint();
1392
+ pp.control = cntrl;
1393
+ var cp = pp.getAsCurrentPoint();
1394
+ pp.addMarker(cp, cntrl, cntrl);
1395
+ bb.addQuadraticCurve(curr.x, curr.y, cntrl.x, cntrl.y, cp.x, cp.y);
1396
+ if (ctx != null) ctx.quadraticCurveTo(cntrl.x, cntrl.y, cp.x, cp.y);
1397
+ }
1398
+ break;
1399
+ case 'A':
1400
+ case 'a':
1401
+ while (!pp.isCommandOrEnd()) {
1402
+ var curr = pp.current;
1403
+ var rx = pp.getScalar();
1404
+ var ry = pp.getScalar();
1405
+ var xAxisRotation = pp.getScalar() * (Math.PI / 180.0);
1406
+ var largeArcFlag = pp.getScalar();
1407
+ var sweepFlag = pp.getScalar();
1408
+ var cp = pp.getAsCurrentPoint();
1409
+
1410
+ // Conversion from endpoint to center parameterization
1411
+ // http://www.w3.org/TR/SVG11/implnote.html#ArcImplementationNotes
1412
+ // x1', y1'
1413
+ var currp = new svg.Point(
1414
+ Math.cos(xAxisRotation) * (curr.x - cp.x) / 2.0 + Math.sin(xAxisRotation) * (curr.y - cp.y) / 2.0,
1415
+ -Math.sin(xAxisRotation) * (curr.x - cp.x) / 2.0 + Math.cos(xAxisRotation) * (curr.y - cp.y) / 2.0
1416
+ );
1417
+ // adjust radii
1418
+ var l = Math.pow(currp.x,2)/Math.pow(rx,2)+Math.pow(currp.y,2)/Math.pow(ry,2);
1419
+ if (l > 1) {
1420
+ rx *= Math.sqrt(l);
1421
+ ry *= Math.sqrt(l);
1422
+ }
1423
+ // cx', cy'
1424
+ var s = (largeArcFlag == sweepFlag ? -1 : 1) * Math.sqrt(
1425
+ ((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))) /
1426
+ (Math.pow(rx,2)*Math.pow(currp.y,2)+Math.pow(ry,2)*Math.pow(currp.x,2))
1427
+ );
1428
+ if (isNaN(s)) s = 0;
1429
+ var cpp = new svg.Point(s * rx * currp.y / ry, s * -ry * currp.x / rx);
1430
+ // cx, cy
1431
+ var centp = new svg.Point(
1432
+ (curr.x + cp.x) / 2.0 + Math.cos(xAxisRotation) * cpp.x - Math.sin(xAxisRotation) * cpp.y,
1433
+ (curr.y + cp.y) / 2.0 + Math.sin(xAxisRotation) * cpp.x + Math.cos(xAxisRotation) * cpp.y
1434
+ );
1435
+ // vector magnitude
1436
+ var m = function(v) { return Math.sqrt(Math.pow(v[0],2) + Math.pow(v[1],2)); }
1437
+ // ratio between two vectors
1438
+ var r = function(u, v) { return (u[0]*v[0]+u[1]*v[1]) / (m(u)*m(v)) }
1439
+ // angle between two vectors
1440
+ var a = function(u, v) { return (u[0]*v[1] < u[1]*v[0] ? -1 : 1) * Math.acos(r(u,v)); }
1441
+ // initial angle
1442
+ var a1 = a([1,0], [(currp.x-cpp.x)/rx,(currp.y-cpp.y)/ry]);
1443
+ // angle delta
1444
+ var u = [(currp.x-cpp.x)/rx,(currp.y-cpp.y)/ry];
1445
+ var v = [(-currp.x-cpp.x)/rx,(-currp.y-cpp.y)/ry];
1446
+ var ad = a(u, v);
1447
+ if (r(u,v) <= -1) ad = Math.PI;
1448
+ if (r(u,v) >= 1) ad = 0;
1449
+
1450
+ // for markers
1451
+ var dir = 1 - sweepFlag ? 1.0 : -1.0;
1452
+ var ah = a1 + dir * (ad / 2.0);
1453
+ var halfWay = new svg.Point(
1454
+ centp.x + rx * Math.cos(ah),
1455
+ centp.y + ry * Math.sin(ah)
1456
+ );
1457
+ pp.addMarkerAngle(halfWay, ah - dir * Math.PI / 2);
1458
+ pp.addMarkerAngle(cp, ah - dir * Math.PI);
1459
+
1460
+ bb.addPoint(cp.x, cp.y); // TODO: this is too naive, make it better
1461
+ if (ctx != null) {
1462
+ var r = rx > ry ? rx : ry;
1463
+ var sx = rx > ry ? 1 : rx / ry;
1464
+ var sy = rx > ry ? ry / rx : 1;
1465
+
1466
+ ctx.translate(centp.x, centp.y);
1467
+ ctx.rotate(xAxisRotation);
1468
+ ctx.scale(sx, sy);
1469
+ ctx.arc(0, 0, r, a1, a1 + ad, 1 - sweepFlag);
1470
+ ctx.scale(1/sx, 1/sy);
1471
+ ctx.rotate(-xAxisRotation);
1472
+ ctx.translate(-centp.x, -centp.y);
1473
+ }
1474
+ }
1475
+ break;
1476
+ case 'Z':
1477
+ case 'z':
1478
+ if (ctx != null) ctx.closePath();
1479
+ pp.current = pp.start;
1480
+ }
1481
+ }
1482
+
1483
+ return bb;
1484
+ }
1485
+
1486
+ this.getMarkers = function() {
1487
+ var points = this.PathParser.getMarkerPoints();
1488
+ var angles = this.PathParser.getMarkerAngles();
1489
+
1490
+ var markers = [];
1491
+ for (var i=0; i<points.length; i++) {
1492
+ markers.push([points[i], angles[i]]);
1493
+ }
1494
+ return markers;
1495
+ }
1496
+ }
1497
+ svg.Element.path.prototype = new svg.Element.PathElementBase;
1498
+
1499
+ // pattern element
1500
+ svg.Element.pattern = function(node) {
1501
+ this.base = svg.Element.ElementBase;
1502
+ this.base(node);
1503
+
1504
+ this.createPattern = function(ctx, element) {
1505
+ var width = this.attribute('width').toPixels('x', true);
1506
+ var height = this.attribute('height').toPixels('y', true);
1507
+
1508
+ // render me using a temporary svg element
1509
+ var tempSvg = new svg.Element.svg();
1510
+ tempSvg.attributes['viewBox'] = new svg.Property('viewBox', this.attribute('viewBox').value);
1511
+ tempSvg.attributes['width'] = new svg.Property('width', width + 'px');
1512
+ tempSvg.attributes['height'] = new svg.Property('height', height + 'px');
1513
+ tempSvg.attributes['transform'] = new svg.Property('transform', this.attribute('patternTransform').value);
1514
+ tempSvg.children = this.children;
1515
+
1516
+ var c = document.createElement('canvas');
1517
+ c.width = width;
1518
+ c.height = height;
1519
+ var cctx = c.getContext('2d');
1520
+ if (this.attribute('x').hasValue() && this.attribute('y').hasValue()) {
1521
+ cctx.translate(this.attribute('x').toPixels('x', true), this.attribute('y').toPixels('y', true));
1522
+ }
1523
+ // render 3x3 grid so when we transform there's no white space on edges
1524
+ for (var x=-1; x<=1; x++) {
1525
+ for (var y=-1; y<=1; y++) {
1526
+ cctx.save();
1527
+ cctx.translate(x * c.width, y * c.height);
1528
+ tempSvg.render(cctx);
1529
+ cctx.restore();
1530
+ }
1531
+ }
1532
+ var pattern = ctx.createPattern(c, 'repeat');
1533
+ return pattern;
1534
+ }
1535
+ }
1536
+ svg.Element.pattern.prototype = new svg.Element.ElementBase;
1537
+
1538
+ // marker element
1539
+ svg.Element.marker = function(node) {
1540
+ this.base = svg.Element.ElementBase;
1541
+ this.base(node);
1542
+
1543
+ this.baseRender = this.render;
1544
+ this.render = function(ctx, point, angle) {
1545
+ ctx.translate(point.x, point.y);
1546
+ if (this.attribute('orient').valueOrDefault('auto') == 'auto') ctx.rotate(angle);
1547
+ if (this.attribute('markerUnits').valueOrDefault('strokeWidth') == 'strokeWidth') ctx.scale(ctx.lineWidth, ctx.lineWidth);
1548
+ ctx.save();
1549
+
1550
+ // render me using a temporary svg element
1551
+ var tempSvg = new svg.Element.svg();
1552
+ tempSvg.attributes['viewBox'] = new svg.Property('viewBox', this.attribute('viewBox').value);
1553
+ tempSvg.attributes['refX'] = new svg.Property('refX', this.attribute('refX').value);
1554
+ tempSvg.attributes['refY'] = new svg.Property('refY', this.attribute('refY').value);
1555
+ tempSvg.attributes['width'] = new svg.Property('width', this.attribute('markerWidth').value);
1556
+ tempSvg.attributes['height'] = new svg.Property('height', this.attribute('markerHeight').value);
1557
+ tempSvg.attributes['fill'] = new svg.Property('fill', this.attribute('fill').valueOrDefault('black'));
1558
+ tempSvg.attributes['stroke'] = new svg.Property('stroke', this.attribute('stroke').valueOrDefault('none'));
1559
+ tempSvg.children = this.children;
1560
+ tempSvg.render(ctx);
1561
+
1562
+ ctx.restore();
1563
+ if (this.attribute('markerUnits').valueOrDefault('strokeWidth') == 'strokeWidth') ctx.scale(1/ctx.lineWidth, 1/ctx.lineWidth);
1564
+ if (this.attribute('orient').valueOrDefault('auto') == 'auto') ctx.rotate(-angle);
1565
+ ctx.translate(-point.x, -point.y);
1566
+ }
1567
+ }
1568
+ svg.Element.marker.prototype = new svg.Element.ElementBase;
1569
+
1570
+ // definitions element
1571
+ svg.Element.defs = function(node) {
1572
+ this.base = svg.Element.ElementBase;
1573
+ this.base(node);
1574
+
1575
+ this.render = function(ctx) {
1576
+ // NOOP
1577
+ }
1578
+ }
1579
+ svg.Element.defs.prototype = new svg.Element.ElementBase;
1580
+
1581
+ // base for gradients
1582
+ svg.Element.GradientBase = function(node) {
1583
+ this.base = svg.Element.ElementBase;
1584
+ this.base(node);
1585
+
1586
+ this.gradientUnits = this.attribute('gradientUnits').valueOrDefault('objectBoundingBox');
1587
+
1588
+ this.stops = [];
1589
+ for (var i=0; i<this.children.length; i++) {
1590
+ var child = this.children[i];
1591
+ if (child.type == 'stop') this.stops.push(child);
1592
+ }
1593
+
1594
+ this.getGradient = function() {
1595
+ // OVERRIDE ME!
1596
+ }
1597
+
1598
+ this.createGradient = function(ctx, element, parentOpacityProp) {
1599
+ var stopsContainer = this;
1600
+ if (this.getHrefAttribute().hasValue()) {
1601
+ stopsContainer = this.getHrefAttribute().getDefinition();
1602
+ }
1603
+
1604
+ var addParentOpacity = function (color) {
1605
+ if (parentOpacityProp.hasValue()) {
1606
+ var p = new svg.Property('color', color);
1607
+ return p.addOpacity(parentOpacityProp.value).value;
1608
+ }
1609
+ return color;
1610
+ };
1611
+
1612
+ var g = this.getGradient(ctx, element);
1613
+ if (g == null) return addParentOpacity(stopsContainer.stops[stopsContainer.stops.length - 1].color);
1614
+ for (var i=0; i<stopsContainer.stops.length; i++) {
1615
+ g.addColorStop(stopsContainer.stops[i].offset, addParentOpacity(stopsContainer.stops[i].color));
1616
+ }
1617
+
1618
+ if (this.attribute('gradientTransform').hasValue()) {
1619
+ // render as transformed pattern on temporary canvas
1620
+ var rootView = svg.ViewPort.viewPorts[0];
1621
+
1622
+ var rect = new svg.Element.rect();
1623
+ rect.attributes['x'] = new svg.Property('x', -svg.MAX_VIRTUAL_PIXELS/3.0);
1624
+ rect.attributes['y'] = new svg.Property('y', -svg.MAX_VIRTUAL_PIXELS/3.0);
1625
+ rect.attributes['width'] = new svg.Property('width', svg.MAX_VIRTUAL_PIXELS);
1626
+ rect.attributes['height'] = new svg.Property('height', svg.MAX_VIRTUAL_PIXELS);
1627
+
1628
+ var group = new svg.Element.g();
1629
+ group.attributes['transform'] = new svg.Property('transform', this.attribute('gradientTransform').value);
1630
+ group.children = [ rect ];
1631
+
1632
+ var tempSvg = new svg.Element.svg();
1633
+ tempSvg.attributes['x'] = new svg.Property('x', 0);
1634
+ tempSvg.attributes['y'] = new svg.Property('y', 0);
1635
+ tempSvg.attributes['width'] = new svg.Property('width', rootView.width);
1636
+ tempSvg.attributes['height'] = new svg.Property('height', rootView.height);
1637
+ tempSvg.children = [ group ];
1638
+
1639
+ var c = document.createElement('canvas');
1640
+ c.width = rootView.width;
1641
+ c.height = rootView.height;
1642
+ var tempCtx = c.getContext('2d');
1643
+ tempCtx.fillStyle = g;
1644
+ tempSvg.render(tempCtx);
1645
+ return tempCtx.createPattern(c, 'no-repeat');
1646
+ }
1647
+
1648
+ return g;
1649
+ }
1650
+ }
1651
+ svg.Element.GradientBase.prototype = new svg.Element.ElementBase;
1652
+
1653
+ // linear gradient element
1654
+ svg.Element.linearGradient = function(node) {
1655
+ this.base = svg.Element.GradientBase;
1656
+ this.base(node);
1657
+
1658
+ this.getGradient = function(ctx, element) {
1659
+ var bb = element.getBoundingBox();
1660
+
1661
+ if (!this.attribute('x1').hasValue()
1662
+ && !this.attribute('y1').hasValue()
1663
+ && !this.attribute('x2').hasValue()
1664
+ && !this.attribute('y2').hasValue()) {
1665
+ this.attribute('x1', true).value = 0;
1666
+ this.attribute('y1', true).value = 0;
1667
+ this.attribute('x2', true).value = 1;
1668
+ this.attribute('y2', true).value = 0;
1669
+ }
1670
+
1671
+ var x1 = (this.gradientUnits == 'objectBoundingBox'
1672
+ ? bb.x() + bb.width() * this.attribute('x1').numValue()
1673
+ : this.attribute('x1').toPixels('x'));
1674
+ var y1 = (this.gradientUnits == 'objectBoundingBox'
1675
+ ? bb.y() + bb.height() * this.attribute('y1').numValue()
1676
+ : this.attribute('y1').toPixels('y'));
1677
+ var x2 = (this.gradientUnits == 'objectBoundingBox'
1678
+ ? bb.x() + bb.width() * this.attribute('x2').numValue()
1679
+ : this.attribute('x2').toPixels('x'));
1680
+ var y2 = (this.gradientUnits == 'objectBoundingBox'
1681
+ ? bb.y() + bb.height() * this.attribute('y2').numValue()
1682
+ : this.attribute('y2').toPixels('y'));
1683
+
1684
+ if (x1 == x2 && y1 == y2) return null;
1685
+ return ctx.createLinearGradient(x1, y1, x2, y2);
1686
+ }
1687
+ }
1688
+ svg.Element.linearGradient.prototype = new svg.Element.GradientBase;
1689
+
1690
+ // radial gradient element
1691
+ svg.Element.radialGradient = function(node) {
1692
+ this.base = svg.Element.GradientBase;
1693
+ this.base(node);
1694
+
1695
+ this.getGradient = function(ctx, element) {
1696
+ var bb = element.getBoundingBox();
1697
+
1698
+ if (!this.attribute('cx').hasValue()) this.attribute('cx', true).value = '50%';
1699
+ if (!this.attribute('cy').hasValue()) this.attribute('cy', true).value = '50%';
1700
+ if (!this.attribute('r').hasValue()) this.attribute('r', true).value = '50%';
1701
+
1702
+ var cx = (this.gradientUnits == 'objectBoundingBox'
1703
+ ? bb.x() + bb.width() * this.attribute('cx').numValue()
1704
+ : this.attribute('cx').toPixels('x'));
1705
+ var cy = (this.gradientUnits == 'objectBoundingBox'
1706
+ ? bb.y() + bb.height() * this.attribute('cy').numValue()
1707
+ : this.attribute('cy').toPixels('y'));
1708
+
1709
+ var fx = cx;
1710
+ var fy = cy;
1711
+ if (this.attribute('fx').hasValue()) {
1712
+ fx = (this.gradientUnits == 'objectBoundingBox'
1713
+ ? bb.x() + bb.width() * this.attribute('fx').numValue()
1714
+ : this.attribute('fx').toPixels('x'));
1715
+ }
1716
+ if (this.attribute('fy').hasValue()) {
1717
+ fy = (this.gradientUnits == 'objectBoundingBox'
1718
+ ? bb.y() + bb.height() * this.attribute('fy').numValue()
1719
+ : this.attribute('fy').toPixels('y'));
1720
+ }
1721
+
1722
+ var r = (this.gradientUnits == 'objectBoundingBox'
1723
+ ? (bb.width() + bb.height()) / 2.0 * this.attribute('r').numValue()
1724
+ : this.attribute('r').toPixels());
1725
+
1726
+ return ctx.createRadialGradient(fx, fy, 0, cx, cy, r);
1727
+ }
1728
+ }
1729
+ svg.Element.radialGradient.prototype = new svg.Element.GradientBase;
1730
+
1731
+ // gradient stop element
1732
+ svg.Element.stop = function(node) {
1733
+ this.base = svg.Element.ElementBase;
1734
+ this.base(node);
1735
+
1736
+ this.offset = this.attribute('offset').numValue();
1737
+ if (this.offset < 0) this.offset = 0;
1738
+ if (this.offset > 1) this.offset = 1;
1739
+
1740
+ var stopColor = this.style('stop-color');
1741
+ if (this.style('stop-opacity').hasValue()) stopColor = stopColor.addOpacity(this.style('stop-opacity').value);
1742
+ this.color = stopColor.value;
1743
+ }
1744
+ svg.Element.stop.prototype = new svg.Element.ElementBase;
1745
+
1746
+ // animation base element
1747
+ svg.Element.AnimateBase = function(node) {
1748
+ this.base = svg.Element.ElementBase;
1749
+ this.base(node);
1750
+
1751
+ svg.Animations.push(this);
1752
+
1753
+ this.duration = 0.0;
1754
+ this.begin = this.attribute('begin').toMilliseconds();
1755
+ this.maxDuration = this.begin + this.attribute('dur').toMilliseconds();
1756
+
1757
+ this.getProperty = function() {
1758
+ var attributeType = this.attribute('attributeType').value;
1759
+ var attributeName = this.attribute('attributeName').value;
1760
+
1761
+ if (attributeType == 'CSS') {
1762
+ return this.parent.style(attributeName, true);
1763
+ }
1764
+ return this.parent.attribute(attributeName, true);
1765
+ };
1766
+
1767
+ this.initialValue = null;
1768
+ this.initialUnits = '';
1769
+ this.removed = false;
1770
+
1771
+ this.calcValue = function() {
1772
+ // OVERRIDE ME!
1773
+ return '';
1774
+ }
1775
+
1776
+ this.update = function(delta) {
1777
+ // set initial value
1778
+ if (this.initialValue == null) {
1779
+ this.initialValue = this.getProperty().value;
1780
+ this.initialUnits = this.getProperty().getUnits();
1781
+ }
1782
+
1783
+ // if we're past the end time
1784
+ if (this.duration > this.maxDuration) {
1785
+ // loop for indefinitely repeating animations
1786
+ if (this.attribute('repeatCount').value == 'indefinite'
1787
+ || this.attribute('repeatDur').value == 'indefinite') {
1788
+ this.duration = 0.0
1789
+ }
1790
+ else if (this.attribute('fill').valueOrDefault('remove') == 'remove' && !this.removed) {
1791
+ this.removed = true;
1792
+ this.getProperty().value = this.initialValue;
1793
+ return true;
1794
+ }
1795
+ else {
1796
+ return false; // no updates made
1797
+ }
1798
+ }
1799
+ this.duration = this.duration + delta;
1800
+
1801
+ // if we're past the begin time
1802
+ var updated = false;
1803
+ if (this.begin < this.duration) {
1804
+ var newValue = this.calcValue(); // tween
1805
+
1806
+ if (this.attribute('type').hasValue()) {
1807
+ // for transform, etc.
1808
+ var type = this.attribute('type').value;
1809
+ newValue = type + '(' + newValue + ')';
1810
+ }
1811
+
1812
+ this.getProperty().value = newValue;
1813
+ updated = true;
1814
+ }
1815
+
1816
+ return updated;
1817
+ }
1818
+
1819
+ this.from = this.attribute('from');
1820
+ this.to = this.attribute('to');
1821
+ this.values = this.attribute('values');
1822
+ if (this.values.hasValue()) this.values.value = this.values.value.split(';');
1823
+
1824
+ // fraction of duration we've covered
1825
+ this.progress = function() {
1826
+ var ret = { progress: (this.duration - this.begin) / (this.maxDuration - this.begin) };
1827
+ if (this.values.hasValue()) {
1828
+ var p = ret.progress * (this.values.value.length - 1);
1829
+ var lb = Math.floor(p), ub = Math.ceil(p);
1830
+ ret.from = new svg.Property('from', parseFloat(this.values.value[lb]));
1831
+ ret.to = new svg.Property('to', parseFloat(this.values.value[ub]));
1832
+ ret.progress = (p - lb) / (ub - lb);
1833
+ }
1834
+ else {
1835
+ ret.from = this.from;
1836
+ ret.to = this.to;
1837
+ }
1838
+ return ret;
1839
+ }
1840
+ }
1841
+ svg.Element.AnimateBase.prototype = new svg.Element.ElementBase;
1842
+
1843
+ // animate element
1844
+ svg.Element.animate = function(node) {
1845
+ this.base = svg.Element.AnimateBase;
1846
+ this.base(node);
1847
+
1848
+ this.calcValue = function() {
1849
+ var p = this.progress();
1850
+
1851
+ // tween value linearly
1852
+ var newValue = p.from.numValue() + (p.to.numValue() - p.from.numValue()) * p.progress;
1853
+ return newValue + this.initialUnits;
1854
+ };
1855
+ }
1856
+ svg.Element.animate.prototype = new svg.Element.AnimateBase;
1857
+
1858
+ // animate color element
1859
+ svg.Element.animateColor = function(node) {
1860
+ this.base = svg.Element.AnimateBase;
1861
+ this.base(node);
1862
+
1863
+ this.calcValue = function() {
1864
+ var p = this.progress();
1865
+ var from = new RGBColor(p.from.value);
1866
+ var to = new RGBColor(p.to.value);
1867
+
1868
+ if (from.ok && to.ok) {
1869
+ // tween color linearly
1870
+ var r = from.r + (to.r - from.r) * p.progress;
1871
+ var g = from.g + (to.g - from.g) * p.progress;
1872
+ var b = from.b + (to.b - from.b) * p.progress;
1873
+ return 'rgb('+parseInt(r,10)+','+parseInt(g,10)+','+parseInt(b,10)+')';
1874
+ }
1875
+ return this.attribute('from').value;
1876
+ };
1877
+ }
1878
+ svg.Element.animateColor.prototype = new svg.Element.AnimateBase;
1879
+
1880
+ // animate transform element
1881
+ svg.Element.animateTransform = function(node) {
1882
+ this.base = svg.Element.AnimateBase;
1883
+ this.base(node);
1884
+
1885
+ this.calcValue = function() {
1886
+ var p = this.progress();
1887
+
1888
+ // tween value linearly
1889
+ var from = svg.ToNumberArray(p.from.value);
1890
+ var to = svg.ToNumberArray(p.to.value);
1891
+ var newValue = '';
1892
+ for (var i=0; i<from.length; i++) {
1893
+ newValue += from[i] + (to[i] - from[i]) * p.progress + ' ';
1894
+ }
1895
+ return newValue;
1896
+ };
1897
+ }
1898
+ svg.Element.animateTransform.prototype = new svg.Element.animate;
1899
+
1900
+ // font element
1901
+ svg.Element.font = function(node) {
1902
+ this.base = svg.Element.ElementBase;
1903
+ this.base(node);
1904
+
1905
+ this.horizAdvX = this.attribute('horiz-adv-x').numValue();
1906
+
1907
+ this.isRTL = false;
1908
+ this.isArabic = false;
1909
+ this.fontFace = null;
1910
+ this.missingGlyph = null;
1911
+ this.glyphs = [];
1912
+ for (var i=0; i<this.children.length; i++) {
1913
+ var child = this.children[i];
1914
+ if (child.type == 'font-face') {
1915
+ this.fontFace = child;
1916
+ if (child.style('font-family').hasValue()) {
1917
+ svg.Definitions[child.style('font-family').value] = this;
1918
+ }
1919
+ }
1920
+ else if (child.type == 'missing-glyph') this.missingGlyph = child;
1921
+ else if (child.type == 'glyph') {
1922
+ if (child.arabicForm != '') {
1923
+ this.isRTL = true;
1924
+ this.isArabic = true;
1925
+ if (typeof(this.glyphs[child.unicode]) == 'undefined') this.glyphs[child.unicode] = [];
1926
+ this.glyphs[child.unicode][child.arabicForm] = child;
1927
+ }
1928
+ else {
1929
+ this.glyphs[child.unicode] = child;
1930
+ }
1931
+ }
1932
+ }
1933
+ }
1934
+ svg.Element.font.prototype = new svg.Element.ElementBase;
1935
+
1936
+ // font-face element
1937
+ svg.Element.fontface = function(node) {
1938
+ this.base = svg.Element.ElementBase;
1939
+ this.base(node);
1940
+
1941
+ this.ascent = this.attribute('ascent').value;
1942
+ this.descent = this.attribute('descent').value;
1943
+ this.unitsPerEm = this.attribute('units-per-em').numValue();
1944
+ }
1945
+ svg.Element.fontface.prototype = new svg.Element.ElementBase;
1946
+
1947
+ // missing-glyph element
1948
+ svg.Element.missingglyph = function(node) {
1949
+ this.base = svg.Element.path;
1950
+ this.base(node);
1951
+
1952
+ this.horizAdvX = 0;
1953
+ }
1954
+ svg.Element.missingglyph.prototype = new svg.Element.path;
1955
+
1956
+ // glyph element
1957
+ svg.Element.glyph = function(node) {
1958
+ this.base = svg.Element.path;
1959
+ this.base(node);
1960
+
1961
+ this.horizAdvX = this.attribute('horiz-adv-x').numValue();
1962
+ this.unicode = this.attribute('unicode').value;
1963
+ this.arabicForm = this.attribute('arabic-form').value;
1964
+ }
1965
+ svg.Element.glyph.prototype = new svg.Element.path;
1966
+
1967
+ // text element
1968
+ svg.Element.text = function(node) {
1969
+ this.captureTextNodes = true;
1970
+ this.base = svg.Element.RenderedElementBase;
1971
+ this.base(node);
1972
+
1973
+ this.baseSetContext = this.setContext;
1974
+ this.setContext = function(ctx) {
1975
+ this.baseSetContext(ctx);
1976
+ if (this.style('dominant-baseline').hasValue()) ctx.textBaseline = this.style('dominant-baseline').value;
1977
+ if (this.style('alignment-baseline').hasValue()) ctx.textBaseline = this.style('alignment-baseline').value;
1978
+ }
1979
+
1980
+ this.getBoundingBox = function () {
1981
+ // TODO: implement
1982
+ return new svg.BoundingBox(this.attribute('x').toPixels('x'), this.attribute('y').toPixels('y'), 0, 0);
1983
+ }
1984
+
1985
+ this.renderChildren = function(ctx) {
1986
+ this.x = this.attribute('x').toPixels('x');
1987
+ this.y = this.attribute('y').toPixels('y');
1988
+ this.x += this.getAnchorDelta(ctx, this, 0);
1989
+ for (var i=0; i<this.children.length; i++) {
1990
+ this.renderChild(ctx, this, i);
1991
+ }
1992
+ }
1993
+
1994
+ this.getAnchorDelta = function (ctx, parent, startI) {
1995
+ var textAnchor = this.style('text-anchor').valueOrDefault('start');
1996
+ if (textAnchor != 'start') {
1997
+ var width = 0;
1998
+ for (var i=startI; i<parent.children.length; i++) {
1999
+ var child = parent.children[i];
2000
+ if (i > startI && child.attribute('x').hasValue()) break; // new group
2001
+ width += child.measureTextRecursive(ctx);
2002
+ }
2003
+ return -1 * (textAnchor == 'end' ? width : width / 2.0);
2004
+ }
2005
+ return 0;
2006
+ }
2007
+
2008
+ this.renderChild = function(ctx, parent, i) {
2009
+ var child = parent.children[i];
2010
+ if (child.attribute('x').hasValue()) {
2011
+ child.x = child.attribute('x').toPixels('x') + this.getAnchorDelta(ctx, parent, i);
2012
+ }
2013
+ else {
2014
+ if (this.attribute('dx').hasValue()) this.x += this.attribute('dx').toPixels('x');
2015
+ if (child.attribute('dx').hasValue()) this.x += child.attribute('dx').toPixels('x');
2016
+ child.x = this.x;
2017
+ }
2018
+ this.x = child.x + child.measureText(ctx);
2019
+
2020
+ if (child.attribute('y').hasValue()) {
2021
+ child.y = child.attribute('y').toPixels('y');
2022
+ }
2023
+ else {
2024
+ if (this.attribute('dy').hasValue()) this.y += this.attribute('dy').toPixels('y');
2025
+ if (child.attribute('dy').hasValue()) this.y += child.attribute('dy').toPixels('y');
2026
+ child.y = this.y;
2027
+ }
2028
+ this.y = child.y;
2029
+
2030
+ child.render(ctx);
2031
+
2032
+ for (var i=0; i<child.children.length; i++) {
2033
+ this.renderChild(ctx, child, i);
2034
+ }
2035
+ }
2036
+ }
2037
+ svg.Element.text.prototype = new svg.Element.RenderedElementBase;
2038
+
2039
+ // text base
2040
+ svg.Element.TextElementBase = function(node) {
2041
+ this.base = svg.Element.RenderedElementBase;
2042
+ this.base(node);
2043
+
2044
+ this.getGlyph = function(font, text, i) {
2045
+ var c = text[i];
2046
+ var glyph = null;
2047
+ if (font.isArabic) {
2048
+ var arabicForm = 'isolated';
2049
+ if ((i==0 || text[i-1]==' ') && i<text.length-2 && text[i+1]!=' ') arabicForm = 'terminal';
2050
+ if (i>0 && text[i-1]!=' ' && i<text.length-2 && text[i+1]!=' ') arabicForm = 'medial';
2051
+ if (i>0 && text[i-1]!=' ' && (i == text.length-1 || text[i+1]==' ')) arabicForm = 'initial';
2052
+ if (typeof(font.glyphs[c]) != 'undefined') {
2053
+ glyph = font.glyphs[c][arabicForm];
2054
+ if (glyph == null && font.glyphs[c].type == 'glyph') glyph = font.glyphs[c];
2055
+ }
2056
+ }
2057
+ else {
2058
+ glyph = font.glyphs[c];
2059
+ }
2060
+ if (glyph == null) glyph = font.missingGlyph;
2061
+ return glyph;
2062
+ }
2063
+
2064
+ this.renderChildren = function(ctx) {
2065
+ var customFont = this.parent.style('font-family').getDefinition();
2066
+ if (customFont != null) {
2067
+ var fontSize = this.parent.style('font-size').numValueOrDefault(svg.Font.Parse(svg.ctx.font).fontSize);
2068
+ var fontStyle = this.parent.style('font-style').valueOrDefault(svg.Font.Parse(svg.ctx.font).fontStyle);
2069
+ var text = this.getText();
2070
+ if (customFont.isRTL) text = text.split("").reverse().join("");
2071
+
2072
+ var dx = svg.ToNumberArray(this.parent.attribute('dx').value);
2073
+ for (var i=0; i<text.length; i++) {
2074
+ var glyph = this.getGlyph(customFont, text, i);
2075
+ var scale = fontSize / customFont.fontFace.unitsPerEm;
2076
+ ctx.translate(this.x, this.y);
2077
+ ctx.scale(scale, -scale);
2078
+ var lw = ctx.lineWidth;
2079
+ ctx.lineWidth = ctx.lineWidth * customFont.fontFace.unitsPerEm / fontSize;
2080
+ if (fontStyle == 'italic') ctx.transform(1, 0, .4, 1, 0, 0);
2081
+ glyph.render(ctx);
2082
+ if (fontStyle == 'italic') ctx.transform(1, 0, -.4, 1, 0, 0);
2083
+ ctx.lineWidth = lw;
2084
+ ctx.scale(1/scale, -1/scale);
2085
+ ctx.translate(-this.x, -this.y);
2086
+
2087
+ this.x += fontSize * (glyph.horizAdvX || customFont.horizAdvX) / customFont.fontFace.unitsPerEm;
2088
+ if (typeof(dx[i]) != 'undefined' && !isNaN(dx[i])) {
2089
+ this.x += dx[i];
2090
+ }
2091
+ }
2092
+ return;
2093
+ }
2094
+
2095
+ if (ctx.fillStyle != '') ctx.fillText(svg.compressSpaces(this.getText()), this.x, this.y);
2096
+ if (ctx.strokeStyle != '') ctx.strokeText(svg.compressSpaces(this.getText()), this.x, this.y);
2097
+ }
2098
+
2099
+ this.getText = function() {
2100
+ // OVERRIDE ME
2101
+ }
2102
+
2103
+ this.measureTextRecursive = function(ctx) {
2104
+ var width = this.measureText(ctx);
2105
+ for (var i=0; i<this.children.length; i++) {
2106
+ width += this.children[i].measureTextRecursive(ctx);
2107
+ }
2108
+ return width;
2109
+ }
2110
+
2111
+ this.measureText = function(ctx) {
2112
+ var customFont = this.parent.style('font-family').getDefinition();
2113
+ if (customFont != null) {
2114
+ var fontSize = this.parent.style('font-size').numValueOrDefault(svg.Font.Parse(svg.ctx.font).fontSize);
2115
+ var measure = 0;
2116
+ var text = this.getText();
2117
+ if (customFont.isRTL) text = text.split("").reverse().join("");
2118
+ var dx = svg.ToNumberArray(this.parent.attribute('dx').value);
2119
+ for (var i=0; i<text.length; i++) {
2120
+ var glyph = this.getGlyph(customFont, text, i);
2121
+ measure += (glyph.horizAdvX || customFont.horizAdvX) * fontSize / customFont.fontFace.unitsPerEm;
2122
+ if (typeof(dx[i]) != 'undefined' && !isNaN(dx[i])) {
2123
+ measure += dx[i];
2124
+ }
2125
+ }
2126
+ return measure;
2127
+ }
2128
+
2129
+ var textToMeasure = svg.compressSpaces(this.getText());
2130
+ if (!ctx.measureText) return textToMeasure.length * 10;
2131
+
2132
+ ctx.save();
2133
+ this.setContext(ctx);
2134
+ var width = ctx.measureText(textToMeasure).width;
2135
+ ctx.restore();
2136
+ return width;
2137
+ }
2138
+ }
2139
+ svg.Element.TextElementBase.prototype = new svg.Element.RenderedElementBase;
2140
+
2141
+ // tspan
2142
+ svg.Element.tspan = function(node) {
2143
+ this.captureTextNodes = true;
2144
+ this.base = svg.Element.TextElementBase;
2145
+ this.base(node);
2146
+
2147
+ this.text = node.nodeValue || node.text || '';
2148
+ this.getText = function() {
2149
+ return this.text;
2150
+ }
2151
+ }
2152
+ svg.Element.tspan.prototype = new svg.Element.TextElementBase;
2153
+
2154
+ // tref
2155
+ svg.Element.tref = function(node) {
2156
+ this.base = svg.Element.TextElementBase;
2157
+ this.base(node);
2158
+
2159
+ this.getText = function() {
2160
+ var element = this.getHrefAttribute().getDefinition();
2161
+ if (element != null) return element.children[0].getText();
2162
+ }
2163
+ }
2164
+ svg.Element.tref.prototype = new svg.Element.TextElementBase;
2165
+
2166
+ // a element
2167
+ svg.Element.a = function(node) {
2168
+ this.base = svg.Element.TextElementBase;
2169
+ this.base(node);
2170
+
2171
+ this.hasText = true;
2172
+ for (var i=0; i<node.childNodes.length; i++) {
2173
+ if (node.childNodes[i].nodeType != 3) this.hasText = false;
2174
+ }
2175
+
2176
+ // this might contain text
2177
+ this.text = this.hasText ? node.childNodes[0].nodeValue : '';
2178
+ this.getText = function() {
2179
+ return this.text;
2180
+ }
2181
+
2182
+ this.baseRenderChildren = this.renderChildren;
2183
+ this.renderChildren = function(ctx) {
2184
+ if (this.hasText) {
2185
+ // render as text element
2186
+ this.baseRenderChildren(ctx);
2187
+ var fontSize = new svg.Property('fontSize', svg.Font.Parse(svg.ctx.font).fontSize);
2188
+ svg.Mouse.checkBoundingBox(this, new svg.BoundingBox(this.x, this.y - fontSize.toPixels('y'), this.x + this.measureText(ctx), this.y));
2189
+ }
2190
+ else {
2191
+ // render as temporary group
2192
+ var g = new svg.Element.g();
2193
+ g.children = this.children;
2194
+ g.parent = this;
2195
+ g.render(ctx);
2196
+ }
2197
+ }
2198
+
2199
+ this.onclick = function() {
2200
+ window.open(this.getHrefAttribute().value);
2201
+ }
2202
+
2203
+ this.onmousemove = function() {
2204
+ svg.ctx.canvas.style.cursor = 'pointer';
2205
+ }
2206
+ }
2207
+ svg.Element.a.prototype = new svg.Element.TextElementBase;
2208
+
2209
+ // image element
2210
+ svg.Element.image = function(node) {
2211
+ this.base = svg.Element.RenderedElementBase;
2212
+ this.base(node);
2213
+
2214
+ var href = this.getHrefAttribute().value;
2215
+ var isSvg = href.match(/\.svg$/)
2216
+
2217
+ svg.Images.push(this);
2218
+ this.loaded = false;
2219
+ if (!isSvg) {
2220
+ this.img = document.createElement('img');
2221
+ var self = this;
2222
+ this.img.onload = function() { self.loaded = true; }
2223
+ this.img.onerror = function() { if (typeof(console) != 'undefined') { console.log('ERROR: image "' + href + '" not found'); self.loaded = true; } }
2224
+ this.img.src = href;
2225
+ }
2226
+ else {
2227
+ this.img = svg.ajax(href);
2228
+ this.loaded = true;
2229
+ }
2230
+
2231
+ this.renderChildren = function(ctx) {
2232
+ var x = this.attribute('x').toPixels('x');
2233
+ var y = this.attribute('y').toPixels('y');
2234
+
2235
+ var width = this.attribute('width').toPixels('x');
2236
+ var height = this.attribute('height').toPixels('y');
2237
+ if (width == 0 || height == 0) return;
2238
+
2239
+ ctx.save();
2240
+ if (isSvg) {
2241
+ ctx.drawSvg(this.img, x, y, width, height);
2242
+ }
2243
+ else {
2244
+ ctx.translate(x, y);
2245
+ svg.AspectRatio(ctx,
2246
+ this.attribute('preserveAspectRatio').value,
2247
+ width,
2248
+ this.img.width,
2249
+ height,
2250
+ this.img.height,
2251
+ 0,
2252
+ 0);
2253
+ ctx.drawImage(this.img, 0, 0);
2254
+ }
2255
+ ctx.restore();
2256
+ }
2257
+
2258
+ this.getBoundingBox = function() {
2259
+ var x = this.attribute('x').toPixels('x');
2260
+ var y = this.attribute('y').toPixels('y');
2261
+ var width = this.attribute('width').toPixels('x');
2262
+ var height = this.attribute('height').toPixels('y');
2263
+ return new svg.BoundingBox(x, y, x + width, y + height);
2264
+ }
2265
+ }
2266
+ svg.Element.image.prototype = new svg.Element.RenderedElementBase;
2267
+
2268
+ // group element
2269
+ svg.Element.g = function(node) {
2270
+ this.base = svg.Element.RenderedElementBase;
2271
+ this.base(node);
2272
+
2273
+ this.getBoundingBox = function() {
2274
+ var bb = new svg.BoundingBox();
2275
+ for (var i=0; i<this.children.length; i++) {
2276
+ bb.addBoundingBox(this.children[i].getBoundingBox());
2277
+ }
2278
+ return bb;
2279
+ };
2280
+ }
2281
+ svg.Element.g.prototype = new svg.Element.RenderedElementBase;
2282
+
2283
+ // symbol element
2284
+ svg.Element.symbol = function(node) {
2285
+ this.base = svg.Element.RenderedElementBase;
2286
+ this.base(node);
2287
+
2288
+ this.baseSetContext = this.setContext;
2289
+ this.setContext = function(ctx) {
2290
+ this.baseSetContext(ctx);
2291
+
2292
+ // viewbox
2293
+ if (this.attribute('viewBox').hasValue()) {
2294
+ var viewBox = svg.ToNumberArray(this.attribute('viewBox').value);
2295
+ var minX = viewBox[0];
2296
+ var minY = viewBox[1];
2297
+ width = viewBox[2];
2298
+ height = viewBox[3];
2299
+
2300
+ svg.AspectRatio(ctx,
2301
+ this.attribute('preserveAspectRatio').value,
2302
+ this.attribute('width').toPixels('x'),
2303
+ width,
2304
+ this.attribute('height').toPixels('y'),
2305
+ height,
2306
+ minX,
2307
+ minY);
2308
+
2309
+ svg.ViewPort.SetCurrent(viewBox[2], viewBox[3]);
2310
+ }
2311
+ }
2312
+ }
2313
+ svg.Element.symbol.prototype = new svg.Element.RenderedElementBase;
2314
+
2315
+ // style element
2316
+ svg.Element.style = function(node) {
2317
+ this.base = svg.Element.ElementBase;
2318
+ this.base(node);
2319
+
2320
+ // text, or spaces then CDATA
2321
+ var css = ''
2322
+ for (var i=0; i<node.childNodes.length; i++) {
2323
+ css += node.childNodes[i].nodeValue;
2324
+ }
2325
+ css = css.replace(/(\/\*([^*]|[\r\n]|(\*+([^*\/]|[\r\n])))*\*+\/)|(^[\s]*\/\/.*)/gm, ''); // remove comments
2326
+ css = svg.compressSpaces(css); // replace whitespace
2327
+ var cssDefs = css.split('}');
2328
+ for (var i=0; i<cssDefs.length; i++) {
2329
+ if (svg.trim(cssDefs[i]) != '') {
2330
+ var cssDef = cssDefs[i].split('{');
2331
+ var cssClasses = cssDef[0].split(',');
2332
+ var cssProps = cssDef[1].split(';');
2333
+ for (var j=0; j<cssClasses.length; j++) {
2334
+ var cssClass = svg.trim(cssClasses[j]);
2335
+ if (cssClass != '') {
2336
+ var props = {};
2337
+ for (var k=0; k<cssProps.length; k++) {
2338
+ var prop = cssProps[k].indexOf(':');
2339
+ var name = cssProps[k].substr(0, prop);
2340
+ var value = cssProps[k].substr(prop + 1, cssProps[k].length - prop);
2341
+ if (name != null && value != null) {
2342
+ props[svg.trim(name)] = new svg.Property(svg.trim(name), svg.trim(value));
2343
+ }
2344
+ }
2345
+ svg.Styles[cssClass] = props;
2346
+ if (cssClass == '@font-face') {
2347
+ var fontFamily = props['font-family'].value.replace(/"/g,'');
2348
+ var srcs = props['src'].value.split(',');
2349
+ for (var s=0; s<srcs.length; s++) {
2350
+ if (srcs[s].indexOf('format("svg")') > 0) {
2351
+ var urlStart = srcs[s].indexOf('url');
2352
+ var urlEnd = srcs[s].indexOf(')', urlStart);
2353
+ var url = srcs[s].substr(urlStart + 5, urlEnd - urlStart - 6);
2354
+ var doc = svg.parseXml(svg.ajax(url));
2355
+ var fonts = doc.getElementsByTagName('font');
2356
+ for (var f=0; f<fonts.length; f++) {
2357
+ var font = svg.CreateElement(fonts[f]);
2358
+ svg.Definitions[fontFamily] = font;
2359
+ }
2360
+ }
2361
+ }
2362
+ }
2363
+ }
2364
+ }
2365
+ }
2366
+ }
2367
+ }
2368
+ svg.Element.style.prototype = new svg.Element.ElementBase;
2369
+
2370
+ // use element
2371
+ svg.Element.use = function(node) {
2372
+ this.base = svg.Element.RenderedElementBase;
2373
+ this.base(node);
2374
+
2375
+ this.baseSetContext = this.setContext;
2376
+ this.setContext = function(ctx) {
2377
+ this.baseSetContext(ctx);
2378
+ if (this.attribute('x').hasValue()) ctx.translate(this.attribute('x').toPixels('x'), 0);
2379
+ if (this.attribute('y').hasValue()) ctx.translate(0, this.attribute('y').toPixels('y'));
2380
+ }
2381
+
2382
+ this.getDefinition = function() {
2383
+ var element = this.getHrefAttribute().getDefinition();
2384
+ if (this.attribute('width').hasValue()) element.attribute('width', true).value = this.attribute('width').value;
2385
+ if (this.attribute('height').hasValue()) element.attribute('height', true).value = this.attribute('height').value;
2386
+ return element;
2387
+ }
2388
+
2389
+ this.path = function(ctx) {
2390
+ var element = this.getDefinition();
2391
+ if (element != null) element.path(ctx);
2392
+ }
2393
+
2394
+ this.getBoundingBox = function() {
2395
+ var element = this.getDefinition();
2396
+ if (element != null) return element.getBoundingBox();
2397
+ }
2398
+
2399
+ this.renderChildren = function(ctx) {
2400
+ var element = this.getDefinition();
2401
+ if (element != null) {
2402
+ // temporarily detach from parent and render
2403
+ var oldParent = element.parent;
2404
+ element.parent = null;
2405
+ element.render(ctx);
2406
+ element.parent = oldParent;
2407
+ }
2408
+ }
2409
+ }
2410
+ svg.Element.use.prototype = new svg.Element.RenderedElementBase;
2411
+
2412
+ // mask element
2413
+ svg.Element.mask = function(node) {
2414
+ this.base = svg.Element.ElementBase;
2415
+ this.base(node);
2416
+
2417
+ this.apply = function(ctx, element) {
2418
+ // render as temp svg
2419
+ var x = this.attribute('x').toPixels('x');
2420
+ var y = this.attribute('y').toPixels('y');
2421
+ var width = this.attribute('width').toPixels('x');
2422
+ var height = this.attribute('height').toPixels('y');
2423
+
2424
+ if (width == 0 && height == 0) {
2425
+ var bb = new svg.BoundingBox();
2426
+ for (var i=0; i<this.children.length; i++) {
2427
+ bb.addBoundingBox(this.children[i].getBoundingBox());
2428
+ }
2429
+ var x = Math.floor(bb.x1);
2430
+ var y = Math.floor(bb.y1);
2431
+ var width = Math.floor(bb.width());
2432
+ var height = Math.floor(bb.height());
2433
+ }
2434
+
2435
+ // temporarily remove mask to avoid recursion
2436
+ var mask = element.attribute('mask').value;
2437
+ element.attribute('mask').value = '';
2438
+
2439
+ var cMask = document.createElement('canvas');
2440
+ cMask.width = x + width;
2441
+ cMask.height = y + height;
2442
+ var maskCtx = cMask.getContext('2d');
2443
+ this.renderChildren(maskCtx);
2444
+
2445
+ var c = document.createElement('canvas');
2446
+ c.width = x + width;
2447
+ c.height = y + height;
2448
+ var tempCtx = c.getContext('2d');
2449
+ element.render(tempCtx);
2450
+ tempCtx.globalCompositeOperation = 'destination-in';
2451
+ tempCtx.fillStyle = maskCtx.createPattern(cMask, 'no-repeat');
2452
+ tempCtx.fillRect(0, 0, x + width, y + height);
2453
+
2454
+ ctx.fillStyle = tempCtx.createPattern(c, 'no-repeat');
2455
+ ctx.fillRect(0, 0, x + width, y + height);
2456
+
2457
+ // reassign mask
2458
+ element.attribute('mask').value = mask;
2459
+ }
2460
+
2461
+ this.render = function(ctx) {
2462
+ // NO RENDER
2463
+ }
2464
+ }
2465
+ svg.Element.mask.prototype = new svg.Element.ElementBase;
2466
+
2467
+ // clip element
2468
+ svg.Element.clipPath = function(node) {
2469
+ this.base = svg.Element.ElementBase;
2470
+ this.base(node);
2471
+
2472
+ this.apply = function(ctx) {
2473
+ for (var i=0; i<this.children.length; i++) {
2474
+ var child = this.children[i];
2475
+ if (typeof(child.path) != 'undefined') {
2476
+ var transform = null;
2477
+ if (child.attribute('transform').hasValue()) {
2478
+ transform = new svg.Transform(child.attribute('transform').value);
2479
+ transform.apply(ctx);
2480
+ }
2481
+ child.path(ctx);
2482
+ ctx.clip();
2483
+ if (transform) { transform.unapply(ctx); }
2484
+ }
2485
+ }
2486
+ }
2487
+
2488
+ this.render = function(ctx) {
2489
+ // NO RENDER
2490
+ }
2491
+ }
2492
+ svg.Element.clipPath.prototype = new svg.Element.ElementBase;
2493
+
2494
+ // filters
2495
+ svg.Element.filter = function(node) {
2496
+ this.base = svg.Element.ElementBase;
2497
+ this.base(node);
2498
+
2499
+ this.apply = function(ctx, element) {
2500
+ // render as temp svg
2501
+ var bb = element.getBoundingBox();
2502
+ var x = Math.floor(bb.x1);
2503
+ var y = Math.floor(bb.y1);
2504
+ var width = Math.floor(bb.width());
2505
+ var height = Math.floor(bb.height());
2506
+
2507
+ // temporarily remove filter to avoid recursion
2508
+ var filter = element.style('filter').value;
2509
+ element.style('filter').value = '';
2510
+
2511
+ var px = 0, py = 0;
2512
+ for (var i=0; i<this.children.length; i++) {
2513
+ var efd = this.children[i].extraFilterDistance || 0;
2514
+ px = Math.max(px, efd);
2515
+ py = Math.max(py, efd);
2516
+ }
2517
+
2518
+ var c = document.createElement('canvas');
2519
+ c.width = width + 2*px;
2520
+ c.height = height + 2*py;
2521
+ var tempCtx = c.getContext('2d');
2522
+ tempCtx.translate(-x + px, -y + py);
2523
+ element.render(tempCtx);
2524
+
2525
+ // apply filters
2526
+ for (var i=0; i<this.children.length; i++) {
2527
+ this.children[i].apply(tempCtx, 0, 0, width + 2*px, height + 2*py);
2528
+ }
2529
+
2530
+ // render on me
2531
+ ctx.drawImage(c, 0, 0, width + 2*px, height + 2*py, x - px, y - py, width + 2*px, height + 2*py);
2532
+
2533
+ // reassign filter
2534
+ element.style('filter', true).value = filter;
2535
+ }
2536
+
2537
+ this.render = function(ctx) {
2538
+ // NO RENDER
2539
+ }
2540
+ }
2541
+ svg.Element.filter.prototype = new svg.Element.ElementBase;
2542
+
2543
+ svg.Element.feMorphology = function(node) {
2544
+ this.base = svg.Element.ElementBase;
2545
+ this.base(node);
2546
+
2547
+ this.apply = function(ctx, x, y, width, height) {
2548
+ // TODO: implement
2549
+ }
2550
+ }
2551
+ svg.Element.feMorphology.prototype = new svg.Element.ElementBase;
2552
+
2553
+ svg.Element.feColorMatrix = function(node) {
2554
+ this.base = svg.Element.ElementBase;
2555
+ this.base(node);
2556
+
2557
+ function imGet(img, x, y, width, height, rgba) {
2558
+ return img[y*width*4 + x*4 + rgba];
2559
+ }
2560
+
2561
+ function imSet(img, x, y, width, height, rgba, val) {
2562
+ img[y*width*4 + x*4 + rgba] = val;
2563
+ }
2564
+
2565
+ this.apply = function(ctx, x, y, width, height) {
2566
+ // only supporting grayscale for now per Issue 195, need to extend to all matrix
2567
+ // assuming x==0 && y==0 for now
2568
+ var srcData = ctx.getImageData(0, 0, width, height);
2569
+ for (var y = 0; y < height; y++) {
2570
+ for (var x = 0; x < width; x++) {
2571
+ var r = imGet(srcData.data, x, y, width, height, 0);
2572
+ var g = imGet(srcData.data, x, y, width, height, 1);
2573
+ var b = imGet(srcData.data, x, y, width, height, 2);
2574
+ var gray = (r + g + b) / 3;
2575
+ imSet(srcData.data, x, y, width, height, 0, gray);
2576
+ imSet(srcData.data, x, y, width, height, 1, gray);
2577
+ imSet(srcData.data, x, y, width, height, 2, gray);
2578
+ }
2579
+ }
2580
+ ctx.clearRect(0, 0, width, height);
2581
+ ctx.putImageData(srcData, 0, 0);
2582
+ }
2583
+ }
2584
+ svg.Element.feColorMatrix.prototype = new svg.Element.ElementBase;
2585
+
2586
+ svg.Element.feGaussianBlur = function(node) {
2587
+ this.base = svg.Element.ElementBase;
2588
+ this.base(node);
2589
+
2590
+ this.blurRadius = Math.floor(this.attribute('stdDeviation').numValue());
2591
+ this.extraFilterDistance = this.blurRadius;
2592
+
2593
+ this.apply = function(ctx, x, y, width, height) {
2594
+ if (typeof(stackBlurCanvasRGBA) == 'undefined') {
2595
+ if (typeof(console) != 'undefined') { console.log('ERROR: StackBlur.js must be included for blur to work'); }
2596
+ return;
2597
+ }
2598
+
2599
+ // StackBlur requires canvas be on document
2600
+ ctx.canvas.id = svg.UniqueId();
2601
+ ctx.canvas.style.display = 'none';
2602
+ document.body.appendChild(ctx.canvas);
2603
+ stackBlurCanvasRGBA(ctx.canvas.id, x, y, width, height, this.blurRadius);
2604
+ document.body.removeChild(ctx.canvas);
2605
+ }
2606
+ }
2607
+ svg.Element.feGaussianBlur.prototype = new svg.Element.ElementBase;
2608
+
2609
+ // title element, do nothing
2610
+ svg.Element.title = function(node) {
2611
+ }
2612
+ svg.Element.title.prototype = new svg.Element.ElementBase;
2613
+
2614
+ // desc element, do nothing
2615
+ svg.Element.desc = function(node) {
2616
+ }
2617
+ svg.Element.desc.prototype = new svg.Element.ElementBase;
2618
+
2619
+ svg.Element.MISSING = function(node) {
2620
+ if (typeof(console) != 'undefined') { console.log('ERROR: Element \'' + node.nodeName + '\' not yet implemented.'); }
2621
+ }
2622
+ svg.Element.MISSING.prototype = new svg.Element.ElementBase;
2623
+
2624
+ // element factory
2625
+ svg.CreateElement = function(node) {
2626
+ var className = node.nodeName.replace(/^[^:]+:/,''); // remove namespace
2627
+ className = className.replace(/\-/g,''); // remove dashes
2628
+ var e = null;
2629
+ if (typeof(svg.Element[className]) != 'undefined') {
2630
+ e = new svg.Element[className](node);
2631
+ }
2632
+ else {
2633
+ e = new svg.Element.MISSING(node);
2634
+ }
2635
+
2636
+ e.type = node.nodeName;
2637
+ return e;
2638
+ }
2639
+
2640
+ // load from url
2641
+ svg.load = function(ctx, url) {
2642
+ svg.loadXml(ctx, svg.ajax(url));
2643
+ }
2644
+
2645
+ // load from xml
2646
+ svg.loadXml = function(ctx, xml) {
2647
+ svg.loadXmlDoc(ctx, svg.parseXml(xml));
2648
+ }
2649
+
2650
+ svg.loadXmlDoc = function(ctx, dom) {
2651
+ svg.init(ctx);
2652
+
2653
+ var mapXY = function(p) {
2654
+ var e = ctx.canvas;
2655
+ while (e) {
2656
+ p.x -= e.offsetLeft;
2657
+ p.y -= e.offsetTop;
2658
+ e = e.offsetParent;
2659
+ }
2660
+ if (window.scrollX) p.x += window.scrollX;
2661
+ if (window.scrollY) p.y += window.scrollY;
2662
+ return p;
2663
+ }
2664
+
2665
+ // bind mouse
2666
+ if (svg.opts['ignoreMouse'] != true) {
2667
+ ctx.canvas.onclick = function(e) {
2668
+ var p = mapXY(new svg.Point(e != null ? e.clientX : event.clientX, e != null ? e.clientY : event.clientY));
2669
+ svg.Mouse.onclick(p.x, p.y);
2670
+ };
2671
+ ctx.canvas.onmousemove = function(e) {
2672
+ var p = mapXY(new svg.Point(e != null ? e.clientX : event.clientX, e != null ? e.clientY : event.clientY));
2673
+ svg.Mouse.onmousemove(p.x, p.y);
2674
+ };
2675
+ }
2676
+
2677
+ var e = svg.CreateElement(dom.documentElement);
2678
+ e.root = true;
2679
+
2680
+ // render loop
2681
+ var isFirstRender = true;
2682
+ var draw = function() {
2683
+ svg.ViewPort.Clear();
2684
+ if (ctx.canvas.parentNode) svg.ViewPort.SetCurrent(ctx.canvas.parentNode.clientWidth, ctx.canvas.parentNode.clientHeight);
2685
+
2686
+ if (svg.opts['ignoreDimensions'] != true) {
2687
+ // set canvas size
2688
+ if (e.style('width').hasValue()) {
2689
+ ctx.canvas.width = e.style('width').toPixels('x');
2690
+ ctx.canvas.style.width = ctx.canvas.width + 'px';
2691
+ }
2692
+ if (e.style('height').hasValue()) {
2693
+ ctx.canvas.height = e.style('height').toPixels('y');
2694
+ ctx.canvas.style.height = ctx.canvas.height + 'px';
2695
+ }
2696
+ }
2697
+ var cWidth = ctx.canvas.clientWidth || ctx.canvas.width;
2698
+ var cHeight = ctx.canvas.clientHeight || ctx.canvas.height;
2699
+ if (svg.opts['ignoreDimensions'] == true && e.style('width').hasValue() && e.style('height').hasValue()) {
2700
+ cWidth = e.style('width').toPixels('x');
2701
+ cHeight = e.style('height').toPixels('y');
2702
+ }
2703
+ svg.ViewPort.SetCurrent(cWidth, cHeight);
2704
+
2705
+ if (svg.opts['offsetX'] != null) e.attribute('x', true).value = svg.opts['offsetX'];
2706
+ if (svg.opts['offsetY'] != null) e.attribute('y', true).value = svg.opts['offsetY'];
2707
+ if (svg.opts['scaleWidth'] != null && svg.opts['scaleHeight'] != null) {
2708
+ var xRatio = 1, yRatio = 1, viewBox = svg.ToNumberArray(e.attribute('viewBox').value);
2709
+ if (e.attribute('width').hasValue()) xRatio = e.attribute('width').toPixels('x') / svg.opts['scaleWidth'];
2710
+ else if (!isNaN(viewBox[2])) xRatio = viewBox[2] / svg.opts['scaleWidth'];
2711
+ if (e.attribute('height').hasValue()) yRatio = e.attribute('height').toPixels('y') / svg.opts['scaleHeight'];
2712
+ else if (!isNaN(viewBox[3])) yRatio = viewBox[3] / svg.opts['scaleHeight'];
2713
+
2714
+ e.attribute('width', true).value = svg.opts['scaleWidth'];
2715
+ e.attribute('height', true).value = svg.opts['scaleHeight'];
2716
+ e.attribute('viewBox', true).value = '0 0 ' + (cWidth * xRatio) + ' ' + (cHeight * yRatio);
2717
+ e.attribute('preserveAspectRatio', true).value = 'none';
2718
+ }
2719
+
2720
+ // clear and render
2721
+ if (svg.opts['ignoreClear'] != true) {
2722
+ ctx.clearRect(0, 0, cWidth, cHeight);
2723
+ }
2724
+ e.render(ctx);
2725
+ if (isFirstRender) {
2726
+ isFirstRender = false;
2727
+ if (typeof(svg.opts['renderCallback']) == 'function') svg.opts['renderCallback'](dom);
2728
+ }
2729
+ }
2730
+
2731
+ var waitingForImages = true;
2732
+ if (svg.ImagesLoaded()) {
2733
+ waitingForImages = false;
2734
+ draw();
2735
+ }
2736
+ svg.intervalID = setInterval(function() {
2737
+ var needUpdate = false;
2738
+
2739
+ if (waitingForImages && svg.ImagesLoaded()) {
2740
+ waitingForImages = false;
2741
+ needUpdate = true;
2742
+ }
2743
+
2744
+ // need update from mouse events?
2745
+ if (svg.opts['ignoreMouse'] != true) {
2746
+ needUpdate = needUpdate | svg.Mouse.hasEvents();
2747
+ }
2748
+
2749
+ // need update from animations?
2750
+ if (svg.opts['ignoreAnimation'] != true) {
2751
+ for (var i=0; i<svg.Animations.length; i++) {
2752
+ needUpdate = needUpdate | svg.Animations[i].update(1000 / svg.FRAMERATE);
2753
+ }
2754
+ }
2755
+
2756
+ // need update from redraw?
2757
+ if (typeof(svg.opts['forceRedraw']) == 'function') {
2758
+ if (svg.opts['forceRedraw']() == true) needUpdate = true;
2759
+ }
2760
+
2761
+ // render if needed
2762
+ if (needUpdate) {
2763
+ draw();
2764
+ svg.Mouse.runEvents(); // run and clear our events
2765
+ }
2766
+ }, 1000 / svg.FRAMERATE);
2767
+ }
2768
+
2769
+ svg.stop = function() {
2770
+ if (svg.intervalID) {
2771
+ clearInterval(svg.intervalID);
2772
+ }
2773
+ }
2774
+
2775
+ svg.Mouse = new (function() {
2776
+ this.events = [];
2777
+ this.hasEvents = function() { return this.events.length != 0; }
2778
+
2779
+ this.onclick = function(x, y) {
2780
+ this.events.push({ type: 'onclick', x: x, y: y,
2781
+ run: function(e) { if (e.onclick) e.onclick(); }
2782
+ });
2783
+ }
2784
+
2785
+ this.onmousemove = function(x, y) {
2786
+ this.events.push({ type: 'onmousemove', x: x, y: y,
2787
+ run: function(e) { if (e.onmousemove) e.onmousemove(); }
2788
+ });
2789
+ }
2790
+
2791
+ this.eventElements = [];
2792
+
2793
+ this.checkPath = function(element, ctx) {
2794
+ for (var i=0; i<this.events.length; i++) {
2795
+ var e = this.events[i];
2796
+ if (ctx.isPointInPath && ctx.isPointInPath(e.x, e.y)) this.eventElements[i] = element;
2797
+ }
2798
+ }
2799
+
2800
+ this.checkBoundingBox = function(element, bb) {
2801
+ for (var i=0; i<this.events.length; i++) {
2802
+ var e = this.events[i];
2803
+ if (bb.isPointInBox(e.x, e.y)) this.eventElements[i] = element;
2804
+ }
2805
+ }
2806
+
2807
+ this.runEvents = function() {
2808
+ svg.ctx.canvas.style.cursor = '';
2809
+
2810
+ for (var i=0; i<this.events.length; i++) {
2811
+ var e = this.events[i];
2812
+ var element = this.eventElements[i];
2813
+ while (element) {
2814
+ e.run(element);
2815
+ element = element.parent;
2816
+ }
2817
+ }
2818
+
2819
+ // done running, clear
2820
+ this.events = [];
2821
+ this.eventElements = [];
2822
+ }
2823
+ });
2824
+
2825
+ return svg;
2826
+ }
2827
+ })();
2828
+
2829
+ if (typeof(CanvasRenderingContext2D) != 'undefined') {
2830
+ CanvasRenderingContext2D.prototype.drawSvg = function(s, dx, dy, dw, dh) {
2831
+ canvg(this.canvas, s, {
2832
+ ignoreMouse: true,
2833
+ ignoreAnimation: true,
2834
+ ignoreDimensions: true,
2835
+ ignoreClear: true,
2836
+ offsetX: dx,
2837
+ offsetY: dy,
2838
+ scaleWidth: dw,
2839
+ scaleHeight: dh
2840
+ });
2841
+ }
2842
+ }