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