1 /*
  2     Copyright 2008-2025
  3         Matthias Ehmann,
  4         Michael Gerhaeuser,
  5         Carsten Miller,
  6         Bianca Valentin,
  7         Alfred Wassermann,
  8         Peter Wilfahrt
  9 
 10     This file is part of JSXGraph.
 11 
 12     JSXGraph is free software dual licensed under the GNU LGPL or MIT License.
 13 
 14     You can redistribute it and/or modify it under the terms of the
 15 
 16       * GNU Lesser General Public License as published by
 17         the Free Software Foundation, either version 3 of the License, or
 18         (at your option) any later version
 19       OR
 20       * MIT License: https://github.com/jsxgraph/jsxgraph/blob/master/LICENSE.MIT
 21 
 22     JSXGraph is distributed in the hope that it will be useful,
 23     but WITHOUT ANY WARRANTY; without even the implied warranty of
 24     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 25     GNU Lesser General Public License for more details.
 26 
 27     You should have received a copy of the GNU Lesser General Public License and
 28     the MIT License along with JSXGraph. If not, see <https://www.gnu.org/licenses/>
 29     and <https://opensource.org/licenses/MIT/>.
 30  */
 31 
 32 /*global JXG: true, define: true, AMprocessNode: true, MathJax: true, document: true, window: true */
 33 
 34 /*
 35     nomen:    Allow underscores to indicate private class members. Might be replaced by local variables.
 36     plusplus: Only allowed in for-loops
 37     newcap:   AsciiMathMl exposes non-constructor functions beginning with upper case letters
 38 */
 39 /*jslint nomen: true, plusplus: true, newcap: true, unparam: true*/
 40 /*eslint no-unused-vars: "off"*/
 41 
 42 /**
 43  * @fileoverview JSXGraph can use various technologies to render the contents of a construction, e.g.
 44  * SVG, VML, and HTML5 Canvas. To accomplish this, The rendering and the logic and control mechanisms
 45  * are completely separated from each other. Every rendering technology has it's own class, called
 46  * Renderer, e.g. SVGRenderer for SVG, the same for VML and Canvas. The common base for all available
 47  * renderers is the class AbstractRenderer defined in this file.
 48  */
 49 
 50 import JXG from "../jxg.js";
 51 import Options from "../options.js";
 52 import Coords from "../base/coords.js";
 53 import Const from "../base/constants.js";
 54 import Mat from "../math/math.js";
 55 import Geometry from "../math/geometry.js";
 56 import Type from "../utils/type.js";
 57 import Env from "../utils/env.js";
 58 
 59 /**
 60  * <p>This class defines the interface to the graphics part of JSXGraph. This class is an abstract class, it
 61  * actually does not render anything. This is up to the {@link JXG.SVGRenderer}, {@link JXG.VMLRenderer},
 62  * and {@link JXG.CanvasRenderer} classes. We strongly discourage you from using the methods in these classes
 63  * directly. Only the methods which are defined in this class and are not marked as private are guaranteed
 64  * to exist in any renderer instance you can access via {@link JXG.Board#renderer}. But not all methods may
 65  * work as expected.</p>
 66  * <p>The methods of this renderer can be divided into different categories:
 67  * <dl>
 68  *     <dt>Draw basic elements</dt>
 69  *     <dd>In this category we find methods to draw basic elements like {@link JXG.Point}, {@link JXG.Line},
 70  *     and {@link JXG.Curve} as well as assisting methods tightly bound to these basic painters. You do not
 71  *     need to implement these methods in a descendant renderer but instead implement the primitive drawing
 72  *     methods described below. This approach is encouraged when you're using a XML based rendering engine
 73  *     like VML and SVG. If you want to use a bitmap based rendering technique you are supposed to override
 74  *     these methods instead of the primitive drawing methods.</dd>
 75  *     <dt>Draw primitives</dt>
 76  *     <dd>This category summarizes methods to handle primitive nodes. As creation and management of these nodes
 77  *     is different among different the rendering techniques most of these methods are purely virtual and need
 78  *     proper implementation if you choose to not overwrite the basic element drawing methods.</dd>
 79  *     <dt>Attribute manipulation</dt>
 80  *     <dd>In XML based renders you have to manipulate XML nodes and their attributes to change the graphics.
 81  *     For that purpose attribute manipulation methods are defined to set the color, opacity, and other things.
 82  *     Please note that some of these methods are required in bitmap based renderers, too, because some elements
 83  *     like {@link JXG.Text} can be HTML nodes floating over the construction.</dd>
 84  *     <dt>Renderer control</dt>
 85  *     <dd>Methods to clear the drawing board or to stop and to resume the rendering engine.</dd>
 86  * </dl></p>
 87  * @class JXG.AbstractRenderer
 88  * @constructor
 89  * @see JXG.SVGRenderer
 90  * @see JXG.VMLRenderer
 91  * @see JXG.CanvasRenderer
 92  */
 93 JXG.AbstractRenderer = function () {
 94     // WHY THIS IS A CLASS INSTEAD OF A SINGLETON OBJECT:
 95     //
 96     // The renderers need to keep track of some stuff which is not always the same on different boards,
 97     // like enhancedRendering, reference to the container object, and resolution in VML. Sure, those
 98     // things could be stored in board. But they are rendering related and JXG.Board is already very
 99     // very big.
100     //
101     // And we can't save the rendering related data in {SVG,VML,Canvas}Renderer and make only the
102     // JXG.AbstractRenderer a singleton because of that:
103     //
104     // Given an object o with property a set to true
105     //     var o = {a: true};
106     // and a class c doing nothing
107     //     c = function() {};
108     // Set c's prototype to o
109     //     c.prototype = o;
110     // and create an instance of c we get i.a to be true
111     //     i = new c();
112     //     i.a;
113     //     > true
114     // But we can overwrite this property via
115     //     c.prototype.a = false;
116     //     i.a;
117     //     > false
118 
119     /**
120      * The vertical offset for {@link Text} elements. Every {@link Text} element will
121      * be placed this amount of pixels below the user given coordinates.
122      * @type Number
123      * @default 0
124      */
125     this.vOffsetText = 0;
126 
127     /**
128      * If this property is set to <tt>true</tt> the visual properties of the elements are updated
129      * on every update. Visual properties means: All the stuff stored in the
130      * {@link JXG.GeometryElement#visProp} property won't be set if enhancedRendering is <tt>false</tt>
131      * @type Boolean
132      * @default true
133      */
134     this.enhancedRendering = true;
135 
136     /**
137      * The HTML element that stores the JSXGraph board in it.
138      * @type Node
139      */
140     this.container = null;
141 
142     /**
143      * This is used to easily determine which renderer we are using
144      * @example if (board.renderer.type === 'vml') {
145      *     // do something
146      * }
147      * @type String
148      */
149     this.type = "";
150 
151     /**
152      * True if the browsers' SVG engine supports foreignObject.
153      * Not supported browsers are IE 9 - 11.
154      * It is tested in svg renderer.
155      *
156      * @type Boolean
157      * @private
158      */
159     this.supportsForeignObject = false;
160 
161     /**
162      * Defines dash patterns. Sizes are in pixel.
163      * Defined styles are:
164      * <ol>
165      * <li> 2 dash, 2 space</li>
166      * <li> 5 dash, 5 space</li>
167      * <li> 10 dash, 10 space</li>
168      * <li> 20 dash, 20 space</li>
169      * <li> 20 dash, 10 space, 10 dash, 10 space</li>
170      * <li> 20 dash, 5 space, 10 dash, 5 space</li>
171      * <li> 0 dash, 5 space (dotted line)</li>
172      * </ol>
173      * This means, the numbering is <b>1-based</b>.
174      * Solid lines are set with dash:0.
175      * If the object's attribute "dashScale:true" the dash pattern is multiplied by
176      * strokeWidth / 2.
177      *
178      * @type Array
179      * @default [[2, 2], [5, 5], [10, 10], [20, 20], [20, 10, 10, 10], [20, 5, 10, 5], [0, 5]]
180      * @see JXG.GeometryElement#dash
181      * @see JXG.GeometryElement#dashScale
182      */
183     this.dashArray = [
184         [2, 2],
185         [5, 5],
186         [10, 10],
187         [20, 20],
188         [20, 10, 10, 10],
189         [20, 5, 10, 5],
190         [0, 5]
191     ];
192 };
193 
194 JXG.extend(
195     JXG.AbstractRenderer.prototype,
196     /** @lends JXG.AbstractRenderer.prototype */ {
197 
198         /* ********* Private methods *********** */
199 
200         /**
201          * Update visual properties, but only if {@link JXG.AbstractRenderer#enhancedRendering} or <tt>enhanced</tt> is set to true.
202          * @param {JXG.GeometryElement} el The element to update
203          * @param {Object} [not={}] Select properties you don't want to be updated: <tt>{fill: true, dash: true}</tt> updates
204          * everything except for fill and dash. Possible values are <tt>stroke, fill, dash, shadow, gradient</tt>.
205          * @param {Boolean} [enhanced=false] If true, {@link JXG.AbstractRenderer#enhancedRendering} is assumed to be true.
206          * @private
207          */
208         _updateVisual: function (el, not, enhanced) {
209             if (enhanced || this.enhancedRendering) {
210                 not = not || {};
211 
212                 this.setObjectTransition(el);
213                 if (!el.evalVisProp('draft')) {
214                     if (!not.stroke) {
215                         if (el.highlighted) {
216                             this.setObjectStrokeColor(
217                                 el,
218                                 el.evalVisProp('highlightstrokecolor'),
219                                 el.evalVisProp('highlightstrokeopacity')
220                             );
221                             this.setObjectStrokeWidth(el, el.evalVisProp('highlightstrokewidth'));
222                         } else {
223                             this.setObjectStrokeColor(
224                                 el,
225                                 el.evalVisProp('strokecolor'),
226                                 el.evalVisProp('strokeopacity')
227                             );
228                             this.setObjectStrokeWidth(el, el.evalVisProp('strokewidth'));
229                         }
230                     }
231 
232                     if (!not.fill) {
233                         if (el.highlighted) {
234                             this.setObjectFillColor(
235                                 el,
236                                 el.evalVisProp('highlightfillcolor'),
237                                 el.evalVisProp('highlightfillopacity')
238                             );
239                         } else {
240                             this.setObjectFillColor(
241                                 el,
242                                 el.evalVisProp('fillcolor'),
243                                 el.evalVisProp('fillopacity')
244                             );
245                         }
246                     }
247 
248                     if (!not.dash) {
249                         this.setDashStyle(el, el.visProp);
250                     }
251 
252                     if (!not.shadow) {
253                         this.setShadow(el);
254                     }
255 
256                     // if (!not.gradient) {
257                     //     // this.setGradient(el);
258                     //     this.setShadow(el);
259                     // }
260 
261                     if (!not.tabindex) {
262                         this.setTabindex(el);
263                     }
264                 } else {
265                     this.setDraft(el);
266                 }
267 
268                 if (el.highlighted) {
269                     this.setCssClass(el, el.evalVisProp('highlightcssclass'));
270                 } else {
271                     this.setCssClass(el, el.evalVisProp('cssclass'));
272                 }
273 
274                 if (el.evalVisProp('aria.enabled')) {
275                     this.setARIA(el);
276                 }
277             }
278         },
279 
280         /**
281          * Get information if element is highlighted.
282          * @param {JXG.GeometryElement} el The element which is tested for being highlighted.
283          * @returns {String} 'highlight' if highlighted, otherwise the ampty string '' is returned.
284          * @private
285          */
286         _getHighlighted: function (el) {
287             var isTrace = false,
288                 hl;
289 
290             if (!Type.exists(el.board) || !Type.exists(el.board.highlightedObjects)) {
291                 // This case handles trace elements.
292                 // To make them work, we simply neglect highlighting.
293                 isTrace = true;
294             }
295 
296             if (!isTrace && Type.exists(el.board.highlightedObjects[el.id])) {
297                 hl = "highlight";
298             } else {
299                 hl = "";
300             }
301             return hl;
302         },
303 
304         /* ********* Point related stuff *********** */
305 
306         /**
307          * Draws a point on the {@link JXG.Board}.
308          * @param {JXG.Point} el Reference to a {@link JXG.Point} object that has to be drawn.
309          * @see Point
310          * @see JXG.Point
311          * @see JXG.AbstractRenderer#updatePoint
312          * @see JXG.AbstractRenderer#changePointStyle
313          */
314         drawPoint: function (el) {
315             var prim,
316                 // Sometimes el is not a real point and lacks the methods of a JXG.Point instance,
317                 // in these cases to not use el directly.
318                 face = Options.normalizePointFace(el.evalVisProp('face'));
319 
320             // Determine how the point looks like
321             if (face === "o") {
322                 prim = "ellipse";
323             } else if (face === "[]") {
324                 prim = "rect";
325             } else {
326                 // cross/x, diamond/<>, triangleup/A/^, triangledown/v, triangleleft/<,
327                 // triangleright/>, plus/+, |, -
328                 prim = "path";
329             }
330 
331             el.rendNode = this.appendChildPrim(
332                 this.createPrim(prim, el.id),
333                 el.evalVisProp('layer')
334             );
335             this.appendNodesToElement(el, prim);
336 
337             // Adjust visual properties
338             this._updateVisual(el, { dash: true, shadow: true }, true);
339 
340             // By now we only created the xml nodes and set some styles, in updatePoint
341             // the attributes are filled with data.
342             this.updatePoint(el);
343         },
344 
345         /**
346          * Updates visual appearance of the renderer element assigned to the given {@link JXG.Point}.
347          * @param {JXG.Point} el Reference to a {@link JXG.Point} object, that has to be updated.
348          * @see Point
349          * @see JXG.Point
350          * @see JXG.AbstractRenderer#drawPoint
351          * @see JXG.AbstractRenderer#changePointStyle
352          */
353         updatePoint: function (el) {
354             var size = el.evalVisProp('size'),
355                 // sometimes el is not a real point and lacks the methods of a JXG.Point instance,
356                 // in these cases to not use el directly.
357                 face = Options.normalizePointFace(el.evalVisProp('face')),
358                 unit = el.evalVisProp('sizeunit'),
359                 zoom = el.evalVisProp('zoom'),
360                 s1;
361 
362             if (!isNaN(el.coords.scrCoords[2] + el.coords.scrCoords[1])) {
363                 if (unit === "user") {
364                     size *= Math.sqrt(Math.abs(el.board.unitX * el.board.unitY));
365                 }
366                 size *= !el.board || !zoom ? 1.0 : Math.sqrt(el.board.zoomX * el.board.zoomY);
367                 s1 = size === 0 ? 0 : size + 1;
368 
369                 if (face === "o") {
370                     // circle
371                     this.updateEllipsePrim(
372                         el.rendNode,
373                         el.coords.scrCoords[1],
374                         el.coords.scrCoords[2],
375                         s1,
376                         s1
377                     );
378                 } else if (face === "[]") {
379                     // rectangle
380                     this.updateRectPrim(
381                         el.rendNode,
382                         el.coords.scrCoords[1] - size,
383                         el.coords.scrCoords[2] - size,
384                         size * 2,
385                         size * 2
386                     );
387                 } else {
388                     // x, +, <>, <<>>, ^, v, <, >
389                     this.updatePathPrim(
390                         el.rendNode,
391                         this.updatePathStringPoint(el, size, face),
392                         el.board
393                     );
394                 }
395                 this._updateVisual(el, { dash: false, shadow: false });
396                 this.setShadow(el);
397             }
398         },
399 
400         /**
401          * Changes the style of a {@link JXG.Point}. This is required because the point styles differ in what
402          * elements have to be drawn, e.g. if the point is marked by a "x" or a "+" two lines are drawn, if
403          * it's marked by spot a circle is drawn. This method removes the old renderer element(s) and creates
404          * the new one(s).
405          * @param {JXG.Point} el Reference to a {@link JXG.Point} object, that's style is changed.
406          * @see Point
407          * @see JXG.Point
408          * @see JXG.AbstractRenderer#updatePoint
409          * @see JXG.AbstractRenderer#drawPoint
410          */
411         changePointStyle: function (el) {
412             var node = this.getElementById(el.id);
413 
414             // remove the existing point rendering node
415             if (Type.exists(node)) {
416                 this.remove(node);
417             }
418 
419             // and make a new one
420             this.drawPoint(el);
421             Type.clearVisPropOld(el);
422 
423             if (!el.visPropCalc.visible) {
424                 this.display(el, false);
425             }
426 
427             if (el.evalVisProp('draft')) {
428                 this.setDraft(el);
429             }
430         },
431 
432         /* ********* Line related stuff *********** */
433 
434         /**
435          * Draws a line on the {@link JXG.Board}.
436          * @param {JXG.Line} el Reference to a line object, that has to be drawn.
437          * @see Line
438          * @see JXG.Line
439          * @see JXG.AbstractRenderer#updateLine
440          */
441         drawLine: function (el) {
442             el.rendNode = this.appendChildPrim(
443                 this.createPrim("line", el.id),
444                 el.evalVisProp('layer')
445             );
446             this.appendNodesToElement(el, "lines");
447             this.updateLine(el);
448         },
449 
450         /**
451          * Updates visual appearance of the renderer element assigned to the given {@link JXG.Line}.
452          * @param {JXG.Line} el Reference to the {@link JXG.Line} object that has to be updated.
453          * @see Line
454          * @see JXG.Line
455          * @see JXG.AbstractRenderer#drawLine
456          */
457         updateLine: function (el) {
458             this._updateVisual(el);
459             this.updatePathWithArrowHeads(el); // Calls the renderer primitive
460             this.setLineCap(el);
461         },
462 
463         /* ********* Curve related stuff *********** */
464 
465         /**
466          * Draws a {@link JXG.Curve} on the {@link JXG.Board}.
467          * @param {JXG.Curve} el Reference to a graph object, that has to be plotted.
468          * @see Curve
469          * @see JXG.Curve
470          * @see JXG.AbstractRenderer#updateCurve
471          */
472         drawCurve: function (el) {
473             el.rendNode = this.appendChildPrim(
474                 this.createPrim("path", el.id),
475                 el.evalVisProp('layer')
476             );
477             this.appendNodesToElement(el, "path");
478             this.updateCurve(el);
479         },
480 
481         /**
482          * Updates visual appearance of the renderer element assigned to the given {@link JXG.Curve}.
483          * @param {JXG.Curve} el Reference to a {@link JXG.Curve} object, that has to be updated.
484          * @see Curve
485          * @see JXG.Curve
486          * @see JXG.AbstractRenderer#drawCurve
487          */
488         updateCurve: function (el) {
489             this._updateVisual(el);
490             this.updatePathWithArrowHeads(el); // Calls the renderer primitive
491             this.setLineCap(el);
492         },
493 
494         /* ********* Arrow heads and related stuff *********** */
495 
496         /**
497          * Handles arrow heads of a line or curve element and calls the renderer primitive.
498          *
499          * @param {JXG.GeometryElement} el Reference to a line or curve object that has to be drawn.
500          * @param {Boolean} doHighlight
501          *
502          * @private
503          * @see Line
504          * @see JXG.Line
505          * @see Curve
506          * @see JXG.Curve
507          * @see JXG.AbstractRenderer#updateLine
508          * @see JXG.AbstractRenderer#updateCurve
509          * @see JXG.AbstractRenderer#makeArrows
510          * @see JXG.AbstractRenderer#getArrowHeadData
511          */
512         updatePathWithArrowHeads: function (el, doHighlight) {
513             var hl = doHighlight ? 'highlight' : '',
514                 w,
515                 arrowData;
516 
517             if (doHighlight && el.evalVisProp('highlightstrokewidth')) {
518                 w = Math.max(
519                     el.evalVisProp('highlightstrokewidth'),
520                     el.evalVisProp('strokewidth')
521                 );
522             } else {
523                 w = el.evalVisProp('strokewidth');
524             }
525 
526             // Get information if there are arrow heads and how large they are.
527             arrowData = this.getArrowHeadData(el, w, hl);
528 
529             // Create the SVG nodes if necessary
530             this.makeArrows(el, arrowData);
531 
532             // Draw the paths with arrow heads
533             if (el.elementClass === Const.OBJECT_CLASS_LINE) {
534                 this.updateLineWithEndings(el, arrowData);
535             } else if (el.elementClass === Const.OBJECT_CLASS_CURVE) {
536                 this.updatePath(el);
537             }
538 
539             this.setArrowSize(el, arrowData);
540         },
541 
542         /**
543          * This method determines some data about the line endings of this element.
544          * If there are arrow heads, the offset is determined so that no parts of the line stroke
545          * lap over the arrow head.
546          * <p>
547          * The returned object also contains the types of the arrow heads.
548          *
549          * @param {JXG.GeometryElement} el JSXGraph line or curve element
550          * @param {Number} strokewidth strokewidth of the element
551          * @param {String} hl Ither 'highlight' or empty string
552          * @returns {Object} object containing the data
553          *
554          * @private
555          */
556         getArrowHeadData: function (el, strokewidth, hl) {
557             var minlen = Mat.eps,
558                 typeFirst,
559                 typeLast,
560                 offFirst = 0,
561                 offLast = 0,
562                 sizeFirst = 0,
563                 sizeLast = 0,
564                 ev_fa = el.evalVisProp('firstarrow'),
565                 ev_la = el.evalVisProp('lastarrow'),
566                 off,
567                 size;
568 
569             /*
570                Handle arrow heads.
571 
572                The default arrow head is an isosceles triangle with base length 10 units and height 10 units.
573                These 10 units are scaled to strokeWidth * arrowSize pixels.
574             */
575             if (ev_fa || ev_la) {
576                 if (Type.exists(ev_fa.type)) {
577                     typeFirst = el.eval(ev_fa.type);
578                 } else {
579                     if (el.elementClass === Const.OBJECT_CLASS_LINE) {
580                         typeFirst = 1;
581                     } else {
582                         typeFirst = 7;
583                     }
584                 }
585                 if (Type.exists(ev_la.type)) {
586                     typeLast = el.eval(ev_la.type);
587                 } else {
588                     if (el.elementClass === Const.OBJECT_CLASS_LINE) {
589                         typeLast = 1;
590                     } else {
591                         typeLast = 7;
592                     }
593                 }
594 
595                 if (ev_fa) {
596                     size = 6;
597                     if (Type.exists(ev_fa.size)) {
598                         size = el.eval(ev_fa.size);
599                     }
600                     if (hl !== "" && Type.exists(ev_fa[hl + "size"])) {
601                         size = el.eval(ev_fa[hl + "size"]);
602                     }
603 
604                     off = strokewidth * size;
605                     if (typeFirst === 2) {
606                         off *= 0.5;
607                         minlen += strokewidth * size;
608                     } else if (typeFirst === 3) {
609                         off = (strokewidth * size) / 3;
610                         minlen += strokewidth;
611                     } else if (typeFirst === 4 || typeFirst === 5 || typeFirst === 6) {
612                         off = (strokewidth * size) / 1.5;
613                         minlen += strokewidth * size;
614                     } else if (typeFirst === 7) {
615                         off = 0;
616                         size = 10;
617                         minlen += strokewidth;
618                     } else {
619                         minlen += strokewidth * size;
620                     }
621                     offFirst += off;
622                     sizeFirst = size;
623                 }
624 
625                 if (ev_la) {
626                     size = 6;
627                     if (Type.exists(ev_la.size)) {
628                         size = el.eval(ev_la.size);
629                     }
630                     if (hl !== "" && Type.exists(ev_la[hl + "size"])) {
631                         size = el.eval(ev_la[hl + "size"]);
632                     }
633                     off = strokewidth * size;
634                     if (typeLast === 2) {
635                         off *= 0.5;
636                         minlen += strokewidth * size;
637                     } else if (typeLast === 3) {
638                         off = (strokewidth * size) / 3;
639                         minlen += strokewidth;
640                     } else if (typeLast === 4 || typeLast === 5 || typeLast === 6) {
641                         off = (strokewidth * size) / 1.5;
642                         minlen += strokewidth * size;
643                     } else if (typeLast === 7) {
644                         off = 0;
645                         size = 10;
646                         minlen += strokewidth;
647                     } else {
648                         minlen += strokewidth * size;
649                     }
650                     offLast += off;
651                     sizeLast = size;
652                 }
653             }
654             el.visPropCalc.typeFirst = typeFirst;
655             el.visPropCalc.typeLast = typeLast;
656 
657             return {
658                 evFirst: ev_fa,
659                 evLast: ev_la,
660                 typeFirst: typeFirst,
661                 typeLast: typeLast,
662                 offFirst: offFirst,
663                 offLast: offLast,
664                 sizeFirst: sizeFirst,
665                 sizeLast: sizeLast,
666                 showFirst: 1, // Show arrow head. 0 if the distance is too small
667                 showLast: 1, // Show arrow head. 0 if the distance is too small
668                 minLen: minlen,
669                 strokeWidth: strokewidth
670             };
671         },
672 
673         /**
674          * Corrects the line length if there are arrow heads, such that
675          * the arrow ends exactly at the intended position.
676          * Calls the renderer method to draw the line.
677          *
678          * @param {JXG.Line} el Reference to a line object, that has to be drawn
679          * @param {Object} arrowData Data concerning possible arrow heads
680          *
681          * @returns {JXG.AbstractRenderer} Reference to the renderer
682          *
683          * @private
684          * @see Line
685          * @see JXG.Line
686          * @see JXG.AbstractRenderer#updateLine
687          * @see JXG.AbstractRenderer#getPositionArrowHead
688          *
689          */
690         updateLineWithEndings: function (el, arrowData) {
691             var c1,
692                 c2,
693                 // useTotalLength = true,
694                 margin = null;
695 
696             c1 = new Coords(Const.COORDS_BY_USER, el.point1.coords.usrCoords, el.board);
697             c2 = new Coords(Const.COORDS_BY_USER, el.point2.coords.usrCoords, el.board);
698             margin = el.evalVisProp('margin');
699             Geometry.calcStraight(el, c1, c2, margin);
700 
701             this.handleTouchpoints(el, c1, c2, arrowData);
702             this.getPositionArrowHead(el, c1, c2, arrowData);
703 
704             this.updateLinePrim(
705                 el.rendNode,
706                 c1.scrCoords[1],
707                 c1.scrCoords[2],
708                 c2.scrCoords[1],
709                 c2.scrCoords[2],
710                 el.board
711             );
712 
713             return this;
714         },
715 
716         /**
717          *
718          * Calls the renderer method to draw a curve.
719          *
720          * @param {JXG.GeometryElement} el Reference to a line object, that has to be drawn.
721          * @returns {JXG.AbstractRenderer} Reference to the renderer
722          *
723          * @private
724          * @see Curve
725          * @see JXG.Curve
726          * @see JXG.AbstractRenderer#updateCurve
727          *
728          */
729         updatePath: function (el) {
730             if (el.evalVisProp('handdrawing')) {
731                 this.updatePathPrim(el.rendNode, this.updatePathStringBezierPrim(el), el.board);
732             } else {
733                 this.updatePathPrim(el.rendNode, this.updatePathStringPrim(el), el.board);
734             }
735 
736             return this;
737         },
738 
739         /**
740          * Shorten the length of a line element such that the arrow head touches
741          * the start or end point and such that the arrow head ends exactly
742          * at the start / end position of the line.
743          * <p>
744          * The Coords objects c1 and c2 are changed in place. In object a, the Boolean properties
745          * 'showFirst' and 'showLast' are set.
746          *
747          * @param  {JXG.Line} el Reference to the line object that gets arrow heads.
748          * @param  {JXG.Coords} c1  Coords of the first point of the line (after {@link JXG.Math.Geometry#calcStraight}).
749          * @param  {JXG.Coords} c2  Coords of the second point of the line (after {@link JXG.Math.Geometry#calcStraight}).
750          * @param  {Object}  a Object { evFirst: Boolean, evLast: Boolean} containing information about arrow heads.
751          * @see JXG.AbstractRenderer#getArrowHeadData
752          *
753          */
754         getPositionArrowHead: function (el, c1, c2, a) {
755             var d, d1x, d1y, d2x, d2y;
756 
757             //    Handle arrow heads.
758 
759             //    The default arrow head (type==1) is an isosceles triangle with base length 10 units and height 10 units.
760             //    These 10 units are scaled to strokeWidth * arrowSize pixels.
761             if (a.evFirst || a.evLast) {
762                 // Correct the position of the arrow heads
763                 d1x = d1y = d2x = d2y = 0.0;
764                 d = c1.distance(Const.COORDS_BY_SCREEN, c2);
765 
766                 if (a.evFirst && el.board.renderer.type !== "vml") {
767                     if (d >= a.minLen) {
768                         d1x = ((c2.scrCoords[1] - c1.scrCoords[1]) * a.offFirst) / d;
769                         d1y = ((c2.scrCoords[2] - c1.scrCoords[2]) * a.offFirst) / d;
770                     } else {
771                         a.showFirst = 0;
772                     }
773                 }
774 
775                 if (a.evLast && el.board.renderer.type !== "vml") {
776                     if (d >= a.minLen) {
777                         d2x = ((c2.scrCoords[1] - c1.scrCoords[1]) * a.offLast) / d;
778                         d2y = ((c2.scrCoords[2] - c1.scrCoords[2]) * a.offLast) / d;
779                     } else {
780                         a.showLast = 0;
781                     }
782                 }
783                 c1.setCoordinates(
784                     Const.COORDS_BY_SCREEN,
785                     [c1.scrCoords[1] + d1x, c1.scrCoords[2] + d1y],
786                     false,
787                     true
788                 );
789                 c2.setCoordinates(
790                     Const.COORDS_BY_SCREEN,
791                     [c2.scrCoords[1] - d2x, c2.scrCoords[2] - d2y],
792                     false,
793                     true
794                 );
795             }
796 
797             return this;
798         },
799 
800         /**
801          * Handle touchlastpoint / touchfirstpoint
802          *
803          * @param {JXG.GeometryElement} el
804          * @param {JXG.Coords} c1 Coordinates of the start of the line. The coordinates are changed in place.
805          * @param {JXG.Coords} c2 Coordinates of the end of the line. The coordinates are changed in place.
806          * @param {Object} a
807          * @see JXG.AbstractRenderer#getArrowHeadData
808          */
809         handleTouchpoints: function (el, c1, c2, a) {
810             var s1, s2, d, d1x, d1y, d2x, d2y;
811 
812             if (a.evFirst || a.evLast) {
813                 d = d1x = d1y = d2x = d2y = 0.0;
814 
815                 s1 = el.point1.evalVisProp('size') +
816                     el.point1.evalVisProp('strokewidth');
817 
818                 s2 = el.point2.evalVisProp('size') +
819                     el.point2.evalVisProp('strokewidth');
820 
821                 // Handle touchlastpoint /touchfirstpoint
822                 if (a.evFirst && el.evalVisProp('touchfirstpoint') &&
823                         el.point1.evalVisProp('visible')) {
824                     d = c1.distance(Const.COORDS_BY_SCREEN, c2);
825                     //if (d > s) {
826                     d1x = ((c2.scrCoords[1] - c1.scrCoords[1]) * s1) / d;
827                     d1y = ((c2.scrCoords[2] - c1.scrCoords[2]) * s1) / d;
828                     //}
829                 }
830                 if (a.evLast && el.evalVisProp('touchlastpoint') &&
831                         el.point2.evalVisProp('visible')) {
832                     d = c1.distance(Const.COORDS_BY_SCREEN, c2);
833                     //if (d > s) {
834                     d2x = ((c2.scrCoords[1] - c1.scrCoords[1]) * s2) / d;
835                     d2y = ((c2.scrCoords[2] - c1.scrCoords[2]) * s2) / d;
836                     //}
837                 }
838                 c1.setCoordinates(
839                     Const.COORDS_BY_SCREEN,
840                     [c1.scrCoords[1] + d1x, c1.scrCoords[2] + d1y],
841                     false,
842                     true
843                 );
844                 c2.setCoordinates(
845                     Const.COORDS_BY_SCREEN,
846                     [c2.scrCoords[1] - d2x, c2.scrCoords[2] - d2y],
847                     false,
848                     true
849                 );
850             }
851 
852             return this;
853         },
854 
855         /**
856          * Set the arrow head size.
857          *
858          * @param {JXG.GeometryElement} el Reference to a line or curve object that has to be drawn.
859          * @param {Object} arrowData Data concerning possible arrow heads
860          * @returns {JXG.AbstractRenderer} Reference to the renderer
861          *
862          * @private
863          * @see Line
864          * @see JXG.Line
865          * @see Curve
866          * @see JXG.Curve
867          * @see JXG.AbstractRenderer#updatePathWithArrowHeads
868          * @see JXG.AbstractRenderer#getArrowHeadData
869          */
870         setArrowSize: function (el, a) {
871             if (a.evFirst) {
872                 this._setArrowWidth(
873                     el.rendNodeTriangleStart,
874                     a.showFirst * a.strokeWidth,
875                     el.rendNode,
876                     a.sizeFirst
877                 );
878             }
879             if (a.evLast) {
880                 this._setArrowWidth(
881                     el.rendNodeTriangleEnd,
882                     a.showLast * a.strokeWidth,
883                     el.rendNode,
884                     a.sizeLast
885                 );
886             }
887             return this;
888         },
889 
890         /**
891          * Update the line endings (linecap) of a straight line from its attribute
892          * 'linecap'.
893          * Possible values for the attribute 'linecap' are: 'butt', 'round', 'square'.
894          * The default value is 'butt'. Not available for VML renderer.
895          *
896          * @param {JXG.Line} element A arbitrary line.
897          * @see Line
898          * @see JXG.Line
899          * @see JXG.AbstractRenderer#updateLine
900          */
901         setLineCap: function (el) { /* stub */ },
902 
903         /* ********* Ticks related stuff *********** */
904 
905         /**
906          * Creates a rendering node for ticks added to a line.
907          * @param {JXG.Line} el A arbitrary line.
908          * @see Line
909          * @see Ticks
910          * @see JXG.Line
911          * @see JXG.Ticks
912          * @see JXG.AbstractRenderer#updateTicks
913          */
914         drawTicks: function (el) {
915             el.rendNode = this.appendChildPrim(
916                 this.createPrim("path", el.id),
917                 el.evalVisProp('layer')
918             );
919             this.appendNodesToElement(el, "path");
920         },
921 
922         /**
923          * Update {@link Ticks} on a {@link JXG.Line}. This method is only a stub and has to be implemented
924          * in any descendant renderer class.
925          * @param {JXG.Ticks} el Reference of a ticks object that has to be updated.
926          * @see Line
927          * @see Ticks
928          * @see JXG.Line
929          * @see JXG.Ticks
930          * @see JXG.AbstractRenderer#drawTicks
931          */
932         updateTicks: function (el) { /* stub */ },
933 
934         /* ********* Circle related stuff *********** */
935 
936         /**
937          * Draws a {@link JXG.Circle}
938          * @param {JXG.Circle} el Reference to a {@link JXG.Circle} object that has to be drawn.
939          * @see Circle
940          * @see JXG.Circle
941          * @see JXG.AbstractRenderer#updateEllipse
942          */
943         drawEllipse: function (el) {
944             el.rendNode = this.appendChildPrim(
945                 this.createPrim("ellipse", el.id),
946                 el.evalVisProp('layer')
947             );
948             this.appendNodesToElement(el, "ellipse");
949             this.updateEllipse(el);
950         },
951 
952         /**
953          * Updates visual appearance of a given {@link JXG.Circle} on the {@link JXG.Board}.
954          * @param {JXG.Circle} el Reference to a {@link JXG.Circle} object, that has to be updated.
955          * @see Circle
956          * @see JXG.Circle
957          * @see JXG.AbstractRenderer#drawEllipse
958          */
959         updateEllipse: function (el) {
960             this._updateVisual(el);
961 
962             var radius = el.Radius();
963 
964             if (
965                 /*radius > 0.0 &&*/
966                 Math.abs(el.center.coords.usrCoords[0]) > Mat.eps &&
967                 !isNaN(radius + el.center.coords.scrCoords[1] + el.center.coords.scrCoords[2]) &&
968                 radius * el.board.unitX < 2000000
969             ) {
970                 this.updateEllipsePrim(
971                     el.rendNode,
972                     el.center.coords.scrCoords[1],
973                     el.center.coords.scrCoords[2],
974                     radius * el.board.unitX,
975                     radius * el.board.unitY
976                 );
977             }
978             this.setLineCap(el);
979         },
980 
981         /* ********* Polygon related stuff *********** */
982 
983         /**
984          * Draws a {@link JXG.Polygon} on the {@link JXG.Board}.
985          * @param {JXG.Polygon} el Reference to a Polygon object, that is to be drawn.
986          * @see Polygon
987          * @see JXG.Polygon
988          * @see JXG.AbstractRenderer#updatePolygon
989          */
990         drawPolygon: function (el) {
991             el.rendNode = this.appendChildPrim(
992                 this.createPrim("polygon", el.id),
993                 el.evalVisProp('layer')
994             );
995             this.appendNodesToElement(el, "polygon");
996             this.updatePolygon(el);
997         },
998 
999         /**
1000          * Updates properties of a {@link JXG.Polygon}'s rendering node.
1001          * @param {JXG.Polygon} el Reference to a {@link JXG.Polygon} object, that has to be updated.
1002          * @see Polygon
1003          * @see JXG.Polygon
1004          * @see JXG.AbstractRenderer#drawPolygon
1005          */
1006         updatePolygon: function (el) {
1007             // Here originally strokecolor wasn't updated but strokewidth was.
1008             // But if there's no strokecolor i don't see why we should update strokewidth.
1009             this._updateVisual(el, { stroke: true, dash: true });
1010             this.updatePolygonPrim(el.rendNode, el);
1011         },
1012 
1013         /* ********* Text related stuff *********** */
1014 
1015         /**
1016          * Shows a small copyright notice in the top left corner of the board.
1017          * @param {String} str The copyright notice itself
1018          * @param {Number} fontsize Size of the font the copyright notice is written in
1019          * @see JXG.AbstractRenderer#displayLogo
1020          * @see Text#fontSize
1021          */
1022         displayCopyright: function (str, fontsize) { /* stub */ },
1023 
1024         /**
1025          * Shows a small JSXGraph logo in the top left corner of the board.
1026          * @param {String} str The data-URL of the logo
1027          * @param {Number} fontsize Size of the font the copyright notice is written in
1028          * @see JXG.AbstractRenderer#displayCopyright
1029          * @see Text#fontSize
1030          */
1031         displayLogo: function (str, fontsize) { /* stub */ },
1032 
1033         /**
1034          * An internal text is a {@link JXG.Text} element which is drawn using only
1035          * the given renderer but no HTML. This method is only a stub, the drawing
1036          * is done in the special renderers.
1037          * @param {JXG.Text} el Reference to a {@link JXG.Text} object
1038          * @see Text
1039          * @see JXG.Text
1040          * @see JXG.AbstractRenderer#updateInternalText
1041          * @see JXG.AbstractRenderer#drawText
1042          * @see JXG.AbstractRenderer#updateText
1043          * @see JXG.AbstractRenderer#updateTextStyle
1044          */
1045         drawInternalText: function (el) { /* stub */ },
1046 
1047         /**
1048          * Updates visual properties of an already existing {@link JXG.Text} element.
1049          * @param {JXG.Text} el Reference to an {@link JXG.Text} object, that has to be updated.
1050          * @see Text
1051          * @see JXG.Text
1052          * @see JXG.AbstractRenderer#drawInternalText
1053          * @see JXG.AbstractRenderer#drawText
1054          * @see JXG.AbstractRenderer#updateText
1055          * @see JXG.AbstractRenderer#updateTextStyle
1056          */
1057         updateInternalText: function (el) { /* stub */ },
1058 
1059         /**
1060          * Displays a {@link JXG.Text} on the {@link JXG.Board} by putting a HTML div over it.
1061          * @param {JXG.Text} el Reference to an {@link JXG.Text} object, that has to be displayed
1062          * @see Text
1063          * @see JXG.Text
1064          * @see JXG.AbstractRenderer#drawInternalText
1065          * @see JXG.AbstractRenderer#updateText
1066          * @see JXG.AbstractRenderer#updateInternalText
1067          * @see JXG.AbstractRenderer#updateTextStyle
1068          */
1069         drawText: function (el) {
1070             var node, z, level, ev_visible;
1071 
1072             if (
1073                 el.evalVisProp('display') === "html" &&
1074                 Env.isBrowser &&
1075                 this.type !== "no"
1076             ) {
1077                 node = this.container.ownerDocument.createElement("div");
1078                 //node = this.container.ownerDocument.createElementNS('http://www.w3.org/1999/xhtml', 'div'); //
1079                 node.style.position = "absolute";
1080                 node.className = el.evalVisProp('cssclass');
1081 
1082                 level = el.evalVisProp('layer');
1083                 if (!Type.exists(level)) {
1084                     // trace nodes have level not set
1085                     level = 0;
1086                 }
1087 
1088                 if (this.container.style.zIndex === "") {
1089                     z = 0;
1090                 } else {
1091                     z = parseInt(this.container.style.zIndex, 10);
1092                 }
1093 
1094                 node.style.zIndex = z + level;
1095                 this.container.appendChild(node);
1096 
1097                 node.setAttribute("id", this.container.id + "_" + el.id);
1098             } else {
1099                 node = this.drawInternalText(el);
1100             }
1101 
1102             el.rendNode = node;
1103             el.htmlStr = "";
1104 
1105             // Set el.visPropCalc.visible
1106             if (el.visProp.islabel && Type.exists(el.visProp.anchor)) {
1107                 ev_visible = el.visProp.anchor.evalVisProp('visible');
1108                 el.prepareUpdate().updateVisibility(ev_visible);
1109             } else {
1110                 el.prepareUpdate().updateVisibility();
1111             }
1112             this.updateText(el);
1113         },
1114 
1115         /**
1116          * Updates visual properties of an already existing {@link JXG.Text} element.
1117          * @param {JXG.Text} el Reference to an {@link JXG.Text} object, that has to be updated.
1118          * @see Text
1119          * @see JXG.Text
1120          * @see JXG.AbstractRenderer#drawText
1121          * @see JXG.AbstractRenderer#drawInternalText
1122          * @see JXG.AbstractRenderer#updateInternalText
1123          * @see JXG.AbstractRenderer#updateTextStyle
1124          */
1125         updateText: function (el) {
1126             var content = el.plaintext,
1127                 v, c,
1128                 parentNode, node,
1129                 // scale, vshift,
1130                 // id, wrap_id,
1131                 ax, ay, angle, co, si,
1132                 to_h, to_v;
1133 
1134             if (el.visPropCalc.visible) {
1135                 this.updateTextStyle(el, false);
1136 
1137                 if (el.evalVisProp('display') === "html" && this.type !== "no") {
1138                     // Set the position
1139                     if (!isNaN(el.coords.scrCoords[1] + el.coords.scrCoords[2])) {
1140                         // Horizontal
1141                         c = el.coords.scrCoords[1];
1142                         // webkit seems to fail for extremely large values for c.
1143                         c = Math.abs(c) < 1000000 ? c : 1000000;
1144                         ax = el.getAnchorX();
1145 
1146                         if (ax === "right") {
1147                             // v = Math.floor(el.board.canvasWidth - c);
1148                             v = el.board.canvasWidth - c;
1149                             to_h = "right";
1150                         } else if (ax === "middle") {
1151                             // v = Math.floor(c - 0.5 * el.size[0]);
1152                             v = c - 0.5 * el.size[0];
1153                             to_h = "center";
1154                         } else {
1155                             // 'left'
1156                             // v = Math.floor(c);
1157                             v = c;
1158                             to_h = "left";
1159                         }
1160 
1161                         // This may be useful for foreignObj.
1162                         //if (window.devicePixelRatio !== undefined) {
1163                         //v *= window.devicePixelRatio;
1164                         //}
1165 
1166                         if (el.visPropOld.left !== ax + v) {
1167                             if (ax === "right") {
1168                                 el.rendNode.style.right = v + "px";
1169                                 el.rendNode.style.left = "auto";
1170                             } else {
1171                                 el.rendNode.style.left = v + "px";
1172                                 el.rendNode.style.right = "auto";
1173                             }
1174                             el.visPropOld.left = ax + v;
1175                         }
1176 
1177                         // Vertical
1178                         c = el.coords.scrCoords[2] + this.vOffsetText;
1179                         c = Math.abs(c) < 1000000 ? c : 1000000;
1180                         ay = el.getAnchorY();
1181 
1182                         if (ay === "bottom") {
1183                             // v = Math.floor(el.board.canvasHeight - c);
1184                             v = el.board.canvasHeight - c;
1185                             to_v = "bottom";
1186                         } else if (ay === "middle") {
1187                             // v = Math.floor(c - 0.5 * el.size[1]);
1188                             v = c - 0.5 * el.size[1];
1189                             to_v = "center";
1190                         } else {
1191                             // top
1192                             // v = Math.floor(c);
1193                             v = c;
1194                             to_v = "top";
1195                         }
1196 
1197                         // This may be useful for foreignObj.
1198                         //if (window.devicePixelRatio !== undefined) {
1199                         //v *= window.devicePixelRatio;
1200                         //}
1201 
1202                         if (el.visPropOld.top !== ay + v) {
1203                             if (ay === "bottom") {
1204                                 el.rendNode.style.top = "auto";
1205                                 el.rendNode.style.bottom = v + "px";
1206                             } else {
1207                                 el.rendNode.style.bottom = "auto";
1208                                 el.rendNode.style.top = v + "px";
1209                             }
1210                             el.visPropOld.top = ay + v;
1211                         }
1212                     }
1213 
1214                     // Set the content
1215                     if (el.htmlStr !== content) {
1216                         try {
1217                             if (el.type === Type.OBJECT_TYPE_BUTTON) {
1218                                 el.rendNodeButton.innerHTML = content;
1219                             } else if (
1220                                 el.type === Type.OBJECT_TYPE_CHECKBOX ||
1221                                 el.type === Type.OBJECT_TYPE_INPUT
1222                             ) {
1223                                 el.rendNodeLabel.innerHTML = content;
1224                             } else {
1225                                 el.rendNode.innerHTML = content;
1226                             }
1227                         } catch (e) {
1228                             // Setting innerHTML sometimes fails in IE8.
1229                             // A workaround is to take the node off the DOM, assign innerHTML,
1230                             // then append back.
1231                             // Works for text elements as they are absolutely positioned.
1232                             parentNode = el.rendNode.parentNode;
1233                             el.rendNode.parentNode.removeChild(el.rendNode);
1234                             el.rendNode.innerHTML = content;
1235                             parentNode.appendChild(el.rendNode);
1236                         }
1237                         el.htmlStr = content;
1238 
1239                         if (el.evalVisProp('usemathjax')) {
1240                             // Typesetting directly might not work because MathJax was not loaded completely
1241                             try {
1242                                 if (MathJax.typeset) {
1243                                     // Version 3
1244                                     MathJax.typeset([el.rendNode]);
1245                                 } else {
1246                                     // Version 2
1247                                     MathJax.Hub.Queue(["Typeset", MathJax.Hub, el.rendNode]);
1248                                 }
1249 
1250                                 // Obsolete:
1251                                 // // Restore the transformation necessary for fullscreen mode
1252                                 // // MathJax removes it when handling dynamic content
1253                                 // id = el.board.container;
1254                                 // wrap_id = "fullscreenwrap_" + id;
1255                                 // if (document.getElementById(wrap_id)) {
1256                                 //     scale = el.board.containerObj._cssFullscreenStore.scale;
1257                                 //     vshift = el.board.containerObj._cssFullscreenStore.vshift;
1258                                 //     Env.scaleJSXGraphDiv(
1259                                 //         "#" + wrap_id,
1260                                 //         "#" + id,
1261                                 //         scale,
1262                                 //         vshift
1263                                 //     );
1264                                 // }
1265                             } catch (e) {
1266                                 JXG.debug("MathJax (not yet) loaded");
1267                             }
1268                         } else if (el.evalVisProp('usekatex')) {
1269                             try {
1270                                 // Checkboxes et. al. do not possess rendNodeLabel during the first update.
1271                                 // In this case node will be undefined and not rendered by KaTeX.
1272                                 if (el.rendNode.innerHTML.indexOf('<span') === 0 &&
1273                                     el.rendNode.innerHTML.indexOf('<label') > 0 &&
1274                                     (
1275                                         el.rendNode.innerHTML.indexOf('<checkbox') > 0 ||
1276                                         el.rendNode.innerHTML.indexOf('<input') > 0
1277                                     )
1278                                  ) {
1279                                     node = el.rendNodeLabel;
1280                                 } else if (el.rendNode.innerHTML.indexOf('<button') === 0) {
1281                                     node = el.rendNodeButton;
1282                                 } else {
1283                                     node = el.rendNode;
1284                                 }
1285 
1286                                 if (node) {
1287                                     /* eslint-disable no-undef */
1288                                     katex.render(content, node, {
1289                                         macros: el.evalVisProp('katexmacros'),
1290                                         throwOnError: false
1291                                     });
1292                                     /* eslint-enable no-undef */
1293                                 }
1294                             } catch (e) {
1295                                 JXG.debug("KaTeX not loaded (yet)");
1296                             }
1297                         } else if (el.evalVisProp('useasciimathml')) {
1298                             // This is not a constructor.
1299                             // See http://asciimath.org/ for more information
1300                             // about AsciiMathML and the project's source code.
1301                             try {
1302                                 AMprocessNode(el.rendNode, false);
1303                             } catch (e) {
1304                                 JXG.debug("AsciiMathML not loaded (yet)");
1305                             }
1306                         }
1307                     }
1308 
1309                     angle = el.evalVisProp('rotate');
1310                     if (angle !== 0) {
1311                         // Don't forget to convert to rad
1312                         angle *= (Math.PI / 180);
1313                         co = Math.cos(angle);
1314                         si = Math.sin(angle);
1315 
1316                         el.rendNode.style['transform'] = 'matrix(' +
1317                                 [co, -1 * si, si, co, 0, 0].join(',') +
1318                             ')';
1319                         el.rendNode.style['transform-origin'] = to_h + ' ' + to_v;
1320                     }
1321                     this.transformRect(el, el.transformations);
1322                 } else {
1323                     this.updateInternalText(el);
1324                 }
1325             }
1326         },
1327 
1328         /**
1329          * Converts string containing CSS properties into
1330          * array with key-value pair objects.
1331          *
1332          * @example
1333          * "color:blue; background-color:yellow" is converted to
1334          * [{'color': 'blue'}, {'backgroundColor': 'yellow'}]
1335          *
1336          * @param  {String} cssString String containing CSS properties
1337          * @return {Array}           Array of CSS key-value pairs
1338          */
1339         _css2js: function (cssString) {
1340             var pairs = [],
1341                 i,
1342                 len,
1343                 key,
1344                 val,
1345                 s,
1346                 list = Type.trim(cssString).replace(/;$/, "").split(";");
1347 
1348             len = list.length;
1349             for (i = 0; i < len; ++i) {
1350                 if (Type.trim(list[i]) !== "") {
1351                     s = list[i].split(":");
1352                     key = Type.trim(
1353                         s[0].replace(/-([a-z])/gi, function (match, char) {
1354                             return char.toUpperCase();
1355                         })
1356                     );
1357                     val = Type.trim(s[1]);
1358                     pairs.push({ key: key, val: val });
1359                 }
1360             }
1361             return pairs;
1362         },
1363 
1364         /**
1365          * Updates font-size, color and opacity properties and CSS style properties of a {@link JXG.Text} node.
1366          * This function is also called by highlight() and nohighlight().
1367          * @param {JXG.Text} el Reference to the {@link JXG.Text} object, that has to be updated.
1368          * @param {Boolean} doHighlight
1369          * @see Text
1370          * @see JXG.Text
1371          * @see JXG.AbstractRenderer#drawText
1372          * @see JXG.AbstractRenderer#drawInternalText
1373          * @see JXG.AbstractRenderer#updateText
1374          * @see JXG.AbstractRenderer#updateInternalText
1375          * @see JXG.AbstractRenderer#updateInternalTextStyle
1376          */
1377         updateTextStyle: function (el, doHighlight) {
1378             var fs,
1379                 so, sc,
1380                 css,
1381                 node,
1382                 display = Env.isBrowser ? el.visProp.display : "internal",
1383                 nodeList = ["rendNode", "rendNodeTag", "rendNodeLabel"],
1384                 lenN = nodeList.length,
1385                 fontUnit = el.evalVisProp('fontunit'),
1386                 cssList,
1387                 prop,
1388                 style,
1389                 cssString,
1390                 styleList = ["cssdefaultstyle", "cssstyle"],
1391                 lenS = styleList.length;
1392 
1393             if (doHighlight) {
1394                 sc = el.evalVisProp('highlightstrokecolor');
1395                 so = el.evalVisProp('highlightstrokeopacity');
1396                 css = el.evalVisProp('highlightcssclass');
1397             } else {
1398                 sc = el.evalVisProp('strokecolor');
1399                 so = el.evalVisProp('strokeopacity');
1400                 css = el.evalVisProp('cssclass');
1401             }
1402 
1403             // This part is executed for all text elements except internal texts in canvas.
1404             // HTML-texts or internal texts in SVG or VML.
1405             //            HTML    internal
1406             //  SVG        +         +
1407             //  VML        +         +
1408             //  canvas     +         -
1409             //  no         -         -
1410             if (this.type !== "no" && (display === "html" || this.type !== "canvas")) {
1411                 for (style = 0; style < lenS; style++) {
1412                     // First set cssString to
1413                     // ev.cssdefaultstyle of ev.highlightcssdefaultstyle,
1414                     // then to
1415                     // ev.cssstyle of ev.highlightcssstyle
1416                     cssString = el.evalVisProp(
1417                         (doHighlight ? 'highlight' : '') + styleList[style]
1418                     );
1419                     // Set the CSS style properties - without deleting other properties
1420                     for (node = 0; node < lenN; node++) {
1421                         if (Type.exists(el[nodeList[node]])) {
1422                             if (cssString !== "" && el.visPropOld[styleList[style] + '_' + node] !== cssString) {
1423                                 cssList = this._css2js(cssString);
1424                                 for (prop in cssList) {
1425                                     if (cssList.hasOwnProperty(prop)) {
1426                                         el[nodeList[node]].style[cssList[prop].key] = cssList[prop].val;
1427                                     }
1428                                 }
1429                                 el.visPropOld[styleList[style] + '_' + node] = cssString;
1430                             }
1431                         }
1432                         // el.visPropOld[styleList[style]] = cssString;
1433                     }
1434                 }
1435 
1436                 fs = el.evalVisProp('fontsize');
1437                 if (el.visPropOld.fontsize !== fs) {
1438                     el.needsSizeUpdate = true;
1439                     try {
1440                         for (node = 0; node < lenN; node++) {
1441                             if (Type.exists(el[nodeList[node]])) {
1442                                 el[nodeList[node]].style.fontSize = fs + fontUnit;
1443                             }
1444                         }
1445                     } catch (e) {
1446                         // IE needs special treatment.
1447                         for (node = 0; node < lenN; node++) {
1448                             if (Type.exists(el[nodeList[node]])) {
1449                                 el[nodeList[node]].style.fontSize = fs;
1450                             }
1451                         }
1452                     }
1453                     el.visPropOld.fontsize = fs;
1454                 }
1455             }
1456 
1457             this.setTabindex(el);
1458 
1459             this.setObjectTransition(el);
1460             if (display === "html" && this.type !== "no") {
1461                 // Set new CSS class
1462                 if (el.visPropOld.cssclass !== css) {
1463                     el.rendNode.className = css;
1464                     el.visPropOld.cssclass = css;
1465                     el.needsSizeUpdate = true;
1466                 }
1467                 this.setObjectStrokeColor(el, sc, so);
1468             } else {
1469                 this.updateInternalTextStyle(el, sc, so);
1470             }
1471 
1472             if (el.evalVisProp('aria.enabled')) {
1473                 this.setARIA(el);
1474             }
1475 
1476             return this;
1477         },
1478 
1479         /**
1480          * Set color and opacity of internal texts.
1481          * This method is used for Canvas and VML.
1482          * SVG needs its own version.
1483          * @private
1484          * @see JXG.AbstractRenderer#updateTextStyle
1485          * @see JXG.SVGRenderer#updateInternalTextStyle
1486          */
1487         updateInternalTextStyle: function (el, strokeColor, strokeOpacity) {
1488             this.setObjectStrokeColor(el, strokeColor, strokeOpacity);
1489         },
1490 
1491         /* ********* Image related stuff *********** */
1492 
1493         /**
1494          * Draws an {@link JXG.Image} on a board; This is just a template that has to be implemented by special
1495          * renderers.
1496          * @param {JXG.Image} el Reference to the image object that is to be drawn
1497          * @see Image
1498          * @see JXG.Image
1499          * @see JXG.AbstractRenderer#updateImage
1500          */
1501         drawImage: function (el) { /* stub */ },
1502 
1503         /**
1504          * Updates the properties of an {@link JXG.Image} element.
1505          * @param {JXG.Image} el Reference to an {@link JXG.Image} object, that has to be updated.
1506          * @see Image
1507          * @see JXG.Image
1508          * @see JXG.AbstractRenderer#drawImage
1509          */
1510         updateImage: function (el) {
1511             this.updateRectPrim(
1512                 el.rendNode,
1513                 el.coords.scrCoords[1],
1514                 el.coords.scrCoords[2] - el.size[1],
1515                 el.size[0],
1516                 el.size[1]
1517             );
1518 
1519             this.updateImageURL(el);
1520             this.transformRect(el, el.transformations);
1521             this._updateVisual(el, { stroke: true, dash: true }, true);
1522         },
1523 
1524         /**
1525          * Multiplication of transformations without updating. That means, at that point it is expected that the
1526          * matrices contain numbers only. First, the origin in user coords is translated to <tt>(0,0)</tt> in screen
1527          * coords. Then, the stretch factors are divided out. After the transformations in user coords, the stretch
1528          * factors are multiplied in again, and the origin in user coords is translated back to its position. This
1529          * method does not have to be implemented in a new renderer.
1530          * @param {JXG.GeometryElement} el A JSXGraph element. We only need its board property.
1531          * @param {Array} transformations An array of JXG.Transformations.
1532          * @returns {Array} A matrix represented by a two dimensional array of numbers.
1533          * @see JXG.AbstractRenderer#transformRect
1534          */
1535         joinTransforms: function (el, transformations) {
1536             var i,
1537                 ox = el.board.origin.scrCoords[1],
1538                 oy = el.board.origin.scrCoords[2],
1539                 ux = el.board.unitX,
1540                 uy = el.board.unitY,
1541 
1542                 len = transformations.length,
1543                 // Translate to 0,0 in screen coords and then scale
1544                 m = [
1545                     [1, 0, 0],
1546                     [-ox / ux, 1 / ux, 0],
1547                     [oy / uy, 0, -1 / uy]
1548                 ];
1549 
1550             for (i = 0; i < len; i++) {
1551                 m = Mat.matMatMult(transformations[i].matrix, m);
1552             }
1553             // Scale back and then translate back
1554             m = Mat.matMatMult(
1555                 [
1556                     [1, 0, 0],
1557                     [ox, ux, 0],
1558                     [oy, 0, -uy]
1559                 ],
1560                 m
1561             );
1562             return m;
1563         },
1564 
1565         /**
1566          * Applies transformations on images and text elements. This method has to implemented in
1567          * all descendant classes where text and image transformations are to be supported.
1568          * <p>
1569          * Only affine transformation are supported, no proper projective transformations. This means, the
1570          * respective entries of the transformation matrix are simply ignored.
1571          *
1572          * @param {JXG.Image|JXG.Text} el A {@link JXG.Image} or {@link JXG.Text} object.
1573          * @param {Array} transformations An array of {@link JXG.Transformation} objects. This is usually the
1574          * transformations property of the given element <tt>el</tt>.
1575          */
1576         transformRect: function (el, transformations) { /* stub */ },
1577 
1578         /**
1579          * If the URL of the image is provided by a function the URL has to be updated during updateImage()
1580          * @param {JXG.Image} el Reference to an image object.
1581          * @see JXG.AbstractRenderer#updateImage
1582          */
1583         updateImageURL: function (el) { /* stub */ },
1584 
1585         /**
1586          * Updates CSS style properties of a {@link JXG.Image} node.
1587          * In SVGRenderer opacity is the only available style element.
1588          * This function is called by highlight() and nohighlight().
1589          * This function works for VML.
1590          * It does not work for Canvas.
1591          * SVGRenderer overwrites this method.
1592          * @param {JXG.Text} el Reference to the {@link JXG.Image} object, that has to be updated.
1593          * @param {Boolean} doHighlight
1594          * @see Image
1595          * @see JXG.Image
1596          * @see JXG.AbstractRenderer#highlight
1597          * @see JXG.AbstractRenderer#noHighlight
1598          */
1599         updateImageStyle: function (el, doHighlight) {
1600             el.rendNode.className = el.evalVisProp(
1601                 doHighlight ? 'highlightcssclass' : 'cssclass'
1602             );
1603         },
1604 
1605         drawForeignObject: function (el) { /* stub */ },
1606 
1607         updateForeignObject: function (el) {
1608             /* stub */
1609         },
1610 
1611         /* ********* Render primitive objects *********** */
1612 
1613         /**
1614          * Appends a node to a specific layer level. This is just an abstract method and has to be implemented
1615          * in all renderers that want to use the <tt>createPrim</tt> model to draw.
1616          * @param {Node} node A DOM tree node.
1617          * @param {Number} level The layer the node is attached to. This is the index of the layer in
1618          * {@link JXG.SVGRenderer#layer} or the <tt>z-index</tt> style property of the node in VMLRenderer.
1619          */
1620         appendChildPrim: function (node, level) { /* stub */ },
1621 
1622         /**
1623          * Stores the rendering nodes. This is an abstract method which has to be implemented in all renderers that use
1624          * the <tt>createPrim</tt> method.
1625          * @param {JXG.GeometryElement} el A JSXGraph element.
1626          * @param {String} type The XML node name. Only used in VMLRenderer.
1627          */
1628         appendNodesToElement: function (el, type) { /* stub */ },
1629 
1630         /**
1631          * Creates a node of a given type with a given id.
1632          * @param {String} type The type of the node to create.
1633          * @param {String} id Set the id attribute to this.
1634          * @returns {Node} Reference to the created node.
1635          */
1636         createPrim: function (type, id) { /* stub */ return null; },
1637 
1638         /**
1639          * Removes an element node. Just a stub.
1640          * @param {Node} node The node to remove.
1641          */
1642         remove: function (node) { /* stub */ },
1643 
1644         /**
1645          * Can be used to create the nodes to display arrows. This is an abstract method which has to be implemented
1646          * in any descendant renderer.
1647          * @param {JXG.GeometryElement} el The element the arrows are to be attached to.
1648          * @param {Object} arrowData Data concerning possible arrow heads
1649          *
1650          */
1651         makeArrows: function (el, arrowData) { /* stub */ },
1652 
1653         /**
1654          * Updates width of an arrow DOM node. Used in
1655          * @param {Node} node The arrow node.
1656          * @param {Number} width
1657          * @param {Node} parentNode Used in IE only
1658          */
1659         _setArrowWidth: function (node, width, parentNode) { /* stub */ },
1660 
1661         /**
1662          * Updates an ellipse node primitive. This is an abstract method which has to be implemented in all renderers
1663          * that use the <tt>createPrim</tt> method.
1664          * @param {Node} node Reference to the node.
1665          * @param {Number} x Centre X coordinate
1666          * @param {Number} y Centre Y coordinate
1667          * @param {Number} rx The x-axis radius.
1668          * @param {Number} ry The y-axis radius.
1669          */
1670         updateEllipsePrim: function (node, x, y, rx, ry) { /* stub */ },
1671 
1672         /**
1673          * Refreshes a line node. This is an abstract method which has to be implemented in all renderers that use
1674          * the <tt>createPrim</tt> method.
1675          * @param {Node} node The node to be refreshed.
1676          * @param {Number} p1x The first point's x coordinate.
1677          * @param {Number} p1y The first point's y coordinate.
1678          * @param {Number} p2x The second point's x coordinate.
1679          * @param {Number} p2y The second point's y coordinate.
1680          * @param {JXG.Board} board
1681          */
1682         updateLinePrim: function (node, p1x, p1y, p2x, p2y, board) { /* stub */ },
1683 
1684         /**
1685          * Updates a path element. This is an abstract method which has to be implemented in all renderers that use
1686          * the <tt>createPrim</tt> method.
1687          * @param {Node} node The path node.
1688          * @param {String} pathString A string formatted like e.g. <em>'M 1,2 L 3,1 L5,5'</em>. The format of the string
1689          * depends on the rendering engine.
1690          * @param {JXG.Board} board Reference to the element's board.
1691          */
1692         updatePathPrim: function (node, pathString, board) { /* stub */ },
1693 
1694         /**
1695          * Builds a path data string to draw a point with a face other than <em>rect</em> and <em>circle</em>. Since
1696          * the format of such a string usually depends on the renderer this method
1697          * is only an abstract method. Therefore, it has to be implemented in the descendant renderer itself unless
1698          * the renderer does not use the createPrim interface but the draw* interfaces to paint.
1699          * @param {JXG.Point} el The point element
1700          * @param {Number} size A positive number describing the size. Usually the half of the width and height of
1701          * the drawn point.
1702          * @param {String} type A string describing the point's face. This method only accepts the shortcut version of
1703          * each possible face: <tt>x, +, |, -, [], <>, <<>>,^, v, >, < </tt>
1704          */
1705         updatePathStringPoint: function (el, size, type) { /* stub */ },
1706 
1707         /**
1708          * Builds a path data string from a {@link JXG.Curve} element. Since the path data strings heavily depend on the
1709          * underlying rendering technique this method is just a stub. Although such a path string is of no use for the
1710          * CanvasRenderer, this method is used there to draw a path directly.
1711          * @param {JXG.GeometryElement} el
1712          */
1713         updatePathStringPrim: function (el) { /* stub */ },
1714 
1715         /**
1716          * Builds a path data string from a {@link JXG.Curve} element such that the curve looks like hand drawn. Since
1717          * the path data strings heavily depend on the underlying rendering technique this method is just a stub.
1718          * Although such a path string is of no use for the CanvasRenderer, this method is used there to draw a path
1719          * directly.
1720          * @param  {JXG.GeometryElement} el
1721          */
1722         updatePathStringBezierPrim: function (el) { /* stub */ },
1723 
1724         /**
1725          * Update a polygon primitive.
1726          * @param {Node} node
1727          * @param {JXG.Polygon} el A JSXGraph element of type {@link JXG.Polygon}
1728          */
1729         updatePolygonPrim: function (node, el) { /* stub */ },
1730 
1731         /**
1732          * Update a rectangle primitive. This is used only for points with face of type 'rect'.
1733          * @param {Node} node The node yearning to be updated.
1734          * @param {Number} x x coordinate of the top left vertex.
1735          * @param {Number} y y coordinate of the top left vertex.
1736          * @param {Number} w Width of the rectangle.
1737          * @param {Number} h The rectangle's height.
1738          */
1739         updateRectPrim: function (node, x, y, w, h) { /* stub */ },
1740 
1741         /* ********* Set attributes *********** */
1742 
1743         /**
1744          * Shows or hides an element on the canvas; Only a stub, requires implementation in the derived renderer.
1745          * @param {JXG.GeometryElement} el Reference to the object that has to appear.
1746          * @param {Boolean} value true to show the element, false to hide the element.
1747          */
1748         display: function (el, value) {
1749             if (el) {
1750                 el.visPropOld.visible = value;
1751             }
1752         },
1753 
1754         /**
1755          * Hides an element on the canvas; Only a stub, requires implementation in the derived renderer.
1756          *
1757          * Please use JXG.AbstractRenderer#display instead
1758          * @param {JXG.GeometryElement} el Reference to the geometry element that has to disappear.
1759          * @see JXG.AbstractRenderer#show
1760          * @deprecated
1761          */
1762         hide: function (el) { /* stub */ },
1763 
1764         /**
1765          * Highlights an object, i.e. changes the current colors of the object to its highlighting colors
1766          * and highlighting strokewidth.
1767          * @param {JXG.GeometryElement} el Reference of the object that will be highlighted.
1768          * @param {Boolean} [suppressHighlightStrokeWidth=undefined] If undefined or false, highlighting also changes strokeWidth. This might not be
1769          * the cases for polygon borders. Thus, if a polygon is highlighted, its polygon borders change strokeWidth only if the polygon attribute
1770          * highlightByStrokeWidth == true.
1771          * @returns {JXG.AbstractRenderer} Reference to the renderer
1772          * @see JXG.AbstractRenderer#updateTextStyle
1773          */
1774         highlight: function (el, suppressHighlightStrokeWidth) {
1775             var i, do_hl, sw;
1776 
1777             this.setObjectTransition(el);
1778             if (!el.visProp.draft) {
1779                 if (el.type === Const.OBJECT_TYPE_POLYGON) {
1780                     this.setObjectFillColor(el, el.evalVisProp('highlightfillcolor'), el.evalVisProp('highlightfillopacity'));
1781                     do_hl = el.evalVisProp('highlightbystrokewidth');
1782                     for (i = 0; i < el.borders.length; i++) {
1783                         this.highlight(el.borders[i], !do_hl);
1784                     }
1785                 } else {
1786                     if (el.elementClass === Const.OBJECT_CLASS_TEXT) {
1787                         this.updateTextStyle(el, true);
1788                     } else if (el.type === Const.OBJECT_TYPE_IMAGE) {
1789                         this.updateImageStyle(el, true);
1790                         this.setObjectFillColor(
1791                             el,
1792                             el.evalVisProp('highlightfillcolor'),
1793                             el.evalVisProp('highlightfillopacity')
1794                         );
1795                     } else {
1796                         this.setObjectStrokeColor(
1797                             el,
1798                             el.evalVisProp('highlightstrokecolor'),
1799                             el.evalVisProp('highlightstrokeopacity')
1800                         );
1801                         this.setObjectFillColor(
1802                             el,
1803                             el.evalVisProp('highlightfillcolor'),
1804                             el.evalVisProp('highlightfillopacity')
1805                         );
1806                     }
1807                 }
1808 
1809                 // Highlight strokeWidth is suppressed if
1810                 // parameter suppressHighlightStrokeWidth is false or undefined.
1811                 // suppressHighlightStrokeWidth is false if polygon attribute
1812                 // highlightbystrokewidth is true.
1813                 if (!suppressHighlightStrokeWidth && el.evalVisProp('highlightstrokewidth')) {
1814                     sw = Math.max(
1815                         el.evalVisProp('highlightstrokewidth'),
1816                         el.evalVisProp('strokewidth')
1817                     );
1818                     this.setObjectStrokeWidth(el, sw);
1819                     if (
1820                         el.elementClass === Const.OBJECT_CLASS_LINE ||
1821                         el.elementClass === Const.OBJECT_CLASS_CURVE
1822                     ) {
1823                         this.updatePathWithArrowHeads(el, true);
1824                     }
1825                 }
1826             }
1827             this.setCssClass(el, el.evalVisProp('highlightcssclass'));
1828 
1829             return this;
1830         },
1831 
1832         /**
1833          * Uses the normal colors of an object, i.e. the opposite of {@link JXG.AbstractRenderer#highlight}.
1834          * @param {JXG.GeometryElement} el Reference of the object that will get its normal colors.
1835          * @returns {JXG.AbstractRenderer} Reference to the renderer
1836          * @see JXG.AbstractRenderer#updateTextStyle
1837          */
1838         noHighlight: function (el) {
1839             var i, sw;
1840 
1841             this.setObjectTransition(el);
1842             if (!el.evalVisProp('draft')) {
1843                 if (el.type === Const.OBJECT_TYPE_POLYGON) {
1844                     this.setObjectFillColor(el, el.evalVisProp('fillcolor'), el.evalVisProp('fillopacity'));
1845                     for (i = 0; i < el.borders.length; i++) {
1846                         this.noHighlight(el.borders[i]);
1847                     }
1848                 } else {
1849                     if (el.elementClass === Const.OBJECT_CLASS_TEXT) {
1850                         this.updateTextStyle(el, false);
1851                     } else if (el.type === Const.OBJECT_TYPE_IMAGE) {
1852                         this.updateImageStyle(el, false);
1853                         this.setObjectFillColor(el, el.evalVisProp('fillcolor'), el.evalVisProp('fillopacity'));
1854                     } else {
1855                         this.setObjectStrokeColor(el, el.evalVisProp('strokecolor'), el.evalVisProp('strokeopacity'));
1856                         this.setObjectFillColor(el, el.evalVisProp('fillcolor'), el.evalVisProp('fillopacity'));
1857                     }
1858                 }
1859 
1860                 sw = el.evalVisProp('strokewidth');
1861                 this.setObjectStrokeWidth(el, sw);
1862                 if (
1863                     el.elementClass === Const.OBJECT_CLASS_LINE ||
1864                     el.elementClass === Const.OBJECT_CLASS_CURVE
1865                 ) {
1866                     this.updatePathWithArrowHeads(el, false);
1867                 }
1868             }
1869             this.setCssClass(el, el.evalVisProp('cssclass'));
1870 
1871             return this;
1872         },
1873 
1874         /**
1875          * Puts an object from draft mode back into normal mode.
1876          * @param {JXG.GeometryElement} el Reference of the object that no longer is in draft mode.
1877          */
1878         removeDraft: function (el) {
1879             this.setObjectTransition(el);
1880             if (el.type === Const.OBJECT_TYPE_POLYGON) {
1881                 this.setObjectFillColor(el, el.evalVisProp('fillcolor'), el.evalVisProp('fillopacity'));
1882             } else {
1883                 if (el.type === Const.OBJECT_CLASS_POINT) {
1884                     this.setObjectFillColor(el, el.evalVisProp('fillcolor'), el.evalVisProp('fillopacity'));
1885                 }
1886                 this.setObjectStrokeColor(el, el.evalVisProp('strokecolor'), el.evalVisProp('strokeopacity'));
1887                 this.setObjectStrokeWidth(el, el.evalVisProp('strokewidth'));
1888             }
1889         },
1890 
1891         /**
1892          * Set ARIA related properties of an element. The attribute "aria" of an element contains at least the
1893          * properties "enabled", "label", and "live". Additionally, all available properties from
1894          * {@link https://developer.mozilla.org/en-US/docs/Web/Accessibility/ARIA} may be set.
1895          * <p>
1896          * In JSXGraph, the available properties are used without the leading 'aria-'.
1897          * For example, the value of the JSXGraph attribute 'aria.label' will be set to the
1898          * HTML attribute 'aria-label'.
1899          *
1900          * @param {JXG.GeometryElement} el Reference of the object that wants new
1901          *        ARIA attributes.
1902          */
1903         setARIA: function(el) { /* stub */ },
1904 
1905         /**
1906          * Sets the buffering as recommended by SVGWG. Until now only Opera supports this and will be ignored by other
1907          * browsers. Although this feature is only supported by SVG we have this method in {@link JXG.AbstractRenderer}
1908          * because it is called from outside the renderer.
1909          * @param {Node} node The SVG DOM Node which buffering type to update.
1910          * @param {String} type Either 'auto', 'dynamic', or 'static'. For an explanation see
1911          *   {@link https://www.w3.org/TR/SVGTiny12/painting.html#BufferedRenderingProperty}.
1912          */
1913         setBuffering: function (node, type) { /* stub */ },
1914 
1915         /**
1916          * Sets CSS classes for elements (relevant for SVG only).
1917          *
1918          * @param {JXG.GeometryElement} el Reference of the object that wants a
1919          *         new set of CSS classes.
1920          * @param {String} cssClass String containing a space separated list of CSS classes.
1921          */
1922         setCssClass: function (el, cssClass) { /* stub */ },
1923 
1924         /**
1925          * Sets an element's dash style.
1926          * @param {JXG.GeometryElement} el An JSXGraph element.
1927          */
1928         setDashStyle: function (el) { /* stub */ },
1929 
1930         /**
1931          * Puts an object into draft mode, i.e. it's visual appearance will be changed. For GEONE<sub>x</sub>T backwards
1932          * compatibility.
1933          * @param {JXG.GeometryElement} el Reference of the object that is in draft mode.
1934          */
1935         setDraft: function (el) {
1936             if (!el.evalVisProp('draft')) {
1937                 return;
1938             }
1939             var draftColor = el.board.options.elements.draft.color,
1940                 draftOpacity = el.board.options.elements.draft.opacity;
1941 
1942                 this.setObjectTransition(el);
1943             if (el.type === Const.OBJECT_TYPE_POLYGON) {
1944                 this.setObjectFillColor(el, draftColor, draftOpacity);
1945             } else {
1946                 if (el.elementClass === Const.OBJECT_CLASS_POINT) {
1947                     this.setObjectFillColor(el, draftColor, draftOpacity);
1948                 } else {
1949                     this.setObjectFillColor(el, "none", 0);
1950                 }
1951                 this.setObjectStrokeColor(el, draftColor, draftOpacity);
1952                 this.setObjectStrokeWidth(el, el.board.options.elements.draft.strokeWidth);
1953             }
1954         },
1955 
1956         /**
1957          * Sets up nodes for rendering a gradient fill.
1958          * @param {JXG.GeometryElement}  el Reference of the object which gets the gradient
1959          */
1960         setGradient: function (el) { /* stub */ },
1961 
1962         /**
1963          * Move element into new layer. This is trivial for canvas, but needs more effort in SVG.
1964          * Does not work dynamically, i.e. if level is a function.
1965          *
1966          * @param {JXG.GeometryElement} el Element which is put into different layer
1967          * @param {Number} value Layer number
1968          * @private
1969          */
1970         setLayer: function (el, level) { /* stub */ },
1971 
1972         /**
1973          * Sets an objects fill color.
1974          * @param {JXG.GeometryElement} el Reference of the object that wants a new fill color.
1975          * @param {String} color Color in a HTML/CSS compatible format. If you don't want any fill color at all, choose
1976          * 'none'.
1977          * @param {Number} opacity Opacity of the fill color. Must be between 0 and 1.
1978          */
1979         setObjectFillColor: function (el, color, opacity) { /* stub */ },
1980 
1981         /**
1982          * Changes an objects stroke color to the given color.
1983          * @param {JXG.GeometryElement} el Reference of the {@link JXG.GeometryElement} that gets a new stroke
1984          * color.
1985          * @param {String} color Color value in a HTML compatible format, e.g. <strong>#00ff00</strong> or
1986          * <strong>green</strong> for green.
1987          * @param {Number} opacity Opacity of the fill color. Must be between 0 and 1.
1988          */
1989         setObjectStrokeColor: function (el, color, opacity) { /* stub */ },
1990 
1991         /**
1992          * Sets an element's stroke width.
1993          * @param {JXG.GeometryElement} el Reference to the geometry element.
1994          * @param {Number} width The new stroke width to be assigned to the element.
1995          */
1996         setObjectStrokeWidth: function (el, width) { /* stub */ },
1997 
1998         /**
1999          * Sets the transition duration (in milliseconds) for fill color and stroke
2000          * color and opacity.
2001          * @param {JXG.GeometryElement} el Reference of the object that wants a
2002          *         new transition duration.
2003          * @param {Number} duration (Optional) duration in milliseconds. If not given,
2004          *        element.visProp.transitionDuration is taken. This is the default.
2005          */
2006         setObjectTransition: function (el, duration) { /* stub */ },
2007 
2008         /**
2009          * Sets a node's attribute.
2010          * @param {Node} node The node that is to be updated.
2011          * @param {String} key Name of the attribute.
2012          * @param {String} val New value for the attribute.
2013          */
2014         setPropertyPrim: function (node, key, val) { /* stub */ },
2015 
2016         /**
2017          * Sets the shadow properties to a geometry element. This method is only a stub, it is implemented in the actual
2018          * renderers.
2019          * @param {JXG.GeometryElement} el Reference to a geometry object, that should get a shadow
2020          */
2021         setShadow: function (el) { /* stub */ },
2022 
2023         /**
2024          * Set the attribute `tabindex` to the attribute `tabindex` of an element.
2025          * This is only relevant for the SVG renderer.
2026          *
2027          * @param {JXG.GeometryElement} el
2028          */
2029         setTabindex: function (el) { /* stub */ },
2030 
2031         /**
2032          * Shows a hidden element on the canvas; Only a stub, requires implementation in the derived renderer.
2033          *
2034          * Please use JXG.AbstractRenderer#display instead
2035          * @param {JXG.GeometryElement} el Reference to the object that has to appear.
2036          * @see JXG.AbstractRenderer#hide
2037          * @deprecated
2038          */
2039         show: function (el) { /* stub */ },
2040 
2041         /**
2042          * Updates the gradient fill.
2043          * @param {JXG.GeometryElement} el An JSXGraph element with an area that can be filled.
2044          */
2045         updateGradient: function (el) { /* stub */ },
2046 
2047         /* ********* Renderer control *********** */
2048 
2049         /**
2050          * Stop redraw. This method is called before every update, so a non-vector-graphics based renderer can use this
2051          * method to delete the contents of the drawing panel. This is an abstract method every descendant renderer
2052          * should implement, if appropriate.
2053          * @see JXG.AbstractRenderer#unsuspendRedraw
2054          */
2055         suspendRedraw: function () { /* stub */ },
2056 
2057         /**
2058          * Restart redraw. This method is called after updating all the rendering node attributes.
2059          * @see JXG.AbstractRenderer#suspendRedraw
2060          */
2061         unsuspendRedraw: function () { /* stub */ },
2062 
2063         /**
2064          * The tiny zoom bar shown on the bottom of a board (if board attribute "showNavigation" is true).
2065          * It is a div element and gets the CSS class "JXG_navigation" and the id {board id}_navigationbar.
2066          * <p>
2067          * The buttons get the CSS class "JXG_navigation_button" and the id {board_id}_name where name is
2068          * one of [top, down, left, right, out, 100, in, fullscreen, screenshot, reload, cleartraces].
2069          * <p>
2070          * The symbols for zoom, navigation and reload are hard-coded.
2071          *
2072          * @param {JXG.Board} board Reference to a JSXGraph board.
2073          * @param {Object} attr Attributes of the navigation bar
2074          * @private
2075          */
2076         drawNavigationBar: function (board, attr) {
2077             var doc,
2078                 node,
2079                 cancelbubble = function (e) {
2080                     if (!e) {
2081                         e = window.event;
2082                     }
2083 
2084                     if (e.stopPropagation) {
2085                         // Non IE<=8
2086                         e.stopPropagation();
2087                     } else {
2088                         e.cancelBubble = true;
2089                     }
2090                 },
2091                 createButton = function (label, handler, board_id, type) {
2092                     var button;
2093 
2094                     board_id = board_id || "";
2095 
2096                     button = doc.createElement("span");
2097                     button.innerHTML = label; // button.appendChild(doc.createTextNode(label));
2098 
2099                     // Style settings are superseded by adding the CSS class below
2100                     button.style.paddingLeft = "7px";
2101                     button.style.paddingRight = "7px";
2102 
2103                     if (button.classList !== undefined) {
2104                         // classList not available in IE 9
2105                         button.classList.add("JXG_navigation_button");
2106                         button.classList.add("JXG_navigation_button_" + type);
2107                     }
2108                     // button.setAttribute('tabindex', 0);
2109 
2110                     button.setAttribute("id", board_id + '_navigation_' + type);
2111                     button.setAttribute("aria-hidden", 'true');   // navigation buttons should never appear in screen reader
2112 
2113                     node.appendChild(button);
2114 
2115                     Env.addEvent(
2116                         button,
2117                         "click",
2118                         function (e) {
2119                             Type.bind(handler, board)();
2120                             return false;
2121                         },
2122                         board
2123                     );
2124                     // prevent the click from bubbling down to the board
2125                     Env.addEvent(button, "pointerup", cancelbubble, board);
2126                     Env.addEvent(button, "pointerdown", cancelbubble, board);
2127                     Env.addEvent(button, "pointerleave", cancelbubble, board);
2128                     Env.addEvent(button, "mouseup", cancelbubble, board);
2129                     Env.addEvent(button, "mousedown", cancelbubble, board);
2130                     Env.addEvent(button, "touchend", cancelbubble, board);
2131                     Env.addEvent(button, "touchstart", cancelbubble, board);
2132                 };
2133 
2134             if (Env.isBrowser && this.type !== "no") {
2135                 doc = board.containerObj.ownerDocument;
2136                 node = doc.createElement("div");
2137 
2138                 node.setAttribute("id", board.container + "_navigationbar");
2139 
2140                 // Style settings are superseded by adding the CSS class below
2141                 node.style.color = attr.strokecolor;
2142                 node.style.backgroundColor = attr.fillcolor;
2143                 node.style.padding = attr.padding;
2144                 node.style.position = attr.position;
2145                 node.style.fontSize = attr.fontsize;
2146                 node.style.cursor = attr.cursor;
2147                 node.style.zIndex = attr.zindex;
2148                 board.containerObj.appendChild(node);
2149                 node.style.right = attr.right;
2150                 node.style.bottom = attr.bottom;
2151 
2152                 if (node.classList !== undefined) {
2153                     // classList not available in IE 9
2154                     node.classList.add("JXG_navigation");
2155                 }
2156                 // For XHTML we need unicode instead of HTML entities
2157 
2158                 if (board.attr.showfullscreen) {
2159                     createButton(
2160                         board.attr.fullscreen.symbol,
2161                         function () {
2162                             board.toFullscreen(board.attr.fullscreen.id);
2163                         },
2164                         board.container, "fullscreen"
2165                     );
2166                 }
2167 
2168                 if (board.attr.showscreenshot) {
2169                     createButton(
2170                         board.attr.screenshot.symbol,
2171                         function () {
2172                             window.setTimeout(function () {
2173                                 board.renderer.screenshot(board, "", false);
2174                             }, 330);
2175                         },
2176                         board.container, "screenshot"
2177                     );
2178                 }
2179 
2180                 if (board.attr.showreload) {
2181                     // full reload circle: \u27F2
2182                     // the board.reload() method does not exist during the creation
2183                     // of this button. That's why this anonymous function wrapper is required.
2184                     createButton(
2185                         "\u21BB",
2186                         function () {
2187                             board.reload();
2188                         },
2189                         board.container, "reload"
2190                     );
2191                 }
2192 
2193                 if (board.attr.showcleartraces) {
2194                     // clear traces symbol (otimes): \u27F2
2195                     createButton("\u2297",
2196                         function () {
2197                             board.clearTraces();
2198                         },
2199                         board.container, "cleartraces"
2200                     );
2201                 }
2202 
2203                 if (board.attr.shownavigation) {
2204                     if (board.attr.showzoom) {
2205                         createButton("\u2013", board.zoomOut, board.container, "out");
2206                         createButton("o", board.zoom100, board.container, "100");
2207                         createButton("+", board.zoomIn, board.container, "in");
2208                     }
2209                     createButton("\u2190", board.clickLeftArrow, board.container, "left");
2210                     createButton("\u2193", board.clickUpArrow, board.container, "down"); // Down arrow
2211                     createButton("\u2191", board.clickDownArrow, board.container, "up"); // Up arrow
2212                     createButton("\u2192", board.clickRightArrow, board.container, "right");
2213                 }
2214             }
2215         },
2216 
2217         /**
2218          * Wrapper for getElementById for maybe other renderers which elements are not directly accessible by DOM
2219          * methods like document.getElementById().
2220          * @param {String} id Unique identifier for element.
2221          * @returns {Object} Reference to a JavaScript object. In case of SVG/VMLRenderer it's a reference to a SVG/VML node.
2222          */
2223         getElementById: function (id) {
2224             var str;
2225             if (Type.exists(this.container)) {
2226                 // Use querySelector over getElementById for compatibility with both 'regular' document
2227                 // and ShadowDOM fragments.
2228                 str = this.container.id + '_' + id;
2229                 // Mask special symbols like '/' and '\' in id
2230                 if (Type.exists(CSS) && Type.exists(CSS.escape)) {
2231                     str = CSS.escape(str);
2232                 }
2233                 return this.container.querySelector('#' + str);
2234             }
2235             return "";
2236         },
2237 
2238         /**
2239          * Remove an element and provide a function that inserts it into its original position. This method
2240          * is taken from this article {@link https://developers.google.com/speed/articles/javascript-dom}.
2241          * @author KeeKim Heng, Google Web Developer
2242          * @param {Element} el The element to be temporarily removed
2243          * @returns {Function} A function that inserts the element into its original position
2244          */
2245         removeToInsertLater: function (el) {
2246             var parentNode = el.parentNode,
2247                 nextSibling = el.nextSibling;
2248 
2249             if (parentNode === null) {
2250                 return;
2251             }
2252             parentNode.removeChild(el);
2253 
2254             return function () {
2255                 if (nextSibling) {
2256                     parentNode.insertBefore(el, nextSibling);
2257                 } else {
2258                     parentNode.appendChild(el);
2259                 }
2260             };
2261         },
2262 
2263         /**
2264          * Resizes the rendering element
2265          * @param {Number} w New width
2266          * @param {Number} h New height
2267          */
2268         resize: function (w, h) { /* stub */ },
2269 
2270         /**
2271          * Create crosshair elements (Fadenkreuz) for presentations.
2272          * @param {Number} n Number of crosshairs.
2273          */
2274         createTouchpoints: function (n) { /* stub */ },
2275 
2276         /**
2277          * Show a specific crosshair.
2278          * @param {Number} i Number of the crosshair to show
2279          */
2280         showTouchpoint: function (i) { /* stub */ },
2281 
2282         /**
2283          * Hide a specific crosshair.
2284          * @param {Number} i Number of the crosshair to show
2285          */
2286         hideTouchpoint: function (i) { /* stub */ },
2287 
2288         /**
2289          * Move a specific crosshair.
2290          * @param {Number} i Number of the crosshair to show
2291          * @param {Array} pos New positon in screen coordinates
2292          */
2293         updateTouchpoint: function (i, pos) { /* stub */ },
2294 
2295         /* ********* Dump related stuff *********** */
2296 
2297         /**
2298          * Convert SVG construction to base64 encoded SVG data URL.
2299          * Only available on SVGRenderer.
2300          *
2301          * @see JXG.SVGRenderer#dumpToDataURI
2302          */
2303         dumpToDataURI: function (_ignoreTexts) { /* stub */ },
2304 
2305         /**
2306          * Convert SVG construction to canvas.
2307          * Only available on SVGRenderer.
2308          *
2309          * @see JXG.SVGRenderer#dumpToCanvas
2310          */
2311         dumpToCanvas: function (canvasId, w, h, _ignoreTexts) { /* stub */ },
2312 
2313         /**
2314          * Display SVG image in html img-tag which enables
2315          * easy download for the user.
2316          *
2317          * See JXG.SVGRenderer#screenshot
2318          */
2319         screenshot: function (board) { /* stub */ }
2320 
2321     }
2322 );
2323 
2324 export default JXG.AbstractRenderer;
2325