2
* @license A class to parse color values
3
* @author Stoyan Stefanov <sstoo@gmail.com>
4
* @link http://www.phpied.com/rgb-color-parser-in-javascript/
5
* Use it if you like it
8
function RGBColor(color_string)
12
// strip any leading #
13
if (color_string.charAt(0) == '#') { // remove # if any
14
color_string = color_string.substr(1,6);
17
color_string = color_string.replace(/ /g,'');
18
color_string = color_string.toLowerCase();
20
// before getting into regexps, try simple matches
21
// and overwrite the input
24
antiquewhite: 'faebd7',
31
blanchedalmond: 'ffebcd',
40
cornflowerblue: '6495ed',
46
darkgoldenrod: 'b8860b',
50
darkmagenta: '8b008b',
51
darkolivegreen: '556b2f',
56
darkseagreen: '8fbc8f',
57
darkslateblue: '483d8b',
58
darkslategray: '2f4f4f',
59
darkturquoise: '00ced1',
62
deepskyblue: '00bfff',
67
floralwhite: 'fffaf0',
68
forestgreen: '228b22',
76
greenyellow: 'adff2f',
84
lavenderblush: 'fff0f5',
86
lemonchiffon: 'fffacd',
90
lightgoldenrodyellow: 'fafad2',
94
lightsalmon: 'ffa07a',
95
lightseagreen: '20b2aa',
96
lightskyblue: '87cefa',
97
lightslateblue: '8470ff',
98
lightslategray: '778899',
99
lightsteelblue: 'b0c4de',
100
lightyellow: 'ffffe0',
106
mediumaquamarine: '66cdaa',
107
mediumblue: '0000cd',
108
mediumorchid: 'ba55d3',
109
mediumpurple: '9370d8',
110
mediumseagreen: '3cb371',
111
mediumslateblue: '7b68ee',
112
mediumspringgreen: '00fa9a',
113
mediumturquoise: '48d1cc',
114
mediumvioletred: 'c71585',
115
midnightblue: '191970',
119
navajowhite: 'ffdead',
127
palegoldenrod: 'eee8aa',
129
paleturquoise: 'afeeee',
130
palevioletred: 'd87093',
131
papayawhip: 'ffefd5',
136
powderblue: 'b0e0e6',
141
saddlebrown: '8b4513',
143
sandybrown: 'f4a460',
152
springgreen: '00ff7f',
163
whitesmoke: 'f5f5f5',
165
yellowgreen: '9acd32'
167
for (var key in simple_colors) {
168
if (color_string == key) {
169
color_string = simple_colors[key];
172
// emd of simple type-in colors
174
// array of color definition objects
177
re: /^rgb\((\d{1,3}),\s*(\d{1,3}),\s*(\d{1,3})\)$/,
178
example: ['rgb(123, 234, 45)', 'rgb(255,234,245)'],
179
process: function (bits){
188
re: /^(\w{2})(\w{2})(\w{2})$/,
189
example: ['#00ff00', '336699'],
190
process: function (bits){
192
parseInt(bits[1], 16),
193
parseInt(bits[2], 16),
194
parseInt(bits[3], 16)
199
re: /^(\w{1})(\w{1})(\w{1})$/,
200
example: ['#fb0', 'f0f'],
201
process: function (bits){
203
parseInt(bits[1] + bits[1], 16),
204
parseInt(bits[2] + bits[2], 16),
205
parseInt(bits[3] + bits[3], 16)
211
// search through the definitions to find a match
212
for (var i = 0; i < color_defs.length; i++) {
213
var re = color_defs[i].re;
214
var processor = color_defs[i].process;
215
var bits = re.exec(color_string);
217
channels = processor(bits);
218
this.r = channels[0];
219
this.g = channels[1];
220
this.b = channels[2];
226
// validate/cleanup values
227
this.r = (this.r < 0 || isNaN(this.r)) ? 0 : ((this.r > 255) ? 255 : this.r);
228
this.g = (this.g < 0 || isNaN(this.g)) ? 0 : ((this.g > 255) ? 255 : this.g);
229
this.b = (this.b < 0 || isNaN(this.b)) ? 0 : ((this.b > 255) ? 255 : this.b);
232
this.toRGB = function () {
233
return 'rgb(' + this.r + ', ' + this.g + ', ' + this.b + ')';
235
this.toHex = function () {
236
var r = this.r.toString(16);
237
var g = this.g.toString(16);
238
var b = this.b.toString(16);
239
if (r.length == 1) r = '0' + r;
240
if (g.length == 1) g = '0' + g;
241
if (b.length == 1) b = '0' + b;
242
return '#' + r + g + b;
246
this.getHelpXML = function () {
248
var examples = new Array();
250
for (var i = 0; i < color_defs.length; i++) {
251
var example = color_defs[i].example;
252
for (var j = 0; j < example.length; j++) {
253
examples[examples.length] = example[j];
256
// add type-in colors
257
for (var sc in simple_colors) {
258
examples[examples.length] = sc;
261
var xml = document.createElement('ul');
262
xml.setAttribute('id', 'rgbcolor-examples');
263
for (var i = 0; i < examples.length; i++) {
265
var list_item = document.createElement('li');
266
var list_color = new RGBColor(examples[i]);
267
var example_div = document.createElement('div');
268
example_div.style.cssText =
270
+ 'border: 1px solid black; '
271
+ 'background:' + list_color.toHex() + '; '
272
+ 'color:' + list_color.toHex()
274
example_div.appendChild(document.createTextNode('test'));
275
var list_item_value = document.createTextNode(
276
' ' + examples[i] + ' -> ' + list_color.toRGB() + ' -> ' + list_color.toHex()
278
list_item.appendChild(example_div);
279
list_item.appendChild(list_item_value);
280
xml.appendChild(list_item);
291
* @license canvg.js - Javascript SVG parser and renderer on Canvas
293
* Gabe Lerner (gabelerner@gmail.com)
294
* http://code.google.com/p/canvg/
296
* Requires: rgbcolor.js - http://www.phpied.com/rgb-color-parser-in-javascript/
299
if(!window.console) {
301
window.console.log = function(str) {};
302
window.console.dir = function(str) {};
305
if(!Array.prototype.indexOf){
306
Array.prototype.indexOf = function(obj){
307
for(var i=0; i<this.length; i++){
318
// empty parameters: replace all 'svg' elements on page with 'canvas' elements
319
// target: canvas element or the id of a canvas element
320
// s: svg string, url to svg file, or xml document
321
// opts: optional hash of options
322
// ignoreMouse: true => ignore mouse events
323
// ignoreAnimation: true => ignore animations
324
// ignoreDimensions: true => does not try to resize canvas
325
// ignoreClear: true => does not clear canvas
326
// offsetX: int => draws at a x offset
327
// offsetY: int => draws at a y offset
328
// scaleWidth: int => scales horizontally to width
329
// scaleHeight: int => scales vertically to height
330
// renderCallback: function => will call the function after the first render is completed
331
// forceRedraw: function => will call the function on every frame, if it returns true, will redraw
332
this.canvg = function (target, s, opts) {
334
if (target == null && s == null && opts == null) {
335
var svgTags = document.getElementsByTagName('svg');
336
for (var i=0; i<svgTags.length; i++) {
337
var svgTag = svgTags[i];
338
var c = document.createElement('canvas');
339
c.width = svgTag.clientWidth;
340
c.height = svgTag.clientHeight;
341
svgTag.parentNode.insertBefore(c, svgTag);
342
svgTag.parentNode.removeChild(svgTag);
343
var div = document.createElement('div');
344
div.appendChild(svgTag);
345
canvg(c, div.innerHTML);
351
if (typeof target == 'string') {
352
target = document.getElementById(target);
355
// reuse class per canvas
357
if (target.svg == null) {
367
var ctx = target.getContext('2d');
368
if (typeof(s.documentElement) != 'undefined') {
370
svg.loadXmlDoc(ctx, s);
372
else if (s.substr(0,1) == '<') {
373
// load from xml string
386
svg.MAX_VIRTUAL_PIXELS = 30000;
389
svg.init = function(ctx) {
390
svg.Definitions = {};
395
svg.ViewPort = new (function () {
397
this.Clear = function() { this.viewPorts = []; }
398
this.SetCurrent = function(width, height) { this.viewPorts.push({ width: width, height: height }); }
399
this.RemoveCurrent = function() { this.viewPorts.pop(); }
400
this.Current = function() { return this.viewPorts[this.viewPorts.length - 1]; }
401
this.width = function() { return this.Current().width; }
402
this.height = function() { return this.Current().height; }
403
this.ComputeSize = function(d) {
404
if (d != null && typeof(d) == 'number') return d;
405
if (d == 'x') return this.width();
406
if (d == 'y') return this.height();
407
return Math.sqrt(Math.pow(this.width(), 2) + Math.pow(this.height(), 2)) / Math.sqrt(2);
414
svg.ImagesLoaded = function() {
415
for (var i=0; i<svg.Images.length; i++) {
416
if (!svg.Images[i].loaded) return false;
422
svg.trim = function(s) { return s.replace(/^\s+|\s+$/g, ''); }
425
svg.compressSpaces = function(s) { return s.replace(/[\s\r\t\n]+/gm,' '); }
428
svg.ajax = function(url) {
430
if(window.XMLHttpRequest){AJAX=new XMLHttpRequest();}
431
else{AJAX=new ActiveXObject('Microsoft.XMLHTTP');}
433
AJAX.open('GET',url,false);
435
return AJAX.responseText;
441
svg.parseXml = function(xml) {
442
if (window.DOMParser)
444
var parser = new DOMParser();
445
return parser.parseFromString(xml, 'text/xml');
449
xml = xml.replace(/<!DOCTYPE svg[^>]*>/, '');
450
var xmlDoc = new ActiveXObject('Microsoft.XMLDOM');
451
xmlDoc.async = 'false';
457
svg.Property = function(name, value) {
461
this.hasValue = function() {
462
return (this.value != null && this.value !== '');
465
// return the numerical value of the property
466
this.numValue = function() {
467
if (!this.hasValue()) return 0;
469
var n = parseFloat(this.value);
470
if ((this.value + '').match(/%$/)) {
476
this.valueOrDefault = function(def) {
477
if (this.hasValue()) return this.value;
481
this.numValueOrDefault = function(def) {
482
if (this.hasValue()) return this.numValue();
491
// augment the current color value with the opacity
492
addOpacity: function(opacity) {
493
var newValue = that.value;
494
if (opacity != null && opacity != '') {
495
var color = new RGBColor(that.value);
497
newValue = 'rgba(' + color.r + ', ' + color.g + ', ' + color.b + ', ' + opacity + ')';
500
return new svg.Property(that.name, newValue);
504
// definition extensions
506
// get the definition from the definitions table
507
getDefinition: function() {
508
var name = that.value.replace(/^(url\()?#([^\)]+)\)?$/, '$2');
509
return svg.Definitions[name];
513
return that.value.indexOf('url(') == 0
516
getFillStyle: function(e) {
517
var def = this.getDefinition();
520
if (def != null && def.createGradient) {
521
return def.createGradient(svg.ctx, e);
525
if (def != null && def.createPattern) {
526
return def.createPattern(svg.ctx, e);
535
DPI: function(viewPort) {
536
return 96.0; // TODO: compute?
539
EM: function(viewPort) {
542
var fontSize = new svg.Property('fontSize', svg.Font.Parse(svg.ctx.font).fontSize);
543
if (fontSize.hasValue()) em = fontSize.Length.toPixels(viewPort);
548
// get the length as pixels
549
toPixels: function(viewPort) {
550
if (!that.hasValue()) return 0;
551
var s = that.value+'';
552
if (s.match(/em$/)) return that.numValue() * this.EM(viewPort);
553
if (s.match(/ex$/)) return that.numValue() * this.EM(viewPort) / 2.0;
554
if (s.match(/px$/)) return that.numValue();
555
if (s.match(/pt$/)) return that.numValue() * 1.25;
556
if (s.match(/pc$/)) return that.numValue() * 15;
557
if (s.match(/cm$/)) return that.numValue() * this.DPI(viewPort) / 2.54;
558
if (s.match(/mm$/)) return that.numValue() * this.DPI(viewPort) / 25.4;
559
if (s.match(/in$/)) return that.numValue() * this.DPI(viewPort);
560
if (s.match(/%$/)) return that.numValue() * svg.ViewPort.ComputeSize(viewPort);
561
return that.numValue();
567
// get the time as milliseconds
568
toMilliseconds: function() {
569
if (!that.hasValue()) return 0;
570
var s = that.value+'';
571
if (s.match(/s$/)) return that.numValue() * 1000;
572
if (s.match(/ms$/)) return that.numValue();
573
return that.numValue();
579
// get the angle as radians
580
toRadians: function() {
581
if (!that.hasValue()) return 0;
582
var s = that.value+'';
583
if (s.match(/deg$/)) return that.numValue() * (Math.PI / 180.0);
584
if (s.match(/grad$/)) return that.numValue() * (Math.PI / 200.0);
585
if (s.match(/rad$/)) return that.numValue();
586
return that.numValue() * (Math.PI / 180.0);
592
svg.Font = new (function() {
593
this.Styles = ['normal','italic','oblique','inherit'];
594
this.Variants = ['normal','small-caps','inherit'];
595
this.Weights = ['normal','bold','bolder','lighter','100','200','300','400','500','600','700','800','900','inherit'];
597
this.CreateFont = function(fontStyle, fontVariant, fontWeight, fontSize, fontFamily, inherit) {
598
var f = inherit != null ? this.Parse(inherit) : this.CreateFont('', '', '', '', '', svg.ctx.font);
600
fontFamily: fontFamily || f.fontFamily,
601
fontSize: fontSize || f.fontSize,
602
fontStyle: fontStyle || f.fontStyle,
603
fontWeight: fontWeight || f.fontWeight,
604
fontVariant: fontVariant || f.fontVariant,
605
toString: function () { return [this.fontStyle, this.fontVariant, this.fontWeight, this.fontSize, this.fontFamily].join(' ') }
610
this.Parse = function(s) {
612
var d = svg.trim(svg.compressSpaces(s || '')).split(' ');
613
var set = { fontSize: false, fontStyle: false, fontWeight: false, fontVariant: false }
615
for (var i=0; i<d.length; i++) {
616
if (!set.fontStyle && that.Styles.indexOf(d[i]) != -1) { if (d[i] != 'inherit') f.fontStyle = d[i]; set.fontStyle = true; }
617
else if (!set.fontVariant && that.Variants.indexOf(d[i]) != -1) { if (d[i] != 'inherit') f.fontVariant = d[i]; set.fontStyle = set.fontVariant = true; }
618
else if (!set.fontWeight && that.Weights.indexOf(d[i]) != -1) { if (d[i] != 'inherit') f.fontWeight = d[i]; set.fontStyle = set.fontVariant = set.fontWeight = true; }
619
else if (!set.fontSize) { if (d[i] != 'inherit') f.fontSize = d[i].split('/')[0]; set.fontStyle = set.fontVariant = set.fontWeight = set.fontSize = true; }
620
else { if (d[i] != 'inherit') ff += d[i]; }
621
} if (ff != '') f.fontFamily = ff;
627
svg.ToNumberArray = function(s) {
628
var a = svg.trim(svg.compressSpaces((s || '').replace(/,/g, ' '))).split(' ');
629
for (var i=0; i<a.length; i++) {
630
a[i] = parseFloat(a[i]);
634
svg.Point = function(x, y) {
638
this.angleTo = function(p) {
639
return Math.atan2(p.y - this.y, p.x - this.x);
642
this.applyTransform = function(v) {
643
var xp = this.x * v[0] + this.y * v[2] + v[4];
644
var yp = this.x * v[1] + this.y * v[3] + v[5];
649
svg.CreatePoint = function(s) {
650
var a = svg.ToNumberArray(s);
651
return new svg.Point(a[0], a[1]);
653
svg.CreatePath = function(s) {
654
var a = svg.ToNumberArray(s);
656
for (var i=0; i<a.length; i+=2) {
657
path.push(new svg.Point(a[i], a[i+1]));
663
svg.BoundingBox = function(x1, y1, x2, y2) { // pass in initial points if you want
664
this.x1 = Number.NaN;
665
this.y1 = Number.NaN;
666
this.x2 = Number.NaN;
667
this.y2 = Number.NaN;
669
this.x = function() { return this.x1; }
670
this.y = function() { return this.y1; }
671
this.width = function() { return this.x2 - this.x1; }
672
this.height = function() { return this.y2 - this.y1; }
674
this.addPoint = function(x, y) {
676
if (isNaN(this.x1) || isNaN(this.x2)) {
680
if (x < this.x1) this.x1 = x;
681
if (x > this.x2) this.x2 = x;
685
if (isNaN(this.y1) || isNaN(this.y2)) {
689
if (y < this.y1) this.y1 = y;
690
if (y > this.y2) this.y2 = y;
693
this.addX = function(x) { this.addPoint(x, null); }
694
this.addY = function(y) { this.addPoint(null, y); }
696
this.addBoundingBox = function(bb) {
697
this.addPoint(bb.x1, bb.y1);
698
this.addPoint(bb.x2, bb.y2);
701
this.addQuadraticCurve = function(p0x, p0y, p1x, p1y, p2x, p2y) {
702
var cp1x = p0x + 2/3 * (p1x - p0x); // CP1 = QP0 + 2/3 *(QP1-QP0)
703
var cp1y = p0y + 2/3 * (p1y - p0y); // CP1 = QP0 + 2/3 *(QP1-QP0)
704
var cp2x = cp1x + 1/3 * (p2x - p0x); // CP2 = CP1 + 1/3 *(QP2-QP0)
705
var cp2y = cp1y + 1/3 * (p2y - p0y); // CP2 = CP1 + 1/3 *(QP2-QP0)
706
this.addBezierCurve(p0x, p0y, cp1x, cp2x, cp1y, cp2y, p2x, p2y);
709
this.addBezierCurve = function(p0x, p0y, p1x, p1y, p2x, p2y, p3x, p3y) {
710
// from http://blog.hackers-cafe.net/2009/06/how-to-calculate-bezier-curves-bounding.html
711
var p0 = [p0x, p0y], p1 = [p1x, p1y], p2 = [p2x, p2y], p3 = [p3x, p3y];
712
this.addPoint(p0[0], p0[1]);
713
this.addPoint(p3[0], p3[1]);
715
for (i=0; i<=1; i++) {
716
var f = function(t) {
717
return Math.pow(1-t, 3) * p0[i]
718
+ 3 * Math.pow(1-t, 2) * t * p1[i]
719
+ 3 * (1-t) * Math.pow(t, 2) * p2[i]
720
+ Math.pow(t, 3) * p3[i];
723
var b = 6 * p0[i] - 12 * p1[i] + 6 * p2[i];
724
var a = -3 * p0[i] + 9 * p1[i] - 9 * p2[i] + 3 * p3[i];
725
var c = 3 * p1[i] - 3 * p0[i];
728
if (b == 0) continue;
730
if (0 < t && t < 1) {
731
if (i == 0) this.addX(f(t));
732
if (i == 1) this.addY(f(t));
737
var b2ac = Math.pow(b, 2) - 4 * c * a;
738
if (b2ac < 0) continue;
739
var t1 = (-b + Math.sqrt(b2ac)) / (2 * a);
740
if (0 < t1 && t1 < 1) {
741
if (i == 0) this.addX(f(t1));
742
if (i == 1) this.addY(f(t1));
744
var t2 = (-b - Math.sqrt(b2ac)) / (2 * a);
745
if (0 < t2 && t2 < 1) {
746
if (i == 0) this.addX(f(t2));
747
if (i == 1) this.addY(f(t2));
752
this.isPointInBox = function(x, y) {
753
return (this.x1 <= x && x <= this.x2 && this.y1 <= y && y <= this.y2);
756
this.addPoint(x1, y1);
757
this.addPoint(x2, y2);
761
svg.Transform = function(v) {
766
this.Type.translate = function(s) {
767
this.p = svg.CreatePoint(s);
768
this.apply = function(ctx) {
769
ctx.translate(this.p.x || 0.0, this.p.y || 0.0);
771
this.applyToPoint = function(p) {
772
p.applyTransform([1, 0, 0, 1, this.p.x || 0.0, this.p.y || 0.0]);
777
this.Type.rotate = function(s) {
778
var a = svg.ToNumberArray(s);
779
this.angle = new svg.Property('angle', a[0]);
782
this.apply = function(ctx) {
783
ctx.translate(this.cx, this.cy);
784
ctx.rotate(this.angle.Angle.toRadians());
785
ctx.translate(-this.cx, -this.cy);
787
this.applyToPoint = function(p) {
788
var a = this.angle.Angle.toRadians();
789
p.applyTransform([1, 0, 0, 1, this.p.x || 0.0, this.p.y || 0.0]);
790
p.applyTransform([Math.cos(a), Math.sin(a), -Math.sin(a), Math.cos(a), 0, 0]);
791
p.applyTransform([1, 0, 0, 1, -this.p.x || 0.0, -this.p.y || 0.0]);
795
this.Type.scale = function(s) {
796
this.p = svg.CreatePoint(s);
797
this.apply = function(ctx) {
798
ctx.scale(this.p.x || 1.0, this.p.y || this.p.x || 1.0);
800
this.applyToPoint = function(p) {
801
p.applyTransform([this.p.x || 0.0, 0, 0, this.p.y || 0.0, 0, 0]);
805
this.Type.matrix = function(s) {
806
this.m = svg.ToNumberArray(s);
807
this.apply = function(ctx) {
808
ctx.transform(this.m[0], this.m[1], this.m[2], this.m[3], this.m[4], this.m[5]);
810
this.applyToPoint = function(p) {
811
p.applyTransform(this.m);
815
this.Type.SkewBase = function(s) {
816
this.base = that.Type.matrix;
818
this.angle = new svg.Property('angle', s);
820
this.Type.SkewBase.prototype = new this.Type.matrix;
822
this.Type.skewX = function(s) {
823
this.base = that.Type.SkewBase;
825
this.m = [1, 0, Math.tan(this.angle.Angle.toRadians()), 1, 0, 0];
827
this.Type.skewX.prototype = new this.Type.SkewBase;
829
this.Type.skewY = function(s) {
830
this.base = that.Type.SkewBase;
832
this.m = [1, Math.tan(this.angle.Angle.toRadians()), 0, 1, 0, 0];
834
this.Type.skewY.prototype = new this.Type.SkewBase;
836
this.transforms = [];
838
this.apply = function(ctx) {
839
for (var i=0; i<this.transforms.length; i++) {
840
this.transforms[i].apply(ctx);
844
this.applyToPoint = function(p) {
845
for (var i=0; i<this.transforms.length; i++) {
846
this.transforms[i].applyToPoint(p);
850
var data = svg.trim(svg.compressSpaces(v)).split(/\s(?=[a-z])/);
851
for (var i=0; i<data.length; i++) {
852
var type = data[i].split('(')[0];
853
var s = data[i].split('(')[1].replace(')','');
854
var transform = new this.Type[type](s);
855
this.transforms.push(transform);
860
svg.AspectRatio = function(ctx, aspectRatio, width, desiredWidth, height, desiredHeight, minX, minY, refX, refY) {
861
// aspect ratio - http://www.w3.org/TR/SVG/coords.html#PreserveAspectRatioAttribute
862
aspectRatio = svg.compressSpaces(aspectRatio);
863
aspectRatio = aspectRatio.replace(/^defer\s/,''); // ignore defer
864
var align = aspectRatio.split(' ')[0] || 'xMidYMid';
865
var meetOrSlice = aspectRatio.split(' ')[1] || 'meet';
868
var scaleX = width / desiredWidth;
869
var scaleY = height / desiredHeight;
870
var scaleMin = Math.min(scaleX, scaleY);
871
var scaleMax = Math.max(scaleX, scaleY);
872
if (meetOrSlice == 'meet') { desiredWidth *= scaleMin; desiredHeight *= scaleMin; }
873
if (meetOrSlice == 'slice') { desiredWidth *= scaleMax; desiredHeight *= scaleMax; }
875
refX = new svg.Property('refX', refX);
876
refY = new svg.Property('refY', refY);
877
if (refX.hasValue() && refY.hasValue()) {
878
ctx.translate(-scaleMin * refX.Length.toPixels('x'), -scaleMin * refY.Length.toPixels('y'));
882
if (align.match(/^xMid/) && ((meetOrSlice == 'meet' && scaleMin == scaleY) || (meetOrSlice == 'slice' && scaleMax == scaleY))) ctx.translate(width / 2.0 - desiredWidth / 2.0, 0);
883
if (align.match(/YMid$/) && ((meetOrSlice == 'meet' && scaleMin == scaleX) || (meetOrSlice == 'slice' && scaleMax == scaleX))) ctx.translate(0, height / 2.0 - desiredHeight / 2.0);
884
if (align.match(/^xMax/) && ((meetOrSlice == 'meet' && scaleMin == scaleY) || (meetOrSlice == 'slice' && scaleMax == scaleY))) ctx.translate(width - desiredWidth, 0);
885
if (align.match(/YMax$/) && ((meetOrSlice == 'meet' && scaleMin == scaleX) || (meetOrSlice == 'slice' && scaleMax == scaleX))) ctx.translate(0, height - desiredHeight);
889
if (align == 'none') ctx.scale(scaleX, scaleY);
890
else if (meetOrSlice == 'meet') ctx.scale(scaleMin, scaleMin);
891
else if (meetOrSlice == 'slice') ctx.scale(scaleMax, scaleMax);
894
ctx.translate(minX == null ? 0 : -minX, minY == null ? 0 : -minY);
900
svg.Element.ElementBase = function(node) {
901
this.attributes = {};
905
// get or create attribute
906
this.attribute = function(name, createIfNotExists) {
907
var a = this.attributes[name];
908
if (a != null) return a;
910
a = new svg.Property(name, '');
911
if (createIfNotExists == true) this.attributes[name] = a;
915
// get or create style, crawls up node tree
916
this.style = function(name, createIfNotExists) {
917
var s = this.styles[name];
918
if (s != null) return s;
920
var a = this.attribute(name);
921
if (a != null && a.hasValue()) {
927
var ps = p.style(name);
928
if (ps != null && ps.hasValue()) {
933
s = new svg.Property(name, '');
934
if (createIfNotExists == true) this.styles[name] = s;
939
this.render = function(ctx) {
940
// don't render display=none
941
if (this.style('display').value == 'none') return;
943
// don't render visibility=hidden
944
if (this.attribute('visibility').value == 'hidden') return;
947
this.setContext(ctx);
949
if (this.attribute('mask').hasValue()) {
950
var mask = this.attribute('mask').Definition.getDefinition();
951
if (mask != null) mask.apply(ctx, this);
953
else if (this.style('filter').hasValue()) {
954
var filter = this.style('filter').Definition.getDefinition();
955
if (filter != null) filter.apply(ctx, this);
957
else this.renderChildren(ctx);
958
this.clearContext(ctx);
963
this.setContext = function(ctx) {
967
// base clear context
968
this.clearContext = function(ctx) {
972
// base render children
973
this.renderChildren = function(ctx) {
974
for (var i=0; i<this.children.length; i++) {
975
this.children[i].render(ctx);
979
this.addChild = function(childNode, create) {
980
var child = childNode;
981
if (create) child = svg.CreateElement(childNode);
983
this.children.push(child);
986
if (node != null && node.nodeType == 1) { //ELEMENT_NODE
988
for (var i=0; i<node.childNodes.length; i++) {
989
var childNode = node.childNodes[i];
990
if (childNode.nodeType == 1) this.addChild(childNode, true); //ELEMENT_NODE
994
for (var i=0; i<node.attributes.length; i++) {
995
var attribute = node.attributes[i];
996
this.attributes[attribute.nodeName] = new svg.Property(attribute.nodeName, attribute.nodeValue);
1000
var styles = svg.Styles[node.nodeName];
1001
if (styles != null) {
1002
for (var name in styles) {
1003
this.styles[name] = styles[name];
1008
if (this.attribute('class').hasValue()) {
1009
var classes = svg.compressSpaces(this.attribute('class').value).split(' ');
1010
for (var j=0; j<classes.length; j++) {
1011
styles = svg.Styles['.'+classes[j]];
1012
if (styles != null) {
1013
for (var name in styles) {
1014
this.styles[name] = styles[name];
1017
styles = svg.Styles[node.nodeName+'.'+classes[j]];
1018
if (styles != null) {
1019
for (var name in styles) {
1020
this.styles[name] = styles[name];
1026
// add inline styles
1027
if (this.attribute('style').hasValue()) {
1028
var styles = this.attribute('style').value.split(';');
1029
for (var i=0; i<styles.length; i++) {
1030
if (svg.trim(styles[i]) != '') {
1031
var style = styles[i].split(':');
1032
var name = svg.trim(style[0]);
1033
var value = svg.trim(style[1]);
1034
this.styles[name] = new svg.Property(name, value);
1040
if (this.attribute('id').hasValue()) {
1041
if (svg.Definitions[this.attribute('id').value] == null) {
1042
svg.Definitions[this.attribute('id').value] = this;
1048
svg.Element.RenderedElementBase = function(node) {
1049
this.base = svg.Element.ElementBase;
1052
this.setContext = function(ctx) {
1054
if (this.style('fill').Definition.isUrl()) {
1055
var fs = this.style('fill').Definition.getFillStyle(this);
1056
if (fs != null) ctx.fillStyle = fs;
1058
else if (this.style('fill').hasValue()) {
1059
var fillStyle = this.style('fill');
1060
if (this.style('fill-opacity').hasValue()) fillStyle = fillStyle.Color.addOpacity(this.style('fill-opacity').value);
1061
ctx.fillStyle = (fillStyle.value == 'none' ? 'rgba(0,0,0,0)' : fillStyle.value);
1065
if (this.style('stroke').Definition.isUrl()) {
1066
var fs = this.style('stroke').Definition.getFillStyle(this);
1067
if (fs != null) ctx.strokeStyle = fs;
1069
else if (this.style('stroke').hasValue()) {
1070
var strokeStyle = this.style('stroke');
1071
if (this.style('stroke-opacity').hasValue()) strokeStyle = strokeStyle.Color.addOpacity(this.style('stroke-opacity').value);
1072
ctx.strokeStyle = (strokeStyle.value == 'none' ? 'rgba(0,0,0,0)' : strokeStyle.value);
1074
if (this.style('stroke-width').hasValue()) ctx.lineWidth = this.style('stroke-width').Length.toPixels();
1075
if (this.style('stroke-linecap').hasValue()) ctx.lineCap = this.style('stroke-linecap').value;
1076
if (this.style('stroke-linejoin').hasValue()) ctx.lineJoin = this.style('stroke-linejoin').value;
1077
if (this.style('stroke-miterlimit').hasValue()) ctx.miterLimit = this.style('stroke-miterlimit').value;
1080
if (typeof(ctx.font) != 'undefined') {
1081
ctx.font = svg.Font.CreateFont(
1082
this.style('font-style').value,
1083
this.style('font-variant').value,
1084
this.style('font-weight').value,
1085
this.style('font-size').hasValue() ? this.style('font-size').Length.toPixels() + 'px' : '',
1086
this.style('font-family').value).toString();
1090
if (this.attribute('transform').hasValue()) {
1091
var transform = new svg.Transform(this.attribute('transform').value);
1092
transform.apply(ctx);
1096
if (this.attribute('clip-path').hasValue()) {
1097
var clip = this.attribute('clip-path').Definition.getDefinition();
1098
if (clip != null) clip.apply(ctx);
1102
if (this.style('opacity').hasValue()) {
1103
ctx.globalAlpha = this.style('opacity').numValue();
1107
svg.Element.RenderedElementBase.prototype = new svg.Element.ElementBase;
1109
svg.Element.PathElementBase = function(node) {
1110
this.base = svg.Element.RenderedElementBase;
1113
this.path = function(ctx) {
1114
if (ctx != null) ctx.beginPath();
1115
return new svg.BoundingBox();
1118
this.renderChildren = function(ctx) {
1120
svg.Mouse.checkPath(this, ctx);
1121
if (ctx.fillStyle != '') ctx.fill();
1122
if (ctx.strokeStyle != '') ctx.stroke();
1124
var markers = this.getMarkers();
1125
if (markers != null) {
1126
if (this.style('marker-start').Definition.isUrl()) {
1127
var marker = this.style('marker-start').Definition.getDefinition();
1128
marker.render(ctx, markers[0][0], markers[0][1]);
1130
if (this.style('marker-mid').Definition.isUrl()) {
1131
var marker = this.style('marker-mid').Definition.getDefinition();
1132
for (var i=1;i<markers.length-1;i++) {
1133
marker.render(ctx, markers[i][0], markers[i][1]);
1136
if (this.style('marker-end').Definition.isUrl()) {
1137
var marker = this.style('marker-end').Definition.getDefinition();
1138
marker.render(ctx, markers[markers.length-1][0], markers[markers.length-1][1]);
1143
this.getBoundingBox = function() {
1147
this.getMarkers = function() {
1151
svg.Element.PathElementBase.prototype = new svg.Element.RenderedElementBase;
1154
svg.Element.svg = function(node) {
1155
this.base = svg.Element.RenderedElementBase;
1158
this.baseClearContext = this.clearContext;
1159
this.clearContext = function(ctx) {
1160
this.baseClearContext(ctx);
1161
svg.ViewPort.RemoveCurrent();
1164
this.baseSetContext = this.setContext;
1165
this.setContext = function(ctx) {
1167
ctx.strokeStyle = 'rgba(0,0,0,0)';
1168
ctx.lineCap = 'butt';
1169
ctx.lineJoin = 'miter';
1172
this.baseSetContext(ctx);
1174
// create new view port
1175
if (this.attribute('x').hasValue() && this.attribute('y').hasValue()) {
1176
ctx.translate(this.attribute('x').Length.toPixels('x'), this.attribute('y').Length.toPixels('y'));
1179
var width = svg.ViewPort.width();
1180
var height = svg.ViewPort.height();
1181
if (typeof(this.root) == 'undefined' && this.attribute('width').hasValue() && this.attribute('height').hasValue()) {
1182
width = this.attribute('width').Length.toPixels('x');
1183
height = this.attribute('height').Length.toPixels('y');
1187
if (this.attribute('refX').hasValue() && this.attribute('refY').hasValue()) {
1188
x = -this.attribute('refX').Length.toPixels('x');
1189
y = -this.attribute('refY').Length.toPixels('y');
1194
ctx.lineTo(width, y);
1195
ctx.lineTo(width, height);
1196
ctx.lineTo(x, height);
1200
svg.ViewPort.SetCurrent(width, height);
1203
if (this.attribute('viewBox').hasValue()) {
1204
var viewBox = svg.ToNumberArray(this.attribute('viewBox').value);
1205
var minX = viewBox[0];
1206
var minY = viewBox[1];
1208
height = viewBox[3];
1210
svg.AspectRatio(ctx,
1211
this.attribute('preserveAspectRatio').value,
1212
svg.ViewPort.width(),
1214
svg.ViewPort.height(),
1218
this.attribute('refX').value,
1219
this.attribute('refY').value);
1221
svg.ViewPort.RemoveCurrent();
1222
svg.ViewPort.SetCurrent(viewBox[2], viewBox[3]);
1226
svg.Element.svg.prototype = new svg.Element.RenderedElementBase;
1229
svg.Element.rect = function(node) {
1230
this.base = svg.Element.PathElementBase;
1233
this.path = function(ctx) {
1234
var x = this.attribute('x').Length.toPixels('x');
1235
var y = this.attribute('y').Length.toPixels('y');
1236
var width = this.attribute('width').Length.toPixels('x');
1237
var height = this.attribute('height').Length.toPixels('y');
1238
var rx = this.attribute('rx').Length.toPixels('x');
1239
var ry = this.attribute('ry').Length.toPixels('y');
1240
if (this.attribute('rx').hasValue() && !this.attribute('ry').hasValue()) ry = rx;
1241
if (this.attribute('ry').hasValue() && !this.attribute('rx').hasValue()) rx = ry;
1245
ctx.moveTo(x + rx, y);
1246
ctx.lineTo(x + width - rx, y);
1247
ctx.quadraticCurveTo(x + width, y, x + width, y + ry)
1248
ctx.lineTo(x + width, y + height - ry);
1249
ctx.quadraticCurveTo(x + width, y + height, x + width - rx, y + height)
1250
ctx.lineTo(x + rx, y + height);
1251
ctx.quadraticCurveTo(x, y + height, x, y + height - ry)
1252
ctx.lineTo(x, y + ry);
1253
ctx.quadraticCurveTo(x, y, x + rx, y)
1257
return new svg.BoundingBox(x, y, x + width, y + height);
1260
svg.Element.rect.prototype = new svg.Element.PathElementBase;
1263
svg.Element.circle = function(node) {
1264
this.base = svg.Element.PathElementBase;
1267
this.path = function(ctx) {
1268
var cx = this.attribute('cx').Length.toPixels('x');
1269
var cy = this.attribute('cy').Length.toPixels('y');
1270
var r = this.attribute('r').Length.toPixels();
1274
ctx.arc(cx, cy, r, 0, Math.PI * 2, true);
1278
return new svg.BoundingBox(cx - r, cy - r, cx + r, cy + r);
1281
svg.Element.circle.prototype = new svg.Element.PathElementBase;
1284
svg.Element.ellipse = function(node) {
1285
this.base = svg.Element.PathElementBase;
1288
this.path = function(ctx) {
1289
var KAPPA = 4 * ((Math.sqrt(2) - 1) / 3);
1290
var rx = this.attribute('rx').Length.toPixels('x');
1291
var ry = this.attribute('ry').Length.toPixels('y');
1292
var cx = this.attribute('cx').Length.toPixels('x');
1293
var cy = this.attribute('cy').Length.toPixels('y');
1297
ctx.moveTo(cx, cy - ry);
1298
ctx.bezierCurveTo(cx + (KAPPA * rx), cy - ry, cx + rx, cy - (KAPPA * ry), cx + rx, cy);
1299
ctx.bezierCurveTo(cx + rx, cy + (KAPPA * ry), cx + (KAPPA * rx), cy + ry, cx, cy + ry);
1300
ctx.bezierCurveTo(cx - (KAPPA * rx), cy + ry, cx - rx, cy + (KAPPA * ry), cx - rx, cy);
1301
ctx.bezierCurveTo(cx - rx, cy - (KAPPA * ry), cx - (KAPPA * rx), cy - ry, cx, cy - ry);
1305
return new svg.BoundingBox(cx - rx, cy - ry, cx + rx, cy + ry);
1308
svg.Element.ellipse.prototype = new svg.Element.PathElementBase;
1311
svg.Element.line = function(node) {
1312
this.base = svg.Element.PathElementBase;
1315
this.getPoints = function() {
1317
new svg.Point(this.attribute('x1').Length.toPixels('x'), this.attribute('y1').Length.toPixels('y')),
1318
new svg.Point(this.attribute('x2').Length.toPixels('x'), this.attribute('y2').Length.toPixels('y'))];
1321
this.path = function(ctx) {
1322
var points = this.getPoints();
1326
ctx.moveTo(points[0].x, points[0].y);
1327
ctx.lineTo(points[1].x, points[1].y);
1330
return new svg.BoundingBox(points[0].x, points[0].y, points[1].x, points[1].y);
1333
this.getMarkers = function() {
1334
var points = this.getPoints();
1335
var a = points[0].angleTo(points[1]);
1336
return [[points[0], a], [points[1], a]];
1339
svg.Element.line.prototype = new svg.Element.PathElementBase;
1342
svg.Element.polyline = function(node) {
1343
this.base = svg.Element.PathElementBase;
1346
this.points = svg.CreatePath(this.attribute('points').value);
1347
this.path = function(ctx) {
1348
var bb = new svg.BoundingBox(this.points[0].x, this.points[0].y);
1351
ctx.moveTo(this.points[0].x, this.points[0].y);
1353
for (var i=1; i<this.points.length; i++) {
1354
bb.addPoint(this.points[i].x, this.points[i].y);
1355
if (ctx != null) ctx.lineTo(this.points[i].x, this.points[i].y);
1360
this.getMarkers = function() {
1362
for (var i=0; i<this.points.length - 1; i++) {
1363
markers.push([this.points[i], this.points[i].angleTo(this.points[i+1])]);
1365
markers.push([this.points[this.points.length-1], markers[markers.length-1][1]]);
1369
svg.Element.polyline.prototype = new svg.Element.PathElementBase;
1372
svg.Element.polygon = function(node) {
1373
this.base = svg.Element.polyline;
1376
this.basePath = this.path;
1377
this.path = function(ctx) {
1378
var bb = this.basePath(ctx);
1380
ctx.lineTo(this.points[0].x, this.points[0].y);
1386
svg.Element.polygon.prototype = new svg.Element.polyline;
1389
svg.Element.path = function(node) {
1390
this.base = svg.Element.PathElementBase;
1393
var d = this.attribute('d').value;
1394
// TODO: convert to real lexer based on http://www.w3.org/TR/SVG11/paths.html#PathDataBNF
1395
d = d.replace(/,/gm,' '); // get rid of all commas
1396
d = d.replace(/([MmZzLlHhVvCcSsQqTtAa])([MmZzLlHhVvCcSsQqTtAa])/gm,'$1 $2'); // separate commands from commands
1397
d = d.replace(/([MmZzLlHhVvCcSsQqTtAa])([MmZzLlHhVvCcSsQqTtAa])/gm,'$1 $2'); // separate commands from commands
1398
d = d.replace(/([MmZzLlHhVvCcSsQqTtAa])([^\s])/gm,'$1 $2'); // separate commands from points
1399
d = d.replace(/([^\s])([MmZzLlHhVvCcSsQqTtAa])/gm,'$1 $2'); // separate commands from points
1400
d = d.replace(/([0-9])([+\-])/gm,'$1 $2'); // separate digits when no comma
1401
d = d.replace(/(\.[0-9]*)(\.)/gm,'$1 $2'); // separate digits when no comma
1402
d = d.replace(/([Aa](\s+[0-9]+){3})\s+([01])\s*([01])/gm,'$1 $3 $4 '); // shorthand elliptical arc path syntax
1403
d = svg.compressSpaces(d); // compress multiple spaces
1405
this.PathParser = new (function(d) {
1406
this.tokens = d.split(' ');
1408
this.reset = function() {
1411
this.previousCommand = '';
1412
this.start = new svg.Point(0, 0);
1413
this.control = new svg.Point(0, 0);
1414
this.current = new svg.Point(0, 0);
1419
this.isEnd = function() {
1420
return this.i >= this.tokens.length - 1;
1423
this.isCommandOrEnd = function() {
1424
if (this.isEnd()) return true;
1425
return this.tokens[this.i + 1].match(/^[A-Za-z]$/) != null;
1428
this.isRelativeCommand = function() {
1429
return this.command == this.command.toLowerCase();
1432
this.getToken = function() {
1433
this.i = this.i + 1;
1434
return this.tokens[this.i];
1437
this.getScalar = function() {
1438
return parseFloat(this.getToken());
1441
this.nextCommand = function() {
1442
this.previousCommand = this.command;
1443
this.command = this.getToken();
1446
this.getPoint = function() {
1447
var p = new svg.Point(this.getScalar(), this.getScalar());
1448
return this.makeAbsolute(p);
1451
this.getAsControlPoint = function() {
1452
var p = this.getPoint();
1457
this.getAsCurrentPoint = function() {
1458
var p = this.getPoint();
1463
this.getReflectedControlPoint = function() {
1464
if (this.previousCommand.toLowerCase() != 'c' && this.previousCommand.toLowerCase() != 's') {
1465
return this.current;
1469
var p = new svg.Point(2 * this.current.x - this.control.x, 2 * this.current.y - this.control.y);
1473
this.makeAbsolute = function(p) {
1474
if (this.isRelativeCommand()) {
1475
p.x = this.current.x + p.x;
1476
p.y = this.current.y + p.y;
1481
this.addMarker = function(p, from, priorTo) {
1482
// if the last angle isn't filled in because we didn't have this point yet ...
1483
if (priorTo != null && this.angles.length > 0 && this.angles[this.angles.length-1] == null) {
1484
this.angles[this.angles.length-1] = this.points[this.points.length-1].angleTo(priorTo);
1486
this.addMarkerAngle(p, from == null ? null : from.angleTo(p));
1489
this.addMarkerAngle = function(p, a) {
1490
this.points.push(p);
1491
this.angles.push(a);
1494
this.getMarkerPoints = function() { return this.points; }
1495
this.getMarkerAngles = function() {
1496
for (var i=0; i<this.angles.length; i++) {
1497
if (this.angles[i] == null) {
1498
for (var j=i+1; j<this.angles.length; j++) {
1499
if (this.angles[j] != null) {
1500
this.angles[i] = this.angles[j];
1510
this.path = function(ctx) {
1511
var pp = this.PathParser;
1514
var bb = new svg.BoundingBox();
1515
if (ctx != null) ctx.beginPath();
1516
while (!pp.isEnd()) {
1518
switch (pp.command.toUpperCase()) {
1520
var p = pp.getAsCurrentPoint();
1522
bb.addPoint(p.x, p.y);
1523
if (ctx != null) ctx.moveTo(p.x, p.y);
1524
pp.start = pp.current;
1525
while (!pp.isCommandOrEnd()) {
1526
var p = pp.getAsCurrentPoint();
1527
pp.addMarker(p, pp.start);
1528
bb.addPoint(p.x, p.y);
1529
if (ctx != null) ctx.lineTo(p.x, p.y);
1533
while (!pp.isCommandOrEnd()) {
1535
var p = pp.getAsCurrentPoint();
1537
bb.addPoint(p.x, p.y);
1538
if (ctx != null) ctx.lineTo(p.x, p.y);
1542
while (!pp.isCommandOrEnd()) {
1543
var newP = new svg.Point((pp.isRelativeCommand() ? pp.current.x : 0) + pp.getScalar(), pp.current.y);
1544
pp.addMarker(newP, pp.current);
1546
bb.addPoint(pp.current.x, pp.current.y);
1547
if (ctx != null) ctx.lineTo(pp.current.x, pp.current.y);
1551
while (!pp.isCommandOrEnd()) {
1552
var newP = new svg.Point(pp.current.x, (pp.isRelativeCommand() ? pp.current.y : 0) + pp.getScalar());
1553
pp.addMarker(newP, pp.current);
1555
bb.addPoint(pp.current.x, pp.current.y);
1556
if (ctx != null) ctx.lineTo(pp.current.x, pp.current.y);
1560
while (!pp.isCommandOrEnd()) {
1561
var curr = pp.current;
1562
var p1 = pp.getPoint();
1563
var cntrl = pp.getAsControlPoint();
1564
var cp = pp.getAsCurrentPoint();
1565
pp.addMarker(cp, cntrl, p1);
1566
bb.addBezierCurve(curr.x, curr.y, p1.x, p1.y, cntrl.x, cntrl.y, cp.x, cp.y);
1567
if (ctx != null) ctx.bezierCurveTo(p1.x, p1.y, cntrl.x, cntrl.y, cp.x, cp.y);
1571
while (!pp.isCommandOrEnd()) {
1572
var curr = pp.current;
1573
var p1 = pp.getReflectedControlPoint();
1574
var cntrl = pp.getAsControlPoint();
1575
var cp = pp.getAsCurrentPoint();
1576
pp.addMarker(cp, cntrl, p1);
1577
bb.addBezierCurve(curr.x, curr.y, p1.x, p1.y, cntrl.x, cntrl.y, cp.x, cp.y);
1578
if (ctx != null) ctx.bezierCurveTo(p1.x, p1.y, cntrl.x, cntrl.y, cp.x, cp.y);
1582
while (!pp.isCommandOrEnd()) {
1583
var curr = pp.current;
1584
var cntrl = pp.getAsControlPoint();
1585
var cp = pp.getAsCurrentPoint();
1586
pp.addMarker(cp, cntrl, cntrl);
1587
bb.addQuadraticCurve(curr.x, curr.y, cntrl.x, cntrl.y, cp.x, cp.y);
1588
if (ctx != null) ctx.quadraticCurveTo(cntrl.x, cntrl.y, cp.x, cp.y);
1592
while (!pp.isCommandOrEnd()) {
1593
var curr = pp.current;
1594
var cntrl = pp.getReflectedControlPoint();
1596
var cp = pp.getAsCurrentPoint();
1597
pp.addMarker(cp, cntrl, cntrl);
1598
bb.addQuadraticCurve(curr.x, curr.y, cntrl.x, cntrl.y, cp.x, cp.y);
1599
if (ctx != null) ctx.quadraticCurveTo(cntrl.x, cntrl.y, cp.x, cp.y);
1603
while (!pp.isCommandOrEnd()) {
1604
var curr = pp.current;
1605
var rx = pp.getScalar();
1606
var ry = pp.getScalar();
1607
var xAxisRotation = pp.getScalar() * (Math.PI / 180.0);
1608
var largeArcFlag = pp.getScalar();
1609
var sweepFlag = pp.getScalar();
1610
var cp = pp.getAsCurrentPoint();
1612
// Conversion from endpoint to center parameterization
1613
// http://www.w3.org/TR/SVG11/implnote.html#ArcImplementationNotes
1615
var currp = new svg.Point(
1616
Math.cos(xAxisRotation) * (curr.x - cp.x) / 2.0 + Math.sin(xAxisRotation) * (curr.y - cp.y) / 2.0,
1617
-Math.sin(xAxisRotation) * (curr.x - cp.x) / 2.0 + Math.cos(xAxisRotation) * (curr.y - cp.y) / 2.0
1620
var l = Math.pow(currp.x,2)/Math.pow(rx,2)+Math.pow(currp.y,2)/Math.pow(ry,2);
1626
var s = (largeArcFlag == sweepFlag ? -1 : 1) * Math.sqrt(
1627
((Math.pow(rx,2)*Math.pow(ry,2))-(Math.pow(rx,2)*Math.pow(currp.y,2))-(Math.pow(ry,2)*Math.pow(currp.x,2))) /
1628
(Math.pow(rx,2)*Math.pow(currp.y,2)+Math.pow(ry,2)*Math.pow(currp.x,2))
1630
if (isNaN(s)) s = 0;
1631
var cpp = new svg.Point(s * rx * currp.y / ry, s * -ry * currp.x / rx);
1633
var centp = new svg.Point(
1634
(curr.x + cp.x) / 2.0 + Math.cos(xAxisRotation) * cpp.x - Math.sin(xAxisRotation) * cpp.y,
1635
(curr.y + cp.y) / 2.0 + Math.sin(xAxisRotation) * cpp.x + Math.cos(xAxisRotation) * cpp.y
1638
var m = function(v) { return Math.sqrt(Math.pow(v[0],2) + Math.pow(v[1],2)); }
1639
// ratio between two vectors
1640
var r = function(u, v) { return (u[0]*v[0]+u[1]*v[1]) / (m(u)*m(v)) }
1641
// angle between two vectors
1642
var a = function(u, v) { return (u[0]*v[1] < u[1]*v[0] ? -1 : 1) * Math.acos(r(u,v)); }
1644
var a1 = a([1,0], [(currp.x-cpp.x)/rx,(currp.y-cpp.y)/ry]);
1646
var u = [(currp.x-cpp.x)/rx,(currp.y-cpp.y)/ry];
1647
var v = [(-currp.x-cpp.x)/rx,(-currp.y-cpp.y)/ry];
1649
if (r(u,v) <= -1) ad = Math.PI;
1650
if (r(u,v) >= 1) ad = 0;
1652
if (sweepFlag == 0 && ad > 0) ad = ad - 2 * Math.PI;
1653
if (sweepFlag == 1 && ad < 0) ad = ad + 2 * Math.PI;
1656
var halfWay = new svg.Point(
1657
centp.x - rx * Math.cos((a1 + ad) / 2),
1658
centp.y - ry * Math.sin((a1 + ad) / 2)
1660
pp.addMarkerAngle(halfWay, (a1 + ad) / 2 + (sweepFlag == 0 ? 1 : -1) * Math.PI / 2);
1661
pp.addMarkerAngle(cp, ad + (sweepFlag == 0 ? 1 : -1) * Math.PI / 2);
1663
bb.addPoint(cp.x, cp.y); // TODO: this is too naive, make it better
1665
var r = rx > ry ? rx : ry;
1666
var sx = rx > ry ? 1 : rx / ry;
1667
var sy = rx > ry ? ry / rx : 1;
1669
ctx.translate(centp.x, centp.y);
1670
ctx.rotate(xAxisRotation);
1672
ctx.arc(0, 0, r, a1, a1 + ad, 1 - sweepFlag);
1673
ctx.scale(1/sx, 1/sy);
1674
ctx.rotate(-xAxisRotation);
1675
ctx.translate(-centp.x, -centp.y);
1680
if (ctx != null) ctx.closePath();
1681
pp.current = pp.start;
1688
this.getMarkers = function() {
1689
var points = this.PathParser.getMarkerPoints();
1690
var angles = this.PathParser.getMarkerAngles();
1693
for (var i=0; i<points.length; i++) {
1694
markers.push([points[i], angles[i]]);
1699
svg.Element.path.prototype = new svg.Element.PathElementBase;
1702
svg.Element.pattern = function(node) {
1703
this.base = svg.Element.ElementBase;
1706
this.createPattern = function(ctx, element) {
1707
// render me using a temporary svg element
1708
var tempSvg = new svg.Element.svg();
1709
tempSvg.attributes['viewBox'] = new svg.Property('viewBox', this.attribute('viewBox').value);
1710
tempSvg.attributes['x'] = new svg.Property('x', this.attribute('x').value);
1711
tempSvg.attributes['y'] = new svg.Property('y', this.attribute('y').value);
1712
tempSvg.attributes['width'] = new svg.Property('width', this.attribute('width').value);
1713
tempSvg.attributes['height'] = new svg.Property('height', this.attribute('height').value);
1714
tempSvg.children = this.children;
1716
var c = document.createElement('canvas');
1717
c.width = this.attribute('width').Length.toPixels('x');
1718
c.height = this.attribute('height').Length.toPixels('y');
1719
tempSvg.render(c.getContext('2d'));
1720
return ctx.createPattern(c, 'repeat');
1723
svg.Element.pattern.prototype = new svg.Element.ElementBase;
1726
svg.Element.marker = function(node) {
1727
this.base = svg.Element.ElementBase;
1730
this.baseRender = this.render;
1731
this.render = function(ctx, point, angle) {
1732
ctx.translate(point.x, point.y);
1733
if (this.attribute('orient').valueOrDefault('auto') == 'auto') ctx.rotate(angle);
1734
if (this.attribute('markerUnits').valueOrDefault('strokeWidth') == 'strokeWidth') ctx.scale(ctx.lineWidth, ctx.lineWidth);
1737
// render me using a temporary svg element
1738
var tempSvg = new svg.Element.svg();
1739
tempSvg.attributes['viewBox'] = new svg.Property('viewBox', this.attribute('viewBox').value);
1740
tempSvg.attributes['refX'] = new svg.Property('refX', this.attribute('refX').value);
1741
tempSvg.attributes['refY'] = new svg.Property('refY', this.attribute('refY').value);
1742
tempSvg.attributes['width'] = new svg.Property('width', this.attribute('markerWidth').value);
1743
tempSvg.attributes['height'] = new svg.Property('height', this.attribute('markerHeight').value);
1744
tempSvg.attributes['fill'] = new svg.Property('fill', this.attribute('fill').valueOrDefault('black'));
1745
tempSvg.attributes['stroke'] = new svg.Property('stroke', this.attribute('stroke').valueOrDefault('none'));
1746
tempSvg.children = this.children;
1747
tempSvg.render(ctx);
1750
if (this.attribute('markerUnits').valueOrDefault('strokeWidth') == 'strokeWidth') ctx.scale(1/ctx.lineWidth, 1/ctx.lineWidth);
1751
if (this.attribute('orient').valueOrDefault('auto') == 'auto') ctx.rotate(-angle);
1752
ctx.translate(-point.x, -point.y);
1755
svg.Element.marker.prototype = new svg.Element.ElementBase;
1757
// definitions element
1758
svg.Element.defs = function(node) {
1759
this.base = svg.Element.ElementBase;
1762
this.render = function(ctx) {
1766
svg.Element.defs.prototype = new svg.Element.ElementBase;
1768
// base for gradients
1769
svg.Element.GradientBase = function(node) {
1770
this.base = svg.Element.ElementBase;
1773
this.gradientUnits = this.attribute('gradientUnits').valueOrDefault('objectBoundingBox');
1776
for (var i=0; i<this.children.length; i++) {
1777
var child = this.children[i];
1778
this.stops.push(child);
1781
this.getGradient = function() {
1785
this.createGradient = function(ctx, element) {
1786
var stopsContainer = this;
1787
if (this.attribute('xlink:href').hasValue()) {
1788
stopsContainer = this.attribute('xlink:href').Definition.getDefinition();
1791
var g = this.getGradient(ctx, element);
1792
for (var i=0; i<stopsContainer.stops.length; i++) {
1793
g.addColorStop(stopsContainer.stops[i].offset, stopsContainer.stops[i].color);
1796
if (this.attribute('gradientTransform').hasValue()) {
1797
// render as transformed pattern on temporary canvas
1798
var rootView = svg.ViewPort.viewPorts[0];
1800
var rect = new svg.Element.rect();
1801
rect.attributes['x'] = new svg.Property('x', -svg.MAX_VIRTUAL_PIXELS/3.0);
1802
rect.attributes['y'] = new svg.Property('y', -svg.MAX_VIRTUAL_PIXELS/3.0);
1803
rect.attributes['width'] = new svg.Property('width', svg.MAX_VIRTUAL_PIXELS);
1804
rect.attributes['height'] = new svg.Property('height', svg.MAX_VIRTUAL_PIXELS);
1806
var group = new svg.Element.g();
1807
group.attributes['transform'] = new svg.Property('transform', this.attribute('gradientTransform').value);
1808
group.children = [ rect ];
1810
var tempSvg = new svg.Element.svg();
1811
tempSvg.attributes['x'] = new svg.Property('x', 0);
1812
tempSvg.attributes['y'] = new svg.Property('y', 0);
1813
tempSvg.attributes['width'] = new svg.Property('width', rootView.width);
1814
tempSvg.attributes['height'] = new svg.Property('height', rootView.height);
1815
tempSvg.children = [ group ];
1817
var c = document.createElement('canvas');
1818
c.width = rootView.width;
1819
c.height = rootView.height;
1820
var tempCtx = c.getContext('2d');
1821
tempCtx.fillStyle = g;
1822
tempSvg.render(tempCtx);
1823
return tempCtx.createPattern(c, 'no-repeat');
1829
svg.Element.GradientBase.prototype = new svg.Element.ElementBase;
1831
// linear gradient element
1832
svg.Element.linearGradient = function(node) {
1833
this.base = svg.Element.GradientBase;
1836
this.getGradient = function(ctx, element) {
1837
var bb = element.getBoundingBox();
1839
var x1 = (this.gradientUnits == 'objectBoundingBox'
1840
? bb.x() + bb.width() * this.attribute('x1').numValue()
1841
: this.attribute('x1').Length.toPixels('x'));
1842
var y1 = (this.gradientUnits == 'objectBoundingBox'
1843
? bb.y() + bb.height() * this.attribute('y1').numValue()
1844
: this.attribute('y1').Length.toPixels('y'));
1845
var x2 = (this.gradientUnits == 'objectBoundingBox'
1846
? bb.x() + bb.width() * this.attribute('x2').numValue()
1847
: this.attribute('x2').Length.toPixels('x'));
1848
var y2 = (this.gradientUnits == 'objectBoundingBox'
1849
? bb.y() + bb.height() * this.attribute('y2').numValue()
1850
: this.attribute('y2').Length.toPixels('y'));
1852
return ctx.createLinearGradient(x1, y1, x2, y2);
1855
svg.Element.linearGradient.prototype = new svg.Element.GradientBase;
1857
// radial gradient element
1858
svg.Element.radialGradient = function(node) {
1859
this.base = svg.Element.GradientBase;
1862
this.getGradient = function(ctx, element) {
1863
var bb = element.getBoundingBox();
1865
var cx = (this.gradientUnits == 'objectBoundingBox'
1866
? bb.x() + bb.width() * this.attribute('cx').numValue()
1867
: this.attribute('cx').Length.toPixels('x'));
1868
var cy = (this.gradientUnits == 'objectBoundingBox'
1869
? bb.y() + bb.height() * this.attribute('cy').numValue()
1870
: this.attribute('cy').Length.toPixels('y'));
1874
if (this.attribute('fx').hasValue()) {
1875
fx = (this.gradientUnits == 'objectBoundingBox'
1876
? bb.x() + bb.width() * this.attribute('fx').numValue()
1877
: this.attribute('fx').Length.toPixels('x'));
1879
if (this.attribute('fy').hasValue()) {
1880
fy = (this.gradientUnits == 'objectBoundingBox'
1881
? bb.y() + bb.height() * this.attribute('fy').numValue()
1882
: this.attribute('fy').Length.toPixels('y'));
1885
var r = (this.gradientUnits == 'objectBoundingBox'
1886
? (bb.width() + bb.height()) / 2.0 * this.attribute('r').numValue()
1887
: this.attribute('r').Length.toPixels());
1889
return ctx.createRadialGradient(fx, fy, 0, cx, cy, r);
1892
svg.Element.radialGradient.prototype = new svg.Element.GradientBase;
1894
// gradient stop element
1895
svg.Element.stop = function(node) {
1896
this.base = svg.Element.ElementBase;
1899
this.offset = this.attribute('offset').numValue();
1901
var stopColor = this.style('stop-color');
1902
if (this.style('stop-opacity').hasValue()) stopColor = stopColor.Color.addOpacity(this.style('stop-opacity').value);
1903
this.color = stopColor.value;
1905
svg.Element.stop.prototype = new svg.Element.ElementBase;
1907
// animation base element
1908
svg.Element.AnimateBase = function(node) {
1909
this.base = svg.Element.ElementBase;
1912
svg.Animations.push(this);
1914
this.duration = 0.0;
1915
this.begin = this.attribute('begin').Time.toMilliseconds();
1916
this.maxDuration = this.begin + this.attribute('dur').Time.toMilliseconds();
1918
this.getProperty = function() {
1919
var attributeType = this.attribute('attributeType').value;
1920
var attributeName = this.attribute('attributeName').value;
1922
if (attributeType == 'CSS') {
1923
return this.parent.style(attributeName, true);
1925
return this.parent.attribute(attributeName, true);
1928
this.initialValue = null;
1929
this.removed = false;
1931
this.calcValue = function() {
1936
this.update = function(delta) {
1937
// set initial value
1938
if (this.initialValue == null) {
1939
this.initialValue = this.getProperty().value;
1942
// if we're past the end time
1943
if (this.duration > this.maxDuration) {
1944
// loop for indefinitely repeating animations
1945
if (this.attribute('repeatCount').value == 'indefinite') {
1948
else if (this.attribute('fill').valueOrDefault('remove') == 'remove' && !this.removed) {
1949
this.removed = true;
1950
this.getProperty().value = this.initialValue;
1954
return false; // no updates made
1957
this.duration = this.duration + delta;
1959
// if we're past the begin time
1960
var updated = false;
1961
if (this.begin < this.duration) {
1962
var newValue = this.calcValue(); // tween
1964
if (this.attribute('type').hasValue()) {
1965
// for transform, etc.
1966
var type = this.attribute('type').value;
1967
newValue = type + '(' + newValue + ')';
1970
this.getProperty().value = newValue;
1977
// fraction of duration we've covered
1978
this.progress = function() {
1979
return ((this.duration - this.begin) / (this.maxDuration - this.begin));
1982
svg.Element.AnimateBase.prototype = new svg.Element.ElementBase;
1985
svg.Element.animate = function(node) {
1986
this.base = svg.Element.AnimateBase;
1989
this.calcValue = function() {
1990
var from = this.attribute('from').numValue();
1991
var to = this.attribute('to').numValue();
1993
// tween value linearly
1994
return from + (to - from) * this.progress();
1997
svg.Element.animate.prototype = new svg.Element.AnimateBase;
1999
// animate color element
2000
svg.Element.animateColor = function(node) {
2001
this.base = svg.Element.AnimateBase;
2004
this.calcValue = function() {
2005
var from = new RGBColor(this.attribute('from').value);
2006
var to = new RGBColor(this.attribute('to').value);
2008
if (from.ok && to.ok) {
2009
// tween color linearly
2010
var r = from.r + (to.r - from.r) * this.progress();
2011
var g = from.g + (to.g - from.g) * this.progress();
2012
var b = from.b + (to.b - from.b) * this.progress();
2013
return 'rgb('+parseInt(r,10)+','+parseInt(g,10)+','+parseInt(b,10)+')';
2015
return this.attribute('from').value;
2018
svg.Element.animateColor.prototype = new svg.Element.AnimateBase;
2020
// animate transform element
2021
svg.Element.animateTransform = function(node) {
2022
this.base = svg.Element.animate;
2025
svg.Element.animateTransform.prototype = new svg.Element.animate;
2028
svg.Element.font = function(node) {
2029
this.base = svg.Element.ElementBase;
2032
this.horizAdvX = this.attribute('horiz-adv-x').numValue();
2035
this.isArabic = false;
2036
this.fontFace = null;
2037
this.missingGlyph = null;
2039
for (var i=0; i<this.children.length; i++) {
2040
var child = this.children[i];
2041
if (child.type == 'font-face') {
2042
this.fontFace = child;
2043
if (child.style('font-family').hasValue()) {
2044
svg.Definitions[child.style('font-family').value] = this;
2047
else if (child.type == 'missing-glyph') this.missingGlyph = child;
2048
else if (child.type == 'glyph') {
2049
if (child.arabicForm != '') {
2051
this.isArabic = true;
2052
if (typeof(this.glyphs[child.unicode]) == 'undefined') this.glyphs[child.unicode] = [];
2053
this.glyphs[child.unicode][child.arabicForm] = child;
2056
this.glyphs[child.unicode] = child;
2061
svg.Element.font.prototype = new svg.Element.ElementBase;
2063
// font-face element
2064
svg.Element.fontface = function(node) {
2065
this.base = svg.Element.ElementBase;
2068
this.ascent = this.attribute('ascent').value;
2069
this.descent = this.attribute('descent').value;
2070
this.unitsPerEm = this.attribute('units-per-em').numValue();
2072
svg.Element.fontface.prototype = new svg.Element.ElementBase;
2074
// missing-glyph element
2075
svg.Element.missingglyph = function(node) {
2076
this.base = svg.Element.path;
2081
svg.Element.missingglyph.prototype = new svg.Element.path;
2084
svg.Element.glyph = function(node) {
2085
this.base = svg.Element.path;
2088
this.horizAdvX = this.attribute('horiz-adv-x').numValue();
2089
this.unicode = this.attribute('unicode').value;
2090
this.arabicForm = this.attribute('arabic-form').value;
2092
svg.Element.glyph.prototype = new svg.Element.path;
2095
svg.Element.text = function(node) {
2096
this.base = svg.Element.RenderedElementBase;
2102
for (var i=0; i<node.childNodes.length; i++) {
2103
var childNode = node.childNodes[i];
2104
if (childNode.nodeType == 1) { // capture tspan and tref nodes
2105
this.addChild(childNode, true);
2107
else if (childNode.nodeType == 3) { // capture text
2108
this.addChild(new svg.Element.tspan(childNode), false);
2113
this.baseSetContext = this.setContext;
2114
this.setContext = function(ctx) {
2115
this.baseSetContext(ctx);
2116
if (this.style('dominant-baseline').hasValue()) ctx.textBaseline = this.style('dominant-baseline').value;
2117
if (this.style('alignment-baseline').hasValue()) ctx.textBaseline = this.style('alignment-baseline').value;
2120
this.renderChildren = function(ctx) {
2121
var textAnchor = this.style('text-anchor').valueOrDefault('start');
2122
var x = this.attribute('x').Length.toPixels('x');
2123
var y = this.attribute('y').Length.toPixels('y');
2124
for (var i=0; i<this.children.length; i++) {
2125
var child = this.children[i];
2127
if (child.attribute('x').hasValue()) {
2128
child.x = child.attribute('x').Length.toPixels('x');
2131
if (child.attribute('dx').hasValue()) x += child.attribute('dx').Length.toPixels('x');
2135
var childLength = child.measureText(ctx);
2136
if (textAnchor != 'start' && (i==0 || child.attribute('x').hasValue())) { // new group?
2137
// loop through rest of children
2138
var groupLength = childLength;
2139
for (var j=i+1; j<this.children.length; j++) {
2140
var childInGroup = this.children[j];
2141
if (childInGroup.attribute('x').hasValue()) break; // new group
2142
groupLength += childInGroup.measureText(ctx);
2144
child.x -= (textAnchor == 'end' ? groupLength : groupLength / 2.0);
2146
x = child.x + childLength;
2148
if (child.attribute('y').hasValue()) {
2149
child.y = child.attribute('y').Length.toPixels('y');
2152
if (child.attribute('dy').hasValue()) y += child.attribute('dy').Length.toPixels('y');
2161
svg.Element.text.prototype = new svg.Element.RenderedElementBase;
2164
svg.Element.TextElementBase = function(node) {
2165
this.base = svg.Element.RenderedElementBase;
2168
this.getGlyph = function(font, text, i) {
2171
if (font.isArabic) {
2172
var arabicForm = 'isolated';
2173
if ((i==0 || text[i-1]==' ') && i<text.length-2 && text[i+1]!=' ') arabicForm = 'terminal';
2174
if (i>0 && text[i-1]!=' ' && i<text.length-2 && text[i+1]!=' ') arabicForm = 'medial';
2175
if (i>0 && text[i-1]!=' ' && (i == text.length-1 || text[i+1]==' ')) arabicForm = 'initial';
2176
if (typeof(font.glyphs[c]) != 'undefined') {
2177
glyph = font.glyphs[c][arabicForm];
2178
if (glyph == null && font.glyphs[c].type == 'glyph') glyph = font.glyphs[c];
2182
glyph = font.glyphs[c];
2184
if (glyph == null) glyph = font.missingGlyph;
2188
this.renderChildren = function(ctx) {
2189
var customFont = this.parent.style('font-family').Definition.getDefinition();
2190
if (customFont != null) {
2191
var fontSize = this.parent.style('font-size').numValueOrDefault(svg.Font.Parse(svg.ctx.font).fontSize);
2192
var fontStyle = this.parent.style('font-style').valueOrDefault(svg.Font.Parse(svg.ctx.font).fontStyle);
2193
var text = this.getText();
2194
if (customFont.isRTL) text = text.split("").reverse().join("");
2196
var dx = svg.ToNumberArray(this.parent.attribute('dx').value);
2197
for (var i=0; i<text.length; i++) {
2198
var glyph = this.getGlyph(customFont, text, i);
2199
var scale = fontSize / customFont.fontFace.unitsPerEm;
2200
ctx.translate(this.x, this.y);
2201
ctx.scale(scale, -scale);
2202
var lw = ctx.lineWidth;
2203
ctx.lineWidth = ctx.lineWidth * customFont.fontFace.unitsPerEm / fontSize;
2204
if (fontStyle == 'italic') ctx.transform(1, 0, .4, 1, 0, 0);
2206
if (fontStyle == 'italic') ctx.transform(1, 0, -.4, 1, 0, 0);
2208
ctx.scale(1/scale, -1/scale);
2209
ctx.translate(-this.x, -this.y);
2211
this.x += fontSize * (glyph.horizAdvX || customFont.horizAdvX) / customFont.fontFace.unitsPerEm;
2212
if (typeof(dx[i]) != 'undefined' && !isNaN(dx[i])) {
2219
if (ctx.strokeStyle != '') ctx.strokeText(svg.compressSpaces(this.getText()), this.x, this.y);
2220
if (ctx.fillStyle != '') ctx.fillText(svg.compressSpaces(this.getText()), this.x, this.y);
2223
this.getText = function() {
2227
this.measureText = function(ctx) {
2228
var customFont = this.parent.style('font-family').Definition.getDefinition();
2229
if (customFont != null) {
2230
var fontSize = this.parent.style('font-size').numValueOrDefault(svg.Font.Parse(svg.ctx.font).fontSize);
2232
var text = this.getText();
2233
if (customFont.isRTL) text = text.split("").reverse().join("");
2234
var dx = svg.ToNumberArray(this.parent.attribute('dx').value);
2235
for (var i=0; i<text.length; i++) {
2236
var glyph = this.getGlyph(customFont, text, i);
2237
measure += (glyph.horizAdvX || customFont.horizAdvX) * fontSize / customFont.fontFace.unitsPerEm;
2238
if (typeof(dx[i]) != 'undefined' && !isNaN(dx[i])) {
2245
var textToMeasure = svg.compressSpaces(this.getText());
2246
if (!ctx.measureText) return textToMeasure.length * 10;
2249
this.setContext(ctx);
2250
var width = ctx.measureText(textToMeasure).width;
2255
svg.Element.TextElementBase.prototype = new svg.Element.RenderedElementBase;
2258
svg.Element.tspan = function(node) {
2259
this.base = svg.Element.TextElementBase;
2262
this.text = node.nodeType == 3 ? node.nodeValue : // text
2263
node.childNodes.length > 0 ? node.childNodes[0].nodeValue : // element
2265
this.getText = function() {
2269
svg.Element.tspan.prototype = new svg.Element.TextElementBase;
2272
svg.Element.tref = function(node) {
2273
this.base = svg.Element.TextElementBase;
2276
this.getText = function() {
2277
var element = this.attribute('xlink:href').Definition.getDefinition();
2278
if (element != null) return element.children[0].getText();
2281
svg.Element.tref.prototype = new svg.Element.TextElementBase;
2284
svg.Element.a = function(node) {
2285
this.base = svg.Element.TextElementBase;
2288
this.hasText = true;
2289
for (var i=0; i<node.childNodes.length; i++) {
2290
if (node.childNodes[i].nodeType != 3) this.hasText = false;
2293
// this might contain text
2294
this.text = this.hasText ? node.childNodes[0].nodeValue : '';
2295
this.getText = function() {
2299
this.baseRenderChildren = this.renderChildren;
2300
this.renderChildren = function(ctx) {
2302
// render as text element
2303
this.baseRenderChildren(ctx);
2304
var fontSize = new svg.Property('fontSize', svg.Font.Parse(svg.ctx.font).fontSize);
2305
svg.Mouse.checkBoundingBox(this, new svg.BoundingBox(this.x, this.y - fontSize.Length.toPixels('y'), this.x + this.measureText(ctx), this.y));
2308
// render as temporary group
2309
var g = new svg.Element.g();
2310
g.children = this.children;
2316
this.onclick = function() {
2317
window.open(this.attribute('xlink:href').value);
2320
this.onmousemove = function() {
2321
svg.ctx.canvas.style.cursor = 'pointer';
2324
svg.Element.a.prototype = new svg.Element.TextElementBase;
2327
svg.Element.image = function(node) {
2328
this.base = svg.Element.RenderedElementBase;
2331
svg.Images.push(this);
2332
this.img = document.createElement('img');
2333
this.loaded = false;
2335
this.img.onload = function() { that.loaded = true; }
2336
this.img.src = this.attribute('xlink:href').value;
2338
this.renderChildren = function(ctx) {
2339
var x = this.attribute('x').Length.toPixels('x');
2340
var y = this.attribute('y').Length.toPixels('y');
2342
var width = this.attribute('width').Length.toPixels('x');
2343
var height = this.attribute('height').Length.toPixels('y');
2344
if (width == 0 || height == 0) return;
2347
ctx.translate(x, y);
2348
svg.AspectRatio(ctx,
2349
this.attribute('preserveAspectRatio').value,
2356
ctx.drawImage(this.img, 0, 0);
2360
svg.Element.image.prototype = new svg.Element.RenderedElementBase;
2363
svg.Element.g = function(node) {
2364
this.base = svg.Element.RenderedElementBase;
2367
this.getBoundingBox = function() {
2368
var bb = new svg.BoundingBox();
2369
for (var i=0; i<this.children.length; i++) {
2370
bb.addBoundingBox(this.children[i].getBoundingBox());
2375
svg.Element.g.prototype = new svg.Element.RenderedElementBase;
2378
svg.Element.symbol = function(node) {
2379
this.base = svg.Element.RenderedElementBase;
2382
this.baseSetContext = this.setContext;
2383
this.setContext = function(ctx) {
2384
this.baseSetContext(ctx);
2387
if (this.attribute('viewBox').hasValue()) {
2388
var viewBox = svg.ToNumberArray(this.attribute('viewBox').value);
2389
var minX = viewBox[0];
2390
var minY = viewBox[1];
2392
height = viewBox[3];
2394
svg.AspectRatio(ctx,
2395
this.attribute('preserveAspectRatio').value,
2396
this.attribute('width').Length.toPixels('x'),
2398
this.attribute('height').Length.toPixels('y'),
2403
svg.ViewPort.SetCurrent(viewBox[2], viewBox[3]);
2407
svg.Element.symbol.prototype = new svg.Element.RenderedElementBase;
2410
svg.Element.style = function(node) {
2411
this.base = svg.Element.ElementBase;
2414
// text, or spaces then CDATA
2415
var css = node.childNodes[0].nodeValue + (node.childNodes.length > 1 ? node.childNodes[1].nodeValue : '');
2416
css = css.replace(/(\/\*([^*]|[\r\n]|(\*+([^*\/]|[\r\n])))*\*+\/)|(^[\s]*\/\/.*)/gm, ''); // remove comments
2417
css = svg.compressSpaces(css); // replace whitespace
2418
var cssDefs = css.split('}');
2419
for (var i=0; i<cssDefs.length; i++) {
2420
if (svg.trim(cssDefs[i]) != '') {
2421
var cssDef = cssDefs[i].split('{');
2422
var cssClasses = cssDef[0].split(',');
2423
var cssProps = cssDef[1].split(';');
2424
for (var j=0; j<cssClasses.length; j++) {
2425
var cssClass = svg.trim(cssClasses[j]);
2426
if (cssClass != '') {
2428
for (var k=0; k<cssProps.length; k++) {
2429
var prop = cssProps[k].indexOf(':');
2430
var name = cssProps[k].substr(0, prop);
2431
var value = cssProps[k].substr(prop + 1, cssProps[k].length - prop);
2432
if (name != null && value != null) {
2433
props[svg.trim(name)] = new svg.Property(svg.trim(name), svg.trim(value));
2436
svg.Styles[cssClass] = props;
2437
if (cssClass == '@font-face') {
2438
var fontFamily = props['font-family'].value.replace(/"/g,'');
2439
var srcs = props['src'].value.split(',');
2440
for (var s=0; s<srcs.length; s++) {
2441
if (srcs[s].indexOf('format("svg")') > 0) {
2442
var urlStart = srcs[s].indexOf('url');
2443
var urlEnd = srcs[s].indexOf(')', urlStart);
2444
var url = srcs[s].substr(urlStart + 5, urlEnd - urlStart - 6);
2445
var doc = svg.parseXml(svg.ajax(url));
2446
var fonts = doc.getElementsByTagName('font');
2447
for (var f=0; f<fonts.length; f++) {
2448
var font = svg.CreateElement(fonts[f]);
2449
svg.Definitions[fontFamily] = font;
2459
svg.Element.style.prototype = new svg.Element.ElementBase;
2462
svg.Element.use = function(node) {
2463
this.base = svg.Element.RenderedElementBase;
2466
this.baseSetContext = this.setContext;
2467
this.setContext = function(ctx) {
2468
this.baseSetContext(ctx);
2469
if (this.attribute('x').hasValue()) ctx.translate(this.attribute('x').Length.toPixels('x'), 0);
2470
if (this.attribute('y').hasValue()) ctx.translate(0, this.attribute('y').Length.toPixels('y'));
2473
this.getDefinition = function() {
2474
var element = this.attribute('xlink:href').Definition.getDefinition();
2475
if (this.attribute('width').hasValue()) element.attribute('width', true).value = this.attribute('width').value;
2476
if (this.attribute('height').hasValue()) element.attribute('height', true).value = this.attribute('height').value;
2480
this.path = function(ctx) {
2481
var element = this.getDefinition();
2482
if (element != null) element.path(ctx);
2485
this.renderChildren = function(ctx) {
2486
var element = this.getDefinition();
2487
if (element != null) element.render(ctx);
2490
svg.Element.use.prototype = new svg.Element.RenderedElementBase;
2493
svg.Element.mask = function(node) {
2494
this.base = svg.Element.ElementBase;
2497
this.apply = function(ctx, element) {
2498
// render as temp svg
2499
var x = this.attribute('x').Length.toPixels('x');
2500
var y = this.attribute('y').Length.toPixels('y');
2501
var width = this.attribute('width').Length.toPixels('x');
2502
var height = this.attribute('height').Length.toPixels('y');
2504
// temporarily remove mask to avoid recursion
2505
var mask = element.attribute('mask').value;
2506
element.attribute('mask').value = '';
2508
var cMask = document.createElement('canvas');
2509
cMask.width = x + width;
2510
cMask.height = y + height;
2511
var maskCtx = cMask.getContext('2d');
2512
this.renderChildren(maskCtx);
2514
var c = document.createElement('canvas');
2515
c.width = x + width;
2516
c.height = y + height;
2517
var tempCtx = c.getContext('2d');
2518
element.render(tempCtx);
2519
tempCtx.globalCompositeOperation = 'destination-in';
2520
tempCtx.fillStyle = maskCtx.createPattern(cMask, 'no-repeat');
2521
tempCtx.fillRect(0, 0, x + width, y + height);
2523
ctx.fillStyle = tempCtx.createPattern(c, 'no-repeat');
2524
ctx.fillRect(0, 0, x + width, y + height);
2527
element.attribute('mask').value = mask;
2530
this.render = function(ctx) {
2534
svg.Element.mask.prototype = new svg.Element.ElementBase;
2537
svg.Element.clipPath = function(node) {
2538
this.base = svg.Element.ElementBase;
2541
this.apply = function(ctx) {
2542
for (var i=0; i<this.children.length; i++) {
2543
if (this.children[i].path) {
2544
this.children[i].path(ctx);
2550
this.render = function(ctx) {
2554
svg.Element.clipPath.prototype = new svg.Element.ElementBase;
2557
svg.Element.filter = function(node) {
2558
this.base = svg.Element.ElementBase;
2561
this.apply = function(ctx, element) {
2562
// render as temp svg
2563
var bb = element.getBoundingBox();
2564
var x = this.attribute('x').Length.toPixels('x');
2565
var y = this.attribute('y').Length.toPixels('y');
2566
if (x == 0 || y == 0) {
2570
var width = this.attribute('width').Length.toPixels('x');
2571
var height = this.attribute('height').Length.toPixels('y');
2572
if (width == 0 || height == 0) {
2574
height = bb.height();
2577
// temporarily remove filter to avoid recursion
2578
var filter = element.style('filter').value;
2579
element.style('filter').value = '';
2581
// max filter distance
2582
var extraPercent = .20;
2583
var px = extraPercent * width;
2584
var py = extraPercent * height;
2586
var c = document.createElement('canvas');
2587
c.width = width + 2*px;
2588
c.height = height + 2*py;
2589
var tempCtx = c.getContext('2d');
2590
tempCtx.translate(-x + px, -y + py);
2591
element.render(tempCtx);
2594
for (var i=0; i<this.children.length; i++) {
2595
this.children[i].apply(tempCtx, 0, 0, width + 2*px, height + 2*py);
2599
ctx.drawImage(c, 0, 0, width + 2*px, height + 2*py, x - px, y - py, width + 2*px, height + 2*py);
2602
element.style('filter', true).value = filter;
2605
this.render = function(ctx) {
2609
svg.Element.filter.prototype = new svg.Element.ElementBase;
2611
svg.Element.feGaussianBlur = function(node) {
2612
this.base = svg.Element.ElementBase;
2615
function make_fgauss(sigma) {
2616
sigma = Math.max(sigma, 0.01);
2617
var len = Math.ceil(sigma * 4.0) + 1;
2619
for (var i = 0; i < len; i++) {
2620
mask[i] = Math.exp(-0.5 * (i / sigma) * (i / sigma));
2625
function normalize(mask) {
2627
for (var i = 1; i < mask.length; i++) {
2628
sum += Math.abs(mask[i]);
2630
sum = 2 * sum + Math.abs(mask[0]);
2631
for (var i = 0; i < mask.length; i++) {
2637
function convolve_even(src, dst, mask, width, height) {
2638
for (var y = 0; y < height; y++) {
2639
for (var x = 0; x < width; x++) {
2640
var a = imGet(src, x, y, width, height, 3)/255;
2641
for (var rgba = 0; rgba < 4; rgba++) {
2642
var sum = mask[0] * (a==0?255:imGet(src, x, y, width, height, rgba)) * (a==0||rgba==3?1:a);
2643
for (var i = 1; i < mask.length; i++) {
2644
var a1 = imGet(src, Math.max(x-i,0), y, width, height, 3)/255;
2645
var a2 = imGet(src, Math.min(x+i, width-1), y, width, height, 3)/255;
2647
((a1==0?255:imGet(src, Math.max(x-i,0), y, width, height, rgba)) * (a1==0||rgba==3?1:a1) +
2648
(a2==0?255:imGet(src, Math.min(x+i, width-1), y, width, height, rgba)) * (a2==0||rgba==3?1:a2));
2650
imSet(dst, y, x, height, width, rgba, sum);
2656
function imGet(img, x, y, width, height, rgba) {
2657
return img[y*width*4 + x*4 + rgba];
2660
function imSet(img, x, y, width, height, rgba, val) {
2661
img[y*width*4 + x*4 + rgba] = val;
2664
function blur(ctx, width, height, sigma)
2666
var srcData = ctx.getImageData(0, 0, width, height);
2667
var mask = make_fgauss(sigma);
2668
mask = normalize(mask);
2670
convolve_even(srcData.data, tmp, mask, width, height);
2671
convolve_even(tmp, srcData.data, mask, height, width);
2672
ctx.clearRect(0, 0, width, height);
2673
ctx.putImageData(srcData, 0, 0);
2676
this.apply = function(ctx, x, y, width, height) {
2677
// assuming x==0 && y==0 for now
2678
blur(ctx, width, height, this.attribute('stdDeviation').numValue());
2681
svg.Element.filter.prototype = new svg.Element.feGaussianBlur;
2683
// title element, do nothing
2684
svg.Element.title = function(node) {
2686
svg.Element.title.prototype = new svg.Element.ElementBase;
2688
// desc element, do nothing
2689
svg.Element.desc = function(node) {
2691
svg.Element.desc.prototype = new svg.Element.ElementBase;
2693
svg.Element.MISSING = function(node) {
2694
console.log('ERROR: Element \'' + node.nodeName + '\' not yet implemented.');
2696
svg.Element.MISSING.prototype = new svg.Element.ElementBase;
2699
svg.CreateElement = function(node) {
2700
var className = node.nodeName.replace(/^[^:]+:/,''); // remove namespace
2701
className = className.replace(/\-/g,''); // remove dashes
2703
if (typeof(svg.Element[className]) != 'undefined') {
2704
e = new svg.Element[className](node);
2707
e = new svg.Element.MISSING(node);
2710
e.type = node.nodeName;
2715
svg.load = function(ctx, url) {
2716
svg.loadXml(ctx, svg.ajax(url));
2720
svg.loadXml = function(ctx, xml) {
2721
svg.loadXmlDoc(ctx, svg.parseXml(xml));
2724
svg.loadXmlDoc = function(ctx, dom) {
2727
var mapXY = function(p) {
2730
p.x -= e.offsetLeft;
2734
if (window.scrollX) p.x += window.scrollX;
2735
if (window.scrollY) p.y += window.scrollY;
2740
if (svg.opts['ignoreMouse'] != true) {
2741
ctx.canvas.onclick = function(e) {
2742
var p = mapXY(new svg.Point(e != null ? e.clientX : event.clientX, e != null ? e.clientY : event.clientY));
2743
svg.Mouse.onclick(p.x, p.y);
2745
ctx.canvas.onmousemove = function(e) {
2746
var p = mapXY(new svg.Point(e != null ? e.clientX : event.clientX, e != null ? e.clientY : event.clientY));
2747
svg.Mouse.onmousemove(p.x, p.y);
2751
var e = svg.CreateElement(dom.documentElement);
2755
var isFirstRender = true;
2756
var draw = function() {
2757
svg.ViewPort.Clear();
2758
if (ctx.canvas.parentNode) svg.ViewPort.SetCurrent(ctx.canvas.parentNode.clientWidth, ctx.canvas.parentNode.clientHeight);
2760
if (svg.opts['ignoreDimensions'] != true) {
2762
if (e.style('width').hasValue()) {
2763
ctx.canvas.width = e.style('width').Length.toPixels('x');
2764
ctx.canvas.style.width = ctx.canvas.width + 'px';
2766
if (e.style('height').hasValue()) {
2767
ctx.canvas.height = e.style('height').Length.toPixels('y');
2768
ctx.canvas.style.height = ctx.canvas.height + 'px';
2771
var cWidth = ctx.canvas.clientWidth || ctx.canvas.width;
2772
var cHeight = ctx.canvas.clientHeight || ctx.canvas.height;
2773
svg.ViewPort.SetCurrent(cWidth, cHeight);
2775
if (svg.opts != null && svg.opts['offsetX'] != null) e.attribute('x', true).value = svg.opts['offsetX'];
2776
if (svg.opts != null && svg.opts['offsetY'] != null) e.attribute('y', true).value = svg.opts['offsetY'];
2777
if (svg.opts != null && svg.opts['scaleWidth'] != null && svg.opts['scaleHeight'] != null) {
2778
var xRatio = 1, yRatio = 1;
2779
if (e.attribute('width').hasValue()) xRatio = e.attribute('width').Length.toPixels('x') / svg.opts['scaleWidth'];
2780
if (e.attribute('height').hasValue()) yRatio = e.attribute('height').Length.toPixels('y') / svg.opts['scaleHeight'];
2782
e.attribute('width', true).value = svg.opts['scaleWidth'];
2783
e.attribute('height', true).value = svg.opts['scaleHeight'];
2784
e.attribute('viewBox', true).value = '0 0 ' + (cWidth * xRatio) + ' ' + (cHeight * yRatio);
2785
e.attribute('preserveAspectRatio', true).value = 'none';
2789
if (svg.opts['ignoreClear'] != true) {
2790
ctx.clearRect(0, 0, cWidth, cHeight);
2793
if (isFirstRender) {
2794
isFirstRender = false;
2795
if (svg.opts != null && typeof(svg.opts['renderCallback']) == 'function') svg.opts['renderCallback']();
2799
var waitingForImages = true;
2800
if (svg.ImagesLoaded()) {
2801
waitingForImages = false;
2804
svg.intervalID = setInterval(function() {
2805
var needUpdate = false;
2807
if (waitingForImages && svg.ImagesLoaded()) {
2808
waitingForImages = false;
2812
// need update from mouse events?
2813
if (svg.opts['ignoreMouse'] != true) {
2814
needUpdate = needUpdate | svg.Mouse.hasEvents();
2817
// need update from animations?
2818
if (svg.opts['ignoreAnimation'] != true) {
2819
for (var i=0; i<svg.Animations.length; i++) {
2820
needUpdate = needUpdate | svg.Animations[i].update(1000 / svg.FRAMERATE);
2824
// need update from redraw?
2825
if (svg.opts != null && typeof(svg.opts['forceRedraw']) == 'function') {
2826
if (svg.opts['forceRedraw']() == true) needUpdate = true;
2832
svg.Mouse.runEvents(); // run and clear our events
2834
}, 1000 / svg.FRAMERATE);
2837
svg.stop = function() {
2838
if (svg.intervalID) {
2839
clearInterval(svg.intervalID);
2843
svg.Mouse = new (function() {
2845
this.hasEvents = function() { return this.events.length != 0; }
2847
this.onclick = function(x, y) {
2848
this.events.push({ type: 'onclick', x: x, y: y,
2849
run: function(e) { if (e.onclick) e.onclick(); }
2853
this.onmousemove = function(x, y) {
2854
this.events.push({ type: 'onmousemove', x: x, y: y,
2855
run: function(e) { if (e.onmousemove) e.onmousemove(); }
2859
this.eventElements = [];
2861
this.checkPath = function(element, ctx) {
2862
for (var i=0; i<this.events.length; i++) {
2863
var e = this.events[i];
2864
if (ctx.isPointInPath && ctx.isPointInPath(e.x, e.y)) this.eventElements[i] = element;
2868
this.checkBoundingBox = function(element, bb) {
2869
for (var i=0; i<this.events.length; i++) {
2870
var e = this.events[i];
2871
if (bb.isPointInBox(e.x, e.y)) this.eventElements[i] = element;
2875
this.runEvents = function() {
2876
svg.ctx.canvas.style.cursor = '';
2878
for (var i=0; i<this.events.length; i++) {
2879
var e = this.events[i];
2880
var element = this.eventElements[i];
2883
element = element.parent;
2887
// done running, clear
2889
this.eventElements = [];
2897
if (CanvasRenderingContext2D) {
2898
CanvasRenderingContext2D.prototype.drawSvg = function(s, dx, dy, dw, dh) {
2899
canvg(this.canvas, s, {
2901
ignoreAnimation: true,
2902
ignoreDimensions: true,
2911
* @license Highstock JS v1.3.10 (2014-03-10)
2912
* CanVGRenderer Extension module
2914
* (c) 2011-2012 Torstein Honsi, Erik Olsson
2916
* License: www.highcharts.com/license
2920
/*global Highcharts */
2922
(function (Highcharts) { // encapsulate
2925
ABSOLUTE = 'absolute',
2926
RELATIVE = 'relative',
2928
VISIBLE = 'visible',
2930
css = Highcharts.css,
2931
CanVGRenderer = Highcharts.CanVGRenderer,
2932
SVGRenderer = Highcharts.SVGRenderer,
2933
extend = Highcharts.extend,
2934
merge = Highcharts.merge,
2935
addEvent = Highcharts.addEvent,
2936
createElement = Highcharts.createElement,
2937
discardElement = Highcharts.discardElement;
2939
// Extend CanVG renderer on demand, inherit from SVGRenderer
2940
extend(CanVGRenderer.prototype, SVGRenderer.prototype);
2942
// Add additional functionality:
2943
extend(CanVGRenderer.prototype, {
2944
create: function (chart, container, chartWidth, chartHeight) {
2945
this.setContainer(container, chartWidth, chartHeight);
2946
this.configure(chart);
2948
setContainer: function (container, chartWidth, chartHeight) {
2949
var containerStyle = container.style,
2950
containerParent = container.parentNode,
2951
containerLeft = containerStyle.left,
2952
containerTop = containerStyle.top,
2953
containerOffsetWidth = container.offsetWidth,
2954
containerOffsetHeight = container.offsetHeight,
2956
initialHiddenStyle = { visibility: HIDDEN, position: ABSOLUTE };
2958
this.init.apply(this, [container, chartWidth, chartHeight]);
2960
// add the canvas above it
2961
canvas = createElement('canvas', {
2962
width: containerOffsetWidth,
2963
height: containerOffsetHeight
2966
left: containerLeft,
2969
this.canvas = canvas;
2971
// Create the tooltip line and div, they are placed as siblings to
2972
// the container (and as direct childs to the div specified in the html page)
2973
this.ttLine = createElement(DIV, null, initialHiddenStyle, containerParent);
2974
this.ttDiv = createElement(DIV, null, initialHiddenStyle, containerParent);
2975
this.ttTimer = UNDEFINED;
2977
// Move away the svg node to a new div inside the container's parent so we can hide it.
2978
var hiddenSvg = createElement(DIV, {
2979
width: containerOffsetWidth,
2980
height: containerOffsetHeight
2983
left: containerLeft,
2985
}, containerParent);
2986
this.hiddenSvg = hiddenSvg;
2987
hiddenSvg.appendChild(this.box);
2991
* Configures the renderer with the chart. Attach a listener to the event tooltipRefresh.
2993
configure: function (chart) {
2994
var renderer = this,
2995
options = chart.options.tooltip,
2996
borderWidth = options.borderWidth,
2997
tooltipDiv = renderer.ttDiv,
2998
tooltipDivStyle = options.style,
2999
tooltipLine = renderer.ttLine,
3000
padding = parseInt(tooltipDivStyle.padding, 10);
3002
// Add border styling from options to the style
3003
tooltipDivStyle = merge(tooltipDivStyle, {
3004
padding: padding + PX,
3005
'background-color': options.backgroundColor,
3006
'border-style': 'solid',
3007
'border-width': borderWidth + PX,
3008
'border-radius': options.borderRadius + PX
3011
// Optionally add shadow
3012
if (options.shadow) {
3013
tooltipDivStyle = merge(tooltipDivStyle, {
3014
'box-shadow': '1px 1px 3px gray', // w3c
3015
'-webkit-box-shadow': '1px 1px 3px gray' // webkit
3018
css(tooltipDiv, tooltipDivStyle);
3020
// Set simple style on the line
3022
'border-left': '1px solid darkgray'
3025
// This event is triggered when a new tooltip should be shown
3026
addEvent(chart, 'tooltipRefresh', function (args) {
3027
var chartContainer = chart.container,
3028
offsetLeft = chartContainer.offsetLeft,
3029
offsetTop = chartContainer.offsetTop,
3032
// Set the content of the tooltip
3033
tooltipDiv.innerHTML = args.text;
3035
// Compute the best position for the tooltip based on the divs size and container size.
3036
position = chart.tooltip.getPosition(tooltipDiv.offsetWidth, tooltipDiv.offsetHeight, {plotX: args.x, plotY: args.y});
3039
visibility: VISIBLE,
3040
left: position.x + PX,
3041
top: position.y + PX,
3042
'border-color': args.borderColor
3045
// Position the tooltip line
3047
visibility: VISIBLE,
3048
left: offsetLeft + args.x + PX,
3049
top: offsetTop + chart.plotTop + PX,
3050
height: chart.plotHeight + PX
3053
// This timeout hides the tooltip after 3 seconds
3054
// First clear any existing timer
3055
if (renderer.ttTimer !== UNDEFINED) {
3056
clearTimeout(renderer.ttTimer);
3059
// Start a new timer that hides tooltip and line
3060
renderer.ttTimer = setTimeout(function () {
3061
css(tooltipDiv, { visibility: HIDDEN });
3062
css(tooltipLine, { visibility: HIDDEN });
3068
* Extend SVGRenderer.destroy to also destroy the elements added by CanVGRenderer.
3070
destroy: function () {
3071
var renderer = this;
3073
// Remove the canvas
3074
discardElement(renderer.canvas);
3077
if (renderer.ttTimer !== UNDEFINED) {
3078
clearTimeout(renderer.ttTimer);
3081
// Remove the divs for tooltip and line
3082
discardElement(renderer.ttLine);
3083
discardElement(renderer.ttDiv);
3084
discardElement(renderer.hiddenSvg);
3086
// Continue with base class
3087
return SVGRenderer.prototype.destroy.apply(renderer);
3091
* Take a color and return it if it's a string, do not make it a gradient even if it is a
3092
* gradient. Currently canvg cannot render gradients (turns out black),
3093
* see: http://code.google.com/p/canvg/issues/detail?id=104
3095
* @param {Object} color The color or config object
3097
color: function (color, elem, prop) {
3098
if (color && color.linearGradient) {
3099
// Pick the end color and forward to base implementation
3100
color = color.stops[color.stops.length - 1][1];
3102
return SVGRenderer.prototype.color.call(this, color, elem, prop);
3106
* Draws the SVG on the canvas or adds a draw invokation to the deferred list.
3109
var renderer = this;
3110
window.canvg(renderer.canvas, renderer.hiddenSvg.innerHTML);