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