1 /*
  2     Copyright 2008-2017
  3         Matthias Ehmann,
  4         Michael Gerhaeuser,
  5         Carsten Miller,
  6         Bianca Valentin,
  7         Alfred Wassermann,
  8         Peter Wilfahrt
  9 
 10     This file is part of JSXGraph.
 11 
 12     JSXGraph is free software dual licensed under the GNU LGPL or MIT License.
 13 
 14     You can redistribute it and/or modify it under the terms of the
 15 
 16       * GNU Lesser General Public License as published by
 17         the Free Software Foundation, either version 3 of the License, or
 18         (at your option) any later version
 19       OR
 20       * MIT License: https://github.com/jsxgraph/jsxgraph/blob/master/LICENSE.MIT
 21 
 22     JSXGraph is distributed in the hope that it will be useful,
 23     but WITHOUT ANY WARRANTY; without even the implied warranty of
 24     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 25     GNU Lesser General Public License for more details.
 26 
 27     You should have received a copy of the GNU Lesser General Public License and
 28     the MIT License along with JSXGraph. If not, see <http://www.gnu.org/licenses/>
 29     and <http://opensource.org/licenses/MIT/>.
 30  */
 31 
 32 
 33 /*global JXG: true, document:true, jQuery:true, define: true, window: true*/
 34 /*jslint nomen: true, plusplus: true*/
 35 
 36 /* depends:
 37  jxg
 38  utils/env
 39  utils/type
 40  base/board
 41  reader/file
 42  options
 43  renderer/svg
 44  renderer/vml
 45  renderer/canvas
 46  renderer/no
 47  */
 48 
 49 /**
 50  * @fileoverview The JSXGraph object is defined in this file. JXG.JSXGraph controls all boards.
 51  * It has methods to create, save, load and free boards. Additionally some helper functions are
 52  * defined in this file directly in the JXG namespace.
 53  * @version 0.99
 54  */
 55 
 56 define([
 57     'jxg', 'utils/env', 'utils/type', 'base/board', 'reader/file', 'options',
 58     'renderer/svg', 'renderer/vml', 'renderer/canvas', 'renderer/no'
 59 ], function (JXG, Env, Type, Board, FileReader, Options, SVGRenderer, VMLRenderer, CanvasRenderer, NoRenderer) {
 60 
 61     "use strict";
 62 
 63     /**
 64      * Constructs a new JSXGraph singleton object.
 65      * @class The JXG.JSXGraph singleton stores all properties required
 66      * to load, save, create and free a board.
 67      */
 68     JXG.JSXGraph = {
 69         /**
 70          * Stores the renderer that is used to draw the boards.
 71          * @type String
 72          */
 73         rendererType: (function () {
 74             Options.board.renderer = 'no';
 75 
 76             if (Env.supportsVML()) {
 77                 Options.board.renderer = 'vml';
 78                 // Ok, this is some real magic going on here. IE/VML always was so
 79                 // terribly slow, except in one place: Examples placed in a moodle course
 80                 // was almost as fast as in other browsers. So i grabbed all the css and
 81                 // lib scripts from our moodle, added them to a jsxgraph example and it
 82                 // worked. next step was to strip all the css/lib code which didn't affect
 83                 // the VML update speed. The following five lines are what was left after
 84                 // the last step and yes - it basically does nothing but reads two
 85                 // properties of document.body on every mouse move. why? we don't know. if
 86                 // you know, please let us know.
 87                 //
 88                 // If we want to use the strict mode we have to refactor this a little bit. Let's
 89                 // hope the magic isn't gone now. Anywho... it's only useful in old versions of IE
 90                 // which should not be used anymore.
 91                 document.onmousemove = function () {
 92                     var t;
 93 
 94                     if (document.body) {
 95                         t = document.body.scrollLeft;
 96                         t += document.body.scrollTop;
 97                     }
 98 
 99                     return t;
100                 };
101             }
102 
103             if (Env.supportsCanvas()) {
104                 Options.board.renderer = 'canvas';
105             }
106 
107             if (Env.supportsSVG()) {
108                 Options.board.renderer = 'svg';
109             }
110 
111             // we are inside node
112             if (Env.isNode() && Env.supportsCanvas()) {
113                 Options.board.renderer = 'canvas';
114             }
115 
116             if (Env.isNode() || Options.renderer === 'no') {
117                 Options.text.display = 'internal';
118                 Options.infobox.display = 'internal';
119             }
120 
121             return Options.board.renderer;
122         }()),
123 
124         initRenderer: function (box, dim, doc, attrRenderer) {
125             var boxid, renderer;
126 
127             // Former version:
128             // doc = doc || document
129             if ((!Type.exists(doc) || doc === false) && typeof document === 'object') {
130                 doc = document;
131             }
132 
133             if (typeof doc === 'object' && box !== null) {
134                 boxid = doc.getElementById(box);
135 
136                 // Remove everything from the container before initializing the renderer and the board
137                 while (boxid.firstChild) {
138                     boxid.removeChild(boxid.firstChild);
139                 }
140             } else {
141                 boxid = box;
142             }
143 
144             // create the renderer
145             if (attrRenderer === 'svg') {
146                 renderer = new SVGRenderer(boxid, dim);
147             } else if (attrRenderer === 'vml') {
148                 renderer = new VMLRenderer(boxid);
149             } else if (attrRenderer === 'canvas') {
150                 renderer = new CanvasRenderer(boxid, dim);
151             } else {
152                 renderer = new NoRenderer();
153             }
154 
155             return renderer;
156         },
157 
158         /**
159          * Initialise a new board.
160          * @param {String} box Html-ID to the Html-element in which the board is painted.
161          * @param {Object} attributes An object that sets some of the board properties. Most of these properties can be set via JXG.Options.
162          * @param {Array} [attributes.boundingbox=[-5, 5, 5, -5]] An array containing four numbers describing the left, top, right and bottom boundary of the board in user coordinates
163          * @param {Boolean} [attributes.keepaspectratio=false] If <tt>true</tt>, the bounding box is adjusted to the same aspect ratio as the aspect ratio of the div containing the board.
164          * @param {Boolean} [attributes.showCopyright=false] Show the copyright string in the top left corner.
165          * @param {Boolean} [attributes.showNavigation=false] Show the navigation buttons in the bottom right corner.
166          * @param {Object} [attributes.zoom] Allow the user to zoom with the mouse wheel or the two-fingers-zoom gesture.
167          * @param {Object} [attributes.pan] Allow the user to pan with shift+drag mouse or two-fingers-pan gesture.
168          * @param {Boolean} [attributes.axis=false] If set to true, show the axis. Can also be set to an object that is given to both axes as an attribute object.
169          * @param {Boolean|Object} [attributes.grid] If set to true, shows the grid. Can also bet set to an object that is given to the grid as its attribute object.
170          * @param {Boolean} [attributes.registerEvents=true] Register mouse / touch events.
171          * @returns {JXG.Board} Reference to the created board.
172          */
173         initBoard: function (box, attributes) {
174             var originX, originY, unitX, unitY,
175                 renderer,
176                 w, h, dimensions,
177                 bbox, attr, axattr, axattr_x, axattr_y,
178                 defaultaxesattr,
179                 selectionattr,
180                 board;
181 
182             attributes = attributes || {};
183 
184             // merge attributes
185             attr = Type.copyAttributes(attributes, Options, 'board');
186             attr.zoom = Type.copyAttributes(attr, Options, 'board', 'zoom');
187             attr.pan = Type.copyAttributes(attr, Options, 'board', 'pan');
188             attr.selection = Type.copyAttributes(attr, Options, 'board', 'selection');
189             attr.navbar = Type.copyAttributes(attr.navbar, Options, 'navbar');
190 
191             dimensions = Env.getDimensions(box, attr.document);
192 
193             if (attr.unitx || attr.unity) {
194                 originX = Type.def(attr.originx, 150);
195                 originY = Type.def(attr.originy, 150);
196                 unitX = Type.def(attr.unitx, 50);
197                 unitY = Type.def(attr.unity, 50);
198             } else {
199                 bbox = attr.boundingbox;
200                 w = parseInt(dimensions.width, 10);
201                 h = parseInt(dimensions.height, 10);
202 
203                 if (Type.exists(bbox) && attr.keepaspectratio) {
204                     /*
205                      * If the boundingbox attribute is given and the ratio of height and width of the
206                      * sides defined by the bounding box and the ratio of the dimensions of the div tag
207                      * which contains the board do not coincide, then the smaller side is chosen.
208                      */
209                     unitX = w / (bbox[2] - bbox[0]);
210                     unitY = h / (bbox[1] - bbox[3]);
211 
212                     if (Math.abs(unitX) < Math.abs(unitY)) {
213                         unitY = Math.abs(unitX) * unitY / Math.abs(unitY);
214                     } else {
215                         unitX = Math.abs(unitY) * unitX / Math.abs(unitX);
216                     }
217                 } else {
218                     unitX = w / (bbox[2] - bbox[0]);
219                     unitY = h / (bbox[1] - bbox[3]);
220                 }
221                 originX = -unitX * bbox[0];
222                 originY = unitY * bbox[1];
223             }
224 
225             renderer = this.initRenderer(box, dimensions, attr.document, attr.renderer);
226 
227             // create the board
228             board = new Board(box, renderer, attr.id, [originX, originY],
229                         attr.zoomfactor * attr.zoomx,
230                         attr.zoomfactor * attr.zoomy,
231                         unitX, unitY,
232                         dimensions.width, dimensions.height,
233                         attr);
234 
235             JXG.boards[board.id] = board;
236 
237             board.keepaspectratio = attr.keepaspectratio;
238             board.resizeContainer(dimensions.width, dimensions.height, true, true);
239 
240             // create elements like axes, grid, navigation, ...
241             board.suspendUpdate();
242             board.initInfobox();
243 
244             if (attr.axis) {
245                 axattr = typeof attr.axis === 'object' ? attr.axis : {};
246 
247                 // The defaultAxes attributes are overwritten by user supplied axis object.
248                 axattr_x = Type.deepCopy(Options.board.defaultAxes.x, axattr);
249                 axattr_y = Type.deepCopy(Options.board.defaultAxes.y, axattr);
250                 // The user supplied defaultAxes attributes are merged in.
251                 if (attr.defaultaxes.x) {
252                     axattr_x = Type.deepCopy(axattr_x, attr.defaultaxes.x);
253                 }
254                 if (attr.defaultaxes.y) {
255                     axattr_y = Type.deepCopy(axattr_y, attr.defaultaxes.y);
256                 }
257 
258                 board.defaultAxes = {};
259                 board.defaultAxes.x = board.create('axis', [[0, 0], [1, 0]], axattr_x);
260                 board.defaultAxes.y = board.create('axis', [[0, 0], [0, 1]], axattr_y);
261             }
262 
263             if (attr.grid) {
264                 board.create('grid', [], (typeof attr.grid === 'object' ? attr.grid : {}));
265             }
266 
267             board._createSelectionPolygon(attr);
268             /*
269             selectionattr = Type.copyAttributes(attr, Options, 'board', 'selection');
270             if (selectionattr.enabled === true) {
271                 board.selectionPolygon = board.create('polygon', [[0, 0], [0, 0], [0, 0], [0, 0]], selectionattr);
272             }
273             */
274 
275             board.renderer.drawZoomBar(board, attr.navbar);
276             board.unsuspendUpdate();
277 
278             return board;
279         },
280 
281         /**
282          * Load a board from a file containing a construction made with either GEONExT,
283          * Intergeo, Geogebra, or Cinderella.
284          * @param {String} box HTML-ID to the HTML-element in which the board is painted.
285          * @param {String} file base64 encoded string.
286          * @param {String} format containing the file format: 'Geonext' or 'Intergeo'.
287          * @param {Object} [attributes]
288          * @returns {JXG.Board} Reference to the created board.
289          * @see JXG.FileReader
290          * @see JXG.GeonextReader
291          * @see JXG.GeogebraReader
292          * @see JXG.IntergeoReader
293          * @see JXG.CinderellaReader
294          */
295         loadBoardFromFile: function (box, file, format, attributes, callback) {
296             var attr, renderer, board, dimensions,
297                 selectionattr;
298 
299             attributes = attributes || {};
300 
301             // merge attributes
302             attr = Type.copyAttributes(attributes, Options, 'board');
303             attr.zoom = Type.copyAttributes(attributes, Options, 'board', 'zoom');
304             attr.pan = Type.copyAttributes(attributes, Options, 'board', 'pan');
305             attr.selection = Type.copyAttributes(attr, Options, 'board', 'selection');
306             attr.navbar = Type.copyAttributes(attr.navbar, Options, 'navbar');
307 
308             dimensions = Env.getDimensions(box, attr.document);
309             renderer = this.initRenderer(box, dimensions, attr.document);
310 
311             /* User default parameters, in parse* the values in the gxt files are submitted to board */
312             board = new Board(box, renderer, '', [150, 150], 1, 1, 50, 50, dimensions.width, dimensions.height, attr);
313             board.initInfobox();
314             board.resizeContainer(dimensions.width, dimensions.height, true, true);
315 
316             FileReader.parseFileContent(file, board, format, true, callback);
317 
318             selectionattr = Type.copyAttributes(attr, Options, 'board', 'selection');
319 	        board.selectionPolygon = board.create('polygon', [[0, 0], [0, 0], [0, 0], [0, 0]], selectionattr);
320 
321             board.renderer.drawZoomBar(board, attr.navbar);
322             JXG.boards[board.id] = board;
323 
324             return board;
325         },
326 
327         /**
328          * Load a board from a base64 encoded string containing a construction made with either GEONExT,
329          * Intergeo, Geogebra, or Cinderella.
330          * @param {String} box HTML-ID to the HTML-element in which the board is painted.
331          * @param {String} string base64 encoded string.
332          * @param {String} format containing the file format: 'Geonext' or 'Intergeo'.
333          * @param {Object} [attributes]
334          * @returns {JXG.Board} Reference to the created board.
335          * @see JXG.FileReader
336          * @see JXG.GeonextReader
337          * @see JXG.GeogebraReader
338          * @see JXG.IntergeoReader
339          * @see JXG.CinderellaReader
340          */
341         loadBoardFromString: function (box, string, format, attributes, callback) {
342             var attr, renderer, dimensions, board,
343                 selectionattr;
344 
345             attributes = attributes || {};
346 
347             // merge attributes
348             attr = Type.copyAttributes(attributes, Options, 'board');
349             attr.zoom = Type.copyAttributes(attributes, Options, 'board', 'zoom');
350             attr.pan = Type.copyAttributes(attributes, Options, 'board', 'pan');
351             attr.selection = Type.copyAttributes(attr, Options, 'board', 'selection');
352             attr.navbar = Type.copyAttributes(attr.navbar, Options, 'navbar');
353 
354             dimensions = Env.getDimensions(box, attr.document);
355             renderer = this.initRenderer(box, dimensions, attr.document);
356 
357             /* User default parameters, in parse* the values in the gxt files are submitted to board */
358             board = new Board(box, renderer, '', [150, 150], 1.0, 1.0, 50, 50, dimensions.width, dimensions.height, attr);
359             board.initInfobox();
360             board.resizeContainer(dimensions.width, dimensions.height, true, true);
361 
362             FileReader.parseString(string, board, format, true, callback);
363 
364             selectionattr = Type.copyAttributes(attr, Options, 'board', 'selection');
365 	        board.selectionPolygon = board.create('polygon', [[0, 0], [0, 0], [0, 0], [0, 0]], selectionattr);
366 
367             board.renderer.drawZoomBar(board, attr.navbar);
368             JXG.boards[board.id] = board;
369 
370             return board;
371         },
372 
373         /**
374          * Delete a board and all its contents.
375          * @param {JXG.Board,String} board HTML-ID to the DOM-element in which the board is drawn.
376          */
377         freeBoard: function (board) {
378             var el;
379 
380             if (typeof board === 'string') {
381                 board = JXG.boards[board];
382             }
383 
384             board.removeEventHandlers();
385             board.suspendUpdate();
386 
387             // Remove all objects from the board.
388             for (el in board.objects) {
389                 if (board.objects.hasOwnProperty(el)) {
390                     board.objects[el].remove();
391                 }
392             }
393 
394             // Remove all the other things, left on the board, XHTML save
395             while (board.containerObj.firstChild) {
396                 board.containerObj.removeChild(board.containerObj.firstChild);
397             }
398 
399             // Tell the browser the objects aren't needed anymore
400             for (el in board.objects) {
401                 if (board.objects.hasOwnProperty(el)) {
402                     delete board.objects[el];
403                 }
404             }
405 
406             // Free the renderer and the algebra object
407             delete board.renderer;
408 
409             // clear the creator cache
410             board.jc.creator.clearCache();
411             delete board.jc;
412 
413             // Finally remove the board itself from the boards array
414             delete JXG.boards[board.id];
415         },
416 
417         /**
418          * @deprecated Use JXG#registerElement
419          * @param element
420          * @param creator
421          */
422         registerElement: function (element, creator) {
423             JXG.deprecated('JXG.JSXGraph.registerElement()', 'JXG.registerElement()');
424             JXG.registerElement(element, creator);
425         }
426     };
427 
428     // JessieScript/JessieCode startup: Search for script tags of type text/jessiescript and interprete them.
429     if (Env.isBrowser && typeof window === 'object' && typeof document === 'object') {
430         Env.addEvent(window, 'load', function () {
431             var type, i, j, div, id, board, width, height, bbox, axis, grid, code,
432                 scripts = document.getElementsByTagName('script'),
433                 init = function (code, type, bbox) {
434                     var board = JXG.JSXGraph.initBoard(id, {boundingbox: bbox, keepaspectratio: true, grid: grid, axis: axis, showReload: true});
435 
436                     if (type.toLowerCase().indexOf('script') > -1) {
437                         board.construct(code);
438                     } else {
439                         try {
440                             board.jc.parse(code);
441                         } catch (e2) {
442                             JXG.debug(e2);
443                         }
444                     }
445 
446                     return board;
447                 },
448                 makeReload = function (board, code, type, bbox) {
449                     return function () {
450                         var newBoard;
451 
452                         JXG.JSXGraph.freeBoard(board);
453                         newBoard = init(code, type, bbox);
454                         newBoard.reload = makeReload(newBoard, code, type, bbox);
455                     };
456                 };
457 
458             for (i = 0; i < scripts.length; i++) {
459                 type = scripts[i].getAttribute('type', false);
460 
461                 if (Type.exists(type) &&
462                     (type.toLowerCase() === 'text/jessiescript' || type.toLowerCase() === 'jessiescript' ||
463                      type.toLowerCase() === 'text/jessiecode' || type.toLowerCase() === 'jessiecode')) {
464                     width = scripts[i].getAttribute('width', false) || '500px';
465                     height = scripts[i].getAttribute('height', false) || '500px';
466                     bbox = scripts[i].getAttribute('boundingbox', false) || '-5, 5, 5, -5';
467                     id = scripts[i].getAttribute('container', false);
468 
469                     bbox = bbox.split(',');
470                     if (bbox.length !== 4) {
471                         bbox = [-5, 5, 5, -5];
472                     } else {
473                         for (j = 0; j < bbox.length; j++) {
474                             bbox[j] = parseFloat(bbox[j]);
475                         }
476                     }
477                     axis = Type.str2Bool(scripts[i].getAttribute('axis', false) || 'false');
478                     grid = Type.str2Bool(scripts[i].getAttribute('grid', false) || 'false');
479 
480                     if (!Type.exists(id)) {
481                         id = 'jessiescript_autgen_jxg_' + i;
482                         div = document.createElement('div');
483                         div.setAttribute('id', id);
484                         div.setAttribute('style', 'width:' + width + '; height:' + height + '; float:left');
485                         div.setAttribute('class', 'jxgbox');
486                         try {
487                             document.body.insertBefore(div, scripts[i]);
488                         } catch (e) {
489                             // there's probably jquery involved...
490                             if (typeof jQuery === 'object') {
491                                 jQuery(div).insertBefore(scripts[i]);
492                             }
493                         }
494                     } else {
495                         div = document.getElementById(id);
496                     }
497 
498                     if (document.getElementById(id)) {
499                         code = scripts[i].innerHTML;
500                         code = code.replace(/<!\[CDATA\[/g, '').replace(/\]\]>/g, '');
501                         scripts[i].innerHTML = code;
502 
503                         board = init(code, type, bbox);
504                         board.reload = makeReload(board, code, type, bbox);
505                     } else {
506                         JXG.debug('JSXGraph: Apparently the div injection failed. Can\'t create a board, sorry.');
507                     }
508                 }
509             }
510         }, window);
511     }
512 
513     return JXG.JSXGraph;
514 });
515