      1 /*
      2     Copyright 2008-2018
      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 <http://www.gnu.org/licenses/>
     29     and <http://opensource.org/licenses/MIT/>.
     30  */
     31
     32
     33 /*global JXG: true, define: true, window: true*/
     34 /*jslint nomen: true, plusplus: true*/
     35
     36 /* depends:
     37  jxg
     38  base/constants
     39  base/coords
     40  base/element
     41  parser/geonext
     42  math/statistics
     43  utils/env
     44  utils/type
     45  */
     46
     47 /**
     48  * @fileoverview In this file the Text element is defined.
     49  */
     50
     51 define([
     52     'jxg', 'base/constants', 'base/coords', 'base/element', 'parser/geonext', 'math/statistics',
     53     'utils/env', 'utils/type', 'math/math', 'base/coordselement'
     54 ], function (JXG, Const, Coords, GeometryElement, GeonextParser, Statistics, Env, Type, Mat, CoordsElement) {
     55
     56     "use strict";
     57
     58     var priv = {
     59             HTMLSliderInputEventHandler: function () {
     60                 this._val = parseFloat(this.rendNodeRange.value);
     61                 this.rendNodeOut.value = this.rendNodeRange.value;
     62                 this.board.update();
     63             }
     64         };
     65
     66     /**
     67      * Construct and handle texts.
     68      *
     69      * The coordinates can be relative to the coordinates of an element
     70      * given in {@link JXG.Options#text.anchor}.
     71      *
     72      * MathJax, HTML and GEONExT syntax can be handled.
     73      * @class Creates a new text object. Do not use this constructor to create a text. Use {@link JXG.Board#create} with
     74      * type {@link Text} instead.
     75      * @augments JXG.GeometryElement
     76      * @augments JXG.CoordsElement
     77      * @param {string|JXG.Board} board The board the new text is drawn on.
     78      * @param {Array} coordinates An array with the user coordinates of the text.
     79      * @param {Object} attributes An object containing visual properties and optional a name and a id.
     80      * @param {string|function} content A string or a function returning a string.
     81      *
     82      */
     83     JXG.Text = function (board, coords, attributes, content) {
     84         this.constructor(board, attributes, Const.OBJECT_TYPE_TEXT, Const.OBJECT_CLASS_TEXT);
     85
     86         this.element = this.board.select(attributes.anchor);
     87         this.coordsConstructor(coords, Type.evaluate(this.visProp.islabel));
     88
     89         this.content = '';
     90         this.plaintext = '';
     91         this.plaintextOld = null;
     92         this.orgText = '';
     93
     94         this.needsSizeUpdate = false;
     95         // Only used by infobox anymore
     96         this.hiddenByParent = false;
     97
     98         /**
     99          * Width and height of the the text element in pixel.
    100          *
    101          * @private
    102          * @type {Array}
    103          */
    104         this.size = [1.0, 1.0];
    105         this.id = this.board.setId(this, 'T');
    106
    107         // Set text before drawing
    108         this._setUpdateText(content);
    109         this.updateText();
    110
    111         this.board.renderer.drawText(this);
    112         this.board.finalizeAdding(this);
    113
    114         if (Type.isString(this.content)) {
    115             this.notifyParents(this.content);
    116         }
    117         this.elType = 'text';
    118
    119         this.methodMap = Type.deepCopy(this.methodMap, {
    120             setText: 'setTextJessieCode',
    121             // free: 'free',
    122             move: 'setCoords'
    123         });
    124     };
    125
    126     JXG.Text.prototype = new GeometryElement();
    127     Type.copyPrototypeMethods(JXG.Text, CoordsElement, 'coordsConstructor');
    128
    129     JXG.extend(JXG.Text.prototype, /** @lends JXG.Text.prototype */ {
    130         /**
    131          * @private
    132          * Test if the the screen coordinates (x,y) are in a small stripe
    133          * at the left side or at the right side of the text.
    134          * Sensitivity is set in this.board.options.precision.hasPoint.
    135          * If dragarea is set to 'all' (default), tests if the the screen
    136         * coordinates (x,y) are in within the text boundary.
    137          * @param {Number} x
    138          * @param {Number} y
    139          * @returns {Boolean}
    140          */
    141         hasPoint: function (x, y) {
    142             var lft, rt, top, bot, ax, ay,
    143                 r = this.board.options.precision.hasPoint;
    144
    145             if (this.transformations.length > 0) {
    146                 //Transform the mouse/touch coordinates
    147                 // back to the original position of the text.
    148                 lft = Mat.matVecMult(Mat.inverse(this.board.renderer.joinTransforms(this, this.transformations)), [1, x, y]);
    149                 x = lft[1];
    150                 y = lft[2];
    151             }
    152
    153             ax = this.getAnchorX();
    154             if (ax === 'right') {
    155                 lft = this.coords.scrCoords[1] - this.size[0];
    156             } else if (ax === 'middle') {
    157                 lft = this.coords.scrCoords[1] - 0.5 * this.size[0];
    158             } else {
    159                 lft = this.coords.scrCoords[1];
    160             }
    161             rt = lft + this.size[0];
    162
    163             ay = this.getAnchorY();
    164             if (ay === 'top') {
    165                 bot = this.coords.scrCoords[2] + this.size[1];
    166             } else if (ay === 'middle') {
    167                 bot = this.coords.scrCoords[2] + 0.5 * this.size[1];
    168             } else {
    169                 bot = this.coords.scrCoords[2];
    170             }
    171             top = bot - this.size[1];
    172
    173             if (Type.evaluate(this.visProp.dragarea) === 'all') {
    174                 return x >= lft - r && x < rt + r && y >= top - r  && y <= bot + r;
    175             }
    176
    177             return (y >= top - r && y <= bot + r) &&
    178                 ((x >= lft - r  && x <= lft + 2 * r) ||
    179                 (x >= rt - 2 * r && x <= rt + r));
    180         },
    181
    182         /**
    183          * This sets the updateText function of this element depending on the type of text content passed.
    184          * Used by {@link JXG.Text#_setText} and {@link JXG.Text} constructor.
    185          * @param {String|Function|Number} text
    186          * @private
    187          */
    188         _setUpdateText: function (text) {
    189             var updateText, resolvedText,
    190                 ev_p = Type.evaluate(this.visProp.parse),
    191                 ev_um = Type.evaluate(this.visProp.usemathjax);
    192
    193             this.orgText = text;
    194             if (Type.isFunction(text)) {
    195                 this.updateText = function () {
    196                     resolvedText = text().toString();
    197                     if (ev_p && !ev_um) {
    198                         this.plaintext = this.replaceSub(this.replaceSup(this.convertGeonext2CSS(resolvedText)));
    199                     } else {
    200                         this.plaintext = resolvedText;
    201                     }
    202                 };
    203             } else if (Type.isString(text) && !ev_p) {   // Do not parse
    204                 this.updateText = function () {
    205                     this.plaintext = text;
    206                 };
    207             } else {                                     // Parse
    208                 if (Type.isNumber(text)) {
    209                     this.content = Type.toFixed(text, Type.evaluate(this.visProp.digits));
    210                 } else {
    211                     if (Type.evaluate(this.visProp.useasciimathml)) {
    212                         // Convert via ASCIIMathML
    213                         this.content = "'`" + text + "`'";
    214                     } else if (ev_um) {
    215                         this.content = "'" + text + "'";
    216                     } else {
    217                         // Converts GEONExT syntax into JavaScript string
    218                         // Short math is allowed
    219                         // Avoid geonext2JS calls
    220                         this.content = this.generateTerm(text, true, true);
    221                     }
    222                 }
    223                 updateText = this.board.jc.snippet(this.content, true, '', false);
    224                 this.updateText = function () {
    225                     this.plaintext = updateText();
    226                 };
    227             }
    228         },
    229
    230         /**
    231          * Defines new content. This is used by {@link JXG.Text#setTextJessieCode} and {@link JXG.Text#setText}. This is required because
    232          * JessieCode needs to filter all Texts inserted into the DOM and thus has to replace setText by setTextJessieCode.
    233          * @param {String|Function|Number} text
    234          * @returns {JXG.Text}
    235          * @private
    236          */
    237         _setText: function (text) {
    238             this._setUpdateText(text);
    239
    240             // First evaluation of the string.
    241             // We need this for display='internal' and Canvas
    242             this.updateText();
    243             this.fullUpdate();
    244
    245             // We do not call updateSize for the infobox to speed up rendering
    246             if (!this.board.infobox || this.id !== this.board.infobox.id) {
    247                 this.updateSize();    // updateSize() is called at least once.
    248             }
    249
    250             return this;
    251         },
    252
    253         /**
    254          * Defines new content but converts < and > to HTML entities before updating the DOM.
    255          * @param {String|function} text
    256          */
    257         setTextJessieCode: function (text) {
    258             var s;
    259
    260             this.visProp.castext = text;
    261
    262             if (Type.isFunction(text)) {
    263                 s = function () {
    264                     return Type.sanitizeHTML(text());
    265                 };
    266             } else {
    267                 if (Type.isNumber(text)) {
    268                     s = text;
    269                 } else {
    270                     s = Type.sanitizeHTML(text);
    271                 }
    272             }
    273
    274             return this._setText(s);
    275         },
    276
    277         /**
    278          * Defines new content.
    279          * @param {String|function} text
    280          * @returns {JXG.Text} Reference to the text object.
    281          */
    282         setText: function (text) {
    283             return this._setText(text);
    284         },
    285
    286         /**
    287          * Recompute the width and the height of the text box.
    288          * Updates the array {@link JXG.Text#size} with pixel values.
    289          * The result may differ from browser to browser
    290          * by some pixels.
    291          * In canvas an old IEs we use a very crude estimation of the dimensions of
    292          * the textbox.
    293          * JSXGraph needs {@link JXG.Text#size} for applying rotations in IE and
    294          * for aligning text.
    295          *
    296          * @return {[type]} [description]
    297          */
    298         updateSize: function () {
    299             var tmp, s, that, node,
    300                 ev_d = Type.evaluate(this.visProp.display);
    301
    302             if (!Env.isBrowser || this.board.renderer.type === 'no') {
    303                 return this;
    304             }
    305             node = this.rendNode;
    306
    307             /**
    308              * offsetWidth and offsetHeight seem to be supported for internal vml elements by IE10+ in IE8 mode.
    309              */
    310             if (ev_d === 'html' || this.board.renderer.type === 'vml') {
    311                 if (Type.exists(node.offsetWidth)) {
    312                     s = [node.offsetWidth, node.offsetHeight];
    313                     if (s[0] === 0 && s[1] === 0) { // Some browsers need some time to set offsetWidth and offsetHeight
    314                         that = this;
    315                         window.setTimeout(function () {
    316                             that.size = [node.offsetWidth, node.offsetHeight];
    317                             that.needsUpdate = true;
    318                             that.updateRenderer();
    319                         }, 0);
    320                     } else {
    321                         this.size = s;
    322                     }
    323                 } else {
    324                     this.size = this.crudeSizeEstimate();
    325                 }
    326             } else if (ev_d === 'internal') {
    327                 if (this.board.renderer.type === 'svg') {
    328                     try {
    329                         tmp = node.getBBox();
    330                         this.size = [tmp.width, tmp.height];
    331                     } catch (e) {}
    332                 } else if (this.board.renderer.type === 'canvas') {
    333                     this.size = this.crudeSizeEstimate();
    334                 }
    335             }
    336
    337             return this;
    338         },
    339
    340         /**
    341          * A very crude estimation of the dimensions of the textbox in case nothing else is available.
    342          * @returns {Array}
    343          */
    344         crudeSizeEstimate: function () {
    345             var ev_fs = parseFloat(Type.evaluate(this.visProp.fontsize));
    346             return [ev_fs * this.plaintext.length * 0.45, ev_fs * 0.9];
    347         },
    348
    349         /**
    350          * Decode unicode entities into characters.
    351          * @param {String} string
    352          * @returns {String}
    353          */
    354         utf8_decode : function (string) {
    355             return string.replace(/&#x(\w+);/g, function (m, p1) {
    356                 return String.fromCharCode(parseInt(p1, 16));
    357             });
    358         },
    359
    360         /**
    361          * Replace _{} by <sub>
    362          * @param {String} te String containing _{}.
    363          * @returns {String} Given string with _{} replaced by <sub>.
    364          */
    365         replaceSub: function (te) {
    366             if (!te.indexOf) {
    367                 return te;
    368             }
    369
    370             var j,
    371                 i = te.indexOf('_{');
    372
    373             // the regexp in here are not used for filtering but to provide some kind of sugar for label creation,
    374             // i.e. replacing _{...} with <sub>...</sub>. What is passed would get out anyway.
    375             /*jslint regexp: true*/
    376
    377             while (i >= 0) {
    378                 te = te.substr(0, i) + te.substr(i).replace(/_\{/, '<sub>');
    379                 j = te.substr(i).indexOf('}');
    380                 if (j >= 0) {
    381                     te = te.substr(0, j) + te.substr(j).replace(/\}/, '</sub>');
    382                 }
    383                 i = te.indexOf('_{');
    384             }
    385
    386             i = te.indexOf('_');
    387             while (i >= 0) {
    388                 te = te.substr(0, i) + te.substr(i).replace(/_(.?)/, '<sub>$1</sub>');
    389                 i = te.indexOf('_');
    390             }
    391
    392             return te;
    393         },
    394
    395         /**
    396          * Replace ^{} by <sup>
    397          * @param {String} te String containing ^{}.
    398          * @returns {String} Given string with ^{} replaced by <sup>.
    399          */
    400         replaceSup: function (te) {
    401             if (!te.indexOf) {
    402                 return te;
    403             }
    404
    405             var j,
    406                 i = te.indexOf('^{');
    407
    408             // the regexp in here are not used for filtering but to provide some kind of sugar for label creation,
    409             // i.e. replacing ^{...} with <sup>...</sup>. What is passed would get out anyway.
    410             /*jslint regexp: true*/
    411
    412             while (i >= 0) {
    413                 te = te.substr(0, i) + te.substr(i).replace(/\^\{/, '<sup>');
    414                 j = te.substr(i).indexOf('}');
    415                 if (j >= 0) {
    416                     te = te.substr(0, j) + te.substr(j).replace(/\}/, '</sup>');
    417                 }
    418                 i = te.indexOf('^{');
    419             }
    420
    421             i = te.indexOf('^');
    422             while (i >= 0) {
    423                 te = te.substr(0, i) + te.substr(i).replace(/\^(.?)/, '<sup>$1</sup>');
    424                 i = te.indexOf('^');
    425             }
    426
    427             return te;
    428         },
    429
    430         /**
    431          * Return the width of the text element.
    432          * @returns {Array} [width, height] in pixel
    433          */
    434         getSize: function () {
    435             return this.size;
    436         },
    437
    438         /**
    439          * Move the text to new coordinates.
    440          * @param {number} x
    441          * @param {number} y
    442          * @returns {object} reference to the text object.
    443          */
    444         setCoords: function (x, y) {
    445             var coordsAnchor, dx, dy;
    446             if (Type.isArray(x) && x.length > 1) {
    447                 y = x[1];
    448                 x = x[0];
    449             }
    450
    451             if (Type.evaluate(this.visProp.islabel) && Type.exists(this.element)) {
    452                 coordsAnchor = this.element.getLabelAnchor();
    453                 dx = (x - coordsAnchor.usrCoords[1]) * this.board.unitX;
    454                 dy = -(y - coordsAnchor.usrCoords[2]) * this.board.unitY;
    455
    456                 this.relativeCoords.setCoordinates(Const.COORDS_BY_SCREEN, [dx, dy]);
    457             } else {
    458                 /*
    459                 this.X = function () {
    460                     return x;
    461                 };
    462
    463                 this.Y = function () {
    464                     return y;
    465                 };
    466                 */
    467                 this.coords.setCoordinates(Const.COORDS_BY_USER, [x, y]);
    468             }
    469
    470             // this should be a local update, otherwise there might be problems
    471             // with the tick update routine resulting in orphaned tick labels
    472             this.fullUpdate();
    473
    474             return this;
    475         },
    476
    477         /**
    478          * Evaluates the text.
    479          * Then, the update function of the renderer
    480          * is called.
    481          */
    482         update: function (fromParent) {
    483             if (!this.needsUpdate) {
    484                 return this;
    485             }
    486
    487             this.updateCoords(fromParent);
    488             this.updateText();
    489
    490             if (Type.evaluate(this.visProp.display) === 'internal') {
    491                 if (Type.isString(this.plaintext)) {
    492                     this.plaintext = this.utf8_decode(this.plaintext);
    493                 }
    494             }
    495
    496             this.checkForSizeUpdate();
    497             if (this.needsSizeUpdate) {
    498                 this.updateSize();
    499             }
    500
    501             return this;
    502         },
    503
    504         /**
    505          * Used to save updateSize() calls.
    506          * Called in JXG.Text.update
    507          * That means this.update() has been called.
    508          * More tests are in JXG.Renderer.updateTextStyle. The latter tests
    509          * are one update off. But this should pose not too many problems, since
    510          * it affects fontSize and cssClass changes.
    511          *
    512          * @private
    513          */
    514         checkForSizeUpdate: function () {
    515             if (this.board.infobox && this.id === this.board.infobox.id) {
    516                 this.needsSizeUpdate = false;
    517             } else {
    518                 // For some magic reason it is more efficient on the iPad to
    519                 // call updateSize() for EVERY text element EVERY time.
    520                 this.needsSizeUpdate = (this.plaintextOld !== this.plaintext);
    521
    522                 if (this.needsSizeUpdate) {
    523                     this.plaintextOld = this.plaintext;
    524                 }
    525             }
    526
    527         },
    528
    529         /**
    530          * The update function of the renderert
    531          * is called.
    532          * @private
    533          */
    534         updateRenderer: function () {
    535             return this.updateRendererGeneric('updateText');
    536         },
    537
    538         /**
    539          * Converts shortened math syntax into correct syntax:  3x instead of 3*x or
    540          * (a+b)(3+1) instead of (a+b)*(3+1).
    541          *
    542          * @private
    543          * @param{String} expr Math term
    544          * @returns {string} expanded String
    545          */
    546         expandShortMath: function(expr) {
    547             var re = /([\)0-9\.])\s*([\(a-zA-Z_])/g;
    548             return expr.replace(re, '$1*$2');
    549         },
    550
    551         /**
    552          * Converts the GEONExT syntax of the <value> terms into JavaScript.
    553          * Also, all Objects whose name appears in the term are searched and
    554          * the text is added as child to these objects.
    555          *
    556          * @param{String} contentStr String to be parsed
    557          * @param{Boolean} [expand] Optional flag if shortened math syntax is allowed (e.g. 3x instead of 3*x).
    558          * @param{Boolean} [avoidGeonext2JS] Optional flag if geonext2JS should be called. For backwards compatibility
    559          * this has to be set explicitely to true.
    560          * @private
    561          * @see JXG.GeonextParser.geonext2JS.
    562          */
    563         generateTerm: function (contentStr, expand, avoidGeonext2JS) {
    564             var res, term, i, j,
    565                 plaintext = '""';
    566
    567             // revert possible jc replacement
    568             contentStr = contentStr || '';
    569             contentStr = contentStr.replace(/\r/g, '');
    570             contentStr = contentStr.replace(/\n/g, '');
    571             contentStr = contentStr.replace(/"/g, '\'');
    572             contentStr = contentStr.replace(/'/g, "\\'");
    573
    574             contentStr = contentStr.replace(/&arc;/g, '∠');
    575             contentStr = contentStr.replace(/<arc\s*\/>/g, '∠');
    576             contentStr = contentStr.replace(/<arc\s*\/>/g, '∠');
    577             contentStr = contentStr.replace(/<sqrt\s*\/>/g, '√');
    578
    579             contentStr = contentStr.replace(/<value>/g, '<value>');
    580             contentStr = contentStr.replace(/<\/value>/g, '</value>');
    581
    582             // Convert GEONExT syntax into  JavaScript syntax
    583             i = contentStr.indexOf('<value>');
    584             j = contentStr.indexOf('</value>');
    585             if (i >= 0) {
    586                 while (i >= 0) {
    587                     plaintext += ' + "' + this.replaceSub(this.replaceSup(contentStr.slice(0, i))) + '"';
    588                     term = contentStr.slice(i + 7, j);
    589                     term = term.replace(/\s+/g, ''); // Remove all whitespace
    590                     if (expand === true) {
    591                         term = this.expandShortMath(term);
    592                     }
    593                     if (avoidGeonext2JS) {
    594                         res = term;
    595                     } else {
    596                         res = GeonextParser.geonext2JS(term, this.board);
    597                     }
    598                     res = res.replace(/\\"/g, "'");
    599                     res = res.replace(/\\'/g, "'");
    600
    601                     // GEONExT-Hack: apply rounding once only.
    602                     if (res.indexOf('toFixed') < 0) {
    603                         // output of a value tag
    604                         if (Type.isNumber((Type.bind(this.board.jc.snippet(res, true, '', false), this))())) {
    605                             // may also be a string
    606                             plaintext += '+(' + res + ').toFixed(' + (Type.evaluate(this.visProp.digits)) + ')';
    607                         } else {
    608                             plaintext += '+(' + res + ')';
    609                         }
    610                     } else {
    611                         plaintext += '+(' + res + ')';
    612                     }
    613
    614                     contentStr = contentStr.slice(j + 8);
    615                     i = contentStr.indexOf('<value>');
    616                     j = contentStr.indexOf('</value>');
    617                 }
    618             }
    619
    620             plaintext += ' + "' + this.replaceSub(this.replaceSup(contentStr)) + '"';
    621             plaintext = this.convertGeonext2CSS(plaintext);
    622
    623             // This should replace &pi; by π
    624             plaintext = plaintext.replace(/&/g, '&');
    625             plaintext = plaintext.replace(/"/g, "'");
    626
    627             return plaintext;
    628         },
    629
    630         /**
    631          * Converts the GEONExT tags <overline> and <arrow> to
    632          * HTML span tags with proper CSS formating.
    633          * @private
    634          * @see JXG.Text.generateTerm @see JXG.Text._setText
    635          */
    636         convertGeonext2CSS: function (s) {
    637             if (Type.isString(s)) {
    638                 s = s.replace(/<overline>/g, '<span style=text-decoration:overline>');
    639                 s = s.replace(/<overline>/g, '<span style=text-decoration:overline>');
    640                 s = s.replace(/<\/overline>/g, '</span>');
    641                 s = s.replace(/<\/overline>/g, '</span>');
    642                 s = s.replace(/<arrow>/g, '<span style=text-decoration:overline>');
    643                 s = s.replace(/<arrow>/g, '<span style=text-decoration:overline>');
    644                 s = s.replace(/<\/arrow>/g, '</span>');
    645                 s = s.replace(/<\/arrow>/g, '</span>');
    646             }
    647
    648             return s;
    649         },
    650
    651         /**
    652          * Finds dependencies in a given term and notifies the parents by adding the
    653          * dependent object to the found objects child elements.
    654          * @param {String} content String containing dependencies for the given object.
    655          * @private
    656          */
    657         notifyParents: function (content) {
    658             var search,
    659                 res = null;
    660
    661             // revert possible jc replacement
    662             content = content.replace(/<value>/g, '<value>');
    663             content = content.replace(/<\/value>/g, '</value>');
    664
    665             do {
    666                 search = /<value>([\w\s\*\/\^\-\+\(\)\[\],<>=!]+)<\/value>/;
    667                 res = search.exec(content);
    668
    669                 if (res !== null) {
    670                     GeonextParser.findDependencies(this, res[1], this.board);
    671                     content = content.substr(res.index);
    672                     content = content.replace(search, '');
    673                 }
    674             } while (res !== null);
    675
    676             return this;
    677         },
    678
    679         // documented in element.js
    680         getParents: function () {
    681             var p;
    682             if (this.relativeCoords !== undefined) { // Texts with anchor elements, excluding labels
    683                 p = [this.relativeCoords.usrCoords[1], this.relativeCoords.usrCoords[2], this.orgText];
    684             } else {                                 // Other texts
    685                 p = [this.Z(), this.X(), this.Y(), this.orgText];
    686             }
    687
    688             if (this.parents.length !== 0) {
    689                 p = this.parents;
    690             }
    691
    692             return p;
    693         },
    694
    695         bounds: function () {
    696             var c = this.coords.usrCoords;
    697
    698             if (Type.evaluate(this.visProp.islabel) || this.board.unitY === 0 || this.board.unitX === 0) {
    699                 return [0, 0, 0, 0];
    700             } else {
    701                 return [c[1], c[2] + this.size[1] / this.board.unitY, c[1] + this.size[0] / this.board.unitX, c[2]];
    702             }
    703         },
    704
    705         getAnchorX: function() {
    706             var a = Type.evaluate(this.visProp.anchorx);
    707             if (a == 'auto') {
    708                 switch (this.visProp.position) {
    709                     case 'top':
    710                     case 'bot':
    711                         return 'middle';
    712                     case 'rt':
    713                     case 'lrt':
    714                     case 'urt':
    715                         return 'left';
    716                     case 'lft':
    717                     case 'llft':
    718                     case 'ulft':
    719                     default:
    720                         return 'right';
    721                 }
    722             }
    723             return a;
    724         },
    725
    726         getAnchorY: function() {
    727             var a = Type.evaluate(this.visProp.anchory);
    728             if (a == 'auto') {
    729                 switch (this.visProp.position) {
    730                     case 'top':
    731                     case 'ulft':
    732                     case 'urt':
    733                         return 'bottom';
    734                     case 'bot':
    735                     case 'lrt':
    736                     case 'llft':
    737                         return 'top';
    738                     case 'rt':
    739                     case 'lft':
    740                     default:
    741                         return 'middle';
    742                 }
    743             }
    744             return a;
    745         }
    746     });
    747
    748     /**
    749      * @class Construct and handle texts.
    750      *
    751      * The coordinates can be relative to the coordinates of an element
    752      * given in {@link JXG.Options#text.anchor}.
    753      *
    754      * MathJaX, HTML and GEONExT syntax can be handled.
    755      * @pseudo
    756      * @description
    757      * @name Text
    758      * @augments JXG.Text
    759      * @constructor
    760      * @type JXG.Text
    761      *
    762      * @param {number,function_number,function_number,function_String,function} z_,x,y,str Parent elements for text elements.
    763      *                     <p>
    764      *   Parent elements can be two or three elements of type number, a string containing a GEONE<sub>x</sub>T
    765      *   constraint, or a function which takes no parameter and returns a number. Every parent element determines one coordinate. If a coordinate is
    766      *   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
    767      *   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
    768      *   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
    769      *   parent elements are given they will be interpreted as homogeneous coordinates.
    770      *                     <p>
    771      *                     The text to display may be given as string or as function returning a string.
    772      *
    773      * There is the attribute 'display' which takes the values 'html' or 'internal'. In case of 'html' a HTML division tag is created to display
    774      * the text. In this case it is also possible to use ASCIIMathML. Incase of 'internal', a SVG or VML text element is used to display the text.
    775      * @see JXG.Text
    776      * @example
    777      * // Create a fixed text at position [0,1].
    778      *   var t1 = board.create('text',[0,1,"Hello World"]);
    779      * </pre><div class="jxgbox" id="896013aa-f24e-4e83-ad50-7bc7df23f6b7" style="width: 300px; height: 300px;"></div>
    780      * <script type="text/javascript">
    781      *   var t1_board = JXG.JSXGraph.initBoard('896013aa-f24e-4e83-ad50-7bc7df23f6b7', {boundingbox: [-3, 6, 5, -3], axis: true, showcopyright: false, shownavigation: false});
    782      *   var t1 = t1_board.create('text',[0,1,"Hello World"]);
    783      * </script><pre>
    784      * @example
    785      * // Create a variable text at a variable position.
    786      *   var s = board.create('slider',[[0,4],[3,4],[-2,0,2]]);
    787      *   var graph = board.create('text',
    788      *                        [function(x){ return s.Value();}, 1,
    789      *                         function(){return "The value of s is"+JXG.toFixed(s.Value(), 2);}
    790      *                        ]
    791      *                     );
    792      * </pre><div class="jxgbox" id="5441da79-a48d-48e8-9e53-75594c384a1c" style="width: 300px; height: 300px;"></div>
    793      * <script type="text/javascript">
    794      *   var t2_board = JXG.JSXGraph.initBoard('5441da79-a48d-48e8-9e53-75594c384a1c', {boundingbox: [-3, 6, 5, -3], axis: true, showcopyright: false, shownavigation: false});
    795      *   var s = t2_board.create('slider',[[0,4],[3,4],[-2,0,2]]);
    796      *   var t2 = t2_board.create('text',[function(x){ return s.Value();}, 1, function(){return "The value of s is "+JXG.toFixed(s.Value(), 2);}]);
    797      * </script><pre>
    798      * @example
    799      * // Create a text bound to the point A
    800      * var p = board.create('point',[0, 1]),
    801      *     t = board.create('text',[0, -1,"Hello World"], {anchor: p});
    802      *
    803      * </pre><div class="jxgbox" id="ff5a64b2-2b9a-11e5-8dd9-901b0e1b8723" style="width: 300px; height: 300px;"></div>
    804      * <script type="text/javascript">
    805      *     (function() {
    806      *         var board = JXG.JSXGraph.initBoard('ff5a64b2-2b9a-11e5-8dd9-901b0e1b8723',
    807      *             {boundingbox: [-8, 8, 8,-8], axis: true, showcopyright: false, shownavigation: false});
    808      *     var p = board.create('point',[0, 1]),
    809      *         t = board.create('text',[0, -1,"Hello World"], {anchor: p});
    810      *
    811      *     })();
    812      *
    813      * </script><pre>
    814      *
    815      */
    816     JXG.createText = function (board, parents, attributes) {
    817         var t,
    818             attr = Type.copyAttributes(attributes, board.options, 'text'),
    819             coords = parents.slice(0, -1),
    820             content = parents[parents.length - 1];
    821
    822         // downwards compatibility
    823         attr.anchor = attr.parent || attr.anchor;
    824         t = CoordsElement.create(JXG.Text, board, coords, attr, content);
    825
    826         if (!t) {
    827             throw new Error("JSXGraph: Can't create text with parent types '" +
    828                     (typeof parents[0]) + "' and '" + (typeof parents[1]) + "'." +
    829                     "\nPossible parent types: [x,y], [z,x,y], [element,transformation]");
    830         }
    831
    832         if (Type.evaluate(attr.rotate) !== 0 && attr.display === 'internal') {
    833             t.addRotation(Type.evaluate(attr.rotate));
    834         }
    835
    836         return t;
    837     };
    838
    839     JXG.registerElement('text', JXG.createText);
    840
    841     /**
    842      * @class Labels are text objects tied to other elements like points, lines and curves.
    843      * Labels are handled internally by JSXGraph, only. There is NO constructor "board.create('label', ...)".
    844      *
    845      * @pseudo
    846      * @description
    847      * @name Label
    848      * @augments JXG.Text
    849      * @constructor
    850      * @type JXG.Text
    851      */
    852     //  See element.js#createLabel
    853
    854     /**
    855      * [[x,y], [w px, h px], [range]
    856      */
    857     JXG.createHTMLSlider = function (board, parents, attributes) {
    858         var t, par,
    859             attr = Type.copyAttributes(attributes, board.options, 'htmlslider');
    860
    861         if (parents.length !== 2 || parents[0].length !== 2 || parents[1].length !== 3) {
    862             throw new Error("JSXGraph: Can't create htmlslider with parent types '" +
    863                 (typeof parents[0]) + "' and '" + (typeof parents[1]) + "'." +
    864                 "\nPossible parents are: [[x,y], [min, start, max]]");
    865         }
    866
    867         // backwards compatibility
    868         attr.anchor = attr.parent || attr.anchor;
    869         attr.fixed = attr.fixed || true;
    870
    871         par = [parents[0][0], parents[0][1],
    872             '<form style="display:inline">' +
    873             '<input type="range" /><span></span><input type="text" />' +
    874             '</form>'];
    875
    876         t = JXG.createText(board, par, attr);
    877         t.type = Type.OBJECT_TYPE_HTMLSLIDER;
    878
    879         t.rendNodeForm = t.rendNode.childNodes[0];
    880
    881         t.rendNodeRange = t.rendNodeForm.childNodes[0];
    882         t.rendNodeRange.min = parents[1][0];
    883         t.rendNodeRange.max = parents[1][2];
    884         t.rendNodeRange.step = attr.step;
    885         t.rendNodeRange.value = parents[1][1];
    886
    887         t.rendNodeLabel = t.rendNodeForm.childNodes[1];
    888         t.rendNodeLabel.id = t.rendNode.id + '_label';
    889
    890         if (attr.withlabel) {
    891             t.rendNodeLabel.innerHTML = t.name + '=';
    892         }
    893
    894         t.rendNodeOut = t.rendNodeForm.childNodes[2];
    895         t.rendNodeOut.value = parents[1][1];
    896
    897         try {
    898             t.rendNodeForm.id = t.rendNode.id + '_form';
    899             t.rendNodeRange.id = t.rendNode.id + '_range';
    900             t.rendNodeOut.id = t.rendNode.id + '_out';
    901 	} catch (e) {
    902             JXG.debug(e);
    903         }
    904
    905         t.rendNodeRange.style.width = attr.widthrange + 'px';
    906         t.rendNodeRange.style.verticalAlign = 'middle';
    907         t.rendNodeOut.style.width = attr.widthout + 'px';
    908
    909         t._val = parents[1][1];
    910
    911         if (JXG.supportsVML()) {
    912             /*
    913             * OnChange event is used for IE browsers
    914             * The range element is supported since IE10
    915             */
    916             Env.addEvent(t.rendNodeForm, 'change', priv.HTMLSliderInputEventHandler, t);
    917         } else {
    918             /*
    919             * OnInput event is used for non-IE browsers
    920             */
    921             Env.addEvent(t.rendNodeForm, 'input', priv.HTMLSliderInputEventHandler, t);
    922         }
    923
    924         t.Value = function () {
    925             return this._val;
    926         };
    927
    928         return t;
    929     };
    930
    931     JXG.registerElement('htmlslider', JXG.createHTMLSlider);
    932
    933     return {
    934         Text: JXG.Text,
    935         createText: JXG.createText,
    936         createHTMLSlider: JXG.createHTMLSlider
    937     };
    938 });
    939