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