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