1 (function () {
  2   
  3   if (fabric.Element) {
  4     fabric.warn('fabric.Element is already defined.');
  5     return;
  6   }
  7   
  8   var global = this,
  9       window = global.window,
 10       document = window.document,
 11       
 12       // aliases for faster resolution
 13       extend = fabric.util.object.extend,
 14       capitalize = fabric.util.string.capitalize,
 15       camelize = fabric.util.string.camelize,
 16       fireEvent = fabric.util.fireEvent,
 17       getPointer = fabric.util.getPointer,
 18       getElementOffset = fabric.util.getElementOffset,
 19       removeFromArray = fabric.util.removeFromArray,
 20       addListener = fabric.util.addListener,
 21       removeListener = fabric.util.removeListener,
 22       
 23       utilMin = fabric.util.array.min,
 24       utilMax = fabric.util.array.max,
 25       
 26       sqrt = Math.sqrt,
 27       pow = Math.pow,
 28       atan2 = Math.atan2,
 29       abs = Math.abs,
 30       min = Math.min,
 31       max = Math.max,
 32       
 33       CANVAS_INIT_ERROR = new Error('Could not initialize `canvas` element'),
 34       FX_DURATION = 500,
 35       STROKE_OFFSET = 0.5,
 36       FX_TRANSITION = 'decel',
 37       
 38       cursorMap = {
 39         'tr': 'ne-resize',
 40         'br': 'se-resize',
 41         'bl': 'sw-resize',
 42         'tl': 'nw-resize',
 43         'ml': 'w-resize',
 44         'mt': 'n-resize',
 45         'mr': 'e-resize',
 46         'mb': 's-resize'
 47       };
 48   
 49   /**
 50    * @class fabric.Element
 51    * @constructor
 52    * @param {HTMLElement | String} el <canvas> element to initialize instance on
 53    * @param {Object} [options] Options object
 54    */
 55   fabric.Element = function (el, options) {
 56     
 57     /**
 58      * The object literal containing mouse position if clicked in an empty area (no image)
 59      * @property _groupSelector
 60      * @type object
 61      */
 62     this._groupSelector = null;
 63 
 64     /**
 65      * The array literal containing all objects on canvas
 66      * @property _objects
 67      * @type array
 68      */
 69     this._objects = [];
 70 
 71     /**
 72      * The element that references the canvas interface implementation
 73      * @property _context
 74      * @type object
 75      */
 76     this._context = null;
 77 
 78     /**
 79      * The main element that contains the canvas
 80      * @property _element
 81      * @type object
 82      */
 83     this._element = null;
 84 
 85     /**
 86      * The object literal containing the current x,y params of the transformation
 87      * @property _currentTransform
 88      * @type object
 89      */
 90     this._currentTransform = null;
 91     
 92     /**
 93      * References instance of fabric.Group - when multiple objects are selected
 94      * @property _activeGroup
 95      * @type object
 96      */
 97     this._activeGroup = null;
 98     
 99     /**
100      * X coordinates of a path, captured during free drawing
101      */
102     this._freeDrawingXPoints = [ ];
103     
104     /**
105      * Y coordinates of a path, captured during free drawing
106      */
107     this._freeDrawingYPoints = [ ];
108     
109      /**
110       * An object containing config parameters
111       * @property _config
112       * @type object
113       */
114     this._config = { 
115       width: 300, 
116       height: 150 
117     };
118     
119     config = config || { };
120     
121     this._initElement(el);
122     this._initConfig(config);
123     
124     if (config.overlayImage) {
125       this.setOverlayImage(config.overlayImage);
126     }
127     
128     if (config.afterRender) {
129       this.afterRender = config.afterRender;
130     }
131     
132     this._createCanvasBackground();
133     this._createCanvasContainer();
134     this._initEvents();
135     this.calcOffset();
136   };
137   
138   extend(fabric.Element.prototype, /** @scope fabric.Element.prototype */ {
139     
140     /**
141      * @property
142      * @type String
143      */
144     selectionColor:         'rgba(100, 100, 255, 0.3)', // blue
145     
146     /**
147      * @property
148      * @type String
149      */
150     selectionBorderColor:   'rgba(255, 255, 255, 0.3)',
151     
152     /**
153      * @property
154      * @type String
155      */
156     freeDrawingColor:       'rgb(0, 0, 0)',
157     
158     /**
159      * @property
160      * @type String
161      */
162     backgroundColor:        'rgba(0, 0, 0, 0)',
163     
164     /**
165      * @property
166      * @type Number
167      */
168     freeDrawingLineWidth:   1,
169     
170     /**
171      * @property
172      * @type Number
173      */
174     selectionLineWidth:     1,
175     
176     /**
177      * @property
178      * @type Boolean
179      */
180     includeDefaultValues:   true,
181     
182     /**
183      * @property
184      * @type Boolean
185      */
186     shouldCacheImages:      false,
187     
188     /**
189      * @constant
190      * @type Number
191      */
192     CANVAS_WIDTH:           600,
193     
194     /**
195      * @constant
196      * @type Number
197      */
198     CANVAS_HEIGHT:          600,
199     
200     /**
201      * Callback; invoked right before object is about to be scaled/rotated
202      * @method onBeforeScaleRotate
203      * @param {fabric.Object} target Object that's about to be scaled/rotated
204      */
205     onBeforeScaleRotate: function (target) {
206       /* NOOP */
207     },
208     
209     /**
210      * Callback; invoked on every redraw of canvas and is being passed a number indicating current fps
211      * @method onFpsUpdate
212      * @param {Number} fps
213      */
214     onFpsUpdate: function(fps) {
215       /* NOOP */
216     },
217     
218     /**
219      * Calculates canvas element offset relative to the document
220      * This method is also attached as "resize" event handler of window
221      * @method calcOffset
222      * @return {fabric.Element} instance
223      * @chainable
224      */
225     calcOffset: function () {
226       this._offset = getElementOffset(this.getElement());
227       return this;
228     },
229     
230     /**
231      * Sets overlay image for this canvas
232      * @method setOverlayImage
233      * @param {String} url url of an image to set background to
234      * @param {Function} callback callback to invoke when image is loaded and set as an overlay one
235      * @return {fabric.Element} thisArg
236      * @chainable
237      */
238     setOverlayImage: function (url, callback) { // TODO (kangax): test callback
239       if (url) {
240         var _this = this, img = new Image();
241         
242         /** @ignore */
243         img.onload = function () { 
244           _this.overlayImage = img;
245           if (callback) {
246             callback();
247           }
248           img = img.onload = null;
249         };
250         img.src = url;
251       }
252       return this;
253     },
254     
255     /**
256      * Canvas class' initialization method; Automatically called by constructor;
257      * Sets up all DOM references for pre-existing markup and creates required markup if it's not yet created.
258      * already present.
259      * @method _initElement
260      * @param {HTMLElement|String} canvasEl Canvas element
261      * @throws {CANVAS_INIT_ERROR} If canvas can not be initialized
262      */
263     _initElement: function (canvasEl) {
264       var el = fabric.util.getById(canvasEl);
265       this._element = el || document.createElement('canvas');
266       
267       if (typeof this._element.getContext === 'undefined' && typeof G_vmlCanvasManager !== 'undefined') {
268         G_vmlCanvasManager.initElement(this._element);
269       }
270       if (typeof this._element.getContext === 'undefined') {
271         throw CANVAS_INIT_ERROR;
272       }
273       if (!(this.contextTop = this._element.getContext('2d'))) {
274         throw CANVAS_INIT_ERROR;
275       }
276       
277       var width = this._element.width || 0,
278           height = this._element.height || 0;
279       
280       this._initWrapperElement(width, height);
281       this._setElementStyle(width, height);
282     },
283     
284     /**
285      * @private
286      * @method _initWrapperElement
287      * @param {Number} width
288      * @param {Number} height
289      */
290     _initWrapperElement: function (width, height) {
291       var wrapper = fabric.util.wrapElement(this.getElement(), 'div', { 'class': 'canvas_container' });
292       fabric.util.setStyle(wrapper, {
293         width: width + 'px',
294         height: height + 'px'
295       });
296       fabric.util.makeElementUnselectable(wrapper);
297       this.wrapper = wrapper;
298     },
299     
300     /**
301      * @private
302      * @method _setElementStyle
303      * @param {Number} width
304      * @param {Number} height
305      */
306     _setElementStyle: function (width, height) {
307       fabric.util.setStyle(this.getElement(), {
308         position: 'absolute',
309         width: width + 'px',
310         height: height + 'px',
311         left: 0,
312         top: 0
313       });
314     },
315 
316     /**
317      * For now, use an object literal without methods to store the config params
318      * @method _initConfig
319      * @param config {Object} userConfig The configuration Object literal 
320      * containing the configuration that should be set for this module;
321      * See configuration documentation for more details.
322      */
323     _initConfig: function (config) {
324       extend(this._config, config || { });
325       
326       this._config.width = parseInt(this._element.width, 10) || 0;
327       this._config.height = parseInt(this._element.height, 10) || 0;
328 
329       this._element.style.width = this._config.width + 'px';
330       this._element.style.height = this._config.height + 'px';
331     },
332 
333     /**
334      * Adds mouse listeners to  canvas
335      * @method _initEvents
336      * @private
337      * See configuration documentation for more details.
338      */
339     _initEvents: function () {
340       
341       var _this = this;
342       
343       this._onMouseDown = function (e) { _this.__onMouseDown(e); };
344       this._onMouseUp = function (e) { _this.__onMouseUp(e); };
345       this._onMouseMove = function (e) { _this.__onMouseMove(e); };
346       this._onResize = function (e) { _this.calcOffset() };
347       
348       addListener(this._element, 'mousedown', this._onMouseDown);
349       addListener(document, 'mousemove', this._onMouseMove);
350       addListener(document, 'mouseup', this._onMouseUp);
351       addListener(window, 'resize', this._onResize);
352     },
353     
354     /**
355      * Creates canvas elements
356      * @method _createCanvasElement
357      * @private
358      */
359     _createCanvasElement: function (className) {
360         
361       var element = document.createElement('canvas');
362       if (!element) {
363         return;
364       }
365       
366       element.className = className;
367       var oContainer = this._element.parentNode.insertBefore(element, this._element);
368       
369       oContainer.width = this.getWidth();
370       oContainer.height = this.getHeight();
371       oContainer.style.width = this.getWidth() + 'px';
372       oContainer.style.height = this.getHeight() + 'px';
373       oContainer.style.position = 'absolute';
374       oContainer.style.left = 0;
375       oContainer.style.top = 0;
376       
377       if (typeof element.getContext === 'undefined' && typeof G_vmlCanvasManager !== 'undefined') {
378         // try augmenting element with excanvas' G_vmlCanvasManager
379         G_vmlCanvasManager.initElement(element);
380       }
381       if (typeof element.getContext === 'undefined') {
382         // if that didn't work, throw error
383         throw CANVAS_INIT_ERROR;
384       }
385       fabric.util.makeElementUnselectable(oContainer);
386       return oContainer;
387     },
388 
389     /**
390      * Creates a secondary canvas to contain all the images are not being translated/rotated/scaled
391      * @method _createCanvasContainer
392      */
393     _createCanvasContainer: function () {
394       var canvas = this._createCanvasElement('canvas-container');
395       this.contextContainerEl = canvas;
396       this.contextContainer = canvas.getContext('2d');
397     },
398 
399     /**
400      * Creates a "background" canvas
401      * @method _createCanvasBackground
402      */
403     _createCanvasBackground: function () {
404       var canvas = this._createCanvasElement('canvas-container');
405       this._contextBackgroundEl = canvas;
406       this._contextBackground = canvas.getContext('2d');
407     },
408     
409     /**
410      * Returns canvas width
411      * @method getWidth
412      * @return {Number}
413      */
414     getWidth: function () {
415       return this._config.width;
416     },
417     
418     /**
419      * Returns canvas height
420      * @method getHeight
421      * @return {Number}
422      */
423     getHeight: function () {
424       return this._config.height;
425     },
426     
427     /**
428      * Sets width of this canvas instance
429      * @method setWidth
430      * @param {Number} width value to set width to
431      * @return {fabric.Element} instance
432      * @chainable true
433      */
434     setWidth: function (value) {
435       return this._setDimension('width', value);
436     },
437     
438     /**
439      * Sets height of this canvas instance
440      * @method setHeight
441      * @param {Number} height value to set height to
442      * @return {fabric.Element} instance
443      * @chainable true
444      */
445     setHeight: function (value) {
446       return this._setDimension('height', value);
447     },
448     
449     /**
450      * Sets dimensions (width, height) of this canvas instance
451      * @method setDimensions
452      * @param {Object} dimensions
453      * @return {fabric.Element} thisArg
454      * @chainable
455      */
456     setDimensions: function(dimensions) {
457       for (var prop in dimensions) {
458         this._setDimension(prop, dimensions[prop]);
459       }
460       return this;
461     },
462     
463     /**
464      * Helper for setting width/height
465      * @private
466      * @method _setDimensions
467      * @param {String} prop property (width|height)
468      * @param {Number} value value to set property to
469      * @return {fabric.Element} instance
470      * @chainable true
471      */
472     _setDimension: function (prop, value) {
473       this.contextContainerEl[prop] = value;
474       this.contextContainerEl.style[prop] = value + 'px';
475       
476       this._contextBackgroundEl[prop] = value;
477       this._contextBackgroundEl.style[prop] = value + 'px';
478       
479       this._element[prop] = value;
480       this._element.style[prop] = value + 'px';
481       
482       // <DIV> container (parent of all <CANVAS> elements)
483       this._element.parentNode.style[prop] = value + 'px';
484       
485       this._config[prop] = value;
486       this.calcOffset();
487       this.renderAll();
488       
489       return this;
490     },
491     
492     /**
493      * Method that defines the actions when mouse is released on canvas.
494      * The method resets the currentTransform parameters, store the image corner
495      * position in the image object and render the canvas on top.
496      * @method __onMouseUp
497      * @param {Event} e Event object fired on mouseup
498      *
499      */
500     __onMouseUp: function (e) {
501       
502       if (this.isDrawingMode && this._isCurrentlyDrawing) {
503         this._finalizeDrawingPath();
504         return;
505       }
506       
507       if (this._currentTransform) {
508         
509         var transform = this._currentTransform,
510             target = transform.target;
511             
512         if (target._scaling) {
513           fireEvent('object:scaled', { target: target });
514           target._scaling = false;
515         }
516         
517         // determine the new coords everytime the image changes its position
518         var i = this._objects.length;
519         while (i--) {
520           this._objects[i].setCoords();
521         }
522         
523         // only fire :modified event if target coordinates were changed during mousedown-mouseup
524         if (target.hasStateChanged()) {
525           target.isMoving = false;
526           fireEvent('object:modified', { target: target });
527         }
528       }
529       
530       this._currentTransform = null;
531       
532       if (this._groupSelector) {
533         // group selection was completed, determine its bounds
534         this._findSelectedObjects(e);
535       }
536       var activeGroup = this.getActiveGroup();
537       if (activeGroup) {
538         if (activeGroup.hasStateChanged() && 
539             activeGroup.containsPoint(this.getPointer(e))) {
540           fireEvent('group:modified', { target: activeGroup });
541         }
542         activeGroup.setObjectsCoords();
543         activeGroup.set('isMoving', false);
544         this._setCursor('default');
545       }
546       
547       // clear selection
548       this._groupSelector = null;
549       this.renderAll();
550       
551       this._setCursorFromEvent(e, target);
552       // fix for FF
553       this._setCursor('');
554       
555       var _this = this;
556       setTimeout(function () {
557         _this._setCursorFromEvent(e, target);
558       }, 50);
559     },
560     
561     _shouldClearSelection: function (e) {
562       var target = this.findTarget(e),
563           activeGroup = this.getActiveGroup();
564       return (
565         !target || (
566           target && 
567           activeGroup && 
568           !activeGroup.contains(target) && 
569           activeGroup !== target && 
570           !e.shiftKey
571         )
572       );
573     },
574 
575     /**
576      * Method that defines the actions when mouse is clic ked on canvas.
577      * The method inits the currentTransform parameters and renders all the
578      * canvas so the current image can be placed on the top canvas and the rest
579      * in on the container one.
580      * @method __onMouseDown
581      * @param e {Event} Event object fired on mousedown
582      *
583      */
584     __onMouseDown: function (e) {
585       
586       if (this.isDrawingMode) {
587         this._prepareForDrawing(e);
588         
589         // capture coordinates immediately; this allows to draw dots (when movement never occurs)
590         this._captureDrawingPath(e);
591         
592         return;
593       }
594       
595       // ignore if some object is being transformed at this moment
596       if (this._currentTransform) return;
597       
598       var target = this.findTarget(e),
599           pointer = this.getPointer(e),
600           activeGroup = this.getActiveGroup(), 
601           corner;
602       
603       if (this._shouldClearSelection(e)) {
604         
605         this._groupSelector = {
606           ex: pointer.x,
607           ey: pointer.y,
608           top: 0,
609           left: 0
610         };
611         
612         this.deactivateAllWithDispatch();
613       }
614       else {
615         // determine if it's a drag or rotate case
616         // rotate and scale will happen at the same time
617         target.saveState();
618         
619         if (corner = target._findTargetCorner(e, this._offset)) {
620           this.onBeforeScaleRotate(target);
621         }
622         
623         this._setupCurrentTransform(e, target);
624         
625         var shouldHandleGroupLogic = e.shiftKey && (activeGroup || this.getActiveObject());
626         if (shouldHandleGroupLogic) {
627           this._handleGroupLogic(e, target);
628         }
629         else {
630           if (target !== this.getActiveGroup()) {
631             this.deactivateAll();
632           }
633           this.setActiveObject(target);
634         }
635       }
636       // we must renderAll so that active image is placed on the top canvas
637       this.renderAll();
638     },
639     
640     /**
641      * Returns <canvas> element corresponding to this instance
642      * @method getElement
643      * @return {HTMLCanvasElement}
644      */
645     getElement: function () {
646       return this._element;
647     },
648     
649     /**
650      * Deactivates all objects and dispatches appropriate events
651      * @method deactivateAllWithDispatch
652      * @return {fabric.Element} thisArg
653      */
654     deactivateAllWithDispatch: function () {
655       var activeGroup = this.getActiveGroup();
656       if (activeGroup) {
657         fireEvent('before:group:destroyed', {
658           target: activeGroup
659         });
660       }
661       this.deactivateAll();
662       if (activeGroup) {
663         fireEvent('after:group:destroyed');
664       }
665       fireEvent('selection:cleared');
666       return this;
667     },
668     
669     /**
670      * @private
671      * @method _setupCurrentTransform
672      */
673     _setupCurrentTransform: function (e, target) {
674       var action = 'drag', 
675           corner,
676           pointer = getPointer(e);
677       
678       if (corner = target._findTargetCorner(e, this._offset)) {
679         action = (corner === 'ml' || corner === 'mr') 
680           ? 'scaleX' 
681           : (corner === 'mt' || corner === 'mb') 
682             ? 'scaleY' 
683             : 'rotate';
684       }
685       
686       this._currentTransform = {
687         target: target,
688         action: action,
689         scaleX: target.scaleX,
690         scaleY: target.scaleY,
691         offsetX: pointer.x - target.left,
692         offsetY: pointer.y - target.top,
693         ex: pointer.x,
694         ey: pointer.y,
695         left: target.left, 
696         top: target.top,
697         theta: target.theta,
698         width: target.width * target.scaleX
699       };
700       
701       this._currentTransform.original = {
702         left: target.left,
703         top: target.top
704       };
705     },
706     
707     _handleGroupLogic: function (e, target) {
708       if (target.isType('group')) {
709         // if it's a group, find target again, this time skipping group
710         target = this.findTarget(e, true);
711         // if even object is not found, bail out
712         if (!target || target.isType('group')) {
713           return;
714         }
715       }
716       var activeGroup = this.getActiveGroup();
717       if (activeGroup) {
718         if (activeGroup.contains(target)) {
719           activeGroup.remove(target);
720           target.setActive(false);
721           if (activeGroup.size() === 1) {
722             // remove group alltogether if after removal it only contains 1 object
723             this.removeActiveGroup();
724           }
725         }
726         else {
727           activeGroup.add(target);
728         }
729         fireEvent('group:selected', { target: activeGroup });
730         activeGroup.setActive(true);
731       }
732       else {
733         // group does not exist
734         if (this._activeObject) {
735           // only if there's an active object
736           if (target !== this._activeObject) {
737             // and that object is not the actual target
738             var group = new fabric.Group([ this._activeObject,target ]);
739             this.setActiveGroup(group);
740             activeGroup = this.getActiveGroup();
741           }
742         }
743         // activate target object in any case
744         target.setActive(true);
745       }
746       
747       if (activeGroup) {
748         activeGroup.saveCoords();
749       }
750     },
751     
752     /**
753      * @private
754      * @method _prepareForDrawing
755      */
756     _prepareForDrawing: function(e) {
757       
758       this._isCurrentlyDrawing = true;
759       
760       this.removeActiveObject().renderAll();
761       
762       var pointer = this.getPointer(e);
763       
764       this._freeDrawingXPoints.length = this._freeDrawingYPoints.length = 0;
765       
766       this._freeDrawingXPoints.push(pointer.x);
767       this._freeDrawingYPoints.push(pointer.y);
768       
769       this.contextTop.beginPath();
770       this.contextTop.moveTo(pointer.x, pointer.y);
771       this.contextTop.strokeStyle = this.freeDrawingColor;
772       this.contextTop.lineWidth = this.freeDrawingLineWidth;
773       this.contextTop.lineCap = this.contextTop.lineJoin = 'round';
774     },
775     
776     /**
777      * @private
778      * @method _captureDrawingPath
779      */
780     _captureDrawingPath: function(e) {
781       var pointer = this.getPointer(e);
782       
783       this._freeDrawingXPoints.push(pointer.x);
784       this._freeDrawingYPoints.push(pointer.y);
785       
786       this.contextTop.lineTo(pointer.x, pointer.y);
787       this.contextTop.stroke();
788     },
789     
790     /**
791      * @private
792      * @method _finalizeDrawingPath
793      */
794     _finalizeDrawingPath: function() {
795       
796       this.contextTop.closePath();
797       
798       this._isCurrentlyDrawing = false;
799       
800       var minX = utilMin(this._freeDrawingXPoints),
801           minY = utilMin(this._freeDrawingYPoints),
802           maxX = utilMax(this._freeDrawingXPoints),
803           maxY = utilMax(this._freeDrawingYPoints),
804           ctx = this.contextTop,
805           path = [ ],
806           xPoints = this._freeDrawingXPoints,
807           yPoints = this._freeDrawingYPoints;
808       
809       path.push('M ', xPoints[0] - minX, ' ', yPoints[0] - minY, ' ');
810       
811       for (var i = 1; xPoint = xPoints[i], yPoint = yPoints[i]; i++) {
812         path.push('L ', xPoint - minX, ' ', yPoint - minY, ' ');
813       }
814       
815       // TODO (kangax): maybe remove Path creation from here, to decouple fabric.Element from fabric.Path, 
816       // and instead fire something like "drawing:completed" event with path string
817       
818       var p = new fabric.Path(path.join('')); 
819       p.fill = null;
820       p.stroke = this.freeDrawingColor;
821       p.strokeWidth = this.freeDrawingLineWidth;
822       this.add(p);
823       p.set("left", minX + (maxX - minX) / 2).set("top", minY + (maxY - minY) / 2).setCoords();
824       this.renderAll();
825       fireEvent('path:created', { path: p });
826     },
827 
828    /**
829     * Method that defines the actions when mouse is hovering the canvas.
830     * The currentTransform parameter will definde whether the user is rotating/scaling/translating
831     * an image or neither of them (only hovering). A group selection is also possible and would cancel
832     * all any other type of action.
833     * In case of an image transformation only the top canvas will be rendered.
834     * @method __onMouseMove
835     * @param e {Event} Event object fired on mousemove
836     *
837     */
838     __onMouseMove: function (e) {
839       
840       if (this.isDrawingMode) {
841         if (this._isCurrentlyDrawing) {
842           this._captureDrawingPath(e);
843         }
844         return;
845       }
846       
847       var groupSelector = this._groupSelector;
848       
849       // We initially clicked in an empty area, so we draw a box for multiple selection.
850       if (groupSelector !== null) {
851         var pointer = getPointer(e);
852         groupSelector.left = pointer.x - this._offset.left - groupSelector.ex;
853         groupSelector.top = pointer.y - this._offset.top - groupSelector.ey;
854         this.renderTop();
855       }
856       else if (!this._currentTransform) {
857         
858         // alias style to elimintate unnecessary lookup
859         var style = this._element.style;
860         
861         // Here we are hovering the canvas then we will determine
862         // what part of the pictures we are hovering to change the caret symbol.
863         // We won't do that while dragging or rotating in order to improve the
864         // performance.
865         var target = this.findTarget(e);
866         
867         if (!target) {  
868           // image/text was hovered-out from, we remove its borders
869           for (var i = this._objects.length; i--; ) {
870             if (!this._objects[i].active) {
871               this._objects[i].setActive(false);
872             }
873           }
874           style.cursor = 'default';
875         }
876         else {
877           // set proper cursor 
878           this._setCursorFromEvent(e, target);
879           if (target.isActive()) {
880             // display corners when hovering over an image
881             target.setCornersVisibility && target.setCornersVisibility(true);
882           }
883         }
884       }
885       else {
886         // object is being transformed (scaled/rotated/moved/etc.)
887         var pointer = getPointer(e), 
888             x = pointer.x, 
889             y = pointer.y;
890             
891         this._currentTransform.target.isMoving = true;
892         
893         if (this._currentTransform.action === 'rotate') {  
894           // rotate object only if shift key is not pressed 
895           // and if it is not a group we are transforming
896           
897           if (!e.shiftKey) {
898             this._rotateObject(x, y);
899           }
900           this._scaleObject(x, y);
901         }
902         else if (this._currentTransform.action === 'scaleX') {
903           this._scaleObject(x, y, 'x');
904         }
905         else if (this._currentTransform.action === 'scaleY') {
906           this._scaleObject(x, y, 'y');
907         }
908         else {
909           this._translateObject(x, y);
910         }
911         // only commit here. when we are actually moving the pictures
912         this.renderAll();
913       }
914     },
915 
916     /**
917      * Translates object by "setting" its left/top
918      * @method _translateObject
919      * @param x {Number} pointer's x coordinate
920      * @param y {Number} pointer's y coordinate
921      */
922     _translateObject: function (x, y) {
923       var target = this._currentTransform.target;
924       target.lockHorizontally || target.set('left', x - this._currentTransform.offsetX);
925       target.lockVertically || target.set('top', y - this._currentTransform.offsetY);
926     },
927 
928     /**
929      * Scales object by invoking its scaleX/scaleY methods
930      * @method _scaleObject
931      * @param x {Number} pointer's x coordinate
932      * @param y {Number} pointer's y coordinate
933      * @param by {String} Either 'x' or 'y' - specifies dimension constraint by which to scale an object. 
934      *                    When not provided, an object is scaled by both dimensions equally
935      */ 
936     _scaleObject: function (x, y, by) {
937       var t = this._currentTransform,
938           offset = this._offset,
939           target = t.target;
940       
941       if (target.lockScaling) return;
942       
943       var lastLen = sqrt(pow(t.ey - t.top - offset.top, 2) + pow(t.ex - t.left - offset.left, 2)),
944           curLen = sqrt(pow(y - t.top - offset.top, 2) + pow(x - t.left - offset.left, 2));
945       
946       target._scaling = true;
947       
948       if (!by) {
949         target.set('scaleX', t.scaleX * curLen/lastLen);
950         target.set('scaleY', t.scaleY * curLen/lastLen);
951       }
952       else if (by === 'x') {
953         target.set('scaleX', t.scaleX * curLen/lastLen);
954       }
955       else if (by === 'y') {
956         target.set('scaleY', t.scaleY * curLen/lastLen);
957       }
958     },
959 
960     /**
961      * Rotates object by invoking its rotate method
962      * @method _rotateObject
963      * @param x {Number} pointer's x coordinate
964      * @param y {Number} pointer's y coordinate
965      */ 
966     _rotateObject: function (x, y) {
967       
968       var t = this._currentTransform, 
969           o = this._offset;
970       
971       if (t.target.lockRotation) return;
972       
973       var lastAngle = atan2(t.ey - t.top - o.top, t.ex - t.left - o.left),
974           curAngle = atan2(y - t.top - o.top, x - t.left - o.left);
975           
976       t.target.set('theta', (curAngle - lastAngle) + t.theta);
977     },
978     
979     /**
980      * @method _setCursor
981      */
982     _setCursor: function (value) {
983       this._element.style.cursor = value;
984     },
985     
986     /**
987      * Sets the cursor depending on where the canvas is being hovered.
988      * Note: very buggy in Opera
989      * @method _setCursorFromEvent
990      * @param e {Event} Event object
991      * @param target {Object} Object that the mouse is hovering, if so.
992      */
993     _setCursorFromEvent: function (e, target) {
994       var s = this._element.style;
995       if (!target) {
996         s.cursor = 'default';
997         return false;
998       }
999       else {
1000         var activeGroup = this.getActiveGroup();
1001         // only show proper corner when group selection is not active
1002         var corner = !!target._findTargetCorner 
1003                       && (!activeGroup || !activeGroup.contains(target)) 
1004                       && target._findTargetCorner(e, this._offset);
1005         
1006         if (!corner) {
1007           s.cursor = 'move';
1008         }
1009         else {
1010           if (corner in cursorMap) {
1011             s.cursor = cursorMap[corner];
1012           }
1013           else {
1014             s.cursor = 'default';
1015             return false;
1016           }
1017         }
1018       }
1019       return true;
1020     },
1021     
1022     /**
1023      * Given a context, renders an object on that context 
1024      * @param ctx {Object} context to render object on
1025      * @param object {Object} object to render
1026      * @private
1027      */
1028     _draw: function (ctx, object) {
1029       object && object.render(ctx);
1030     },
1031     
1032     /**
1033      * @method _drawSelection
1034      * @private
1035      */
1036     _drawSelection: function () {
1037       var groupSelector = this._groupSelector,
1038           left = groupSelector.left,
1039           top = groupSelector.top,
1040           aleft = abs(left),
1041           atop = abs(top);
1042 
1043       this.contextTop.fillStyle = this.selectionColor;
1044 
1045       this.contextTop.fillRect(
1046         groupSelector.ex - ((left > 0) ? 0 : -left),
1047         groupSelector.ey - ((top > 0) ? 0 : -top),
1048         aleft, 
1049         atop
1050       );
1051 
1052       this.contextTop.lineWidth = this.selectionLineWidth;
1053       this.contextTop.strokeStyle = this.selectionBorderColor;
1054       
1055       this.contextTop.strokeRect(
1056         groupSelector.ex + STROKE_OFFSET - ((left > 0) ? 0 : aleft), 
1057         groupSelector.ey + STROKE_OFFSET - ((top > 0) ? 0 : atop),
1058         aleft,
1059         atop
1060       );
1061     },
1062     
1063     _findSelectedObjects: function (e) {
1064       var target, 
1065           targetRegion,
1066           group = [ ],
1067           x1 = this._groupSelector.ex,
1068           y1 = this._groupSelector.ey,
1069           x2 = x1 + this._groupSelector.left,
1070           y2 = y1 + this._groupSelector.top,
1071           currentObject,
1072           selectionX1Y1 = new fabric.Point(min(x1, x2), min(y1, y2)),
1073           selectionX2Y2 = new fabric.Point(max(x1, x2), max(y1, y2));
1074       
1075       for (var i = 0, len = this._objects.length; i < len; ++i) {
1076         currentObject = this._objects[i];
1077         
1078         if (currentObject.intersectsWithRect(selectionX1Y1, selectionX2Y2) || 
1079             currentObject.isContainedWithinRect(selectionX1Y1, selectionX2Y2)) {
1080               
1081           currentObject.setActive(true);
1082           group.push(currentObject);
1083         }
1084       }
1085       // do not create group for 1 element only
1086       if (group.length === 1) {
1087         this.setActiveObject(group[0]);
1088         fireEvent('object:selected', {
1089           target: group[0]
1090         });
1091       } 
1092       else if (group.length > 1) {
1093         var group = new fabric.Group(group);
1094         this.setActiveGroup(group);
1095         group.saveCoords();
1096         fireEvent('group:selected', { target: group });
1097       }
1098       this.renderAll();
1099     },
1100     
1101     /**
1102      * Adds objects to canvas, then renders canvas;
1103      * Objects should be instances of (or inherit from) fabric.Object
1104      * @method add
1105      * @return {fabric.Element} thisArg
1106      * @chainable
1107      */
1108     add: function () {
1109       this._objects.push.apply(this._objects, arguments);
1110       this.renderAll();
1111       return this;
1112     },
1113     
1114     /**
1115      * Inserts an object to canvas at specified index and renders canvas. 
1116      * An object should be an instance of (or inherit from) fabric.Object
1117      * @method insertAt
1118      * @param object {Object} Object to insert
1119      * @param index {Number} index to insert object at
1120      * @return {fabric.Element} instance
1121      */
1122     insertAt: function (object, index) {
1123       this._objects.splice(index, 0, object);
1124       this.renderAll();
1125       return this;
1126     },
1127     
1128     /**
1129      * Returns an array of objects this instance has
1130      * @method getObjects
1131      * @return {Array}
1132      */
1133     getObjects: function () {
1134       return this._objects;
1135     },
1136     
1137     /**
1138      * Returns topmost canvas context
1139      * @method getContext
1140      * @return {CanvasRenderingContext2D}
1141      */
1142     getContext: function () {
1143       return this.contextTop;
1144     },
1145     
1146     /**
1147      * Clears specified context of canvas element
1148      * @method clearContext
1149      * @param context {Object} ctx context to clear
1150      * @return {fabric.Element} thisArg
1151      * @chainable
1152      */
1153     clearContext: function(ctx) {
1154       // this sucks, but we can't use `getWidth`/`getHeight` here for perf. reasons
1155       ctx.clearRect(0, 0, this._config.width, this._config.height);
1156       return this;
1157     },
1158     
1159     /**
1160      * Clears all contexts (background, main, top) of an instance
1161      * @method clear
1162      * @return {fabric.Element} thisArg
1163      * @chainable
1164      */
1165     clear: function () {
1166       this._objects.length = 0;
1167       this.clearContext(this.contextTop);
1168       this.clearContext(this.contextContainer);
1169       this.renderAll();
1170       return this;
1171     },
1172 
1173     /**
1174      * Renders both the top canvas and the secondary container canvas.
1175      * @method renderAll
1176      * @param allOnTop {Boolean} optional Whether we want to force all images to be rendered on the top canvas
1177      * @return {fabric.Element} instance
1178      * @chainable
1179      */ 
1180     renderAll: function (allOnTop) {
1181       
1182       // this sucks, but we can't use `getWidth`/`getHeight` here for perf. reasons
1183       var w = this._config.width,
1184           h = this._config.height;
1185           
1186       // when allOnTop is true all images are rendered in the top canvas.
1187       // This is used for actions like toDataUrl that needs to take some actions on a unique canvas.
1188       var containerCanvas = allOnTop ? this.contextTop : this.contextContainer;
1189 
1190       this.clearContext(this.contextTop);
1191 
1192       if (!allOnTop) {
1193         this.clearContext(containerCanvas);
1194       }
1195       containerCanvas.fillStyle = this.backgroundColor;
1196       containerCanvas.fillRect(0, 0, w, h);
1197       
1198       var length = this._objects.length,
1199           activeGroup = this.getActiveGroup();
1200       
1201       var startTime = new Date();
1202       
1203       if (length) {
1204         for (var i = 0; i < length; ++i) {
1205           if (!activeGroup ||
1206               (activeGroup &&
1207               !activeGroup.contains(this._objects[i]))) {
1208             this._draw(containerCanvas, this._objects[i]);
1209           }
1210         }
1211       }
1212       
1213       // delegate rendering to group selection (if one exists)
1214       if (activeGroup) {
1215         this._draw(this.contextTop, activeGroup);
1216       }
1217       
1218       if (this.overlayImage) {
1219         this.contextTop.drawImage(this.overlayImage, 0, 0);
1220       }
1221       
1222       var elapsedTime = new Date() - startTime;
1223       this.onFpsUpdate(~~(1000 / elapsedTime));
1224       
1225       if (this.afterRender) {
1226         this.afterRender();
1227       }
1228       
1229       return this;
1230     },
1231 
1232     /**
1233      * Method to render only the top canvas.
1234      * Also used to render the group selection box.
1235      * @method renderTop
1236      * @return {fabric.Element} thisArg
1237      * @chainable
1238      */
1239     renderTop: function () {
1240       
1241       this.clearContext(this.contextTop);
1242       if (this.overlayImage) {
1243         this.contextTop.drawImage(this.overlayImage, 0, 0);
1244       }
1245       
1246       // we render the top context - last object
1247       if (this._groupSelector) {
1248         this._drawSelection();
1249       }
1250       
1251       // delegate rendering to group selection if one exists
1252       // used for drawing selection borders/corners
1253       var activeGroup = this.getActiveGroup();
1254       if (activeGroup) {
1255         activeGroup.render(this.contextTop);
1256       }
1257       
1258       if (this.afterRender) {
1259         this.afterRender();
1260       }
1261       
1262       return this;
1263     },
1264     
1265     /**
1266      * Applies one implementation of 'point inside polygon' algorithm
1267      * @method containsPoint
1268      * @param e { Event } event object
1269      * @param target { fabric.Object } object to test against
1270      * @return {Boolean} true if point contains within area of given object
1271      */
1272     containsPoint: function (e, target) {
1273       var pointer = this.getPointer(e),
1274           xy = this._normalizePointer(target, pointer),
1275           x = xy.x, 
1276           y = xy.y;
1277       
1278       // http://www.geog.ubc.ca/courses/klink/gis.notes/ncgia/u32.html
1279       // http://idav.ucdavis.edu/~okreylos/TAship/Spring2000/PointInPolygon.html
1280       
1281       // we iterate through each object. If target found, return it.
1282       var iLines = target._getImageLines(target.oCoords),
1283           xpoints = target._findCrossPoints(x, y, iLines);
1284       
1285       // if xcount is odd then we clicked inside the object
1286       // For the specific case of square images xcount === 1 in all true cases
1287       if ((xpoints && xpoints % 2 === 1) || target._findTargetCorner(e, this._offset)) {
1288         return true;
1289       }
1290       return false;
1291     },
1292     
1293     /**
1294      * @private
1295      * @method _normalizePointer
1296      */
1297     _normalizePointer: function (object, pointer) {
1298       
1299       var activeGroup = this.getActiveGroup(), 
1300           x = pointer.x, 
1301           y = pointer.y;
1302       
1303       var isObjectInGroup = (
1304         activeGroup && 
1305         object.type !== 'group' && 
1306         activeGroup.contains(object)
1307       );
1308       
1309       if (isObjectInGroup) {
1310         x -= activeGroup.left;
1311         y -= activeGroup.top;
1312       }
1313       return { x: x, y: y };
1314     },
1315 
1316     /**
1317      * Method that determines what object we are clicking on
1318      * @method findTarget
1319      * @param {Event} e mouse event
1320      * @param {Boolean} skipGroup when true, group is skipped and only objects are traversed through
1321      */ 
1322     findTarget: function (e, skipGroup) {
1323       var target,
1324           pointer = this.getPointer(e);
1325       
1326       // first check current group (if one exists)
1327       var activeGroup = this.getActiveGroup();
1328       
1329       if (activeGroup && !skipGroup && this.containsPoint(e, activeGroup)) {
1330         target = activeGroup;
1331         return target;
1332       }
1333       
1334       // then check all of the objects on canvas
1335       for (var i = this._objects.length; i--; ) {
1336         if (this.containsPoint(e, this._objects[i])) {
1337           target = this._objects[i];
1338           this.relatedTarget = target;
1339           break;
1340         }
1341       }
1342       return target;
1343     },
1344 
1345     /**
1346      * Exports canvas element to a dataurl image.
1347      * @method toDataURL
1348      * @param {String} format the format of the output image. Either "jpeg" or "png".
1349      * @return {String}
1350      */
1351     toDataURL: function (format) {
1352       var data;
1353       if (!format) {
1354         format = 'png';
1355       }
1356       if (format === 'jpeg' || format === 'png') {
1357         this.renderAll(true);
1358         data = this.getElement().toDataURL('image/' + format);
1359         this.renderAll();
1360       }
1361       return data;
1362     },
1363     
1364     /**
1365      * Exports canvas element to a dataurl image (allowing to change image size via multiplier).
1366      * @method toDataURLWithMultiplier
1367      * @param {String} format (png|jpeg)
1368      * @param {Number} multiplier
1369      * @return {String}
1370      */
1371     toDataURLWithMultiplier: function (format, multiplier) {
1372       
1373       var origWidth = this.getWidth(),
1374           origHeight = this.getHeight(),
1375           scaledWidth = origWidth * multiplier,
1376           scaledHeight = origHeight * multiplier,
1377           activeObject = this.getActiveObject();
1378       
1379       this.setWidth(scaledWidth).setHeight(scaledHeight);
1380       this.contextTop.scale(multiplier, multiplier);
1381       
1382       if (activeObject) {
1383         this.deactivateAll().renderAll();
1384       }
1385       var dataURL = this.toDataURL(format);
1386 
1387       this.contextTop.scale( 1 / multiplier,  1 / multiplier);
1388       this.setWidth(origWidth).setHeight(origHeight);
1389       
1390       if (activeObject) {
1391         this.setActiveObject(activeObject);
1392       }
1393       this.renderAll();
1394       
1395       return dataURL;
1396     },
1397     
1398     /**
1399      * Returns pointer coordinates relative to canvas.
1400      * @method getPointer
1401      * @return {Object} object with "x" and "y" number values
1402      */
1403     getPointer: function (e) {
1404       var pointer = getPointer(e);
1405       return {
1406         x: pointer.x - this._offset.left,
1407         y: pointer.y - this._offset.top
1408       };
1409     },
1410     
1411     /**
1412      * Returns coordinates of a center of canvas.
1413      * Returned value is an object with top and left properties
1414      * @method getCenter
1415      * @return {Object} object with "top" and "left" number values
1416      */
1417     getCenter: function () {
1418       return {
1419         top: this.getHeight() / 2,
1420         left: this.getWidth() / 2
1421       };
1422     },
1423     
1424     /**
1425      * Centers object horizontally.
1426      * @method centerObjectH
1427      * @param {fabric.Object} object Object to center
1428      * @return {fabric.Element} thisArg
1429      */
1430     centerObjectH: function (object) {
1431       object.set('left', this.getCenter().left);
1432       this.renderAll();
1433       return this;
1434     },
1435     
1436     /**
1437      * Centers object horizontally with animation.
1438      * @method fxCenterObjectH
1439      * @param {fabric.Object} object Object to center
1440      * @param {Object} [callbacks] Callbacks object with optional "onComplete" and/or "onChange" properties
1441      * @return {fabric.Element} thisArg
1442      * @chainable
1443      */
1444     fxCenterObjectH: function (object, callbacks) {
1445       callbacks = callbacks || { };
1446 
1447       var empty = function() { },
1448           onComplete = callbacks.onComplete || empty,
1449           onChange = callbacks.onChange || empty,
1450           _this = this;
1451 
1452       fabric.util.animate({
1453         startValue: object.get('left'),
1454         endValue: this.getCenter().left,
1455         duration: this.FX_DURATION,
1456         onChange: function(value) {
1457           object.set('left', value);
1458           _this.renderAll();
1459           onChange();
1460         },
1461         onComplete: function() {
1462           object.setCoords();
1463           onComplete();
1464         }
1465       });
1466 
1467       return this;
1468     },
1469     
1470     /**
1471      * Centers object vertically.
1472      * @method centerObjectH
1473      * @param {fabric.Object} object Object to center
1474      * @return {fabric.Element} thisArg
1475      * @chainable
1476      */
1477     centerObjectV: function (object) {
1478       object.set('top', this.getCenter().top);
1479       this.renderAll();
1480       return this;
1481     },
1482     
1483     /**
1484      * Centers object vertically with animation.
1485      * @method fxCenterObjectV
1486      * @param {fabric.Object} object Object to center
1487      * @param {Object} [callbacks] Callbacks object with optional "onComplete" and/or "onChange" properties
1488      * @return {fabric.Element} thisArg
1489      * @chainable
1490      */
1491     fxCenterObjectV: function (object, callbacks) {
1492       callbacks = callbacks || { };
1493 
1494       var empty = function() { },
1495           onComplete = callbacks.onComplete || empty,
1496           onChange = callbacks.onChange || empty,
1497           _this = this;
1498 
1499       fabric.util.animate({
1500         startValue: object.get('top'),
1501         endValue: this.getCenter().top,
1502         duration: this.FX_DURATION,
1503         onChange: function(value) {
1504           object.set('top', value);
1505           _this.renderAll();
1506           onChange();
1507         },
1508         onComplete: function() {
1509           object.setCoords();
1510           onComplete();
1511         }
1512       });
1513 
1514       return this;
1515     },
1516     
1517     /**
1518      * Straightens object, then rerenders canvas
1519      * @method straightenObject
1520      * @param {fabric.Object} object Object to straighten
1521      * @return {fabric.Element} thisArg
1522      * @chainable
1523      */
1524     straightenObject: function (object) {
1525       object.straighten();
1526       this.renderAll();
1527       return this;
1528     },
1529     
1530     /**
1531      * Same as `fabric.Element#straightenObject`, but animated
1532      * @method fxStraightenObject
1533      * @param {fabric.Object} object Object to straighten
1534      * @return {fabric.Element} thisArg
1535      * @chainable
1536      */
1537     fxStraightenObject: function (object) {
1538       object.fxStraighten({
1539         onChange: this.renderAll.bind(this)
1540       });
1541       return this;
1542     },
1543     
1544     /**
1545      * Returs dataless JSON representation of canvas
1546      * @method toDatalessJSON
1547      * @return {String} json string
1548      */
1549     toDatalessJSON: function () {
1550       return this.toDatalessObject();
1551     },
1552     
1553     /**
1554      * Returns object representation of canvas
1555      * @method toObject
1556      * @return {Object}
1557      */
1558     toObject: function () {
1559       return this._toObjectMethod('toObject');
1560     },
1561     
1562     /**
1563      * Returns dataless object representation of canvas
1564      * @method toDatalessObject
1565      * @return {Object}
1566      */
1567     toDatalessObject: function () {
1568       return this._toObjectMethod('toDatalessObject');
1569     },
1570     
1571     /**
1572      * @private
1573      * @method _toObjectMethod
1574      */
1575     _toObjectMethod: function (methodName) {
1576       return { 
1577         objects: this._objects.map(function (instance){
1578           // TODO (kangax): figure out how to clean this up
1579           if (!this.includeDefaultValues) {
1580             var originalValue = instance.includeDefaultValues;
1581             instance.includeDefaultValues = false;
1582           }
1583           var object = instance[methodName]();
1584           if (!this.includeDefaultValues) {
1585             instance.includeDefaultValues = originalValue;
1586           }
1587           return object;
1588         }, this),
1589         background: this.backgroundColor
1590       }
1591     },
1592 
1593     /**
1594      * Returns true if canvas contains no objects
1595      * @method isEmpty
1596      * @return {Boolean} true if canvas is empty
1597      */
1598     isEmpty: function () {
1599       return this._objects.length === 0;
1600     },
1601     
1602     /**
1603      * Populates canvas with data from the specified JSON
1604      * JSON format must conform to the one of `fabric.Element#toJSON`
1605      * @method loadFromJSON
1606      * @param {String} json JSON string
1607      * @param {Function} callback Callback, invoked when json is parsed 
1608      *                            and corresponding objects (e.g: fabric.Image) 
1609      *                            are initialized
1610      * @return {fabric.Element} instance
1611      * @chainable
1612      */
1613     loadFromJSON: function (json, callback) {
1614       if (!json) return;
1615       
1616       var serialized = JSON.parse(json);
1617       if (!serialized || (serialized && !serialized.objects)) return;
1618       
1619       this.clear();
1620       var _this = this;
1621       this._enlivenObjects(serialized.objects, function () {
1622         _this.backgroundColor = serialized.background;
1623         if (callback) {
1624           callback();
1625         }
1626       });
1627       
1628       return this;
1629     },
1630     
1631     /**
1632      * @method _enlivenObjects
1633      * @param {Array} objects
1634      * @param {Function} callback
1635      */
1636     _enlivenObjects: function (objects, callback) {
1637       var numLoadedImages = 0,
1638           // get length of all images 
1639           numTotalImages = objects.filter(function (o) {
1640             return o.type === 'image';
1641           }).length;
1642       
1643       var _this = this;
1644       
1645       objects.forEach(function (o, index) {
1646         if (!o.type) {
1647           return;
1648         }
1649         switch (o.type) {
1650           case 'image':
1651           case 'font':
1652             fabric[capitalize(o.type)].fromObject(o, function (o) {
1653               _this.insertAt(o, index);
1654               if (++numLoadedImages === numTotalImages) {
1655                 if (callback) {
1656                   callback();
1657                 }
1658               }
1659             });
1660             break;
1661           default:
1662             var klass = fabric[camelize(capitalize(o.type))];
1663             if (klass && klass.fromObject) {
1664               _this.insertAt(klass.fromObject(o), index);
1665             }
1666             break;
1667         }
1668       });
1669       
1670       if (numTotalImages === 0 && callback) {
1671         callback();
1672       }
1673     },
1674     
1675     /**
1676      * Populates canvas with data from the specified dataless JSON
1677      * JSON format must conform to the one of `fabric.Element#toDatalessJSON`
1678      * @method loadFromDatalessJSON
1679      * @param {String} json JSON string
1680      * @param {Function} callback Callback, invoked when json is parsed 
1681      *                            and corresponding objects (e.g: fabric.Image) 
1682      *                            are initialized
1683      * @return {fabric.Element} instance
1684      * @chainable
1685      */
1686     loadFromDatalessJSON: function (json, callback) {
1687       
1688       if (!json) {
1689         return;
1690       }
1691 
1692       // serialize if it wasn't already
1693       var serialized = (typeof json === 'string')
1694         ? JSON.parse(json)
1695         : json;
1696       
1697       if (!serialized || (serialized && !serialized.objects)) return;
1698       
1699       this.clear();
1700 
1701       // TODO: test this
1702       this.backgroundColor = serialized.background;
1703       this._enlivenDatalessObjects(serialized.objects, callback);
1704     },
1705     
1706     /**
1707      * @method _enlivenDatalessObjects
1708      * @param {Array} objects
1709      * @param {Function} callback
1710      */
1711     _enlivenDatalessObjects: function (objects, callback) {
1712       
1713       /** @ignore */
1714       function onObjectLoaded(object, index) {
1715         _this.insertAt(object, index);
1716 				object.setCoords();
1717         if (++numLoadedObjects === numTotalObjects) {
1718           callback && callback();
1719         }
1720       }
1721       
1722       var _this = this,
1723           numLoadedObjects = 0,
1724           numTotalObjects = objects.length;
1725       
1726       if (numTotalObjects === 0 && callback) {
1727         callback();
1728       }
1729       
1730       try {
1731         objects.forEach(function (obj, index) {
1732           
1733           var pathProp = obj.paths ? 'paths' : 'path';
1734           var path = obj[pathProp];
1735 
1736           delete obj[pathProp];
1737           
1738           if (typeof path !== 'string') {
1739             switch (obj.type) {
1740               case 'image':
1741               case 'text':
1742                 fabric[capitalize(obj.type)].fromObject(obj, function (o) {
1743                   onObjectLoaded(o, index);
1744                 });
1745                 break;
1746               default:
1747                 var klass = fabric[camelize(capitalize(obj.type))];
1748                 if (klass && klass.fromObject) {
1749                   onObjectLoaded(klass.fromObject(obj), index);
1750                 }
1751                 break;
1752             }
1753           }
1754           else {
1755             if (obj.type === 'image') {
1756               _this.loadImageFromURL(path, function (image) {
1757                 image.setSourcePath(path);
1758 
1759                 extend(image, obj);
1760                 image.setAngle(obj.angle);
1761 
1762                 onObjectLoaded(image, index);
1763               });
1764             }
1765             else if (obj.type === 'text') {
1766               
1767               obj.path = path;
1768               var object = fabric.Text.fromObject(obj);
1769               var onscriptload = function () {
1770                 // TODO (kangax): find out why Opera refuses to work without this timeout
1771                 if (Object.prototype.toString.call(window.opera) === '[object Opera]') {
1772                   setTimeout(function () {
1773                     onObjectLoaded(object, index);
1774                   }, 500);
1775                 }
1776                 else {
1777                   onObjectLoaded(object, index);
1778                 }
1779               }
1780               
1781               fabric.util.getScript(path, onscriptload);
1782             }
1783             else {
1784               _this.loadSVGFromURL(path, function (elements, options) {
1785                 if (elements.length > 1) {
1786                   var object = new fabric.PathGroup(elements, obj);
1787                 }
1788                 else {
1789                   var object = elements[0];
1790                 }
1791                 object.setSourcePath(path);
1792 
1793                 // copy parameters from serialied json to object (left, top, scaleX, scaleY, etc.)
1794                 // skip this step if an object is a PathGroup, since we already passed it options object before
1795                 if (!(object instanceof fabric.PathGroup)) {
1796                   extend(object, obj);
1797                   if (typeof obj.angle !== 'undefined') {
1798                     object.setAngle(obj.angle);
1799                   }
1800                 }
1801 
1802                 onObjectLoaded(object, index);
1803               });
1804             }
1805           }
1806         }, this);
1807       } 
1808       catch(e) {
1809         fabric.log(e.message);
1810       }
1811     },
1812     
1813     /**
1814      * Loads an image from URL
1815      * @function
1816      * @method loadImageFromURL
1817      * @param url {String} url of image to load
1818      * @param callback {Function} calback, invoked when image is loaded
1819      */
1820     loadImageFromURL: (function () {
1821       var imgCache = { };
1822 
1823       return function (url, callback) {
1824         // check cache first
1825         
1826         var _this = this;
1827         
1828         function checkIfLoaded() {
1829           var imgEl = document.getElementById(imgCache[url]);
1830           if (imgEl.width && imgEl.height) {
1831             callback(new fabric.Image(imgEl));
1832           }
1833           else {
1834             setTimeout(checkIfLoaded, 50);
1835           }
1836         }
1837 
1838         // get by id from cache
1839         if (imgCache[url]) {
1840           // id can be cached but image might still not be loaded, so we poll here
1841           checkIfLoaded();
1842         }
1843         // else append a new image element
1844         else {
1845           var imgEl = new Image();
1846           
1847           /** @ignore */
1848           imgEl.onload = function () {
1849             imgEl.onload = null;
1850             
1851             _this._resizeImageToFit(imgEl);
1852             
1853             var oImg = new fabric.Image(imgEl);
1854             callback(oImg);
1855           };
1856           
1857           imgEl.className = 'canvas-img-clone';
1858           imgEl.src = url;
1859           
1860           if (this.shouldCacheImages) {
1861             imgCache[url] = Element.identify(imgEl);
1862           }
1863           document.body.appendChild(imgEl);
1864         }
1865       }
1866     })(),
1867     
1868     /**
1869      * Takes url corresponding to an SVG document, and parses it to a set of objects
1870      * @method loadSVGFromURL
1871      * @param {String} url
1872      * @param {Function} callback
1873      */
1874     loadSVGFromURL: function (url, callback) {
1875       
1876       var _this = this;
1877       
1878       url = url.replace(/^\n\s*/, '').replace(/\?.*$/, '').trim();
1879       
1880       this.cache.has(url, function (hasUrl) {
1881         if (hasUrl) {
1882           _this.cache.get(url, function (value) {
1883             var enlivedRecord = _this._enlivenCachedObject(value);
1884             callback(enlivedRecord.objects, enlivedRecord.options);
1885           });
1886         }
1887         else {
1888           // TODO (kangax): replace Prototype's API with fabric's util one
1889           new Ajax.Request(url, {
1890             method: 'get',
1891             onComplete: onComplete,
1892             onFailure: onFailure
1893           });
1894         }
1895       });
1896       
1897       function onComplete(r) {
1898         
1899         var xml = r.responseXML;
1900         if (!xml) return;
1901         
1902         var doc = xml.documentElement;
1903         if (!doc) return;
1904         
1905         fabric.parseSVGDocument(doc, function (results, options) {
1906           _this.cache.set(url, {
1907             objects: results.invoke('toObject'),
1908             options: options
1909           });
1910           callback(results, options);
1911         });
1912       }
1913       
1914       function onFailure() {
1915         fabric.log('ERROR!');
1916       }
1917     },
1918     
1919     /**
1920      * @method _enlivenCachedObject
1921      */
1922     _enlivenCachedObject: function (cachedObject) {
1923       
1924       var objects = cachedObject.objects,
1925           options = cachedObject.options;
1926       
1927       objects = objects.map(function (o) {
1928         return fabric[capitalize(o.type)].fromObject(o);
1929       });
1930       
1931       return ({ objects: objects, options: options });
1932     },
1933     
1934     /**
1935      * Removes an object from canvas and returns it
1936      * @method remove
1937      * @param object {Object} Object to remove
1938      * @return {Object} removed object
1939      */
1940     remove: function (object) {
1941       removeFromArray(this._objects, object);
1942       this.renderAll();
1943       return object;
1944     },
1945     
1946     /**
1947      * Same as `fabric.Element#remove` but animated
1948      * @method fxRemove
1949      * @param {fabric.Object} object Object to remove
1950      * @param {Function} callback Callback, invoked on effect completion
1951      * @return {fabric.Element} thisArg
1952      * @chainable
1953      */
1954     fxRemove: function (object, callback) {
1955       var _this = this;
1956       object.fxRemove({
1957         onChange: this.renderAll.bind(this),
1958         onComplete: function () {
1959           _this.remove(object);
1960           if (typeof callback === 'function') {
1961             callback();
1962           }
1963         }
1964       });
1965       return this;
1966     },
1967     
1968     /**
1969      * Moves an object to the bottom of the stack of drawn objects
1970      * @method sendToBack
1971      * @param object {fabric.Object} Object to send to back
1972      * @return {fabric.Element} thisArg
1973      * @chainable
1974      */
1975     sendToBack: function (object) {
1976       removeFromArray(this._objects, object);
1977       this._objects.unshift(object);
1978       return this.renderAll();
1979     },
1980     
1981     /**
1982      * Moves an object to the top of the stack of drawn objects
1983      * @method bringToFront
1984      * @param object {fabric.Object} Object to send
1985      * @return {fabric.Element} thisArg
1986      * @chainable
1987      */
1988     bringToFront: function (object) {
1989       removeFromArray(this._objects, object);
1990       this._objects.push(object);
1991       return this.renderAll();
1992     },
1993     
1994     /**
1995      * Moves an object one level down in stack of drawn objects
1996      * @method sendBackwards
1997      * @param object {fabric.Object} Object to send
1998      * @return {fabric.Element} thisArg
1999      * @chainable
2000      */
2001     sendBackwards: function (object) {
2002       var idx = this._objects.indexOf(object),
2003           nextIntersectingIdx = idx;
2004       
2005       // if object is not on the bottom of stack
2006       if (idx !== 0) {
2007         
2008         // traverse down the stack looking for the nearest intersecting object
2009         for (var i=idx-1; i>=0; --i) {
2010           if (object.intersectsWithObject(this._objects[i])) {
2011             nextIntersectingIdx = i;
2012             break;
2013           }
2014         }
2015         removeFromArray(this._objects, object);
2016         this._objects.splice(nextIntersectingIdx, 0, object);
2017       }
2018       return this.renderAll();
2019     },
2020     
2021     /**
2022      * Moves an object one level up in stack of drawn objects
2023      * @method sendForward
2024      * @param object {fabric.Object} Object to send
2025      * @return {fabric.Element} thisArg
2026      * @chainable
2027      */
2028     bringForward: function (object) {
2029       var objects = this.getObjects(),
2030           idx = objects.indexOf(object),
2031           nextIntersectingIdx = idx;
2032 
2033       
2034       // if object is not on top of stack (last item in an array)
2035       if (idx !== objects.length-1) {
2036         
2037         // traverse up the stack looking for the nearest intersecting object
2038         for (var i = idx + 1, l = this._objects.length; i < l; ++i) {
2039           if (object.intersectsWithObject(objects[i])) {
2040             nextIntersectingIdx = i;
2041             break;
2042           }
2043         }
2044         removeFromArray(objects, object);
2045         objects.splice(nextIntersectingIdx, 0, object);
2046       }
2047       this.renderAll();
2048     },
2049     
2050     /**
2051      * Sets given object as active
2052      * @method setActiveObject
2053      * @param object {fabric.Object} Object to set as an active one
2054      * @return {fabric.Element} thisArg
2055      * @chainable
2056      */
2057     setActiveObject: function (object) {
2058       if (this._activeObject) {
2059         this._activeObject.setActive(false);
2060       }
2061       this._activeObject = object;
2062       object.setActive(true);
2063       
2064       this.renderAll();
2065       
2066       fireEvent('object:selected', { target: object });
2067       return this;
2068     },
2069     
2070     /**
2071      * Returns currently active object
2072      * @method getActiveObject
2073      * @return {fabric.Object} active object
2074      */
2075     getActiveObject: function () {
2076       return this._activeObject;
2077     },
2078     
2079     /**
2080      * Removes currently active object
2081      * @method removeActiveObject
2082      * @return {fabric.Element} thisArg
2083      * @chainable
2084      */
2085     removeActiveObject: function () {
2086       if (this._activeObject) {
2087         this._activeObject.setActive(false);
2088       }
2089       this._activeObject = null;
2090       return this;
2091     },
2092     
2093     /**
2094      * Sets active group to a speicified one
2095      * @method setActiveGroup
2096      * @param {fabric.Group} group Group to set as a current one 
2097      * @return {fabric.Element} thisArg
2098      * @chainable
2099      */
2100     setActiveGroup: function (group) {
2101       this._activeGroup = group;
2102       return this;
2103     },
2104     
2105     /**
2106      * Returns currently active group
2107      * @method getActiveGroup
2108      * @return {fabric.Group} Current group
2109      */
2110     getActiveGroup: function () {
2111       return this._activeGroup;
2112     },
2113     
2114     /**
2115      * Removes currently active group
2116      * @method removeActiveGroup
2117      * @return {fabric.Element} thisArg
2118      */
2119     removeActiveGroup: function () {
2120       var g = this.getActiveGroup();
2121       if (g) {
2122         g.destroy();
2123       }
2124       return this.setActiveGroup(null);
2125     },
2126     
2127     /**
2128      * Returns object at specified index
2129      * @method item
2130      * @param {Number} index
2131      * @return {fabric.Object}
2132      */
2133     item: function (index) {
2134       return this.getObjects()[index];
2135     },
2136     
2137     /**
2138      * Deactivates all objects by calling their setActive(false)
2139      * @method deactivateAll
2140      * @return {fabric.Element} thisArg
2141      */
2142     deactivateAll: function () {
2143       var allObjects = this.getObjects(),
2144           i = 0,
2145           len = allObjects.length;
2146       for ( ; i < len; i++) {
2147         allObjects[i].setActive(false);
2148       }
2149       this.removeActiveGroup();
2150       this.removeActiveObject();
2151       return this;
2152     },
2153     
2154     /**
2155      * Returns number representation of an instance complexity
2156      * @method complexity
2157      * @return {Number} complexity
2158      */
2159     complexity: function () {
2160       return this.getObjects().reduce(function (memo, current) {
2161         memo += current.complexity ? current.complexity() : 0;
2162         return memo;
2163       }, 0);
2164     },
2165     
2166     /**
2167      * Clears a canvas element and removes all event handlers.
2168      * @method dispose
2169      * @return {fabric.Element} thisArg
2170      * @chainable
2171      */
2172     dispose: function () {
2173       this.clear();
2174       removeListener(this.getElement(), 'mousedown', this._onMouseDown);
2175       removeListener(document, 'mouseup', this._onMouseUp);
2176       removeListener(document, 'mousemove', this._onMouseMove);
2177       removeListener(window, 'resize', this._onResize);
2178       return this;
2179     },
2180     
2181     /**
2182      * Clones canvas instance
2183      * @method clone
2184      * @param {Object} [callback] Expects `onBeforeClone` and `onAfterClone` functions
2185      * @return {fabric.Element} Clone of this instance
2186      */
2187     clone: function (callback) {
2188       var el = document.createElement('canvas');
2189       
2190       el.width = this.getWidth();
2191       el.height = this.getHeight();
2192           
2193       // cache
2194       var clone = this.__clone || (this.__clone = new fabric.Element(el));
2195       
2196       return clone.loadFromJSON(JSON.stringify(this.toJSON()), function () {
2197         if (callback) {
2198           callback(clone);
2199         }
2200       });
2201     },
2202     
2203     /**
2204      * @private
2205      * @method _toDataURL
2206      * @param {String} format
2207      * @param {Function} callback
2208      */
2209     _toDataURL: function (format, callback) {
2210       this.clone(function (clone) {
2211         callback(clone.toDataURL(format));
2212       });
2213     },
2214     
2215     /**
2216      * @private
2217      * @method _toDataURLWithMultiplier
2218      * @param {String} format
2219      * @param {Number} multiplier
2220      * @param {Function} callback
2221      */
2222     _toDataURLWithMultiplier: function (format, multiplier, callback) {
2223       this.clone(function (clone) {
2224         callback(clone.toDataURLWithMultiplier(format, multiplier));
2225       });
2226     },
2227     
2228     /**
2229      * @private
2230      * @method _resizeImageToFit
2231      * @param {HTMLImageElement} imgEl
2232      */
2233     _resizeImageToFit: function (imgEl) {
2234       
2235       var imageWidth = imgEl.width || imgEl.offsetWidth,
2236           widthScaleFactor = this.getWidth() / imageWidth;
2237       
2238       // scale image down so that it has original dimensions when printed in large resolution
2239       if (imageWidth) {
2240         imgEl.width = imageWidth * widthScaleFactor;
2241       }
2242     },
2243     
2244     /**
2245      * @property
2246      * @namespace
2247      */
2248     cache: {
2249       
2250       /**
2251        * @method has
2252        * @param {String} name
2253        * @param {Function} callback
2254        */
2255       has: function (name, callback) { 
2256         callback(false);
2257       },
2258       
2259       /**
2260        * @method get
2261        * @param {String} url
2262        * @param {Function} callback
2263        */
2264       get: function (url, callback) {
2265         /* NOOP */
2266       },
2267       
2268       /**
2269        * @method set
2270        * @param {String} url
2271        * @param {Object} object
2272        */
2273       set: function (url, object) {
2274         /* NOOP */
2275       }
2276     }
2277   });
2278   
2279   /**
2280    * Returns a string representation of an instance
2281    * @method toString
2282    * @return {String} string representation of an instance
2283    */
2284   fabric.Element.prototype.toString = function () { // Assign explicitly since `extend` doesn't take care of DontEnum bug yet
2285     return '#<fabric.Element (' + this.complexity() + '): '+
2286            '{ objects: ' + this.getObjects().length + ' }>';
2287   };
2288   
2289   extend(fabric.Element, /** @scope fabric.Element */ {
2290     
2291     /**
2292      * @static
2293      * @property EMPTY_JSON
2294      * @type String
2295      */
2296     EMPTY_JSON: '{"objects": [], "background": "white"}',
2297     
2298     /**
2299      * Takes <canvas> element and transforms its data in such way that it becomes grayscale
2300      * @static
2301      * @method toGrayscale
2302      * @param {HTMLCanvasElement} canvasEl
2303      */
2304     toGrayscale: function (canvasEl) {
2305        var context = canvasEl.getContext('2d'),
2306            imageData = context.getImageData(0, 0, canvasEl.width, canvasEl.height),
2307            data = imageData.data, 
2308            iLen = imageData.width,
2309            jLen = imageData.height,
2310            index, average;
2311 
2312        for (i = 0; i < iLen; i++) {
2313          for (j = 0; j < jLen; j++) {
2314 
2315            index = (i * 4) * jLen + (j * 4);
2316            average = (data[index] + data[index + 1] + data[index + 2]) / 3;
2317 
2318            data[index]     = average;
2319            data[index + 1] = average;
2320            data[index + 2] = average;
2321          }
2322        }
2323 
2324        context.putImageData(imageData, 0, 0);
2325      },
2326     
2327     /**
2328      * Provides a way to check support of some of the canvas methods 
2329      * (either those of HTMLCanvasElement itself, or rendering context)
2330      *
2331      * @method supports
2332      * @param methodName {String} Method to check support for; 
2333      *                            Could be one of "getImageData" or "toDataURL"
2334      * @return {Boolean | null} `true` if method is supported (or at least exists), 
2335      *                          `null` if canvas element or context can not be initialized
2336      */
2337     supports: function (methodName) {
2338       var el = document.createElement('canvas');
2339       
2340       if (typeof G_vmlCanvasManager !== 'undefined') {
2341         G_vmlCanvasManager.initElement(el);
2342       }
2343       if (!el || !el.getContext) {
2344         return null;
2345       }
2346       
2347       var ctx = el.getContext('2d');
2348       if (!ctx) {
2349         return null;
2350       }
2351       
2352       switch (methodName) {
2353         
2354         case 'getImageData':
2355           return typeof ctx.getImageData !== 'undefined';
2356           
2357         case 'toDataURL':
2358           return typeof el.toDataURL !== 'undefined';
2359           
2360         default:
2361           return null;
2362       }
2363     }
2364     
2365   });
2366   
2367   /**
2368    * Returs JSON representation of canvas
2369    * @function
2370    * @method toJSON
2371    * @return {String} json string
2372    */
2373   fabric.Element.prototype.toJSON = fabric.Element.prototype.toObject;
2374 })();