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