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