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