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