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