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