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