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