1 //= require "path.class"
  2 
  3 (function(){
  4   
  5   var fabric = this.fabric || (this.fabric = { }),
  6       extend = fabric.util.object.extend,
  7       invoke = fabric.util.array.invoke,
  8       parentSet = fabric.Object.prototype.set,
  9       parentToObject = fabric.Object.prototype.toObject,
 10       camelize = fabric.util.string.camelize,
 11       capitalize = fabric.util.string.capitalize;
 12   
 13   if (fabric.PathGroup) {
 14     fabric.warn('fabric.PathGroup is already defined');
 15     return;
 16   }
 17   
 18   /** 
 19    * @class PathGroup
 20    * @extends fabric.Path
 21    */
 22   fabric.PathGroup = fabric.util.createClass(fabric.Path, /** @scope fabric.PathGroup.prototype */ {
 23     
 24     /**
 25      * @property
 26      * @type String
 27      */
 28     type: 'path-group',
 29     
 30     /**
 31      * @property
 32      * @type Boolean
 33      */
 34     forceFillOverwrite: false,
 35     
 36     /**
 37      * Constructor
 38      * @method initialize
 39      * @param {Array} paths
 40      * @param {Object} [options] Options object
 41      * @return {fabric.PathGroup} thisArg
 42      */
 43     initialize: function(paths, options) {
 44       
 45       options = options || { };
 46       
 47       this.originalState = { };
 48       this.paths = paths;
 49       
 50       this.setOptions(options);
 51       this.initProperties();
 52       
 53       this.setCoords();
 54       
 55       if (options.sourcePath) {
 56         this.setSourcePath(options.sourcePath);
 57       }
 58     },
 59     
 60     initProperties: function() {
 61       this.stateProperties.forEach(function(prop) {
 62         if (prop === 'fill') {
 63           this.set(prop, this.options[prop]);
 64         }
 65         else if (prop === 'angle') {
 66           this.setAngle(this.options[prop]);
 67         }
 68         else {
 69           this[prop] = this.options[prop];
 70         }
 71       }, this);
 72     },
 73     
 74     render: function(ctx) {
 75       if (this.stub) {
 76         // fast-path, rendering image stub
 77         ctx.save();
 78         
 79         this.transform(ctx);
 80         this.stub.render(ctx, false /* no transform */);
 81         if (this.active) {
 82           this.drawBorders(ctx);
 83           this.drawCorners(ctx);
 84         }
 85         ctx.restore();
 86       }
 87       else {
 88         ctx.save();
 89         
 90         var m = this.transformMatrix;
 91         if (m) {
 92           ctx.transform(m[0], m[1], m[2], m[3], m[4], m[5]);
 93         }
 94         
 95         this.transform(ctx);
 96         for (var i = 0, l = this.paths.length; i < l; ++i) {
 97           this.paths[i].render(ctx, true);
 98         }
 99         if (this.active) {
100           this.drawBorders(ctx);
101           this.hideCorners || this.drawCorners(ctx);
102         }
103         ctx.restore();
104       }
105     },
106     
107     /**
108      * @method set
109      * @param {String} prop
110      * @param {Any} value
111      * @return {fabric.PathGroup} thisArg
112      */
113     set: function(prop, value) {
114       if ((prop === 'fill' || prop === 'overlayFill') && this.isSameColor()) {
115         this[prop] = value;
116         var i = this.paths.length;
117         while (i--) {
118           this.paths[i].set(prop, value);
119         }
120       }
121       else {
122         // skipping parent "class" - fabric.Path
123         parentSet.call(this, prop, value);
124       }
125       return this;
126     },
127     
128     /**
129      * @method toObject
130      * @return {Object} object representation of an instance
131      */
132     toObject: function() {
133       return extend(parentToObject.call(this), {
134         paths: invoke(this.getObjects(), 'clone'),
135         sourcePath: this.sourcePath
136       });
137     },
138     
139     /**
140      * @method toDatalessObject
141      * @return {Object} dataless object representation of an instance
142      */
143     toDatalessObject: function() {
144       var o = this.toObject();
145       if (this.sourcePath) {
146         o.paths = this.sourcePath;
147       }
148       return o;
149     },
150     
151      /**
152       * Returns a string representation of an object
153       * @method toString
154       * @return {String} string representation of an object
155       */
156     toString: function() {
157       return '#<fabric.PathGroup (' + this.complexity() + 
158         '): { top: ' + this.top + ', left: ' + this.left + ' }>';
159     },
160     
161     /**
162      * @method isSameColor
163      * @return {Boolean} true if all paths are of the same color (`fill`)
164      */
165     isSameColor: function() {
166       var firstPathFill = this.getObjects()[0].get('fill');
167       return this.getObjects().every(function(path) {
168         return path.get('fill') === firstPathFill;
169       });
170     },
171     
172     /**
173       * Returns number representation of object's complexity
174       * @method complexity
175       * @return {Number} complexity
176       */
177     complexity: function() {
178       return this.paths.reduce(function(total, path) {
179         return total + ((path && path.complexity) ? path.complexity() : 0);
180       }, 0);
181     },
182     
183     /**
184       * Makes path group grayscale
185       * @method toGrayscale
186       * @return {fabric.PathGroup} thisArg
187       */
188     toGrayscale: function() {
189       var i = this.paths.length;
190       while (i--) {
191         this.paths[i].toGrayscale();
192       }
193       return this;
194     },
195     
196     /**
197      * @method getObjects
198      * @return {Array} array of path objects included in this path group
199      */
200     getObjects: function() {
201       return this.paths;
202     }
203   });
204   
205   /**
206    * @private
207    * @method instantiatePaths
208    */
209   function instantiatePaths(paths) {
210     for (var i = 0, len = paths.length; i < len; i++) {
211       if (!(paths[i] instanceof fabric.Object)) {
212         var klassName = camelize(capitalize(paths[i].type));
213         paths[i] = fabric[klassName].fromObject(paths[i]);
214       }
215     }
216     return paths;
217   }
218   
219   /**
220    * @static
221    * @method fabric.PathGroup.fromObject
222    * @param {Object} object
223    * @return {fabric.PathGroup}
224    */
225   fabric.PathGroup.fromObject = function(object) {
226     var paths = instantiatePaths(object.paths);
227     return new fabric.PathGroup(paths, object);
228   };
229 })();