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