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