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