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