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