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          * Updates font-size, color and opacity properties and CSS style properties of a {@link JXG.Text} node.
1393          * This function is also called by highlight() and nohighlight().
1394          * @param {JXG.Text} el Reference to the {@link JXG.Text} object, that has to be updated.
1395          * @param {Boolean} doHighlight
1396          * @see Text
1397          * @see JXG.Text
1398          * @see JXG.AbstractRenderer#drawText
1399          * @see JXG.AbstractRenderer#drawInternalText
1400          * @see JXG.AbstractRenderer#updateText
1401          * @see JXG.AbstractRenderer#updateInternalText
1402          * @see JXG.AbstractRenderer#updateInternalTextStyle
1403          */
1404         updateTextStyle: function (el, doHighlight) {
1405             var fs,
1406                 so, sc,
1407                 css,
1408                 node,
1409                 display = Env.isBrowser ? el.visProp.display : "internal",
1410                 nodeList = ["rendNode", "rendNodeTag", "rendNodeLabel"],
1411                 lenN = nodeList.length,
1412                 fontUnit = el.evalVisProp('fontunit'),
1413                 cssList,
1414                 prop, pair,
1415                 style,
1416                 cssString,
1417                 styleList = ["cssdefaultstyle", "cssstyle"],
1418                 lenS = styleList.length;
1419 
1420             if (doHighlight) {
1421                 sc = el.evalVisProp('highlightstrokecolor');
1422                 so = el.evalVisProp('highlightstrokeopacity');
1423                 css = el.evalVisProp('highlightcssclass');
1424             } else {
1425                 sc = el.evalVisProp('strokecolor');
1426                 so = el.evalVisProp('strokeopacity');
1427                 css = el.evalVisProp('cssclass');
1428             }
1429 
1430             // This part is executed for all text elements except internal texts in canvas.
1431             // HTML-texts or internal texts in SVG or VML.
1432             //            HTML    internal
1433             //  SVG        +         +
1434             //  VML        +         +
1435             //  canvas     +         -
1436             //  no         -         -
1437             if (this.type !== "no" && (display === "html" || this.type !== 'canvas')) {
1438                 for (style = 0; style < lenS; style++) {
1439                     // First set cssString to
1440                     // ev.cssdefaultstyle of ev.highlightcssdefaultstyle,
1441                     // then to
1442                     // ev.cssstyle of ev.highlightcssstyle
1443                     cssString = el.evalVisProp(
1444                         (doHighlight ? 'highlight' : '') + styleList[style]
1445                     );
1446                     // Set the CSS style properties - without deleting other properties
1447                     for (node = 0; node < lenN; node++) if (Type.exists(el[nodeList[node]])) {
1448                         if (cssString !== "" && el.visPropOld[styleList[style] + '_' + node] !== cssString) {
1449                             cssList = Type.css2js(cssString);
1450                             for (prop in cssList) if (cssList.hasOwnProperty(prop)) {
1451                                 pair = cssList[prop];
1452                                 el[nodeList[node]].style[pair.key] = pair.val;
1453                             }
1454                             el.visPropOld[styleList[style] + '_' + node] = cssString;
1455                         }
1456                         // el.visPropOld[styleList[style]] = cssString;
1457                     }
1458                 }
1459 
1460                 fs = el.evalVisProp('fontsize');
1461                 if (el.visPropOld.fontsize !== fs) {
1462                     el.needsSizeUpdate = true;
1463                     try {
1464                         for (node = 0; node < lenN; node++) {
1465                             if (Type.exists(el[nodeList[node]])) {
1466                                 el[nodeList[node]].style.fontSize = fs + fontUnit;
1467                             }
1468                         }
1469                     } catch (e) {
1470                         // IE needs special treatment.
1471                         for (node = 0; node < lenN; node++) {
1472                             if (Type.exists(el[nodeList[node]])) {
1473                                 el[nodeList[node]].style.fontSize = fs;
1474                             }
1475                         }
1476                     }
1477                     el.visPropOld.fontsize = fs;
1478                 }
1479             }
1480 
1481             this.setTabindex(el);
1482 
1483             this.setObjectTransition(el);
1484             if (display === "html" && this.type !== 'no') {
1485                 // Set new CSS class
1486                 if (el.visPropOld.cssclass !== css) {
1487                     el.rendNode.className = css;
1488                     el.visPropOld.cssclass = css;
1489                     el.needsSizeUpdate = true;
1490                 }
1491                 this.setObjectStrokeColor(el, sc, so);
1492             } else {
1493                 this.updateInternalTextStyle(el, sc, so);
1494             }
1495 
1496             if (el.evalVisProp('aria.enabled')) {
1497                 this.setARIA(el);
1498             }
1499 
1500             return this;
1501         },
1502 
1503         /**
1504          * Set color and opacity of internal texts.
1505          * This method is used for Canvas and VML.
1506          * SVG needs its own version.
1507          * @private
1508          * @see JXG.AbstractRenderer#updateTextStyle
1509          * @see JXG.SVGRenderer#updateInternalTextStyle
1510          */
1511         updateInternalTextStyle: function (el, strokeColor, strokeOpacity) {
1512             this.setObjectStrokeColor(el, strokeColor, strokeOpacity);
1513         },
1514 
1515         /* ********* Image related stuff *********** */
1516 
1517         /**
1518          * Draws an {@link JXG.Image} on a board; This is just a template that has to be implemented by special
1519          * renderers.
1520          * @param {JXG.Image} el Reference to the image object that is to be drawn
1521          * @see Image
1522          * @see JXG.Image
1523          * @see JXG.AbstractRenderer#updateImage
1524          */
1525         drawImage: function (el) { /* stub */ },
1526 
1527         /**
1528          * Updates the properties of an {@link JXG.Image} element.
1529          * @param {JXG.Image} el Reference to an {@link JXG.Image} object, that has to be updated.
1530          * @see Image
1531          * @see JXG.Image
1532          * @see JXG.AbstractRenderer#drawImage
1533          */
1534         updateImage: function (el) {
1535             this.updateRectPrim(
1536                 el.rendNode,
1537                 el.coords.scrCoords[1],
1538                 el.coords.scrCoords[2] - el.size[1],
1539                 el.size[0],
1540                 el.size[1]
1541             );
1542 
1543             this.updateImageURL(el);
1544             this.transformRect(el, el.transformations);
1545             this._updateVisual(el, { stroke: true, dash: true }, true);
1546         },
1547 
1548         /**
1549          * Multiplication of transformations without updating. That means, at that point it is expected that the
1550          * matrices contain numbers only. First, the origin in user coords is translated to <tt>(0,0)</tt> in screen
1551          * coords. Then, the stretch factors are divided out. After the transformations in user coords, the stretch
1552          * factors are multiplied in again, and the origin in user coords is translated back to its position. This
1553          * method does not have to be implemented in a new renderer.
1554          * @param {JXG.GeometryElement} el A JSXGraph element. We only need its board property.
1555          * @param {Array} transformations An array of JXG.Transformations.
1556          * @returns {Array} A matrix represented by a two dimensional array of numbers.
1557          * @see JXG.AbstractRenderer#transformRect
1558          */
1559         joinTransforms: function (el, transformations) {
1560             var i,
1561                 ox = el.board.origin.scrCoords[1],
1562                 oy = el.board.origin.scrCoords[2],
1563                 ux = el.board.unitX,
1564                 uy = el.board.unitY,
1565 
1566                 len = transformations.length,
1567                 // Translate to 0,0 in screen coords and then scale
1568                 m = [
1569                     [1, 0, 0],
1570                     [-ox / ux, 1 / ux, 0],
1571                     [oy / uy, 0, -1 / uy]
1572                 ];
1573 
1574             for (i = 0; i < len; i++) {
1575                 m = Mat.matMatMult(transformations[i].matrix, m);
1576             }
1577             // Scale back and then translate back
1578             m = Mat.matMatMult(
1579                 [
1580                     [1, 0, 0],
1581                     [ox, ux, 0],
1582                     [oy, 0, -uy]
1583                 ],
1584                 m
1585             );
1586             return m;
1587         },
1588 
1589         /**
1590          * Applies transformations on images and text elements. This method has to implemented in
1591          * all descendant classes where text and image transformations are to be supported.
1592          * <p>
1593          * Only affine transformation are supported, no proper projective transformations. This means, the
1594          * respective entries of the transformation matrix are simply ignored.
1595          *
1596          * @param {JXG.Image|JXG.Text} el A {@link JXG.Image} or {@link JXG.Text} object.
1597          * @param {Array} transformations An array of {@link JXG.Transformation} objects. This is usually the
1598          * transformations property of the given element <tt>el</tt>.
1599          */
1600         transformRect: function (el, transformations) { /* stub */ },
1601 
1602         /**
1603          * If the URL of the image is provided by a function the URL has to be updated during updateImage()
1604          * @param {JXG.Image} el Reference to an image object.
1605          * @see JXG.AbstractRenderer#updateImage
1606          */
1607         updateImageURL: function (el) { /* stub */ },
1608 
1609         /**
1610          * Updates CSS style properties of a {@link JXG.Image} node.
1611          * In SVGRenderer opacity is the only available style element.
1612          * This function is called by highlight() and nohighlight().
1613          * This function works for VML.
1614          * It does not work for Canvas.
1615          * SVGRenderer overwrites this method.
1616          * @param {JXG.Text} el Reference to the {@link JXG.Image} object, that has to be updated.
1617          * @param {Boolean} doHighlight
1618          * @see Image
1619          * @see JXG.Image
1620          * @see JXG.AbstractRenderer#highlight
1621          * @see JXG.AbstractRenderer#noHighlight
1622          */
1623         updateImageStyle: function (el, doHighlight) {
1624             el.rendNode.className = el.evalVisProp(
1625                 doHighlight ? 'highlightcssclass' : 'cssclass'
1626             );
1627         },
1628 
1629         drawForeignObject: function (el) { /* stub */ },
1630 
1631         updateForeignObject: function (el) {
1632             /* stub */
1633         },
1634 
1635         /* ********* Render primitive objects *********** */
1636 
1637         /**
1638          * Appends a node to a specific layer level. This is just an abstract method and has to be implemented
1639          * in all renderers that want to use the <tt>createPrim</tt> model to draw.
1640          * @param {Node} node A DOM tree node.
1641          * @param {Number} level The layer the node is attached to. This is the index of the layer in
1642          * {@link JXG.SVGRenderer#layer} or the <tt>z-index</tt> style property of the node in VMLRenderer.
1643          */
1644         appendChildPrim: function (node, level) { /* stub */ },
1645 
1646         /**
1647          * Stores the rendering nodes. This is an abstract method which has to be implemented in all renderers that use
1648          * the <tt>createPrim</tt> method.
1649          * @param {JXG.GeometryElement} el A JSXGraph element.
1650          * @param {String} type The XML node name. Only used in VMLRenderer.
1651          */
1652         appendNodesToElement: function (el, type) { /* stub */ },
1653 
1654         /**
1655          * Creates a node of a given type with a given id.
1656          * @param {String} type The type of the node to create.
1657          * @param {String} id Set the id attribute to this.
1658          * @returns {Node} Reference to the created node.
1659          */
1660         createPrim: function (type, id) { /* stub */ return null; },
1661 
1662         /**
1663          * Removes an element node. Just a stub.
1664          * @param {Node} node The node to remove.
1665          */
1666         remove: function (node) { /* stub */ },
1667 
1668         /**
1669          * Can be used to create the nodes to display arrows. This is an abstract method which has to be implemented
1670          * in any descendant renderer.
1671          * @param {JXG.GeometryElement} el The element the arrows are to be attached to.
1672          * @param {Object} arrowData Data concerning possible arrow heads
1673          *
1674          */
1675         makeArrows: function (el, arrowData) { /* stub */ },
1676 
1677         /**
1678          * Updates width of an arrow DOM node. Used in
1679          * @param {Node} node The arrow node.
1680          * @param {Number} width
1681          * @param {Node} parentNode Used in IE only
1682          */
1683         _setArrowWidth: function (node, width, parentNode) { /* stub */ },
1684 
1685         /**
1686          * Updates an ellipse node primitive. This is an abstract method which has to be implemented in all renderers
1687          * that use the <tt>createPrim</tt> method.
1688          * @param {Node} node Reference to the node.
1689          * @param {Number} x Centre X coordinate
1690          * @param {Number} y Centre Y coordinate
1691          * @param {Number} rx The x-axis radius.
1692          * @param {Number} ry The y-axis radius.
1693          */
1694         updateEllipsePrim: function (node, x, y, rx, ry) { /* stub */ },
1695 
1696         /**
1697          * Refreshes a line node. This is an abstract method which has to be implemented in all renderers that use
1698          * the <tt>createPrim</tt> method.
1699          * @param {Node} node The node to be refreshed.
1700          * @param {Number} p1x The first point's x coordinate.
1701          * @param {Number} p1y The first point's y coordinate.
1702          * @param {Number} p2x The second point's x coordinate.
1703          * @param {Number} p2y The second point's y coordinate.
1704          * @param {JXG.Board} board
1705          */
1706         updateLinePrim: function (node, p1x, p1y, p2x, p2y, board) { /* stub */ },
1707 
1708         /**
1709          * Updates a path element. This is an abstract method which has to be implemented in all renderers that use
1710          * the <tt>createPrim</tt> method.
1711          * @param {Node} node The path node.
1712          * @param {String} pathString A string formatted like e.g. <em>'M 1,2 L 3,1 L5,5'</em>. The format of the string
1713          * depends on the rendering engine.
1714          * @param {JXG.Board} board Reference to the element's board.
1715          */
1716         updatePathPrim: function (node, pathString, board) { /* stub */ },
1717 
1718         /**
1719          * Builds a path data string to draw a point with a face other than <em>rect</em> and <em>circle</em>. Since
1720          * the format of such a string usually depends on the renderer this method
1721          * is only an abstract method. Therefore, it has to be implemented in the descendant renderer itself unless
1722          * the renderer does not use the createPrim interface but the draw* interfaces to paint.
1723          * @param {JXG.Point} el The point element
1724          * @param {Number} size A positive number describing the size. Usually the half of the width and height of
1725          * the drawn point.
1726          * @param {String} type A string describing the point's face. This method only accepts the shortcut version of
1727          * each possible face: <tt>x, +, |, -, [], <>, <<>>,^, v, >, < </tt>
1728          */
1729         updatePathStringPoint: function (el, size, type) { /* stub */ },
1730 
1731         /**
1732          * Builds a path data string from a {@link JXG.Curve} element. Since the path data strings heavily depend on the
1733          * underlying rendering technique this method is just a stub. Although such a path string is of no use for the
1734          * CanvasRenderer, this method is used there to draw a path directly.
1735          * @param {JXG.GeometryElement} el
1736          */
1737         updatePathStringPrim: function (el) { /* stub */ },
1738 
1739         /**
1740          * Builds a path data string from a {@link JXG.Curve} element such that the curve looks like hand drawn. Since
1741          * the path data strings heavily depend on the underlying rendering technique this method is just a stub.
1742          * Although such a path string is of no use for the CanvasRenderer, this method is used there to draw a path
1743          * directly.
1744          * @param  {JXG.GeometryElement} el
1745          */
1746         updatePathStringBezierPrim: function (el) { /* stub */ },
1747 
1748         /**
1749          * Update a polygon primitive.
1750          * @param {Node} node
1751          * @param {JXG.Polygon} el A JSXGraph element of type {@link JXG.Polygon}
1752          */
1753         updatePolygonPrim: function (node, el) { /* stub */ },
1754 
1755         /**
1756          * Update a rectangle primitive. This is used only for points with face of type 'rect'.
1757          * @param {Node} node The node yearning to be updated.
1758          * @param {Number} x x coordinate of the top left vertex.
1759          * @param {Number} y y coordinate of the top left vertex.
1760          * @param {Number} w Width of the rectangle.
1761          * @param {Number} h The rectangle's height.
1762          */
1763         updateRectPrim: function (node, x, y, w, h) { /* stub */ },
1764 
1765         /* ********* Set attributes *********** */
1766 
1767         /**
1768          * Shows or hides an element on the canvas; Only a stub, requires implementation in the derived renderer.
1769          * @param {JXG.GeometryElement} el Reference to the object that has to appear.
1770          * @param {Boolean} value true to show the element, false to hide the element.
1771          */
1772         display: function (el, value) {
1773             if (el) {
1774                 el.visPropOld.visible = value;
1775             }
1776         },
1777 
1778         /**
1779          * Hides an element on the canvas; Only a stub, requires implementation in the derived renderer.
1780          *
1781          * Please use JXG.AbstractRenderer#display instead
1782          * @param {JXG.GeometryElement} el Reference to the geometry element that has to disappear.
1783          * @see JXG.AbstractRenderer#show
1784          * @deprecated
1785          */
1786         hide: function (el) { /* stub */ },
1787 
1788         /**
1789          * Highlights an object, i.e. changes the current colors of the object to its highlighting colors
1790          * and highlighting strokewidth.
1791          * @param {JXG.GeometryElement} el Reference of the object that will be highlighted.
1792          * @param {Boolean} [suppressHighlightStrokeWidth=undefined] If undefined or false, highlighting also changes strokeWidth. This might not be
1793          * the cases for polygon borders. Thus, if a polygon is highlighted, its polygon borders change strokeWidth only if the polygon attribute
1794          * highlightByStrokeWidth == true.
1795          * @returns {JXG.AbstractRenderer} Reference to the renderer
1796          * @see JXG.AbstractRenderer#updateTextStyle
1797          */
1798         highlight: function (el, suppressHighlightStrokeWidth) {
1799             var i, do_hl, sw;
1800 
1801             this.setObjectTransition(el);
1802             if (!el.visProp.draft) {
1803                 if (el.type === Const.OBJECT_TYPE_POLYGON) {
1804                     this.setObjectFillColor(el, el.evalVisProp('highlightfillcolor'), el.evalVisProp('highlightfillopacity'));
1805                     do_hl = el.evalVisProp('highlightbystrokewidth');
1806                     for (i = 0; i < el.borders.length; i++) {
1807                         this.highlight(el.borders[i], !do_hl);
1808                     }
1809                 } else {
1810                     if (el.elementClass === Const.OBJECT_CLASS_TEXT) {
1811                         this.updateTextStyle(el, true);
1812                     } else if (el.type === Const.OBJECT_TYPE_IMAGE) {
1813                         this.updateImageStyle(el, true);
1814                         this.setObjectFillColor(
1815                             el,
1816                             el.evalVisProp('highlightfillcolor'),
1817                             el.evalVisProp('highlightfillopacity')
1818                         );
1819                     } else {
1820                         this.setObjectStrokeColor(
1821                             el,
1822                             el.evalVisProp('highlightstrokecolor'),
1823                             el.evalVisProp('highlightstrokeopacity')
1824                         );
1825                         this.setObjectFillColor(
1826                             el,
1827                             el.evalVisProp('highlightfillcolor'),
1828                             el.evalVisProp('highlightfillopacity')
1829                         );
1830                     }
1831                 }
1832 
1833                 // Highlight strokeWidth is suppressed if
1834                 // parameter suppressHighlightStrokeWidth is false or undefined.
1835                 // suppressHighlightStrokeWidth is false if polygon attribute
1836                 // highlightbystrokewidth is true.
1837                 if (!suppressHighlightStrokeWidth && el.evalVisProp('highlightstrokewidth')) {
1838                     sw = Math.max(
1839                         el.evalVisProp('highlightstrokewidth'),
1840                         el.evalVisProp('strokewidth')
1841                     );
1842                     this.setObjectStrokeWidth(el, sw);
1843                     if (
1844                         el.elementClass === Const.OBJECT_CLASS_LINE ||
1845                         el.elementClass === Const.OBJECT_CLASS_CURVE
1846                     ) {
1847                         this.updatePathWithArrowHeads(el, true);
1848                     }
1849                 }
1850             }
1851             this.setCssClass(el, el.evalVisProp('highlightcssclass'));
1852 
1853             return this;
1854         },
1855 
1856         /**
1857          * Uses the normal colors of an object, i.e. the opposite of {@link JXG.AbstractRenderer#highlight}.
1858          * @param {JXG.GeometryElement} el Reference of the object that will get its normal colors.
1859          * @returns {JXG.AbstractRenderer} Reference to the renderer
1860          * @see JXG.AbstractRenderer#updateTextStyle
1861          */
1862         noHighlight: function (el) {
1863             var i, sw;
1864 
1865             this.setObjectTransition(el);
1866             if (!el.evalVisProp('draft')) {
1867                 if (el.type === Const.OBJECT_TYPE_POLYGON) {
1868                     this.setObjectFillColor(el, el.evalVisProp('fillcolor'), el.evalVisProp('fillopacity'));
1869                     for (i = 0; i < el.borders.length; i++) {
1870                         this.noHighlight(el.borders[i]);
1871                     }
1872                 } else {
1873                     if (el.elementClass === Const.OBJECT_CLASS_TEXT) {
1874                         this.updateTextStyle(el, false);
1875                     } else if (el.type === Const.OBJECT_TYPE_IMAGE) {
1876                         this.updateImageStyle(el, false);
1877                         this.setObjectFillColor(el, el.evalVisProp('fillcolor'), el.evalVisProp('fillopacity'));
1878                     } else {
1879                         this.setObjectStrokeColor(el, el.evalVisProp('strokecolor'), el.evalVisProp('strokeopacity'));
1880                         this.setObjectFillColor(el, el.evalVisProp('fillcolor'), el.evalVisProp('fillopacity'));
1881                     }
1882                 }
1883 
1884                 sw = el.evalVisProp('strokewidth');
1885                 this.setObjectStrokeWidth(el, sw);
1886                 if (
1887                     el.elementClass === Const.OBJECT_CLASS_LINE ||
1888                     el.elementClass === Const.OBJECT_CLASS_CURVE
1889                 ) {
1890                     this.updatePathWithArrowHeads(el, false);
1891                 }
1892             }
1893             this.setCssClass(el, el.evalVisProp('cssclass'));
1894 
1895             return this;
1896         },
1897 
1898         /**
1899          * Puts an object from draft mode back into normal mode.
1900          * @param {JXG.GeometryElement} el Reference of the object that no longer is in draft mode.
1901          */
1902         removeDraft: function (el) {
1903             this.setObjectTransition(el);
1904             if (el.type === Const.OBJECT_TYPE_POLYGON) {
1905                 this.setObjectFillColor(el, el.evalVisProp('fillcolor'), el.evalVisProp('fillopacity'));
1906             } else {
1907                 if (el.type === Const.OBJECT_CLASS_POINT) {
1908                     this.setObjectFillColor(el, el.evalVisProp('fillcolor'), el.evalVisProp('fillopacity'));
1909                 }
1910                 this.setObjectStrokeColor(el, el.evalVisProp('strokecolor'), el.evalVisProp('strokeopacity'));
1911                 this.setObjectStrokeWidth(el, el.evalVisProp('strokewidth'));
1912             }
1913         },
1914 
1915         /**
1916          * Set ARIA related properties of an element. The attribute "aria" of an element contains at least the
1917          * properties "enabled", "label", and "live". Additionally, all available properties from
1918          * {@link https://developer.mozilla.org/en-US/docs/Web/Accessibility/ARIA} may be set.
1919          * <p>
1920          * In JSXGraph, the available properties are used without the leading 'aria-'.
1921          * For example, the value of the JSXGraph attribute 'aria.label' will be set to the
1922          * HTML attribute 'aria-label'.
1923          *
1924          * @param {JXG.GeometryElement} el Reference of the object that wants new
1925          *        ARIA attributes.
1926          */
1927         setARIA: function(el) { /* stub */ },
1928 
1929         /**
1930          * Sets the buffering as recommended by SVGWG. Until now only Opera supports this and will be ignored by other
1931          * browsers. Although this feature is only supported by SVG we have this method in {@link JXG.AbstractRenderer}
1932          * because it is called from outside the renderer.
1933          * @param {Node} node The SVG DOM Node which buffering type to update.
1934          * @param {String} type Either 'auto', 'dynamic', or 'static'. For an explanation see
1935          *   {@link https://www.w3.org/TR/SVGTiny12/painting.html#BufferedRenderingProperty}.
1936          */
1937         setBuffering: function (node, type) { /* stub */ },
1938 
1939         /**
1940          * Clip element to the JSXGraph container element (div). To be precise: to the SVG node
1941          * in case of SVG rendering - the only renderer, where this is relevant.
1942          *
1943          * @param {JXG.GeometryElement} el Reference of the object
1944          * @param {Boolean} val true: clip to the JSXGraph div, false: do not clip
1945          * @see JXG.SVGRenderer#setClipPathRect
1946          */
1947         setClipPath: function(el, val) { /* stub */ },
1948 
1949         /**
1950          * Sets CSS classes for elements (relevant for SVG only).
1951          *
1952          * @param {JXG.GeometryElement} el Reference of the object that wants a
1953          *         new set of CSS classes.
1954          * @param {String} cssClass String containing a space separated list of CSS classes.
1955          */
1956         setCssClass: function (el, cssClass) { /* stub */ },
1957 
1958         /**
1959          * Sets an element's dash style.
1960          * @param {JXG.GeometryElement} el An JSXGraph element.
1961          */
1962         setDashStyle: function (el) { /* stub */ },
1963 
1964         /**
1965          * Puts an object into draft mode, i.e. it's visual appearance will be changed. For GEONE<sub>x</sub>T backwards
1966          * compatibility.
1967          * @param {JXG.GeometryElement} el Reference of the object that is in draft mode.
1968          */
1969         setDraft: function (el) {
1970             if (!el.evalVisProp('draft')) {
1971                 return;
1972             }
1973             var draftColor = el.board.options.elements.draft.color,
1974                 draftOpacity = el.board.options.elements.draft.opacity;
1975 
1976                 this.setObjectTransition(el);
1977             if (el.type === Const.OBJECT_TYPE_POLYGON) {
1978                 this.setObjectFillColor(el, draftColor, draftOpacity);
1979             } else {
1980                 if (el.elementClass === Const.OBJECT_CLASS_POINT) {
1981                     this.setObjectFillColor(el, draftColor, draftOpacity);
1982                 } else {
1983                     this.setObjectFillColor(el, "none", 0);
1984                 }
1985                 this.setObjectStrokeColor(el, draftColor, draftOpacity);
1986                 this.setObjectStrokeWidth(el, el.board.options.elements.draft.strokeWidth);
1987             }
1988         },
1989 
1990         /**
1991          * Sets up nodes for rendering a gradient fill.
1992          * @param {JXG.GeometryElement}  el Reference of the object which gets the gradient
1993          */
1994         setGradient: function (el) { /* stub */ },
1995 
1996         /**
1997          * Move element into new layer. This is trivial for canvas, but needs more effort in SVG.
1998          * Does not work dynamically, i.e. if level is a function.
1999          *
2000          * @param {JXG.GeometryElement} el Element which is put into different layer
2001          * @param {Number} value Layer number
2002          * @private
2003          */
2004         setLayer: function (el, level) { /* stub */ },
2005 
2006         /**
2007          * Sets an objects fill color.
2008          * @param {JXG.GeometryElement} el Reference of the object that wants a new fill color.
2009          * @param {String} color Color in a HTML/CSS compatible format. If you don't want any fill color at all, choose
2010          * 'none'.
2011          * @param {Number} opacity Opacity of the fill color. Must be between 0 and 1.
2012          */
2013         setObjectFillColor: function (el, color, opacity) { /* stub */ },
2014 
2015         /**
2016          * Changes an objects stroke color to the given color.
2017          * @param {JXG.GeometryElement} el Reference of the {@link JXG.GeometryElement} that gets a new stroke
2018          * color.
2019          * @param {String} color Color value in a HTML compatible format, e.g. <strong>#00ff00</strong> or
2020          * <strong>green</strong> for green.
2021          * @param {Number} opacity Opacity of the fill color. Must be between 0 and 1.
2022          */
2023         setObjectStrokeColor: function (el, color, opacity) { /* stub */ },
2024 
2025         /**
2026          * Sets an element's stroke width.
2027          * @param {JXG.GeometryElement} el Reference to the geometry element.
2028          * @param {Number} width The new stroke width to be assigned to the element.
2029          */
2030         setObjectStrokeWidth: function (el, width) { /* stub */ },
2031 
2032         /**
2033          * Sets the transition duration (in milliseconds) for fill color and stroke
2034          * color and opacity.
2035          * @param {JXG.GeometryElement} el Reference of the object that wants a
2036          *         new transition duration.
2037          * @param {Number} duration (Optional) duration in milliseconds. If not given,
2038          *        element.visProp.transitionDuration is taken. This is the default.
2039          */
2040         setObjectTransition: function (el, duration) { /* stub */ },
2041 
2042         /**
2043          * Sets a node's attribute.
2044          * @param {Node} node The node that is to be updated.
2045          * @param {String} key Name of the attribute.
2046          * @param {String} val New value for the attribute.
2047          */
2048         setPropertyPrim: function (node, key, val) { /* stub */ },
2049 
2050         /**
2051          * Sets the shadow properties to a geometry element. This method is only a stub, it is implemented in the actual
2052          * renderers.
2053          * @param {JXG.GeometryElement} el Reference to a geometry object, that should get a shadow
2054          */
2055         setShadow: function (el) { /* stub */ },
2056 
2057         /**
2058          * Set the attribute `tabindex` to the attribute `tabindex` of an element.
2059          * This is only relevant for the SVG renderer.
2060          *
2061          * @param {JXG.GeometryElement} el
2062          */
2063         setTabindex: function (el) { /* stub */ },
2064 
2065         /**
2066          * Shows a hidden element on the canvas; Only a stub, requires implementation in the derived renderer.
2067          *
2068          * Please use JXG.AbstractRenderer#display instead
2069          * @param {JXG.GeometryElement} el Reference to the object that has to appear.
2070          * @see JXG.AbstractRenderer#hide
2071          * @deprecated
2072          */
2073         show: function (el) { /* stub */ },
2074 
2075         /**
2076          * Updates the gradient fill.
2077          * @param {JXG.GeometryElement} el An JSXGraph element with an area that can be filled.
2078          */
2079         updateGradient: function (el) { /* stub */ },
2080 
2081         /* ********* Renderer control *********** */
2082 
2083         /**
2084          * Stop redraw. This method is called before every update, so a non-vector-graphics based renderer can use this
2085          * method to delete the contents of the drawing panel. This is an abstract method every descendant renderer
2086          * should implement, if appropriate.
2087          * @see JXG.AbstractRenderer#unsuspendRedraw
2088          */
2089         suspendRedraw: function () { /* stub */ },
2090 
2091         /**
2092          * Restart redraw. This method is called after updating all the rendering node attributes.
2093          * @see JXG.AbstractRenderer#suspendRedraw
2094          */
2095         unsuspendRedraw: function () { /* stub */ },
2096 
2097         /**
2098          * The tiny zoom bar shown on the bottom of a board (if board attribute "showNavigation" is true).
2099          * It is a div element and gets the CSS class "JXG_navigation" and the id {board id}_navigationbar.
2100          * <p>
2101          * The buttons get the CSS class "JXG_navigation_button" and the id {board_id}_name where name is
2102          * one of [top, down, left, right, out, 100, in, fullscreen, screenshot, reload, cleartraces].
2103          * <p>
2104          * The symbols for zoom, navigation and reload are hard-coded.
2105          *
2106          * @param {JXG.Board} board Reference to a JSXGraph board.
2107          * @param {Object} attr Attributes of the navigation bar
2108          * @private
2109          */
2110         drawNavigationBar: function (board, attr) {
2111             var doc,
2112                 node,
2113                 cancelbubble = function (e) {
2114                     if (!e) {
2115                         e = window.event;
2116                     }
2117 
2118                     if (e.stopPropagation) {
2119                         // Non IE<=8
2120                         e.stopPropagation();
2121                     } else {
2122                         e.cancelBubble = true;
2123                     }
2124                 },
2125                 createButton = function (label, handler, board_id, type) {
2126                     var button;
2127 
2128                     board_id = board_id || "";
2129 
2130                     button = doc.createElement('span');
2131                     button.innerHTML = label; // button.appendChild(doc.createTextNode(label));
2132 
2133                     // Style settings are superseded by adding the CSS class below
2134                     button.style.paddingLeft = '7px';
2135                     button.style.paddingRight = '7px';
2136 
2137                     if (button.classList !== undefined) {
2138                         // classList not available in IE 9
2139                         button.classList.add("JXG_navigation_button");
2140                         button.classList.add("JXG_navigation_button_" + type);
2141                     }
2142                     // button.setAttribute('tabindex', 0);
2143 
2144                     button.setAttribute("id", board_id + '_navigation_' + type);
2145                     button.setAttribute("aria-hidden", 'true');   // navigation buttons should never appear in screen reader
2146 
2147                     node.appendChild(button);
2148 
2149                     Env.addEvent(
2150                         button,
2151                         "click",
2152                         function (e) {
2153                             Type.bind(handler, board)();
2154                             return false;
2155                         },
2156                         board
2157                     );
2158                     // prevent the click from bubbling down to the board
2159                     Env.addEvent(button, "pointerup", cancelbubble, board);
2160                     Env.addEvent(button, "pointerdown", cancelbubble, board);
2161                     Env.addEvent(button, "pointerleave", cancelbubble, board);
2162                     Env.addEvent(button, "mouseup", cancelbubble, board);
2163                     Env.addEvent(button, "mousedown", cancelbubble, board);
2164                     Env.addEvent(button, "touchend", cancelbubble, board);
2165                     Env.addEvent(button, "touchstart", cancelbubble, board);
2166                 };
2167 
2168             if (Env.isBrowser && this.type !== 'no') {
2169                 doc = board.containerObj.ownerDocument;
2170                 node = doc.createElement('div');
2171 
2172                 node.setAttribute("id", board.container + "_navigationbar");
2173 
2174                 // Style settings are superseded by adding the CSS class below
2175                 node.style.color = attr.strokecolor;
2176                 node.style.backgroundColor = attr.fillcolor;
2177                 node.style.padding = attr.padding;
2178                 node.style.position = attr.position;
2179                 node.style.fontSize = attr.fontsize;
2180                 node.style.cursor = attr.cursor;
2181                 node.style.zIndex = attr.zindex;
2182                 board.containerObj.appendChild(node);
2183                 node.style.right = attr.right;
2184                 node.style.bottom = attr.bottom;
2185 
2186                 if (node.classList !== undefined) {
2187                     // classList not available in IE 9
2188                     node.classList.add("JXG_navigation");
2189                 }
2190                 // For XHTML we need unicode instead of HTML entities
2191 
2192                 if (board.attr.showfullscreen) {
2193                     createButton(
2194                         board.attr.fullscreen.symbol,
2195                         function () {
2196                             board.toFullscreen(board.attr.fullscreen.id);
2197                         },
2198                         board.container, "fullscreen"
2199                     );
2200                 }
2201 
2202                 if (board.attr.showscreenshot) {
2203                     createButton(
2204                         board.attr.screenshot.symbol,
2205                         function () {
2206                             window.setTimeout(function () {
2207                                 board.renderer.screenshot(board, "", false);
2208                             }, 330);
2209                         },
2210                         board.container, "screenshot"
2211                     );
2212                 }
2213 
2214                 if (board.attr.showreload) {
2215                     // full reload circle: \u27F2
2216                     // the board.reload() method does not exist during the creation
2217                     // of this button. That's why this anonymous function wrapper is required.
2218                     createButton(
2219                         "\u21BB",
2220                         function () {
2221                             board.reload();
2222                         },
2223                         board.container, "reload"
2224                     );
2225                 }
2226 
2227                 if (board.attr.showcleartraces) {
2228                     // clear traces symbol (otimes): \u27F2
2229                     createButton("\u2297",
2230                         function () {
2231                             board.clearTraces();
2232                         },
2233                         board.container, "cleartraces"
2234                     );
2235                 }
2236 
2237                 if (board.attr.shownavigation) {
2238                     if (board.attr.showzoom) {
2239                         createButton("\u2013", board.zoomOut, board.container, 'out');
2240                         createButton("o", board.zoom100, board.container, '100');
2241                         createButton("+", board.zoomIn, board.container, 'in');
2242                     }
2243                     createButton("\u2190", board.clickLeftArrow, board.container, 'left');
2244                     createButton("\u2193", board.clickUpArrow, board.container, 'down'); // Down arrow
2245                     createButton("\u2191", board.clickDownArrow, board.container, 'up'); // Up arrow
2246                     createButton("\u2192", board.clickRightArrow, board.container, 'right');
2247                 }
2248             }
2249         },
2250 
2251         /**
2252          * Wrapper for getElementById for maybe other renderers which elements are not directly accessible by DOM
2253          * methods like document.getElementById().
2254          * @param {String} id Unique identifier for element.
2255          * @returns {Object} Reference to a JavaScript object. In case of SVG/VMLRenderer it's a reference to a SVG/VML node.
2256          */
2257         getElementById: function (id) {
2258             var str;
2259             if (Type.exists(this.container)) {
2260                 // Use querySelector over getElementById for compatibility with both 'regular' document
2261                 // and ShadowDOM fragments.
2262                 str = this.container.id + '_' + id;
2263                 // Mask special symbols like '/' and '\' in id
2264                 if (Type.exists(CSS) && Type.exists(CSS.escape)) {
2265                     str = CSS.escape(str);
2266                 }
2267                 return this.container.querySelector('#' + str);
2268             }
2269             return "";
2270         },
2271 
2272         /**
2273          * Remove an element and provide a function that inserts it into its original position. This method
2274          * is taken from this article {@link https://developers.google.com/speed/articles/javascript-dom}.
2275          * @author KeeKim Heng, Google Web Developer
2276          * @param {Element} el The element to be temporarily removed
2277          * @returns {Function} A function that inserts the element into its original position
2278          */
2279         removeToInsertLater: function (el) {
2280             var parentNode = el.parentNode,
2281                 nextSibling = el.nextSibling;
2282 
2283             if (parentNode === null) {
2284                 return;
2285             }
2286             parentNode.removeChild(el);
2287 
2288             return function () {
2289                 if (nextSibling) {
2290                     parentNode.insertBefore(el, nextSibling);
2291                 } else {
2292                     parentNode.appendChild(el);
2293                 }
2294             };
2295         },
2296 
2297         /**
2298          * Resizes the rendering element
2299          * @param {Number} w New width
2300          * @param {Number} h New height
2301          */
2302         resize: function (w, h) { /* stub */ },
2303 
2304         /**
2305          * Create crosshair elements (Fadenkreuz) for presentations.
2306          * @param {Number} n Number of crosshairs.
2307          */
2308         createTouchpoints: function (n) { /* stub */ },
2309 
2310         /**
2311          * Show a specific crosshair.
2312          * @param {Number} i Number of the crosshair to show
2313          */
2314         showTouchpoint: function (i) { /* stub */ },
2315 
2316         /**
2317          * Hide a specific crosshair.
2318          * @param {Number} i Number of the crosshair to show
2319          */
2320         hideTouchpoint: function (i) { /* stub */ },
2321 
2322         /**
2323          * Move a specific crosshair.
2324          * @param {Number} i Number of the crosshair to show
2325          * @param {Array} pos New positon in screen coordinates
2326          */
2327         updateTouchpoint: function (i, pos) { /* stub */ },
2328 
2329         /* ********* Dump related stuff *********** */
2330 
2331         /**
2332          * Convert SVG construction to base64 encoded SVG data URL.
2333          * Only available on SVGRenderer.
2334          *
2335          * @see JXG.SVGRenderer#dumpToDataURI
2336          */
2337         dumpToDataURI: function (_ignoreTexts) { /* stub */ },
2338 
2339         /**
2340          * Convert SVG construction to canvas.
2341          * Only available on SVGRenderer.
2342          *
2343          * @see JXG.SVGRenderer#dumpToCanvas
2344          */
2345         dumpToCanvas: function (canvasId, w, h, _ignoreTexts) { /* stub */ },
2346 
2347         /**
2348          * Display SVG image in html img-tag which enables
2349          * easy download for the user.
2350          *
2351          * See JXG.SVGRenderer#screenshot
2352          */
2353         screenshot: function (board) { /* stub */ }
2354 
2355     }
2356 );
2357 
2358 export default JXG.AbstractRenderer;
2359