1 /*
  2     Copyright 2008-2023
  3         Matthias Ehmann,
  4         Michael Gerhaeuser,
  5         Carsten Miller,
  6         Bianca Valentin,
  7         Andreas Walter,
  8         Alfred Wassermann,
  9         Peter Wilfahrt
 10 
 11     This file is part of JSXGraph.
 12 
 13     JSXGraph is free software dual licensed under the GNU LGPL or MIT License.
 14 
 15     You can redistribute it and/or modify it under the terms of the
 16 
 17       * GNU Lesser General Public License as published by
 18         the Free Software Foundation, either version 3 of the License, or
 19         (at your option) any later version
 20       OR
 21       * MIT License: https://github.com/jsxgraph/jsxgraph/blob/master/LICENSE.MIT
 22 
 23     JSXGraph is distributed in the hope that it will be useful,
 24     but WITHOUT ANY WARRANTY; without even the implied warranty of
 25     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 26     GNU Lesser General Public License for more details.
 27 
 28     You should have received a copy of the GNU Lesser General Public License and
 29     the MIT License along with JSXGraph. If not, see <https://www.gnu.org/licenses/>
 30     and <https://opensource.org/licenses/MIT/>.
 31  */
 32 
 33 /*global JXG: true, define: true, html_sanitize: true*/
 34 /*jslint nomen: true, plusplus: true*/
 35 
 36 /**
 37  * @fileoverview type.js contains several functions to help deal with javascript's weak types.
 38  * This file mainly consists of detector functions which verify if a variable is or is not of
 39  * a specific type and converter functions that convert variables to another type or normalize
 40  * the type of a variable.
 41  */
 42 
 43 import JXG from "../jxg";
 44 import Const from "../base/constants";
 45 
 46 JXG.extend(
 47     JXG,
 48     /** @lends JXG */ {
 49         /**
 50          * Checks if the given object is an JSXGraph board.
 51          * @param {Object} v
 52          * @returns {Boolean}
 53          */
 54         isBoard: function (v) {
 55             return v !== null &&
 56                 typeof v === "object" &&
 57                 this.isNumber(v.BOARD_MODE_NONE) &&
 58                 this.isObject(v.objects) &&
 59                 this.isObject(v.jc) &&
 60                 this.isFunction(v.update) &&
 61                 !!v.containerObj &&
 62                 this.isString(v.id);
 63         },
 64 
 65         /**
 66          * Checks if the given string is an id within the given board.
 67          * @param {JXG.Board} board
 68          * @param {String} s
 69          * @returns {Boolean}
 70          */
 71         isId: function (board, s) {
 72             return typeof s === "string" && !!board.objects[s];
 73         },
 74 
 75         /**
 76          * Checks if the given string is a name within the given board.
 77          * @param {JXG.Board} board
 78          * @param {String} s
 79          * @returns {Boolean}
 80          */
 81         isName: function (board, s) {
 82             return typeof s === "string" && !!board.elementsByName[s];
 83         },
 84 
 85         /**
 86          * Checks if the given string is a group id within the given board.
 87          * @param {JXG.Board} board
 88          * @param {String} s
 89          * @returns {Boolean}
 90          */
 91         isGroup: function (board, s) {
 92             return typeof s === "string" && !!board.groups[s];
 93         },
 94 
 95         /**
 96          * Checks if the value of a given variable is of type string.
 97          * @param v A variable of any type.
 98          * @returns {Boolean} True, if v is of type string.
 99          */
100         isString: function (v) {
101             return typeof v === "string";
102         },
103 
104         /**
105          * Checks if the value of a given variable is of type number.
106          * @param v A variable of any type.
107          * @param {Boolean} [dontAcceptNaN=false] If set to true, the function returns true for v=NaN.
108          * @returns {Boolean} True, if v is of type number.
109          */
110         isNumber: function (v, dontAcceptNaN) {
111             var result = (
112                 typeof v === "number" || Object.prototype.toString.call(v) === "[Object Number]"
113             );
114             dontAcceptNaN = dontAcceptNaN || false;
115 
116             if(dontAcceptNaN) {
117                 return result && !isNaN(v);
118             }
119             return result;
120         },
121 
122         /**
123          * Checks if a given variable references a function.
124          * @param v A variable of any type.
125          * @returns {Boolean} True, if v is a function.
126          */
127         isFunction: function (v) {
128             return typeof v === "function";
129         },
130 
131         /**
132          * Checks if a given variable references an array.
133          * @param v A variable of any type.
134          * @returns {Boolean} True, if v is of type array.
135          */
136         isArray: function (v) {
137             var r;
138 
139             // use the ES5 isArray() method and if that doesn't exist use a fallback.
140             if (Array.isArray) {
141                 r = Array.isArray(v);
142             } else {
143                 r =
144                     v !== null &&
145                     typeof v === "object" &&
146                     typeof v.splice === "function" &&
147                     typeof v.join === "function";
148             }
149 
150             return r;
151         },
152 
153         /**
154          * Tests if the input variable is an Object
155          * @param v
156          */
157         isObject: function (v) {
158             return typeof v === "object" && !this.isArray(v);
159         },
160 
161         /**
162          * Tests if the input variable is a DOM Document or DocumentFragment node
163          * @param v A variable of any type
164          */
165         isDocumentOrFragment: function (v) {
166             return this.isObject(v) && (
167                 v.nodeType === 9 || // Node.DOCUMENT_NODE
168                 v.nodeType === 11   // Node.DOCUMENT_FRAGMENT_NODE
169             );
170         },
171 
172         /**
173          * Checks if a given variable is a reference of a JSXGraph Point element.
174          * @param v A variable of any type.
175          * @returns {Boolean} True, if v is of type JXG.Point.
176          */
177         isPoint: function (v) {
178             if (v !== null && typeof v === "object" && this.exists(v.elementClass)) {
179                 return v.elementClass === Const.OBJECT_CLASS_POINT;
180             }
181 
182             return false;
183         },
184 
185         isPoint3D: function (v) {
186             if (v !== null && typeof v === "object" && this.exists(v.elType)) {
187                 return v.elType === "point3d";
188             }
189 
190             return false;
191         },
192 
193         /**
194          * Checks if a given variable is a reference of a JSXGraph Point element or an array of length at least two or
195          * a function returning an array of length two or three.
196          * @param {JXG.Board} board
197          * @param v A variable of any type.
198          * @returns {Boolean} True, if v is of type JXG.Point.
199          */
200         isPointType: function (board, v) {
201             var val, p;
202 
203             if (this.isArray(v)) {
204                 return true;
205             }
206             if (this.isFunction(v)) {
207                 val = v();
208                 if (this.isArray(val) && val.length > 1) {
209                     return true;
210                 }
211             }
212             p = board.select(v);
213             return this.isPoint(p);
214         },
215 
216         /**
217          * Checks if a given variable is a reference of a JSXGraph transformation element or an array
218          * of JSXGraph transformation elements.
219          * @param v A variable of any type.
220          * @returns {Boolean} True, if v is of type JXG.Transformation.
221          */
222         isTransformationOrArray: function (v) {
223             if (v !== null) {
224                 if (this.isArray(v) && v.length > 0) {
225                     return this.isTransformationOrArray(v[0]);
226                 }
227                 if (typeof v === "object") {
228                     return v.type === Const.OBJECT_TYPE_TRANSFORMATION;
229                 }
230             }
231             return false;
232         },
233 
234         /**
235          * Checks if v is an empty object or empty.
236          * @param v {Object|Array}
237          * @returns {boolean} True, if v is an empty object or array.
238          */
239         isEmpty: function (v) {
240             return Object.keys(v).length === 0;
241         },
242 
243         /**
244          * Checks if a given variable is neither undefined nor null. You should not use this together with global
245          * variables!
246          * @param v A variable of any type.
247          * @param {Boolean} [checkEmptyString=false] If set to true, it is also checked whether v is not equal to ''.
248          * @returns {Boolean} True, if v is neither undefined nor null.
249          */
250         exists: function (v, checkEmptyString) {
251             /* eslint-disable eqeqeq */
252             var result = !(v == undefined || v === null);
253             /* eslint-enable eqeqeq */
254             checkEmptyString = checkEmptyString || false;
255 
256             if (checkEmptyString) {
257                 return result && v !== "";
258             }
259             return result;
260         },
261         // exists: (function (undef) {
262         //     return function (v, checkEmptyString) {
263         //         var result = !(v === undef || v === null);
264 
265         //         checkEmptyString = checkEmptyString || false;
266 
267         //         if (checkEmptyString) {
268         //             return result && v !== '';
269         //         }
270         //         return result;
271         //     };
272         // }()),
273 
274         /**
275          * Handle default parameters.
276          * @param v Given value
277          * @param d Default value
278          * @returns <tt>d</tt>, if <tt>v</tt> is undefined or null.
279          */
280         def: function (v, d) {
281             if (this.exists(v)) {
282                 return v;
283             }
284 
285             return d;
286         },
287 
288         /**
289          * Converts a string containing either <strong>true</strong> or <strong>false</strong> into a boolean value.
290          * @param {String} s String containing either <strong>true</strong> or <strong>false</strong>.
291          * @returns {Boolean} String typed boolean value converted to boolean.
292          */
293         str2Bool: function (s) {
294             if (!this.exists(s)) {
295                 return true;
296             }
297 
298             if (typeof s === "boolean") {
299                 return s;
300             }
301 
302             if (this.isString(s)) {
303                 return s.toLowerCase() === "true";
304             }
305 
306             return false;
307         },
308 
309         /**
310          * Convert a String, a number or a function into a function. This method is used in Transformation.js
311          * @param {JXG.Board} board Reference to a JSXGraph board. It is required to resolve dependencies given
312          * by a GEONE<sub>X</sub>T string, thus it must be a valid reference only in case one of the param
313          * values is of type string.
314          * @param {Array} param An array containing strings, numbers, or functions.
315          * @param {Number} n Length of <tt>param</tt>.
316          * @returns {Function} A function taking one parameter k which specifies the index of the param element
317          * to evaluate.
318          */
319         createEvalFunction: function (board, param, n) {
320             var f = [], func, i, e,
321                 deps = {};
322 
323             for (i = 0; i < n; i++) {
324                 f[i] = JXG.createFunction(param[i], board, "", true);
325                 for (e in f[i].deps) {
326                     deps[e] = f[i].deps;
327                 }
328             }
329 
330             func = function (k) {
331                 return f[k]();
332             };
333             func.deps = deps;
334 
335             return func;
336         },
337 
338         /**
339          * Convert a String, number or function into a function.
340          * @param {String|Number|Function} term A variable of type string, function or number.
341          * @param {JXG.Board} board Reference to a JSXGraph board. It is required to resolve dependencies given
342          * by a GEONE<sub>X</sub>T string, thus it must be a valid reference only in case one of the param
343          * values is of type string.
344          * @param {String} variableName Only required if function is supplied as JessieCode string or evalGeonext is set to true.
345          * Describes the variable name of the variable in a GEONE<sub>X</sub>T string given as term.
346          * @param {Boolean} [evalGeonext=true] Set this true, if term should be treated as a GEONE<sub>X</sub>T string.
347          * @returns {Function} A function evaluation the value given by term or null if term is not of type string,
348          * function or number.
349          */
350         createFunction: function (term, board, variableName, evalGeonext) {
351             var f = null;
352 
353             if ((!this.exists(evalGeonext) || evalGeonext) && this.isString(term)) {
354                 // Convert GEONExT syntax into  JavaScript syntax
355                 //newTerm = JXG.GeonextParser.geonext2JS(term, board);
356                 //return new Function(variableName,'return ' + newTerm + ';');
357 
358                 //term = JXG.GeonextParser.replaceNameById(term, board);
359                 //term = JXG.GeonextParser.geonext2JS(term, board);
360                 f = board.jc.snippet(term, true, variableName, true);
361             } else if (this.isFunction(term)) {
362                 f = term;
363                 f.deps = {};
364             } else if (this.isNumber(term)) {
365                 /** @ignore */
366                 f = function () { return term; };
367                 f.deps = {};
368             } else if (this.isString(term)) {
369                 // In case of string function like fontsize
370                 /** @ignore */
371                 f = function () { return term; };
372                 f.deps = {};
373             }
374 
375             if (f !== null) {
376                 f.origin = term;
377             }
378 
379             return f;
380         },
381 
382         /**
383          *  Test if the parents array contains existing points. If instead parents contains coordinate arrays or
384          *  function returning coordinate arrays
385          *  free points with these coordinates are created.
386          *
387          * @param {JXG.Board} board Board object
388          * @param {Array} parents Array containing parent elements for a new object. This array may contain
389          *    <ul>
390          *      <li> {@link JXG.Point} objects
391          *      <li> {@link JXG.GeometryElement#name} of {@link JXG.Point} objects
392          *      <li> {@link JXG.GeometryElement#id} of {@link JXG.Point} objects
393          *      <li> Coordinates of points given as array of numbers of length two or three, e.g. [2, 3].
394          *      <li> Coordinates of points given as array of functions of length two or three. Each function returns one coordinate, e.g.
395          *           [function(){ return 2; }, function(){ return 3; }]
396          *      <li> Function returning coordinates, e.g. function() { return [2, 3]; }
397          *    </ul>
398          *  In the last three cases a new point will be created.
399          * @param {String} attrClass Main attribute class of newly created points, see {@link JXG#copyAttributes}
400          * @param {Array} attrArray List of subtype attributes for the newly created points. The list of subtypes is mapped to the list of new points.
401          * @returns {Array} List of newly created {@link JXG.Point} elements or false if not all returned elements are points.
402          */
403         providePoints: function (board, parents, attributes, attrClass, attrArray) {
404             var i,
405                 j,
406                 len,
407                 lenAttr = 0,
408                 points = [],
409                 attr,
410                 val;
411 
412             if (!this.isArray(parents)) {
413                 parents = [parents];
414             }
415             len = parents.length;
416             if (this.exists(attrArray)) {
417                 lenAttr = attrArray.length;
418             }
419             if (lenAttr === 0) {
420                 attr = this.copyAttributes(attributes, board.options, attrClass);
421             }
422 
423             for (i = 0; i < len; ++i) {
424                 if (lenAttr > 0) {
425                     j = Math.min(i, lenAttr - 1);
426                     attr = this.copyAttributes(
427                         attributes,
428                         board.options,
429                         attrClass,
430                         attrArray[j]
431                     );
432                 }
433                 if (this.isArray(parents[i]) && parents[i].length > 1) {
434                     points.push(board.create("point", parents[i], attr));
435                     points[points.length - 1]._is_new = true;
436                 } else if (this.isFunction(parents[i])) {
437                     val = parents[i]();
438                     if (this.isArray(val) && val.length > 1) {
439                         points.push(board.create("point", [parents[i]], attr));
440                         points[points.length - 1]._is_new = true;
441                     }
442                 } else {
443                     points.push(board.select(parents[i]));
444                 }
445 
446                 if (!this.isPoint(points[i])) {
447                     return false;
448                 }
449             }
450 
451             return points;
452         },
453 
454         /**
455          *  Test if the parents array contains existing points. If instead parents contains coordinate arrays or
456          *  function returning coordinate arrays
457          *  free points with these coordinates are created.
458          *
459          * @param {JXG.View3D} view View3D object
460          * @param {Array} parents Array containing parent elements for a new object. This array may contain
461          *    <ul>
462          *      <li> {@link JXG.Point3D} objects
463          *      <li> {@link JXG.GeometryElement#name} of {@link JXG.Point3D} objects
464          *      <li> {@link JXG.GeometryElement#id} of {@link JXG.Point3D} objects
465          *      <li> Coordinates of 3D points given as array of numbers of length three, e.g. [2, 3, 1].
466          *      <li> Coordinates of 3D points given as array of functions of length three. Each function returns one coordinate, e.g.
467          *           [function(){ return 2; }, function(){ return 3; }, function(){ return 1; }]
468          *      <li> Function returning coordinates, e.g. function() { return [2, 3, 1]; }
469          *    </ul>
470          *  In the last three cases a new 3D point will be created.
471          * @param {String} attrClass Main attribute class of newly created 3D points, see {@link JXG#copyAttributes}
472          * @param {Array} attrArray List of subtype attributes for the newly created 3D points. The list of subtypes is mapped to the list of new 3D points.
473          * @returns {Array} List of newly created {@link JXG.Point3D} elements or false if not all returned elements are 3D points.
474          */
475         providePoints3D: function (view, parents, attributes, attrClass, attrArray) {
476             var i,
477                 j,
478                 len,
479                 lenAttr = 0,
480                 points = [],
481                 attr,
482                 val;
483 
484             if (!this.isArray(parents)) {
485                 parents = [parents];
486             }
487             len = parents.length;
488             if (this.exists(attrArray)) {
489                 lenAttr = attrArray.length;
490             }
491             if (lenAttr === 0) {
492                 attr = this.copyAttributes(attributes, view.board.options, attrClass);
493             }
494 
495             for (i = 0; i < len; ++i) {
496                 if (lenAttr > 0) {
497                     j = Math.min(i, lenAttr - 1);
498                     attr = this.copyAttributes(
499                         attributes,
500                         view.board.options,
501                         attrClass,
502                         attrArray[j]
503                     );
504                 }
505 
506                 if (this.isArray(parents[i]) && parents[i].length > 1) {
507                     points.push(view.create("point3d", parents[i], attr));
508                     points[points.length - 1]._is_new = true;
509                 } else if (this.isFunction(parents[i])) {
510                     val = parents[i]();
511                     if (this.isArray(val) && val.length > 1) {
512                         points.push(view.create("point3d", [parents[i]], attr));
513                         points[points.length - 1]._is_new = true;
514                     }
515                 } else {
516                     points.push(view.select(parents[i]));
517                 }
518 
519                 if (!this.isPoint3D(points[i])) {
520                     return false;
521                 }
522             }
523 
524             return points;
525         },
526 
527         /**
528          * Generates a function which calls the function fn in the scope of owner.
529          * @param {Function} fn Function to call.
530          * @param {Object} owner Scope in which fn is executed.
531          * @returns {Function} A function with the same signature as fn.
532          */
533         bind: function (fn, owner) {
534             return function () {
535                 return fn.apply(owner, arguments);
536             };
537         },
538 
539         /**
540          * If <tt>val</tt> is a function, it will be evaluated without giving any parameters, else the input value
541          * is just returned.
542          * @param val Could be anything. Preferably a number or a function.
543          * @returns If <tt>val</tt> is a function, it is evaluated and the result is returned. Otherwise <tt>val</tt> is returned.
544          */
545         evaluate: function (val) {
546             if (this.isFunction(val)) {
547                 return val();
548             }
549 
550             return val;
551         },
552 
553         /**
554          * Search an array for a given value.
555          * @param {Array} array
556          * @param value
557          * @param {String} [sub] Use this property if the elements of the array are objects.
558          * @returns {Number} The index of the first appearance of the given value, or
559          * <tt>-1</tt> if the value was not found.
560          */
561         indexOf: function (array, value, sub) {
562             var i,
563                 s = this.exists(sub);
564 
565             if (Array.indexOf && !s) {
566                 return array.indexOf(value);
567             }
568 
569             for (i = 0; i < array.length; i++) {
570                 if ((s && array[i][sub] === value) || (!s && array[i] === value)) {
571                     return i;
572                 }
573             }
574 
575             return -1;
576         },
577 
578         /**
579          * Eliminates duplicate entries in an array consisting of numbers and strings.
580          * @param {Array} a An array of numbers and/or strings.
581          * @returns {Array} The array with duplicate entries eliminated.
582          */
583         eliminateDuplicates: function (a) {
584             var i,
585                 len = a.length,
586                 result = [],
587                 obj = {};
588 
589             for (i = 0; i < len; i++) {
590                 obj[a[i]] = 0;
591             }
592 
593             for (i in obj) {
594                 if (obj.hasOwnProperty(i)) {
595                     result.push(i);
596                 }
597             }
598 
599             return result;
600         },
601 
602         /**
603          * Swaps to array elements.
604          * @param {Array} arr
605          * @param {Number} i
606          * @param {Number} j
607          * @returns {Array} Reference to the given array.
608          */
609         swap: function (arr, i, j) {
610             var tmp;
611 
612             tmp = arr[i];
613             arr[i] = arr[j];
614             arr[j] = tmp;
615 
616             return arr;
617         },
618 
619         /**
620          * Generates a copy of an array and removes the duplicate entries. The original
621          * Array will be altered.
622          * @param {Array} arr
623          * @returns {Array}
624          */
625         uniqueArray: function (arr) {
626             var i,
627                 j,
628                 isArray,
629                 ret = [];
630 
631             if (arr.length === 0) {
632                 return [];
633             }
634 
635             for (i = 0; i < arr.length; i++) {
636                 isArray = this.isArray(arr[i]);
637 
638                 if (!this.exists(arr[i])) {
639                     arr[i] = "";
640                     continue;
641                 }
642                 for (j = i + 1; j < arr.length; j++) {
643                     if (isArray && JXG.cmpArrays(arr[i], arr[j])) {
644                         arr[i] = [];
645                     } else if (!isArray && arr[i] === arr[j]) {
646                         arr[i] = "";
647                     }
648                 }
649             }
650 
651             j = 0;
652 
653             for (i = 0; i < arr.length; i++) {
654                 isArray = this.isArray(arr[i]);
655 
656                 if (!isArray && arr[i] !== "") {
657                     ret[j] = arr[i];
658                     j++;
659                 } else if (isArray && arr[i].length !== 0) {
660                     ret[j] = arr[i].slice(0);
661                     j++;
662                 }
663             }
664 
665             arr = ret;
666             return ret;
667         },
668 
669         /**
670          * Checks if an array contains an element equal to <tt>val</tt> but does not check the type!
671          * @param {Array} arr
672          * @param val
673          * @returns {Boolean}
674          */
675         isInArray: function (arr, val) {
676             return JXG.indexOf(arr, val) > -1;
677         },
678 
679         /**
680          * Converts an array of {@link JXG.Coords} objects into a coordinate matrix.
681          * @param {Array} coords
682          * @param {Boolean} split
683          * @returns {Array}
684          */
685         coordsArrayToMatrix: function (coords, split) {
686             var i,
687                 x = [],
688                 m = [];
689 
690             for (i = 0; i < coords.length; i++) {
691                 if (split) {
692                     x.push(coords[i].usrCoords[1]);
693                     m.push(coords[i].usrCoords[2]);
694                 } else {
695                     m.push([coords[i].usrCoords[1], coords[i].usrCoords[2]]);
696                 }
697             }
698 
699             if (split) {
700                 m = [x, m];
701             }
702 
703             return m;
704         },
705 
706         /**
707          * Compare two arrays.
708          * @param {Array} a1
709          * @param {Array} a2
710          * @returns {Boolean} <tt>true</tt>, if the arrays coefficients are of same type and value.
711          */
712         cmpArrays: function (a1, a2) {
713             var i;
714 
715             // trivial cases
716             if (a1 === a2) {
717                 return true;
718             }
719 
720             if (a1.length !== a2.length) {
721                 return false;
722             }
723 
724             for (i = 0; i < a1.length; i++) {
725                 if (this.isArray(a1[i]) && this.isArray(a2[i])) {
726                     if (!this.cmpArrays(a1[i], a2[i])) {
727                         return false;
728                     }
729                 } else if (a1[i] !== a2[i]) {
730                     return false;
731                 }
732             }
733 
734             return true;
735         },
736 
737         /**
738          * Removes an element from the given array
739          * @param {Array} ar
740          * @param el
741          * @returns {Array}
742          */
743         removeElementFromArray: function (ar, el) {
744             var i;
745 
746             for (i = 0; i < ar.length; i++) {
747                 if (ar[i] === el) {
748                     ar.splice(i, 1);
749                     return ar;
750                 }
751             }
752 
753             return ar;
754         },
755 
756         /**
757          * Truncate a number <tt>n</tt> after <tt>p</tt> decimals.
758          * @param {Number} n
759          * @param {Number} p
760          * @returns {Number}
761          */
762         trunc: function (n, p) {
763             p = JXG.def(p, 0);
764 
765             return this.toFixed(n, p);
766         },
767 
768         /**
769          * Decimal adjustment of a number.
770          * From https://developer.mozilla.org/de/docs/Web/JavaScript/Reference/Global_Objects/Math/round
771          *
772          * @param    {String}    type    The type of adjustment.
773          * @param    {Number}    value    The number.
774          * @param    {Number}    exp        The exponent (the 10 logarithm of the adjustment base).
775          * @returns    {Number}            The adjusted value.
776          *
777          * @private
778          */
779         _decimalAdjust: function (type, value, exp) {
780             // If the exp is undefined or zero...
781             if (exp === undefined || +exp === 0) {
782                 return Math[type](value);
783             }
784 
785             value = +value;
786             exp = +exp;
787             // If the value is not a number or the exp is not an integer...
788             if (isNaN(value) || !(typeof exp === "number" && exp % 1 === 0)) {
789                 return NaN;
790             }
791 
792             // Shift
793             value = value.toString().split("e");
794             value = Math[type](+(value[0] + "e" + (value[1] ? +value[1] - exp : -exp)));
795 
796             // Shift back
797             value = value.toString().split("e");
798             return +(value[0] + "e" + (value[1] ? +value[1] + exp : exp));
799         },
800 
801         /**
802          * Round a number to given number of decimal digits.
803          *
804          * Example: JXG._toFixed(3.14159, -2) gives 3.14
805          * @param  {Number} value Number to be rounded
806          * @param  {Number} exp   Number of decimal digits given as negative exponent
807          * @return {Number}       Rounded number.
808          *
809          * @private
810          */
811         _round10: function (value, exp) {
812             return this._decimalAdjust("round", value, exp);
813         },
814 
815         /**
816          * "Floor" a number to given number of decimal digits.
817          *
818          * Example: JXG._toFixed(3.14159, -2) gives 3.14
819          * @param  {Number} value Number to be floored
820          * @param  {Number} exp   Number of decimal digits given as negative exponent
821          * @return {Number}       "Floored" number.
822          *
823          * @private
824          */
825         _floor10: function (value, exp) {
826             return this._decimalAdjust("floor", value, exp);
827         },
828 
829         /**
830          * "Ceil" a number to given number of decimal digits.
831          *
832          * Example: JXG._toFixed(3.14159, -2) gives 3.15
833          * @param  {Number} value Number to be ceiled
834          * @param  {Number} exp   Number of decimal digits given as negative exponent
835          * @return {Number}       "Ceiled" number.
836          *
837          * @private
838          */
839         _ceil10: function (value, exp) {
840             return this._decimalAdjust("ceil", value, exp);
841         },
842 
843         /**
844          * Replacement of the default toFixed() method.
845          * It does a correct rounding (independent of the browser) and
846          * returns "0.00" for toFixed(-0.000001, 2) instead of "-0.00" which
847          * is returned by JavaScript's toFixed()
848          *
849          * @memberOf JXG
850          * @param  {Number} num    Number tp be rounded
851          * @param  {Number} digits Decimal digits
852          * @return {String}        Rounded number is returned as string
853          */
854         toFixed: function (num, digits) {
855             return this._round10(num, -digits).toFixed(digits);
856         },
857 
858         /**
859          * Truncate a number <tt>val</tt> automatically.
860          * @memberOf JXG
861          * @param val
862          * @returns {Number}
863          */
864         autoDigits: function (val) {
865             var x = Math.abs(val),
866                 str;
867 
868             if (x >= 0.1) {
869                 str = this.toFixed(val, 2);
870             } else if (x >= 0.01) {
871                 str = this.toFixed(val, 4);
872             } else if (x >= 0.0001) {
873                 str = this.toFixed(val, 6);
874             } else {
875                 str = val;
876             }
877             return str;
878         },
879 
880         /**
881          * Extracts the keys of a given object.
882          * @param object The object the keys are to be extracted
883          * @param onlyOwn If true, hasOwnProperty() is used to verify that only keys are collected
884          * the object owns itself and not some other object in the prototype chain.
885          * @returns {Array} All keys of the given object.
886          */
887         keys: function (object, onlyOwn) {
888             var keys = [],
889                 property;
890 
891             // the caller decides if we use hasOwnProperty
892             /*jslint forin:true*/
893             for (property in object) {
894                 if (onlyOwn) {
895                     if (object.hasOwnProperty(property)) {
896                         keys.push(property);
897                     }
898                 } else {
899                     keys.push(property);
900                 }
901             }
902             /*jslint forin:false*/
903 
904             return keys;
905         },
906 
907         /**
908          * This outputs an object with a base class reference to the given object. This is useful if
909          * you need a copy of an e.g. attributes object and want to overwrite some of the attributes
910          * without changing the original object.
911          * @param {Object} obj Object to be embedded.
912          * @returns {Object} An object with a base class reference to <tt>obj</tt>.
913          */
914         clone: function (obj) {
915             var cObj = {};
916 
917             cObj.prototype = obj;
918 
919             return cObj;
920         },
921 
922         /**
923          * Embeds an existing object into another one just like {@link #clone} and copies the contents of the second object
924          * to the new one. Warning: The copied properties of obj2 are just flat copies.
925          * @param {Object} obj Object to be copied.
926          * @param {Object} obj2 Object with data that is to be copied to the new one as well.
927          * @returns {Object} Copy of given object including some new/overwritten data from obj2.
928          */
929         cloneAndCopy: function (obj, obj2) {
930             var r,
931                 cObj = function () {
932                     return undefined;
933                 };
934 
935             cObj.prototype = obj;
936 
937             // no hasOwnProperty on purpose
938             /*jslint forin:true*/
939             /*jshint forin:true*/
940 
941             for (r in obj2) {
942                 cObj[r] = obj2[r];
943             }
944 
945             /*jslint forin:false*/
946             /*jshint forin:false*/
947 
948             return cObj;
949         },
950 
951         /**
952          * Recursively merges obj2 into obj1 in-place. Contrary to {@link JXG#deepCopy} this won't create a new object
953          * but instead will overwrite obj1.
954          * <p>
955          * In contrast to method JXG.mergeAttr, merge recurses into any kind of object, e.g. DOM object and JSXGraph objects.
956          * So, please be careful.
957          * @param {Object} obj1
958          * @param {Object} obj2
959          * @returns {Object}
960          * @see JXG#mergeAttr
961          *
962          * @example
963          * JXG.Options = JXG.merge(JXG.Options, {
964          *     board: {
965          *         showNavigation: false,
966          *         showInfobox: true
967          *     },
968          *     point: {
969          *         face: 'o',
970          *         size: 4,
971          *         fillColor: '#eeeeee',
972          *         highlightFillColor: '#eeeeee',
973          *         strokeColor: 'white',
974          *         highlightStrokeColor: 'white',
975          *         showInfobox: 'inherit'
976          *     }
977          * });
978          *
979          * </pre><div id="JXGc5bf0f2a-bd5a-4612-97c2-09f17b1bbc6b" class="jxgbox" style="width: 300px; height: 300px;"></div>
980          * <script type="text/javascript">
981          *     (function() {
982          *         var board = JXG.JSXGraph.initBoard('JXGc5bf0f2a-bd5a-4612-97c2-09f17b1bbc6b',
983          *             {boundingbox: [-8, 8, 8,-8], axis: true, showcopyright: false, shownavigation: false});
984          *     JXG.Options = JXG.merge(JXG.Options, {
985          *         board: {
986          *             showNavigation: false,
987          *             showInfobox: true
988          *         },
989          *         point: {
990          *             face: 'o',
991          *             size: 4,
992          *             fillColor: '#eeeeee',
993          *             highlightFillColor: '#eeeeee',
994          *             strokeColor: 'white',
995          *             highlightStrokeColor: 'white',
996          *             showInfobox: 'inherit'
997          *         }
998          *     });
999          *
1000          *
1001          *     })();
1002          *
1003          * </script><pre>
1004          */
1005         merge: function (obj1, obj2) {
1006             var i, j, o, oo;
1007 
1008             for (i in obj2) {
1009                 if (obj2.hasOwnProperty(i)) {
1010                     o = obj2[i];
1011                     if (this.isArray(o)) {
1012                         if (!obj1[i]) {
1013                             obj1[i] = [];
1014                         }
1015 
1016                         for (j = 0; j < o.length; j++) {
1017                             oo = obj2[i][j];
1018                             if (typeof obj2[i][j] === 'object') {
1019                                 obj1[i][j] = this.merge(obj1[i][j], oo);
1020                             } else {
1021                                 obj1[i][j] = obj2[i][j];
1022                             }
1023                         }
1024                     } else if (typeof o === 'object') {
1025                         if (!obj1[i]) {
1026                             obj1[i] = {};
1027                         }
1028 
1029                         obj1[i] = this.merge(obj1[i], o);
1030                     } else {
1031                         obj1[i] = o;
1032                     }
1033                 }
1034             }
1035 
1036             return obj1;
1037         },
1038 
1039         /**
1040          * Creates a deep copy of an existing object, i.e. arrays or sub-objects are copied component resp.
1041          * element-wise instead of just copying the reference. If a second object is supplied, the two objects
1042          * are merged into one object. The properties of the second object have priority.
1043          * @param {Object} obj This object will be copied.
1044          * @param {Object} obj2 This object will merged into the newly created object
1045          * @param {Boolean} [toLower=false] If true the keys are convert to lower case. This is needed for visProp, see JXG#copyAttributes
1046          * @returns {Object} copy of obj or merge of obj and obj2.
1047          */
1048         deepCopy: function (obj, obj2, toLower) {
1049             var c, i, prop, i2;
1050 
1051             toLower = toLower || false;
1052             if (typeof obj !== 'object' || obj === null) {
1053                 return obj;
1054             }
1055 
1056             // Missing hasOwnProperty is on purpose in this function
1057             if (this.isArray(obj)) {
1058                 c = [];
1059                 for (i = 0; i < obj.length; i++) {
1060                     prop = obj[i];
1061                     if (typeof prop === "object") {
1062                         // We certainly do not want to recurse into a JSXGraph object.
1063                         // This would for sure result in an infinite recursion.
1064                         // As alternative we copy the id of the object.
1065                         if (this.exists(prop.board)) {
1066                             c[i] = prop.id;
1067                         } else {
1068                             c[i] = this.deepCopy(prop);
1069                         }
1070                     } else {
1071                         c[i] = prop;
1072                     }
1073                 }
1074             } else {
1075                 c = {};
1076                 for (i in obj) {
1077                     if (obj.hasOwnProperty(i)) {
1078                         i2 = toLower ? i.toLowerCase() : i;
1079                         prop = obj[i];
1080                         if (prop !== null && typeof prop === "object") {
1081                             if (this.exists(prop.board)) {
1082                                 c[i2] = prop.id;
1083                             } else {
1084                                 c[i2] = this.deepCopy(prop);
1085                             }
1086                         } else {
1087                             c[i2] = prop;
1088                         }
1089                     }
1090                 }
1091 
1092                 for (i in obj2) {
1093                     if (obj2.hasOwnProperty(i)) {
1094                         i2 = toLower ? i.toLowerCase() : i;
1095 
1096                         prop = obj2[i];
1097                         if (typeof prop === "object") {
1098                             if (this.isArray(prop) || !this.exists(c[i2])) {
1099                                 c[i2] = this.deepCopy(prop);
1100                             } else {
1101                                 c[i2] = this.deepCopy(c[i2], prop, toLower);
1102                             }
1103                         } else {
1104                             c[i2] = prop;
1105                         }
1106                     }
1107                 }
1108             }
1109 
1110             return c;
1111         },
1112 
1113         /**
1114          * In-place (deep) merging of attributes. Allows attributes like `{shadow: {enabled: true...}}`
1115          * <p>
1116          * In contrast to method JXG.merge, mergeAttr does not recurse into DOM objects and JSXGraph objects. Instead
1117          * handles (pointers) to these objects are used.
1118          *
1119          * @param {Object} attr Object with attributes - usually containing default options
1120          * @param {Object} special Special option values which overwrite (recursively) the default options
1121          * @param {Boolean} [toLower=true] If true the keys are converted to lower case.
1122          *
1123          * @see JXG#merge
1124          *
1125          */
1126         mergeAttr: function (attr, special, toLower) {
1127             var e, e2, o;
1128 
1129             toLower = toLower || true;
1130 
1131             for (e in special) {
1132                 if (special.hasOwnProperty(e)) {
1133                     e2 = (toLower) ? e.toLowerCase(): e;
1134 
1135                     o = special[e];
1136                     if (this.isObject(o) && o !== null &&
1137                         // Do not recurse into a document object or a JSXGraph object
1138                         !this.isDocumentOrFragment(o) && !this.exists(o.board) &&
1139                         // Do not recurse if a string is provided as "new String(...)"
1140                         typeof o.valueOf() !== 'string') {
1141                         if (attr[e2] === undefined || attr[e2] === null || !this.isObject(attr[e2])) {
1142                             // The last test handles the case:
1143                             //   attr.draft = false;
1144                             //   special.draft = { strokewidth: 4}
1145                             attr[e2] = {};
1146                         }
1147                         this.mergeAttr(attr[e2], o, toLower);
1148                     } else {
1149                         // Flat copy
1150                         // This is also used in the cases
1151                         //   attr.shadow = { enabled: true ...}
1152                         //   special.shadow = false;
1153                         // and
1154                         //   special.anchor is a JSXGraph element
1155                         attr[e2] = o;
1156                     }
1157                 }
1158             }
1159         },
1160 
1161         /**
1162          * Generates an attributes object that is filled with default values from the Options object
1163          * and overwritten by the user specified attributes.
1164          * @param {Object} attributes user specified attributes
1165          * @param {Object} options defaults options
1166          * @param {String} s variable number of strings, e.g. 'slider', subtype 'point1'.
1167          * @returns {Object} The resulting attributes object
1168          */
1169         copyAttributes: function (attributes, options, s) {
1170             var a, i, len, o, isAvail,
1171                 primitives = {
1172                     circle: 1,
1173                     curve: 1,
1174                     foreignobject: 1,
1175                     image: 1,
1176                     line: 1,
1177                     point: 1,
1178                     polygon: 1,
1179                     text: 1,
1180                     ticks: 1,
1181                     integral: 1
1182                 };
1183 
1184             len = arguments.length;
1185             if (len < 3 || primitives[s]) {
1186                 // Default options from Options.elements
1187                 a = JXG.deepCopy(options.elements, null, true);
1188             } else {
1189                 a = {};
1190             }
1191 
1192             // Only the layer of the main element is set.
1193             if (len < 4 && this.exists(s) && this.exists(options.layer[s])) {
1194                 a.layer = options.layer[s];
1195             }
1196 
1197             // Default options from the specific element like 'line' in
1198             // copyAttribute(attributes, board.options, 'line')
1199             o = options;
1200             isAvail = true;
1201             for (i = 2; i < len; i++) {
1202                 if (this.exists(o[arguments[i]])) {
1203                     o = o[arguments[i]];
1204                 } else {
1205                     isAvail = false;
1206                     break;
1207                 }
1208             }
1209             if (isAvail) {
1210                 a = JXG.deepCopy(a, o, true);
1211             }
1212 
1213             // Merge the specific options given in the parameter 'attributes'
1214             // into the default options.
1215             // Additionally, we step into a subelement of attribute like line.point1 in case it is supplied as in
1216             // copyAttribute(attributes, board.options, 'line', 'point1')
1217             // In this case we would merge attributes.point1 into the global line.point1 attributes.
1218             o = (typeof attributes === 'object') ? attributes : {};
1219             isAvail = true;
1220             for (i = 3; i < len; i++) {
1221                 if (this.exists(o[arguments[i]])) {
1222                     o = o[arguments[i]];
1223                 } else {
1224                     isAvail = false;
1225                     break;
1226                 }
1227             }
1228             if (isAvail) {
1229                 this.mergeAttr(a, o, true);
1230             }
1231 
1232             if (arguments[2] === "board") {
1233                 // For board attributes we are done now.
1234                 return a;
1235             }
1236 
1237             // Special treatment of labels
1238             o = options;
1239             isAvail = true;
1240             for (i = 2; i < len; i++) {
1241                 if (this.exists(o[arguments[i]])) {
1242                     o = o[arguments[i]];
1243                 } else {
1244                     isAvail = false;
1245                     break;
1246                 }
1247             }
1248             if (isAvail && this.exists(o.label)) {
1249                 a.label = JXG.deepCopy(o.label, a.label);
1250             }
1251             a.label = JXG.deepCopy(options.label, a.label);
1252 
1253             return a;
1254         },
1255 
1256         /**
1257          * Copy all prototype methods from object "superObject" to object
1258          * "subObject". The constructor of superObject will be available
1259          * in subObject as subObject.constructor[constructorName].
1260          * @param {Object} subObj A JavaScript object which receives new methods.
1261          * @param {Object} superObj A JavaScript object which lends its prototype methods to subObject
1262          * @returns {String} constructorName Under this name the constructor of superObj will be available
1263          * in subObject.
1264          * @private
1265          */
1266         copyPrototypeMethods: function (subObject, superObject, constructorName) {
1267             var key;
1268 
1269             subObject.prototype[constructorName] = superObject.prototype.constructor;
1270             for (key in superObject.prototype) {
1271                 if (superObject.prototype.hasOwnProperty(key)) {
1272                     subObject.prototype[key] = superObject.prototype[key];
1273                 }
1274             }
1275         },
1276 
1277         /**
1278          * Converts a JavaScript object into a JSON string.
1279          * @param {Object} obj A JavaScript object, functions will be ignored.
1280          * @param {Boolean} [noquote=false] No quotes around the name of a property.
1281          * @returns {String} The given object stored in a JSON string.
1282          */
1283         toJSON: function (obj, noquote) {
1284             var list, prop, i, s, val;
1285 
1286             noquote = JXG.def(noquote, false);
1287 
1288             // check for native JSON support:
1289             if (typeof JSON && JSON.stringify && !noquote) {
1290                 try {
1291                     s = JSON.stringify(obj);
1292                     return s;
1293                 } catch (e) {
1294                     // if something goes wrong, e.g. if obj contains functions we won't return
1295                     // and use our own implementation as a fallback
1296                 }
1297             }
1298 
1299             switch (typeof obj) {
1300                 case "object":
1301                     if (obj) {
1302                         list = [];
1303 
1304                         if (this.isArray(obj)) {
1305                             for (i = 0; i < obj.length; i++) {
1306                                 list.push(JXG.toJSON(obj[i], noquote));
1307                             }
1308 
1309                             return "[" + list.join(",") + "]";
1310                         }
1311 
1312                         for (prop in obj) {
1313                             if (obj.hasOwnProperty(prop)) {
1314                                 try {
1315                                     val = JXG.toJSON(obj[prop], noquote);
1316                                 } catch (e2) {
1317                                     val = "";
1318                                 }
1319 
1320                                 if (noquote) {
1321                                     list.push(prop + ":" + val);
1322                                 } else {
1323                                     list.push('"' + prop + '":' + val);
1324                                 }
1325                             }
1326                         }
1327 
1328                         return "{" + list.join(",") + "} ";
1329                     }
1330                     return "null";
1331                 case "string":
1332                     return "'" + obj.replace(/(["'])/g, "\\$1") + "'";
1333                 case "number":
1334                 case "boolean":
1335                     return obj.toString();
1336             }
1337 
1338             return "0";
1339         },
1340 
1341         /**
1342          * Resets visPropOld.
1343          * @param {JXG.GeometryElement} el
1344          * @returns {GeometryElement}
1345          */
1346         clearVisPropOld: function (el) {
1347             el.visPropOld = {
1348                 cssclass: "",
1349                 cssdefaultstyle: "",
1350                 cssstyle: "",
1351                 fillcolor: "",
1352                 fillopacity: "",
1353                 firstarrow: false,
1354                 fontsize: -1,
1355                 lastarrow: false,
1356                 left: -100000,
1357                 linecap: "",
1358                 shadow: false,
1359                 strokecolor: "",
1360                 strokeopacity: "",
1361                 strokewidth: "",
1362                 tabindex: -100000,
1363                 transitionduration: 0,
1364                 top: -100000,
1365                 visible: null
1366             };
1367 
1368             return el;
1369         },
1370 
1371         /**
1372          * Checks if an object contains a key, whose value equals to val.
1373          * @param {Object} obj
1374          * @param val
1375          * @returns {Boolean}
1376          */
1377         isInObject: function (obj, val) {
1378             var el;
1379 
1380             for (el in obj) {
1381                 if (obj.hasOwnProperty(el)) {
1382                     if (obj[el] === val) {
1383                         return true;
1384                     }
1385                 }
1386             }
1387 
1388             return false;
1389         },
1390 
1391         /**
1392          * Replaces all occurences of & by &amp;, > by &gt;, and < by &lt;.
1393          * @param {String} str
1394          * @returns {String}
1395          */
1396         escapeHTML: function (str) {
1397             return str.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">");
1398         },
1399 
1400         /**
1401          * Eliminates all substrings enclosed by < and > and replaces all occurences of
1402          * &amp; by &, &gt; by >, and &lt; by <.
1403          * @param {String} str
1404          * @returns {String}
1405          */
1406         unescapeHTML: function (str) {
1407             // This regex is NOT insecure. We are replacing everything found with ''
1408             /*jslint regexp:true*/
1409             return str
1410                 .replace(/<\/?[^>]+>/gi, "")
1411                 .replace(/&/g, "&")
1412                 .replace(/</g, "<")
1413                 .replace(/>/g, ">");
1414         },
1415 
1416         /**
1417          * Makes a string lower case except for the first character which will be upper case.
1418          * @param {String} str Arbitrary string
1419          * @returns {String} The capitalized string.
1420          */
1421         capitalize: function (str) {
1422             return str.charAt(0).toUpperCase() + str.substring(1).toLowerCase();
1423         },
1424 
1425         /**
1426          * Make numbers given as strings nicer by removing all unnecessary leading and trailing zeroes.
1427          * @param {String} str
1428          * @returns {String}
1429          */
1430         trimNumber: function (str) {
1431             str = str.replace(/^0+/, "");
1432             str = str.replace(/0+$/, "");
1433 
1434             if (str[str.length - 1] === "." || str[str.length - 1] === ",") {
1435                 str = str.slice(0, -1);
1436             }
1437 
1438             if (str[0] === "." || str[0] === ",") {
1439                 str = "0" + str;
1440             }
1441 
1442             return str;
1443         },
1444 
1445         /**
1446          * Filter an array of elements.
1447          * @param {Array} list
1448          * @param {Object|function} filter
1449          * @returns {Array}
1450          */
1451         filterElements: function (list, filter) {
1452             var i,
1453                 f,
1454                 item,
1455                 flower,
1456                 value,
1457                 visPropValue,
1458                 pass,
1459                 l = list.length,
1460                 result = [];
1461 
1462             if (typeof filter !== "function" && typeof filter !== "object") {
1463                 return result;
1464             }
1465 
1466             for (i = 0; i < l; i++) {
1467                 pass = true;
1468                 item = list[i];
1469 
1470                 if (typeof filter === "object") {
1471                     for (f in filter) {
1472                         if (filter.hasOwnProperty(f)) {
1473                             flower = f.toLowerCase();
1474 
1475                             if (typeof item[f] === "function") {
1476                                 value = item[f]();
1477                             } else {
1478                                 value = item[f];
1479                             }
1480 
1481                             if (item.visProp && typeof item.visProp[flower] === "function") {
1482                                 visPropValue = item.visProp[flower]();
1483                             } else {
1484                                 visPropValue = item.visProp && item.visProp[flower];
1485                             }
1486 
1487                             if (typeof filter[f] === "function") {
1488                                 pass = filter[f](value) || filter[f](visPropValue);
1489                             } else {
1490                                 pass = value === filter[f] || visPropValue === filter[f];
1491                             }
1492 
1493                             if (!pass) {
1494                                 break;
1495                             }
1496                         }
1497                     }
1498                 } else if (typeof filter === "function") {
1499                     pass = filter(item);
1500                 }
1501 
1502                 if (pass) {
1503                     result.push(item);
1504                 }
1505             }
1506 
1507             return result;
1508         },
1509 
1510         /**
1511          * Remove all leading and trailing whitespaces from a given string.
1512          * @param {String} str
1513          * @returns {String}
1514          */
1515         trim: function (str) {
1516             // str = str.replace(/^\s+/, '');
1517             // str = str.replace(/\s+$/, '');
1518             //
1519             // return str;
1520             return str.replace(/^[\s\uFEFF\xA0]+|[\s\uFEFF\xA0]+$/g, "");
1521         },
1522 
1523         /**
1524          * Convert HTML tags to entities or use html_sanitize if the google caja html sanitizer is available.
1525          * @param {String} str
1526          * @param {Boolean} caja
1527          * @returns {String} Sanitized string
1528          */
1529         sanitizeHTML: function (str, caja) {
1530             if (typeof html_sanitize === "function" && caja) {
1531                 return html_sanitize(
1532                     str,
1533                     function () {
1534                         return undefined;
1535                     },
1536                     function (id) {
1537                         return id;
1538                     }
1539                 );
1540             }
1541 
1542             if (str && typeof str === "string") {
1543                 str = str.replace(/</g, "<").replace(/>/g, ">");
1544             }
1545 
1546             return str;
1547         },
1548 
1549         /**
1550          * If <tt>s</tt> is a slider, it returns the sliders value, otherwise it just returns the given value.
1551          * @param {*} s
1552          * @returns {*} s.Value() if s is an element of type slider, s otherwise
1553          */
1554         evalSlider: function (s) {
1555             if (s && s.type === Const.OBJECT_TYPE_GLIDER && typeof s.Value === "function") {
1556                 return s.Value();
1557             }
1558 
1559             return s;
1560         }
1561     }
1562 );
1563 
1564 export default JXG;
1565