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