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