1 /* 2 Copyright 2008-2016 3 Matthias Ehmann, 4 Michael Gerhaeuser, 5 Carsten Miller, 6 Bianca Valentin, 7 Alfred Wassermann, 8 Peter Wilfahrt 9 10 This file is part of JSXGraph. 11 12 JSXGraph is free software dual licensed under the GNU LGPL or MIT License. 13 14 You can redistribute it and/or modify it under the terms of the 15 16 * GNU Lesser General Public License as published by 17 the Free Software Foundation, either version 3 of the License, or 18 (at your option) any later version 19 OR 20 * MIT License: https://github.com/jsxgraph/jsxgraph/blob/master/LICENSE.MIT 21 22 JSXGraph is distributed in the hope that it will be useful, 23 but WITHOUT ANY WARRANTY; without even the implied warranty of 24 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 25 GNU Lesser General Public License for more details. 26 27 You should have received a copy of the GNU Lesser General Public License and 28 the MIT License along with JSXGraph. If not, see <http://www.gnu.org/licenses/> 29 and <http://opensource.org/licenses/MIT/>. 30 */ 31 32 /*global JXG: true, define: true, AMprocessNode: true, MathJax: true, document: true */ 33 /*jslint nomen: true, plusplus: true, newcap:true*/ 34 35 /* depends: 36 jxg 37 options 38 renderer/abstract 39 base/constants 40 utils/type 41 utils/env 42 utils/color 43 math/numerics 44 */ 45 46 define([ 47 'jxg', 'options', 'renderer/abstract', 'base/constants', 'utils/type', 'utils/env', 'utils/color', 'utils/base64', 'math/numerics' 48 ], function (JXG, Options, AbstractRenderer, Const, Type, Env, Color, Base64, Numerics) { 49 50 "use strict"; 51 52 /** 53 * Uses SVG to implement the rendering methods defined in {@link JXG.AbstractRenderer}. 54 * @class JXG.AbstractRenderer 55 * @augments JXG.AbstractRenderer 56 * @param {Node} container Reference to a DOM node containing the board. 57 * @param {Object} dim The dimensions of the board 58 * @param {Number} dim.width 59 * @param {Number} dim.height 60 * @see JXG.AbstractRenderer 61 */ 62 JXG.SVGRenderer = function (container, dim) { 63 var i; 64 65 // docstring in AbstractRenderer 66 this.type = 'svg'; 67 68 this.isIE = navigator.appVersion.indexOf("MSIE") !== -1 || navigator.userAgent.match(/Trident\//); 69 70 /** 71 * SVG root node 72 * @type Node 73 */ 74 this.svgRoot = null; 75 76 /** 77 * The SVG Namespace used in JSXGraph. 78 * @see http://www.w3.org/TR/SVG/ 79 * @type String 80 * @default http://www.w3.org/2000/svg 81 */ 82 this.svgNamespace = 'http://www.w3.org/2000/svg'; 83 84 /** 85 * The xlink namespace. This is used for images. 86 * @see http://www.w3.org/TR/xlink/ 87 * @type String 88 * @default http://www.w3.org/1999/xlink 89 */ 90 this.xlinkNamespace = 'http://www.w3.org/1999/xlink'; 91 92 // container is documented in AbstractRenderer 93 this.container = container; 94 95 // prepare the div container and the svg root node for use with JSXGraph 96 this.container.style.MozUserSelect = 'none'; 97 98 this.container.style.overflow = 'hidden'; 99 if (this.container.style.position === '') { 100 this.container.style.position = 'relative'; 101 } 102 103 this.svgRoot = this.container.ownerDocument.createElementNS(this.svgNamespace, "svg"); 104 this.svgRoot.style.overflow = 'hidden'; 105 106 this.resize(dim.width, dim.height); 107 108 //this.svgRoot.setAttributeNS(null, 'shape-rendering', 'crispEdge'); //'optimizeQuality'); //geometricPrecision'); 109 110 this.container.appendChild(this.svgRoot); 111 112 /** 113 * The <tt>defs</tt> element is a container element to reference reusable SVG elements. 114 * @type Node 115 * @see http://www.w3.org/TR/SVG/struct.html#DefsElement 116 */ 117 this.defs = this.container.ownerDocument.createElementNS(this.svgNamespace, 'defs'); 118 this.svgRoot.appendChild(this.defs); 119 120 /** 121 * Filters are used to apply shadows. 122 * @type Node 123 * @see http://www.w3.org/TR/SVG/filters.html#FilterElement 124 */ 125 this.filter = this.container.ownerDocument.createElementNS(this.svgNamespace, 'filter'); 126 this.filter.setAttributeNS(null, 'id', this.container.id + '_' + 'f1'); 127 /* 128 this.filter.setAttributeNS(null, 'x', '-100%'); 129 this.filter.setAttributeNS(null, 'y', '-100%'); 130 this.filter.setAttributeNS(null, 'width', '400%'); 131 this.filter.setAttributeNS(null, 'height', '400%'); 132 //this.filter.setAttributeNS(null, 'filterUnits', 'userSpaceOnUse'); 133 */ 134 this.filter.setAttributeNS(null, 'width', '300%'); 135 this.filter.setAttributeNS(null, 'height', '300%'); 136 this.filter.setAttributeNS(null, 'filterUnits', 'userSpaceOnUse'); 137 138 this.feOffset = this.container.ownerDocument.createElementNS(this.svgNamespace, 'feOffset'); 139 this.feOffset.setAttributeNS(null, 'result', 'offOut'); 140 this.feOffset.setAttributeNS(null, 'in', 'SourceAlpha'); 141 this.feOffset.setAttributeNS(null, 'dx', '5'); 142 this.feOffset.setAttributeNS(null, 'dy', '5'); 143 this.filter.appendChild(this.feOffset); 144 145 this.feGaussianBlur = this.container.ownerDocument.createElementNS(this.svgNamespace, 'feGaussianBlur'); 146 this.feGaussianBlur.setAttributeNS(null, 'result', 'blurOut'); 147 this.feGaussianBlur.setAttributeNS(null, 'in', 'offOut'); 148 this.feGaussianBlur.setAttributeNS(null, 'stdDeviation', '3'); 149 this.filter.appendChild(this.feGaussianBlur); 150 151 this.feBlend = this.container.ownerDocument.createElementNS(this.svgNamespace, 'feBlend'); 152 this.feBlend.setAttributeNS(null, 'in', 'SourceGraphic'); 153 this.feBlend.setAttributeNS(null, 'in2', 'blurOut'); 154 this.feBlend.setAttributeNS(null, 'mode', 'normal'); 155 this.filter.appendChild(this.feBlend); 156 157 this.defs.appendChild(this.filter); 158 159 /** 160 * JSXGraph uses a layer system to sort the elements on the board. This puts certain types of elements in front 161 * of other types of elements. For the order used see {@link JXG.Options.layer}. The number of layers is documented 162 * there, too. The higher the number, the "more on top" are the elements on this layer. 163 * @type Array 164 */ 165 this.layer = []; 166 for (i = 0; i < Options.layer.numlayers; i++) { 167 this.layer[i] = this.container.ownerDocument.createElementNS(this.svgNamespace, 'g'); 168 this.svgRoot.appendChild(this.layer[i]); 169 } 170 171 // already documented in JXG.AbstractRenderer 172 this.supportsForeignObject = document.implementation.hasFeature("www.http://w3.org/TR/SVG11/feature#Extensibility", "1.1"); 173 if (this.supportsForeignObject) { 174 this.foreignObjLayer = []; 175 for (i = 0; i < Options.layer.numlayers; i++) { 176 if (i === Options.layer.text || i === 0) { // 0 is for traces 177 this.foreignObjLayer[i] = this.container.ownerDocument.createElementNS(this.svgNamespace, 'foreignObject'); 178 179 this.foreignObjLayer[i].setAttribute("x",0); 180 this.foreignObjLayer[i].setAttribute("y",0); 181 this.foreignObjLayer[i].setAttribute("width","100%"); 182 this.foreignObjLayer[i].setAttribute("height","100%"); 183 this.layer[i].appendChild(this.foreignObjLayer[i]); 184 } 185 } 186 } 187 188 /** 189 * Defines dash patterns. Defined styles are: <ol> 190 * <li value="-1"> 2px dash, 2px space</li> 191 * <li> 5px dash, 5px space</li> 192 * <li> 10px dash, 10px space</li> 193 * <li> 20px dash, 20px space</li> 194 * <li> 20px dash, 10px space, 10px dash, 10px dash</li> 195 * <li> 20px dash, 5px space, 10px dash, 5px space</li></ol> 196 * @type Array 197 * @default ['2, 2', '5, 5', '10, 10', '20, 20', '20, 10, 10, 10', '20, 5, 10, 5'] 198 * @see http://www.w3.org/TR/SVG/painting.html#StrokeProperties 199 */ 200 this.dashArray = ['2, 2', '5, 5', '10, 10', '20, 20', '20, 10, 10, 10', '20, 5, 10, 5']; 201 }; 202 203 JXG.SVGRenderer.prototype = new AbstractRenderer(); 204 205 JXG.extend(JXG.SVGRenderer.prototype, /** @lends JXG.SVGRenderer.prototype */ { 206 207 /** 208 * Creates an arrow DOM node. Arrows are displayed in SVG with a <em>marker</em> tag. 209 * @private 210 * @param {JXG.GeometryElement} element A JSXGraph element, preferably one that can have an arrow attached. 211 * @param {String} [idAppendix=''] A string that is added to the node's id. 212 * @returns {Node} Reference to the node added to the DOM. 213 */ 214 _createArrowHead: function (element, idAppendix) { 215 var node2, node3, 216 id = element.id + 'Triangle', 217 s, d; 218 219 if (Type.exists(idAppendix)) { 220 id += idAppendix; 221 } 222 node2 = this.createPrim('marker', id); 223 224 node2.setAttributeNS(null, 'stroke', Type.evaluate(element.visProp.strokecolor)); 225 node2.setAttributeNS(null, 'stroke-opacity', Type.evaluate(element.visProp.strokeopacity)); 226 node2.setAttributeNS(null, 'fill', Type.evaluate(element.visProp.strokecolor)); 227 node2.setAttributeNS(null, 'fill-opacity', Type.evaluate(element.visProp.strokeopacity)); 228 node2.setAttributeNS(null, 'stroke-width', 0); // this is the stroke-width of the arrow head. 229 // Should be zero to make the positioning easy 230 231 node2.setAttributeNS(null, 'orient', 'auto'); 232 node2.setAttributeNS(null, 'markerUnits', 'strokeWidth'); // 'strokeWidth' 'userSpaceOnUse'); 233 234 /* 235 * Changes here are also necessary in _setArrowWidth() 236 */ 237 s = parseInt(element.visProp.strokewidth, 10); 238 //node2.setAttributeNS(null, 'viewBox', (-s) + ' ' + (-s) + ' ' + s * 12 + ' ' + s * 12); 239 node2.setAttributeNS(null, 'viewBox', (-s) + ' ' + (-s) + ' ' + s * 10 + ' ' + s * 10); 240 241 /* 242 The arrow head is an equilateral triangle with base length 10 and height 10. 243 This 10 units are scaled to strokeWidth*3 pixels or minimum 10 pixels. 244 See also abstractRenderer.updateLine() where the line path is shortened accordingly. 245 */ 246 d = Math.max(s * 3, 10); 247 node2.setAttributeNS(null, 'markerHeight', d); 248 node2.setAttributeNS(null, 'markerWidth', d); 249 250 node3 = this.container.ownerDocument.createElementNS(this.svgNamespace, 'path'); 251 252 if (idAppendix === 'End') { // First arrow 253 node2.setAttributeNS(null, 'refY', 5); 254 node2.setAttributeNS(null, 'refX', 10); 255 node3.setAttributeNS(null, 'd', 'M 10 0 L 0 5 L 10 10 z'); 256 } else { // Last arrow 257 node2.setAttributeNS(null, 'refY', 5); 258 node2.setAttributeNS(null, 'refX', 0); 259 node3.setAttributeNS(null, 'd', 'M 0 0 L 10 5 L 0 10 z'); 260 } 261 262 node2.appendChild(node3); 263 return node2; 264 }, 265 266 /** 267 * Updates color of an arrow DOM node. 268 * @param {Node} node The arrow node. 269 * @param {String} color Color value in a HTML compatible format, e.g. <tt>#00ff00</tt> or <tt>green</tt> for green. 270 * @param {Number} opacity 271 */ 272 _setArrowColor: function (node, color, opacity, parentNode) { 273 var s, d; 274 275 if (node) { 276 if (Type.isString(color)) { 277 node.setAttributeNS(null, 'stroke', color); 278 node.setAttributeNS(null, 'fill', color); 279 } 280 node.setAttributeNS(null, 'stroke-opacity', opacity); 281 node.setAttributeNS(null, 'fill-opacity', opacity); 282 283 if (this.isIE) { 284 parentNode.parentNode.insertBefore(parentNode, parentNode); 285 } 286 } 287 288 }, 289 290 /** 291 * Updates width of an arrow DOM node. 292 * @param {Node} node The arrow node. 293 * @param {Number} width 294 */ 295 _setArrowWidth: function (node, width, parentNode) { 296 var s, d; 297 298 if (node) { 299 // This is the stroke-width of the arrow head. 300 // Should be zero to make the positioning easy 301 node.setAttributeNS(null, 'stroke-width', 0); 302 303 // The next lines are important if the strokeWidth of the line is changed. 304 s = width; 305 node.setAttributeNS(null, 'viewBox', (-s) + ' ' + (-s) + ' ' + s * 10 + ' ' + s * 10); 306 d = Math.max(s * 3, 10); 307 308 node.setAttributeNS(null, 'markerHeight', d); 309 node.setAttributeNS(null, 'markerWidth', d); 310 311 if (this.isIE) { 312 parentNode.parentNode.insertBefore(parentNode, parentNode); 313 } 314 } 315 316 }, 317 318 /* ******************************** * 319 * This renderer does not need to 320 * override draw/update* methods 321 * since it provides draw/update*Prim 322 * methods except for some cases like 323 * internal texts or images. 324 * ******************************** */ 325 326 /* ************************** 327 * Lines 328 * **************************/ 329 330 // documented in AbstractRenderer 331 updateTicks: function (ticks) { 332 var i, c, node, x, y, 333 tickStr = '', 334 len = ticks.ticks.length; 335 336 for (i = 0; i < len; i++) { 337 c = ticks.ticks[i]; 338 x = c[0]; 339 y = c[1]; 340 341 if (Type.isNumber(x[0]) && Type.isNumber(x[1])) { 342 tickStr += "M " + (x[0]) + " " + (y[0]) + " L " + (x[1]) + " " + (y[1]) + " "; 343 } 344 } 345 346 node = ticks.rendNode; 347 348 if (!Type.exists(node)) { 349 node = this.createPrim('path', ticks.id); 350 this.appendChildPrim(node, ticks.visProp.layer); 351 ticks.rendNode = node; 352 } 353 354 node.setAttributeNS(null, 'stroke', ticks.visProp.strokecolor); 355 node.setAttributeNS(null, 'stroke-opacity', ticks.visProp.strokeopacity); 356 node.setAttributeNS(null, 'stroke-width', ticks.visProp.strokewidth); 357 this.updatePathPrim(node, tickStr, ticks.board); 358 }, 359 360 /* ************************** 361 * Text related stuff 362 * **************************/ 363 364 // already documented in JXG.AbstractRenderer 365 displayCopyright: function (str, fontsize) { 366 var node = this.createPrim('text', 'licenseText'), 367 t; 368 node.setAttributeNS(null, 'x', '20px'); 369 node.setAttributeNS(null, 'y', (2 + fontsize) + 'px'); 370 node.setAttributeNS(null, "style", "font-family:Arial,Helvetica,sans-serif; font-size:" + fontsize + "px; fill:#356AA0; opacity:0.3;"); 371 t = this.container.ownerDocument.createTextNode(str); 372 node.appendChild(t); 373 this.appendChildPrim(node, 0); 374 }, 375 376 // already documented in JXG.AbstractRenderer 377 drawInternalText: function (el) { 378 var node = this.createPrim('text', el.id); 379 380 node.setAttributeNS(null, "class", el.visProp.cssclass); 381 //node.setAttributeNS(null, "style", "alignment-baseline:middle"); // Not yet supported by Firefox 382 383 // Preserve spaces 384 node.setAttributeNS("http://www.w3.org/XML/1998/namespace", "space", "preserve"); 385 386 el.rendNodeText = this.container.ownerDocument.createTextNode(''); 387 node.appendChild(el.rendNodeText); 388 this.appendChildPrim(node, el.visProp.layer); 389 390 return node; 391 }, 392 393 // already documented in JXG.AbstractRenderer 394 updateInternalText: function (el) { 395 var content = el.plaintext, v; 396 397 // el.rendNode.setAttributeNS(null, "class", el.visProp.cssclass); 398 if (!isNaN(el.coords.scrCoords[1] + el.coords.scrCoords[2])) { 399 400 // Horizontal 401 v = el.coords.scrCoords[1]; 402 if (el.visPropOld.left !== (el.visProp.anchorx + v)) { 403 el.rendNode.setAttributeNS(null, 'x', v + 'px'); 404 405 if (el.visProp.anchorx === 'left') { 406 el.rendNode.setAttributeNS(null, 'text-anchor', 'start'); 407 } else if (el.visProp.anchorx === 'right') { 408 el.rendNode.setAttributeNS(null, 'text-anchor', 'end'); 409 } else if (el.visProp.anchorx === 'middle') { 410 el.rendNode.setAttributeNS(null, 'text-anchor', 'middle'); 411 } 412 el.visPropOld.left = el.visProp.anchorx + v; 413 } 414 415 // Vertical 416 v = el.coords.scrCoords[2]; 417 if (el.visPropOld.top !== (el.visProp.anchory + v)) { 418 el.rendNode.setAttributeNS(null, 'y', (v + this.vOffsetText * 0.5) + 'px'); 419 420 if (el.visProp.anchory === 'bottom') { 421 el.rendNode.setAttributeNS(null, 'dominant-baseline', 'text-after-edge'); 422 } else if (el.visProp.anchory === 'top') { 423 el.rendNode.setAttributeNS(null, 'dominant-baseline', 'text-before-edge'); 424 } else if (el.visProp.anchory === 'middle') { 425 el.rendNode.setAttributeNS(null, 'dominant-baseline', 'middle'); 426 } 427 el.visPropOld.top = el.visProp.anchory + v; 428 } 429 } 430 if (el.htmlStr !== content) { 431 el.rendNodeText.data = content; 432 el.htmlStr = content; 433 } 434 this.transformImage(el, el.transformations); 435 }, 436 437 /** 438 * Set color and opacity of internal texts. 439 * SVG needs its own version. 440 * @private 441 * @see JXG.AbstractRenderer#updateTextStyle 442 * @see JXG.AbstractRenderer#updateInternalTextStyle 443 */ 444 updateInternalTextStyle: function (element, strokeColor, strokeOpacity) { 445 this.setObjectFillColor(element, strokeColor, strokeOpacity); 446 }, 447 448 /* ************************** 449 * Image related stuff 450 * **************************/ 451 452 // already documented in JXG.AbstractRenderer 453 drawImage: function (el) { 454 var node = this.createPrim('image', el.id); 455 456 node.setAttributeNS(null, 'preserveAspectRatio', 'none'); 457 this.appendChildPrim(node, el.visProp.layer); 458 el.rendNode = node; 459 460 this.updateImage(el); 461 }, 462 463 // already documented in JXG.AbstractRenderer 464 transformImage: function (el, t) { 465 var s, m, 466 node = el.rendNode, 467 str = "", 468 len = t.length; 469 470 if (len > 0) { 471 m = this.joinTransforms(el, t); 472 s = [m[1][1], m[2][1], m[1][2], m[2][2], m[1][0], m[2][0]].join(','); 473 str += ' matrix(' + s + ') '; 474 node.setAttributeNS(null, 'transform', str); 475 } 476 }, 477 478 // already documented in JXG.AbstractRenderer 479 updateImageURL: function (el) { 480 var url = Type.evaluate(el.url); 481 482 el.rendNode.setAttributeNS(this.xlinkNamespace, 'xlink:href', url); 483 }, 484 485 // already documented in JXG.AbstractRenderer 486 updateImageStyle: function (el, doHighlight) { 487 var css = doHighlight ? el.visProp.highlightcssclass : el.visProp.cssclass; 488 489 el.rendNode.setAttributeNS(null, 'class', css); 490 }, 491 492 /* ************************** 493 * Render primitive objects 494 * **************************/ 495 496 // already documented in JXG.AbstractRenderer 497 appendChildPrim: function (node, level) { 498 if (!Type.exists(level)) { // trace nodes have level not set 499 level = 0; 500 } else if (level >= Options.layer.numlayers) { 501 level = Options.layer.numlayers - 1; 502 } 503 504 this.layer[level].appendChild(node); 505 506 return node; 507 }, 508 509 // already documented in JXG.AbstractRenderer 510 createPrim: function (type, id) { 511 var node = this.container.ownerDocument.createElementNS(this.svgNamespace, type); 512 node.setAttributeNS(null, 'id', this.container.id + '_' + id); 513 node.style.position = 'absolute'; 514 if (type === 'path') { 515 node.setAttributeNS(null, 'stroke-linecap', 'round'); 516 node.setAttributeNS(null, 'stroke-linejoin', 'round'); 517 } 518 return node; 519 }, 520 521 // already documented in JXG.AbstractRenderer 522 remove: function (shape) { 523 if (Type.exists(shape) && Type.exists(shape.parentNode)) { 524 shape.parentNode.removeChild(shape); 525 } 526 }, 527 528 // already documented in JXG.AbstractRenderer 529 makeArrows: function (el) { 530 var node2; 531 532 if (el.visPropOld.firstarrow === el.visProp.firstarrow && el.visPropOld.lastarrow === el.visProp.lastarrow) { 533 if (this.isIE && el.visProp.visible && (el.visProp.firstarrow || el.visProp.lastarrow)) { 534 el.rendNode.parentNode.insertBefore(el.rendNode, el.rendNode); 535 } 536 return; 537 } 538 539 if (el.visProp.firstarrow) { 540 node2 = el.rendNodeTriangleStart; 541 if (!Type.exists(node2)) { 542 node2 = this._createArrowHead(el, 'End'); 543 this.defs.appendChild(node2); 544 el.rendNodeTriangleStart = node2; 545 el.rendNode.setAttributeNS(null, 'marker-start', 'url(#' + this.container.id + '_' + el.id + 'TriangleEnd)'); 546 } else { 547 this.defs.appendChild(node2); 548 } 549 } else { 550 node2 = el.rendNodeTriangleStart; 551 if (Type.exists(node2)) { 552 this.remove(node2); 553 } 554 } 555 if (el.visProp.lastarrow) { 556 node2 = el.rendNodeTriangleEnd; 557 if (!Type.exists(node2)) { 558 node2 = this._createArrowHead(el, 'Start'); 559 this.defs.appendChild(node2); 560 el.rendNodeTriangleEnd = node2; 561 el.rendNode.setAttributeNS(null, 'marker-end', 'url(#' + this.container.id + '_' + el.id + 'TriangleStart)'); 562 } else { 563 this.defs.appendChild(node2); 564 } 565 } else { 566 node2 = el.rendNodeTriangleEnd; 567 if (Type.exists(node2)) { 568 this.remove(node2); 569 } 570 } 571 el.visPropOld.firstarrow = el.visProp.firstarrow; 572 el.visPropOld.lastarrow = el.visProp.lastarrow; 573 }, 574 575 // already documented in JXG.AbstractRenderer 576 updateEllipsePrim: function (node, x, y, rx, ry) { 577 var huge = 1000000; 578 579 huge = 200000; // IE 580 // webkit does not like huge values if the object is dashed 581 // iE doesn't like huge values above 216000 582 x = Math.abs(x) < huge ? x : huge * x / Math.abs(x); 583 y = Math.abs(y) < huge ? y : huge * y / Math.abs(y); 584 rx = Math.abs(rx) < huge ? rx : huge * rx / Math.abs(rx); 585 ry = Math.abs(ry) < huge ? ry : huge * ry / Math.abs(ry); 586 587 node.setAttributeNS(null, 'cx', x); 588 node.setAttributeNS(null, 'cy', y); 589 node.setAttributeNS(null, 'rx', Math.abs(rx)); 590 node.setAttributeNS(null, 'ry', Math.abs(ry)); 591 }, 592 593 // already documented in JXG.AbstractRenderer 594 updateLinePrim: function (node, p1x, p1y, p2x, p2y) { 595 var huge = 1000000; 596 597 huge = 200000; //IE 598 if (!isNaN(p1x + p1y + p2x + p2y)) { 599 // webkit does not like huge values if the object is dashed 600 // IE doesn't like huge values above 216000 601 p1x = Math.abs(p1x) < huge ? p1x : huge * p1x / Math.abs(p1x); 602 p1y = Math.abs(p1y) < huge ? p1y : huge * p1y / Math.abs(p1y); 603 p2x = Math.abs(p2x) < huge ? p2x : huge * p2x / Math.abs(p2x); 604 p2y = Math.abs(p2y) < huge ? p2y : huge * p2y / Math.abs(p2y); 605 606 node.setAttributeNS(null, 'x1', p1x); 607 node.setAttributeNS(null, 'y1', p1y); 608 node.setAttributeNS(null, 'x2', p2x); 609 node.setAttributeNS(null, 'y2', p2y); 610 } 611 }, 612 613 // already documented in JXG.AbstractRenderer 614 updatePathPrim: function (node, pointString) { 615 if (pointString === '') { 616 pointString = 'M 0 0'; 617 } 618 node.setAttributeNS(null, 'd', pointString); 619 }, 620 621 // already documented in JXG.AbstractRenderer 622 updatePathStringPoint: function (el, size, type) { 623 var s = '', 624 scr = el.coords.scrCoords, 625 sqrt32 = size * Math.sqrt(3) * 0.5, 626 s05 = size * 0.5; 627 628 if (type === 'x') { 629 s = ' M ' + (scr[1] - size) + ' ' + (scr[2] - size) + 630 ' L ' + (scr[1] + size) + ' ' + (scr[2] + size) + 631 ' M ' + (scr[1] + size) + ' ' + (scr[2] - size) + 632 ' L ' + (scr[1] - size) + ' ' + (scr[2] + size); 633 } else if (type === '+') { 634 s = ' M ' + (scr[1] - size) + ' ' + (scr[2]) + 635 ' L ' + (scr[1] + size) + ' ' + (scr[2]) + 636 ' M ' + (scr[1]) + ' ' + (scr[2] - size) + 637 ' L ' + (scr[1]) + ' ' + (scr[2] + size); 638 } else if (type === '<>') { 639 s = ' M ' + (scr[1] - size) + ' ' + (scr[2]) + 640 ' L ' + (scr[1]) + ' ' + (scr[2] + size) + 641 ' L ' + (scr[1] + size) + ' ' + (scr[2]) + 642 ' L ' + (scr[1]) + ' ' + (scr[2] - size) + ' Z '; 643 } else if (type === '^') { 644 s = ' M ' + (scr[1]) + ' ' + (scr[2] - size) + 645 ' L ' + (scr[1] - sqrt32) + ' ' + (scr[2] + s05) + 646 ' L ' + (scr[1] + sqrt32) + ' ' + (scr[2] + s05) + 647 ' Z '; // close path 648 } else if (type === 'v') { 649 s = ' M ' + (scr[1]) + ' ' + (scr[2] + size) + 650 ' L ' + (scr[1] - sqrt32) + ' ' + (scr[2] - s05) + 651 ' L ' + (scr[1] + sqrt32) + ' ' + (scr[2] - s05) + 652 ' Z '; 653 } else if (type === '>') { 654 s = ' M ' + (scr[1] + size) + ' ' + (scr[2]) + 655 ' L ' + (scr[1] - s05) + ' ' + (scr[2] - sqrt32) + 656 ' L ' + (scr[1] - s05) + ' ' + (scr[2] + sqrt32) + 657 ' Z '; 658 } else if (type === '<') { 659 s = ' M ' + (scr[1] - size) + ' ' + (scr[2]) + 660 ' L ' + (scr[1] + s05) + ' ' + (scr[2] - sqrt32) + 661 ' L ' + (scr[1] + s05) + ' ' + (scr[2] + sqrt32) + 662 ' Z '; 663 } 664 return s; 665 }, 666 667 // already documented in JXG.AbstractRenderer 668 updatePathStringPrim: function (el) { 669 var i, scr, len, 670 symbm = ' M ', 671 symbl = ' L ', 672 symbc = ' C ', 673 nextSymb = symbm, 674 maxSize = 5000.0, 675 pStr = ''; 676 // isNotPlot = (el.visProp.curvetype !== 'plot'); 677 678 if (el.numberPoints <= 0) { 679 return ''; 680 } 681 682 len = Math.min(el.points.length, el.numberPoints); 683 684 if (el.bezierDegree === 1) { 685 /* 686 if (isNotPlot && el.visProp.rdpsmoothing) { 687 el.points = Numerics.RamerDouglasPeucker(el.points, 0.5); 688 el.numberPoints = el.points.length; 689 } 690 */ 691 692 for (i = 0; i < len; i++) { 693 scr = el.points[i].scrCoords; 694 if (isNaN(scr[1]) || isNaN(scr[2])) { // PenUp 695 nextSymb = symbm; 696 } else { 697 // Chrome has problems with values being too far away. 698 scr[1] = Math.max(Math.min(scr[1], maxSize), -maxSize); 699 scr[2] = Math.max(Math.min(scr[2], maxSize), -maxSize); 700 701 // Attention: first coordinate may be inaccurate if far way 702 //pStr += [nextSymb, scr[1], ' ', scr[2]].join(''); 703 pStr += nextSymb + scr[1] + ' ' + scr[2]; // Seems to be faster now (webkit and firefox) 704 nextSymb = symbl; 705 } 706 } 707 } else if (el.bezierDegree === 3) { 708 i = 0; 709 while (i < len) { 710 scr = el.points[i].scrCoords; 711 if (isNaN(scr[1]) || isNaN(scr[2])) { // PenUp 712 nextSymb = symbm; 713 } else { 714 pStr += nextSymb + scr[1] + ' ' + scr[2]; 715 if (nextSymb === symbc) { 716 i += 1; 717 scr = el.points[i].scrCoords; 718 pStr += ' ' + scr[1] + ' ' + scr[2]; 719 i += 1; 720 scr = el.points[i].scrCoords; 721 pStr += ' ' + scr[1] + ' ' + scr[2]; 722 } 723 nextSymb = symbc; 724 } 725 i += 1; 726 } 727 } 728 return pStr; 729 }, 730 731 // already documented in JXG.AbstractRenderer 732 updatePathStringBezierPrim: function (el) { 733 var i, j, k, scr, lx, ly, len, 734 symbm = ' M ', 735 symbl = ' C ', 736 nextSymb = symbm, 737 maxSize = 5000.0, 738 pStr = '', 739 f = el.visProp.strokewidth, 740 isNoPlot = (el.visProp.curvetype !== 'plot'); 741 742 if (el.numberPoints <= 0) { 743 return ''; 744 } 745 746 if (isNoPlot && el.board.options.curve.RDPsmoothing) { 747 el.points = Numerics.RamerDouglasPeucker(el.points, 0.5); 748 } 749 750 len = Math.min(el.points.length, el.numberPoints); 751 for (j = 1; j < 3; j++) { 752 nextSymb = symbm; 753 for (i = 0; i < len; i++) { 754 scr = el.points[i].scrCoords; 755 756 if (isNaN(scr[1]) || isNaN(scr[2])) { // PenUp 757 nextSymb = symbm; 758 } else { 759 // Chrome has problems with values being too far away. 760 scr[1] = Math.max(Math.min(scr[1], maxSize), -maxSize); 761 scr[2] = Math.max(Math.min(scr[2], maxSize), -maxSize); 762 763 // Attention: first coordinate may be inaccurate if far way 764 if (nextSymb === symbm) { 765 //pStr += [nextSymb, scr[1], ' ', scr[2]].join(''); 766 pStr += nextSymb + scr[1] + ' ' + scr[2]; // Seems to be faster now (webkit and firefox) 767 } else { 768 k = 2 * j; 769 pStr += [nextSymb, 770 (lx + (scr[1] - lx) * 0.333 + f * (k * Math.random() - j)), ' ', 771 (ly + (scr[2] - ly) * 0.333 + f * (k * Math.random() - j)), ' ', 772 (lx + (scr[1] - lx) * 0.666 + f * (k * Math.random() - j)), ' ', 773 (ly + (scr[2] - ly) * 0.666 + f * (k * Math.random() - j)), ' ', 774 scr[1], ' ', scr[2]].join(''); 775 } 776 777 nextSymb = symbl; 778 lx = scr[1]; 779 ly = scr[2]; 780 } 781 } 782 } 783 return pStr; 784 }, 785 786 // already documented in JXG.AbstractRenderer 787 updatePolygonPrim: function (node, el) { 788 var i, 789 pStr = '', 790 scrCoords, 791 len = el.vertices.length; 792 793 node.setAttributeNS(null, 'stroke', 'none'); 794 795 for (i = 0; i < len - 1; i++) { 796 if (el.vertices[i].isReal) { 797 scrCoords = el.vertices[i].coords.scrCoords; 798 pStr = pStr + scrCoords[1] + "," + scrCoords[2]; 799 } else { 800 node.setAttributeNS(null, 'points', ''); 801 return; 802 } 803 804 if (i < len - 2) { 805 pStr += " "; 806 } 807 } 808 if (pStr.indexOf('NaN') === -1) { 809 node.setAttributeNS(null, 'points', pStr); 810 } 811 }, 812 813 // already documented in JXG.AbstractRenderer 814 updateRectPrim: function (node, x, y, w, h) { 815 node.setAttributeNS(null, 'x', x); 816 node.setAttributeNS(null, 'y', y); 817 node.setAttributeNS(null, 'width', w); 818 node.setAttributeNS(null, 'height', h); 819 }, 820 821 /* ************************** 822 * Set Attributes 823 * **************************/ 824 825 // documented in JXG.AbstractRenderer 826 setPropertyPrim: function (node, key, val) { 827 if (key === 'stroked') { 828 return; 829 } 830 node.setAttributeNS(null, key, val); 831 }, 832 833 // documented in JXG.AbstractRenderer 834 show: function (el) { 835 var node; 836 837 if (el && el.rendNode) { 838 node = el.rendNode; 839 node.setAttributeNS(null, 'display', 'inline'); 840 node.style.visibility = "inherit"; 841 } 842 }, 843 844 // documented in JXG.AbstractRenderer 845 hide: function (el) { 846 var node; 847 848 if (el && el.rendNode) { 849 node = el.rendNode; 850 node.setAttributeNS(null, 'display', 'none'); 851 node.style.visibility = "hidden"; 852 } 853 }, 854 855 // documented in JXG.AbstractRenderer 856 setBuffering: function (el, type) { 857 el.rendNode.setAttribute('buffered-rendering', type); 858 }, 859 860 // documented in JXG.AbstractRenderer 861 setDashStyle: function (el) { 862 var dashStyle = el.visProp.dash, node = el.rendNode; 863 864 if (el.visProp.dash > 0) { 865 node.setAttributeNS(null, 'stroke-dasharray', this.dashArray[dashStyle - 1]); 866 } else { 867 if (node.hasAttributeNS(null, 'stroke-dasharray')) { 868 node.removeAttributeNS(null, 'stroke-dasharray'); 869 } 870 } 871 }, 872 873 // documented in JXG.AbstractRenderer 874 setGradient: function (el) { 875 var fillNode = el.rendNode, col, op, 876 node, node2, node3, x1, x2, y1, y2; 877 878 op = Type.evaluate(el.visProp.fillopacity); 879 op = (op > 0) ? op : 0; 880 881 col = Type.evaluate(el.visProp.fillcolor); 882 883 if (el.visProp.gradient === 'linear') { 884 node = this.createPrim('linearGradient', el.id + '_gradient'); 885 x1 = '0%'; 886 x2 = '100%'; 887 y1 = '0%'; 888 y2 = '0%'; 889 890 node.setAttributeNS(null, 'x1', x1); 891 node.setAttributeNS(null, 'x2', x2); 892 node.setAttributeNS(null, 'y1', y1); 893 node.setAttributeNS(null, 'y2', y2); 894 node2 = this.createPrim('stop', el.id + '_gradient1'); 895 node2.setAttributeNS(null, 'offset', '0%'); 896 node2.setAttributeNS(null, 'style', 'stop-color:' + col + ';stop-opacity:' + op); 897 node3 = this.createPrim('stop', el.id + '_gradient2'); 898 node3.setAttributeNS(null, 'offset', '100%'); 899 node3.setAttributeNS(null, 'style', 'stop-color:' + el.visProp.gradientsecondcolor + ';stop-opacity:' + el.visProp.gradientsecondopacity); 900 node.appendChild(node2); 901 node.appendChild(node3); 902 this.defs.appendChild(node); 903 fillNode.setAttributeNS(null, 'style', 'fill:url(#' + this.container.id + '_' + el.id + '_gradient)'); 904 el.gradNode1 = node2; 905 el.gradNode2 = node3; 906 } else if (el.visProp.gradient === 'radial') { 907 node = this.createPrim('radialGradient', el.id + '_gradient'); 908 909 node.setAttributeNS(null, 'cx', '50%'); 910 node.setAttributeNS(null, 'cy', '50%'); 911 node.setAttributeNS(null, 'r', '50%'); 912 node.setAttributeNS(null, 'fx', el.visProp.gradientpositionx * 100 + '%'); 913 node.setAttributeNS(null, 'fy', el.visProp.gradientpositiony * 100 + '%'); 914 915 node2 = this.createPrim('stop', el.id + '_gradient1'); 916 node2.setAttributeNS(null, 'offset', '0%'); 917 node2.setAttributeNS(null, 'style', 'stop-color:' + el.visProp.gradientsecondcolor + ';stop-opacity:' + el.visProp.gradientsecondopacity); 918 node3 = this.createPrim('stop', el.id + '_gradient2'); 919 node3.setAttributeNS(null, 'offset', '100%'); 920 node3.setAttributeNS(null, 'style', 'stop-color:' + col + ';stop-opacity:' + op); 921 922 node.appendChild(node2); 923 node.appendChild(node3); 924 this.defs.appendChild(node); 925 fillNode.setAttributeNS(null, 'style', 'fill:url(#' + this.container.id + '_' + el.id + '_gradient)'); 926 el.gradNode1 = node2; 927 el.gradNode2 = node3; 928 } else { 929 fillNode.removeAttributeNS(null, 'style'); 930 } 931 }, 932 933 // documented in JXG.AbstractRenderer 934 updateGradient: function (el) { 935 var col, op, 936 node2 = el.gradNode1, 937 node3 = el.gradNode2; 938 939 if (!Type.exists(node2) || !Type.exists(node3)) { 940 return; 941 } 942 943 op = Type.evaluate(el.visProp.fillopacity); 944 op = (op > 0) ? op : 0; 945 946 col = Type.evaluate(el.visProp.fillcolor); 947 948 if (el.visProp.gradient === 'linear') { 949 node2.setAttributeNS(null, 'style', 'stop-color:' + col + ';stop-opacity:' + op); 950 node3.setAttributeNS(null, 'style', 'stop-color:' + el.visProp.gradientsecondcolor + ';stop-opacity:' + el.visProp.gradientsecondopacity); 951 } else if (el.visProp.gradient === 'radial') { 952 node2.setAttributeNS(null, 'style', 'stop-color:' + el.visProp.gradientsecondcolor + ';stop-opacity:' + el.visProp.gradientsecondopacity); 953 node3.setAttributeNS(null, 'style', 'stop-color:' + col + ';stop-opacity:' + op); 954 } 955 }, 956 957 // documented in JXG.AbstractRenderer 958 setObjectFillColor: function (el, color, opacity, rendNode) { 959 var node, c, rgbo, oo, 960 rgba = Type.evaluate(color), 961 o = Type.evaluate(opacity); 962 963 o = (o > 0) ? o : 0; 964 965 if (el.visPropOld.fillcolor === rgba && el.visPropOld.fillopacity === o) { 966 return; 967 } 968 969 if (Type.exists(rgba) && rgba !== false) { 970 if (rgba.length !== 9) { // RGB, not RGBA 971 c = rgba; 972 oo = o; 973 } else { // True RGBA, not RGB 974 rgbo = Color.rgba2rgbo(rgba); 975 c = rgbo[0]; 976 oo = o * rgbo[1]; 977 } 978 979 if (rendNode === undefined) { 980 node = el.rendNode; 981 } else { 982 node = rendNode; 983 } 984 985 if (c !== 'none') { 986 node.setAttributeNS(null, 'fill', c); 987 } 988 989 if (el.type === JXG.OBJECT_TYPE_IMAGE) { 990 node.setAttributeNS(null, 'opacity', oo); 991 //node.style['opacity'] = oo; // This would overwrite values set by CSS class. 992 } else { 993 if (c === 'none') { // This is don only for non-images 994 // because images have no fill color. 995 oo = 0; 996 } 997 node.setAttributeNS(null, 'fill-opacity', oo); 998 } 999 1000 if (Type.exists(el.visProp.gradient)) { 1001 this.updateGradient(el); 1002 } 1003 } 1004 el.visPropOld.fillcolor = rgba; 1005 el.visPropOld.fillopacity = o; 1006 }, 1007 1008 // documented in JXG.AbstractRenderer 1009 setObjectStrokeColor: function (el, color, opacity) { 1010 var rgba = Type.evaluate(color), c, rgbo, 1011 o = Type.evaluate(opacity), oo, 1012 node; 1013 1014 o = (o > 0) ? o : 0; 1015 1016 if (el.visPropOld.strokecolor === rgba && el.visPropOld.strokeopacity === o) { 1017 return; 1018 } 1019 1020 if (Type.exists(rgba) && rgba !== false) { 1021 if (rgba.length !== 9) { // RGB, not RGBA 1022 c = rgba; 1023 oo = o; 1024 } else { // True RGBA, not RGB 1025 rgbo = Color.rgba2rgbo(rgba); 1026 c = rgbo[0]; 1027 oo = o * rgbo[1]; 1028 } 1029 1030 node = el.rendNode; 1031 1032 if (el.elementClass === Const.OBJECT_CLASS_TEXT) { 1033 if (el.visProp.display === 'html') { 1034 node.style.color = c; 1035 node.style.opacity = oo; 1036 } else { 1037 node.setAttributeNS(null, "style", "fill:" + c); 1038 node.setAttributeNS(null, "style", "fill-opacity:" + oo); 1039 } 1040 } else { 1041 node.setAttributeNS(null, 'stroke', c); 1042 node.setAttributeNS(null, 'stroke-opacity', oo); 1043 } 1044 1045 if (el.type === Const.OBJECT_TYPE_ARROW) { 1046 this._setArrowColor(el.rendNodeTriangle, c, oo, el.rendNode); 1047 } else if (el.elementClass === Const.OBJECT_CLASS_CURVE || 1048 el.elementClass === Const.OBJECT_CLASS_LINE) { 1049 if (el.visProp.firstarrow) { 1050 this._setArrowColor(el.rendNodeTriangleStart, c, oo, el.rendNode); 1051 } 1052 1053 if (el.visProp.lastarrow) { 1054 this._setArrowColor(el.rendNodeTriangleEnd, c, oo, el.rendNode); 1055 } 1056 } 1057 } 1058 1059 el.visPropOld.strokecolor = rgba; 1060 el.visPropOld.strokeopacity = o; 1061 }, 1062 1063 // documented in JXG.AbstractRenderer 1064 setObjectStrokeWidth: function (el, width) { 1065 var node, 1066 w = Type.evaluate(width), 1067 rgba, c, rgbo, o, oo; 1068 1069 if (isNaN(w) || el.visPropOld.strokewidth === w) { 1070 return; 1071 } 1072 1073 node = el.rendNode; 1074 this.setPropertyPrim(node, 'stroked', 'true'); 1075 if (Type.exists(w)) { 1076 this.setPropertyPrim(node, 'stroke-width', w + 'px'); 1077 1078 if (el.type === Const.OBJECT_TYPE_ARROW) { 1079 this._setArrowWidth(el.rendNodeTriangle, w, el.rendNode); 1080 } else if (el.elementClass === Const.OBJECT_CLASS_CURVE || 1081 el.elementClass === Const.OBJECT_CLASS_LINE) { 1082 if (el.visProp.firstarrow) { 1083 this._setArrowWidth(el.rendNodeTriangleStart, w, el.rendNode); 1084 } 1085 1086 if (el.visProp.lastarrow) { 1087 this._setArrowWidth(el.rendNodeTriangleEnd, w, el.rendNode); 1088 } 1089 } 1090 } 1091 el.visPropOld.strokewidth = w; 1092 }, 1093 1094 // documented in JXG.AbstractRenderer 1095 setShadow: function (el) { 1096 if (el.visPropOld.shadow === el.visProp.shadow) { 1097 return; 1098 } 1099 1100 if (Type.exists(el.rendNode)) { 1101 if (el.visProp.shadow) { 1102 el.rendNode.setAttributeNS(null, 'filter', 'url(#' + this.container.id + '_' + 'f1)'); 1103 } else { 1104 el.rendNode.removeAttributeNS(null, 'filter'); 1105 } 1106 } 1107 el.visPropOld.shadow = el.visProp.shadow; 1108 }, 1109 1110 /* ************************** 1111 * renderer control 1112 * **************************/ 1113 1114 // documented in JXG.AbstractRenderer 1115 suspendRedraw: function () { 1116 // It seems to be important for the Linux version of firefox 1117 //this.suspendHandle = this.svgRoot.suspendRedraw(10000); 1118 }, 1119 1120 // documented in JXG.AbstractRenderer 1121 unsuspendRedraw: function () { 1122 //this.svgRoot.unsuspendRedraw(this.suspendHandle); 1123 //this.svgRoot.unsuspendRedrawAll(); 1124 //this.svgRoot.forceRedraw(); 1125 }, 1126 1127 // documented in AbstractRenderer 1128 resize: function (w, h) { 1129 this.svgRoot.style.width = parseFloat(w) + 'px'; 1130 this.svgRoot.style.height = parseFloat(h) + 'px'; 1131 this.svgRoot.setAttribute("width", parseFloat(w)); 1132 this.svgRoot.setAttribute("height", parseFloat(h)); 1133 }, 1134 1135 // documented in JXG.AbstractRenderer 1136 createTouchpoints: function (n) { 1137 var i, na1, na2, node; 1138 this.touchpoints = []; 1139 for (i = 0; i < n; i++) { 1140 na1 = 'touchpoint1_' + i; 1141 node = this.createPrim('path', na1); 1142 this.appendChildPrim(node, 19); 1143 node.setAttributeNS(null, 'd', 'M 0 0'); 1144 this.touchpoints.push(node); 1145 1146 this.setPropertyPrim(node, 'stroked', 'true'); 1147 this.setPropertyPrim(node, 'stroke-width', '1px'); 1148 node.setAttributeNS(null, 'stroke', '#000000'); 1149 node.setAttributeNS(null, 'stroke-opacity', 1.0); 1150 node.setAttributeNS(null, 'display', 'none'); 1151 1152 na2 = 'touchpoint2_' + i; 1153 node = this.createPrim('ellipse', na2); 1154 this.appendChildPrim(node, 19); 1155 this.updateEllipsePrim(node, 0, 0, 0, 0); 1156 this.touchpoints.push(node); 1157 1158 this.setPropertyPrim(node, 'stroked', 'true'); 1159 this.setPropertyPrim(node, 'stroke-width', '1px'); 1160 node.setAttributeNS(null, 'stroke', '#000000'); 1161 node.setAttributeNS(null, 'stroke-opacity', 1.0); 1162 node.setAttributeNS(null, 'fill', '#ffffff'); 1163 node.setAttributeNS(null, 'fill-opacity', 0.0); 1164 1165 node.setAttributeNS(null, 'display', 'none'); 1166 } 1167 }, 1168 1169 // documented in JXG.AbstractRenderer 1170 showTouchpoint: function (i) { 1171 if (this.touchpoints && i >= 0 && 2 * i < this.touchpoints.length) { 1172 this.touchpoints[2 * i].setAttributeNS(null, 'display', 'inline'); 1173 this.touchpoints[2 * i + 1].setAttributeNS(null, 'display', 'inline'); 1174 } 1175 }, 1176 1177 // documented in JXG.AbstractRenderer 1178 hideTouchpoint: function (i) { 1179 if (this.touchpoints && i >= 0 && 2 * i < this.touchpoints.length) { 1180 this.touchpoints[2 * i].setAttributeNS(null, 'display', 'none'); 1181 this.touchpoints[2 * i + 1].setAttributeNS(null, 'display', 'none'); 1182 } 1183 }, 1184 1185 // documented in JXG.AbstractRenderer 1186 updateTouchpoint: function (i, pos) { 1187 var x, y, 1188 d = 37; 1189 1190 if (this.touchpoints && i >= 0 && 2 * i < this.touchpoints.length) { 1191 x = pos[0]; 1192 y = pos[1]; 1193 1194 this.touchpoints[2 * i].setAttributeNS(null, 'd', 'M ' + (x - d) + ' ' + y + ' ' + 1195 'L ' + (x + d) + ' ' + y + ' ' + 1196 'M ' + x + ' ' + (y - d) + ' ' + 1197 'L ' + x + ' ' + (y + d)); 1198 this.updateEllipsePrim(this.touchpoints[2 * i + 1], pos[0], pos[1], 25, 25); 1199 } 1200 }, 1201 1202 /** 1203 * Convert the SVG construction into an HTML canvas image. 1204 * This works for all SVG supporting browsers. 1205 * For IE it works from version 9. 1206 * But HTML texts are ignored on IE. The drawing is done with a delay of 1207 * 200 ms. Otherwise there are problems with IE. 1208 * 1209 * @param {String} canvasId Id of an HTML canvas element 1210 * @returns {Object} the svg renderer object. 1211 * 1212 * @example 1213 * board.renderer.dumpToCanvas('canvas'); 1214 */ 1215 dumpToCanvas: function(canvasId) { 1216 var svgRoot = this.svgRoot, 1217 btoa = window.btoa || Base64.encode, 1218 svg, tmpImg, cv, ctx; 1219 1220 svgRoot.setAttribute("xmlns", "http://www.w3.org/2000/svg"); 1221 svg = new XMLSerializer().serializeToString(svgRoot); 1222 1223 // In IE we have to remove the namespace again. 1224 if ((svg.match(/xmlns=\"http:\/\/www.w3.org\/2000\/svg\"/g) || []).length > 1) { 1225 svg = svg.replace(/xmlns=\"http:\/\/www.w3.org\/2000\/svg\"/, ''); 1226 } 1227 1228 cv = document.getElementById(canvasId); 1229 ctx = cv.getContext("2d"); 1230 1231 tmpImg = new Image(); 1232 tmpImg.onload = function () { 1233 // IE needs a pause... 1234 setTimeout(function(){ 1235 cv.width = cv.width; 1236 ctx.drawImage(tmpImg, 0, 0); 1237 }, 200); 1238 }; 1239 tmpImg.src = 'data:image/svg+xml;base64,' + btoa(unescape(encodeURIComponent(svg))); 1240 1241 return this; 1242 } 1243 1244 }); 1245 1246 return JXG.SVGRenderer; 1247 }); 1248