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