diff --git a/CHANGELOG.md b/CHANGELOG.md
index c9812444..01b6eb85 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,3 +1,6 @@
+**Version 2.4.0**
+- Add: Add clipPath support to canvas and svg import/export. Low compatibility yet.
+
**Version 2.3.6**
- Fix: Make image.class aware of naturalWidth and naturalHeight. [#5178](https://github.com/fabricjs/fabric.js/pull/5178)
- Fix: Make 2 finger events works again [#5177](https://github.com/fabricjs/fabric.js/pull/5177)
diff --git a/HEADER.js b/HEADER.js
index b3a5e43e..f81030b3 100644
--- a/HEADER.js
+++ b/HEADER.js
@@ -1,6 +1,6 @@
/*! Fabric.js Copyright 2008-2015, Printio (Juriy Zaytsev, Maxim Chernyak) */
-var fabric = fabric || { version: '2.3.6' };
+var fabric = fabric || { version: '2.4.0' };
if (typeof exports !== 'undefined') {
exports.fabric = fabric;
}
diff --git a/dist/fabric.js b/dist/fabric.js
index 765fcd2a..9c014815 100644
--- a/dist/fabric.js
+++ b/dist/fabric.js
@@ -1,7 +1,7 @@
/* build: `node build.js modules=ALL exclude=gestures,accessors requirejs minifier=uglifyjs` */
/*! Fabric.js Copyright 2008-2015, Printio (Juriy Zaytsev, Maxim Chernyak) */
-var fabric = fabric || { version: '2.3.6' };
+var fabric = fabric || { version: '2.4.0' };
if (typeof exports !== 'undefined') {
exports.fabric = fabric;
}
@@ -48,15 +48,15 @@ fabric.isLikelyNode = typeof Buffer !== 'undefined' &&
* @type array
*/
fabric.SHARED_ATTRIBUTES = [
- "display",
- "transform",
- "fill", "fill-opacity", "fill-rule",
- "opacity",
- "stroke", "stroke-dasharray", "stroke-linecap",
- "stroke-linejoin", "stroke-miterlimit",
- "stroke-opacity", "stroke-width",
- "id", "paint-order",
- "instantiated_by_use"
+ 'display',
+ 'transform',
+ 'fill', 'fill-opacity', 'fill-rule',
+ 'opacity',
+ 'stroke', 'stroke-dasharray', 'stroke-linecap',
+ 'stroke-linejoin', 'stroke-miterlimit',
+ 'stroke-opacity', 'stroke-width',
+ 'id', 'paint-order',
+ 'instantiated_by_use', 'clip-path'
];
/* _FROM_SVG_END_ */
@@ -1171,6 +1171,20 @@ fabric.CommonMethods = {
return fabric.document.createElement('canvas');
},
+ /**
+ * Creates a canvas element that is a copy of another and is also painted
+ * @static
+ * @memberOf fabric.util
+ * @return {CanvasElement} initialized canvas element
+ */
+ copyCanvasElement: function(canvas) {
+ var newCanvas = fabric.document.createElement('canvas');
+ newCanvas.width = canvas.width;
+ newCanvas.height = canvas.height;
+ newCanvas.getContext('2d').drawImage(canvas, 0, 0);
+ return newCanvas;
+ },
+
/**
* Creates image element (works on client and node)
* @static
@@ -3360,10 +3374,10 @@ if (typeof console !== 'undefined') {
multiplyTransformMatrices = fabric.util.multiplyTransformMatrices,
svgValidTagNames = ['path', 'circle', 'polygon', 'polyline', 'ellipse', 'rect', 'line',
- 'image', 'text', 'linearGradient', 'radialGradient', 'stop'],
+ 'image', 'text'],
svgViewBoxElements = ['symbol', 'image', 'marker', 'pattern', 'view', 'svg'],
svgInvalidAncestors = ['pattern', 'defs', 'symbol', 'metadata', 'clipPath', 'mask', 'desc'],
- svgValidParents = ['symbol', 'g', 'a', 'svg'],
+ svgValidParents = ['symbol', 'g', 'a', 'svg', 'clipPath', 'defs'],
attributesMap = {
cx: 'left',
@@ -3390,7 +3404,9 @@ if (typeof console !== 'undefined') {
'stroke-width': 'strokeWidth',
'text-decoration': 'textDecoration',
'text-anchor': 'textAnchor',
- opacity: 'opacity'
+ opacity: 'opacity',
+ 'clip-path': 'clipPath',
+ 'clip-rule': 'clipRule',
},
colorAttributes = {
@@ -3405,6 +3421,7 @@ if (typeof console !== 'undefined') {
fabric.cssRules = { };
fabric.gradientDefs = { };
+ fabric.clipPaths = { };
function normalizeAttr(attr) {
// transform attribute names
@@ -3962,7 +3979,7 @@ if (typeof console !== 'undefined') {
scaleY + ' ' +
(minX * scaleX + widthDiff) + ' ' +
(minY * scaleY + heightDiff) + ') ';
-
+ parsedDim.viewboxTransform = fabric.parseTransformAttribute(matrix);
if (element.nodeName === 'svg') {
el = element.ownerDocument.createElement('g');
// element.firstChild != null
@@ -3975,7 +3992,6 @@ if (typeof console !== 'undefined') {
el = element;
matrix = el.getAttribute('transform') + matrix;
}
-
el.setAttribute('transform', matrix);
return parsedDim;
}
@@ -4036,13 +4052,24 @@ if (typeof console !== 'undefined') {
callback && callback([], {});
return;
}
-
+ var clipPaths = { };
+ descendants.filter(function(el) {
+ return el.nodeName.replace('svg:', '') === 'clipPath';
+ }).forEach(function(el) {
+ clipPaths[el.id] = fabric.util.toArray(el.getElementsByTagName('*')).filter(function(el) {
+ return fabric.svgValidTagNamesRegEx.test(el.nodeName.replace('svg:', ''));
+ });
+ });
fabric.gradientDefs[svgUid] = fabric.getGradientDefs(doc);
fabric.cssRules[svgUid] = fabric.getCSSRules(doc);
+ fabric.clipPaths[svgUid] = clipPaths;
// Precedence of rules: style > class > attribute
fabric.parseElements(elements, function(instances, elements) {
if (callback) {
callback(instances, options, elements, descendants);
+ delete fabric.gradientDefs[svgUid];
+ delete fabric.cssRules[svgUid];
+ delete fabric.clipPaths[svgUid];
}
}, clone(options), reviver, parsingOptions);
};
@@ -4395,82 +4422,130 @@ fabric.ElementsParser = function(elements, callback, options, reviver, parsingOp
this.regexUrl = /^url\(['"]?#([^'"]+)['"]?\)/g;
};
-fabric.ElementsParser.prototype.parse = function() {
- this.instances = new Array(this.elements.length);
- this.numElements = this.elements.length;
-
- this.createObjects();
-};
-
-fabric.ElementsParser.prototype.createObjects = function() {
- for (var i = 0, len = this.elements.length; i < len; i++) {
- this.elements[i].setAttribute('svgUid', this.svgUid);
- (function(_obj, i) {
- setTimeout(function() {
- _obj.createObject(_obj.elements[i], i);
- }, 0);
- })(this, i);
- }
-};
-
-fabric.ElementsParser.prototype.createObject = function(el, index) {
- var klass = fabric[fabric.util.string.capitalize(el.tagName.replace('svg:', ''))];
- if (klass && klass.fromElement) {
- try {
- this._createObject(klass, el, index);
- }
- catch (err) {
- fabric.log(err);
- }
- }
- else {
- this.checkIfDone();
- }
-};
-
-fabric.ElementsParser.prototype._createObject = function(klass, el, index) {
- klass.fromElement(el, this.createCallback(index, el), this.options);
-};
-
-fabric.ElementsParser.prototype.createCallback = function(index, el) {
- var _this = this;
- return function(obj) {
- var _options;
- _this.resolveGradient(obj, 'fill');
- _this.resolveGradient(obj, 'stroke');
- if (obj instanceof fabric.Image) {
- _options = obj.parsePreserveAspectRatioAttribute(el);
- }
- obj._removeTransformMatrix(_options);
- _this.reviver && _this.reviver(el, obj);
- _this.instances[index] = obj;
- _this.checkIfDone();
+(function(proto) {
+ proto.parse = function() {
+ this.instances = new Array(this.elements.length);
+ this.numElements = this.elements.length;
+ this.createObjects();
};
-};
-fabric.ElementsParser.prototype.resolveGradient = function(obj, property) {
-
- var instanceFillValue = obj[property];
- if (!(/^url\(/).test(instanceFillValue)) {
- return;
- }
- var gradientId = this.regexUrl.exec(instanceFillValue)[1];
- this.regexUrl.lastIndex = 0;
- if (fabric.gradientDefs[this.svgUid][gradientId]) {
- obj.set(property,
- fabric.Gradient.fromElement(fabric.gradientDefs[this.svgUid][gradientId], obj));
- }
-};
-
-fabric.ElementsParser.prototype.checkIfDone = function() {
- if (--this.numElements === 0) {
- this.instances = this.instances.filter(function(el) {
- // eslint-disable-next-line no-eq-null, eqeqeq
- return el != null;
+ proto.createObjects = function() {
+ var _this = this;
+ this.elements.forEach(function(element, i) {
+ element.setAttribute('svgUid', _this.svgUid);
+ _this.createObject(element, i);
});
- this.callback(this.instances, this.elements);
- }
-};
+ };
+
+ proto.findTag = function(el) {
+ return fabric[fabric.util.string.capitalize(el.tagName.replace('svg:', ''))];
+ };
+
+ proto.createObject = function(el, index) {
+ var klass = this.findTag(el);
+ if (klass && klass.fromElement) {
+ try {
+ klass.fromElement(el, this.createCallback(index, el), this.options);
+ }
+ catch (err) {
+ fabric.log(err);
+ }
+ }
+ else {
+ this.checkIfDone();
+ }
+ };
+
+ proto.createCallback = function(index, el) {
+ var _this = this;
+ return function(obj) {
+ var _options;
+ _this.resolveGradient(obj, 'fill');
+ _this.resolveGradient(obj, 'stroke');
+ if (obj instanceof fabric.Image) {
+ _options = obj.parsePreserveAspectRatioAttribute(el);
+ }
+ obj._removeTransformMatrix(_options);
+ _this.resolveClipPath(obj);
+ _this.reviver && _this.reviver(el, obj);
+ _this.instances[index] = obj;
+ _this.checkIfDone();
+ };
+ };
+
+ proto.extractPropertyDefinition = function(obj, property, storage) {
+ var value = obj[property];
+ if (!(/^url\(/).test(value)) {
+ return;
+ }
+ var id = this.regexUrl.exec(value)[1];
+ this.regexUrl.lastIndex = 0;
+ return fabric[storage][this.svgUid][id];
+ };
+
+ proto.resolveGradient = function(obj, property) {
+ var gradientDef = this.extractPropertyDefinition(obj, property, 'gradientDefs');
+ if (gradientDef) {
+ obj.set(property, fabric.Gradient.fromElement(gradientDef, obj));
+ }
+ };
+
+ proto.createClipPathCallback = function(obj, container) {
+ return function(_newObj) {
+ _newObj._removeTransformMatrix();
+ _newObj.fillRule = _newObj.clipRule;
+ container.push(_newObj);
+ };
+ };
+
+ proto.resolveClipPath = function(obj) {
+ var clipPath = this.extractPropertyDefinition(obj, 'clipPath', 'clipPaths'),
+ element, klass, objTransformInv, container, gTransform, options;
+ if (clipPath) {
+ container = [];
+ objTransformInv = fabric.util.invertTransform(obj.calcTransformMatrix());
+ for (var i = 0; i < clipPath.length; i++) {
+ element = clipPath[i];
+ klass = this.findTag(element);
+ klass.fromElement(
+ element,
+ this.createClipPathCallback(obj, container),
+ this.options
+ );
+ }
+ if (container.length === 1) {
+ clipPath = container[0];
+ }
+ else {
+ clipPath = new fabric.Group(container);
+ }
+ gTransform = fabric.util.multiplyTransformMatrices(
+ objTransformInv,
+ clipPath.calcTransformMatrix()
+ );
+ var options = fabric.util.qrDecompose(gTransform);
+ clipPath.flipX = false;
+ clipPath.flipY = false;
+ clipPath.set('scaleX', options.scaleX);
+ clipPath.set('scaleY', options.scaleY);
+ clipPath.angle = options.angle;
+ clipPath.skewX = options.skewX;
+ clipPath.skewY = 0;
+ clipPath.setPositionByOrigin({ x: options.translateX, y: options.translateY }, 'center', 'center');
+ obj.clipPath = clipPath;
+ }
+ };
+
+ proto.checkIfDone = function() {
+ if (--this.numElements === 0) {
+ this.instances = this.instances.filter(function(el) {
+ // eslint-disable-next-line no-eq-null, eqeqeq
+ return el != null;
+ });
+ this.callback(this.instances, this.elements);
+ }
+ };
+})(fabric.ElementsParser.prototype);
(function(global) {
@@ -6668,6 +6743,15 @@ fabric.ElementsParser.prototype.checkIfDone = function() {
*/
skipOffscreen: true,
+ /**
+ * a fabricObject that, without stroke define a clipping area with their shape. filled in black
+ * the clipPath object gets used when the canvas has rendered, and the context is placed in the
+ * top left corner of the canvas.
+ * clipPath will clip away controls, if you do not want this to happen use controlsAboveOverlay = true
+ * @type fabric.Object
+ */
+ clipPath: undefined,
+
/**
* @private
* @param {HTMLElement | String} el <canvas> element to initialize instance on
@@ -7363,11 +7447,11 @@ fabric.ElementsParser.prototype.checkIfDone = function() {
* @chainable
*/
renderCanvas: function(ctx, objects) {
- var v = this.viewportTransform;
+ var v = this.viewportTransform, path = this.clipPath;
this.cancelRequestedRender();
this.calcViewportBoundaries();
this.clearContext(ctx);
- this.fire('before:render');
+ this.fire('before:render', { ctx: ctx, });
if (this.clipTo) {
fabric.util.clipContext(this, ctx);
}
@@ -7384,11 +7468,38 @@ fabric.ElementsParser.prototype.checkIfDone = function() {
if (this.clipTo) {
ctx.restore();
}
+ if (path) {
+ if (path.isCacheDirty()) {
+ // needed to setup a couple of variables
+ path.shouldCache();
+ path.canvas = this;
+ path._transformDone = true;
+ path.renderCache({ forClipping: true });
+ }
+ this.drawClipPathOnCanvas(ctx);
+ }
this._renderOverlay(ctx);
if (this.controlsAboveOverlay && this.interactive) {
this.drawControls(ctx);
}
- this.fire('after:render');
+ this.fire('after:render', { ctx: ctx, });
+ },
+
+ /**
+ * Paint the cached clipPath on the lowerCanvasEl
+ * @param {CanvasRenderingContext2D} ctx Context to render on
+ */
+ drawClipPathOnCanvas: function(ctx) {
+ var v = this.viewportTransform, path = this.clipPath;
+ ctx.save();
+ ctx.transform(v[0], v[1], v[2], v[3], v[4], v[5]);
+ // DEBUG: uncomment this line, comment the following
+ // ctx.globalAlpha = 0.4
+ ctx.globalCompositeOperation = 'destination-in';
+ path.transform(ctx);
+ ctx.scale(1 / path.zoomX, 1 / path.zoomY);
+ ctx.drawImage(path._cacheCanvas, -path.cacheTranslationX, -path.cacheTranslationY);
+ ctx.restore();
},
/**
@@ -7585,11 +7696,13 @@ fabric.ElementsParser.prototype.checkIfDone = function() {
*/
_toObjectMethod: function (methodName, propertiesToInclude) {
- var data = {
+ var clipPath = this.clipPath, data = {
version: fabric.version,
- objects: this._toObjects(methodName, propertiesToInclude)
+ objects: this._toObjects(methodName, propertiesToInclude),
};
-
+ if (clipPath) {
+ clipPath = clipPath.toObject(propertiesToInclude);
+ }
extend(data, this.__serializeBgOverlay(methodName, propertiesToInclude));
fabric.util.populateWithProperties(this, data, propertiesToInclude);
@@ -12867,6 +12980,36 @@ fabric.util.object.extend(fabric.StaticCanvas.prototype, /** @lends fabric.Stati
' strokeLineCap strokeLineJoin strokeMiterLimit backgroundColor'
).split(' '),
+ /**
+ * a fabricObject that, without stroke define a clipping area with their shape. filled in black
+ * the clipPath object gets used when the object has rendered, and the context is placed in the center
+ * of the object cacheCanvas.
+ * If you want 0,0 of a clipPath to align with an object center, use clipPath.originX/Y to 'center'
+ * @type fabric.Object
+ */
+ clipPath: undefined,
+
+ /**
+ * Meaningfull ONLY when the object is used as clipPath.
+ * if true, the clipPath will make the object clip to the outside of the clipPath
+ * since 2.4.0
+ * @type boolean
+ * @default false
+ */
+ inverted: false,
+
+ /**
+ * Meaningfull ONLY when the object is used as clipPath.
+ * if true, the clipPath will have its top and left relative to canvas, and will
+ * not be influenced by the object transform. This will make the clipPath relative
+ * to the canvas, but clipping just a particular object.
+ * WARNING this is beta, this feature may change or be renamed.
+ * since 2.4.0
+ * @type boolean
+ * @default false
+ */
+ absolutePositioned: false,
+
/**
* Constructor
* @param {Object} [options] Options object
@@ -12939,8 +13082,8 @@ fabric.util.object.extend(fabric.StaticCanvas.prototype, /** @lends fabric.Stati
* Return the dimension and the zoom level needed to create a cache canvas
* big enough to host the object to be cached.
* @private
- * @param {Object} dim.x width of object to be cached
- * @param {Object} dim.y height of object to be cached
+ * @return {Object}.x width of object to be cached
+ * @return {Object}.y height of object to be cached
* @return {Object}.width width of canvas
* @return {Object}.height height of canvas
* @return {Object}.zoomX zoomX zoom value to unscale the canvas before drawing cache
@@ -12972,9 +13115,10 @@ fabric.util.object.extend(fabric.StaticCanvas.prototype, /** @lends fabric.Stati
* @return {Boolean} true if the canvas has been resized
*/
_updateCacheCanvas: function() {
- if (this.noScaleCache && this.canvas && this.canvas._currentTransform) {
- var target = this.canvas._currentTransform.target,
- action = this.canvas._currentTransform.action;
+ var targetCanvas = this.canvas;
+ if (this.noScaleCache && targetCanvas && targetCanvas._currentTransform) {
+ var target = targetCanvas._currentTransform.target,
+ action = targetCanvas._currentTransform.action;
if (this === target && action.slice && action.slice(0, 5) === 'scale') {
return false;
}
@@ -13091,9 +13235,15 @@ fabric.util.object.extend(fabric.StaticCanvas.prototype, /** @lends fabric.Stati
globalCompositeOperation: this.globalCompositeOperation,
transformMatrix: this.transformMatrix ? this.transformMatrix.concat() : null,
skewX: toFixed(this.skewX, NUM_FRACTION_DIGITS),
- skewY: toFixed(this.skewY, NUM_FRACTION_DIGITS)
+ skewY: toFixed(this.skewY, NUM_FRACTION_DIGITS),
};
+ if (this.clipPath) {
+ object.clipPath = this.clipPath.toObject(propertiesToInclude);
+ object.clipPath.inverted = this.clipPath.inverted;
+ object.clipPath.absolutePositioned = this.clipPath.absolutePositioned;
+ }
+
fabric.util.populateWithProperties(this, object, propertiesToInclude);
if (!this.includeDefaultValues) {
object = this._removeDefaultValues(object);
@@ -13284,15 +13434,7 @@ fabric.util.object.extend(fabric.StaticCanvas.prototype, /** @lends fabric.Stati
}
this.clipTo && fabric.util.clipContext(this, ctx);
if (this.shouldCache()) {
- if (!this._cacheCanvas) {
- this._createCacheCanvas();
-
- }
- if (this.isCacheDirty()) {
- this.statefullCache && this.saveState({ propertySet: 'cacheProperties' });
- this.drawObject(this._cacheContext);
- this.dirty = false;
- }
+ this.renderCache();
this.drawCacheOnCanvas(ctx);
}
else {
@@ -13307,6 +13449,18 @@ fabric.util.object.extend(fabric.StaticCanvas.prototype, /** @lends fabric.Stati
ctx.restore();
},
+ renderCache: function(options) {
+ options = options || {};
+ if (!this._cacheCanvas) {
+ this._createCacheCanvas();
+ }
+ if (this.isCacheDirty(false)) {
+ this.statefullCache && this.saveState({ propertySet: 'cacheProperties' });
+ this.drawObject(this._cacheContext, options.forClipping);
+ this.dirty = false;
+ }
+ },
+
/**
* Remove cacheCanvas and its dimensions from the objects
*/
@@ -13328,6 +13482,9 @@ fabric.util.object.extend(fabric.StaticCanvas.prototype, /** @lends fabric.Stati
if (this.paintFirst === 'stroke' && typeof this.shadow === 'object') {
return true;
}
+ if (this.clipPath) {
+ return true;
+ }
return false;
},
@@ -13354,15 +13511,61 @@ fabric.util.object.extend(fabric.StaticCanvas.prototype, /** @lends fabric.Stati
return !!this.shadow && (this.shadow.offsetX !== 0 || this.shadow.offsetY !== 0);
},
+ /**
+ * Execute the drawing operation for an object clipPath
+ * @param {CanvasRenderingContext2D} ctx Context to render on
+ */
+ drawClipPathOnCache: function(ctx) {
+ var path = this.clipPath;
+ ctx.save();
+ // DEBUG: uncomment this line, comment the following
+ // ctx.globalAlpha = 0.4
+ if (path.inverted) {
+ ctx.globalCompositeOperation = 'destination-out';
+ }
+ else {
+ ctx.globalCompositeOperation = 'destination-in';
+ }
+ //ctx.scale(1 / 2, 1 / 2);
+ if (path.absolutePositioned) {
+ var m = fabric.util.invertTransform(this.calcTransformMatrix());
+ ctx.transform(m[0], m[1], m[2], m[3], m[4], m[5]);
+ }
+ path.transform(ctx);
+ ctx.scale(1 / path.zoomX, 1 / path.zoomY);
+ ctx.drawImage(path._cacheCanvas, -path.cacheTranslationX, -path.cacheTranslationY);
+ ctx.restore();
+ },
+
/**
* Execute the drawing operation for an object on a specified context
* @param {CanvasRenderingContext2D} ctx Context to render on
*/
- drawObject: function(ctx) {
- this._renderBackground(ctx);
- this._setStrokeStyles(ctx, this);
- this._setFillStyles(ctx, this);
+ drawObject: function(ctx, forClipping) {
+
+ if (forClipping) {
+ this._setClippingProperties(ctx);
+ }
+ else {
+ this._renderBackground(ctx);
+ this._setStrokeStyles(ctx, this);
+ this._setFillStyles(ctx, this);
+ }
this._render(ctx);
+ this._drawClipPath(ctx);
+ },
+
+ _drawClipPath: function(ctx) {
+ var path = this.clipPath;
+ if (!path) { return; }
+ // needed to setup a couple of variables
+ // path canvas gets overridden with this one.
+ // TODO find a better solution?
+ path.canvas = this.canvas;
+ path.shouldCache();
+ path._transformDone = true;
+ path.renderCache({ forClipping: true });
+ this.drawClipPathOnCache(ctx);
},
/**
@@ -13388,7 +13591,10 @@ fabric.util.object.extend(fabric.StaticCanvas.prototype, /** @lends fabric.Stati
return true;
}
else {
- if (this.dirty || (this.statefullCache && this.hasStateChanged('cacheProperties'))) {
+ if (this.dirty ||
+ (this.clipPath && this.clipPath.absolutePositioned) ||
+ (this.statefullCache && this.hasStateChanged('cacheProperties'))
+ ) {
if (this._cacheCanvas && !skipCanvas) {
var width = this.cacheWidth / this.zoomX;
var height = this.cacheHeight / this.zoomY;
@@ -13456,6 +13662,12 @@ fabric.util.object.extend(fabric.StaticCanvas.prototype, /** @lends fabric.Stati
}
},
+ _setClippingProperties: function(ctx) {
+ ctx.globalAlpha = 1;
+ ctx.strokeStyle = 'transparent';
+ ctx.fillStyle = '#000000';
+ },
+
/**
* @private
* Sets line dash
@@ -14067,8 +14279,11 @@ fabric.util.object.extend(fabric.StaticCanvas.prototype, /** @lends fabric.Stati
if (typeof patterns[1] !== 'undefined') {
object.stroke = patterns[1];
}
- var instance = extraParam ? new klass(object[extraParam], object) : new klass(object);
- callback && callback(instance);
+ fabric.util.enlivenObjects([object.clipPath], function(enlivedProps) {
+ object.clipPath = enlivedProps[0];
+ var instance = extraParam ? new klass(object[extraParam], object) : new klass(object);
+ callback && callback(instance);
+ });
});
};
@@ -15179,8 +15394,11 @@ fabric.util.object.extend(fabric.Object.prototype, /** @lends fabric.Object.prot
* Returns id attribute for svg output
* @return {String}
*/
- getSvgId: function() {
- return this.id ? 'id="' + this.id + '" ' : '';
+ getSvgCommons: function() {
+ return [
+ this.id ? 'id="' + this.id + '" ' : '',
+ this.clipPath ? 'clip-path="url(#' + this.clipPath.clipPathId + ')" ' : '',
+ ].join('');
},
/**
@@ -15256,7 +15474,7 @@ fabric.util.object.extend(fabric.Object.prototype, /** @lends fabric.Object.prot
* @private
*/
_createBaseSVGMarkup: function() {
- var markup = [];
+ var markup = [], clipPath = this.clipPath;
if (this.fill && this.fill.toLive) {
markup.push(this.fill.toSVG(this, false));
@@ -15267,6 +15485,14 @@ fabric.util.object.extend(fabric.Object.prototype, /** @lends fabric.Object.prot
if (this.shadow) {
markup.push(this.shadow.toSVG(this));
}
+ if (clipPath) {
+ clipPath.clipPathId = 'CLIPPATH_' + fabric.Object.__uid++;
+ markup.push(
+ '\n\t',
+ this.clipPath.toSVG(),
+ '\n'
+ );
+ }
return markup;
},
@@ -16283,7 +16509,7 @@ fabric.util.object.extend(fabric.Object.prototype, /** @lends fabric.Object.prot
var markup = this._createBaseSVGMarkup(),
p = this.calcLinePoints();
markup.push(
- '\n');
- var imageMarkup = ['\t\n',
textAndBg.textBgRects.join(''),
'\t\t/g,">")},graphemeSplit:function(t){var e,i=0,r=[];for(i=0;i/i,"")));e&&e.documentElement||n&&n(null);C.parseSVGDocument(e.documentElement,function(t,e,i,r){n&&n(t,e,i,r)},i,r)}})},loadSVGFromString:function(t,n,e,i){var r;if(t=t.trim(),"undefined"!=typeof DOMParser){var s=new DOMParser;s&&s.parseFromString&&(r=s.parseFromString(t,"text/xml"))}else C.window.ActiveXObject&&((r=new ActiveXObject("Microsoft.XMLDOM")).async="false",r.loadXML(t.replace(//i,"")));C.parseSVGDocument(r.documentElement,function(t,e,i,r){n(t,e,i,r)},e,i)}})}("undefined"!=typeof exports?exports:this),fabric.ElementsParser=function(t,e,i,r,n){this.elements=t,this.callback=e,this.options=i,this.reviver=r,this.svgUid=i&&i.svgUid||0,this.parsingOptions=n,this.regexUrl=/^url\(['"]?#([^'"]+)['"]?\)/g},fabric.ElementsParser.prototype.parse=function(){this.instances=new Array(this.elements.length),this.numElements=this.elements.length,this.createObjects()},fabric.ElementsParser.prototype.createObjects=function(){for(var t=0,e=this.elements.length;tt.x&&this.y>t.y},gte:function(t){return this.x>=t.x&&this.y>=t.y},lerp:function(t,e){return void 0===e&&(e=.5),e=Math.max(Math.min(1,e),0),new i(this.x+(t.x-this.x)*e,this.y+(t.y-this.y)*e)},distanceFrom:function(t){var e=this.x-t.x,i=this.y-t.y;return Math.sqrt(e*e+i*i)},midPointFrom:function(t){return this.lerp(t)},min:function(t){return new i(Math.min(this.x,t.x),Math.min(this.y,t.y))},max:function(t){return new i(Math.max(this.x,t.x),Math.max(this.y,t.y))},toString:function(){return this.x+","+this.y},setXY:function(t,e){return this.x=t,this.y=e,this},setX:function(t){return this.x=t,this},setY:function(t){return this.y=t,this},setFromPoint:function(t){return this.x=t.x,this.y=t.y,this},swap:function(t){var e=this.x,i=this.y;this.x=t.x,this.y=t.y,t.x=e,t.y=i},clone:function(){return new i(this.x,this.y)}}}("undefined"!=typeof exports?exports:this),function(t){"use strict";var f=t.fabric||(t.fabric={});function d(t){this.status=t,this.points=[]}f.Intersection?f.warn("fabric.Intersection is already defined"):(f.Intersection=d,f.Intersection.prototype={constructor:d,appendPoint:function(t){return this.points.push(t),this},appendPoints:function(t){return this.points=this.points.concat(t),this}},f.Intersection.intersectLineLine=function(t,e,i,r){var n,s=(r.x-i.x)*(t.y-i.y)-(r.y-i.y)*(t.x-i.x),o=(e.x-t.x)*(t.y-i.y)-(e.y-t.y)*(t.x-i.x),a=(r.y-i.y)*(e.x-t.x)-(r.x-i.x)*(e.y-t.y);if(0!==a){var h=s/a,c=o/a;0<=h&&h<=1&&0<=c&&c<=1?(n=new d("Intersection")).appendPoint(new f.Point(t.x+h*(e.x-t.x),t.y+h*(e.y-t.y))):n=new d}else n=new d(0===s||0===o?"Coincident":"Parallel");return n},f.Intersection.intersectLinePolygon=function(t,e,i){var r,n,s,o,a=new d,h=i.length;for(o=0;os.r2,h=t.width/2,c=t.height/2;for(var l in o.sort(function(t,e){return t.offset-e.offset}),"path"===t.type&&(h-=t.pathOffset.x,c-=t.pathOffset.y),s)"x1"===l||"x2"===l?s[l]+=this.offsetX-h:"y1"!==l&&"y2"!==l||(s[l]+=this.offsetY-c);if(n='id="SVGID_'+this.id+'" gradientUnits="userSpaceOnUse"',this.gradientTransform&&(n+=' gradientTransform="matrix('+this.gradientTransform.join(" ")+')" '),"linear"===this.type?r=["\n']:"radial"===this.type&&(r=["\n']),"radial"===this.type){if(a)for((o=o.concat()).reverse(),e=0,i=o.length;e\n')}return r.push("linear"===this.type?"\n":"\n"),r.join("")},toLive:function(t){var e,i,r,n=fabric.util.object.clone(this.coords);if(this.type){for("linear"===this.type?e=t.createLinearGradient(n.x1,n.y1,n.x2,n.y2):"radial"===this.type&&(e=t.createRadialGradient(n.x1,n.y1,n.r1,n.x2,n.y2,n.r2)),i=0,r=this.colorStops.length;i\n\n\n'},setOptions:function(t){for(var e in t)this[e]=t[e]},toLive:function(t){var e="function"==typeof this.source?this.source():this.source;if(!e)return"";if(void 0!==e.src){if(!e.complete)return"";if(0===e.naturalWidth||0===e.naturalHeight)return""}return t.createPattern(e,this.repeat)}})}(),function(t){"use strict";var o=t.fabric||(t.fabric={}),a=o.util.toFixed;o.Shadow?o.warn("fabric.Shadow is already defined."):(o.Shadow=o.util.createClass({color:"rgb(0,0,0)",blur:0,offsetX:0,offsetY:0,affectStroke:!1,includeDefaultValues:!0,initialize:function(t){for(var e in"string"==typeof t&&(t=this._parseShadow(t)),t)this[e]=t[e];this.id=o.Object.__uid++},_parseShadow:function(t){var e=t.trim(),i=o.Shadow.reOffsetsAndBlur.exec(e)||[];return{color:(e.replace(o.Shadow.reOffsetsAndBlur,"")||"rgb(0,0,0)").trim(),offsetX:parseInt(i[1],10)||0,offsetY:parseInt(i[2],10)||0,blur:parseInt(i[3],10)||0}},toString:function(){return[this.offsetX,this.offsetY,this.blur,this.color].join("px ")},toSVG:function(t){var e=40,i=40,r=o.Object.NUM_FRACTION_DIGITS,n=o.util.rotateVector({x:this.offsetX,y:this.offsetY},o.util.degreesToRadians(-t.angle)),s=new o.Color(this.color);return t.width&&t.height&&(e=100*a((Math.abs(n.x)+this.blur)/t.width,r)+20,i=100*a((Math.abs(n.y)+this.blur)/t.height,r)+20),t.flipX&&(n.x*=-1),t.flipY&&(n.y*=-1),'\n\t\n\t\n\t\n\t\n\t\n\t\t\n\t\t\n\t\n\n'},toObject:function(){if(this.includeDefaultValues)return{color:this.color,blur:this.blur,offsetX:this.offsetX,offsetY:this.offsetY,affectStroke:this.affectStroke};var e={},i=o.Shadow.prototype;return["color","blur","offsetX","offsetY","affectStroke"].forEach(function(t){this[t]!==i[t]&&(e[t]=this[t])},this),e}}),o.Shadow.reOffsetsAndBlur=/(?:\s|^)(-?\d+(?:px)?(?:\s?|$))?(-?\d+(?:px)?(?:\s?|$))?(\d+(?:px)?)?(?:\s?|$)(?:$|\s)/)}("undefined"!=typeof exports?exports:this),function(){"use strict";if(fabric.StaticCanvas)fabric.warn("fabric.StaticCanvas is already defined.");else{var r=fabric.util.object.extend,t=fabric.util.getElementOffset,c=fabric.util.removeFromArray,a=fabric.util.toFixed,s=fabric.util.transformPoint,o=fabric.util.invertTransform,e=new Error("Could not initialize `canvas` element");fabric.StaticCanvas=fabric.util.createClass(fabric.CommonMethods,{initialize:function(t,e){e||(e={}),this.renderAndResetBound=this.renderAndReset.bind(this),this.requestRenderAllBound=this.requestRenderAll.bind(this),this._initStatic(t,e)},backgroundColor:"",backgroundImage:null,overlayColor:"",overlayImage:null,includeDefaultValues:!0,stateful:!1,renderOnAddRemove:!0,clipTo:null,controlsAboveOverlay:!1,allowTouchScrolling:!1,imageSmoothingEnabled:!0,viewportTransform:fabric.iMatrix.concat(),backgroundVpt:!0,overlayVpt:!0,onBeforeScaleRotate:function(){},enableRetinaScaling:!0,vptCoords:{},skipOffscreen:!0,_initStatic:function(t,e){var i=this.requestRenderAllBound;this._objects=[],this._createLowerCanvas(t),this._initOptions(e),this._setImageSmoothing(),this.interactive||this._initRetinaScaling(),e.overlayImage&&this.setOverlayImage(e.overlayImage,i),e.backgroundImage&&this.setBackgroundImage(e.backgroundImage,i),e.backgroundColor&&this.setBackgroundColor(e.backgroundColor,i),e.overlayColor&&this.setOverlayColor(e.overlayColor,i),this.calcOffset()},_isRetinaScaling:function(){return 1!==fabric.devicePixelRatio&&this.enableRetinaScaling},getRetinaScaling:function(){return this._isRetinaScaling()?fabric.devicePixelRatio:1},_initRetinaScaling:function(){this._isRetinaScaling()&&(this.lowerCanvasEl.setAttribute("width",this.width*fabric.devicePixelRatio),this.lowerCanvasEl.setAttribute("height",this.height*fabric.devicePixelRatio),this.contextContainer.scale(fabric.devicePixelRatio,fabric.devicePixelRatio))},calcOffset:function(){return this._offset=t(this.lowerCanvasEl),this},setOverlayImage:function(t,e,i){return this.__setBgOverlayImage("overlayImage",t,e,i)},setBackgroundImage:function(t,e,i){return this.__setBgOverlayImage("backgroundImage",t,e,i)},setOverlayColor:function(t,e){return this.__setBgOverlayColor("overlayColor",t,e)},setBackgroundColor:function(t,e){return this.__setBgOverlayColor("backgroundColor",t,e)},_setImageSmoothing:function(){var t=this.getContext();t.imageSmoothingEnabled=t.imageSmoothingEnabled||t.webkitImageSmoothingEnabled||t.mozImageSmoothingEnabled||t.msImageSmoothingEnabled||t.oImageSmoothingEnabled,t.imageSmoothingEnabled=this.imageSmoothingEnabled},__setBgOverlayImage:function(e,t,i,r){return"string"==typeof t?fabric.util.loadImage(t,function(t){t&&(this[e]=new fabric.Image(t,r)),i&&i(t)},this,r&&r.crossOrigin):(r&&t.setOptions(r),this[e]=t,i&&i(t)),this},__setBgOverlayColor:function(t,e,i){return this[t]=e,this._initGradient(e,t),this._initPattern(e,t,i),this},_createCanvasElement:function(){var t=fabric.util.createCanvasElement();if(!t)throw e;if(t.style||(t.style={}),void 0===t.getContext)throw e;return t},_initOptions:function(t){this._setOptions(t),this.width=this.width||parseInt(this.lowerCanvasEl.width,10)||0,this.height=this.height||parseInt(this.lowerCanvasEl.height,10)||0,this.lowerCanvasEl.style&&(this.lowerCanvasEl.width=this.width,this.lowerCanvasEl.height=this.height,this.lowerCanvasEl.style.width=this.width+"px",this.lowerCanvasEl.style.height=this.height+"px",this.viewportTransform=this.viewportTransform.slice())},_createLowerCanvas:function(t){t&&t.getContext?this.lowerCanvasEl=t:this.lowerCanvasEl=fabric.util.getById(t)||this._createCanvasElement(),fabric.util.addClass(this.lowerCanvasEl,"lower-canvas"),this.interactive&&this._applyCanvasStyle(this.lowerCanvasEl),this.contextContainer=this.lowerCanvasEl.getContext("2d")},getWidth:function(){return this.width},getHeight:function(){return this.height},setWidth:function(t,e){return this.setDimensions({width:t},e)},setHeight:function(t,e){return this.setDimensions({height:t},e)},setDimensions:function(t,e){var i;for(var r in e=e||{},t)i=t[r],e.cssOnly||(this._setBackstoreDimension(r,t[r]),i+="px",this.hasLostContext=!0),e.backstoreOnly||this._setCssDimension(r,i);return this._isCurrentlyDrawing&&this.freeDrawingBrush&&this.freeDrawingBrush._setBrushStyles(),this._initRetinaScaling(),this._setImageSmoothing(),this.calcOffset(),e.cssOnly||this.requestRenderAll(),this},_setBackstoreDimension:function(t,e){return this.lowerCanvasEl[t]=e,this.upperCanvasEl&&(this.upperCanvasEl[t]=e),this.cacheCanvasEl&&(this.cacheCanvasEl[t]=e),this[t]=e,this},_setCssDimension:function(t,e){return this.lowerCanvasEl.style[t]=e,this.upperCanvasEl&&(this.upperCanvasEl.style[t]=e),this.wrapperEl&&(this.wrapperEl.style[t]=e),this},getZoom:function(){return this.viewportTransform[0]},setViewportTransform:function(t){var e,i,r,n=this._activeObject;for(this.viewportTransform=t,i=0,r=this._objects.length;i"),i.join("")},_setSVGPreamble:function(t,e){e.suppressPreamble||t.push('\n','\n')},_setSVGHeader:function(t,e){var i,r=e.width||this.width,n=e.height||this.height,s='viewBox="0 0 '+this.width+" "+this.height+'" ',o=fabric.Object.NUM_FRACTION_DIGITS;e.viewBox?s='viewBox="'+e.viewBox.x+" "+e.viewBox.y+" "+e.viewBox.width+" "+e.viewBox.height+'" ':this.svgViewportTransformation&&(i=this.viewportTransform,s='viewBox="'+a(-i[4]/i[0],o)+" "+a(-i[5]/i[3],o)+" "+a(this.width/i[0],o)+" "+a(this.height/i[3],o)+'" '),t.push("