diff --git a/CHANGELOG.md b/CHANGELOG.md
index 156bea65e22..7dd3ac446c6 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,5 +1,11 @@
# Changelog
+## [2.4.2]
+- Fix: Better toSVG support to enable clipPath [#5284](https://github.com/fabricjs/fabric.js/pull/5284)
+- Fix: Per pixel target find and groups and sub targets [#5287](https://github.com/fabricjs/fabric.js/pull/5287)
+- Fix: Object clone as Image and shadow clipping [#5308](https://github.com/fabricjs/fabric.js/pull/5308)
+- Fix: IE11 loading SVG [#5307](https://github.com/fabricjs/fabric.js/pull/5307)
+
## [2.4.1]
- Fix: Avoid enterEditing if another object is the activeObject [#5261](https://github.com/fabricjs/fabric.js/pull/5261)
- Fix: clipPath enliving for Image fromObject [#5279](https://github.com/fabricjs/fabric.js/pull/5279)
diff --git a/HEADER.js b/HEADER.js
index 1b2fe70f8a2..f465f9c668f 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.4.1' };
+var fabric = fabric || { version: '2.4.2' };
if (typeof exports !== 'undefined') {
exports.fabric = fabric;
}
diff --git a/dist/fabric.js b/dist/fabric.js
index f0923458154..507f33f0e38 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.4.1' };
+var fabric = fabric || { version: '2.4.2' };
if (typeof exports !== 'undefined') {
exports.fabric = fabric;
}
@@ -1081,7 +1081,7 @@ fabric.CommonMethods = {
*/
groupSVGElements: function(elements, options, path) {
var object;
- if (elements.length === 1) {
+ if (elements && elements.length === 1) {
return elements[0];
}
if (options) {
@@ -2720,24 +2720,6 @@ fabric.CommonMethods = {
return url + (/\?/.test(url) ? '&' : '?') + param;
}
- var makeXHR = (function() {
- var factories = [
- function() { return new fabric.window.XMLHttpRequest(); },
- function() { return new ActiveXObject('Microsoft.XMLHTTP'); },
- function() { return new ActiveXObject('Msxml2.XMLHTTP'); },
- function() { return new ActiveXObject('Msxml2.XMLHTTP.3.0'); }
- ];
- for (var i = factories.length; i--; ) {
- try {
- var req = factories[i]();
- if (req) {
- return factories[i];
- }
- }
- catch (err) { }
- }
- })();
-
function emptyFn() { }
/**
@@ -2756,7 +2738,7 @@ fabric.CommonMethods = {
var method = options.method ? options.method.toUpperCase() : 'GET',
onComplete = options.onComplete || function() { },
- xhr = makeXHR(),
+ xhr = new fabric.window.XMLHttpRequest(),
body = options.body || options.parameters;
/** @ignore */
@@ -3823,7 +3805,6 @@ if (typeof console !== 'undefined') {
*/
function parseUseDirectives(doc) {
var nodelist = _getMultipleNodes(doc, ['use', 'svg:use']), i = 0;
-
while (nodelist.length && i < nodelist.length) {
var el = nodelist[i],
xlink = (el.getAttribute('xlink:href') || el.getAttribute('href')).substr(1),
@@ -6555,6 +6536,8 @@ fabric.ElementsParser = function(elements, callback, options, reviver, parsingOp
toFixed = fabric.util.toFixed,
transformPoint = fabric.util.transformPoint,
invertTransform = fabric.util.invertTransform,
+ getNodeCanvas = fabric.util.getNodeCanvas,
+ createCanvasElement = fabric.util.createCanvasElement,
CANVAS_INIT_ERROR = new Error('Could not initialize `canvas` element');
@@ -6600,6 +6583,9 @@ fabric.ElementsParser = function(elements, callback, options, reviver, parsingOp
* Backwards incompatibility note: The "backgroundImageOpacity"
* and "backgroundImageStretch" properties are deprecated since 1.3.9.
* Use {@link fabric.Image#opacity}, {@link fabric.Image#width} and {@link fabric.Image#height}.
+ * since 2.4.0 image caching is active, please when putting an image as background, add to the
+ * canvas property a reference to the canvas it is on. Otherwise the image cannot detect the zoom
+ * vale. As an alternative you can disable image objectCaching
* @type fabric.Image
* @default
*/
@@ -6620,6 +6606,9 @@ fabric.ElementsParser = function(elements, callback, options, reviver, parsingOp
* Backwards incompatibility note: The "overlayImageLeft"
* and "overlayImageTop" properties are deprecated since 1.3.9.
* Use {@link fabric.Image#left} and {@link fabric.Image#top}.
+ * since 2.4.0 image caching is active, please when putting an image as overlay, add to the
+ * canvas property a reference to the canvas it is on. Otherwise the image cannot detect the zoom
+ * vale. As an alternative you can disable image objectCaching
* @type fabric.Image
* @default
*/
@@ -7000,13 +6989,18 @@ fabric.ElementsParser = function(elements, callback, options, reviver, parsingOp
__setBgOverlayImage: function(property, image, callback, options) {
if (typeof image === 'string') {
fabric.util.loadImage(image, function(img) {
- img && (this[property] = new fabric.Image(img, options));
+ if (img) {
+ var instance = new fabric.Image(img, options);
+ this[property] = instance;
+ instance.canvas = this;
+ }
callback && callback(img);
}, this, options && options.crossOrigin);
}
else {
options && image.setOptions(options);
this[property] = image;
+ image && (image.canvas = this);
callback && callback(image);
}
@@ -7031,7 +7025,7 @@ fabric.ElementsParser = function(elements, callback, options, reviver, parsingOp
* @private
*/
_createCanvasElement: function() {
- var element = fabric.util.createCanvasElement();
+ var element = createCanvasElement();
if (!element) {
throw CANVAS_INIT_ERROR;
}
@@ -7049,20 +7043,21 @@ fabric.ElementsParser = function(elements, callback, options, reviver, parsingOp
* @param {Object} [options] Options object
*/
_initOptions: function (options) {
+ var lowerCanvasEl = this.lowerCanvasEl;
this._setOptions(options);
- this.width = this.width || parseInt(this.lowerCanvasEl.width, 10) || 0;
- this.height = this.height || parseInt(this.lowerCanvasEl.height, 10) || 0;
+ this.width = this.width || parseInt(lowerCanvasEl.width, 10) || 0;
+ this.height = this.height || parseInt(lowerCanvasEl.height, 10) || 0;
if (!this.lowerCanvasEl.style) {
return;
}
- this.lowerCanvasEl.width = this.width;
- this.lowerCanvasEl.height = this.height;
+ lowerCanvasEl.width = this.width;
+ lowerCanvasEl.height = this.height;
- this.lowerCanvasEl.style.width = this.width + 'px';
- this.lowerCanvasEl.style.height = this.height + 'px';
+ lowerCanvasEl.style.width = this.width + 'px';
+ lowerCanvasEl.style.height = this.height + 'px';
this.viewportTransform = this.viewportTransform.slice();
},
@@ -7815,7 +7810,7 @@ fabric.ElementsParser = function(elements, callback, options, reviver, parsingOp
*/
toSVG: function(options, reviver) {
options || (options = { });
-
+ options.reviver = reviver;
var markup = [];
this._setSVGPreamble(markup, options);
@@ -7823,9 +7818,13 @@ fabric.ElementsParser = function(elements, callback, options, reviver, parsingOp
this._setSVGBgOverlayColor(markup, 'backgroundColor');
this._setSVGBgOverlayImage(markup, 'backgroundImage', reviver);
-
+ if (this.clipPath) {
+ markup.push('\n');
+ }
this._setSVGObjects(markup, reviver);
-
+ if (this.clipPath) {
+ markup.push('\n');
+ }
this._setSVGBgOverlayColor(markup, 'overlayColor');
this._setSVGBgOverlayImage(markup, 'overlayImage', reviver);
@@ -7888,10 +7887,22 @@ fabric.ElementsParser = function(elements, callback, options, reviver, parsingOp
'\n',
this.createSVGFontFacesMarkup(),
this.createSVGRefElementsMarkup(),
+ this.createSVGClipPathMarkup(options),
'\n'
);
},
+ createSVGClipPathMarkup: function(options) {
+ var clipPath = this.clipPath;
+ if (clipPath) {
+ clipPath.clipPathId = 'CLIPPATH_' + fabric.Object.__uid++;
+ return '\n' +
+ this.clipPath.toClipPathSVG(options.reviver) +
+ '\n';
+ }
+ return '';
+ },
+
/**
* Creates markup containing SVG referenced elements like patterns, gradients etc.
* @return {String}
@@ -8310,7 +8321,7 @@ fabric.ElementsParser = function(elements, callback, options, reviver, parsingOp
* `null` if canvas element or context can not be initialized
*/
supports: function (methodName) {
- var el = fabric.util.createCanvasElement();
+ var el = createCanvasElement();
if (!el || !el.getContext) {
return null;
@@ -8365,11 +8376,11 @@ fabric.ElementsParser = function(elements, callback, options, reviver, parsingOp
if (fabric.isLikelyNode) {
fabric.StaticCanvas.prototype.createPNGStream = function() {
- var impl = fabric.util.getNodeCanvas(this.lowerCanvasEl);
+ var impl = getNodeCanvas(this.lowerCanvasEl);
return impl && impl.createPNGStream();
};
fabric.StaticCanvas.prototype.createJPEGStream = function(opts) {
- var impl = fabric.util.getNodeCanvas(this.lowerCanvasEl);
+ var impl = getNodeCanvas(this.lowerCanvasEl);
return impl && impl.createJPEGStream(opts);
};
}
@@ -10373,15 +10384,20 @@ fabric.PatternBrush = fabric.util.createClass(fabric.PencilBrush, /** @lends fab
},
/**
+ * Checks point is inside the object.
+ * @param {Object} [pointer] x,y object of point coordinates we want to check.
+ * @param {fabric.Object} obj Object to test against
+ * @param {Object} [globalPointer] x,y object of point coordinates relative to canvas used to search per pixel target.
+ * @return {Boolean} true if point is contained within an area of given object
* @private
*/
- _checkTarget: function(pointer, obj) {
+ _checkTarget: function(pointer, obj, globalPointer) {
if (obj &&
obj.visible &&
obj.evented &&
this.containsPoint(null, obj, pointer)){
if ((this.perPixelTargetFind || obj.perPixelTargetFind) && !obj.isEditing) {
- var isTransparent = this.isTargetTransparent(obj, pointer.x, pointer.y);
+ var isTransparent = this.isTargetTransparent(obj, globalPointer.x, globalPointer.y);
if (!isTransparent) {
return true;
}
@@ -10393,20 +10409,26 @@ fabric.PatternBrush = fabric.util.createClass(fabric.PencilBrush, /** @lends fab
},
/**
+ * Function used to search inside objects an object that contains pointer in bounding box or that contains pointerOnCanvas when painted
+ * @param {Array} [objects] objects array to look into
+ * @param {Object} [pointer] x,y object of point coordinates we want to check.
+ * @return {fabric.Object} object that contains pointer
* @private
*/
_searchPossibleTargets: function(objects, pointer) {
-
// Cache all targets where their bounding box contains point.
- var target, i = objects.length, normalizedPointer, subTarget;
+ var target, i = objects.length, subTarget;
// Do not check for currently grouped objects, since we check the parent group itself.
// until we call this function specifically to search inside the activeGroup
while (i--) {
- if (this._checkTarget(pointer, objects[i])) {
+ var objToCheck = objects[i];
+ if (this._checkTarget(objToCheck.group && objToCheck.group.type !== 'activeSelection'
+ ? this._normalizePointer(objToCheck.group, pointer)
+ : pointer,
+ objToCheck, pointer)) {
target = objects[i];
if (target.subTargetCheck && target instanceof fabric.Group) {
- normalizedPointer = this._normalizePointer(target, pointer);
- subTarget = this._searchPossibleTargets(target._objects, normalizedPointer);
+ subTarget = this._searchPossibleTargets(target._objects, pointer);
subTarget && this.targets.push(subTarget);
}
break;
@@ -12135,11 +12157,6 @@ fabric.PatternBrush = fabric.util.createClass(fabric.PencilBrush, /** @lends fab
__toDataURL: function(format, quality) {
var canvasEl = this.contextContainer.canvas;
- // to avoid common confusion https://github.com/kangax/fabric.js/issues/806
- if (format === 'jpg') {
- format = 'jpeg';
- }
-
var data = supportQuality
? canvasEl.toDataURL('image/' + format, quality)
: canvasEl.toDataURL('image/' + format);
@@ -13906,7 +13923,16 @@ fabric.util.object.extend(fabric.StaticCanvas.prototype, /** @lends fabric.Stati
* Creates an instance of fabric.Image out of an object
* @param {Function} callback callback, invoked with an instance as a first argument
* @param {Object} [options] for clone as image, passed to toDataURL
- * @param {Boolean} [options.enableRetinaScaling] enable retina scaling for the cloned image
+ * @param {String} [options.format=png] The format of the output image. Either "jpeg" or "png"
+ * @param {Number} [options.quality=1] Quality level (0..1). Only used for jpeg.
+ * @param {Number} [options.multiplier=1] Multiplier to scale by
+ * @param {Number} [options.left] Cropping left offset. Introduced in v1.2.14
+ * @param {Number} [options.top] Cropping top offset. Introduced in v1.2.14
+ * @param {Number} [options.width] Cropping width. Introduced in v1.2.14
+ * @param {Number} [options.height] Cropping height. Introduced in v1.2.14
+ * @param {Boolean} [options.enableRetinaScaling] Enable retina scaling for clone image. Introduce in 1.6.4
+ * @param {Boolean} [options.withoutTransform] Remove current object transform ( no scale , no angle, no flip, no skew ). Introduced in 2.3.4
+ * @param {Boolean} [options.withoutShadow] Remove current object shadow. Introduced in 2.4.2
* @return {fabric.Object} thisArg
*/
cloneAsImage: function(callback, options) {
@@ -13931,42 +13957,52 @@ fabric.util.object.extend(fabric.StaticCanvas.prototype, /** @lends fabric.Stati
* @param {Number} [options.height] Cropping height. Introduced in v1.2.14
* @param {Boolean} [options.enableRetinaScaling] Enable retina scaling for clone image. Introduce in 1.6.4
* @param {Boolean} [options.withoutTransform] Remove current object transform ( no scale , no angle, no flip, no skew ). Introduced in 2.3.4
+ * @param {Boolean} [options.withoutShadow] Remove current object shadow. Introduced in 2.4.2
* @return {String} Returns a data: URL containing a representation of the object in the format specified by options.format
*/
toDataURL: function(options) {
options || (options = { });
- var origParams = fabric.util.saveObjectTransform(this);
+ var utils = fabric.util, origParams = utils.saveObjectTransform(this),
+ originalShadow = this.shadow, abs = Math.abs;
if (options.withoutTransform) {
- fabric.util.resetObjectTransform(this);
+ utils.resetObjectTransform(this);
+ }
+ if (options.withoutShadow) {
+ this.shadow = null;
}
var el = fabric.util.createCanvasElement(),
// skip canvas zoom and calculate with setCoords now.
- boundingRect = this.getBoundingRect(true, true);
-
- el.width = boundingRect.width;
- el.height = boundingRect.height;
+ boundingRect = this.getBoundingRect(true, true),
+ shadow = this.shadow, scaling,
+ shadowOffset = { x: 0, y: 0 }, shadowBlur;
+
+ if (shadow) {
+ shadowBlur = shadow.blur;
+ scaling = this.getObjectScaling();
+ shadowOffset.x = 2 * Math.round((abs(shadow.offsetX) + shadowBlur) * abs(scaling.scaleX));
+ shadowOffset.y = 2 * Math.round((abs(shadow.offsetY) + shadowBlur) * abs(scaling.scaleY));
+ }
+ el.width = boundingRect.width + shadowOffset.x;
+ el.height = boundingRect.height + shadowOffset.y;
+ el.width += el.width % 2 ? 2 - el.width % 2 : 0;
+ el.height += el.height % 2 ? 2 - el.height % 2 : 0;
var canvas = new fabric.StaticCanvas(el, {
enableRetinaScaling: options.enableRetinaScaling,
renderOnAddRemove: false,
skipOffscreen: false,
});
- // to avoid common confusion https://github.com/kangax/fabric.js/issues/806
- if (options.format === 'jpg') {
- options.format = 'jpeg';
- }
-
if (options.format === 'jpeg') {
canvas.backgroundColor = '#fff';
}
-
this.setPositionByOrigin(new fabric.Point(canvas.width / 2, canvas.height / 2), 'center', 'center');
var originalCanvas = this.canvas;
canvas.add(this);
var data = canvas.toDataURL(options);
+ this.shadow = originalShadow;
this.set(origParams).setCoords();
this.canvas = originalCanvas;
// canvas.dispose will call image.dispose that will nullify the elements
@@ -15305,7 +15341,7 @@ fabric.util.object.extend(fabric.Object.prototype, /** @lends fabric.Object.prot
*/
getSvgStyles: function(skipShadow) {
- var fillRule = this.fillRule,
+ var fillRule = this.fillRule ? this.fillRule : 'nonzero',
strokeWidth = this.strokeWidth ? this.strokeWidth : '0',
strokeDashArray = this.strokeDashArray ? this.strokeDashArray.join(' ') : 'none',
strokeLineCap = this.strokeLineCap ? this.strokeLineCap : 'butt',
@@ -15404,45 +15440,16 @@ fabric.util.object.extend(fabric.Object.prototype, /** @lends fabric.Object.prot
/**
* Returns transform-string for svg-export
+ * @param {Boolean} use the full transform or the single object one.
* @return {String}
*/
- getSvgTransform: function() {
- var angle = this.angle,
- skewX = (this.skewX % 360),
- skewY = (this.skewY % 360),
- center = this.getCenterPoint(),
-
- NUM_FRACTION_DIGITS = fabric.Object.NUM_FRACTION_DIGITS,
-
- translatePart = 'translate(' +
- toFixed(center.x, NUM_FRACTION_DIGITS) +
- ' ' +
- toFixed(center.y, NUM_FRACTION_DIGITS) +
- ')',
-
- anglePart = angle !== 0
- ? (' rotate(' + toFixed(angle, NUM_FRACTION_DIGITS) + ')')
- : '',
-
- scalePart = (this.scaleX === 1 && this.scaleY === 1)
- ? '' :
- (' scale(' +
- toFixed(this.scaleX, NUM_FRACTION_DIGITS) +
- ' ' +
- toFixed(this.scaleY, NUM_FRACTION_DIGITS) +
- ')'),
-
- skewXPart = skewX !== 0 ? ' skewX(' + toFixed(skewX, NUM_FRACTION_DIGITS) + ')' : '',
-
- skewYPart = skewY !== 0 ? ' skewY(' + toFixed(skewY, NUM_FRACTION_DIGITS) + ')' : '',
-
- flipXPart = this.flipX ? ' matrix(-1 0 0 1 0 0) ' : '',
-
- flipYPart = this.flipY ? ' matrix(1 0 0 -1 0 0)' : '';
-
- return [
- translatePart, anglePart, scalePart, flipXPart, flipYPart, skewXPart, skewYPart
- ].join('');
+ getSvgTransform: function(full, additionalTransform) {
+ var transform = full ? this.calcTransformMatrix() : this.calcOwnMatrix(),
+ svgTransform = transform.map(function(value) {
+ return toFixed(value, fabric.Object.NUM_FRACTION_DIGITS);
+ }).join(' ');
+ return 'transform="matrix(' + svgTransform + ')' +
+ (additionalTransform || '') + this.getSvgTransformMatrix() + '" ';
},
/**
@@ -15450,7 +15457,7 @@ fabric.util.object.extend(fabric.Object.prototype, /** @lends fabric.Object.prot
* @return {String}
*/
getSvgTransformMatrix: function() {
- return this.transformMatrix ? ' matrix(' + this.transformMatrix.join(' ') + ') ' : '';
+ return this.transformMatrix ? ' matrix(' + this.transformMatrix.join(' ') + ')' : '';
},
_setSVGBg: function(textBgRects) {
@@ -15471,12 +15478,77 @@ fabric.util.object.extend(fabric.Object.prototype, /** @lends fabric.Object.prot
}
},
+ /**
+ * 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) {
+ return this._createBaseSVGMarkup(this._toSVG(), { reviver: reviver });
+ },
+
+ /**
+ * Returns svg clipPath representation of an instance
+ * @param {Function} [reviver] Method for further parsing of svg representation.
+ * @return {String} svg representation of an instance
+ */
+ toClipPathSVG: function(reviver) {
+ return '\t' + this._createBaseClipPathSVGMarkup(this._toSVG(), { reviver: reviver });
+ },
+
/**
* @private
*/
- _createBaseSVGMarkup: function() {
- var markup = [], clipPath = this.clipPath;
+ _createBaseClipPathSVGMarkup: function(objectMarkup, options) {
+ options = options || {};
+ var reviver = options.reviver,
+ additionalTransform = options.additionalTransform || '',
+ commonPieces = [
+ this.getSvgTransform(true, additionalTransform),
+ this.getSvgCommons(),
+ ].join(''),
+ // insert commons in the markup, style and svgCommons
+ index = objectMarkup.indexOf('COMMON_PARTS');
+ objectMarkup[index] = commonPieces;
+ return reviver ? reviver(objectMarkup.join('')) : objectMarkup.join('');
+ },
+ /**
+ * @private
+ */
+ _createBaseSVGMarkup: function(objectMarkup, options) {
+ options = options || {};
+ var noStyle = options.noStyle, withShadow = options.withShadow,
+ reviver = options.reviver,
+ styleInfo = noStyle ? '' : 'style="' + this.getSvgStyles() + '" ',
+ shadowInfo = withShadow ? 'style="' + this.getSvgFilter() + '" ' : '',
+ clipPath = this.clipPath,
+ absoluteClipPath = this.clipPath && this.clipPath.absolutePositioned,
+ commonPieces, markup = [], clipPathMarkup,
+ // insert commons in the markup, style and svgCommons
+ index = objectMarkup.indexOf('COMMON_PARTS');
+ if (clipPath) {
+ clipPath.clipPathId = 'CLIPPATH_' + fabric.Object.__uid++;
+ clipPathMarkup = '\n' +
+ this.clipPath.toClipPathSVG(reviver) +
+ '\n';
+ }
+ if (absoluteClipPath) {
+ markup.push(
+ '\n'
+ );
+ }
+ markup.push(
+ '\n'
+ );
+ commonPieces = [
+ styleInfo,
+ noStyle ? '' : this.addPaintOrder(), ' '
+ ].join('');
+ objectMarkup[index] = commonPieces;
if (this.fill && this.fill.toLive) {
markup.push(this.fill.toSVG(this, false));
}
@@ -15487,14 +15559,12 @@ fabric.util.object.extend(fabric.Object.prototype, /** @lends fabric.Object.prot
markup.push(this.shadow.toSVG(this));
}
if (clipPath) {
- clipPath.clipPathId = 'CLIPPATH_' + fabric.Object.__uid++;
- markup.push(
- '\n\t',
- this.clipPath.toSVG(),
- '\n'
- );
+ markup.push(clipPathMarkup);
}
- return markup;
+ markup.push(objectMarkup.join(''));
+ markup.push('\n');
+ absoluteClipPath && markup.push('\n');
+ return reviver ? reviver(markup.join('')) : markup.join('');
},
addPaintOrder: function() {
@@ -16502,26 +16572,20 @@ fabric.util.object.extend(fabric.Object.prototype, /** @lends fabric.Object.prot
/* _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
+ * Returns svg representation of an instance
+ * @return {Array} an array of strings with the specific svg representation
+ * of the instance
*/
- toSVG: function(reviver) {
- var markup = this._createBaseSVGMarkup(),
- p = this.calcLinePoints();
- markup.push(
- '\n'
- );
-
- return reviver ? reviver(markup.join('')) : markup.join('');
+ '" />\n'
+ ];
},
/* _TO_SVG_END_ */
});
@@ -16681,26 +16745,23 @@ fabric.util.object.extend(fabric.Object.prototype, /** @lends fabric.Object.prot
},
/* _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
+ * @return {Array} an array of strings with the specific svg representation
+ * of the instance
*/
- toSVG: function(reviver) {
- var markup = this._createBaseSVGMarkup(), x = 0, y = 0,
+ _toSVG: function() {
+ var svgString, x = 0, y = 0,
angle = (this.endAngle - this.startAngle) % ( 2 * pi);
if (angle === 0) {
- markup.push(
- '\n'
- );
+ '" />\n'
+ ];
}
else {
var startX = fabric.util.cos(this.startAngle) * this.radius,
@@ -16708,20 +16769,14 @@ fabric.util.object.extend(fabric.Object.prototype, /** @lends fabric.Object.prot
endX = fabric.util.cos(this.endAngle) * this.radius,
endY = fabric.util.sin(this.endAngle) * this.radius,
largeFlag = angle > pi ? '1' : '0';
-
- markup.push(
+ svgString = [
'\n'
- );
+ '"', 'COMMON_PARTS', ' />\n'
+ ];
}
-
- return reviver ? reviver(markup.join('')) : markup.join('');
+ return svgString;
},
/* _TO_SVG_END_ */
@@ -16894,31 +16949,23 @@ fabric.util.object.extend(fabric.Object.prototype, /** @lends fabric.Object.prot
/* _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
+ * Returns svg representation of an instance
+ * @return {Array} an array of strings with the specific svg representation
+ * of the instance
*/
- toSVG: function(reviver) {
- var markup = this._createBaseSVGMarkup(),
- widthBy2 = this.width / 2,
+ _toSVG: function() {
+ var widthBy2 = this.width / 2,
heightBy2 = this.height / 2,
points = [
-widthBy2 + ' ' + heightBy2,
'0 ' + -heightBy2,
widthBy2 + ' ' + heightBy2
- ]
- .join(',');
-
- markup.push(
- ''
- );
-
- return reviver ? reviver(markup.join('')) : markup.join('');
+ '" />'
+ ];
},
/* _TO_SVG_END_ */
});
@@ -17044,24 +17091,17 @@ fabric.util.object.extend(fabric.Object.prototype, /** @lends fabric.Object.prot
/* _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
+ * @return {Array} an array of strings with the specific svg representation
+ * of the instance
*/
- toSVG: function(reviver) {
- var markup = this._createBaseSVGMarkup();
- markup.push(
- '\n'
- );
-
- return reviver ? reviver(markup.join('')) : markup.join('');
+ '" />\n'
+ ];
},
/* _TO_SVG_END_ */
@@ -17273,23 +17313,18 @@ fabric.util.object.extend(fabric.Object.prototype, /** @lends fabric.Object.prot
/* _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
+ * @return {Array} an array of strings with the specific svg representation
+ * of the instance
*/
- toSVG: function(reviver) {
- var markup = this._createBaseSVGMarkup(), x = -this.width / 2, y = -this.height / 2;
- markup.push(
- '\n');
-
- return reviver ? reviver(markup.join('')) : markup.join('');
+ '" />\n'
+ ];
},
/* _TO_SVG_END_ */
});
@@ -17460,12 +17495,11 @@ fabric.util.object.extend(fabric.Object.prototype, /** @lends fabric.Object.prot
/* _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
+ * @return {Array} an array of strings with the specific svg representation
+ * of the instance
*/
- toSVG: function(reviver) {
+ _toSVG: function() {
var points = [], diffX = this.pathOffset.x, diffY = this.pathOffset.y,
- markup = this._createBaseSVGMarkup(),
NUM_FRACTION_DIGITS = fabric.Object.NUM_FRACTION_DIGITS;
for (var i = 0, len = this.points.length; i < len; i++) {
@@ -17474,17 +17508,11 @@ fabric.util.object.extend(fabric.Object.prototype, /** @lends fabric.Object.prot
toFixed(this.points[i].y - diffY, NUM_FRACTION_DIGITS), ' '
);
}
- markup.push(
- '<', this.type, ' ', this.getSvgCommons(),
+ return [
+ '<' + this.type + ' ', 'COMMON_PARTS',
'points="', points.join(''),
- '" style="', this.getSvgStyles(),
- '" transform="', this.getSvgTransform(),
- ' ', this.getSvgTransformMatrix(), '"',
- this.addPaintOrder(),
- '/>\n'
- );
-
- return reviver ? reviver(markup.join('')) : markup.join('');
+ '" />\n'
+ ];
},
/* _TO_SVG_END_ */
@@ -17695,6 +17723,7 @@ fabric.util.object.extend(fabric.Object.prototype, /** @lends fabric.Object.prot
extend = fabric.util.object.extend,
_toString = Object.prototype.toString,
drawArc = fabric.util.drawArc,
+ toFixed = fabric.util.toFixed,
commandLengths = {
m: 2,
l: 2,
@@ -18153,29 +18182,39 @@ fabric.util.object.extend(fabric.Object.prototype, /** @lends fabric.Object.prot
/* _TO_SVG_START_ */
/**
* Returns svg representation of an instance
+ * @return {Array} an array of strings with the specific svg representation
+ * of the instance
+ */
+ _toSVG: function() {
+ var specificTransform = this._getOffsetTransform(),
+ path = this.path.map(function(path) {
+ return path.join(' ');
+ }).join(' ');
+ return [
+ '\n'
+ ];
+ },
+
+ _getOffsetTransform: function() {
+ var digits = fabric.Object.NUM_FRACTION_DIGITS;
+ return ' translate(' + toFixed(-this.pathOffset.x, digits) + ', ' +
+ toFixed(-this.pathOffset.y, digits) + ')';
+ },
+
+ /**
+ * Returns svg clipPath 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 chunks = [],
- markup = this._createBaseSVGMarkup(), addTransform = '';
-
- for (var i = 0, len = this.path.length; i < len; i++) {
- chunks.push(this.path[i].join(' '));
- }
- var path = chunks.join(' ');
- addTransform = ' translate(' + (-this.pathOffset.x) + ', ' + (-this.pathOffset.y) + ') ';
- markup.push(
- '\n'
+ toClipPathSVG: function(reviver) {
+ var additionalTransform = this._getOffsetTransform();
+ return '\t' + this._createBaseClipPathSVGMarkup(
+ this._toSVG(), { reviver: reviver, additionalTransform: additionalTransform }
);
-
- return reviver ? reviver(markup.join('')) : markup.join('');
},
/* _TO_SVG_END_ */
@@ -19132,24 +19171,30 @@ fabric.util.object.extend(fabric.Object.prototype, /** @lends fabric.Object.prot
* @return {String} svg representation of an instance
*/
toSVG: function(reviver) {
- var markup = this._createBaseSVGMarkup();
- markup.push(
- '\n'
- );
+ var svgString = [];
for (var i = 0, len = this._objects.length; i < len; i++) {
- markup.push('\t', this._objects[i].toSVG(reviver));
+ svgString.push('\t', this._objects[i].toSVG(reviver));
}
- markup.push('\n');
+ return this._createBaseSVGMarkup(
+ svgString,
+ { reviver: reviver, noStyle: true, withShadow: true });
+ },
- return reviver ? reviver(markup.join('')) : markup.join('');
+ /**
+ * Returns svg clipPath representation of an instance
+ * @param {Function} [reviver] Method for further parsing of svg representation.
+ * @return {String} svg representation of an instance
+ */
+ toClipPathSVG: function(reviver) {
+ var svgString = [];
+
+ for (var i = 0, len = this._objects.length; i < len; i++) {
+ svgString.push('\t', this._objects[i].toClipPathSVG(reviver));
+ }
+
+ return this._createBaseClipPathSVGMarkup(svgString, { reviver: reviver });
},
/* _TO_SVG_END_ */
});
@@ -19620,53 +19665,51 @@ fabric.util.object.extend(fabric.Object.prototype, /** @lends fabric.Object.prot
/* _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
+ * Returns svg representation of an instance
+ * @return {Array} an array of strings with the specific svg representation
+ * of the instance
*/
- toSVG: function(reviver) {
- var markup = this._createBaseSVGMarkup(), x = -this.width / 2, y = -this.height / 2, clipPath = '';
+ _toSVG: function() {
+ var svgString = [], imageMarkup = [], strokeSvg,
+ x = -this.width / 2, y = -this.height / 2, clipPath = '';
if (this.hasCrop()) {
var clipPathId = fabric.Object.__uid++;
- markup.push(
+ svgString.push(
'\n',
'\t\n',
'\n'
);
clipPath = ' clip-path="url(#imageCrop_' + clipPathId + ')" ';
}
- markup.push('\n');
- var imageMarkup = ['\t\n'];
- if (this.paintFirst === 'fill') {
- Array.prototype.push.apply(markup, imageMarkup);
- }
+ '>\n');
+
if (this.stroke || this.strokeDashArray) {
var origFill = this.fill;
this.fill = null;
- markup.push(
+ strokeSvg = [
'\t\n'
- );
+ ];
this.fill = origFill;
}
if (this.paintFirst !== 'fill') {
- Array.prototype.push.apply(markup, imageMarkup);
+ svgString = svgString.concat(strokeSvg, imageMarkup);
}
- markup.push('\n');
-
- return reviver ? reviver(markup.join('')) : markup.join('');
+ else {
+ svgString = svgString.concat(imageMarkup, strokeSvg);
+ }
+ return svgString;
},
/* _TO_SVG_END_ */
@@ -19819,6 +19862,23 @@ fabric.util.object.extend(fabric.Object.prototype, /** @lends fabric.Object.prot
this._renderPaintInOrder(ctx);
},
+ /**
+ * Decide if the object should cache or not. Create its own cache level
+ * objectCaching is a global flag, wins over everything
+ * needsItsOwnCache should be used when the object drawing method requires
+ * a cache step. None of the fabric classes requires it.
+ * Generally you do not cache objects in groups because the group outside is cached.
+ * This is the special image version where we would like to avoid caching where possible.
+ * Essentially images do not benefit from caching. They may require caching, and in that
+ * case we do it. Also caching an image usually ends in a loss of details.
+ * A full performance audit should be done.
+ * @return {Boolean}
+ */
+ shouldCache: function() {
+ this.ownCaching = this.objectCaching && this.needsItsOwnCache();
+ return this.ownCaching;
+ },
+
_renderFill: function(ctx) {
var w = this.width, h = this.height, sW = w * this._filterScalingX, sH = h * this._filterScalingY,
x = -w / 2, y = -h / 2, elementToDraw = this._element;
@@ -22961,8 +23021,8 @@ fabric.Image.filters.BaseFilter.fromObject = function(object, callback) {
/**
* Resize type
- * for webgl resizyType is just lanczos, for canvas2d can be:
- * bilinear, hermite, sliceHacl, lanczos.
+ * for webgl resizeType is just lanczos, for canvas2d can be:
+ * bilinear, hermite, sliceHack, lanczos.
* @param {String} resizeType
* @default
*/
@@ -28296,12 +28356,11 @@ fabric.util.object.extend(fabric.IText.prototype, /** @lends fabric.IText.protot
* @return {String} svg representation of an instance
*/
toSVG: function(reviver) {
- var markup = this._createBaseSVGMarkup(),
- offsets = this._getSVGLeftTopOffsets(),
- textAndBg = this._getSVGTextAndBg(offsets.textTop, offsets.textLeft);
- this._wrapSVGTextAndBg(markup, textAndBg);
-
- return reviver ? reviver(markup.join('')) : markup.join('');
+ var offsets = this._getSVGLeftTopOffsets(),
+ textAndBg = this._getSVGTextAndBg(offsets.textTop, offsets.textLeft),
+ internalMarkup = this._wrapSVGTextAndBg(textAndBg);
+ return this._createBaseSVGMarkup(
+ internalMarkup, { reviver: reviver, noStyle: true, withShadow: true });
},
/**
@@ -28318,13 +28377,10 @@ fabric.util.object.extend(fabric.IText.prototype, /** @lends fabric.IText.protot
/**
* @private
*/
- _wrapSVGTextAndBg: function(markup, textAndBg) {
- var noShadow = true, filter = this.getSvgFilter(),
- style = filter === '' ? '' : ' style="' + filter + '"',
+ _wrapSVGTextAndBg: function(textAndBg) {
+ var noShadow = true,
textDecoration = this.getSvgTextDecoration(this);
- markup.push(
- '\t\n',
+ return [
textAndBg.textBgRects.join(''),
'\t\t',
textAndBg.textSpans.join(''),
- '\n',
- '\t\n'
- );
+ '\n'
+ ];
},
/**
diff --git a/package.json b/package.json
index 93afdb04a3e..7b0d03c2b1a 100644
--- a/package.json
+++ b/package.json
@@ -2,7 +2,7 @@
"name": "fabric",
"description": "Object model for HTML5 canvas, and SVG-to-canvas parser. Backed by jsdom and node-canvas.",
"homepage": "http://fabricjs.com/",
- "version": "2.4.1",
+ "version": "2.4.2",
"author": "Juriy Zaytsev ",
"contributors": [
{
diff --git a/test/unit/canvas_static.js b/test/unit/canvas_static.js
index d2d637c2dee..adc2b752607 100644
--- a/test/unit/canvas_static.js
+++ b/test/unit/canvas_static.js
@@ -872,7 +872,7 @@
canvasClip.clipPath = new fabric.Rect({ width: 200, height: 200 });
canvasClip.add(new fabric.Circle({ radius: 200 }));
var svg = canvasClip.toSVG();
- var expectedSVG = '\n\n';
+ var expectedSVG = '\n\n';
assert.equal(svg, expectedSVG, 'SVG with clipPath should match');
});
@@ -884,7 +884,7 @@
canvas.renderOnAddRemove = false;
canvas.backgroundImage = imageBG;
canvas.overlayImage = imageOL;
- var expectedSVG = '\n\n';
+ var expectedSVG = '\n\n';
var svg1 = canvas.toSVG();
assert.equal(svg1, expectedSVG, 'svg with bg and overlay do not match');
imageBG.excludeFromExport = true;