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