      1 /*
      2     Copyright 2008-2025
      3         Matthias Ehmann,
      4         Michael Gerhaeuser,
      5         Carsten Miller,
      6         Bianca Valentin,
      7         Alfred Wassermann,
      8         Peter Wilfahrt
      9
     10     This file is part of JSXGraph.
     11
     12     JSXGraph is free software dual licensed under the GNU LGPL or MIT License.
     13
     14     You can redistribute it and/or modify it under the terms of the
     15
     16       * GNU Lesser General Public License as published by
     17         the Free Software Foundation, either version 3 of the License, or
     18         (at your option) any later version
     19       OR
     20       * MIT License: https://github.com/jsxgraph/jsxgraph/blob/master/LICENSE.MIT
     21
     22     JSXGraph is distributed in the hope that it will be useful,
     23     but WITHOUT ANY WARRANTY; without even the implied warranty of
     24     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
     25     GNU Lesser General Public License for more details.
     26
     27     You should have received a copy of the GNU Lesser General Public License and
     28     the MIT License along with JSXGraph. If not, see <https://www.gnu.org/licenses/>
     29     and <https://opensource.org/licenses/MIT/>.
     30  */
     31
     32 /*global JXG: true, define: true, window: true*/
     33 /*jslint nomen: true, plusplus: true*/
     34
     35 /**
     36  * @fileoverview In this file the Text element is defined.
     37  */
     38
     39 import JXG from "../jxg.js";
     40 import Const from "./constants.js";
     41 import GeometryElement from "./element.js";
     42 import GeonextParser from "../parser/geonext.js";
     43 import Env from "../utils/env.js";
     44 import Type from "../utils/type.js";
     45 import Mat from "../math/math.js";
     46 import CoordsElement from "./coordselement.js";
     47
     48 var priv = {
     49     /**
     50      * @class
     51      * @ignore
     52      */
     53     HTMLSliderInputEventHandler: function () {
     54         this._val = parseFloat(this.rendNodeRange.value);
     55         this.rendNodeOut.value = this.rendNodeRange.value;
     56         this.board.update();
     57     }
     58 };
     59
     60 /**
     61  * Construct and handle texts.
     62  *
     63  * The coordinates can be relative to the coordinates of an element
     64  * given in {@link JXG.Options#text.anchor}.
     65  *
     66  * MathJax, HTML and GEONExT syntax can be handled.
     67  * @class Creates a new text object. Do not use this constructor to create a text. Use {@link JXG.Board#create} with
     68  * type {@link Text} instead.
     69  * @augments JXG.GeometryElement
     70  * @augments JXG.CoordsElement
     71  * @param {string|JXG.Board} board The board the new text is drawn on.
     72  * @param {Array} coordinates An array with the user coordinates of the text.
     73  * @param {Object} attributes An object containing visual properties and optional a name and a id.
     74  * @param {string|function} content A string or a function returning a string.
     75  *
     76  */
     77 JXG.Text = function (board, coords, attributes, content) {
     78     var tmp;
     79
     80     this.constructor(board, attributes, Const.OBJECT_TYPE_TEXT, Const.OBJECT_CLASS_TEXT);
     81
     82     this.element = this.board.select(attributes.anchor);
     83     this.coordsConstructor(coords, this.evalVisProp('islabel'));
     84
     85     this.content = "";
     86     this.plaintext = "";
     87     this.plaintextOld = null;
     88     this.orgText = "";
     89
     90     this.needsSizeUpdate = false;
     91     // Only used by infobox anymore
     92     this.hiddenByParent = false;
     93
     94     /**
     95      * Width and height of the text element in pixel.
     96      *
     97      * @private
     98      * @type Array
     99      */
    100     this.size = [1.0, 1.0];
    101     this.id = this.board.setId(this, "T");
    102
    103     this.board.renderer.drawText(this);
    104     this.board.finalizeAdding(this);
    105
    106     // Set text before drawing
    107     // this._createFctUpdateText(content);
    108     // this.updateText();
    109
    110     // Set attribute visible to true. This is necessary to
    111     // create all sub-elements for button, input and checkbox
    112     tmp = this.visProp.visible;
    113     this.visProp.visible = true;
    114     this.setText(content);
    115     // Restore the correct attribute visible.
    116     this.visProp.visible = tmp;
    117
    118     if (Type.isString(this.content)) {
    119         this.notifyParents(this.content);
    120     }
    121     this.elType = "text";
    122
    123     this.methodMap = Type.deepCopy(this.methodMap, {
    124         setText: "setTextJessieCode",
    125         // free: 'free',
    126         move: "setCoords",
    127         Size: "getSize",
    128         setAutoPosition: "setAutoPosition"
    129     });
    130 };
    131
    132 JXG.Text.prototype = new GeometryElement();
    133 Type.copyPrototypeMethods(JXG.Text, CoordsElement, "coordsConstructor");
    134
    135 JXG.extend(
    136     JXG.Text.prototype,
    137     /** @lends JXG.Text.prototype */ {
    138         /**
    139          * @private
    140          * @param {Number} x
    141          * @param {Number} y
    142          * @returns {Boolean}
    143         */
    144         // Test if the screen coordinates (x,y) are in a small stripe
    145         // at the left side or at the right side of the text.
    146         // Sensitivity is set in this.board.options.precision.hasPoint.
    147         // If dragarea is set to 'all' (default), tests if the screen
    148         // coordinates (x,y) are in within the text boundary.
    149         hasPoint: function (x, y) {
    150             var lft, rt, top, bot, ax, ay, type, r;
    151
    152             if (Type.isObject(this.evalVisProp('precision'))) {
    153                 type = this.board._inputDevice;
    154                 r = this.evalVisProp('precision.' + type);
    155             } else {
    156                 // 'inherit'
    157                 r = this.board.options.precision.hasPoint;
    158             }
    159             if (this.transformations.length > 0) {
    160                 //Transform the mouse/touch coordinates
    161                 // back to the original position of the text.
    162                 lft = Mat.matVecMult(
    163                     Mat.inverse(this.board.renderer.joinTransforms(this, this.transformations)),
    164                     [1, x, y]
    165                 );
    166                 x = lft[1];
    167                 y = lft[2];
    168             }
    169
    170             ax = this.getAnchorX();
    171             if (ax === "right") {
    172                 lft = this.coords.scrCoords[1] - this.size[0];
    173             } else if (ax === "middle") {
    174                 lft = this.coords.scrCoords[1] - 0.5 * this.size[0];
    175             } else {
    176                 lft = this.coords.scrCoords[1];
    177             }
    178             rt = lft + this.size[0];
    179
    180             ay = this.getAnchorY();
    181             if (ay === "top") {
    182                 bot = this.coords.scrCoords[2] + this.size[1];
    183             } else if (ay === "middle") {
    184                 bot = this.coords.scrCoords[2] + 0.5 * this.size[1];
    185             } else {
    186                 bot = this.coords.scrCoords[2];
    187             }
    188             top = bot - this.size[1];
    189
    190             if (this.evalVisProp('dragarea') === "all") {
    191                 return x >= lft - r && x < rt + r && y >= top - r && y <= bot + r;
    192             }
    193             // e.g. 'small'
    194             return (
    195                 y >= top - r &&
    196                 y <= bot + r &&
    197                 ((x >= lft - r && x <= lft + 2 * r) || (x >= rt - 2 * r && x <= rt + r))
    198             );
    199         },
    200
    201         /**
    202          * This sets the updateText function of this element depending on the type of text content passed.
    203          * Used by {@link JXG.Text#_setText}.
    204          * @param {String|Function|Number} text
    205          * @private
    206          * @see JXG.Text#_setText
    207          */
    208         _createFctUpdateText: function (text) {
    209             var updateText, e, digits,
    210                 resolvedText,
    211                 i, that,
    212                 ev_p = this.evalVisProp('parse'),
    213                 ev_um = this.evalVisProp('usemathjax'),
    214                 ev_uk = this.evalVisProp('usekatex'),
    215                 convertJessieCode = false;
    216
    217             this.orgText = text;
    218
    219             if (Type.isFunction(text)) {
    220                 /**
    221                  * Dynamically created function to update the content
    222                  * of a text. Can not be overwritten.
    223                  * <p>
    224                  * <value> tags will not be evaluated if text is provided by a function
    225                  * <p>
    226                  * Sets the property <tt>plaintext</tt> of the text element.
    227                  *
    228                  * @private
    229                  */
    230                 this.updateText = function () {
    231                     resolvedText = text().toString(); // Evaluate function
    232                     if (ev_p && !ev_um && !ev_uk) {
    233                         this.plaintext = this.replaceSub(
    234                             this.replaceSup(
    235                                 this.convertGeonextAndSketchometry2CSS(resolvedText, false)
    236                             )
    237                         );
    238                     } else {
    239                         this.plaintext = resolvedText;
    240                     }
    241                 };
    242             } else {
    243                 if (Type.isNumber(text) && this.evalVisProp('formatnumber')) {
    244                     if (this.evalVisProp('tofraction')) {
    245                         if (ev_um) {
    246                             this.content = '\\(' + Type.toFraction(text, true) + '\\)';
    247                         } else {
    248                             this.content = Type.toFraction(text, ev_uk);
    249                         }
    250                     } else {
    251                         digits = this.evalVisProp('digits');
    252                         if (this.useLocale()) {
    253                             this.content = this.formatNumberLocale(text, digits);
    254                         } else {
    255                             this.content = Type.toFixed(text, digits);
    256                         }
    257                     }
    258                 } else if (Type.isString(text) && ev_p) {
    259                     if (this.evalVisProp('useasciimathml')) {
    260                         // ASCIIMathML
    261                         // value-tags are not supported
    262                         this.content = "'`" + text + "`'";
    263                     } else if (ev_um || ev_uk) {
    264                         // MathJax or KaTeX
    265                         // Replace value-tags by functions
    266                         // sketchofont is ignored
    267                         this.content = this.valueTagToJessieCode(text);
    268                         if (!Type.isArray(this.content)) {
    269                             // For some reason we don't have to mask backslashes in an array of strings
    270                             // anymore.
    271                             //
    272                             // for (i = 0; i < this.content.length; i++) {
    273                             //     this.content[i] = this.content[i].replace(/\\/g, "\\\\"); // Replace single backslash by double
    274                             // }
    275                             // } else {
    276                             this.content = this.content.replace(/\\/g, "\\\\"); // Replace single backslash by double
    277                         }
    278                     } else {
    279                         // No TeX involved.
    280                         // Converts GEONExT syntax into JavaScript string
    281                         // Short math is allowed
    282                         // Replace value-tags by functions
    283                         // Avoid geonext2JS calls
    284                         this.content = this.poorMansTeX(this.valueTagToJessieCode(text));
    285                     }
    286                     convertJessieCode = true;
    287                 } else {
    288                     this.content = text;
    289                 }
    290
    291                 // Generate function which returns the text to be displayed
    292                 if (convertJessieCode) {
    293                     // Convert JessieCode to JS function
    294                     if (Type.isArray(this.content)) {
    295                         // This is the case if the text contained value-tags.
    296                         // These value-tags consist of JessieCode snippets
    297                         // which are now replaced by JavaScript functions
    298                         that = this;
    299                         for (i = 0; i < this.content.length; i++) {
    300                             if (this.content[i][0] !== '"') {
    301                                 this.content[i] = this.board.jc.snippet(this.content[i], true, "", false);
    302                                 for (e in this.content[i].deps) {
    303                                     this.addParents(this.content[i].deps[e]);
    304                                     this.content[i].deps[e].addChild(this);
    305                                 }
    306                             }
    307                         }
    308
    309                         updateText = function() {
    310                             var i, t,
    311                                 digits = that.evalVisProp('digits'),
    312                                 txt = '';
    313
    314                             for (i = 0; i < that.content.length; i++) {
    315                                 if (Type.isFunction(that.content[i])) {
    316                                     t = that.content[i]();
    317                                     if (that.useLocale()) {
    318                                         t = that.formatNumberLocale(t, digits);
    319                                     } else {
    320                                         t = Type.toFixed(t, digits);
    321                                     }
    322                                 } else {
    323                                     t = that.content[i];
    324                                     // Instead of 't.at(t.length - 1)' also 't.(-1)' should work.
    325                                     // However in Moodle 4.2 't.(-1)' returns an empty string.
    326                                     // In plain HTML pages it works.
    327                                     if (t[0] === '"' && t[t.length - 1] === '"') {
    328                                         t = t.slice(1, -1);
    329                                     }
    330                                 }
    331
    332                                 txt += t;
    333                             }
    334                             return txt;
    335                         };
    336                     } else {
    337                         updateText = this.board.jc.snippet(this.content, true, "", false);
    338                         for (e in updateText.deps) {
    339                             this.addParents(updateText.deps[e]);
    340                             updateText.deps[e].addChild(this);
    341                         }
    342                     }
    343
    344                     // Ticks have been escaped in valueTagToJessieCode
    345                     this.updateText = function () {
    346                         this.plaintext = this.unescapeTicks(updateText());
    347                     };
    348                 } else {
    349                     this.updateText = function () {
    350                         this.plaintext = this.content; // text;
    351                     };
    352                 }
    353             }
    354         },
    355
    356         /**
    357          * Defines new content. This is used by {@link JXG.Text#setTextJessieCode} and {@link JXG.Text#setText}. This is required because
    358          * JessieCode needs to filter all Texts inserted into the DOM and thus has to replace setText by setTextJessieCode.
    359          * @param {String|Function|Number} text
    360          * @returns {JXG.Text}
    361          * @private
    362          */
    363         _setText: function (text) {
    364             this._createFctUpdateText(text);
    365
    366             // First evaluation of the string.
    367             // We need this for display='internal' and Canvas
    368             this.updateText();
    369             this.fullUpdate();
    370
    371             // We do not call updateSize for the infobox to speed up rendering
    372             if (!this.board.infobox || this.id !== this.board.infobox.id) {
    373                 this.updateSize(); // updateSize() is called at least once.
    374             }
    375
    376             // This may slow down canvas renderer
    377             // if (this.board.renderer.type === 'canvas') {
    378             //     this.board.fullUpdate();
    379             // }
    380
    381             return this;
    382         },
    383
    384         /**
    385          * Defines new content but converts < and > to HTML entities before updating the DOM.
    386          * @param {String|function} text
    387          */
    388         setTextJessieCode: function (text) {
    389             var s;
    390
    391             this.visProp.castext = text;
    392             if (Type.isFunction(text)) {
    393                 s = function () {
    394                     return Type.sanitizeHTML(text());
    395                 };
    396             } else {
    397                 if (Type.isNumber(text)) {
    398                     s = text;
    399                 } else {
    400                     s = Type.sanitizeHTML(text);
    401                 }
    402             }
    403
    404             return this._setText(s);
    405         },
    406
    407         /**
    408          * Defines new content.
    409          * @param {String|function} text
    410          * @returns {JXG.Text} Reference to the text object.
    411          */
    412         setText: function (text) {
    413             return this._setText(text);
    414         },
    415
    416         /**
    417          * Recompute the width and the height of the text box.
    418          * Updates the array {@link JXG.Text#size} with pixel values.
    419          * The result may differ from browser to browser
    420          * by some pixels.
    421          * In canvas an old IEs we use a very crude estimation of the dimensions of
    422          * the textbox.
    423          * JSXGraph needs {@link JXG.Text#size} for applying rotations in IE and
    424          * for aligning text.
    425          *
    426          * @return {this} [description]
    427          */
    428         updateSize: function () {
    429             var tmp,
    430                 that,
    431                 node,
    432                 ev_d = this.evalVisProp('display');
    433
    434             if (!Env.isBrowser || this.board.renderer.type === "no") {
    435                 return this;
    436             }
    437             node = this.rendNode;
    438
    439             /**
    440              * offsetWidth and offsetHeight seem to be supported for internal vml elements by IE10+ in IE8 mode.
    441              */
    442             if (ev_d === "html" || this.board.renderer.type === "vml") {
    443                 if (Type.exists(node.offsetWidth)) {
    444                     that = this;
    445                     window.setTimeout(function () {
    446                         that.size = [node.offsetWidth, node.offsetHeight];
    447                         that.needsUpdate = true;
    448                         that.updateRenderer();
    449                     }, 0);
    450                     // In case, there is non-zero padding or borders
    451                     // the following approach does not longer work.
    452                     // s = [node.offsetWidth, node.offsetHeight];
    453                     // if (s[0] === 0 && s[1] === 0) { // Some browsers need some time to set offsetWidth and offsetHeight
    454                     //     that = this;
    455                     //     window.setTimeout(function () {
    456                     //         that.size = [node.offsetWidth, node.offsetHeight];
    457                     //         that.needsUpdate = true;
    458                     //         that.updateRenderer();
    459                     //     }, 0);
    460                     // } else {
    461                     //     this.size = s;
    462                     // }
    463                 } else {
    464                     this.size = this.crudeSizeEstimate();
    465                 }
    466             } else if (ev_d === "internal") {
    467                 if (this.board.renderer.type === "svg") {
    468                     that = this;
    469                     window.setTimeout(function () {
    470                         try {
    471                             tmp = node.getBBox();
    472                             that.size = [tmp.width, tmp.height];
    473                             that.needsUpdate = true;
    474                             that.updateRenderer();
    475                         } catch (e) {}
    476                     }, 0);
    477                 } else if (this.board.renderer.type === "canvas") {
    478                     this.size = this.crudeSizeEstimate();
    479                 }
    480             }
    481
    482             return this;
    483         },
    484
    485         /**
    486          * A very crude estimation of the dimensions of the textbox in case nothing else is available.
    487          * @returns {Array}
    488          */
    489         crudeSizeEstimate: function () {
    490             var ev_fs = parseFloat(this.evalVisProp('fontsize'));
    491             return [ev_fs * this.plaintext.length * 0.45, ev_fs * 0.9];
    492         },
    493
    494         /**
    495          * Decode unicode entities into characters.
    496          * @param {String} string
    497          * @returns {String}
    498          */
    499         utf8_decode: function (string) {
    500             return string.replace(/&#x(\w+);/g, function (m, p1) {
    501                 return String.fromCharCode(parseInt(p1, 16));
    502             });
    503         },
    504
    505         /**
    506          * Replace _{} by <sub>
    507          * @param {String} te String containing _{}.
    508          * @returns {String} Given string with _{} replaced by <sub>.
    509          */
    510         replaceSub: function (te) {
    511             if (!te.indexOf) {
    512                 return te;
    513             }
    514
    515             var j,
    516                 i = te.indexOf("_{");
    517
    518             // The regexp in here are not used for filtering but to provide some kind of sugar for label creation,
    519             // i.e. replacing _{...} with <sub>...</sub>. What is passed would get out anyway.
    520             /*jslint regexp: true*/
    521             while (i >= 0) {
    522                 te = te.slice(0, i) + te.slice(i).replace(/_\{/, "<sub>");
    523                 j = te.indexOf("}", i + 4);
    524                 if (j >= 0) {
    525                     te = te.slice(0, j) + te.slice(j).replace(/\}/, "</sub>");
    526                 }
    527                 i = te.indexOf("_{");
    528             }
    529
    530             i = te.indexOf("_");
    531             while (i >= 0) {
    532                 te = te.slice(0, i) + te.slice(i).replace(/_(.?)/, "<sub>$1</sub>");
    533                 i = te.indexOf("_");
    534             }
    535
    536             return te;
    537         },
    538
    539         /**
    540          * Replace ^{} by <sup>
    541          * @param {String} te String containing ^{}.
    542          * @returns {String} Given string with ^{} replaced by <sup>.
    543          */
    544         replaceSup: function (te) {
    545             if (!te.indexOf) {
    546                 return te;
    547             }
    548
    549             var j,
    550                 i = te.indexOf("^{");
    551
    552             // The regexp in here are not used for filtering but to provide some kind of sugar for label creation,
    553             // i.e. replacing ^{...} with <sup>...</sup>. What is passed would get out anyway.
    554             /*jslint regexp: true*/
    555             while (i >= 0) {
    556                 te = te.slice(0, i) + te.slice(i).replace(/\^\{/, "<sup>");
    557                 j = te.indexOf("}", i + 4);
    558                 if (j >= 0) {
    559                     te = te.slice(0, j) + te.slice(j).replace(/\}/, "</sup>");
    560                 }
    561                 i = te.indexOf("^{");
    562             }
    563
    564             i = te.indexOf("^");
    565             while (i >= 0) {
    566                 te = te.slice(0, i) + te.slice(i).replace(/\^(.?)/, "<sup>$1</sup>");
    567                 i = te.indexOf("^");
    568             }
    569
    570             return te;
    571         },
    572
    573         /**
    574          * Return the width of the text element.
    575          * @returns {Array} [width, height] in pixel
    576          */
    577         getSize: function () {
    578             return this.size;
    579         },
    580
    581         /**
    582          * Move the text to new coordinates.
    583          * @param {number} x
    584          * @param {number} y
    585          * @returns {object} reference to the text object.
    586          */
    587         setCoords: function (x, y) {
    588             var coordsAnchor, dx, dy;
    589             if (Type.isArray(x) && x.length > 1) {
    590                 y = x[1];
    591                 x = x[0];
    592             }
    593
    594             if (this.evalVisProp('islabel') && Type.exists(this.element)) {
    595                 coordsAnchor = this.element.getLabelAnchor();
    596                 dx = (x - coordsAnchor.usrCoords[1]) * this.board.unitX;
    597                 dy = -(y - coordsAnchor.usrCoords[2]) * this.board.unitY;
    598
    599                 this.relativeCoords.setCoordinates(Const.COORDS_BY_SCREEN, [dx, dy]);
    600             } else {
    601                 this.coords.setCoordinates(Const.COORDS_BY_USER, [x, y]);
    602             }
    603
    604             // this should be a local update, otherwise there might be problems
    605             // with the tick update routine resulting in orphaned tick labels
    606             this.fullUpdate();
    607
    608             return this;
    609         },
    610
    611         /**
    612          * Evaluates the text.
    613          * Then, the update function of the renderer
    614          * is called.
    615          */
    616         update: function (fromParent) {
    617             if (!this.needsUpdate) {
    618                 return this;
    619             }
    620
    621             this.updateCoords(fromParent);
    622             this.updateText();
    623
    624             if (this.evalVisProp('display') === "internal") {
    625                 if (Type.isString(this.plaintext)) {
    626                     this.plaintext = this.utf8_decode(this.plaintext);
    627                 }
    628             }
    629
    630             this.checkForSizeUpdate();
    631             if (this.needsSizeUpdate) {
    632                 this.updateSize();
    633             }
    634
    635             return this;
    636         },
    637
    638         /**
    639          * Used to save updateSize() calls.
    640          * Called in JXG.Text.update
    641          * That means this.update() has been called.
    642          * More tests are in JXG.Renderer.updateTextStyle. The latter tests
    643          * are one update off. But this should pose not too many problems, since
    644          * it affects fontSize and cssClass changes.
    645          *
    646          * @private
    647          */
    648         checkForSizeUpdate: function () {
    649             if (this.board.infobox && this.id === this.board.infobox.id) {
    650                 this.needsSizeUpdate = false;
    651             } else {
    652                 // For some magic reason it is more efficient on the iPad to
    653                 // call updateSize() for EVERY text element EVERY time.
    654                 this.needsSizeUpdate = this.plaintextOld !== this.plaintext;
    655
    656                 if (this.needsSizeUpdate) {
    657                     this.plaintextOld = this.plaintext;
    658                 }
    659             }
    660         },
    661
    662         /**
    663          * The update function of the renderer
    664          * is called.
    665          * @private
    666          */
    667         updateRenderer: function () {
    668             if (
    669                 //this.board.updateQuality === this.board.BOARD_QUALITY_HIGH &&
    670                 this.evalVisProp('autoposition')
    671             ) {
    672                 this.setAutoPosition().updateConstraint();
    673             }
    674             return this.updateRendererGeneric("updateText");
    675         },
    676
    677         /**
    678          * Converts shortened math syntax into correct syntax:  3x instead of 3*x or
    679          * (a+b)(3+1) instead of (a+b)*(3+1).
    680          *
    681          * @private
    682          * @param{String} expr Math term
    683          * @returns {string} expanded String
    684          */
    685         expandShortMath: function (expr) {
    686             var re = /([)0-9.])\s*([(a-zA-Z_])/g;
    687             return expr.replace(re, "$1*$2");
    688         },
    689
    690         /**
    691          * Converts the GEONExT syntax of the <value> terms into JavaScript.
    692          * Also, all Objects whose name appears in the term are searched and
    693          * the text is added as child to these objects.
    694          * This method is called if the attribute parse==true is set.
    695          *
    696          * Obsolete, replaced by JXG.Text.valueTagToJessieCode
    697          *
    698          * @param{String} contentStr String to be parsed
    699          * @param{Boolean} [expand] Optional flag if shortened math syntax is allowed (e.g. 3x instead of 3*x).
    700          * @param{Boolean} [avoidGeonext2JS] Optional flag if geonext2JS should be called. For backwards compatibility
    701          * this has to be set explicitly to true.
    702          * @param{Boolean} [outputTeX] Optional flag which has to be true if the resulting term will be sent to MathJax or KaTeX.
    703          * If true, "_" and "^" are NOT replaced by HTML tags sub and sup. Default: false, i.e. the replacement is done.
    704          * This flag allows the combination of <value> tag containing calculations with TeX output.
    705          *
    706          * @deprecated
    707          * @private
    708          * @see JXG.GeonextParser#geonext2JS
    709          * @see JXG.Text#valueTagToJessieCode
    710          *
    711          */
    712         generateTerm: function (contentStr, expand, avoidGeonext2JS) {
    713             var res,
    714                 term,
    715                 i,
    716                 j,
    717                 plaintext = '""';
    718
    719             // Revert possible jc replacement
    720             contentStr = contentStr || "";
    721             contentStr = contentStr.replace(/\r/g, "");
    722             contentStr = contentStr.replace(/\n/g, "");
    723             contentStr = contentStr.replace(/"/g, "'");
    724             contentStr = contentStr.replace(/'/g, "\\'");
    725
    726             // Old GEONExT syntax, not (yet) supported as TeX output.
    727             // Otherwise, the else clause should be used.
    728             // That means, i.e. the <arc> tag and <sqrt> tag are not
    729             // converted into TeX syntax.
    730             contentStr = contentStr.replace(/&arc;/g, "∠");
    731             contentStr = contentStr.replace(/<arc\s*\/>/g, "∠");
    732             contentStr = contentStr.replace(/<arc\s*\/>/g, "∠");
    733             contentStr = contentStr.replace(/<sqrt\s*\/>/g, "√");
    734
    735             contentStr = contentStr.replace(/<value>/g, "<value>");
    736             contentStr = contentStr.replace(/<\/value>/g, "</value>");
    737
    738             // Convert GEONExT syntax into  JavaScript syntax
    739             i = contentStr.indexOf("<value>");
    740             j = contentStr.indexOf("</value>");
    741             if (i >= 0) {
    742                 while (i >= 0) {
    743                     plaintext +=
    744                         ' + "' + this.replaceSub(this.replaceSup(contentStr.slice(0, i))) + '"';
    745                     // plaintext += ' + "' + this.replaceSub(contentStr.slice(0, i)) + '"';
    746
    747                     term = contentStr.slice(i + 7, j);
    748                     term = term.replace(/\s+/g, ""); // Remove all whitespace
    749                     if (expand === true) {
    750                         term = this.expandShortMath(term);
    751                     }
    752                     if (avoidGeonext2JS) {
    753                         res = term;
    754                     } else {
    755                         res = GeonextParser.geonext2JS(term, this.board);
    756                     }
    757                     res = res.replace(/\\"/g, "'");
    758                     res = res.replace(/\\'/g, "'");
    759
    760                     // GEONExT-Hack: apply rounding once only.
    761                     if (res.indexOf("toFixed") < 0) {
    762                         // output of a value tag
    763                         if (
    764                             Type.isNumber(
    765                                 Type.bind(this.board.jc.snippet(res, true, '', false), this)()
    766                             )
    767                         ) {
    768                             // may also be a string
    769                             plaintext += '+(' + res + ').toFixed(' + this.evalVisProp('digits') + ')';
    770                         } else {
    771                             plaintext += '+(' + res + ')';
    772                         }
    773                     } else {
    774                         plaintext += '+(' + res + ')';
    775                     }
    776
    777                     contentStr = contentStr.slice(j + 8);
    778                     i = contentStr.indexOf("<value>");
    779                     j = contentStr.indexOf("</value>");
    780                 }
    781             }
    782
    783             plaintext += ' + "' + this.replaceSub(this.replaceSup(contentStr)) + '"';
    784             plaintext = this.convertGeonextAndSketchometry2CSS(plaintext);
    785
    786             // This should replace e.g. &pi; by π
    787             plaintext = plaintext.replace(/&/g, "&");
    788             plaintext = plaintext.replace(/"/g, "'");
    789
    790             return plaintext;
    791         },
    792
    793         /**
    794          * Replace value-tags in string by JessieCode functions.
    795          * @param {String} contentStr
    796          * @returns String
    797          * @private
    798          * @example
    799          * "The x-coordinate of A is <value>X(A)</value>"
    800          *
    801          */
    802         valueTagToJessieCode: function (contentStr) {
    803             var res, term,
    804                 i, j,
    805                 expandShortMath = true,
    806                 textComps = [],
    807                 tick = '"';
    808
    809             contentStr = contentStr || "";
    810             contentStr = contentStr.replace(/\r/g, "");
    811             contentStr = contentStr.replace(/\n/g, "");
    812
    813             contentStr = contentStr.replace(/<value>/g, "<value>");
    814             contentStr = contentStr.replace(/<\/value>/g, "</value>");
    815
    816             // Convert content of value tag (GEONExT/JessieCode) syntax into JavaScript syntax
    817             i = contentStr.indexOf("<value>");
    818             j = contentStr.indexOf("</value>");
    819             if (i >= 0) {
    820                 while (i >= 0) {
    821                     // Add string fragment before <value> tag
    822                     textComps.push(tick + this.escapeTicks(contentStr.slice(0, i)) + tick);
    823
    824                     term = contentStr.slice(i + 7, j);
    825                     term = term.replace(/\s+/g, ""); // Remove all whitespace
    826                     if (expandShortMath === true) {
    827                         term = this.expandShortMath(term);
    828                     }
    829                     res = term;
    830                     res = res.replace(/\\"/g, "'").replace(/\\'/g, "'");
    831
    832                     // // Hack: apply rounding once only.
    833                     // if (res.indexOf("toFixed") < 0) {
    834                     //     // Output of a value tag
    835                     //     // Run the JessieCode parser
    836                     //     if (
    837                     //         Type.isNumber(
    838                     //             Type.bind(this.board.jc.snippet(res, true, "", false), this)()
    839                     //         )
    840                     //     ) {
    841                     //         // Output is number
    842                     //         // textComps.push(
    843                     //         //     '(' + res + ').toFixed(' + this.evalVisProp('digits') + ')'
    844                     //         // );
    845                     //         textComps.push('(' + res + ')');
    846                     //     } else {
    847                     //         // Output is a string
    848                     //         textComps.push("(" + res + ")");
    849                     //     }
    850                     // } else {
    851                         textComps.push("(" + res + ")");
    852                     // }
    853                     contentStr = contentStr.slice(j + 8);
    854                     i = contentStr.indexOf("<value>");
    855                     j = contentStr.indexOf("</value>");
    856                 }
    857             }
    858             // Add trailing string fragment
    859             textComps.push(tick + this.escapeTicks(contentStr) + tick);
    860
    861             // return textComps.join(" + ").replace(/&/g, "&");
    862             for (i = 0; i < textComps.length; i++) {
    863                 textComps[i] = textComps[i].replace(/&/g, "&");
    864             }
    865             return textComps;
    866         },
    867
    868         /**
    869          * Simple math rendering using HTML / CSS only. In case of array,
    870          * handle each entry separately and return array with the
    871          * rendering strings.
    872          *
    873          * @param {String|Array} s
    874          * @returns {String|Array}
    875          * @see JXG.Text#convertGeonextAndSketchometry2CSS
    876          * @private
    877          * @see JXG.Text#replaceSub
    878          * @see JXG.Text#replaceSup
    879          * @see JXG.Text#convertGeonextAndSketchometry2CSS
    880          */
    881         poorMansTeX: function (s) {
    882             var i, a;
    883             if (Type.isArray(s)) {
    884                 a = [];
    885                 for (i = 0; i < s.length; i++) {
    886                     a.push(this.poorMansTeX(s[i]));
    887                 }
    888                 return a;
    889             }
    890
    891             s = s
    892                 .replace(/<arc\s*\/*>/g, "∠")
    893                 .replace(/<arc\s*\/*>/g, "∠")
    894                 .replace(/<sqrt\s*\/*>/g, "√")
    895                 .replace(/<sqrt\s*\/*>/g, "√");
    896             return this.convertGeonextAndSketchometry2CSS(this.replaceSub(this.replaceSup(s)), true);
    897         },
    898
    899         /**
    900          * Replace ticks by URI escape sequences
    901          *
    902          * @param {String} s
    903          * @returns String
    904          * @private
    905          *
    906          */
    907         escapeTicks: function (s) {
    908             return s.replace(/"/g, "%22").replace(/'/g, "%27");
    909         },
    910
    911         /**
    912          * Replace escape sequences for ticks by ticks
    913          *
    914          * @param {String} s
    915          * @returns String
    916          * @private
    917          */
    918         unescapeTicks: function (s) {
    919             return s.replace(/%22/g, '"').replace(/%27/g, "'");
    920         },
    921
    922         /**
    923          * Converts the GEONExT tags <overline> and <arrow> to
    924          * HTML span tags with proper CSS formatting.
    925          * @private
    926          * @see JXG.Text.poorMansTeX
    927          * @see JXG.Text._setText
    928          */
    929         convertGeonext2CSS: function (s) {
    930             if (Type.isString(s)) {
    931                 s = s.replace(
    932                     /(<|<)overline(>|>)/g,
    933                     "<span style=text-decoration:overline;>"
    934                 );
    935                 s = s.replace(/(<|<)\/overline(>|>)/g, "</span>");
    936                 s = s.replace(
    937                     /(<|<)arrow(>|>)/g,
    938                     "<span style=text-decoration:overline;>"
    939                 );
    940                 s = s.replace(/(<|<)\/arrow(>|>)/g, "</span>");
    941             }
    942
    943             return s;
    944         },
    945
    946         /**
    947          * Converts the sketchometry tag <sketchofont> to
    948          * HTML span tags with proper CSS formatting.
    949          *
    950          * @param {String|Function|Number} s Text
    951          * @param {Boolean} escape Flag if ticks should be escaped. Escaping is necessary
    952          * if s is a text. It has to be avoided if s is a function returning text.
    953          * @private
    954          * @see JXG.Text._setText
    955          * @see JXG.Text.convertGeonextAndSketchometry2CSS
    956          *
    957          */
    958         convertSketchometry2CSS: function (s, escape) {
    959             var t1 = "<span class=\"sketcho sketcho-inherit sketcho-",
    960                 t2 = "\"></span>";
    961
    962             if (Type.isString(s)) {
    963                 if (escape) {
    964                     t1 = this.escapeTicks(t1);
    965                     t2 = this.escapeTicks(t2);
    966                 }
    967                 s = s.replace(/(<|<)sketchofont(>|>)/g, t1);
    968                 s = s.replace(/(<|<)\/sketchofont(>|>)/g, t2);
    969             }
    970
    971             return s;
    972         },
    973
    974         /**
    975          * Alias for convertGeonext2CSS and convertSketchometry2CSS
    976          *
    977          * @param {String|Function|Number} s Text
    978          * @param {Boolean} escape Flag if ticks should be escaped
    979          * @private
    980          * @see JXG.Text.convertGeonext2CSS
    981          * @see JXG.Text.convertSketchometry2CSS
    982          */
    983         convertGeonextAndSketchometry2CSS: function (s, escape) {
    984             s = this.convertGeonext2CSS(s);
    985             s = this.convertSketchometry2CSS(s, escape);
    986             return s;
    987         },
    988
    989         /**
    990          * Finds dependencies in a given term and notifies the parents by adding the
    991          * dependent object to the found objects child elements.
    992          * @param {String} content String containing dependencies for the given object.
    993          * @private
    994          */
    995         notifyParents: function (content) {
    996             var search,
    997                 res = null;
    998
    999             // revert possible jc replacement
    1000             content = content.replace(/<value>/g, "<value>");
    1001             content = content.replace(/<\/value>/g, "</value>");
    1002
    1003             do {
    1004                 search = /<value>([\w\s*/^\-+()[\],<>=!]+)<\/value>/;
    1005                 res = search.exec(content);
    1006
    1007                 if (res !== null) {
    1008                     GeonextParser.findDependencies(this, res[1], this.board);
    1009                     content = content.slice(res.index);
    1010                     content = content.replace(search, "");
    1011                 }
    1012             } while (res !== null);
    1013
    1014             return this;
    1015         },
    1016
    1017         // documented in element.js
    1018         getParents: function () {
    1019             var p;
    1020             if (this.relativeCoords !== undefined) {
    1021                 // Texts with anchor elements, excluding labels
    1022                 p = [
    1023                     this.relativeCoords.usrCoords[1],
    1024                     this.relativeCoords.usrCoords[2],
    1025                     this.orgText
    1026                 ];
    1027             } else {
    1028                 // Other texts
    1029                 p = [this.Z(), this.X(), this.Y(), this.orgText];
    1030             }
    1031
    1032             if (this.parents.length !== 0) {
    1033                 p = this.parents;
    1034             }
    1035
    1036             return p;
    1037         },
    1038
    1039         /**
    1040          * Returns the bounding box of the text element in user coordinates as an
    1041          * array of length 4: [upper left x, upper left y, lower right x, lower right y].
    1042          * The method assumes that the lower left corner is at position [el.X(), el.Y()]
    1043          * of the text element el, i.e. the attributes anchorX, anchorY are ignored.
    1044          *
    1045          * <p>
    1046          * <strong>Attention:</strong> for labels, [0, 0, 0, 0] is returned.
    1047          *
    1048          * @returns Array
    1049          */
    1050         bounds: function () {
    1051             var c = this.coords.usrCoords;
    1052
    1053             if (
    1054                 this.evalVisProp('islabel') ||
    1055                 this.board.unitY === 0 ||
    1056                 this.board.unitX === 0
    1057             ) {
    1058                 return [0, 0, 0, 0];
    1059             }
    1060             return [
    1061                 c[1],
    1062                 c[2] + this.size[1] / this.board.unitY,
    1063                 c[1] + this.size[0] / this.board.unitX,
    1064                 c[2]
    1065             ];
    1066         },
    1067
    1068         /**
    1069          * Returns the value of the attribute "anchorX". If this equals "auto",
    1070          * returns "left", "middle", or "right", depending on the
    1071          * value of the attribute "position".
    1072          * @returns String
    1073          */
    1074         getAnchorX: function () {
    1075             var a = this.evalVisProp('anchorx');
    1076             if (a === "auto") {
    1077                 switch (this.visProp.position) {
    1078                     case "top":
    1079                     case "bot":
    1080                         return "middle";
    1081                     case "rt":
    1082                     case "lrt":
    1083                     case "urt":
    1084                         return "left";
    1085                     case "lft":
    1086                     case "llft":
    1087                     case "ulft":
    1088                     default:
    1089                         return "right";
    1090                 }
    1091             }
    1092             return a;
    1093         },
    1094
    1095         /**
    1096          * Returns the value of the attribute "anchorY". If this equals "auto",
    1097          * returns "bottom", "middle", or "top", depending on the
    1098          * value of the attribute "position".
    1099          * @returns String
    1100          */
    1101         getAnchorY: function () {
    1102             var a = this.evalVisProp('anchory');
    1103             if (a === "auto") {
    1104                 switch (this.visProp.position) {
    1105                     case "top":
    1106                     case "ulft":
    1107                     case "urt":
    1108                         return "bottom";
    1109                     case "bot":
    1110                     case "lrt":
    1111                     case "llft":
    1112                         return "top";
    1113                     case "rt":
    1114                     case "lft":
    1115                     default:
    1116                         return "middle";
    1117                 }
    1118             }
    1119             return a;
    1120         },
    1121
    1122         /**
    1123          * Computes the number of overlaps of a box of w pixels width, h pixels height
    1124          * and center (x, y)
    1125          *
    1126          * @private
    1127          * @param  {Number} x x-coordinate of the center (screen coordinates)
    1128          * @param  {Number} y y-coordinate of the center (screen coordinates)
    1129          * @param  {Number} w width of the box in pixel
    1130          * @param  {Number} h width of the box in pixel
    1131          * @param  {Array} [whiteList] array of ids which should be ignored
    1132          * @return {Number}   Number of overlapping elements
    1133          */
    1134         getNumberOfConflicts: function (x, y, w, h, whiteList) {
    1135             whiteList = whiteList || [];
    1136             var count = 0,
    1137                 i, obj, le,
    1138                 savePointPrecision,
    1139                 saveHasInnerPoints;
    1140
    1141             // Set the precision of hasPoint to half the max if label isn't too long
    1142             savePointPrecision = this.board.options.precision.hasPoint;
    1143             // this.board.options.precision.hasPoint = Math.max(w, h) * 0.5;
    1144             this.board.options.precision.hasPoint = (w + h) * 0.25;
    1145             // TODO:
    1146             // Make it compatible with the objects' visProp.precision attribute
    1147             for (i = 0, le = this.board.objectsList.length; i < le; i++) {
    1148                 obj = this.board.objectsList[i];
    1149                 saveHasInnerPoints = obj.visProp.hasinnerpoints;
    1150                 obj.visProp.hasinnerpoints = false;
    1151                 if (
    1152                     obj.visPropCalc.visible &&
    1153                     obj.elType !== "axis" &&
    1154                     obj.elType !== "ticks" &&
    1155                     obj !== this.board.infobox &&
    1156                     obj !== this &&
    1157                     obj.hasPoint(x, y) &&
    1158                     whiteList.indexOf(obj.id) === -1
    1159                 ) {
    1160                     count++;
    1161                 }
    1162                 obj.visProp.hasinnerpoints = saveHasInnerPoints;
    1163             }
    1164             this.board.options.precision.hasPoint = savePointPrecision;
    1165
    1166             return count;
    1167         },
    1168
    1169         /**
    1170          * Sets the offset of a label element to the position with the least number
    1171          * of overlaps with other elements, while retaining the distance to its
    1172          * anchor element. Twelve different angles are possible.
    1173          *
    1174          * @returns {JXG.Text} Reference to the text object.
    1175          */
    1176         setAutoPosition: function () {
    1177             var x, y, cx, cy,
    1178                 anchorCoords,
    1179                 // anchorX, anchorY,
    1180                 w = this.size[0],
    1181                 h = this.size[1],
    1182                 start_angle, angle,
    1183                 optimum = {
    1184                     conflicts: Infinity,
    1185                     angle: 0,
    1186                     r: 0
    1187                 },
    1188                 max_r, delta_r,
    1189                 conflicts, offset, r,
    1190                 num_positions = 12,
    1191                 step = (2 * Math.PI) / num_positions,
    1192                 j, dx, dy, co, si;
    1193
    1194             if (
    1195                 this === this.board.infobox ||
    1196                 !this.visPropCalc.visible ||
    1197                 !this.evalVisProp('islabel') ||
    1198                 !this.element
    1199             ) {
    1200                 return this;
    1201             }
    1202
    1203             // anchorX = this.evalVisProp('anchorx');
    1204             // anchorY = this.evalVisProp('anchory');
    1205             offset = this.evalVisProp('offset');
    1206             anchorCoords = this.element.getLabelAnchor();
    1207             cx = anchorCoords.scrCoords[1];
    1208             cy = anchorCoords.scrCoords[2];
    1209
    1210             // Set dx, dy as the relative position of the center of the label
    1211             // to its anchor element ignoring anchorx and anchory.
    1212             dx = offset[0];
    1213             dy = offset[1];
    1214
    1215             conflicts = this.getNumberOfConflicts(cx + dx, cy - dy, w, h, this.evalVisProp('autopositionwhitelist'));
    1216             if (conflicts === 0) {
    1217                 return this;
    1218             }
    1219             // console.log(this.id, conflicts, w, h);
    1220             // r = Geometry.distance([0, 0], offset, 2);
    1221
    1222             r = this.evalVisProp('autopositionmindistance');
    1223             max_r = this.evalVisProp('autopositionmaxdistance');
    1224             delta_r = 0.2 * r;
    1225
    1226             start_angle = Math.atan2(dy, dx);
    1227
    1228             optimum.conflicts = conflicts;
    1229             optimum.angle = start_angle;
    1230             optimum.r = r;
    1231
    1232             while (optimum.conflicts > 0 && r <= max_r) {
    1233                 for (
    1234                     j = 1, angle = start_angle + step;
    1235                     j < num_positions && optimum.conflicts > 0;
    1236                     j++
    1237                 ) {
    1238                     co = Math.cos(angle);
    1239                     si = Math.sin(angle);
    1240
    1241                     x = cx + r * co;
    1242                     y = cy - r * si;
    1243
    1244                     conflicts = this.getNumberOfConflicts(x, y, w, h, this.evalVisProp('autopositionwhitelist'));
    1245                     if (conflicts < optimum.conflicts) {
    1246                         optimum.conflicts = conflicts;
    1247                         optimum.angle = angle;
    1248                         optimum.r = r;
    1249                     }
    1250                     if (optimum.conflicts === 0) {
    1251                         break;
    1252                     }
    1253                     angle += step;
    1254                 }
    1255                 r += delta_r;
    1256             }
    1257             // console.log(this.id, "after", optimum)
    1258             r = optimum.r;
    1259             co = Math.cos(optimum.angle);
    1260             si = Math.sin(optimum.angle);
    1261             this.visProp.offset = [r * co, r * si];
    1262
    1263             if (co < -0.2) {
    1264                 this.visProp.anchorx = "right";
    1265             } else if (co > 0.2) {
    1266                 this.visProp.anchorx = "left";
    1267             } else {
    1268                 this.visProp.anchorx = "middle";
    1269             }
    1270
    1271             return this;
    1272         }
    1273     }
    1274 );
    1275
    1276 /**
    1277  * @class Constructs a text element.
    1278  *
    1279  * The coordinates can either be absolute (i.e. respective to the coordinate system of the board) or be relative to the coordinates of an element
    1280  * given in {@link Text#anchor}.
    1281  * <p>
    1282  * HTML, MathJaX, KaTeX and GEONExT syntax can be handled.
    1283  * <p>
    1284  * There are two ways to display texts:
    1285  * <ul>
    1286  * <li> using the text element of the renderer (canvas or svg). In most cases this is the suitable approach if speed matters.
    1287  * However, advanced rendering like MathJax, KaTeX or HTML/CSS are not possible.
    1288  * <li> using HTML <div>. This is the most flexible approach. The drawback is that HTML can only be display "above" the geometry elements.
    1289  * If HTML should be displayed in an inbetween layer, conder to use an element of type {@link ForeignObject} (available in svg renderer, only).
    1290  * </ul>
    1291  * @pseudo
    1292  * @name Text
    1293  * @augments JXG.Text
    1294  * @constructor
    1295  * @type JXG.Text
    1296  *
    1297  * @param {number,function_number,function_number,function_String,function} z_,x,y,str Parent elements for text elements.
    1298  *                     <p>
    1299  *   Parent elements can be two or three elements of type number, a string containing a GEONE<sub>x</sub>T
    1300  *   constraint, or a function which takes no parameter and returns a number. Every parent element beside the last determines one coordinate.
    1301  *   If a coordinate is
    1302  *   given by a number, the number determines the initial position of a free text. If given by a string or a function that coordinate will be constrained
    1303  *   that means the user won't be able to change the texts's position directly by mouse because it will be calculated automatically depending on the string
    1304  *   or the function's return value. If two parent elements are given the coordinates will be interpreted as 2D affine Euclidean coordinates, if three such
    1305  *   parent elements are given they will be interpreted as homogeneous coordinates.
    1306  *                     <p>
    1307  *                     The text to display may be given as string or as function returning a string.
    1308  *
    1309  * There is the attribute 'display' which takes the values 'html' or 'internal'. In case of 'html' an HTML division tag is created to display
    1310  * the text. In this case it is also possible to use MathJax, KaTeX, or ASCIIMathML. If neither of these is used, basic Math rendering is
    1311  * applied.
    1312  * <p>
    1313  * In case of 'internal', an SVG text element is used to display the text.
    1314  * @see JXG.Text
    1315  * @example
    1316  * // Create a fixed text at position [0,1].
    1317  *   var t1 = board.create('text',[0,1,"Hello World"]);
    1318  * </pre><div class="jxgbox" id="JXG896013aa-f24e-4e83-ad50-7bc7df23f6b7" style="width: 300px; height: 300px;"></div>
    1319  * <script type="text/javascript">
    1320  *   var t1_board = JXG.JSXGraph.initBoard('JXG896013aa-f24e-4e83-ad50-7bc7df23f6b7', {boundingbox: [-3, 6, 5, -3], axis: true, showcopyright: false, shownavigation: false});
    1321  *   var t1 = t1_board.create('text',[0,1,"Hello World"]);
    1322  * </script><pre>
    1323  * @example
    1324  * // Create a variable text at a variable position.
    1325  *   var s = board.create('slider',[[0,4],[3,4],[-2,0,2]]);
    1326  *   var graph = board.create('text',
    1327  *                        [function(x){ return s.Value();}, 1,
    1328  *                         function(){return "The value of s is"+JXG.toFixed(s.Value(), 2);}
    1329  *                        ]
    1330  *                     );
    1331  * </pre><div class="jxgbox" id="JXG5441da79-a48d-48e8-9e53-75594c384a1c" style="width: 300px; height: 300px;"></div>
    1332  * <script type="text/javascript">
    1333  *   var t2_board = JXG.JSXGraph.initBoard('JXG5441da79-a48d-48e8-9e53-75594c384a1c', {boundingbox: [-3, 6, 5, -3], axis: true, showcopyright: false, shownavigation: false});
    1334  *   var s = t2_board.create('slider',[[0,4],[3,4],[-2,0,2]]);
    1335  *   var t2 = t2_board.create('text',[function(x){ return s.Value();}, 1, function(){return "The value of s is "+JXG.toFixed(s.Value(), 2);}]);
    1336  * </script><pre>
    1337  * @example
    1338  * // Create a text bound to the point A
    1339  * var p = board.create('point',[0, 1]),
    1340  *     t = board.create('text',[0, -1,"Hello World"], {anchor: p});
    1341  *
    1342  * </pre><div class="jxgbox" id="JXGff5a64b2-2b9a-11e5-8dd9-901b0e1b8723" style="width: 300px; height: 300px;"></div>
    1343  * <script type="text/javascript">
    1344  *     (function() {
    1345  *         var board = JXG.JSXGraph.initBoard('JXGff5a64b2-2b9a-11e5-8dd9-901b0e1b8723',
    1346  *             {boundingbox: [-8, 8, 8,-8], axis: true, showcopyright: false, shownavigation: false});
    1347  *     var p = board.create('point',[0, 1]),
    1348  *         t = board.create('text',[0, -1,"Hello World"], {anchor: p});
    1349  *
    1350  *     })();
    1351  *
    1352  * </script><pre>
    1353  *
    1354  */
    1355 JXG.createText = function (board, parents, attributes) {
    1356     var t,
    1357         attr = Type.copyAttributes(attributes, board.options, "text"),
    1358         coords = parents.slice(0, -1),
    1359         content = parents[parents.length - 1];
    1360
    1361     // Backwards compatibility
    1362     attr.anchor = attr.parent || attr.anchor;
    1363     t = CoordsElement.create(JXG.Text, board, coords, attr, content);
    1364
    1365     if (!t) {
    1366         throw new Error(
    1367             "JSXGraph: Can't create text with parent types '" +
    1368                 typeof parents[0] +
    1369                 "' and '" +
    1370                 typeof parents[1] +
    1371                 "'." +
    1372                 "\nPossible parent types: [x,y], [z,x,y], [element,transformation]"
    1373         );
    1374     }
    1375
    1376     if (attr.rotate !== 0) {
    1377         // This is the default value, i.e. no rotation
    1378         t.addRotation(attr.rotate);
    1379     }
    1380
    1381     return t;
    1382 };
    1383
    1384 JXG.registerElement("text", JXG.createText);
    1385
    1386 /**
    1387  * @class Labels are text objects tied to other elements like points, lines and curves.
    1388  * Labels are handled internally by JSXGraph, only. There is NO constructor "board.create('label', ...)".
    1389  *
    1390  * @description
    1391  * Labels for points are positioned with the attributes {@link Text#anchorX}, {@link Text#anchorX} and {@link Label#offset}.
    1392  * <p>
    1393  * Labels for lines, segments, curves and circles can be controlled additionally by the attributes {@link Label#position} and
    1394  * {@link Label#distance}, i.e. for a segment [A, B] one could use the follwoing attributes:
    1395  * <ul>
    1396  * <li> "position": determines, where in the direction of the segment from A to B the label is placed
    1397  * <li> "distance": determines the (orthogonal) distance of the label from the line segment. It is a factor which is multiplied by the font-size.
    1398  * <li> "offset: [h, v]": a final correction in pixel (horizontally: h, vertically: v)
    1399  * <li> "anchorX" ('left', 'middle', 'right') and "anchorY" ('bottom', 'middle', 'top'): determines which part of the
    1400  * label string is the anchor position that is positioned to the coordinates determined by "position", "distance" and "offset".
    1401  * </ul>
    1402  *
    1403  * @pseudo
    1404  * @name Label
    1405  * @augments JXG.Text
    1406  * @constructor
    1407  * @type JXG.Text
    1408  */
    1409 //  See element.js#createLabel
    1410
    1411 /**
    1412  * [[x,y], [w px, h px], [range]
    1413  */
    1414 JXG.createHTMLSlider = function (board, parents, attributes) {
    1415     var t,
    1416         par,
    1417         attr = Type.copyAttributes(attributes, board.options, "htmlslider");
    1418
    1419     if (parents.length !== 2 || parents[0].length !== 2 || parents[1].length !== 3) {
    1420         throw new Error(
    1421             "JSXGraph: Can't create htmlslider with parent types '" +
    1422                 typeof parents[0] +
    1423                 "' and '" +
    1424                 typeof parents[1] +
    1425                 "'." +
    1426                 "\nPossible parents are: [[x,y], [min, start, max]]"
    1427         );
    1428     }
    1429
    1430     // Backwards compatibility
    1431     attr.anchor = attr.parent || attr.anchor;
    1432     attr.fixed = attr.fixed || true;
    1433
    1434     par = [
    1435         parents[0][0],
    1436         parents[0][1],
    1437         '<form style="display:inline">' +
    1438             '<input type="range" /><span></span><input type="text" />' +
    1439             "</form>"
    1440     ];
    1441
    1442     t = JXG.createText(board, par, attr);
    1443     t.type = Type.OBJECT_TYPE_HTMLSLIDER;
    1444
    1445     t.rendNodeForm = t.rendNode.childNodes[0];
    1446
    1447     t.rendNodeRange = t.rendNodeForm.childNodes[0];
    1448     t.rendNodeRange.min = parents[1][0];
    1449     t.rendNodeRange.max = parents[1][2];
    1450     t.rendNodeRange.step = attr.step;
    1451     t.rendNodeRange.value = parents[1][1];
    1452
    1453     t.rendNodeLabel = t.rendNodeForm.childNodes[1];
    1454     t.rendNodeLabel.id = t.rendNode.id + "_label";
    1455
    1456     if (attr.withlabel) {
    1457         t.rendNodeLabel.innerHTML = t.name + "=";
    1458     }
    1459
    1460     t.rendNodeOut = t.rendNodeForm.childNodes[2];
    1461     t.rendNodeOut.value = parents[1][1];
    1462
    1463     try {
    1464         t.rendNodeForm.id = t.rendNode.id + "_form";
    1465         t.rendNodeRange.id = t.rendNode.id + "_range";
    1466         t.rendNodeOut.id = t.rendNode.id + "_out";
    1467     } catch (e) {
    1468         JXG.debug(e);
    1469     }
    1470
    1471     t.rendNodeRange.style.width = attr.widthrange + "px";
    1472     t.rendNodeRange.style.verticalAlign = "middle";
    1473     t.rendNodeOut.style.width = attr.widthout + "px";
    1474
    1475     t._val = parents[1][1];
    1476
    1477     if (JXG.supportsVML()) {
    1478         /*
    1479          * OnChange event is used for IE browsers
    1480          * The range element is supported since IE10
    1481          */
    1482         Env.addEvent(t.rendNodeForm, "change", priv.HTMLSliderInputEventHandler, t);
    1483     } else {
    1484         /*
    1485          * OnInput event is used for non-IE browsers
    1486          */
    1487         Env.addEvent(t.rendNodeForm, "input", priv.HTMLSliderInputEventHandler, t);
    1488     }
    1489
    1490     t.Value = function () {
    1491         return this._val;
    1492     };
    1493
    1494     return t;
    1495 };
    1496
    1497 JXG.registerElement("htmlslider", JXG.createHTMLSlider);
    1498
    1499 export default JXG.Text;
    1500 // export default {
    1501 //     Text: JXG.Text,
    1502 //     createText: JXG.createText,
    1503 //     createHTMLSlider: JXG.createHTMLSlider
    1504 // };
    1505