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