1 /* 2 Copyright 2008-2021 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/color', 'utils/base64', 'math/numerics' 48 ], function (JXG, Options, AbstractRenderer, Const, Type, 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.SVGRenderer 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 this.container.style.userSelect = 'none'; 98 99 this.container.style.overflow = 'hidden'; 100 if (this.container.style.position === '') { 101 this.container.style.position = 'relative'; 102 } 103 104 this.svgRoot = this.container.ownerDocument.createElementNS(this.svgNamespace, "svg"); 105 this.svgRoot.style.overflow = 'hidden'; 106 this.svgRoot.style.display = 'block'; 107 108 this.resize(dim.width, dim.height); 109 110 //this.svgRoot.setAttributeNS(null, 'shape-rendering', 'crispEdge'); //'optimizeQuality'); //geometricPrecision'); 111 112 this.container.appendChild(this.svgRoot); 113 114 /** 115 * The <tt>defs</tt> element is a container element to reference reusable SVG elements. 116 * @type Node 117 * @see http://www.w3.org/TR/SVG/struct.html#DefsElement 118 */ 119 this.defs = this.container.ownerDocument.createElementNS(this.svgNamespace, 'defs'); 120 this.svgRoot.appendChild(this.defs); 121 122 /** 123 * Filters are used to apply shadows. 124 * @type Node 125 * @see http://www.w3.org/TR/SVG/filters.html#FilterElement 126 */ 127 this.filter = this.container.ownerDocument.createElementNS(this.svgNamespace, 'filter'); 128 this.filter.setAttributeNS(null, 'id', this.container.id + '_' + 'f1'); 129 /* 130 this.filter.setAttributeNS(null, 'x', '-100%'); 131 this.filter.setAttributeNS(null, 'y', '-100%'); 132 this.filter.setAttributeNS(null, 'width', '400%'); 133 this.filter.setAttributeNS(null, 'height', '400%'); 134 //this.filter.setAttributeNS(null, 'filterUnits', 'userSpaceOnUse'); 135 */ 136 this.filter.setAttributeNS(null, 'width', '300%'); 137 this.filter.setAttributeNS(null, 'height', '300%'); 138 this.filter.setAttributeNS(null, 'filterUnits', 'userSpaceOnUse'); 139 140 this.feOffset = this.container.ownerDocument.createElementNS(this.svgNamespace, 'feOffset'); 141 this.feOffset.setAttributeNS(null, 'result', 'offOut'); 142 this.feOffset.setAttributeNS(null, 'in', 'SourceAlpha'); 143 this.feOffset.setAttributeNS(null, 'dx', '5'); 144 this.feOffset.setAttributeNS(null, 'dy', '5'); 145 this.filter.appendChild(this.feOffset); 146 147 this.feGaussianBlur = this.container.ownerDocument.createElementNS(this.svgNamespace, 'feGaussianBlur'); 148 this.feGaussianBlur.setAttributeNS(null, 'result', 'blurOut'); 149 this.feGaussianBlur.setAttributeNS(null, 'in', 'offOut'); 150 this.feGaussianBlur.setAttributeNS(null, 'stdDeviation', '3'); 151 this.filter.appendChild(this.feGaussianBlur); 152 153 this.feBlend = this.container.ownerDocument.createElementNS(this.svgNamespace, 'feBlend'); 154 this.feBlend.setAttributeNS(null, 'in', 'SourceGraphic'); 155 this.feBlend.setAttributeNS(null, 'in2', 'blurOut'); 156 this.feBlend.setAttributeNS(null, 'mode', 'normal'); 157 this.filter.appendChild(this.feBlend); 158 159 this.defs.appendChild(this.filter); 160 161 /** 162 * JSXGraph uses a layer system to sort the elements on the board. This puts certain types of elements in front 163 * of other types of elements. For the order used see {@link JXG.Options.layer}. The number of layers is documented 164 * there, too. The higher the number, the "more on top" are the elements on this layer. 165 * @type Array 166 */ 167 this.layer = []; 168 for (i = 0; i < Options.layer.numlayers; i++) { 169 this.layer[i] = this.container.ownerDocument.createElementNS(this.svgNamespace, 'g'); 170 this.svgRoot.appendChild(this.layer[i]); 171 } 172 173 // Already documented in JXG.AbstractRenderer 174 this.supportsForeignObject = document.implementation.hasFeature("http://w3.org/TR/SVG11/feature#Extensibility", "1.1"); 175 176 if (this.supportsForeignObject) { 177 this.foreignObjLayer = this.container.ownerDocument.createElementNS(this.svgNamespace, 'foreignObject'); 178 this.foreignObjLayer.setAttribute("display", "none"); 179 this.foreignObjLayer.setAttribute("x", 0); 180 this.foreignObjLayer.setAttribute("y", 0); 181 this.foreignObjLayer.setAttribute("width", "100%"); 182 this.foreignObjLayer.setAttribute("height", "100%"); 183 this.foreignObjLayer.setAttribute('id', this.container.id + '_foreignObj'); 184 this.svgRoot.appendChild(this.foreignObjLayer); 185 } 186 187 /** 188 * Defines dash patterns. Defined styles are: <ol> 189 * <li value="-1"> 2px dash, 2px space</li> 190 * <li> 5px dash, 5px space</li> 191 * <li> 10px dash, 10px space</li> 192 * <li> 20px dash, 20px space</li> 193 * <li> 20px dash, 10px space, 10px dash, 10px dash</li> 194 * <li> 20px dash, 5px space, 10px dash, 5px space</li></ol> 195 * @type Array 196 * @default ['2, 2', '5, 5', '10, 10', '20, 20', '20, 10, 10, 10', '20, 5, 10, 5'] 197 * @see http://www.w3.org/TR/SVG/painting.html#StrokeProperties 198 */ 199 this.dashArray = ['2, 2', '5, 5', '10, 10', '20, 20', '20, 10, 10, 10', '20, 5, 10, 5']; 200 }; 201 202 JXG.SVGRenderer.prototype = new AbstractRenderer(); 203 204 JXG.extend(JXG.SVGRenderer.prototype, /** @lends JXG.SVGRenderer.prototype */ { 205 206 /** 207 * Creates an arrow DOM node. Arrows are displayed in SVG with a <em>marker</em> tag. 208 * @private 209 * @param {JXG.GeometryElement} el A JSXGraph element, preferably one that can have an arrow attached. 210 * @param {String} [idAppendix=''] A string that is added to the node's id. 211 * @returns {Node} Reference to the node added to the DOM. 212 */ 213 _createArrowHead: function (el, idAppendix, type) { 214 var node2, node3, 215 id = el.id + 'Triangle', 216 //type = null, 217 v, h; 218 219 if (Type.exists(idAppendix)) { 220 id += idAppendix; 221 } 222 node2 = this.createPrim('marker', id); 223 224 node2.setAttributeNS(null, 'stroke', Type.evaluate(el.visProp.strokecolor)); 225 node2.setAttributeNS(null, 'stroke-opacity', Type.evaluate(el.visProp.strokeopacity)); 226 node2.setAttributeNS(null, 'fill', Type.evaluate(el.visProp.strokecolor)); 227 node2.setAttributeNS(null, 'fill-opacity', Type.evaluate(el.visProp.strokeopacity)); 228 node2.setAttributeNS(null, 'stroke-width', 0); // this is the stroke-width of the arrow head. 229 // Should be zero to simplify the calculations 230 231 node2.setAttributeNS(null, 'orient', 'auto'); 232 node2.setAttributeNS(null, 'markerUnits', 'strokeWidth'); // 'strokeWidth' 'userSpaceOnUse'); 233 234 /* 235 Types 1, 2: 236 The arrow head is an isosceles triangle with base length 10 and height 10. 237 238 Type 3: 239 A rectangle 240 241 Types 4, 5, 6: 242 Defined by Bezier curves from mp_arrowheads.html 243 244 In any case but type 3 the arrow head is 10 units long, 245 type 3 is 10 unitsb high. 246 These 10 units are scaled to strokeWidth * arrowSize pixels, see 247 this._setArrowWidth(). 248 249 See also abstractRenderer.updateLine() where the line path is shortened accordingly. 250 251 Changes here are also necessary in setArrowWidth(). 252 253 So far, lines with arrow heads are shortenend to avoid overlapping of 254 arrow head and line. This is not the case for curves, yet. 255 Therefore, the offset refX has to be adapted to the path type. 256 */ 257 node3 = this.container.ownerDocument.createElementNS(this.svgNamespace, 'path'); 258 h = 5; 259 if (idAppendix === 'End') { 260 // First arrow 261 //type = a.typeFirst; 262 // if (JXG.exists(ev_fa.type)) { 263 // type = Type.evaluate(ev_fa.type); 264 // } 265 266 v = 0; 267 if (type === 2) { 268 node3.setAttributeNS(null, 'd', 'M 10,0 L 0,5 L 10,10 L 5,5 z'); 269 } else if (type === 3) { 270 node3.setAttributeNS(null, 'd', 'M 0,0 L 3.33,0 L 3.33,10 L 0,10 z'); 271 } else if (type === 4) { 272 // insetRatio:0.8 tipAngle:45 wingCurve:15 tailCurve:0 273 h = 3.31; 274 node3.setAttributeNS(null, 'd', 'M 0.00,3.31 C 3.53,3.84 7.13,4.50 10.00,6.63 C 9.33,5.52 8.67,4.42 8.00,3.31 C 8.67,2.21 9.33,1.10 10.00,0.00 C 7.13,2.13 3.53,2.79 0.00,3.31'); 275 } else if (type === 5) { 276 // insetRatio:0.9 tipAngle:40 wingCurve:5 tailCurve:15 277 h = 3.28; 278 node3.setAttributeNS(null, 'd', 'M 0.00,3.28 C 3.39,4.19 6.81,5.07 10.00,6.55 C 9.38,5.56 9.00,4.44 9.00,3.28 C 9.00,2.11 9.38,0.99 10.00,0.00 C 6.81,1.49 3.39,2.37 0.00,3.28'); 279 } else if (type === 6) { 280 // insetRatio:0.9 tipAngle:35 wingCurve:5 tailCurve:0 281 h = 2.84; 282 node3.setAttributeNS(null, 'd', 'M 0.00,2.84 C 3.39,3.59 6.79,4.35 10.00,5.68 C 9.67,4.73 9.33,3.78 9.00,2.84 C 9.33,1.89 9.67,0.95 10.00,0.00 C 6.79,1.33 3.39,2.09 0.00,2.84'); 283 } else if (type === 7) { 284 // insetRatio:0.9 tipAngle:60 wingCurve:30 tailCurve:0 285 h = 5.20; 286 node3.setAttributeNS(null, 'd', 'M 0.00,5.20 C 4.04,5.20 7.99,6.92 10.00,10.39 M 10.00,0.00 C 7.99,3.47 4.04,5.20 0.00,5.20'); 287 } else { 288 // type == 1 or > 6 289 node3.setAttributeNS(null, 'd', 'M 10,0 L 0,5 L 10,10 z'); 290 } 291 if (/*!Type.exists(el.rendNode.getTotalLength) && */el.elementClass === Const.OBJECT_CLASS_LINE) { 292 if (type === 2) { 293 v = 4.9; 294 } else if (type === 3) { 295 v = 3.3; 296 } else if (type === 4 || type === 5 || type === 6) { 297 v = 6.66; 298 } else if (type === 7) { 299 v = 0.0; 300 } else { 301 v = 10.0; 302 } 303 } 304 } else { 305 // Last arrow 306 // if (JXG.exists(ev_la.type)) { 307 // type = Type.evaluate(ev_la.type); 308 // } 309 //type = a.typeLast; 310 311 v = 10.0; 312 if (type === 2) { 313 node3.setAttributeNS(null, 'd', 'M 0,0 L 10,5 L 0,10 L 5,5 z'); 314 } else if (type === 3) { 315 v = 3.3; 316 node3.setAttributeNS(null, 'd', 'M 0,0 L 3.33,0 L 3.33,10 L 0,10 z'); 317 } else if (type === 4) { 318 // insetRatio:0.8 tipAngle:45 wingCurve:15 tailCurve:0 319 h = 3.31; 320 node3.setAttributeNS(null, 'd', 'M 10.00,3.31 C 6.47,3.84 2.87,4.50 0.00,6.63 C 0.67,5.52 1.33,4.42 2.00,3.31 C 1.33,2.21 0.67,1.10 0.00,0.00 C 2.87,2.13 6.47,2.79 10.00,3.31'); 321 } else if (type === 5) { 322 // insetRatio:0.9 tipAngle:40 wingCurve:5 tailCurve:15 323 h = 3.28; 324 node3.setAttributeNS(null, 'd', 'M 10.00,3.28 C 6.61,4.19 3.19,5.07 0.00,6.55 C 0.62,5.56 1.00,4.44 1.00,3.28 C 1.00,2.11 0.62,0.99 0.00,0.00 C 3.19,1.49 6.61,2.37 10.00,3.28'); 325 } else if (type === 6) { 326 // insetRatio:0.9 tipAngle:35 wingCurve:5 tailCurve:0 327 h = 2.84; 328 node3.setAttributeNS(null, 'd', 'M 10.00,2.84 C 6.61,3.59 3.21,4.35 0.00,5.68 C 0.33,4.73 0.67,3.78 1.00,2.84 C 0.67,1.89 0.33,0.95 0.00,0.00 C 3.21,1.33 6.61,2.09 10.00,2.84'); 329 } else if (type === 7) { 330 // insetRatio:0.9 tipAngle:60 wingCurve:30 tailCurve:0 331 h = 5.20; 332 node3.setAttributeNS(null, 'd', 'M 10.00,5.20 C 5.96,5.20 2.01,6.92 0.00,10.39 M 0.00,0.00 C 2.01,3.47 5.96,5.20 10.00,5.20'); 333 } else { 334 // type == 1 or > 6 335 node3.setAttributeNS(null, 'd', 'M 0,0 L 10,5 L 0,10 z'); 336 } 337 if (/*!Type.exists(el.rendNode.getTotalLength) &&*/ el.elementClass === Const.OBJECT_CLASS_LINE) { 338 if (type === 2) { 339 v = 5.1; 340 } else if (type === 3) { 341 v = 0.02; 342 } else if (type === 4 || type === 5 || type === 6) { 343 v = 3.33; 344 } else if (type === 7) { 345 v = 10.0; 346 } else { 347 v = 0.05; 348 } 349 } 350 } 351 if (type === 7) { 352 node2.setAttributeNS(null, 'fill', 'none'); 353 node2.setAttributeNS(null, 'stroke-width', 1); // this is the stroke-width of the arrow head. 354 } 355 node2.setAttributeNS(null, 'refY', h); 356 node2.setAttributeNS(null, 'refX', v); 357 358 node2.appendChild(node3); 359 return node2; 360 }, 361 362 /** 363 * Updates color of an arrow DOM node. 364 * @param {Node} node The arrow node. 365 * @param {String} color Color value in a HTML compatible format, e.g. <tt>#00ff00</tt> or <tt>green</tt> for green. 366 * @param {Number} opacity 367 * @param {JXG.GeometryElement} el The element the arrows are to be attached to 368 */ 369 _setArrowColor: function (node, color, opacity, el, type) { 370 if (node) { 371 if (Type.isString(color)) { 372 if (type !== 7) { 373 this._setAttribute(function () { 374 node.setAttributeNS(null, 'stroke', color); 375 node.setAttributeNS(null, 'fill', color); 376 node.setAttributeNS(null, 'stroke-opacity', opacity); 377 node.setAttributeNS(null, 'fill-opacity', opacity); 378 }, el.visPropOld.fillcolor); 379 } else { 380 this._setAttribute(function () { 381 node.setAttributeNS(null, 'fill', 'none'); 382 node.setAttributeNS(null, 'stroke', color); 383 node.setAttributeNS(null, 'stroke-opacity', opacity); 384 }, el.visPropOld.fillcolor); 385 } 386 } 387 388 if (this.isIE) { 389 el.rendNode.parentNode.insertBefore(el.rendNode, el.rendNode); 390 } 391 } 392 393 }, 394 395 // Already documented in JXG.AbstractRenderer 396 _setArrowWidth: function (node, width, parentNode, size) { 397 var s, d; 398 399 if (node) { 400 // if (width === 0) { 401 // // display:none does not work well in webkit 402 // node.setAttributeNS(null, 'display', 'none'); 403 // } else { 404 s = width; 405 d = s * size; 406 node.setAttributeNS(null, 'viewBox', (0) + ' ' + (0) + ' ' + (s * 10) + ' ' + (s * 10)); 407 node.setAttributeNS(null, 'markerHeight', d); 408 node.setAttributeNS(null, 'markerWidth', d); 409 node.setAttributeNS(null, 'display', 'inherit'); 410 // } 411 412 if (this.isIE) { 413 parentNode.parentNode.insertBefore(parentNode, parentNode); 414 } 415 } 416 }, 417 418 /* ******************************** * 419 * This renderer does not need to 420 * override draw/update* methods 421 * since it provides draw/update*Prim 422 * methods except for some cases like 423 * internal texts or images. 424 * ******************************** */ 425 426 /* ************************** 427 * Lines 428 * **************************/ 429 430 // documented in AbstractRenderer 431 updateTicks: function (ticks) { 432 var i, j, c, node, x, y, 433 tickStr = '', 434 len = ticks.ticks.length, 435 len2, str, 436 isReal = true; 437 438 for (i = 0; i < len; i++) { 439 c = ticks.ticks[i]; 440 x = c[0]; 441 y = c[1]; 442 443 len2 = x.length; 444 str = ' M ' + x[0] + ' ' + y[0]; 445 if (!Type.isNumber(x[0])) { 446 isReal = false; 447 } 448 for (j = 1; isReal && j < len2; ++j) { 449 if (Type.isNumber(x[j])) { 450 str += ' L ' + x[j] + ' ' + y[j]; 451 } else { 452 isReal = false; 453 } 454 455 } 456 if (isReal) { 457 tickStr += str; 458 } 459 } 460 461 node = ticks.rendNode; 462 463 if (!Type.exists(node)) { 464 node = this.createPrim('path', ticks.id); 465 this.appendChildPrim(node, Type.evaluate(ticks.visProp.layer)); 466 ticks.rendNode = node; 467 } 468 469 node.setAttributeNS(null, 'stroke', Type.evaluate(ticks.visProp.strokecolor)); 470 node.setAttributeNS(null, 'fill', 'none'); 471 // node.setAttributeNS(null, 'fill', Type.evaluate(ticks.visProp.fillcolor)); 472 // node.setAttributeNS(null, 'fill-opacity', Type.evaluate(ticks.visProp.fillopacity)); 473 node.setAttributeNS(null, 'stroke-opacity', Type.evaluate(ticks.visProp.strokeopacity)); 474 node.setAttributeNS(null, 'stroke-width', Type.evaluate(ticks.visProp.strokewidth)); 475 this.updatePathPrim(node, tickStr, ticks.board); 476 }, 477 478 /* ************************** 479 * Text related stuff 480 * **************************/ 481 482 // Already documented in JXG.AbstractRenderer 483 displayCopyright: function (str, fontsize) { 484 var node = this.createPrim('text', 'licenseText'), 485 t; 486 node.setAttributeNS(null, 'x', '20px'); 487 node.setAttributeNS(null, 'y', (2 + fontsize) + 'px'); 488 node.setAttributeNS(null, "style", "font-family:Arial,Helvetica,sans-serif; font-size:" + fontsize + "px; fill:#356AA0; opacity:0.3;"); 489 t = this.container.ownerDocument.createTextNode(str); 490 node.appendChild(t); 491 this.appendChildPrim(node, 0); 492 }, 493 494 // Already documented in JXG.AbstractRenderer 495 drawInternalText: function (el) { 496 var node = this.createPrim('text', el.id); 497 498 //node.setAttributeNS(null, "style", "alignment-baseline:middle"); // Not yet supported by Firefox 499 // Preserve spaces 500 //node.setAttributeNS("http://www.w3.org/XML/1998/namespace", "space", "preserve"); 501 node.style.whiteSpace = 'nowrap'; 502 503 el.rendNodeText = this.container.ownerDocument.createTextNode(''); 504 node.appendChild(el.rendNodeText); 505 this.appendChildPrim(node, Type.evaluate(el.visProp.layer)); 506 507 return node; 508 }, 509 510 // Already documented in JXG.AbstractRenderer 511 updateInternalText: function (el) { 512 var content = el.plaintext, v, 513 ev_ax = el.getAnchorX(), 514 ev_ay = el.getAnchorY(); 515 516 if (el.rendNode.getAttributeNS(null, "class") !== el.visProp.cssclass) { 517 el.rendNode.setAttributeNS(null, "class", Type.evaluate(el.visProp.cssclass)); 518 el.needsSizeUpdate = true; 519 } 520 521 if (!isNaN(el.coords.scrCoords[1] + el.coords.scrCoords[2])) { 522 // Horizontal 523 v = el.coords.scrCoords[1]; 524 if (el.visPropOld.left !== (ev_ax + v)) { 525 el.rendNode.setAttributeNS(null, 'x', v + 'px'); 526 527 if (ev_ax === 'left') { 528 el.rendNode.setAttributeNS(null, 'text-anchor', 'start'); 529 } else if (ev_ax === 'right') { 530 el.rendNode.setAttributeNS(null, 'text-anchor', 'end'); 531 } else if (ev_ax === 'middle') { 532 el.rendNode.setAttributeNS(null, 'text-anchor', 'middle'); 533 } 534 el.visPropOld.left = ev_ax + v; 535 } 536 537 // Vertical 538 v = el.coords.scrCoords[2]; 539 if (el.visPropOld.top !== (ev_ay + v)) { 540 el.rendNode.setAttributeNS(null, 'y', (v + this.vOffsetText * 0.5) + 'px'); 541 542 if (ev_ay === 'bottom') { 543 el.rendNode.setAttributeNS(null, 'dominant-baseline', 'text-after-edge'); 544 } else if (ev_ay === 'top') { 545 el.rendNode.setAttributeNS(null, 'dy', '1.6ex'); 546 //el.rendNode.setAttributeNS(null, 'dominant-baseline', 'text-before-edge'); // Not supported by IE, edge 547 } else if (ev_ay === 'middle') { 548 //el.rendNode.setAttributeNS(null, 'dominant-baseline', 'middle'); 549 el.rendNode.setAttributeNS(null, 'dy', '0.6ex'); 550 } 551 el.visPropOld.top = ev_ay + v; 552 } 553 } 554 if (el.htmlStr !== content) { 555 el.rendNodeText.data = content; 556 el.htmlStr = content; 557 } 558 this.transformImage(el, el.transformations); 559 }, 560 561 /** 562 * Set color and opacity of internal texts. 563 * SVG needs its own version. 564 * @private 565 * @see JXG.AbstractRenderer#updateTextStyle 566 * @see JXG.AbstractRenderer#updateInternalTextStyle 567 */ 568 updateInternalTextStyle: function (el, strokeColor, strokeOpacity, duration) { 569 this.setObjectFillColor(el, strokeColor, strokeOpacity); 570 }, 571 572 /* ************************** 573 * Image related stuff 574 * **************************/ 575 576 // Already documented in JXG.AbstractRenderer 577 drawImage: function (el) { 578 var node = this.createPrim('image', el.id); 579 580 node.setAttributeNS(null, 'preserveAspectRatio', 'none'); 581 this.appendChildPrim(node, Type.evaluate(el.visProp.layer)); 582 el.rendNode = node; 583 584 this.updateImage(el); 585 }, 586 587 // Already documented in JXG.AbstractRenderer 588 transformImage: function (el, t) { 589 var s, m, 590 node = el.rendNode, 591 str = "", 592 len = t.length; 593 594 if (len > 0) { 595 m = this.joinTransforms(el, t); 596 s = [m[1][1], m[2][1], m[1][2], m[2][2], m[1][0], m[2][0]].join(','); 597 str += ' matrix(' + s + ') '; 598 node.setAttributeNS(null, 'transform', str); 599 } 600 }, 601 602 // Already documented in JXG.AbstractRenderer 603 updateImageURL: function (el) { 604 var url = Type.evaluate(el.url); 605 606 if (el._src !== url) { 607 el.imgIsLoaded = false; 608 el.rendNode.setAttributeNS(this.xlinkNamespace, 'xlink:href', url); 609 el._src = url; 610 611 return true; 612 } 613 614 return false; 615 }, 616 617 // Already documented in JXG.AbstractRenderer 618 updateImageStyle: function (el, doHighlight) { 619 var css = Type.evaluate(doHighlight ? el.visProp.highlightcssclass : el.visProp.cssclass); 620 621 el.rendNode.setAttributeNS(null, 'class', css); 622 }, 623 624 // Already documented in JXG.AbstractRenderer 625 drawForeignObject: function (el) { 626 el.rendNode = this.appendChildPrim(this.createPrim('foreignObject', el.id), 627 Type.evaluate(el.visProp.layer)); 628 629 this.appendNodesToElement(el, 'foreignObject'); 630 this.updateForeignObject(el); 631 }, 632 633 // Already documented in JXG.AbstractRenderer 634 updateForeignObject: function(el) { 635 if (el._useUserSize) { 636 el.rendNode.style.overflow = 'hidden'; 637 } else { 638 el.rendNode.style.overflow = 'visible'; 639 } 640 641 this.updateRectPrim(el.rendNode, el.coords.scrCoords[1], 642 el.coords.scrCoords[2] - el.size[1], el.size[0], el.size[1]); 643 644 el.rendNode.innerHTML = el.content; 645 this._updateVisual(el, {stroke: true, dash: true}, true); 646 }, 647 648 /* ************************** 649 * Render primitive objects 650 * **************************/ 651 652 // Already documented in JXG.AbstractRenderer 653 appendChildPrim: function (node, level) { 654 if (!Type.exists(level)) { // trace nodes have level not set 655 level = 0; 656 } else if (level >= Options.layer.numlayers) { 657 level = Options.layer.numlayers - 1; 658 } 659 660 this.layer[level].appendChild(node); 661 662 return node; 663 }, 664 665 // Already documented in JXG.AbstractRenderer 666 createPrim: function (type, id) { 667 var node = this.container.ownerDocument.createElementNS(this.svgNamespace, type); 668 node.setAttributeNS(null, 'id', this.container.id + '_' + id); 669 node.style.position = 'absolute'; 670 if (type === 'path') { 671 node.setAttributeNS(null, 'stroke-linecap', 'round'); 672 node.setAttributeNS(null, 'stroke-linejoin', 'round'); 673 node.setAttributeNS(null, 'fill-rule', 'evenodd'); 674 } 675 return node; 676 }, 677 678 // Already documented in JXG.AbstractRenderer 679 remove: function (shape) { 680 if (Type.exists(shape) && Type.exists(shape.parentNode)) { 681 shape.parentNode.removeChild(shape); 682 } 683 }, 684 685 // Already documented in JXG.AbstractRenderer 686 setLayer: function (el, level) { 687 if (!Type.exists(level)) { 688 level = 0; 689 } else if (level >= Options.layer.numlayers) { 690 level = Options.layer.numlayers - 1; 691 } 692 693 this.layer[level].appendChild(el.rendNode); 694 }, 695 696 // Already documented in JXG.AbstractRenderer 697 makeArrows: function (el, a) { 698 var node2, 699 ev_fa = a.evFirst, 700 ev_la = a.evLast; 701 702 // Test if the arrow heads already exist 703 if (el.visPropOld.firstarrow === ev_fa && 704 el.visPropOld.lastarrow === ev_la) { 705 if (this.isIE && el.visPropCalc.visible && 706 (ev_fa || ev_la)) { 707 el.rendNode.parentNode.insertBefore(el.rendNode, el.rendNode); 708 } 709 return; 710 } 711 712 if (ev_fa) { 713 node2 = el.rendNodeTriangleStart; 714 if (!Type.exists(node2)) { 715 node2 = this._createArrowHead(el, 'End', a.typeFirst); 716 this.defs.appendChild(node2); 717 el.rendNodeTriangleStart = node2; 718 el.rendNode.setAttributeNS(null, 'marker-start', 'url(#' + this.container.id + '_' + el.id + 'TriangleEnd)'); 719 } else { 720 this.defs.appendChild(node2); 721 } 722 } else { 723 node2 = el.rendNodeTriangleStart; 724 if (Type.exists(node2)) { 725 this.remove(node2); 726 } 727 } 728 if (ev_la) { 729 node2 = el.rendNodeTriangleEnd; 730 if (!Type.exists(node2)) { 731 node2 = this._createArrowHead(el, 'Start', a.typeLast); 732 this.defs.appendChild(node2); 733 el.rendNodeTriangleEnd = node2; 734 el.rendNode.setAttributeNS(null, 'marker-end', 'url(#' + this.container.id + '_' + el.id + 'TriangleStart)'); 735 } else { 736 this.defs.appendChild(node2); 737 } 738 } else { 739 node2 = el.rendNodeTriangleEnd; 740 if (Type.exists(node2)) { 741 this.remove(node2); 742 } 743 } 744 el.visPropOld.firstarrow = ev_fa; 745 el.visPropOld.lastarrow = ev_la; 746 }, 747 748 // Already documented in JXG.AbstractRenderer 749 updateEllipsePrim: function (node, x, y, rx, ry) { 750 var huge = 1000000; 751 752 huge = 200000; // IE 753 // webkit does not like huge values if the object is dashed 754 // iE doesn't like huge values above 216000 755 x = Math.abs(x) < huge ? x : huge * x / Math.abs(x); 756 y = Math.abs(y) < huge ? y : huge * y / Math.abs(y); 757 rx = Math.abs(rx) < huge ? rx : huge * rx / Math.abs(rx); 758 ry = Math.abs(ry) < huge ? ry : huge * ry / Math.abs(ry); 759 760 node.setAttributeNS(null, 'cx', x); 761 node.setAttributeNS(null, 'cy', y); 762 node.setAttributeNS(null, 'rx', Math.abs(rx)); 763 node.setAttributeNS(null, 'ry', Math.abs(ry)); 764 }, 765 766 // Already documented in JXG.AbstractRenderer 767 updateLinePrim: function (node, p1x, p1y, p2x, p2y) { 768 var huge = 1000000; 769 770 huge = 200000; //IE 771 if (!isNaN(p1x + p1y + p2x + p2y)) { 772 // webkit does not like huge values if the object is dashed 773 // IE doesn't like huge values above 216000 774 p1x = Math.abs(p1x) < huge ? p1x : huge * p1x / Math.abs(p1x); 775 p1y = Math.abs(p1y) < huge ? p1y : huge * p1y / Math.abs(p1y); 776 p2x = Math.abs(p2x) < huge ? p2x : huge * p2x / Math.abs(p2x); 777 p2y = Math.abs(p2y) < huge ? p2y : huge * p2y / Math.abs(p2y); 778 779 node.setAttributeNS(null, 'x1', p1x); 780 node.setAttributeNS(null, 'y1', p1y); 781 node.setAttributeNS(null, 'x2', p2x); 782 node.setAttributeNS(null, 'y2', p2y); 783 } 784 }, 785 786 // Already documented in JXG.AbstractRenderer 787 updatePathPrim: function (node, pointString) { 788 if (pointString === '') { 789 pointString = 'M 0 0'; 790 } 791 node.setAttributeNS(null, 'd', pointString); 792 }, 793 794 // Already documented in JXG.AbstractRenderer 795 updatePathStringPoint: function (el, size, type) { 796 var s = '', 797 scr = el.coords.scrCoords, 798 sqrt32 = size * Math.sqrt(3) * 0.5, 799 s05 = size * 0.5; 800 801 if (type === 'x') { 802 s = ' M ' + (scr[1] - size) + ' ' + (scr[2] - size) + 803 ' L ' + (scr[1] + size) + ' ' + (scr[2] + size) + 804 ' M ' + (scr[1] + size) + ' ' + (scr[2] - size) + 805 ' L ' + (scr[1] - size) + ' ' + (scr[2] + size); 806 } else if (type === '+') { 807 s = ' M ' + (scr[1] - size) + ' ' + (scr[2]) + 808 ' L ' + (scr[1] + size) + ' ' + (scr[2]) + 809 ' M ' + (scr[1]) + ' ' + (scr[2] - size) + 810 ' L ' + (scr[1]) + ' ' + (scr[2] + size); 811 } else if (type === '<>') { 812 s = ' M ' + (scr[1] - size) + ' ' + (scr[2]) + 813 ' L ' + (scr[1]) + ' ' + (scr[2] + size) + 814 ' L ' + (scr[1] + size) + ' ' + (scr[2]) + 815 ' L ' + (scr[1]) + ' ' + (scr[2] - size) + ' Z '; 816 } else if (type === '^') { 817 s = ' M ' + (scr[1]) + ' ' + (scr[2] - size) + 818 ' L ' + (scr[1] - sqrt32) + ' ' + (scr[2] + s05) + 819 ' L ' + (scr[1] + sqrt32) + ' ' + (scr[2] + s05) + 820 ' Z '; // close path 821 } else if (type === 'v') { 822 s = ' M ' + (scr[1]) + ' ' + (scr[2] + size) + 823 ' L ' + (scr[1] - sqrt32) + ' ' + (scr[2] - s05) + 824 ' L ' + (scr[1] + sqrt32) + ' ' + (scr[2] - s05) + 825 ' Z '; 826 } else if (type === '>') { 827 s = ' M ' + (scr[1] + size) + ' ' + (scr[2]) + 828 ' L ' + (scr[1] - s05) + ' ' + (scr[2] - sqrt32) + 829 ' L ' + (scr[1] - s05) + ' ' + (scr[2] + sqrt32) + 830 ' Z '; 831 } else if (type === '<') { 832 s = ' M ' + (scr[1] - size) + ' ' + (scr[2]) + 833 ' L ' + (scr[1] + s05) + ' ' + (scr[2] - sqrt32) + 834 ' L ' + (scr[1] + s05) + ' ' + (scr[2] + sqrt32) + 835 ' Z '; 836 } 837 return s; 838 }, 839 840 // Already documented in JXG.AbstractRenderer 841 updatePathStringPrim: function (el) { 842 var i, scr, len, 843 symbm = ' M ', 844 symbl = ' L ', 845 symbc = ' C ', 846 nextSymb = symbm, 847 maxSize = 5000.0, 848 pStr = ''; 849 850 if (el.numberPoints <= 0) { 851 return ''; 852 } 853 854 len = Math.min(el.points.length, el.numberPoints); 855 856 if (el.bezierDegree === 1) { 857 for (i = 0; i < len; i++) { 858 scr = el.points[i].scrCoords; 859 if (isNaN(scr[1]) || isNaN(scr[2])) { // PenUp 860 nextSymb = symbm; 861 } else { 862 // Chrome has problems with values being too far away. 863 scr[1] = Math.max(Math.min(scr[1], maxSize), -maxSize); 864 scr[2] = Math.max(Math.min(scr[2], maxSize), -maxSize); 865 866 // Attention: first coordinate may be inaccurate if far way 867 //pStr += [nextSymb, scr[1], ' ', scr[2]].join(''); 868 pStr += nextSymb + scr[1] + ' ' + scr[2]; // Seems to be faster now (webkit and firefox) 869 nextSymb = symbl; 870 } 871 } 872 } else if (el.bezierDegree === 3) { 873 i = 0; 874 while (i < len) { 875 scr = el.points[i].scrCoords; 876 if (isNaN(scr[1]) || isNaN(scr[2])) { // PenUp 877 nextSymb = symbm; 878 } else { 879 pStr += nextSymb + scr[1] + ' ' + scr[2]; 880 if (nextSymb === symbc) { 881 i += 1; 882 scr = el.points[i].scrCoords; 883 pStr += ' ' + scr[1] + ' ' + scr[2]; 884 i += 1; 885 scr = el.points[i].scrCoords; 886 pStr += ' ' + scr[1] + ' ' + scr[2]; 887 } 888 nextSymb = symbc; 889 } 890 i += 1; 891 } 892 } 893 return pStr; 894 }, 895 896 // Already documented in JXG.AbstractRenderer 897 updatePathStringBezierPrim: function (el) { 898 var i, j, k, scr, lx, ly, len, 899 symbm = ' M ', 900 symbl = ' C ', 901 nextSymb = symbm, 902 maxSize = 5000.0, 903 pStr = '', 904 f = Type.evaluate(el.visProp.strokewidth), 905 isNoPlot = (Type.evaluate(el.visProp.curvetype) !== 'plot'); 906 907 if (el.numberPoints <= 0) { 908 return ''; 909 } 910 911 if (isNoPlot && el.board.options.curve.RDPsmoothing) { 912 el.points = Numerics.RamerDouglasPeucker(el.points, 0.5); 913 } 914 915 len = Math.min(el.points.length, el.numberPoints); 916 for (j = 1; j < 3; j++) { 917 nextSymb = symbm; 918 for (i = 0; i < len; i++) { 919 scr = el.points[i].scrCoords; 920 921 if (isNaN(scr[1]) || isNaN(scr[2])) { // PenUp 922 nextSymb = symbm; 923 } else { 924 // Chrome has problems with values being too far away. 925 scr[1] = Math.max(Math.min(scr[1], maxSize), -maxSize); 926 scr[2] = Math.max(Math.min(scr[2], maxSize), -maxSize); 927 928 // Attention: first coordinate may be inaccurate if far way 929 if (nextSymb === symbm) { 930 //pStr += [nextSymb, scr[1], ' ', scr[2]].join(''); 931 pStr += nextSymb + scr[1] + ' ' + scr[2]; // Seems to be faster now (webkit and firefox) 932 } else { 933 k = 2 * j; 934 pStr += [nextSymb, 935 (lx + (scr[1] - lx) * 0.333 + f * (k * Math.random() - j)), ' ', 936 (ly + (scr[2] - ly) * 0.333 + f * (k * Math.random() - j)), ' ', 937 (lx + (scr[1] - lx) * 0.666 + f * (k * Math.random() - j)), ' ', 938 (ly + (scr[2] - ly) * 0.666 + f * (k * Math.random() - j)), ' ', 939 scr[1], ' ', scr[2]].join(''); 940 } 941 942 nextSymb = symbl; 943 lx = scr[1]; 944 ly = scr[2]; 945 } 946 } 947 } 948 return pStr; 949 }, 950 951 // Already documented in JXG.AbstractRenderer 952 updatePolygonPrim: function (node, el) { 953 var i, 954 pStr = '', 955 scrCoords, 956 len = el.vertices.length; 957 958 node.setAttributeNS(null, 'stroke', 'none'); 959 if (el.elType === 'polygonalchain') { 960 len++; 961 } 962 963 for (i = 0; i < len - 1; i++) { 964 if (el.vertices[i].isReal) { 965 scrCoords = el.vertices[i].coords.scrCoords; 966 pStr = pStr + scrCoords[1] + "," + scrCoords[2]; 967 } else { 968 node.setAttributeNS(null, 'points', ''); 969 return; 970 } 971 972 if (i < len - 2) { 973 pStr += " "; 974 } 975 } 976 if (pStr.indexOf('NaN') === -1) { 977 node.setAttributeNS(null, 'points', pStr); 978 } 979 }, 980 981 // Already documented in JXG.AbstractRenderer 982 updateRectPrim: function (node, x, y, w, h) { 983 node.setAttributeNS(null, 'x', x); 984 node.setAttributeNS(null, 'y', y); 985 node.setAttributeNS(null, 'width', w); 986 node.setAttributeNS(null, 'height', h); 987 }, 988 989 /* ************************** 990 * Set Attributes 991 * **************************/ 992 993 // documented in JXG.AbstractRenderer 994 setPropertyPrim: function (node, key, val) { 995 if (key === 'stroked') { 996 return; 997 } 998 node.setAttributeNS(null, key, val); 999 }, 1000 1001 display: function (el, val) { 1002 var node; 1003 1004 if (el && el.rendNode) { 1005 el.visPropOld.visible = val; 1006 node = el.rendNode; 1007 if (val) { 1008 node.setAttributeNS(null, 'display', 'inline'); 1009 node.style.visibility = "inherit"; 1010 } else { 1011 node.setAttributeNS(null, 'display', 'none'); 1012 node.style.visibility = "hidden"; 1013 } 1014 } 1015 }, 1016 1017 // documented in JXG.AbstractRenderer 1018 show: function (el) { 1019 JXG.deprecated('Board.renderer.show()', 'Board.renderer.display()'); 1020 this.display(el, true); 1021 // var node; 1022 // 1023 // if (el && el.rendNode) { 1024 // node = el.rendNode; 1025 // node.setAttributeNS(null, 'display', 'inline'); 1026 // node.style.visibility = "inherit"; 1027 // } 1028 }, 1029 1030 // documented in JXG.AbstractRenderer 1031 hide: function (el) { 1032 JXG.deprecated('Board.renderer.hide()', 'Board.renderer.display()'); 1033 this.display(el, false); 1034 // var node; 1035 // 1036 // if (el && el.rendNode) { 1037 // node = el.rendNode; 1038 // node.setAttributeNS(null, 'display', 'none'); 1039 // node.style.visibility = "hidden"; 1040 // } 1041 }, 1042 1043 // documented in JXG.AbstractRenderer 1044 setBuffering: function (el, type) { 1045 el.rendNode.setAttribute('buffered-rendering', type); 1046 }, 1047 1048 // documented in JXG.AbstractRenderer 1049 setDashStyle: function (el) { 1050 var dashStyle = Type.evaluate(el.visProp.dash), 1051 node = el.rendNode; 1052 1053 if (dashStyle > 0) { 1054 node.setAttributeNS(null, 'stroke-dasharray', this.dashArray[dashStyle - 1]); 1055 } else { 1056 if (node.hasAttributeNS(null, 'stroke-dasharray')) { 1057 node.removeAttributeNS(null, 'stroke-dasharray'); 1058 } 1059 } 1060 }, 1061 1062 // documented in JXG.AbstractRenderer 1063 setGradient: function (el) { 1064 var fillNode = el.rendNode, 1065 node, node2, node3, 1066 ev_g = Type.evaluate(el.visProp.gradient); 1067 1068 if (ev_g === 'linear' || ev_g === 'radial') { 1069 node = this.createPrim(ev_g + 'Gradient', el.id + '_gradient'); 1070 node2 = this.createPrim('stop', el.id + '_gradient1'); 1071 node3 = this.createPrim('stop', el.id + '_gradient2'); 1072 node.appendChild(node2); 1073 node.appendChild(node3); 1074 this.defs.appendChild(node); 1075 fillNode.setAttributeNS(null, 'style', 'fill:url(#' + this.container.id + '_' + el.id + '_gradient)'); 1076 el.gradNode1 = node2; 1077 el.gradNode2 = node3; 1078 el.gradNode = node; 1079 } else { 1080 fillNode.removeAttributeNS(null, 'style'); 1081 } 1082 }, 1083 1084 /** 1085 * Set the gradient angle for linear color gradients. 1086 * 1087 * @private 1088 * @param {SVGnode} node SVG gradient node of an arbitrary JSXGraph element. 1089 * @param {Number} radians angle value in radians. 0 is horizontal from left to right, Pi/4 is vertical from top to bottom. 1090 */ 1091 updateGradientAngle: function(node, radians) { 1092 // Angles: 1093 // 0: -> 1094 // 90: down 1095 // 180: <- 1096 // 90: up 1097 var f = 1.0, 1098 co = Math.cos(radians), 1099 si = Math.sin(radians); 1100 1101 if (Math.abs(co) > Math.abs(si)) { 1102 f /= Math.abs(co); 1103 } else { 1104 f /= Math.abs(si); 1105 } 1106 1107 if (co >= 0) { 1108 node.setAttributeNS(null, 'x1', 0); 1109 node.setAttributeNS(null, 'x2', co * f); 1110 } else { 1111 node.setAttributeNS(null, 'x1', -co * f); 1112 node.setAttributeNS(null, 'x2', 0); 1113 } 1114 if (si >= 0) { 1115 node.setAttributeNS(null, 'y1', 0); 1116 node.setAttributeNS(null, 'y2', si * f); 1117 } else { 1118 node.setAttributeNS(null, 'y1', -si * f); 1119 node.setAttributeNS(null, 'y2', 0); 1120 } 1121 }, 1122 1123 /** 1124 * Set circles for radial color gradients. 1125 * 1126 * @private 1127 * @param {SVGnode} node SVG gradient node 1128 * @param {Number} cx SVG value cx (value between 0 and 1) 1129 * @param {Number} cy SVG value cy (value between 0 and 1) 1130 * @param {Number} r SVG value r (value between 0 and 1) 1131 * @param {Number} fx SVG value fx (value between 0 and 1) 1132 * @param {Number} fy SVG value fy (value between 0 and 1) 1133 * @param {Number} fr SVG value fr (value between 0 and 1) 1134 */ 1135 updateGradientCircle: function(node, cx, cy, r, fx, fy, fr) { 1136 node.setAttributeNS(null, 'cx', cx * 100 + '%'); // Center first color 1137 node.setAttributeNS(null, 'cy', cy * 100 + '%'); 1138 node.setAttributeNS(null, 'r', r * 100 + '%'); 1139 node.setAttributeNS(null, 'fx', fx * 100 + '%'); // Center second color / focal point 1140 node.setAttributeNS(null, 'fy', fy * 100 + '%'); 1141 node.setAttributeNS(null, 'fr', fr * 100 + '%'); 1142 }, 1143 1144 // documented in JXG.AbstractRenderer 1145 updateGradient: function (el) { 1146 var col, op, 1147 node2 = el.gradNode1, 1148 node3 = el.gradNode2, 1149 ev_g = Type.evaluate(el.visProp.gradient); 1150 1151 if (!Type.exists(node2) || !Type.exists(node3)) { 1152 return; 1153 } 1154 1155 op = Type.evaluate(el.visProp.fillopacity); 1156 op = (op > 0) ? op : 0; 1157 col = Type.evaluate(el.visProp.fillcolor); 1158 1159 node2.setAttributeNS(null, 'style', 'stop-color:' + col + ';stop-opacity:' + op); 1160 node3.setAttributeNS(null, 'style', 1161 'stop-color:' + Type.evaluate(el.visProp.gradientsecondcolor) + 1162 ';stop-opacity:' + Type.evaluate(el.visProp.gradientsecondopacity) 1163 ); 1164 node2.setAttributeNS(null, 'offset', Type.evaluate(el.visProp.gradientstartoffset) * 100 + '%'); 1165 node3.setAttributeNS(null, 'offset', Type.evaluate(el.visProp.gradientendoffset) * 100 + '%'); 1166 if (ev_g === 'linear') { 1167 this.updateGradientAngle(el.gradNode, Type.evaluate(el.visProp.gradientangle)); 1168 } else if (ev_g === 'radial') { 1169 this.updateGradientCircle(el.gradNode, 1170 Type.evaluate(el.visProp.gradientcx), 1171 Type.evaluate(el.visProp.gradientcy), 1172 Type.evaluate(el.visProp.gradientr), 1173 Type.evaluate(el.visProp.gradientfx), 1174 Type.evaluate(el.visProp.gradientfy), 1175 Type.evaluate(el.visProp.gradientfr) 1176 ); 1177 } 1178 }, 1179 1180 // documented in JXG.AbstractRenderer 1181 setObjectTransition: function (el, duration) { 1182 var node, transitionStr, 1183 i, len, 1184 nodes = ['rendNode', 1185 'rendNodeTriangleStart', 1186 'rendNodeTriangleEnd']; 1187 1188 if (duration === undefined) { 1189 duration = Type.evaluate(el.visProp.transitionduration); 1190 } 1191 1192 if (duration === el.visPropOld.transitionduration) { 1193 return; 1194 } 1195 1196 if (el.elementClass === Const.OBJECT_CLASS_TEXT && 1197 Type.evaluate(el.visProp.display) === 'html') { 1198 transitionStr = ' color ' + duration + 'ms,' + 1199 ' opacity ' + duration + 'ms'; 1200 } else { 1201 transitionStr = ' fill ' + duration + 'ms,' + 1202 ' fill-opacity ' + duration + 'ms,' + 1203 ' stroke ' + duration + 'ms,' + 1204 ' stroke-opacity ' + duration + 'ms'; 1205 } 1206 1207 len = nodes.length; 1208 for (i = 0; i < len; ++i) { 1209 if (el[nodes[i]]) { 1210 node = el[nodes[i]]; 1211 node.style.transition = transitionStr; 1212 } 1213 } 1214 1215 el.visPropOld.transitionduration = duration; 1216 }, 1217 1218 /** 1219 * Call user-defined function to set visual attributes. 1220 * If "testAttribute" is the empty string, the function 1221 * is called immediately, otherwise it is called in a timeOut. 1222 * 1223 * This is necessary to realize smooth transitions but avoid transitions 1224 * when first creating the objects. 1225 * 1226 * Usually, the string in testAttribute is the visPropOld attribute 1227 * of the values which are set. 1228 * 1229 * @param {Function} setFunc Some function which usually sets some attributes 1230 * @param {String} testAttribute If this string is the empty string the function is called immediately, 1231 * otherwise it is called in a setImeout. 1232 * @see JXG.SVGRenderer#setObjectFillColor 1233 * @see JXG.SVGRenderer#setObjectStrokeColor 1234 * @see JXG.SVGRenderer#_setArrowColor 1235 * @private 1236 */ 1237 _setAttribute: function (setFunc, testAttribute) { 1238 if (testAttribute === '') { 1239 setFunc(); 1240 } else { 1241 window.setTimeout(setFunc, 1); 1242 } 1243 }, 1244 1245 // documented in JXG.AbstractRenderer 1246 setObjectFillColor: function (el, color, opacity, rendNode) { 1247 var node, c, rgbo, oo, 1248 rgba = Type.evaluate(color), 1249 o = Type.evaluate(opacity), 1250 grad = Type.evaluate(el.visProp.gradient); 1251 1252 o = (o > 0) ? o : 0; 1253 1254 // TODO save gradient and gradientangle 1255 if (el.visPropOld.fillcolor === rgba && el.visPropOld.fillopacity === o && grad === null) { 1256 return; 1257 } 1258 1259 if (Type.exists(rgba) && rgba !== false) { 1260 if (rgba.length !== 9) { // RGB, not RGBA 1261 c = rgba; 1262 oo = o; 1263 } else { // True RGBA, not RGB 1264 rgbo = Color.rgba2rgbo(rgba); 1265 c = rgbo[0]; 1266 oo = o * rgbo[1]; 1267 } 1268 1269 if (rendNode === undefined) { 1270 node = el.rendNode; 1271 } else { 1272 node = rendNode; 1273 } 1274 1275 if (c !== 'none') { 1276 this._setAttribute(function () { 1277 node.setAttributeNS(null, 'fill', c); 1278 }, el.visPropOld.fillcolor); 1279 } 1280 1281 if (el.type === JXG.OBJECT_TYPE_IMAGE) { 1282 this._setAttribute(function () { 1283 node.setAttributeNS(null, 'opacity', oo); 1284 }, el.visPropOld.fillopacity); 1285 //node.style['opacity'] = oo; // This would overwrite values set by CSS class. 1286 } else { 1287 if (c === 'none') { // This is done only for non-images 1288 // because images have no fill color. 1289 oo = 0; 1290 // This is necessary if there is a foreignObject below. 1291 node.setAttributeNS(null, 'pointer-events', 'visibleStroke'); 1292 } else { 1293 // This is the default 1294 node.setAttributeNS(null, 'pointer-events', 'visiblePainted'); 1295 } 1296 this._setAttribute(function () { 1297 node.setAttributeNS(null, 'fill-opacity', oo); 1298 }, el.visPropOld.fillopacity); 1299 } 1300 1301 if (grad === 'linear' || grad === 'radial') { 1302 this.updateGradient(el); 1303 } 1304 } 1305 el.visPropOld.fillcolor = rgba; 1306 el.visPropOld.fillopacity = o; 1307 }, 1308 1309 // documented in JXG.AbstractRenderer 1310 setObjectStrokeColor: function (el, color, opacity) { 1311 var rgba = Type.evaluate(color), c, rgbo, 1312 o = Type.evaluate(opacity), oo, 1313 node; 1314 1315 o = (o > 0) ? o : 0; 1316 1317 if (el.visPropOld.strokecolor === rgba && el.visPropOld.strokeopacity === o) { 1318 return; 1319 } 1320 1321 if (Type.exists(rgba) && rgba !== false) { 1322 if (rgba.length !== 9) { // RGB, not RGBA 1323 c = rgba; 1324 oo = o; 1325 } else { // True RGBA, not RGB 1326 rgbo = Color.rgba2rgbo(rgba); 1327 c = rgbo[0]; 1328 oo = o * rgbo[1]; 1329 } 1330 1331 node = el.rendNode; 1332 1333 if (el.elementClass === Const.OBJECT_CLASS_TEXT) { 1334 if (Type.evaluate(el.visProp.display) === 'html') { 1335 this._setAttribute(function () { 1336 node.style.color = c; 1337 node.style.opacity = oo; 1338 }, el.visPropOld.strokecolor); 1339 1340 } else { 1341 this._setAttribute(function () { 1342 node.setAttributeNS(null, "style", "fill:" + c); 1343 node.setAttributeNS(null, "style", "fill-opacity:" + oo); 1344 }, el.visPropOld.strokecolor); 1345 } 1346 } else { 1347 this._setAttribute(function () { 1348 node.setAttributeNS(null, 'stroke', c); 1349 node.setAttributeNS(null, 'stroke-opacity', oo); 1350 }, el.visPropOld.strokecolor); 1351 } 1352 1353 if (el.elementClass === Const.OBJECT_CLASS_CURVE || 1354 el.elementClass === Const.OBJECT_CLASS_LINE) { 1355 if (Type.evaluate(el.visProp.firstarrow)) { 1356 this._setArrowColor(el.rendNodeTriangleStart, c, oo, el, el.visPropCalc.typeFirst); 1357 } 1358 1359 if (Type.evaluate(el.visProp.lastarrow)) { 1360 this._setArrowColor(el.rendNodeTriangleEnd, c, oo, el, el.visPropCalc.typeLast); 1361 } 1362 } 1363 } 1364 1365 el.visPropOld.strokecolor = rgba; 1366 el.visPropOld.strokeopacity = o; 1367 }, 1368 1369 // documented in JXG.AbstractRenderer 1370 setObjectStrokeWidth: function (el, width) { 1371 var node, 1372 w = Type.evaluate(width); 1373 1374 if (isNaN(w) || el.visPropOld.strokewidth === w) { 1375 return; 1376 } 1377 1378 node = el.rendNode; 1379 this.setPropertyPrim(node, 'stroked', 'true'); 1380 if (Type.exists(w)) { 1381 this.setPropertyPrim(node, 'stroke-width', w + 'px'); 1382 1383 // if (el.elementClass === Const.OBJECT_CLASS_CURVE || 1384 // el.elementClass === Const.OBJECT_CLASS_LINE) { 1385 // if (Type.evaluate(el.visProp.firstarrow)) { 1386 // this._setArrowWidth(el.rendNodeTriangleStart, w, el.rendNode); 1387 // } 1388 // 1389 // if (Type.evaluate(el.visProp.lastarrow)) { 1390 // this._setArrowWidth(el.rendNodeTriangleEnd, w, el.rendNode); 1391 // } 1392 // } 1393 } 1394 el.visPropOld.strokewidth = w; 1395 }, 1396 1397 // documented in JXG.AbstractRenderer 1398 setLineCap: function (el) { 1399 var capStyle = Type.evaluate(el.visProp.linecap); 1400 1401 if (capStyle === undefined || capStyle === '' || el.visPropOld.linecap === capStyle || 1402 !Type.exists(el.rendNode)) { 1403 return; 1404 } 1405 1406 this.setPropertyPrim(el.rendNode, 'stroke-linecap', capStyle); 1407 el.visPropOld.linecap = capStyle; 1408 1409 }, 1410 1411 // documented in JXG.AbstractRenderer 1412 setShadow: function (el) { 1413 var ev_s = Type.evaluate(el.visProp.shadow); 1414 if (el.visPropOld.shadow === ev_s) { 1415 return; 1416 } 1417 1418 if (Type.exists(el.rendNode)) { 1419 if (ev_s) { 1420 el.rendNode.setAttributeNS(null, 'filter', 'url(#' + this.container.id + '_' + 'f1)'); 1421 } else { 1422 el.rendNode.removeAttributeNS(null, 'filter'); 1423 } 1424 } 1425 el.visPropOld.shadow = ev_s; 1426 }, 1427 1428 /* ************************** 1429 * renderer control 1430 * **************************/ 1431 1432 // documented in JXG.AbstractRenderer 1433 suspendRedraw: function () { 1434 // It seems to be important for the Linux version of firefox 1435 //this.suspendHandle = this.svgRoot.suspendRedraw(10000); 1436 }, 1437 1438 // documented in JXG.AbstractRenderer 1439 unsuspendRedraw: function () { 1440 //this.svgRoot.unsuspendRedraw(this.suspendHandle); 1441 //this.svgRoot.unsuspendRedrawAll(); 1442 //this.svgRoot.forceRedraw(); 1443 }, 1444 1445 // documented in AbstractRenderer 1446 resize: function (w, h) { 1447 // this.svgRoot.style.width = parseFloat(w) + 'px'; 1448 // this.svgRoot.style.height = parseFloat(h) + 'px'; 1449 1450 this.svgRoot.setAttribute('width', parseFloat(w)); 1451 this.svgRoot.setAttribute('height', parseFloat(h)); 1452 // this.svgRoot.setAttribute('width', '100%'); 1453 // this.svgRoot.setAttribute('height', '100%'); 1454 }, 1455 1456 // documented in JXG.AbstractRenderer 1457 createTouchpoints: function (n) { 1458 var i, na1, na2, node; 1459 this.touchpoints = []; 1460 for (i = 0; i < n; i++) { 1461 na1 = 'touchpoint1_' + i; 1462 node = this.createPrim('path', na1); 1463 this.appendChildPrim(node, 19); 1464 node.setAttributeNS(null, 'd', 'M 0 0'); 1465 this.touchpoints.push(node); 1466 1467 this.setPropertyPrim(node, 'stroked', 'true'); 1468 this.setPropertyPrim(node, 'stroke-width', '1px'); 1469 node.setAttributeNS(null, 'stroke', '#000000'); 1470 node.setAttributeNS(null, 'stroke-opacity', 1.0); 1471 node.setAttributeNS(null, 'display', 'none'); 1472 1473 na2 = 'touchpoint2_' + i; 1474 node = this.createPrim('ellipse', na2); 1475 this.appendChildPrim(node, 19); 1476 this.updateEllipsePrim(node, 0, 0, 0, 0); 1477 this.touchpoints.push(node); 1478 1479 this.setPropertyPrim(node, 'stroked', 'true'); 1480 this.setPropertyPrim(node, 'stroke-width', '1px'); 1481 node.setAttributeNS(null, 'stroke', '#000000'); 1482 node.setAttributeNS(null, 'stroke-opacity', 1.0); 1483 node.setAttributeNS(null, 'fill', '#ffffff'); 1484 node.setAttributeNS(null, 'fill-opacity', 0.0); 1485 1486 node.setAttributeNS(null, 'display', 'none'); 1487 } 1488 }, 1489 1490 // documented in JXG.AbstractRenderer 1491 showTouchpoint: function (i) { 1492 if (this.touchpoints && i >= 0 && 2 * i < this.touchpoints.length) { 1493 this.touchpoints[2 * i].setAttributeNS(null, 'display', 'inline'); 1494 this.touchpoints[2 * i + 1].setAttributeNS(null, 'display', 'inline'); 1495 } 1496 }, 1497 1498 // documented in JXG.AbstractRenderer 1499 hideTouchpoint: function (i) { 1500 if (this.touchpoints && i >= 0 && 2 * i < this.touchpoints.length) { 1501 this.touchpoints[2 * i].setAttributeNS(null, 'display', 'none'); 1502 this.touchpoints[2 * i + 1].setAttributeNS(null, 'display', 'none'); 1503 } 1504 }, 1505 1506 // documented in JXG.AbstractRenderer 1507 updateTouchpoint: function (i, pos) { 1508 var x, y, 1509 d = 37; 1510 1511 if (this.touchpoints && i >= 0 && 2 * i < this.touchpoints.length) { 1512 x = pos[0]; 1513 y = pos[1]; 1514 1515 this.touchpoints[2 * i].setAttributeNS(null, 'd', 'M ' + (x - d) + ' ' + y + ' ' + 1516 'L ' + (x + d) + ' ' + y + ' ' + 1517 'M ' + x + ' ' + (y - d) + ' ' + 1518 'L ' + x + ' ' + (y + d)); 1519 this.updateEllipsePrim(this.touchpoints[2 * i + 1], pos[0], pos[1], 25, 25); 1520 } 1521 }, 1522 1523 /** 1524 * Walk recursively through the DOM subtree of a node and collect all 1525 * value attributes together with the id of that node. 1526 * <b>Attention:</b> Only values of nodes having a valid id are taken. 1527 * @param {Node} node root node of DOM subtree that will be searched recursively. 1528 * @return {Array} Array with entries of the form [id, value] 1529 * @private 1530 */ 1531 _getValuesOfDOMElements: function (node) { 1532 var values = []; 1533 if (node.nodeType === 1) { 1534 node = node.firstChild; 1535 while (node) { 1536 if (node.id !== undefined && node.value !== undefined) { 1537 values.push([node.id, node.value]); 1538 } 1539 values = values.concat(this._getValuesOfDOMElements(node)); 1540 node = node.nextSibling; 1541 } 1542 } 1543 return values; 1544 }, 1545 1546 _getDataUri: function (url, callback) { 1547 var image = new Image(); 1548 1549 image.onload = function () { 1550 var canvas = document.createElement('canvas'); 1551 canvas.width = this.naturalWidth; // or 'width' if you want a special/scaled size 1552 canvas.height = this.naturalHeight; // or 'height' if you want a special/scaled size 1553 1554 canvas.getContext('2d').drawImage(this, 0, 0); 1555 1556 callback(canvas.toDataURL('image/png')); 1557 canvas.remove(); 1558 }; 1559 1560 image.src = url; 1561 }, 1562 1563 _getImgDataURL: function(svgRoot) { 1564 var images, len, canvas, ctx, 1565 ur, i; 1566 1567 images = svgRoot.getElementsByTagName("image"); 1568 len = images.length; 1569 if (len > 0) { 1570 canvas = document.createElement('canvas'); 1571 //img = new Image(); 1572 for (i = 0; i < len; i++) { 1573 images[i].setAttribute("crossorigin", "anonymous"); 1574 //img.src = images[i].href; 1575 //img.onload = function() { 1576 // img.crossOrigin = "anonymous"; 1577 ctx = canvas.getContext('2d'); 1578 canvas.width = images[i].getAttribute("width"); 1579 canvas.height = images[i].getAttribute("height"); 1580 try { 1581 ctx.drawImage(images[i], 0, 0, canvas.width, canvas.height); 1582 //ctx.drawImage(document.getElementById('testimg2'), 0, 0, canvas.width, canvas.height); 1583 1584 // If the image is not png, the format must be specified here 1585 ur = canvas.toDataURL(); 1586 images[i].setAttribute("xlink:href", ur); 1587 } catch (err) { 1588 console.log("CORS problem! Image can not be used", err); 1589 } 1590 //}; 1591 } 1592 //canvas.remove(); 1593 } 1594 return true; 1595 }, 1596 1597 /** 1598 * Return a data URI of the SVG code representeing the construction. 1599 * The SVG code of the construction is base64 encoded. The return string starts 1600 * with "data:image/svg+xml;base64,...". 1601 * 1602 * @param {Boolean} ignoreTexts If true, the foreignObject tag is set to display=none. 1603 * This is necessary for older versions of Safari. Default: false 1604 * @returns {String} data URI string 1605 */ 1606 dumpToDataURI: function (ignoreTexts) { 1607 var svgRoot = this.svgRoot, 1608 btoa = window.btoa || Base64.encode, 1609 svg, 1610 virtualNode, doc, 1611 i, len, 1612 values = []; 1613 1614 // Move all HTML tags (beside the SVG root) of the container 1615 // to the foreignObject element inside of the svgRoot node 1616 // Problem: 1617 // input values are not copied. This can be verified by looking at an innerHTML output 1618 // of an input element. Therefore, we do it "by hand". 1619 if (this.container.hasChildNodes() && Type.exists(this.foreignObjLayer)) { 1620 if (!ignoreTexts) { 1621 this.foreignObjLayer.setAttribute('display', 'inline'); 1622 } 1623 while (svgRoot.nextSibling) { 1624 1625 // Copy all value attributes 1626 values = values.concat(this._getValuesOfDOMElements(svgRoot.nextSibling)); 1627 1628 this.foreignObjLayer.appendChild(svgRoot.nextSibling); 1629 } 1630 } 1631 1632 this._getImgDataURL(svgRoot); 1633 1634 // Convert the SVG graphic into a string containing SVG code 1635 svgRoot.setAttribute("xmlns", "http://www.w3.org/2000/svg"); 1636 svg = new XMLSerializer().serializeToString(svgRoot); 1637 1638 if (ignoreTexts !== true) { 1639 // Handle SVG texts 1640 // Insert all value attributes back into the svg string 1641 len = values.length; 1642 for (i = 0; i < len; i++) { 1643 svg = svg.replace('id="' + values[i][0] + '"', 'id="' + values[i][0] + '" value="' + values[i][1] + '"'); 1644 } 1645 } 1646 1647 // if (false) { 1648 // // Debug: use example svg image 1649 // svg = '<svg xmlns="http://www.w3.org/2000/svg" version="1.0" width="220" height="220"><rect width="66" height="30" x="21" y="32" stroke="#204a87" stroke-width="2" fill="none" /></svg>'; 1650 // } 1651 1652 // In IE we have to remove the namespace again. 1653 if ((svg.match(/xmlns=\"http:\/\/www.w3.org\/2000\/svg\"/g) || []).length > 1) { 1654 svg = svg.replace(/xmlns=\"http:\/\/www.w3.org\/2000\/svg\"/g, ''); 1655 } 1656 1657 // Safari fails if the svg string contains a " " 1658 // Obsolete with Safari 12+ 1659 svg = svg.replace(/ /g, ' '); 1660 1661 // Move all HTML tags back from 1662 // the foreignObject element to the container 1663 if (Type.exists(this.foreignObjLayer) && this.foreignObjLayer.hasChildNodes()) { 1664 // Restore all HTML elements 1665 while (this.foreignObjLayer.firstChild) { 1666 this.container.appendChild(this.foreignObjLayer.firstChild); 1667 } 1668 this.foreignObjLayer.setAttribute("display", "none"); 1669 } 1670 1671 return 'data:image/svg+xml;base64,' + btoa(unescape(encodeURIComponent(svg))); 1672 }, 1673 1674 /** 1675 * Convert the SVG construction into an HTML canvas image. 1676 * This works for all SVG supporting browsers. Implemented as Promise. 1677 * <p> 1678 * For IE, it is realized as function. 1679 * It works from version 9, with the exception that HTML texts 1680 * are ignored on IE. The drawing is done with a delay of 1681 * 200 ms. Otherwise there would be problems with IE. 1682 * 1683 * @param {String} canvasId Id of an HTML canvas element 1684 * @param {Number} w Width in pixel of the dumped image, i.e. of the canvas tag. 1685 * @param {Number} h Height in pixel of the dumped image, i.e. of the canvas tag. 1686 * @param {Boolean} ignoreTexts If true, the foreignObject tag is taken out from the SVG root. 1687 * This is necessary for older versions of Safari. Default: false 1688 * @returns {Promise} Promise object 1689 * 1690 * @example 1691 * board.renderer.dumpToCanvas('canvas').then(function() { console.log('done'); }); 1692 * 1693 * @example 1694 * // IE 11 example: 1695 * board.renderer.dumpToCanvas('canvas'); 1696 * setTimeout(function() { console.log('done'); }, 400); 1697 */ 1698 dumpToCanvas: function (canvasId, w, h, ignoreTexts) { 1699 var //svgRoot = this.svgRoot, 1700 svg, tmpImg, cv, ctx; 1701 // wOrg, hOrg; 1702 1703 // wOrg = svgRoot.getAttribute('width'); 1704 // hOrg = svgRoot.getAttribute('height'); 1705 1706 // Prepare the canvas element 1707 cv = document.getElementById(canvasId); 1708 // Clear the canvas 1709 cv.width = cv.width; 1710 ctx = cv.getContext("2d"); 1711 if (w !== undefined && h !== undefined) { 1712 cv.style.width = parseFloat(w) + 'px'; 1713 cv.style.height = parseFloat(h) + 'px'; 1714 // Scale twice the CSS size to make the image crisp 1715 // cv.setAttribute('width', 2 * parseFloat(wOrg)); 1716 // cv.setAttribute('height', 2 * parseFloat(hOrg)); 1717 // ctx.scale(2 * wOrg / w, 2 * hOrg / h); 1718 cv.setAttribute('width', parseFloat(w)); 1719 cv.setAttribute('height', parseFloat(h)); 1720 } 1721 1722 // Display the SVG string as data-uri in an HTML img. 1723 tmpImg = new Image(); 1724 svg = this.dumpToDataURI(ignoreTexts); 1725 tmpImg.src = svg; 1726 1727 // Finally, draw the HTML img in the canvas. 1728 if (!('Promise' in window)) { 1729 tmpImg.onload = function () { 1730 // IE needs a pause... 1731 // Seems to be broken 1732 window.setTimeout(function() { 1733 try { 1734 ctx.drawImage(tmpImg, 0, 0, w, h); 1735 } catch (err) { 1736 console.log("screenshots not longer supported on IE"); 1737 } 1738 }, 200); 1739 }; 1740 return this; 1741 } 1742 1743 return new Promise(function(resolve, reject) { 1744 try { 1745 tmpImg.onload = function () { 1746 ctx.drawImage(tmpImg, 0, 0, w, h); 1747 resolve(); 1748 }; 1749 } catch (e) { 1750 reject(e); 1751 } 1752 }); 1753 1754 }, 1755 1756 /** 1757 * Display SVG image in html img-tag which enables 1758 * easy download for the user. 1759 * 1760 * Support: 1761 * <ul> 1762 * <li> IE: No 1763 * <li> Edge: full 1764 * <li>Firefox: full 1765 * <li> Chrome: full 1766 * <li> Safari: full (No text support in versions prior to 12). 1767 * </ul> 1768 * 1769 * @param {JXG.Board} board Link to the board. 1770 * @param {String} imgId Optional id of an img object. If given and different from the empty string, 1771 * the screenshot is copied to this img object. The width and height will be set to the values of the 1772 * JSXGraph container. 1773 * @param {Boolean} ignoreTexts If set to true, the foreignObject is taken out of the 1774 * SVGRoot and texts are not displayed. This is mandatory for Safari. Default: false 1775 * @return {Object} the svg renderer object 1776 */ 1777 screenshot: function (board, imgId, ignoreTexts) { 1778 var node, 1779 doc = this.container.ownerDocument, 1780 parent = this.container.parentNode, 1781 cPos, 1782 canvas, id, 1783 img, 1784 button, buttonText, 1785 w, h, 1786 bas = board.attr.screenshot, 1787 zbar, zbarDisplay, cssTxt, 1788 newImg = false, 1789 _copyCanvasToImg, 1790 isDebug = false; 1791 1792 if (this.type === 'no') { 1793 return this; 1794 } 1795 1796 w = bas.scale * parseFloat(this.container.style.width); 1797 h = bas.scale * parseFloat(this.container.style.height); 1798 1799 if (imgId === undefined || imgId === '') { 1800 newImg = true; 1801 img = new Image(); //doc.createElement('img'); 1802 img.style.width = w + 'px'; 1803 img.style.height = h + 'px'; 1804 } else { 1805 newImg = false; 1806 img = doc.getElementById(imgId); 1807 } 1808 // img.crossOrigin = 'anonymous'; 1809 1810 // Create div which contains canvas element and close button 1811 if (newImg) { 1812 node = doc.createElement('div'); 1813 node.style.cssText = bas.css; 1814 node.style.width = (w) + 'px'; 1815 node.style.height = (h) + 'px'; 1816 node.style.zIndex = this.container.style.zIndex + 120; 1817 1818 // Try to position the div exactly over the JSXGraph board 1819 node.style.position = 'absolute'; 1820 node.style.top = this.container.offsetTop + 'px'; 1821 node.style.left = this.container.offsetLeft + 'px'; 1822 } 1823 1824 if (!isDebug) { 1825 // Create canvas element and add it to the DOM 1826 // It will be removed after the image has been stored. 1827 canvas = doc.createElement('canvas'); 1828 id = Math.random().toString(36).substr(2, 5); 1829 canvas.setAttribute('id', id); 1830 canvas.setAttribute('width', w); 1831 canvas.setAttribute('height', h); 1832 canvas.style.width = w + 'px'; 1833 canvas.style.height = w + 'px'; 1834 canvas.style.display = 'none'; 1835 parent.appendChild(canvas); 1836 } else { 1837 // Debug: use canvas element 'jxgbox_canvas' from jsxdev/dump.html 1838 id = 'jxgbox_canvas'; 1839 canvas = document.getElementById(id); 1840 } 1841 1842 if (newImg) { 1843 // Create close button 1844 button = doc.createElement('span'); 1845 buttonText = doc.createTextNode('\u2716'); 1846 button.style.cssText = bas.cssButton; 1847 button.appendChild(buttonText); 1848 button.onclick = function () { 1849 node.parentNode.removeChild(node); 1850 }; 1851 1852 // Add all nodes 1853 node.appendChild(img); 1854 node.appendChild(button); 1855 parent.insertBefore(node, this.container.nextSibling); 1856 } 1857 1858 // Hide navigation bar in board 1859 zbar = document.getElementById(this.container.id + '_navigationbar'); 1860 if (Type.exists(zbar)) { 1861 zbarDisplay = zbar.style.display; 1862 zbar.style.display = 'none'; 1863 } 1864 1865 _copyCanvasToImg = function() { 1866 // Show image in img tag 1867 img.src = canvas.toDataURL('image/png'); 1868 1869 // Remove canvas node 1870 if (!isDebug) { 1871 parent.removeChild(canvas); 1872 } 1873 }; 1874 1875 // Create screenshot in image element 1876 if ('Promise' in window) { 1877 this.dumpToCanvas(id, w, h, ignoreTexts).then(_copyCanvasToImg); 1878 } else { 1879 // IE 1880 this.dumpToCanvas(id, w, h, ignoreTexts); 1881 window.setTimeout(_copyCanvasToImg, 200); 1882 } 1883 1884 // Show navigation bar in board 1885 if (Type.exists(zbar)) { 1886 zbar.style.display = zbarDisplay; 1887 } 1888 1889 return this; 1890 } 1891 1892 }); 1893 1894 return JXG.SVGRenderer; 1895 }); 1896