      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 })();