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