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