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