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