You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

3113 lines
98 KiB

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