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