mirror of
https://github.com/Hopiu/fabric.js.git
synced 2026-03-21 08:20:23 +00:00
283 lines
8.3 KiB
JavaScript
283 lines
8.3 KiB
JavaScript
(function(global) {
|
|
|
|
'use strict';
|
|
|
|
var fabric = global.fabric || (global.fabric = { }),
|
|
extend = fabric.util.object.extend,
|
|
invoke = fabric.util.array.invoke,
|
|
parentToObject = fabric.Object.prototype.toObject;
|
|
|
|
if (fabric.PathGroup) {
|
|
fabric.warn('fabric.PathGroup is already defined');
|
|
return;
|
|
}
|
|
|
|
/**
|
|
* Path group class
|
|
* @class fabric.PathGroup
|
|
* @extends fabric.Path
|
|
* @tutorial {@link http://fabricjs.com/fabric-intro-part-1#path_and_pathgroup}
|
|
* @see {@link fabric.PathGroup#initialize} for constructor definition
|
|
*/
|
|
fabric.PathGroup = fabric.util.createClass(fabric.Object, /** @lends fabric.PathGroup.prototype */ {
|
|
|
|
/**
|
|
* Type of an object
|
|
* @type String
|
|
* @default
|
|
*/
|
|
type: 'path-group',
|
|
|
|
/**
|
|
* Fill value
|
|
* @type String
|
|
* @default
|
|
*/
|
|
fill: '',
|
|
|
|
/**
|
|
* Constructor
|
|
* @param {Array} paths
|
|
* @param {Object} [options] Options object
|
|
* @return {fabric.PathGroup} thisArg
|
|
*/
|
|
initialize: function(paths, options) {
|
|
|
|
options = options || { };
|
|
this.paths = paths || [];
|
|
|
|
for (var i = this.paths.length; i--;) {
|
|
this.paths[i].group = this;
|
|
}
|
|
|
|
if (options.toBeParsed) {
|
|
this.parseDimensionsFromPaths(options);
|
|
delete options.toBeParsed;
|
|
}
|
|
this.setOptions(options);
|
|
this.setCoords();
|
|
if (options.sourcePath) {
|
|
this.setSourcePath(options.sourcePath);
|
|
}
|
|
if (this.objectCaching) {
|
|
this._createCacheCanvas();
|
|
this.setupState({ propertySet: 'cacheProperties' });
|
|
}
|
|
},
|
|
|
|
/**
|
|
* Calculate width and height based on paths contained
|
|
*/
|
|
parseDimensionsFromPaths: function(options) {
|
|
var points, p, xC = [], yC = [], path, height, width,
|
|
m;
|
|
for (var j = this.paths.length; j--;) {
|
|
path = this.paths[j];
|
|
height = path.height + path.strokeWidth;
|
|
width = path.width + path.strokeWidth;
|
|
points = [
|
|
{ x: path.left, y: path.top },
|
|
{ x: path.left + width, y: path.top },
|
|
{ x: path.left, y: path.top + height },
|
|
{ x: path.left + width, y: path.top + height }
|
|
];
|
|
m = this.paths[j].transformMatrix;
|
|
for (var i = 0; i < points.length; i++) {
|
|
p = points[i];
|
|
if (m) {
|
|
p = fabric.util.transformPoint(p, m, false);
|
|
}
|
|
xC.push(p.x);
|
|
yC.push(p.y);
|
|
}
|
|
}
|
|
options.width = Math.max.apply(null, xC);
|
|
options.height = Math.max.apply(null, yC);
|
|
},
|
|
|
|
/**
|
|
* Execute the drawing operation for an object on a specified context
|
|
* @param {CanvasRenderingContext2D} ctx Context to render on
|
|
* @param {Boolean} [noTransform] When true, context is not transformed
|
|
*/
|
|
drawObject: function(ctx) {
|
|
ctx.save();
|
|
ctx.translate(-this.width / 2, -this.height / 2);
|
|
for (var i = 0, l = this.paths.length; i < l; ++i) {
|
|
this.paths[i].render(ctx, true);
|
|
}
|
|
ctx.restore();
|
|
},
|
|
|
|
/**
|
|
* Check if cache is dirty
|
|
*/
|
|
isCacheDirty: function() {
|
|
if (this.callSuper('isCacheDirty')) {
|
|
return true
|
|
}
|
|
if (!this.statefullCache) {
|
|
return false;
|
|
}
|
|
for (var i = 0, len = this.paths.length; i < len; i++) {
|
|
if (this.paths[i].isCacheDirty(true)) {
|
|
var dim = this._getNonTransformedDimensions();
|
|
this._cacheContext.clearRect(-dim.x / 2, -dim.y / 2, dim.x, dim.y);
|
|
return true
|
|
}
|
|
}
|
|
return false;
|
|
},
|
|
|
|
/**
|
|
* Sets certain property to a certain value
|
|
* @param {String} prop
|
|
* @param {*} value
|
|
* @return {fabric.PathGroup} thisArg
|
|
*/
|
|
_set: function(prop, value) {
|
|
|
|
if (prop === 'fill' && value && this.isSameColor()) {
|
|
var i = this.paths.length;
|
|
while (i--) {
|
|
this.paths[i]._set(prop, value);
|
|
}
|
|
}
|
|
|
|
return this.callSuper('_set', prop, value);
|
|
},
|
|
|
|
/**
|
|
* Returns object representation of this path group
|
|
* @param {Array} [propertiesToInclude] Any properties that you might want to additionally include in the output
|
|
* @return {Object} object representation of an instance
|
|
*/
|
|
toObject: function(propertiesToInclude) {
|
|
var o = extend(parentToObject.call(this, ['sourcePath'].concat(propertiesToInclude)), {
|
|
paths: invoke(this.getObjects(), 'toObject', propertiesToInclude)
|
|
});
|
|
return o;
|
|
},
|
|
|
|
/**
|
|
* Returns dataless object representation of this path group
|
|
* @param {Array} [propertiesToInclude] Any properties that you might want to additionally include in the output
|
|
* @return {Object} dataless object representation of an instance
|
|
*/
|
|
toDatalessObject: function(propertiesToInclude) {
|
|
var o = this.toObject(propertiesToInclude);
|
|
if (this.sourcePath) {
|
|
o.paths = this.sourcePath;
|
|
}
|
|
return o;
|
|
},
|
|
|
|
/* _TO_SVG_START_ */
|
|
/**
|
|
* Returns svg representation of an instance
|
|
* @param {Function} [reviver] Method for further parsing of svg representation.
|
|
* @return {String} svg representation of an instance
|
|
*/
|
|
toSVG: function(reviver) {
|
|
var objects = this.getObjects(),
|
|
p = this.getPointByOrigin('left', 'top'),
|
|
translatePart = 'translate(' + p.x + ' ' + p.y + ')',
|
|
markup = this._createBaseSVGMarkup();
|
|
markup.push(
|
|
'<g ', this.getSvgId(),
|
|
'style="', this.getSvgStyles(), '" ',
|
|
'transform="', this.getSvgTransformMatrix(), translatePart, this.getSvgTransform(), '" ',
|
|
'>\n'
|
|
);
|
|
|
|
for (var i = 0, len = objects.length; i < len; i++) {
|
|
markup.push('\t', objects[i].toSVG(reviver));
|
|
}
|
|
markup.push('</g>\n');
|
|
|
|
return reviver ? reviver(markup.join('')) : markup.join('');
|
|
},
|
|
/* _TO_SVG_END_ */
|
|
|
|
/**
|
|
* Returns a string representation of this path group
|
|
* @return {String} string representation of an object
|
|
*/
|
|
toString: function() {
|
|
return '#<fabric.PathGroup (' + this.complexity() +
|
|
'): { top: ' + this.top + ', left: ' + this.left + ' }>';
|
|
},
|
|
|
|
/**
|
|
* Returns true if all paths in this group are of same color
|
|
* @return {Boolean} true if all paths are of the same color (`fill`)
|
|
*/
|
|
isSameColor: function() {
|
|
var firstPathFill = this.getObjects()[0].get('fill') || '';
|
|
if (typeof firstPathFill !== 'string') {
|
|
return false;
|
|
}
|
|
firstPathFill = firstPathFill.toLowerCase();
|
|
return this.getObjects().every(function(path) {
|
|
var pathFill = path.get('fill') || '';
|
|
return typeof pathFill === 'string' && (pathFill).toLowerCase() === firstPathFill;
|
|
});
|
|
},
|
|
|
|
/**
|
|
* Returns number representation of object's complexity
|
|
* @return {Number} complexity
|
|
*/
|
|
complexity: function() {
|
|
return this.paths.reduce(function(total, path) {
|
|
return total + ((path && path.complexity) ? path.complexity() : 0);
|
|
}, 0);
|
|
},
|
|
|
|
/**
|
|
* Returns all paths in this path group
|
|
* @return {Array} array of path objects included in this path group
|
|
*/
|
|
getObjects: function() {
|
|
return this.paths;
|
|
}
|
|
});
|
|
|
|
/**
|
|
* Creates fabric.PathGroup instance from an object representation
|
|
* @static
|
|
* @memberOf fabric.PathGroup
|
|
* @param {Object} object Object to create an instance from
|
|
* @param {Function} [callback] Callback to invoke when an fabric.PathGroup instance is created
|
|
*/
|
|
fabric.PathGroup.fromObject = function(object, callback) {
|
|
// remove this pattern from 2.0 accepts only object
|
|
if (typeof object.paths === 'string') {
|
|
fabric.loadSVGFromURL(object.paths, function (elements) {
|
|
|
|
var pathUrl = object.paths;
|
|
delete object.paths;
|
|
|
|
var pathGroup = fabric.util.groupSVGElements(elements, object, pathUrl);
|
|
|
|
callback(pathGroup);
|
|
});
|
|
}
|
|
else {
|
|
fabric.util.enlivenObjects(object.paths, function(enlivenedObjects) {
|
|
delete object.paths;
|
|
callback(new fabric.PathGroup(enlivenedObjects, object));
|
|
});
|
|
}
|
|
};
|
|
|
|
/**
|
|
* Indicates that instances of this type are async
|
|
* @static
|
|
* @memberOf fabric.PathGroup
|
|
* @type Boolean
|
|
* @default
|
|
*/
|
|
fabric.PathGroup.async = true;
|
|
|
|
})(typeof exports !== 'undefined' ? exports : this);
|