1 /*
  2     Copyright 2008-2026
  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.js";
 44 import Const from "../base/constants.js";
 45 import Mat from "../math/math.js";
 46 
 47 JXG.extend(
 48     JXG,
 49     /** @lends JXG */ {
 50         /**
 51          * Checks if the given object is an JSXGraph board.
 52          * @param {Object} v
 53          * @returns {Boolean}
 54          */
 55         isBoard: function (v) {
 56             return v !== null &&
 57                 typeof v === "object" &&
 58                 this.isNumber(v.BOARD_MODE_NONE) &&
 59                 this.isObject(v.objects) &&
 60                 this.isObject(v.jc) &&
 61                 this.isFunction(v.update) &&
 62                 !!v.containerObj &&
 63                 this.isString(v.id);
 64         },
 65 
 66         /**
 67          * Checks if the given string is an id within the given board.
 68          * @param {JXG.Board} board
 69          * @param {String} s
 70          * @returns {Boolean}
 71          */
 72         isId: function (board, s) {
 73             return typeof s === "string" && !!board.objects[s];
 74         },
 75 
 76         /**
 77          * Checks if the given string is a name within the given board.
 78          * @param {JXG.Board} board
 79          * @param {String} s
 80          * @returns {Boolean}
 81          */
 82         isName: function (board, s) {
 83             return typeof s === "string" && !!board.elementsByName[s];
 84         },
 85 
 86         /**
 87          * Checks if the given string is a group id within the given board.
 88          * @param {JXG.Board} board
 89          * @param {String} s
 90          * @returns {Boolean}
 91          */
 92         isGroup: function (board, s) {
 93             return typeof s === "string" && !!board.groups[s];
 94         },
 95 
 96         /**
 97          * Checks if the value of a given variable is of type string.
 98          * @param v A variable of any type.
 99          * @returns {Boolean} True, if v is of type string.
100          */
101         isString: function (v) {
102             return typeof v === 'string';
103         },
104 
105         /**
106          * Checks if the value of a given variable is of type number.
107          * @param v A variable of any type.
108          * @param {Boolean} [acceptStringNumber=false] If set to true, the function returns true for e.g. v='3.1415'.
109          * @param {Boolean} [acceptNaN=true] If set to false, the function returns false for v=NaN.
110          * @returns {Boolean} True, if v is of type number.
111          */
112         isNumber: function (v, acceptStringNumber, acceptNaN) {
113             var result = (
114                 typeof v === 'number' || Object.prototype.toString.call(v) === '[Object Number]'
115             );
116             acceptStringNumber = acceptStringNumber || false;
117             acceptNaN = acceptNaN === undefined ? true : acceptNaN;
118 
119             if (acceptStringNumber) {
120                 result = result || ('' + parseFloat(v)) === v;
121             }
122             if (!acceptNaN) {
123                 result = result && !isNaN(v);
124             }
125             return result;
126         },
127 
128         /**
129          * Checks if a given variable references a function.
130          * @param v A variable of any type.
131          * @returns {Boolean} True, if v is a function.
132          */
133         isFunction: function (v) {
134             return typeof v === 'function';
135         },
136 
137         /**
138          * Checks if a given variable references an array.
139          * @param v A variable of any type.
140          * @returns {Boolean} True, if v is of type array.
141          */
142         isArray: function (v) {
143             var r;
144 
145             // use the ES5 isArray() method and if that doesn't exist use a fallback.
146             if (Array.isArray) {
147                 r = Array.isArray(v);
148             } else {
149                 r =
150                     v !== null &&
151                     typeof v === "object" &&
152                     typeof v.splice === "function" &&
153                     typeof v.join === 'function';
154             }
155 
156             return r;
157         },
158 
159         /**
160          * Tests if the input variable is an Object
161          * @param v
162          */
163         isObject: function (v) {
164             return typeof v === "object" && !this.isArray(v);
165         },
166 
167         /**
168          * Tests if the input variable is a DOM Document or DocumentFragment node
169          * @param v A variable of any type
170          */
171         isDocumentOrFragment: function (v) {
172             return this.isObject(v) && (
173                 v.nodeType === 9 || // Node.DOCUMENT_NODE
174                 v.nodeType === 11   // Node.DOCUMENT_FRAGMENT_NODE
175             );
176         },
177 
178         /**
179          * Checks if a given variable is a reference of a JSXGraph Point element.
180          * @param v A variable of any type.
181          * @returns {Boolean} True, if v is of type JXG.Point.
182          */
183         isPoint: function (v) {
184             if (v !== null && typeof v === "object" && this.exists(v.elementClass)) {
185                 return v.elementClass === Const.OBJECT_CLASS_POINT;
186             }
187 
188             return false;
189         },
190 
191         /**
192          * Checks if a given variable is a reference of a JSXGraph Point3D element.
193          * @param v A variable of any type.
194          * @returns {Boolean} True, if v is of type JXG.Point3D.
195          */
196         isPoint3D: function (v) {
197             if (v !== null && typeof v === "object" && this.exists(v.type)) {
198                 return v.type === Const.OBJECT_TYPE_POINT3D;
199             }
200 
201             return false;
202         },
203 
204         /**
205          * Checks if a given variable is a reference of a JSXGraph Point element or an array of length at least two or
206          * a function returning an array of length two or three.
207          * @param {JXG.Board} board
208          * @param v A variable of any type.
209          * @returns {Boolean} True, if v is of type JXG.Point.
210          */
211         isPointType: function (board, v) {
212             var val, p;
213 
214             if (this.isArray(v)) {
215                 return true;
216             }
217             if (this.isFunction(v)) {
218                 val = v();
219                 if (this.isArray(val) && val.length > 1) {
220                     return true;
221                 }
222             }
223             p = board.select(v);
224             return this.isPoint(p);
225         },
226 
227         /**
228          * Checks if a given variable is a reference of a JSXGraph Point3D element or an array of length three
229          * or a function returning an array of length three.
230          * @param {JXG.Board} board
231          * @param v A variable of any type.
232          * @returns {Boolean} True, if v is of type JXG.Point3D or an array of length at least 3, or a function returning
233          * such an array.
234          */
235         isPointType3D: function (board, v) {
236             var val, p;
237 
238             if (this.isArray(v) && v.length >= 3) {
239                 return true;
240             }
241             if (this.isFunction(v)) {
242                 val = v();
243                 if (this.isArray(val) && val.length >= 3) {
244                     return true;
245                 }
246             }
247             p = board.select(v);
248             return this.isPoint3D(p);
249         },
250 
251         /**
252          * Checks if a given variable is a reference of a JSXGraph transformation element or an array
253          * of JSXGraph transformation elements.
254          * @param v A variable of any type.
255          * @returns {Boolean} True, if v is of type JXG.Transformation.
256          */
257         isTransformationOrArray: function (v) {
258             if (v !== null) {
259                 if (this.isArray(v) && v.length > 0) {
260                     return this.isTransformationOrArray(v[0]);
261                 }
262                 if (typeof v === 'object') {
263                     return v.type === Const.OBJECT_TYPE_TRANSFORMATION;
264                 }
265             }
266             return false;
267         },
268 
269         /**
270          * Checks if v is an empty object or empty.
271          * @param v {Object|Array}
272          * @returns {boolean} True, if v is an empty object or array.
273          */
274         isEmpty: function (v) {
275             return Object.keys(v).length === 0;
276         },
277 
278         /**
279          * Checks if a given variable is neither undefined nor null. You should not use this together with global
280          * variables!
281          * @param v A variable of any type.
282          * @param {Boolean} [checkEmptyString=false] If set to true, it is also checked whether v is not equal to ''.
283          * @returns {Boolean} True, if v is neither undefined nor null.
284          */
285         exists: function (v, checkEmptyString) {
286             /* eslint-disable eqeqeq */
287             var result = !(v == undefined || v === null);
288             /* eslint-enable eqeqeq */
289             checkEmptyString = checkEmptyString || false;
290 
291             if (checkEmptyString) {
292                 return result && v !== "";
293             }
294             return result;
295         },
296         // exists: (function (undef) {
297         //     return function (v, checkEmptyString) {
298         //         var result = !(v === undef || v === null);
299 
300         //         checkEmptyString = checkEmptyString || false;
301 
302         //         if (checkEmptyString) {
303         //             return result && v !== '';
304         //         }
305         //         return result;
306         //     };
307         // }()),
308 
309         /**
310          * Handle default parameters.
311          * @param v Given value
312          * @param d Default value
313          * @returns <tt>d</tt>, if <tt>v</tt> is undefined or null.
314          */
315         def: function (v, d) {
316             if (this.exists(v)) {
317                 return v;
318             }
319 
320             return d;
321         },
322 
323         /**
324          * Converts a string containing either <strong>true</strong> or <strong>false</strong> into a boolean value.
325          * @param {String} s String containing either <strong>true</strong> or <strong>false</strong>.
326          * @returns {Boolean} String typed boolean value converted to boolean.
327          */
328         str2Bool: function (s) {
329             if (!this.exists(s)) {
330                 return true;
331             }
332 
333             if (typeof s === 'boolean') {
334                 return s;
335             }
336 
337             if (this.isString(s)) {
338                 return s.toLowerCase() === 'true';
339             }
340 
341             return false;
342         },
343 
344         /**
345          * Converts a given CSS style string into a JavaScript object. Uses JSON.parse.
346          * Has problems with CSS expressions containing blanks, like
347          * `background: #aaaaaa url("../jsxgraph/img/favicon.png")`.
348          *
349          * @param {String} cssString String containing CSS styles.
350          * @returns {Object} Object containing CSS styles.
351          * @see JXG#css2js
352          * @deprecated
353          */
354         cssParse: function (cssString) {
355             var str = cssString;
356             if (!this.isString(str)) return {};
357 
358             str = str.replace(/\s*;\s*$/g, '');
359             str = str.replace(/\s*;\s*/g, '","');
360             str = str.replace(/\s*:\s*/g, '":"');
361             str = str.trim();
362             str = '{"' + str + '"}';
363 
364             return JSON.parse(str);
365         },
366 
367         /**
368          * Converts string containing CSS properties into
369          * array with key-value pair objects.
370          *
371          * @example
372          * "color:blue; background-color:yellow" is converted to
373          * [{'color': 'blue'}, {'backgroundColor': 'yellow'}]
374          *
375          * @param  {String} cssString String containing CSS properties
376          * @return {Array} Array of CSS key-value pairs
377          */
378         css2js: function (cssString) {
379             var pairs = [],
380                 i, len,
381                 key, val,
382                 s,
383                 list = JXG.trim(cssString).replace(/;$/, "").split(";");
384 
385             len = list.length;
386             for (i = 0; i < len; ++i) {
387                 if (JXG.trim(list[i]) !== "") {
388                     s = list[i].split(":");
389                     key = JXG.trim(
390                         // CSS syntax to camel case: font-family -> fontFamily
391                         s[0].replace(/-([a-z])/gi, function (match, char) { return char.toUpperCase(); })
392                     );
393                     val = JXG.trim(s[1]);
394                     pairs.push({ key: key, val: val });
395                 }
396             }
397             return pairs;
398         },
399 
400         /**
401          * Converts a given object into a CSS style string.
402          * @param {Object} styles Object containing CSS styles.
403          * @returns {String} String containing CSS styles.
404          */
405         cssStringify: function (styles) {
406             var str = '',
407                 attr, val;
408             if (!this.isObject(styles)) return '';
409 
410             for (attr in styles) {
411                 if (!styles.hasOwnProperty(attr)) continue;
412                 val = styles[attr];
413                 if (!this.isString(val) && !this.isNumber(val)) continue;
414 
415                 str += attr + ':' + val + '; ';
416             }
417             str = str.trim();
418 
419             return str;
420         },
421 
422         /**
423          * Convert a String, a number or a function into a function. This method is used in Transformation.js
424          * @param {JXG.Board} board Reference to a JSXGraph board. It is required to resolve dependencies given
425          * by a JessieCode string, thus it must be a valid reference only in case one of the param
426          * values is of type string.
427          * @param {Array} param An array containing strings, numbers, or functions.
428          * @param {Number} n Length of <tt>param</tt>.
429          * @returns {Function} A function taking one parameter k which specifies the index of the param element
430          * to evaluate.
431          */
432         createEvalFunction: function (board, param, n) {
433             var f = [], func, i, e,
434                 deps = {};
435 
436             for (i = 0; i < n; i++) {
437                 f[i] = this.createFunction(param[i], board);
438                 for (e in f[i].deps) {
439                     deps[e] = f[i].deps;
440                 }
441             }
442 
443             func = function (k) {
444                 return f[k]();
445             };
446             func.deps = deps;
447 
448             return func;
449         },
450 
451         /**
452          * Convert a String, number or function into a function.
453          * @param {String|Number|Function} term A variable of type string, function or number.
454          * @param {JXG.Board} board Reference to a JSXGraph board. It is required to resolve dependencies given
455          * by a JessieCode/GEONE<sub>X</sub>T string, thus it must be a valid reference only in case one of the param
456          * values is of type string.
457          * @param {String} variableName Only required if function is supplied as JessieCode string or evalGeonext is set to true.
458          * Describes the variable name of the variable in a JessieCode/GEONE<sub>X</sub>T string given as term.
459          * @param {Boolean} [evalGeonext=false] Obsolete and ignored! Set this true
460          * if term should be treated as a GEONE<sub>X</sub>T string.
461          * @returns {Function} A function evaluating the value given by term or null if term is not of type string,
462          * function or number.
463          */
464         createFunction: function (term, board, variableName, evalGeonext) {
465             var f = null;
466 
467             // if ((!this.exists(evalGeonext) || evalGeonext) && this.isString(term)) {
468             if (this.isString(term)) {
469                 // Convert GEONExT syntax into  JavaScript syntax
470                 //newTerm = JXG.GeonextParser.geonext2JS(term, board);
471                 //return new Function(variableName,'return ' + newTerm + ';');
472                 //term = JXG.GeonextParser.replaceNameById(term, board);
473                 //term = JXG.GeonextParser.geonext2JS(term, board);
474 
475                 f = board.jc.snippet(term, true, variableName, false);
476             } else if (this.isFunction(term)) {
477                 f = term;
478                 f.deps = (this.isObject(term.deps)) ? term.deps : {};
479             } else if (this.isNumber(term) || this.isArray(term)) {
480                 /** @ignore */
481                 f = function () { return term; };
482                 f.deps = {};
483                 // } else if (this.isString(term)) {
484                 //     // In case of string function like fontsize
485                 //     /** @ignore */
486                 //     f = function () { return term; };
487                 //     f.deps = {};
488             }
489 
490             if (f !== null) {
491                 f.origin = term;
492             }
493 
494             return f;
495         },
496 
497         /**
498          *  Test if the parents array contains existing points. If instead parents contains coordinate arrays or
499          *  function returning coordinate arrays
500          *  free points with these coordinates are created.
501          *
502          * @param {JXG.Board} board Board object
503          * @param {Array} parents Array containing parent elements for a new object. This array may contain
504          *    <ul>
505          *      <li> {@link JXG.Point} objects
506          *      <li> {@link JXG.GeometryElement#name} of {@link JXG.Point} objects
507          *      <li> {@link JXG.GeometryElement#id} of {@link JXG.Point} objects
508          *      <li> Coordinates of points given as array of numbers of length two or three, e.g. [2, 3].
509          *      <li> Coordinates of points given as array of functions of length two or three. Each function returns one coordinate, e.g.
510          *           [function(){ return 2; }, function(){ return 3; }]
511          *      <li> Function returning coordinates, e.g. function() { return [2, 3]; }
512          *    </ul>
513          *  In the last three cases a new point will be created.
514          * @param {String} attrClass Main attribute class of newly created points, see {@link JXG#copyAttributes}
515          * @param {Array} attrArray List of subtype attributes for the newly created points. The list of subtypes is mapped to the list of new points.
516          * @returns {Array} List of newly created {@link JXG.Point} elements or false if not all returned elements are points.
517          */
518         providePoints: function (board, parents, attributes, attrClass, attrArray) {
519             var i,
520                 j,
521                 len,
522                 lenAttr = 0,
523                 points = [],
524                 attr,
525                 val;
526 
527             if (!this.isArray(parents)) {
528                 parents = [parents];
529             }
530             len = parents.length;
531             if (this.exists(attrArray)) {
532                 lenAttr = attrArray.length;
533             }
534             if (lenAttr === 0) {
535                 attr = this.copyAttributes(attributes, board.options, attrClass);
536             }
537 
538             for (i = 0; i < len; ++i) {
539                 if (lenAttr > 0) {
540                     j = Math.min(i, lenAttr - 1);
541                     attr = this.copyAttributes(
542                         attributes,
543                         board.options,
544                         attrClass,
545                         attrArray[j].toLowerCase()
546                     );
547                 }
548                 if (this.isArray(parents[i]) && parents[i].length > 1) {
549                     points.push(board.create("point", parents[i], attr));
550                     points[points.length - 1]._is_new = true;
551                 } else if (this.isFunction(parents[i])) {
552                     val = parents[i]();
553                     if (this.isArray(val) && val.length > 1) {
554                         points.push(board.create("point", [parents[i]], attr));
555                         points[points.length - 1]._is_new = true;
556                     }
557                 } else {
558                     points.push(board.select(parents[i]));
559                 }
560 
561                 if (!this.isPoint(points[i])) {
562                     return false;
563                 }
564             }
565 
566             return points;
567         },
568 
569         /**
570          *  Test if the parents array contains existing points. If instead parents contains coordinate arrays or
571          *  function returning coordinate arrays
572          *  free points with these coordinates are created.
573          *
574          * @param {JXG.View3D} view View3D object
575          * @param {Array} parents Array containing parent elements for a new object. This array may contain
576          *    <ul>
577          *      <li> {@link JXG.Point3D} objects
578          *      <li> {@link JXG.GeometryElement#name} of {@link JXG.Point3D} objects
579          *      <li> {@link JXG.GeometryElement#id} of {@link JXG.Point3D} objects
580          *      <li> Coordinates of 3D points given as array of numbers of length three, e.g. [2, 3, 1].
581          *      <li> Coordinates of 3D points given as array of functions of length three. Each function returns one coordinate, e.g.
582          *           [function(){ return 2; }, function(){ return 3; }, function(){ return 1; }]
583          *      <li> Function returning coordinates, e.g. function() { return [2, 3, 1]; }
584          *    </ul>
585          *  In the last three cases a new 3D point will be created.
586          * @param {String} attrClass Main attribute class of newly created 3D points, see {@link JXG#copyAttributes}
587          * @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.
588          * @returns {Array} List of newly created {@link JXG.Point3D} elements or false if not all returned elements are 3D points.
589          */
590         providePoints3D: function (view, parents, attributes, attrClass, attrArray) {
591             var i,
592                 j,
593                 len,
594                 lenAttr = 0,
595                 points = [],
596                 attr,
597                 val;
598 
599             if (!this.isArray(parents)) {
600                 parents = [parents];
601             }
602             len = parents.length;
603             if (this.exists(attrArray)) {
604                 lenAttr = attrArray.length;
605             }
606             if (lenAttr === 0) {
607                 attr = this.copyAttributes(attributes, view.board.options, attrClass);
608             }
609 
610             for (i = 0; i < len; ++i) {
611                 if (lenAttr > 0) {
612                     j = Math.min(i, lenAttr - 1);
613                     attr = this.copyAttributes(
614                         attributes,
615                         view.board.options,
616                         attrClass,
617                         attrArray[j]
618                     );
619                 }
620 
621                 if (this.isArray(parents[i]) && parents[i].length > 0 && parents[i].every((x)=>this.isArray(x) && this.isNumber(x[0]))) {
622                     // Testing for array-of-arrays-of-numbers, like [[1,2,3],[2,3,4]]
623                     for (j = 0; j < parents[i].length; j++) {
624                         points.push(view.create("point3d", parents[i][j], attr));;
625                         points[points.length - 1]._is_new = true;
626                     }
627                 } else if (this.isArray(parents[i]) &&  parents[i].every((x)=> this.isNumber(x) || this.isFunction(x))) {
628                     // Single array [1,2,3]
629                     points.push(view.create("point3d", parents[i], attr));
630                     points[points.length - 1]._is_new = true;
631 
632                 } else if (this.isPoint3D(parents[i])) {
633                     points.push(parents[i]);
634                 } else if (this.isFunction(parents[i])) {
635                     val = parents[i]();
636                     if (this.isArray(val) && val.length > 1) {
637                         points.push(view.create("point3d", [parents[i]], attr));
638                         points[points.length - 1]._is_new = true;
639                     }
640                 } else {
641                     points.push(view.select(parents[i]));
642                 }
643 
644                 if (!this.isPoint3D(points[i])) {
645                     return false;
646                 }
647             }
648 
649             return points;
650         },
651 
652         /**
653          * Generates a function which calls the function fn in the scope of owner.
654          * @param {Function} fn Function to call.
655          * @param {Object} owner Scope in which fn is executed.
656          * @returns {Function} A function with the same signature as fn.
657          */
658         bind: function (fn, owner) {
659             return function () {
660                 return fn.apply(owner, arguments);
661             };
662         },
663 
664         /**
665          * If <tt>val</tt> is a function, it will be evaluated without giving any parameters, else the input value
666          * is just returned.
667          * @param val Could be anything. Preferably a number or a function.
668          * @returns If <tt>val</tt> is a function, it is evaluated and the result is returned. Otherwise <tt>val</tt> is returned.
669          */
670         evaluate: function (val) {
671             if (this.isFunction(val)) {
672                 return val();
673             }
674 
675             return val;
676         },
677 
678         /**
679          * Search an array for a given value.
680          * @param {Array} array
681          * @param value
682          * @param {String} [sub] Use this property if the elements of the array are objects.
683          * @returns {Number} The index of the first appearance of the given value, or
684          * <tt>-1</tt> if the value was not found.
685          */
686         indexOf: function (array, value, sub) {
687             var i,
688                 s = this.exists(sub);
689 
690             if (Array.indexOf && !s) {
691                 return array.indexOf(value);
692             }
693 
694             for (i = 0; i < array.length; i++) {
695                 if ((s && array[i][sub] === value) || (!s && array[i] === value)) {
696                     return i;
697                 }
698             }
699 
700             return -1;
701         },
702 
703         /**
704          * Eliminates duplicate entries in an array consisting of numbers and strings.
705          * @param {Array} a An array of numbers and/or strings.
706          * @returns {Array} The array with duplicate entries eliminated.
707          */
708         eliminateDuplicates: function (a) {
709             var i,
710                 len = a.length,
711                 result = [],
712                 obj = {};
713 
714             for (i = 0; i < len; i++) {
715                 obj[a[i]] = 0;
716             }
717 
718             for (i in obj) {
719                 if (obj.hasOwnProperty(i)) {
720                     result.push(i);
721                 }
722             }
723 
724             return result;
725         },
726 
727         /**
728          * Swaps to array elements.
729          * @param {Array} arr
730          * @param {Number} i
731          * @param {Number} j
732          * @returns {Array} Reference to the given array.
733          */
734         swap: function (arr, i, j) {
735             var tmp;
736 
737             tmp = arr[i];
738             arr[i] = arr[j];
739             arr[j] = tmp;
740 
741             return arr;
742         },
743 
744         /**
745          * Generates a copy of an array and removes the duplicate entries.
746          * The original array will be altered.
747          * @param {Array} arr
748          * @returns {Array}
749          *
750          * @see JXG.toUniqueArrayFloat
751          */
752         uniqueArray: function (arr) {
753             var i,
754                 j,
755                 isArray,
756                 ret = [];
757 
758             if (arr.length === 0) {
759                 return [];
760             }
761 
762             for (i = 0; i < arr.length; i++) {
763                 isArray = this.isArray(arr[i]);
764 
765                 if (!this.exists(arr[i])) {
766                     arr[i] = "";
767                     continue;
768                 }
769                 for (j = i + 1; j < arr.length; j++) {
770                     if (isArray && JXG.cmpArrays(arr[i], arr[j])) {
771                         arr[i] = [];
772                     } else if (!isArray && arr[i] === arr[j]) {
773                         arr[i] = "";
774                     }
775                 }
776             }
777 
778             j = 0;
779 
780             for (i = 0; i < arr.length; i++) {
781                 isArray = this.isArray(arr[i]);
782 
783                 if (!isArray && arr[i] !== "") {
784                     ret[j] = arr[i];
785                     j++;
786                 } else if (isArray && arr[i].length !== 0) {
787                     ret[j] = arr[i].slice(0);
788                     j++;
789                 }
790             }
791 
792             arr = ret;
793             return ret;
794         },
795 
796         /**
797          * Generates a sorted copy of an array containing numbers and removes the duplicate entries up to a supplied precision eps.
798          * An array element arr[i] will be removed if abs(arr[i] - arr[i-1]) is less than eps.
799          *
800          * The original array will stay unaltered.
801          * @param {Array} arr
802          * @returns {Array}
803          *
804          * @param {Array} arr Array of numbers
805          * @param {Number} eps Precision
806          * @returns {Array}
807          *
808          * @example
809          * var arr = [2.3, 4, Math.PI, 2.300001, Math.PI+0.000000001];
810          * console.log(JXG.toUniqueArrayFloat(arr, 0.00001));
811          * // Output: Array(3) [ 2.3, 3.141592653589793, 4 ]
812          *
813          * @see JXG.uniqueArray
814          */
815         toUniqueArrayFloat: function (arr, eps) {
816             var a,
817                 i, le;
818 
819             // if (false && Type.exists(arr.toSorted)) {
820             //     a = arr.toSorted(function(a, b) { return a - b; });
821             // } else {
822             // }
823             // Backwards compatibility to avoid toSorted
824             a = arr.slice();
825             a.sort(function (a, b) { return a - b; });
826             le = a.length;
827             for (i = le - 1; i > 0; i--) {
828                 if (Math.abs(a[i] - a[i - 1]) < eps) {
829                     a.splice(i, 1);
830                 }
831             }
832             return a;
833         },
834 
835         /**
836          * Checks if an array contains an element equal to <tt>val</tt> but does not check the type!
837          * @param {Array} arr
838          * @param val
839          * @returns {Boolean}
840          */
841         isInArray: function (arr, val) {
842             return JXG.indexOf(arr, val) > -1;
843         },
844 
845         /**
846          * Converts an array of {@link JXG.Coords} objects into a coordinate matrix.
847          * @param {Array} coords
848          * @param {Boolean} split
849          * @returns {Array}
850          */
851         coordsArrayToMatrix: function (coords, split) {
852             var i,
853                 x = [],
854                 m = [];
855 
856             for (i = 0; i < coords.length; i++) {
857                 if (split) {
858                     x.push(coords[i].usrCoords[1]);
859                     m.push(coords[i].usrCoords[2]);
860                 } else {
861                     m.push([coords[i].usrCoords[1], coords[i].usrCoords[2]]);
862                 }
863             }
864 
865             if (split) {
866                 m = [x, m];
867             }
868 
869             return m;
870         },
871 
872         /**
873          * Compare two arrays.
874          * @param {Array} a1
875          * @param {Array} a2
876          * @returns {Boolean} <tt>true</tt>, if the arrays coefficients are of same type and value.
877          */
878         cmpArrays: function (a1, a2) {
879             var i;
880 
881             // trivial cases
882             if (a1 === a2) {
883                 return true;
884             }
885 
886             if (a1.length !== a2.length) {
887                 return false;
888             }
889 
890             for (i = 0; i < a1.length; i++) {
891                 if (this.isArray(a1[i]) && this.isArray(a2[i])) {
892                     if (!this.cmpArrays(a1[i], a2[i])) {
893                         return false;
894                     }
895                 } else if (a1[i] !== a2[i]) {
896                     return false;
897                 }
898             }
899 
900             return true;
901         },
902 
903         /**
904          * Removes an element from the given array
905          * @param {Array} ar
906          * @param el
907          * @returns {Array}
908          */
909         removeElementFromArray: function (ar, el) {
910             var i;
911 
912             for (i = 0; i < ar.length; i++) {
913                 if (ar[i] === el) {
914                     ar.splice(i, 1);
915                     return ar;
916                 }
917             }
918 
919             return ar;
920         },
921 
922         /**
923          * Truncate a number <tt>n</tt> after <tt>p</tt> decimals.
924          * @param {Number} n
925          * @param {Number} p
926          * @returns {Number}
927          */
928         trunc: function (n, p) {
929             p = JXG.def(p, 0);
930 
931             return this.toFixed(n, p);
932         },
933 
934         /**
935          * Decimal adjustment of a number.
936          * From https://developer.mozilla.org/de/docs/Web/JavaScript/Reference/Global_Objects/Math/round
937          *
938          * @param    {String}    type    The type of adjustment.
939          * @param    {Number}    value    The number.
940          * @param    {Number}    exp        The exponent (the 10 logarithm of the adjustment base).
941          * @returns    {Number}            The adjusted value.
942          *
943          * @private
944          */
945         _decimalAdjust: function (type, value, exp) {
946             // If the exp is undefined or zero...
947             if (exp === undefined || +exp === 0) {
948                 return Math[type](value);
949             }
950 
951             value = +value;
952             exp = +exp;
953             // If the value is not a number or the exp is not an integer...
954             if (isNaN(value) || !(typeof exp === "number" && exp % 1 === 0)) {
955                 return NaN;
956             }
957 
958             // Shift
959             value = value.toString().split('e');
960             value = Math[type](+(value[0] + "e" + (value[1] ? +value[1] - exp : -exp)));
961 
962             // Shift back
963             value = value.toString().split('e');
964             return +(value[0] + "e" + (value[1] ? +value[1] + exp : exp));
965         },
966 
967         /**
968          * Round a number to given number of decimal digits.
969          *
970          * Example: JXG._toFixed(3.14159, -2) gives 3.14
971          * @param  {Number} value Number to be rounded
972          * @param  {Number} exp   Number of decimal digits given as negative exponent
973          * @return {Number}       Rounded number.
974          *
975          * @private
976          */
977         _round10: function (value, exp) {
978             return this._decimalAdjust("round", value, exp);
979         },
980 
981         /**
982          * "Floor" a number to given number of decimal digits.
983          *
984          * Example: JXG._toFixed(3.14159, -2) gives 3.14
985          * @param  {Number} value Number to be floored
986          * @param  {Number} exp   Number of decimal digits given as negative exponent
987          * @return {Number}       "Floored" number.
988          *
989          * @private
990          */
991         _floor10: function (value, exp) {
992             return this._decimalAdjust("floor", value, exp);
993         },
994 
995         /**
996          * "Ceil" a number to given number of decimal digits.
997          *
998          * Example: JXG._toFixed(3.14159, -2) gives 3.15
999          * @param  {Number} value Number to be ceiled
1000          * @param  {Number} exp   Number of decimal digits given as negative exponent
1001          * @return {Number}       "Ceiled" number.
1002          *
1003          * @private
1004          */
1005         _ceil10: function (value, exp) {
1006             return this._decimalAdjust("ceil", value, exp);
1007         },
1008 
1009         /**
1010          * Replacement of the default toFixed() method.
1011          * It does a correct rounding (independent of the browser) and
1012          * returns "0.00" for toFixed(-0.000001, 2) instead of "-0.00" which
1013          * is returned by JavaScript's toFixed()
1014          *
1015          * @memberOf JXG
1016          * @param  {Number} num    Number tp be rounded
1017          * @param  {Number} digits Decimal digits
1018          * @return {String}        Rounded number is returned as string
1019          */
1020         toFixed: function (num, digits) {
1021             return this._round10(num, -digits).toFixed(digits);
1022         },
1023 
1024         /**
1025          * Truncate a number <tt>val</tt> automatically.
1026          * @memberOf JXG
1027          * @param val
1028          * @returns {Number}
1029          */
1030         autoDigits: function (val) {
1031             var x = Math.abs(val),
1032                 str;
1033 
1034             if (x >= 0.1) {
1035                 str = this.toFixed(val, 2);
1036             } else if (x >= 0.01) {
1037                 str = this.toFixed(val, 4);
1038             } else if (x >= 0.0001) {
1039                 str = this.toFixed(val, 6);
1040             } else {
1041                 str = val;
1042             }
1043             return str;
1044         },
1045 
1046         /**
1047          * Convert value v. If v has the form
1048          * <ul>
1049          * <li> 'x%': return floating point number x * percentOfWhat * 0.01
1050          * <li> 'xfr': return floating point number x * percentOfWhat
1051          * <li> 'xpx': return x * convertPx or convertPx(x) or x
1052          * <li> x or 'x': return floating point number x
1053          * </ul>
1054          * @param {String|Number} v
1055          * @param {Number} percentOfWhat
1056          * @param {Function|Number|*} convertPx
1057          * @returns {String|Number}
1058          */
1059         parseNumber: function(v, percentOfWhat, convertPx) {
1060             var str;
1061 
1062             if (this.isString(v) && v.indexOf('%') > -1) {
1063                 str = v.replace(/\s+%\s+/, '');
1064                 return parseFloat(str) * percentOfWhat * 0.01;
1065             }
1066             if (this.isString(v) && v.indexOf('fr') > -1) {
1067                 str = v.replace(/\s+fr\s+/, '');
1068                 return parseFloat(str) * percentOfWhat;
1069             }
1070             if (this.isString(v) && v.indexOf('px') > -1) {
1071                 str = v.replace(/\s+px\s+/, '');
1072                 str = parseFloat(str);
1073                 if(this.isFunction(convertPx)) {
1074                     return convertPx(str);
1075                 } else if(this.isNumber(convertPx)) {
1076                     return str * convertPx;
1077                 } else {
1078                     return str;
1079                 }
1080             }
1081             // Number or String containing no unit
1082             return parseFloat(v);
1083         },
1084 
1085         /**
1086          * Parse a string for label positioning of the form 'left pos' or 'pos right'
1087          * and return e.g.
1088          * <tt>{ side: 'left', pos: 'pos' }</tt>.
1089          * @param {String} str
1090          * @returns {Obj}  <tt>{ side, pos }</tt>
1091          */
1092         parsePosition: function(str) {
1093             var a, i,
1094                 side = '',
1095                 pos = '';
1096 
1097             str = str.trim();
1098             if (str !== '') {
1099                 a = str.split(/[ ,]+/);
1100                 for (i = 0; i < a.length; i++) {
1101                     if (a[i] === 'left' || a[i] === 'right') {
1102                         side = a[i];
1103                     } else {
1104                         pos = a[i];
1105                     }
1106                 }
1107             }
1108 
1109             return {
1110                 side: side,
1111                 pos: pos
1112             };
1113         },
1114 
1115         /**
1116          * Extracts the keys of a given object.
1117          * @param object The object the keys are to be extracted
1118          * @param onlyOwn If true, hasOwnProperty() is used to verify that only keys are collected
1119          * the object owns itself and not some other object in the prototype chain.
1120          * @returns {Array} All keys of the given object.
1121          */
1122         keys: function (object, onlyOwn) {
1123             var keys = [],
1124                 property;
1125 
1126             // the caller decides if we use hasOwnProperty
1127             /*jslint forin:true*/
1128             for (property in object) {
1129                 if (onlyOwn) {
1130                     if (object.hasOwnProperty(property)) {
1131                         keys.push(property);
1132                     }
1133                 } else {
1134                     keys.push(property);
1135                 }
1136             }
1137             /*jslint forin:false*/
1138 
1139             return keys;
1140         },
1141 
1142         /**
1143          * This outputs an object with a base class reference to the given object. This is useful if
1144          * you need a copy of an e.g. attributes object and want to overwrite some of the attributes
1145          * without changing the original object.
1146          * @param {Object} obj Object to be embedded.
1147          * @returns {Object} An object with a base class reference to <tt>obj</tt>.
1148          */
1149         clone: function (obj) {
1150             var cObj = {};
1151 
1152             cObj.prototype = obj;
1153 
1154             return cObj;
1155         },
1156 
1157         /**
1158          * Embeds an existing object into another one just like {@link #clone} and copies the contents of the second object
1159          * to the new one. Warning: The copied properties of obj2 are just flat copies.
1160          * @param {Object} obj Object to be copied.
1161          * @param {Object} obj2 Object with data that is to be copied to the new one as well.
1162          * @returns {Object} Copy of given object including some new/overwritten data from obj2.
1163          */
1164         cloneAndCopy: function (obj, obj2) {
1165             var r,
1166                 cObj = function () {
1167                     return undefined;
1168                 };
1169 
1170             cObj.prototype = obj;
1171 
1172             // no hasOwnProperty on purpose
1173             /*jslint forin:true*/
1174             /*jshint forin:true*/
1175 
1176             for (r in obj2) {
1177                 cObj[r] = obj2[r];
1178             }
1179 
1180             /*jslint forin:false*/
1181             /*jshint forin:false*/
1182 
1183             return cObj;
1184         },
1185 
1186         /**
1187          * Recursively merges obj2 into obj1 in-place. Contrary to {@link JXG#deepCopy} this won't create a new object
1188          * but instead will overwrite obj1.
1189          * <p>
1190          * In contrast to method JXG.mergeAttr, merge recurses into any kind of object, e.g. DOM object and JSXGraph objects.
1191          * So, please be careful.
1192          * @param {Object} obj1
1193          * @param {Object} obj2
1194          * @returns {Object}
1195          * @see JXG.mergeAttr
1196          *
1197          * @example
1198          * JXG.Options = JXG.merge(JXG.Options, {
1199          *     board: {
1200          *         showNavigation: false,
1201          *         showInfobox: true
1202          *     },
1203          *     point: {
1204          *         face: 'o',
1205          *         size: 4,
1206          *         fillColor: '#eeeeee',
1207          *         highlightFillColor: '#eeeeee',
1208          *         strokeColor: 'white',
1209          *         highlightStrokeColor: 'white',
1210          *         showInfobox: 'inherit'
1211          *     }
1212          * });
1213          *
1214          * </pre><div id="JXGc5bf0f2a-bd5a-4612-97c2-09f17b1bbc6b" class="jxgbox" style="width: 300px; height: 300px;"></div>
1215          * <script type="text/javascript">
1216          *     (function() {
1217          *         var board = JXG.JSXGraph.initBoard('JXGc5bf0f2a-bd5a-4612-97c2-09f17b1bbc6b',
1218          *             {boundingbox: [-8, 8, 8,-8], axis: true, showcopyright: false, shownavigation: false});
1219          *     JXG.Options = JXG.merge(JXG.Options, {
1220          *         board: {
1221          *             showNavigation: false,
1222          *             showInfobox: true
1223          *         },
1224          *         point: {
1225          *             face: 'o',
1226          *             size: 4,
1227          *             fillColor: '#eeeeee',
1228          *             highlightFillColor: '#eeeeee',
1229          *             strokeColor: 'white',
1230          *             highlightStrokeColor: 'white',
1231          *             showInfobox: 'inherit'
1232          *         }
1233          *     });
1234          *
1235          *
1236          *     })();
1237          *
1238          * </script><pre>
1239          */
1240         merge: function (obj1, obj2) {
1241             var i, j, o, oo;
1242 
1243             for (i in obj2) {
1244                 if (obj2.hasOwnProperty(i)) {
1245                     o = obj2[i];
1246                     if (this.isArray(o)) {
1247                         if (!obj1[i]) {
1248                             obj1[i] = [];
1249                         }
1250 
1251                         for (j = 0; j < o.length; j++) {
1252                             oo = obj2[i][j];
1253                             if (typeof obj2[i][j] === 'object') {
1254                                 obj1[i][j] = this.merge(obj1[i][j], oo);
1255                             } else {
1256                                 obj1[i][j] = obj2[i][j];
1257                             }
1258                         }
1259                     } else if (typeof o === 'object') {
1260                         if (!obj1[i]) {
1261                             obj1[i] = {};
1262                         }
1263 
1264                         obj1[i] = this.merge(obj1[i], o);
1265                     } else {
1266                         if (typeof obj1 === 'boolean') {
1267                             // This is necessary in the following scenario:
1268                             //   lastArrow == false
1269                             // and call of
1270                             //   setAttribute({lastArrow: {type: 7}})
1271                             obj1 = {};
1272                         }
1273                         obj1[i] = o;
1274                     }
1275                 }
1276             }
1277 
1278             return obj1;
1279         },
1280 
1281         /**
1282          * Creates a deep copy of an existing object, i.e. arrays or sub-objects are copied component resp.
1283          * element-wise instead of just copying the reference. If a second object is supplied, the two objects
1284          * are merged into one object. The properties of the second object have priority.
1285          * @param {Object} obj This object will be copied.
1286          * @param {Object} obj2 This object will merged into the newly created object
1287          * @param {Boolean} [toLower=false] If true the keys are convert to lower case. This is needed for visProp, see JXG#copyAttributes
1288          * @returns {Object} copy of obj or merge of obj and obj2.
1289          */
1290         deepCopy: function (obj, obj2, toLower) {
1291             var c, i, prop, i2;
1292 
1293             toLower = toLower || false;
1294             if (typeof obj !== 'object' || obj === null) {
1295                 return obj;
1296             }
1297 
1298             // Missing hasOwnProperty is on purpose in this function
1299             if (this.isArray(obj)) {
1300                 c = [];
1301                 for (i = 0; i < obj.length; i++) {
1302                     prop = obj[i];
1303                     // Attention: typeof null === 'object'
1304                     if (prop !== null && typeof prop === 'object') {
1305                         // We certainly do not want to recurse into a JSXGraph object.
1306                         // This would for sure result in an infinite recursion.
1307                         // As alternative we copy the id of the object.
1308                         if (this.exists(prop.board)) {
1309                             c[i] = prop.id;
1310                         } else {
1311                             c[i] = this.deepCopy(prop, {}, toLower);
1312                         }
1313                     } else {
1314                         c[i] = prop;
1315                     }
1316                 }
1317             } else {
1318                 c = {};
1319                 for (i in obj) {
1320                     if (obj.hasOwnProperty(i)) {
1321                         i2 = toLower ? i.toLowerCase() : i;
1322                         prop = obj[i];
1323                         if (prop !== null && typeof prop === 'object') {
1324                             if (this.exists(prop.board)) {
1325                                 c[i2] = prop.id;
1326                             } else {
1327                                 c[i2] = this.deepCopy(prop, {}, toLower);
1328                             }
1329                         } else {
1330                             c[i2] = prop;
1331                         }
1332                     }
1333                 }
1334 
1335                 for (i in obj2) {
1336                     if (obj2.hasOwnProperty(i)) {
1337                         i2 = toLower ? i.toLowerCase() : i;
1338 
1339                         prop = obj2[i];
1340                         if (prop !== null && typeof prop === 'object') {
1341                             if (this.isArray(prop) || !this.exists(c[i2])) {
1342                                 c[i2] = this.deepCopy(prop, {}, toLower);
1343                             } else {
1344                                 c[i2] = this.deepCopy(c[i2], prop, toLower);
1345                             }
1346                         } else {
1347                             c[i2] = prop;
1348                         }
1349                     }
1350                 }
1351             }
1352 
1353             return c;
1354         },
1355 
1356         /**
1357          * In-place (deep) merging of attributes. Allows attributes like `{shadow: {enabled: true...}}`
1358          * <p>
1359          * In contrast to method JXG.merge, mergeAttr does not recurse into DOM objects and JSXGraph objects. Instead
1360          * handles (pointers) to these objects are used.
1361          *
1362          * @param {Object} attr Object with attributes - usually containing default options - that will be changed in-place.
1363          * @param {Object} special Special option values which overwrite (recursively) the default options
1364          * @param {Boolean} [toLower=true] If true the keys are converted to lower case.
1365          * @param {Boolean} [ignoreUndefinedSpecials=false] If true the values in special that are undefined are not used.
1366          *
1367          * @see JXG.merge
1368          *
1369          */
1370         mergeAttr: function (attr, special, toLower, ignoreUndefinedSpecials) {
1371             var e, e2, o;
1372 
1373             toLower = toLower || true;
1374             ignoreUndefinedSpecials = ignoreUndefinedSpecials || false;
1375 
1376             for (e in special) {
1377                 if (special.hasOwnProperty(e)) {
1378                     e2 = (toLower) ? e.toLowerCase(): e;
1379                     // Key already exists, but not in lower case
1380                     if (e2 !== e && attr.hasOwnProperty(e)) {
1381                         if (attr.hasOwnProperty(e2)) {
1382                             // Lower case key already exists - this should not happen
1383                             // We have to unify the two key-value pairs
1384                             // It is not clear which has precedence.
1385                             this.mergeAttr(attr[e2], attr[e], toLower);
1386                         } else {
1387                             attr[e2] = attr[e];
1388                         }
1389                         delete attr[e];
1390                     }
1391 
1392                     o = special[e];
1393                     if (this.isObject(o) && o !== null &&
1394                         // Do not recurse into a document object or a JSXGraph object
1395                         !this.isDocumentOrFragment(o) && !this.exists(o.board) &&
1396                         // Do not recurse if a string is provided as "new String(...)"
1397                         typeof o.valueOf() !== 'string') {
1398                         if (attr[e2] === undefined || attr[e2] === null || !this.isObject(attr[e2])) {
1399                             // The last test handles the case:
1400                             //   attr.draft = false;
1401                             //   special.draft = { strokewidth: 4}
1402                             attr[e2] = {};
1403                         }
1404                         this.mergeAttr(attr[e2], o, toLower);
1405                     } else if(!ignoreUndefinedSpecials || this.exists(o)) {
1406                         // Flat copy
1407                         // This is also used in the cases
1408                         //   attr.shadow = { enabled: true ...}
1409                         //   special.shadow = false;
1410                         // and
1411                         //   special.anchor is a JSXGraph element
1412                         attr[e2] = o;
1413                     }
1414                 }
1415             }
1416         },
1417 
1418         /**
1419          * Convert an object to a new object containing only
1420          * lower case properties.
1421          *
1422          * @param {Object} obj
1423          * @returns Object
1424          * @example
1425          * var attr = JXG.keysToLowerCase({radiusPoint: {visible: false}});
1426          *
1427          * // return {radiuspoint: {visible: false}}
1428          */
1429         keysToLowerCase: function (obj) {
1430             var key, val,
1431                 keys = Object.keys(obj),
1432                 n = keys.length,
1433                 newObj = {};
1434 
1435             if (typeof obj !== 'object') {
1436                 return obj;
1437             }
1438 
1439             while (n--) {
1440                 key = keys[n];
1441                 if (obj.hasOwnProperty(key)) {
1442                     // We recurse into an object only if it is
1443                     // neither a DOM node nor an JSXGraph object
1444                     val = obj[key];
1445                     if (typeof val === 'object' && val !== null &&
1446                         !this.isArray(val) &&
1447                         !this.exists(val.nodeType) &&
1448                         !this.exists(val.board)) {
1449                         newObj[key.toLowerCase()] = this.keysToLowerCase(val);
1450                     } else {
1451                         newObj[key.toLowerCase()] = val;
1452                     }
1453                 }
1454             }
1455             return newObj;
1456         },
1457 
1458         /**
1459          * Generates an attributes object that is filled with default values from the Options object
1460          * and overwritten by the user specified attributes.
1461          * @param {Object} attributes user specified attributes
1462          * @param {Object} options defaults options
1463          * @param {String} s variable number of strings, e.g. 'slider', subtype 'point1'. Must be provided in lower case!
1464          * @returns {Object} The resulting attributes object
1465          */
1466         copyAttributes: function (attributes, options, s) {
1467             var a, arg, i, len, o, isAvail,
1468                 primitives = {
1469                     circle: 1,
1470                     curve: 1,
1471                     foreignobject: 1,
1472                     image: 1,
1473                     line: 1,
1474                     point: 1,
1475                     polygon: 1,
1476                     text: 1,
1477                     ticks: 1,
1478                     integral: 1
1479                 };
1480 
1481             len = arguments.length;
1482             if (len < 3 || primitives[s]) {
1483                 // Default options from Options.elements
1484                 a = JXG.deepCopy(options.elements, null, true);
1485             } else {
1486                 a = {};
1487             }
1488 
1489             // Only the layer of the main element is set.
1490             if (len < 4 && this.exists(s) && this.exists(options.layer[s])) {
1491                 a.layer = options.layer[s];
1492             }
1493 
1494             // Default options from the specific element like 'line' in
1495             //     copyAttribute(attributes, board.options, 'line')
1496             // but also like in
1497             //     Type.copyAttributes(attributes, board.options, 'view3d', 'az', 'slider');
1498             o = options;
1499             isAvail = true;
1500             for (i = 2; i < len; i++) {
1501                 arg = arguments[i];
1502                 if (this.exists(o[arg])) {
1503                     o = o[arg];
1504                 } else {
1505                     isAvail = false;
1506                     break;
1507                 }
1508             }
1509             if (isAvail) {
1510                 a = JXG.deepCopy(a, o, true);
1511             }
1512 
1513             // Merge the specific options given in the parameter 'attributes'
1514             // into the default options.
1515             // Additionally, we step into a sub-element of attribute like line.point1 -
1516             // in case it is supplied as in
1517             //     copyAttribute(attributes, board.options, 'line', 'point1')
1518             // In this case we would merge attributes.point1 into the global line.point1 attributes.
1519             o = (typeof attributes === 'object') ? this.keysToLowerCase(attributes) : {};
1520             isAvail = true;
1521             for (i = 3; i < len; i++) {
1522                 arg = arguments[i].toLowerCase();
1523                 if (this.exists(o[arg])) {
1524                     o = o[arg];
1525                 } else {
1526                     isAvail = false;
1527                     break;
1528                 }
1529             }
1530             if (isAvail) {
1531                 this.mergeAttr(a, o, true);
1532             }
1533 
1534             if (arguments[2] === 'board') {
1535                 // For board attributes we are done now.
1536                 return a;
1537             }
1538 
1539             // Special treatment of labels
1540             o = options;
1541             isAvail = true;
1542             for (i = 2; i < len; i++) {
1543                 arg = arguments[i];
1544                 if (this.exists(o[arg])) {
1545                     o = o[arg];
1546                 } else {
1547                     isAvail = false;
1548                     break;
1549                 }
1550             }
1551             if (isAvail && this.exists(o.label)) {
1552                 a.label = JXG.deepCopy(o.label, a.label, true);
1553             }
1554             a.label = JXG.deepCopy(options.label, a.label, true);
1555 
1556             return a;
1557         },
1558 
1559         /**
1560          * Copy all prototype methods from object "superObject" to object
1561          * "subObject". The constructor of superObject will be available
1562          * in subObject as subObject.constructor[constructorName].
1563          * @param {Object} subObj A JavaScript object which receives new methods.
1564          * @param {Object} superObj A JavaScript object which lends its prototype methods to subObject
1565          * @returns {String} constructorName Under this name the constructor of superObj will be available
1566          * in subObject.
1567          * @private
1568          */
1569         copyPrototypeMethods: function (subObject, superObject, constructorName) {
1570             var key;
1571 
1572             subObject.prototype[constructorName] = superObject.prototype.constructor;
1573             for (key in superObject.prototype) {
1574                 if (superObject.prototype.hasOwnProperty(key)) {
1575                     subObject.prototype[key] = superObject.prototype[key];
1576                 }
1577             }
1578         },
1579 
1580         /**
1581          * Create a stripped down version of a JSXGraph element for cloning to the background.
1582          * Used in {JXG.GeometryElement#cloneToBackground} for creating traces.
1583          *
1584          * @param {JXG.GeometryElement} el Element to be cloned
1585          * @returns Object Cloned element
1586          * @private
1587          */
1588         getCloneObject: function(el) {
1589             var obj, key,
1590                 copy = {};
1591 
1592             copy.id = el.id + "T" + el.numTraces;
1593             el.numTraces += 1;
1594 
1595             copy.coords = el.coords;
1596             obj = this.deepCopy(el.visProp, el.visProp.traceattributes, true);
1597             copy.visProp = {};
1598             for (key in obj) {
1599                 if (obj.hasOwnProperty(key)) {
1600                     if (
1601                         key.indexOf('aria') !== 0 &&
1602                         key.indexOf('highlight') !== 0 &&
1603                         key.indexOf('attractor') !== 0 &&
1604                         key !== 'label' &&
1605                         key !== 'needsregularupdate' &&
1606                         key !== 'infoboxdigits'
1607                     ) {
1608                         copy.visProp[key] = el.eval(obj[key]);
1609                     }
1610                 }
1611             }
1612             copy.evalVisProp = function(val) {
1613                 return copy.visProp[val];
1614             };
1615             copy.eval = function(val) {
1616                 return val;
1617             };
1618 
1619             copy.visProp.layer = el.board.options.layer.trace;
1620             copy.visProp.tabindex = null;
1621             copy.visProp.highlight = false;
1622             copy.board = el.board;
1623             copy.elementClass = el.elementClass;
1624 
1625             this.clearVisPropOld(copy);
1626             copy.visPropCalc = {
1627                 visible: el.evalVisProp('visible')
1628             };
1629 
1630             return copy;
1631         },
1632 
1633         /**
1634          * Converts a JavaScript object into a JSON string.
1635          * @param {Object} obj A JavaScript object, functions will be ignored.
1636          * @param {Boolean} [noquote=false] No quotes around the name of a property.
1637          * @returns {String} The given object stored in a JSON string.
1638          * @deprecated
1639          */
1640         toJSON: function (obj, noquote) {
1641             var list, prop, i, s, val;
1642 
1643             noquote = JXG.def(noquote, false);
1644 
1645             // check for native JSON support:
1646             if (JSON !== undefined && JSON.stringify && !noquote) {
1647                 try {
1648                     s = JSON.stringify(obj);
1649                     return s;
1650                 } catch (e) {
1651                     // if something goes wrong, e.g. if obj contains functions we won't return
1652                     // and use our own implementation as a fallback
1653                 }
1654             }
1655 
1656             switch (typeof obj) {
1657                 case "object":
1658                     if (obj) {
1659                         list = [];
1660 
1661                         if (this.isArray(obj)) {
1662                             for (i = 0; i < obj.length; i++) {
1663                                 list.push(JXG.toJSON(obj[i], noquote));
1664                             }
1665 
1666                             return "[" + list.join(",") + "]";
1667                         }
1668 
1669                         for (prop in obj) {
1670                             if (obj.hasOwnProperty(prop)) {
1671                                 try {
1672                                     val = JXG.toJSON(obj[prop], noquote);
1673                                 } catch (e2) {
1674                                     val = "";
1675                                 }
1676 
1677                                 if (noquote) {
1678                                     list.push(prop + ":" + val);
1679                                 } else {
1680                                     list.push('"' + prop + '":' + val);
1681                                 }
1682                             }
1683                         }
1684 
1685                         return "{" + list.join(",") + "} ";
1686                     }
1687                     return 'null';
1688                 case "string":
1689                     return "'" + obj.replace(/(["'])/g, "\\$1") + "'";
1690                 case "number":
1691                 case "boolean":
1692                     return obj.toString();
1693             }
1694 
1695             return '0';
1696         },
1697 
1698         /**
1699          * Resets visPropOld.
1700          * @param {JXG.GeometryElement} el
1701          * @returns {GeometryElement}
1702          */
1703         clearVisPropOld: function (el) {
1704             el.visPropOld = {
1705                 cssclass: "",
1706                 cssdefaultstyle: "",
1707                 cssstyle: "",
1708                 fillcolor: "",
1709                 fillopacity: "",
1710                 firstarrow: false,
1711                 fontsize: -1,
1712                 lastarrow: false,
1713                 left: -100000,
1714                 linecap: "",
1715                 shadow: false,
1716                 strokecolor: "",
1717                 strokeopacity: "",
1718                 strokewidth: "",
1719                 tabindex: -100000,
1720                 transitionduration: 0,
1721                 top: -100000,
1722                 visible: null
1723             };
1724 
1725             return el;
1726         },
1727 
1728         /**
1729          * Checks if an object contains a key, whose value equals to val.
1730          * @param {Object} obj
1731          * @param val
1732          * @returns {Boolean}
1733          */
1734         isInObject: function (obj, val) {
1735             var el;
1736 
1737             for (el in obj) {
1738                 if (obj.hasOwnProperty(el)) {
1739                     if (obj[el] === val) {
1740                         return true;
1741                     }
1742                 }
1743             }
1744 
1745             return false;
1746         },
1747 
1748         /**
1749          * Replaces all occurences of & by &amp;, > by &gt;, and < by &lt;.
1750          * @param {String} str
1751          * @returns {String}
1752          */
1753         escapeHTML: function (str) {
1754             return str.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">");
1755         },
1756 
1757         /**
1758          * Eliminates all substrings enclosed by < and > and replaces all occurences of
1759          * &amp; by &, &gt; by >, and &lt; by <.
1760          * @param {String} str
1761          * @returns {String}
1762          */
1763         unescapeHTML: function (str) {
1764             // This regex is NOT insecure. We are replacing everything found with ''
1765             /*jslint regexp:true*/
1766             return str
1767                 .replace(/<\/?[^>]+>/gi, "")
1768                 .replace(/&/g, "&")
1769                 .replace(/</g, "<")
1770                 .replace(/>/g, ">");
1771         },
1772 
1773         /**
1774          * Makes a string lower case except for the first character which will be upper case.
1775          * @param {String} str Arbitrary string
1776          * @returns {String} The capitalized string.
1777          */
1778         capitalize: function (str) {
1779             return str.charAt(0).toUpperCase() + str.substring(1).toLowerCase();
1780         },
1781 
1782         /**
1783          * Make numbers given as strings nicer by removing all unnecessary leading and trailing zeroes.
1784          * @param {String} str
1785          * @returns {String}
1786          */
1787         trimNumber: function (str) {
1788             str = str.replace(/^0+/, "");
1789             str = str.replace(/0+$/, "");
1790 
1791             if (str[str.length - 1] === "." || str[str.length - 1] === ",") {
1792                 str = str.slice(0, -1);
1793             }
1794 
1795             if (str[0] === "." || str[0] === ",") {
1796                 str = "0" + str;
1797             }
1798 
1799             return str;
1800         },
1801 
1802         /**
1803          * Filter an array of elements.
1804          * @param {Array} list
1805          * @param {Object|function} filter
1806          * @returns {Array}
1807          */
1808         filterElements: function (list, filter) {
1809             var i,
1810                 f,
1811                 item,
1812                 flower,
1813                 value,
1814                 visPropValue,
1815                 pass,
1816                 l = list.length,
1817                 result = [];
1818 
1819             if (this.exists(filter) && typeof filter !== "function" && typeof filter !== 'object') {
1820                 return result;
1821             }
1822 
1823             for (i = 0; i < l; i++) {
1824                 pass = true;
1825                 item = list[i];
1826 
1827                 if (typeof filter === 'object') {
1828                     for (f in filter) {
1829                         if (filter.hasOwnProperty(f)) {
1830                             flower = f.toLowerCase();
1831 
1832                             if (typeof item[f] === 'function') {
1833                                 value = item[f]();
1834                             } else {
1835                                 value = item[f];
1836                             }
1837 
1838                             if (item.visProp && typeof item.visProp[flower] === 'function') {
1839                                 visPropValue = item.visProp[flower]();
1840                             } else {
1841                                 visPropValue = item.visProp && item.visProp[flower];
1842                             }
1843 
1844                             if (typeof filter[f] === 'function') {
1845                                 pass = filter[f](value) || filter[f](visPropValue);
1846                             } else {
1847                                 pass = value === filter[f] || visPropValue === filter[f];
1848                             }
1849 
1850                             if (!pass) {
1851                                 break;
1852                             }
1853                         }
1854                     }
1855                 } else if (typeof filter === 'function') {
1856                     pass = filter(item);
1857                 }
1858 
1859                 if (pass) {
1860                     result.push(item);
1861                 }
1862             }
1863 
1864             return result;
1865         },
1866 
1867         /**
1868          * Remove all leading and trailing whitespaces from a given string.
1869          * @param {String} str
1870          * @returns {String}
1871          */
1872         trim: function (str) {
1873             // str = str.replace(/^\s+/, '');
1874             // str = str.replace(/\s+$/, '');
1875             //
1876             // return str;
1877             return str.replace(/^[\s\uFEFF\xA0]+|[\s\uFEFF\xA0]+$/g, "");
1878         },
1879 
1880         /**
1881          * Convert a floating point number to a string integer + fraction.
1882          * Returns either a string of the form '3 1/3' (in case of useTeX=false)
1883          * or '3 \\frac{1}{3}' (in case of useTeX=true).
1884          *
1885          * @param {Number} x
1886          * @param {Boolean} [useTeX=false]
1887          * @param {Number} [order=0.001]
1888          * @returns {String}
1889          * @see JXG.Math.decToFraction
1890          */
1891         toFraction: function (x, useTeX, order) {
1892             var arr = Mat.decToFraction(x, order),
1893                 str = '';
1894 
1895             if (arr[1] === 0 && arr[2] === 0) {
1896                 // 0
1897                 str += '0';
1898             } else {
1899                 // Sign
1900                 if (arr[0] < 0) {
1901                     str += '-';
1902                 }
1903                 if (arr[2] === 0) {
1904                     // Integer
1905                     str += arr[1];
1906                 } else if (!(arr[2] === 1 && arr[3] === 1)) {
1907                     // Proper fraction
1908                     if (arr[1] !== 0) {
1909                         // Absolute value larger than 1
1910                         str += arr[1] + ' ';
1911                     }
1912                     // Add fractional part
1913                     if (useTeX === true) {
1914                         str += '\\frac{' + arr[2] + '}{' + arr[3] + '}';
1915                     } else {
1916                         str += arr[2] + '/' + arr[3];
1917                     }
1918                 }
1919             }
1920             return str;
1921         },
1922 
1923         /**
1924          * Concat array src to array dest.
1925          * Uses push instead of JavaScript concat, which is much
1926          * faster.
1927          * The array dest is changed in place.
1928          * <p><b>Attention:</b> if "dest" is an anonymous array, the correct result is returned from the function.
1929          *
1930          * @param {Array} dest
1931          * @param {Array} src
1932          * @returns Array
1933          */
1934         concat: function(dest, src) {
1935             var i,
1936                 le = src.length;
1937             for (i = 0; i < le; i++) {
1938                 dest.push(src[i]);
1939             }
1940             return dest;
1941         },
1942 
1943         /**
1944          * Convert HTML tags to entities or use html_sanitize if the google caja html sanitizer is available.
1945          * @param {String} str
1946          * @param {Boolean} caja
1947          * @returns {String} Sanitized string
1948          */
1949         sanitizeHTML: function (str, caja) {
1950             if (typeof html_sanitize === "function" && caja) {
1951                 return html_sanitize(
1952                     str,
1953                     function () {
1954                         return undefined;
1955                     },
1956                     function (id) {
1957                         return id;
1958                     }
1959                 );
1960             }
1961 
1962             if (str && typeof str === 'string') {
1963                 str = str.replace(/</g, "<").replace(/>/g, ">");
1964             }
1965 
1966             return str;
1967         },
1968 
1969         /**
1970          * If <tt>s</tt> is a slider, it returns the sliders value, otherwise it just returns the given value.
1971          * @param {*} s
1972          * @returns {*} s.Value() if s is an element of type slider, s otherwise
1973          */
1974         evalSlider: function (s) {
1975             if (s && s.type === Const.OBJECT_TYPE_GLIDER && typeof s.Value === 'function') {
1976                 return s.Value();
1977             }
1978 
1979             return s;
1980         },
1981 
1982         /**
1983          * Convert a string containing a MAXIMA /STACK expression into a JSXGraph / JessieCode string
1984          * or an array of JSXGraph / JessieCode strings.
1985          * <p>
1986          * This function is meanwhile superseded by stack_jxg.stack2jsxgraph.
1987          *
1988          * @deprecated
1989          *
1990          * @example
1991          * console.log( JXG.stack2jsxgraph("%e**x") );
1992          * // Output:
1993          * //    "EULER**x"
1994          *
1995          * @example
1996          * console.log( JXG.stack2jsxgraph("[%pi*(x**2 - 1), %phi*(x - 1), %gamma*(x+1)]") );
1997          * // Output:
1998          * //    [ "PI*(x**2 - 1)", "1.618033988749895*(x - 1)", "0.5772156649015329*(x+1)" ]
1999          *
2000          * @param {String} str
2001          * @returns String
2002          */
2003         stack2jsxgraph: function(str) {
2004             var t;
2005 
2006             t = str.
2007                 replace(/%pi/g, 'PI').
2008                 replace(/%e/g, 'EULER').
2009                 replace(/%phi/g, '1.618033988749895').
2010                 replace(/%gamma/g, '0.5772156649015329').
2011                 trim();
2012 
2013             // String containing array -> array containing strings
2014             if (t[0] === '[' && t[t.length - 1] === ']') {
2015                 t = t.slice(1, -1).split(/\s*,\s*/);
2016             }
2017 
2018             return t;
2019         }
2020     }
2021 );
2022 
2023 export default JXG;
2024