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