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

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