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