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