UNPKG

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