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