diff --git a/build.js b/build.js index 5a758890..27d28672 100644 --- a/build.js +++ b/build.js @@ -92,6 +92,7 @@ var filesToInclude = [ 'src/log.js', 'src/observable.mixin.js', + 'src/collection.mixin.js', 'src/util/misc.js', 'src/util/lang_array.js', diff --git a/dist/all.js b/dist/all.js index 27289847..0b95e2be 100644 --- a/dist/all.js +++ b/dist/all.js @@ -1866,6 +1866,151 @@ fabric.Observable.off = fabric.Observable.stopObserving; * @type function */ fabric.Observable.trigger = fabric.Observable.fire; +fabric.Collection = { + + /** + * Adds objects to collection, then renders canvas (if `renderOnAddition` is not `false`) + * Objects should be instances of (or inherit from) fabric.Object + * @method add + * @param [...] Zero or more fabric instances + * @chainable + */ + add: function () { + this._objects.push.apply(this._objects, arguments); + for (var i = arguments.length; i--; ) { + this._onObjectAdded(arguments[i]); + } + this.renderOnAddition && this.renderAll(); + return this; + }, + + /** + * Inserts an object into collection at specified index and renders canvas + * An object should be an instance of (or inherit from) fabric.Object + * @method insertAt + * @param object {Object} Object to insert + * @param index {Number} index to insert object at + * @param nonSplicing {Boolean} when `true`, no splicing (shifting) of objects occurs + * @chainable + */ + insertAt: function (object, index, nonSplicing) { + var objects = this.getObjects(); + if (nonSplicing) { + objects[index] = object; + } + else { + objects.splice(index, 0, object); + } + this._onObjectAdded(object); + this.renderOnAddition && this.renderAll(); + return this; + }, + + /** + * Removes an object from a group + * @method remove + * @param {Object} object + * @return {fabric.Group} thisArg + * @chainable + */ + remove: function(object) { + + var objects = this.getObjects(); + var index = objects.indexOf(object); + + // only call onObjectRemoved if an object was actually removed + if (index !== -1) { + objects.splice(index, 1); + this._onObjectRemoved(object); + } + + this.renderAll && this.renderAll(); + return object; + }, + + /** + * Executes given function for each object in this group + * @method forEachObject + * @param {Function} callback + * Callback invoked with current object as first argument, + * index - as second and an array of all objects - as third. + * Iteration happens in reverse order (for performance reasons). + * Callback is invoked in a context of Global Object (e.g. `window`) + * when no `context` argument is given + * + * @param {Object} context Context (aka thisObject) + * @chainable + */ + forEachObject: function(callback, context) { + var objects = this.getObjects(), + i = objects.length; + while (i--) { + callback.call(context, objects[i], i, objects); + } + return this; + }, + + /** + * Returns object at specified index + * @method item + * @param {Number} index + * @return {fabric.Object} + */ + item: function (index) { + return this.getObjects()[index]; + }, + + /** + * Returns true if collection contains no objects + * @method isEmpty + * @return {Boolean} true if collection is empty + */ + isEmpty: function () { + return this.getObjects().length === 0; + }, + + /** + * Returns a size of a collection (i.e: length of an array containing its objects) + * @return {Number} Collection size + */ + size: function() { + return this.getObjects().length; + }, + + /** + * Returns true if collection contains an object + * @method contains + * @param {Object} object Object to check against + * @return {Boolean} `true` if collection contains an object + */ + contains: function(object) { + return this.getObjects().indexOf(object) > -1; + }, + + /** + * Returns number representation of a collection complexity + * @method complexity + * @return {Number} complexity + */ + complexity: function () { + return this.getObjects().reduce(function (memo, current) { + memo += current.complexity ? current.complexity() : 0; + return memo; + }, 0); + }, + + /** + * Makes all of the collection objects grayscale (i.e. calling `toGrayscale` on them) + * @method toGrayscale + * @return {fabric.Group} thisArg + * @chainable + */ + toGrayscale: function() { + return this.forEachObject(function(obj) { + obj.toGrayscale(); + }); + } +}; (function() { var sqrt = Math.sqrt, @@ -5901,6 +6046,7 @@ fabric.Shadow = fabric.util.createClass(/** @scope fabric.Shadow.prototype */ { }; extend(fabric.StaticCanvas.prototype, fabric.Observable); + extend(fabric.StaticCanvas.prototype, fabric.Collection); extend(fabric.StaticCanvas.prototype, /** @scope fabric.StaticCanvas.prototype */ { @@ -6311,28 +6457,11 @@ fabric.Shadow = fabric.util.createClass(/** @scope fabric.Shadow.prototype */ { } }, - /** - * Adds objects to canvas, then renders canvas (if `renderOnAddition` is not `false`). - * Objects should be instances of (or inherit from) fabric.Object - * @method add - * @param [...] Zero or more fabric instances - * @return {fabric.Canvas} thisArg - * @chainable - */ - add: function () { - this._objects.push.apply(this._objects, arguments); - for (var i = arguments.length; i--; ) { - this._initObject(arguments[i]); - } - this.renderOnAddition && this.renderAll(); - return this; - }, - /** * @private * @method _initObject */ - _initObject: function(obj) { + _onObjectAdded: function(obj) { this.stateful && obj.setupState(); obj.setCoords(); obj.canvas = this; @@ -6341,25 +6470,10 @@ fabric.Shadow = fabric.util.createClass(/** @scope fabric.Shadow.prototype */ { }, /** - * Inserts an object to canvas at specified index and renders canvas. - * An object should be an instance of (or inherit from) fabric.Object - * @method insertAt - * @param object {Object} Object to insert - * @param index {Number} index to insert object at - * @param nonSplicing {Boolean} when `true`, no splicing (shifting) of objects occurs - * @return {fabric.Canvas} thisArg - * @chainable + * @method private */ - insertAt: function (object, index, nonSplicing) { - if (nonSplicing) { - this._objects[index] = object; - } - else { - this._objects.splice(index, 0, object); - } - this._initObject(object); - this.renderOnAddition && this.renderAll(); - return this; + _onObjectRemoved: function(obj) { + this.fire('object:removed', { target: obj }); }, /** @@ -6893,15 +7007,6 @@ fabric.Shadow = fabric.util.createClass(/** @scope fabric.Shadow.prototype */ { return markup.join(''); }, - /** - * Returns true if canvas contains no objects - * @method isEmpty - * @return {Boolean} true if canvas is empty - */ - isEmpty: function () { - return this._objects.length === 0; - }, - /** * Removes an object from canvas and returns it * @method remove @@ -6916,17 +7021,7 @@ fabric.Shadow = fabric.util.createClass(/** @scope fabric.Shadow.prototype */ { this.fire('selection:cleared'); } - var objects = this._objects; - var index = objects.indexOf(object); - - // removing any object should fire "objct:removed" events - if (index !== -1) { - objects.splice(index,1); - this.fire('object:removed', { target: object }); - } - - this.renderAll(); - return object; + return fabric.Collection.remove.call(this, object); }, /** @@ -7021,42 +7116,6 @@ fabric.Shadow = fabric.util.createClass(/** @scope fabric.Shadow.prototype */ { return this.renderAll && this.renderAll(); }, - /** - * Returns object at specified index - * @method item - * @param {Number} index - * @return {fabric.Object} - */ - item: function (index) { - return this.getObjects()[index]; - }, - - /** - * Returns number representation of an instance complexity - * @method complexity - * @return {Number} complexity - */ - complexity: function () { - return this.getObjects().reduce(function (memo, current) { - memo += current.complexity ? current.complexity() : 0; - return memo; - }, 0); - }, - - /** - * Iterates over all objects, invoking callback for each one of them - * @method forEachObject - * @return {fabric.Canvas} thisArg - */ - forEachObject: function(callback, context) { - var objects = this.getObjects(), - i = objects.length; - while (i--) { - callback.call(context, objects[i], i, objects); - } - return this; - }, - /** * Clears a canvas element and removes all event handlers. * @method dispose @@ -14377,8 +14436,7 @@ fabric.util.object.extend(fabric.StaticCanvas.prototype, /** @scope fabric.Stati extend = fabric.util.object.extend, min = fabric.util.array.min, max = fabric.util.array.max, - invoke = fabric.util.array.invoke, - removeFromArray = fabric.util.removeFromArray; + invoke = fabric.util.array.invoke; if (fabric.Group) { return; @@ -14401,7 +14459,7 @@ fabric.util.object.extend(fabric.StaticCanvas.prototype, /** @scope fabric.Stati * @class Group * @extends fabric.Object */ - fabric.Group = fabric.util.createClass(fabric.Object, /** @scope fabric.Group.prototype */ { + fabric.Group = fabric.util.createClass(fabric.Object, fabric.Collection, /** @scope fabric.Group.prototype */ { /** * Type of an object @@ -14513,8 +14571,7 @@ fabric.util.object.extend(fabric.StaticCanvas.prototype, /** @scope fabric.Stati */ removeWithUpdate: function(object) { this._restoreObjectsState(); - removeFromArray(this._objects, object); - delete object.group; + this.remove(object); object.setActive(false); this._calcBounds(); this._updateObjectsCoords(); @@ -14522,37 +14579,17 @@ fabric.util.object.extend(fabric.StaticCanvas.prototype, /** @scope fabric.Stati }, /** - * Adds an object to a group - * @method add - * @param {Object} object - * @return {fabric.Group} thisArg - * @chainable + * @private */ - add: function(object) { - this._objects.push(object); + _onObjectAdded: function(object) { object.group = this; - return this; }, /** - * Removes an object from a group - * @method remove - * @param {Object} object - * @return {fabric.Group} thisArg - * @chainable + * @private */ - remove: function(object) { - removeFromArray(this._objects, object); + _onObjectRemoved: function(object) { delete object.group; - return this; - }, - - /** - * Returns a size of a group (i.e: length of an array containing its objects) - * @return {Number} Group size - */ - size: function() { - return this.getObjects().length; }, /** @@ -14590,16 +14627,6 @@ fabric.util.object.extend(fabric.StaticCanvas.prototype, /** @scope fabric.Stati } }, - /** - * Returns true if a group contains an object - * @method contains - * @param {Object} object Object to check against - * @return {Boolean} `true` if group contains an object - */ - contains: function(object) { - return this._objects.indexOf(object) > -1; - }, - /** * Returns object representation of an instance * @method toObject @@ -14656,28 +14683,6 @@ fabric.util.object.extend(fabric.StaticCanvas.prototype, /** @scope fabric.Stati this.setCoords(); }, - /** - * Returns object from the group at the specified index - * @method item - * @param index {Number} index of item to get - * @return {fabric.Object} - */ - item: function(index) { - return this.getObjects()[index]; - }, - - /** - * Returns complexity of an instance - * @method complexity - * @return {Number} complexity - */ - complexity: function() { - return this.getObjects().reduce(function(total, object) { - total += (typeof object.complexity === 'function') ? object.complexity() : 0; - return total; - }, 0); - }, - /** * Retores original state of each of group objects (original state is that which was before group was created). * @private @@ -14781,23 +14786,6 @@ fabric.util.object.extend(fabric.StaticCanvas.prototype, /** @scope fabric.Stati return this; }, - /** - * Executes given function for each object in this group - * @method forEachObject - * @param {Function} callback - * Callback invoked with current object as first argument, - * index - as second and an array of all objects - as third. - * Iteration happens in reverse order (for performance reasons). - * Callback is invoked in a context of Global Object (e.g. `window`) - * when no `context` argument is given - * - * @param {Object} context Context (aka thisObject) - * - * @return {fabric.Group} thisArg - * @chainable - */ - forEachObject: fabric.StaticCanvas.prototype.forEachObject, - /** * @private * @method _setOpacityIfSame @@ -14869,20 +14857,6 @@ fabric.util.object.extend(fabric.StaticCanvas.prototype, /** @scope fabric.Stati centerY + halfHeight > point.y; }, - /** - * Makes all of this group's objects grayscale (i.e. calling `toGrayscale` on them) - * @method toGrayscale - * @return {fabric.Group} thisArg - * @chainable - */ - toGrayscale: function() { - var i = this._objects.length; - while (i--) { - this._objects[i].toGrayscale(); - } - return this; - }, - /** * Returns svg representation of an instance * @method toSVG diff --git a/dist/all.min.js b/dist/all.min.js index 4ef48343..35e94fa6 100644 --- a/dist/all.min.js +++ b/dist/all.min.js @@ -1,6 +1,6 @@ -/* build: `node build.js modules=ALL exclude=gestures` *//*! Fabric.js Copyright 2008-2013, Printio (Juriy Zaytsev, Maxim Chernyak) */var fabric=fabric||{version:"1.1.2"};typeof exports!="undefined"&&(exports.fabric=fabric),typeof document!="undefined"&&typeof window!="undefined"?(fabric.document=document,fabric.window=window):(fabric.document=require("jsdom").jsdom("
"),fabric.window=fabric.document.createWindow()),fabric.isTouchSupported="ontouchstart"in fabric.document.documentElement,fabric.isLikelyNode=typeof Buffer!="undefined"&&typeof window=="undefined";var Cufon=function(){function r(e){var t=this.face=e.face;this.glyphs=e.glyphs,this.w=e.w,this.baseSize=parseInt(t["units-per-em"],10),this.family=t["font-family"].toLowerCase(),this.weight=t["font-weight"],this.style=t["font-style"]||"normal",this.viewBox=function(){var e=t.bbox.split(/\s+/),n={minX:parseInt(e[0],10),minY:parseInt(e[1],10),maxX:parseInt(e[2],10),maxY:parseInt(e[3],10)};return n.width=n.maxX-n.minX,n.height=n.maxY-n.minY,n.toString=function(){return[this.minX,this.minY,this.width,this.height].join(" ")},n}(),this.ascent=-parseInt(t.ascent,10),this.descent=-parseInt(t.descent,10),this.height=-this.ascent+this.descent}function i(){var e={},t={oblique:"italic",italic:"oblique"};this.add=function(t){(e[t.style]||(e[t.style]={}))[t.weight]=t},this.get=function(n,r){var i=e[n]||e[t[n]]||e.normal||e.italic||e.oblique;if(!i)return null;r={normal:400,bold:700}[r]||parseInt(r,10);if(i[r])return i[r];var s={1:1,99:0}[r%100],o=[],u,a;s===undefined&&(s=r>400),r==500&&(r=400);for(var f in i){f=parseInt(f,10);if(!u||fa)a=f;o.push(f)}return ra&&(r=a),o.sort(function(e,t){return(s?e>r&&t>r?er?n:i-t;s(u(f,a,l,n));if(i>r||o()){e.onComplete&&e.onComplete();return}h(c)}()}function p(e,t,n){if(e){var r=new Image;r.onload=function(){t&&t.call(n,r),r=r.onload=null},r.src=e}else t&&t.call(n,e)}function d(e,t){function n(e){return fabric[fabric.util.string.camelize(fabric.util.string.capitalize(e))]}function r(){++s===o&&t&&t(i)}var i=[],s=0,o=e.length;e.forEach(function(e,t){if(!e.type)return;var s=n(e.type);s.async?s.fromObject(e,function(e,n){n||(i[t]=e),r()}):(i[t]=s.fromObject(e),r())})}function v(e,t,n){var r;if(e.length>1){var i=e.some(function(e){return e.type==="text"});i?(r=new fabric.Group([],t),e.reverse().forEach(function(e){e.cx&&(e.left=e.cx),e.cy&&(e.top=e.cy),r.addWithUpdate(e)})):r=new fabric.PathGroup(e,t)}else r=e[0];return typeof n!="undefined"&&r.setSourcePath(n),r}function m(e,t,n){if(n&&Object.prototype.toString.call(n)==="[object Array]")for(var r=0,i=n.length;rr)r+=u[p++%h],r>l&&(r=l),n[d?"lineTo":"moveTo"](r,0),d=!d;n.restore()}function y(e){return e||(e=fabric.document.createElement("canvas")),!e.getContext&&typeof G_vmlCanvasManager!="undefined"&&G_vmlCanvasManager.initElement(e),e}function b(e){var t=e.prototype;for(var n=t.stateProperties.length;n--;){var r=t.stateProperties[n],i=r.charAt(0).toUpperCase()+r.slice(1),s="set"+i,o="get"+i;t[o]||(t[o]=function(e){return new Function('return this.get("'+e+'")')}(r)),t[s]||(t[s]=function(e){return new Function("value",'return this.set("'+e+'", value)')}(r))}}function w(e,t){t.save(),t.beginPath(),e.clipTo(t),t.clip()}var e=Math.sqrt,t=Math.atan2;fabric.util={};var i=Math.PI/180,c=fabric.window.requestAnimationFrame||fabric.window.webkitRequestAnimationFrame||fabric.window.mozRequestAnimationFrame||fabric.window.oRequestAnimationFrame||fabric.window.msRequestAnimationFrame||function(e){fabric.window.setTimeout(e,1e3/60)},h=function(){return c.apply(fabric.window,arguments)};fabric.util.removeFromArray=n,fabric.util.degreesToRadians=s,fabric.util.radiansToDegrees=o,fabric.util.rotatePoint=u,fabric.util.toFixed=a,fabric.util.getRandomInt=r,fabric.util.falseFunction=f,fabric.util.animate=l,fabric.util.requestAnimFrame=h,fabric.util.loadImage=p,fabric.util.enlivenObjects=d,fabric.util.groupSVGElements=v,fabric.util.populateWithProperties=m,fabric.util.drawDashedLine=g,fabric.util.createCanvasElement=y,fabric.util.createAccessors=b,fabric.util.clipContext=w}(),function(){function t(t,n){var r=e.call(arguments,2),i=[];for(var s=0,o=t.length;s ']:this.type==="radial"&&(i=[">>0,n=0,r;if(arguments.length>1)r=arguments[1];else do{if(n in this){r=this[n++];break}if(++n>=t)throw new TypeError}while(!0);for(;n0&&(i.status="Intersection"),i},t.Intersection.intersectPolygonPolygon=function(e,t){var r=new n("No Intersection"),i=e.length;for(var s=0;s0&&(r.status="Intersection"),r},t.Intersection.intersectPolygonRectangle=function(e,r,i){var s=r.min(i),o=r.max(i),u=new t.Point(o.x,s.y),a=new t.Point(s.x,o.y),f=n.intersectLinePolygon(s,u,e),l=n.intersectLinePolygon(u,o,e),c=n.intersectLinePolygon(o,a,e),h=n.intersectLinePolygon(a,s,e),p=new n("No Intersection");return p.appendPoints(f.points),p.appendPoints(l.points),p.appendPoints(c.points),p.appendPoints(h.points),p.points.length>0&&(p.status="Intersection"),p}}(typeof exports!="undefined"?exports:this),function(e){"use strict";function n(e){e?this._tryParsingColor(e):this.setSource([0,0,0,1])}var t=e.fabric||(e.fabric={});if(t.Color){t.warn("fabric.Color is already defined.");return}t.Color=n,t.Color.prototype={_tryParsingColor:function(e){var t;e in n.colorNameMap&&(e=n.colorNameMap[e]),t=n.sourceFromHex(e),t||(t=n.sourceFromRgb(e)),t&&this.setSource(t)},getSource:function(){return this._source},setSource:function(e){this._source=e},toRgb:function(){var e=this.getSource();return"rgb("+e[0]+","+e[1]+","+e[2]+")"},toRgba:function(){var e=this.getSource();return"rgba("+e[0]+","+e[1]+","+e[2]+","+e[3]+")"},toHex:function(){var e=this.getSource(),t=e[0].toString(16);t=t.length===1?"0"+t:t;var n=e[1].toString(16);n=n.length===1?"0"+n:n;var r=e[2].toString(16);return r=r.length===1?"0"+r:r,t.toUpperCase()+n.toUpperCase()+r.toUpperCase()},getAlpha:function(){return this.getSource()[3]},setAlpha:function(e){var t=this.getSource();return t[3]=e,this.setSource(t),this},toGrayscale:function(){var e=this.getSource(),t=parseInt((e[0]*.3+e[1]*.59+e[2]*.11).toFixed(0),10),n=e[3];return this.setSource([t,t,t,n]),this},toBlackWhite:function(e){var t=this.getSource(),n=(t[0]*.3+t[1]*.59+t[2]*.11).toFixed(0),r=t[3];return e=e||127,n=Number(n)