diff --git a/src/elements_parser.js b/src/elements_parser.js
index 0bb377f0b9c..cccb2d783cb 100644
--- a/src/elements_parser.js
+++ b/src/elements_parser.js
@@ -73,7 +73,7 @@ fabric.ElementsParser = function(elements, callback, options, reviver, parsingOp
var gradientDef = this.extractPropertyDefinition(obj, property, 'gradientDefs');
if (gradientDef) {
var opacityAttr = el.getAttribute(property + '-opacity');
- var gradient = fabric.Gradient.fromElement(gradientDef, obj, opacityAttr);
+ var gradient = fabric.Gradient.fromElement(gradientDef, obj, opacityAttr, this.options);
obj.set(property, gradient);
}
};
diff --git a/src/gradient.class.js b/src/gradient.class.js
index b3313b53e67..a909d23c818 100644
--- a/src/gradient.class.js
+++ b/src/gradient.class.js
@@ -95,18 +95,67 @@
*/
offsetY: 0,
+ /**
+ * A transform matrix to apply to the gradient before painting.
+ * Imported from svg gradients, is not applied with the current transform in the center.
+ * Before this transform is applied, the origin point is at the top left corner of the object
+ * plus the addition of offsetY and offsetX.
+ * @type Array[Number]
+ * @default null
+ */
+ gradientTransform: null,
+
+ /**
+ * coordinates units for coords.
+ * If `pixels`, the number of coords are in the same unit of width / height.
+ * If set as `percentage` the coords are still a number, but 1 means 100% of width
+ * for the X and 100% of the height for the y. It can be bigger than 1 and negative.
+ * @type String pixels || percentage
+ * @default 'pixels'
+ */
+ gradientUnits: 'pixels',
+
+ /**
+ * Gradient type
+ * @type String linear || radial
+ * @default 'pixels'
+ */
+ type: 'linear',
+
/**
* Constructor
- * @param {Object} [options] Options object with type, coords, gradientUnits and colorStops
+ * @param {Object} options Options object with type, coords, gradientUnits and colorStops
+ * @param {Object} [options.type] gradient type linear or radial
+ * @param {Object} [options.gradientUnits] gradient units
+ * @param {Object} [options.offsetX] SVG import compatibility
+ * @param {Object} [options.offsetY] SVG import compatibility
+ * @param {Array[Object]} options.colorStops contains the colorstops.
+ * @param {Object} options.coords contains the coords of the gradient
+ * @param {Number} [options.coords.x1] X coordiante of the first point for linear or of the focal point for radial
+ * @param {Number} [options.coords.y1] Y coordiante of the first point for linear or of the focal point for radial
+ * @param {Number} [options.coords.x2] X coordiante of the second point for linear or of the center point for radial
+ * @param {Number} [options.coords.y2] Y coordiante of the second point for linear or of the center point for radial
+ * @param {Number} [options.coords.r1] only for radial gradient, radius of the inner circle
+ * @param {Number} [options.coords.r2] only for radial gradient, radius of the external circle
* @return {fabric.Gradient} thisArg
*/
initialize: function(options) {
options || (options = { });
+ options.coords || (options.coords = { });
- var coords = { };
+ var coords, _this = this;
- this.id = fabric.Object.__uid++;
- this.type = options.type || 'linear';
+ // sets everything, then coords and colorstops get sets again
+ Object.keys(options).forEach(function(option) {
+ _this[option] = options[option];
+ });
+
+ if (this.id) {
+ this.id += '_' + fabric.Object.__uid++;
+ }
+ else {
+ this.id = fabric.Object.__uid++;
+ }
coords = {
x1: options.coords.x1 || 0,
@@ -119,13 +168,9 @@
coords.r1 = options.coords.r1 || 0;
coords.r2 = options.coords.r2 || 0;
}
+
this.coords = coords;
this.colorStops = options.colorStops.slice();
- if (options.gradientTransform) {
- this.gradientTransform = options.gradientTransform;
- }
- this.offsetX = options.offsetX || this.offsetX;
- this.offsetY = options.offsetY || this.offsetY;
},
/**
@@ -157,6 +202,7 @@
colorStops: this.colorStops,
offsetX: this.offsetX,
offsetY: this.offsetY,
+ gradientUnits: this.gradientUnits,
gradientTransform: this.gradientTransform ? this.gradientTransform.concat() : this.gradientTransform
};
fabric.util.populateWithProperties(this, object, propertiesToInclude);
@@ -175,23 +221,33 @@
markup, commonAttributes, colorStops = clone(this.colorStops, true),
needsSwap = coords.r1 > coords.r2,
transform = this.gradientTransform ? this.gradientTransform.concat() : fabric.iMatrix.concat(),
- offsetX = object.width / 2 - this.offsetX, offsetY = object.height / 2 - this.offsetY,
- withViewport = !!options.additionalTransform;
+ offsetX = -this.offsetX, offsetY = -this.offsetY,
+ withViewport = !!options.additionalTransform,
+ gradientUnits = this.gradientUnits === 'pixels' ? 'userSpaceOnUse' : 'objectBoundingBox';
// colorStops must be sorted ascending
colorStops.sort(function(a, b) {
return a.offset - b.offset;
});
+ if (gradientUnits === 'objectBoundingBox') {
+ offsetX /= object.width;
+ offsetY /= object.height;
+ }
+ else {
+ offsetX += object.width / 2;
+ offsetY += object.height / 2;
+ }
if (object.type === 'path') {
offsetX -= object.pathOffset.x;
offsetY -= object.pathOffset.y;
}
+
transform[4] -= offsetX;
transform[5] -= offsetY;
commonAttributes = 'id="SVGID_' + this.id +
- '" gradientUnits="userSpaceOnUse"';
+ '" gradientUnits="' + gradientUnits + '"';
commonAttributes += ' gradientTransform="' + (withViewport ?
options.additionalTransform + ' ' : '') + fabric.util.matrixToSVG(transform) + '" ';
@@ -303,11 +359,17 @@
* @param {SVGGradientElement} el SVG gradient element
* @param {fabric.Object} instance
* @param {String} opacityAttr A fill-opacity or stroke-opacity attribute to multiply to each stop's opacity.
+ * @param {Object} svgOptions an object containing the size of the SVG in order to parse correctly graidents
+ * that uses gradientUnits as 'userSpaceOnUse' and percentages.
+ * @param {Object.number} viewBoxWidth width part of the viewBox attribute on svg
+ * @param {Object.number} viewBoxHeight height part of the viewBox attribute on svg
+ * @param {Object.number} width width part of the svg tag if viewBox is not specified
+ * @param {Object.number} height height part of the svg tag if viewBox is not specified
* @return {fabric.Gradient} Gradient instance
* @see http://www.w3.org/TR/SVG/pservers.html#LinearGradientElement
* @see http://www.w3.org/TR/SVG/pservers.html#RadialGradientElement
*/
- fromElement: function(el, instance, opacityAttr) {
+ fromElement: function(el, instance, opacityAttr, svgOptions) {
/**
* @example:
*
@@ -349,22 +411,18 @@
var colorStopEls = el.getElementsByTagName('stop'),
type,
- gradientUnits = el.getAttribute('gradientUnits') || 'objectBoundingBox',
- gradientTransform = el.getAttribute('gradientTransform'),
+ gradientUnits = el.getAttribute('gradientUnits') === 'userSpaceOnUse' ?
+ 'pixels' : 'percentage',
+ gradientTransform = el.getAttribute('gradientTransform') || '',
colorStops = [],
- coords, ellipseMatrix, i;
-
+ coords, i, offsetX = 0, offsetY = 0,
+ transformMatrix;
if (el.nodeName === 'linearGradient' || el.nodeName === 'LINEARGRADIENT') {
type = 'linear';
+ coords = getLinearCoords(el);
}
else {
type = 'radial';
- }
-
- if (type === 'linear') {
- coords = getLinearCoords(el);
- }
- else if (type === 'radial') {
coords = getRadialCoords(el);
}
@@ -372,34 +430,46 @@
colorStops.push(getColorStop(colorStopEls[i], multiplier));
}
- ellipseMatrix = _convertPercentUnitsToValues(instance, coords, gradientUnits);
+ transformMatrix = fabric.parseTransformAttribute(gradientTransform);
+
+ __convertPercentUnitsToValues(instance, coords, svgOptions, gradientUnits);
+
+ if (gradientUnits === 'pixels') {
+ offsetX = -instance.left;
+ offsetY = -instance.top;
+ }
var gradient = new fabric.Gradient({
+ id: el.getAttribute('id'),
type: type,
coords: coords,
colorStops: colorStops,
- offsetX: -instance.left,
- offsetY: -instance.top
+ gradientUnits: gradientUnits,
+ gradientTransform: transformMatrix,
+ offsetX: offsetX,
+ offsetY: offsetY,
});
- if (gradientTransform || ellipseMatrix !== '') {
- gradient.gradientTransform = fabric.parseTransformAttribute((gradientTransform || '') + ellipseMatrix);
- }
-
return gradient;
},
/* _FROM_SVG_END_ */
/**
* Returns {@link fabric.Gradient} instance from its object representation
+ * this function is uniquely used by Object.setGradient and is deprecated with it.
* @static
+ * @deprecated since 3.4.0
* @memberOf fabric.Gradient
* @param {Object} obj
* @param {Object} [options] Options object
*/
forObject: function(obj, options) {
options || (options = { });
- _convertPercentUnitsToValues(obj, options.coords, 'userSpaceOnUse');
+ __convertPercentUnitsToValues(obj, options.coords, options.gradientUnits, {
+ // those values are to avoid errors. this function is uniquely used by
+ viewBoxWidth: 100,
+ viewBoxHeight: 100,
+ });
return new fabric.Gradient(options);
}
});
@@ -407,46 +477,32 @@
/**
* @private
*/
- function _convertPercentUnitsToValues(object, options, gradientUnits) {
- var propValue, addFactor = 0, multFactor = 1, ellipseMatrix = '';
- for (var prop in options) {
- if (options[prop] === 'Infinity') {
- options[prop] = 1;
- }
- else if (options[prop] === '-Infinity') {
- options[prop] = 0;
+ function __convertPercentUnitsToValues(instance, options, svgOptions, gradientUnits) {
+ var propValue, finalValue;
+ Object.keys(options).forEach(function(prop) {
+ propValue = options[prop];
+ if (propValue === 'Infinity') {
+ finalValue = 1;
}
- propValue = parseFloat(options[prop], 10);
- if (typeof options[prop] === 'string' && /^(\d+\.\d+)%|(\d+)%$/.test(options[prop])) {
- multFactor = 0.01;
+ else if (propValue === '-Infinity') {
+ finalValue = 0;
}
else {
- multFactor = 1;
- }
- if (prop === 'x1' || prop === 'x2' || prop === 'r2') {
- multFactor *= gradientUnits === 'objectBoundingBox' ? object.width : 1;
- addFactor = gradientUnits === 'objectBoundingBox' ? object.left || 0 : 0;
- }
- else if (prop === 'y1' || prop === 'y2') {
- multFactor *= gradientUnits === 'objectBoundingBox' ? object.height : 1;
- addFactor = gradientUnits === 'objectBoundingBox' ? object.top || 0 : 0;
- }
- options[prop] = propValue * multFactor + addFactor;
- }
- if (object.type === 'ellipse' &&
- options.r2 !== null &&
- gradientUnits === 'objectBoundingBox' &&
- object.rx !== object.ry) {
-
- var scaleFactor = object.ry / object.rx;
- ellipseMatrix = ' scale(1, ' + scaleFactor + ')';
- if (options.y1) {
- options.y1 /= scaleFactor;
- }
- if (options.y2) {
- options.y2 /= scaleFactor;
+ finalValue = parseFloat(options[prop], 10);
+ if (typeof propValue === 'string' && /^(\d+\.\d+)%|(\d+)%$/.test(propValue)) {
+ finalValue *= 0.01;
+ if (gradientUnits === 'pixels') {
+ // then we need to fix those percentages here in svg parsing
+ if (prop === 'x1' || prop === 'x2' || prop === 'r2') {
+ finalValue *= svgOptions.viewBoxWidth || svgOptions.width;
+ }
+ if (prop === 'y1' || prop === 'y2') {
+ finalValue *= svgOptions.viewBoxHeight || svgOptions.height;
+ }
+ }
+ }
}
- }
- return ellipseMatrix;
+ options[prop] = finalValue;
+ });
}
})();
diff --git a/src/mixins/object_geometry.mixin.js b/src/mixins/object_geometry.mixin.js
index 90aa87ee75b..f35b337b801 100644
--- a/src/mixins/object_geometry.mixin.js
+++ b/src/mixins/object_geometry.mixin.js
@@ -478,11 +478,7 @@
* @return {Array} rotation matrix for the object
*/
_calcRotateMatrix: function() {
- if (this.angle) {
- var theta = degreesToRadians(this.angle), cos = fabric.util.cos(theta), sin = fabric.util.sin(theta);
- return [cos, sin, -sin, cos, 0, 0];
- }
- return fabric.iMatrix.concat();
+ return fabric.util.calcRotateMatrix(this);
},
/**
@@ -527,41 +523,41 @@
return matrix;
},
+ /**
+ * calculate transform matrix that represents the current transformations from the
+ * object's properties, this matrix does not include the group transformation
+ * @return {Array} transform matrix for the object
+ */
calcOwnMatrix: function() {
var key = this.transformMatrixKey(true), cache = this.ownMatrixCache || (this.ownMatrixCache = {});
if (cache.key === key) {
return cache.value;
}
- var matrix = this._calcTranslateMatrix(),
- rotateMatrix,
- dimensionMatrix = this._calcDimensionsTransformMatrix(this.skewX, this.skewY, true);
- if (this.angle) {
- rotateMatrix = this._calcRotateMatrix();
- matrix = multiplyMatrices(matrix, rotateMatrix);
- }
- matrix = multiplyMatrices(matrix, dimensionMatrix);
+ var tMatrix = this._calcTranslateMatrix();
+ this.translateX = tMatrix[4];
+ this.translateY = tMatrix[5];
cache.key = key;
- cache.value = matrix;
- return matrix;
+ cache.value = fabric.util.composeMatrix(this);
+ return cache.value;
},
+ /*
+ * Calculate object dimensions from its properties
+ * @private
+ * @deprecated since 3.4.0, please use fabric.util._calcDimensionsTransformMatrix
+ * not including or including flipX, flipY to emulate the flipping boolean
+ * @return {Object} .x width dimension
+ * @return {Object} .y height dimension
+ */
_calcDimensionsTransformMatrix: function(skewX, skewY, flipping) {
- var skewMatrix,
- scaleX = this.scaleX * (flipping && this.flipX ? -1 : 1),
- scaleY = this.scaleY * (flipping && this.flipY ? -1 : 1),
- scaleMatrix = [scaleX, 0, 0, scaleY, 0, 0];
- if (skewX) {
- skewMatrix = [1, 0, Math.tan(degreesToRadians(skewX)), 1];
- scaleMatrix = multiplyMatrices(scaleMatrix, skewMatrix, true);
- }
- if (skewY) {
- skewMatrix = [1, Math.tan(degreesToRadians(skewY)), 0, 1];
- scaleMatrix = multiplyMatrices(scaleMatrix, skewMatrix, true);
- }
- return scaleMatrix;
+ return fabric.util.calcDimensionsMatrix({
+ skewX: skewX,
+ skewY: skewY,
+ scaleX: this.scaleX * (flipping && this.flipX ? -1 : 1),
+ scaleY: this.scaleY * (flipping && this.flipY ? -1 : 1)
+ });
},
-
/*
* Calculate object dimensions from its properties
* @private
@@ -625,12 +621,13 @@
x: dimX,
y: dimY
}],
- i, transformMatrix = this._calcDimensionsTransformMatrix(skewX, skewY, false),
- bbox;
- for (i = 0; i < points.length; i++) {
- points[i] = fabric.util.transformPoint(points[i], transformMatrix);
- }
- bbox = fabric.util.makeBoundingBoxFromPoints(points);
+ transformMatrix = fabric.util.calcDimensionsMatrix({
+ scaleX: this.scaleX,
+ scaleY: this.scaleY,
+ skewX: this.skewX,
+ skewY: this.skewY,
+ }),
+ bbox = fabric.util.makeBoundingBoxFromPoints(points, transformMatrix);
return this._finalizeDimensions(bbox.width, bbox.height);
},
diff --git a/src/mixins/object_interactivity.mixin.js b/src/mixins/object_interactivity.mixin.js
index e1229d49349..1b3a2e847da 100644
--- a/src/mixins/object_interactivity.mixin.js
+++ b/src/mixins/object_interactivity.mixin.js
@@ -195,7 +195,11 @@
drawBordersInGroup: function(ctx, options, styleOverride) {
styleOverride = styleOverride || {};
var p = this._getNonTransformedDimensions(),
- matrix = fabric.util.customTransformMatrix(options.scaleX, options.scaleY, options.skewX),
+ matrix = fabric.util.composeMatrix({
+ scaleX: options.scaleX,
+ scaleY: options.scaleY,
+ skewX: options.skewX
+ }),
wh = fabric.util.transformPoint(p, matrix),
strokeWidth = 1 / this.borderScaleFactor,
width = wh.x + strokeWidth,
diff --git a/src/parser.js b/src/parser.js
index 6e27c7683fa..f8428bef845 100644
--- a/src/parser.js
+++ b/src/parser.js
@@ -559,12 +559,14 @@
parsedDim.height = parseUnit(heightAttr);
return parsedDim;
}
-
minX = -parseFloat(viewBoxAttr[1]);
minY = -parseFloat(viewBoxAttr[2]);
viewBoxWidth = parseFloat(viewBoxAttr[3]);
viewBoxHeight = parseFloat(viewBoxAttr[4]);
-
+ parsedDim.minX = minX;
+ parsedDim.minY = minY;
+ parsedDim.viewBoxWidth = viewBoxWidth;
+ parsedDim.viewBoxHeight = viewBoxHeight;
if (!missingDimAttr) {
parsedDim.width = parseUnit(widthAttr);
parsedDim.height = parseUnit(heightAttr);
@@ -723,7 +725,7 @@
recursivelyParseGradientsXlink(doc, referencedGradient);
}
gradientsAttrs.forEach(function(attr) {
- if (!gradient.hasAttribute(attr)) {
+ if (referencedGradient && !gradient.hasAttribute(attr) && referencedGradient.hasAttribute(attr)) {
gradient.setAttribute(attr, referencedGradient.getAttribute(attr));
}
});
diff --git a/src/shapes/object.class.js b/src/shapes/object.class.js
index 7d98c72e36a..fd449dab63f 100644
--- a/src/shapes/object.class.js
+++ b/src/shapes/object.class.js
@@ -1474,7 +1474,13 @@
var t = filler.gradientTransform || filler.patternTransform;
var offsetX = -this.width / 2 + filler.offsetX || 0,
offsetY = -this.height / 2 + filler.offsetY || 0;
- ctx.translate(offsetX, offsetY);
+
+ if (filler.gradientUnits === 'percentage') {
+ ctx.transform(this.width, 0, 0, this.height, offsetX, offsetY);
+ }
+ else {
+ ctx.transform(1, 0, 0, 1, offsetX, offsetY);
+ }
if (t) {
ctx.transform(t[0], t[1], t[2], t[3], t[4], t[5]);
}
@@ -1527,6 +1533,10 @@
ctx.restore();
},
+ /**
+ * @private
+ * @param {CanvasRenderingContext2D} ctx Context to render on
+ */
_renderStroke: function(ctx) {
if (!this.stroke || this.strokeWidth === 0) {
return;
@@ -1541,11 +1551,56 @@
ctx.scale(1 / this.scaleX, 1 / this.scaleY);
}
this._setLineDash(ctx, this.strokeDashArray, this._renderDashedStroke);
- this._applyPatternGradientTransform(ctx, this.stroke);
+ if (this.stroke.toLive && this.stroke.gradientUnits === 'percentage') {
+ // need to transform gradient in a pattern.
+ // this is a slow process. If you are hitting this codepath, and the object
+ // is not using caching, you should consider switching it on.
+ // we need a canvas as big as the current object caching canvas.
+ this._applyPatternForTransformedGradient(ctx, this.stroke);
+ }
+ else {
+ this._applyPatternGradientTransform(ctx, this.stroke);
+ }
ctx.stroke();
ctx.restore();
},
+ /**
+ * This function try to patch the missing gradientTransform on canvas gradients.
+ * transforming a context to transform the gradient, is going to transform the stroke too.
+ * we want to transform the gradient but not the stroke operation, so we create
+ * a transformed gradient on a pattern and then we use the pattern instead of the gradient.
+ * this method has drwabacks: is slow, is in low resolution, needs a patch for when the size
+ * is limited.
+ * @private
+ * @param {CanvasRenderingContext2D} ctx Context to render on
+ * @param {fabric.Gradient} filler a fabric gradient instance
+ */
+ _applyPatternForTransformedGradient: function(ctx, filler) {
+ var dims = this._limitCacheSize(this._getCacheCanvasDimensions()),
+ pCanvas = fabric.util.createCanvasElement(), pCtx, retinaScaling = this.canvas.getRetinaScaling(),
+ width = dims.x / this.scaleX / retinaScaling, height = dims.y / this.scaleY / retinaScaling;
+ pCanvas.width = width;
+ pCanvas.height = height;
+ pCtx = pCanvas.getContext('2d');
+ pCtx.beginPath(); pCtx.moveTo(0, 0); pCtx.lineTo(width, 0); pCtx.lineTo(width, height);
+ pCtx.lineTo(0, height); pCtx.closePath();
+ pCtx.translate(width / 2, height / 2);
+ pCtx.scale(
+ dims.zoomX / this.scaleX / retinaScaling,
+ dims.zoomY / this.scaleY / retinaScaling
+ );
+ this._applyPatternGradientTransform(pCtx, filler);
+ pCtx.fillStyle = filler.toLive(ctx);
+ pCtx.fill();
+ ctx.translate(-this.width / 2 - this.strokeWidth / 2, -this.height / 2 - this.strokeWidth / 2);
+ ctx.scale(
+ retinaScaling * this.scaleX / dims.zoomX,
+ retinaScaling * this.scaleY / dims.zoomY
+ );
+ ctx.strokeStyle = pCtx.createPattern(pCanvas, 'no-repeat');
+ },
+
/**
* This function is an helper for svg import. it returns the center of the object in the svg
* untransformed coordinates
@@ -1766,6 +1821,7 @@
/**
* Sets gradient (fill or stroke) of an object
+ * percentages for x1,x2,y1,y2,r1,r2 together with gradientUnits 'pixels', are not supported.
* Backwards incompatibility note: This method was named "setGradientFill" until v1.1.0
* @param {String} property Property name 'stroke' or 'fill'
* @param {Object} [options] Options object
@@ -1780,6 +1836,7 @@
* @param {Object} [options.gradientTransform] transformMatrix for gradient
* @return {fabric.Object} thisArg
* @chainable
+ * @deprecated since 3.4.0
* @see {@link http://jsfiddle.net/fabricjs/58y8b/|jsFiddle demo}
* @example
Set linear gradient
* object.setGradient('fill', {
@@ -1824,7 +1881,7 @@
x2: options.x2,
y2: options.y2
};
-
+ gradient.gradientUnits = options.gradientUnits || 'pixels';
if (options.r1 || options.r2) {
gradient.coords.r1 = options.r1;
gradient.coords.r2 = options.r2;
diff --git a/src/util/misc.js b/src/util/misc.js
index 8e9a85381b0..097d4080eb3 100644
--- a/src/util/misc.js
+++ b/src/util/misc.js
@@ -3,7 +3,6 @@
var sqrt = Math.sqrt,
atan2 = Math.atan2,
pow = Math.pow,
- abs = Math.abs,
PiBy180 = Math.PI / 180,
PiBy2 = Math.PI / 2;
@@ -165,9 +164,15 @@
/**
* Returns coordinates of points's bounding rectangle (left, top, width, height)
* @param {Array} points 4 points array
+ * @param {Array} [transform] an array of 6 numbers representing a 2x3 transform matrix
* @return {Object} Object with left, top, width, height properties
*/
- makeBoundingBoxFromPoints: function(points) {
+ makeBoundingBoxFromPoints: function(points, transform) {
+ if (transform) {
+ for (var i = 0; i < points.length; i++) {
+ points[i] = fabric.util.transformPoint(points[i], transform);
+ }
+ }
var xPoints = [points[0].x, points[1].x, points[2].x, points[3].x],
minX = fabric.util.array.min(xPoints),
maxX = fabric.util.array.max(xPoints),
@@ -658,7 +663,7 @@
},
/**
- * Decomposes standard 2x2 matrix into transform componentes
+ * Decomposes standard 2x3 matrix into transform components
* @static
* @memberOf fabric.util
* @param {Array} a transformMatrix
@@ -681,10 +686,113 @@
};
},
+ /**
+ * Returns a transform matrix starting from an object of the same kind of
+ * the one returned from qrDecompose, useful also if you want to calculate some
+ * transformations from an object that is not enlived yet
+ * @static
+ * @memberOf fabric.util
+ * @param {Object} options
+ * @param {Number} [options.angle] angle in degrees
+ * @return {Array[Number]} transform matrix
+ */
+ calcRotateMatrix: function(options) {
+ if (!options.angle) {
+ return fabric.iMatrix.concat();
+ }
+ var theta = fabric.util.degreesToRadians(options.angle),
+ cos = fabric.util.cos(theta),
+ sin = fabric.util.sin(theta);
+ return [cos, sin, -sin, cos, 0, 0];
+ },
+
+ /**
+ * Returns a transform matrix starting from an object of the same kind of
+ * the one returned from qrDecompose, useful also if you want to calculate some
+ * transformations from an object that is not enlived yet.
+ * is called DimensionsTransformMatrix because those properties are the one that influence
+ * the size of the resulting box of the object.
+ * @static
+ * @memberOf fabric.util
+ * @param {Object} options
+ * @param {Number} [options.scaleX]
+ * @param {Number} [options.scaleY]
+ * @param {Boolean} [options.flipX]
+ * @param {Boolean} [options.flipY]
+ * @param {Number} [options.skewX]
+ * @param {Number} [options.skewX]
+ * @return {Array[Number]} transform matrix
+ */
+ calcDimensionsMatrix: function(options) {
+ var scaleX = typeof options.scaleX === 'undefined' ? 1 : options.scaleX,
+ scaleY = typeof options.scaleY === 'undefined' ? 1 : options.scaleY,
+ scaleMatrix = [
+ options.flipX ? -scaleX : scaleX,
+ 0,
+ 0,
+ options.flipY ? -scaleY : scaleY,
+ 0,
+ 0],
+ multiply = fabric.util.multiplyTransformMatrices,
+ degreesToRadians = fabric.util.degreesToRadians;
+ if (options.skewX) {
+ scaleMatrix = multiply(
+ scaleMatrix,
+ [1, 0, Math.tan(degreesToRadians(options.skewX)), 1],
+ true);
+ }
+ if (options.skewY) {
+ scaleMatrix = multiply(
+ scaleMatrix,
+ [1, Math.tan(degreesToRadians(options.skewY)), 0, 1],
+ true);
+ }
+ return scaleMatrix;
+ },
+
+ /**
+ * Returns a transform matrix starting from an object of the same kind of
+ * the one returned from qrDecompose, useful also if you want to calculate some
+ * transformations from an object that is not enlived yet
+ * @static
+ * @memberOf fabric.util
+ * @param {Object} options
+ * @param {Number} [options.angle]
+ * @param {Number} [options.scaleX]
+ * @param {Number} [options.scaleY]
+ * @param {Boolean} [options.flipX]
+ * @param {Boolean} [options.flipY]
+ * @param {Number} [options.skewX]
+ * @param {Number} [options.skewX]
+ * @param {Number} [options.translateX]
+ * @param {Number} [options.translateY]
+ * @return {Array[Number]} transform matrix
+ */
+ composeMatrix: function(options) {
+ var matrix = [1, 0, 0, 1, options.translateX || 0, options.translateY || 0],
+ multiply = fabric.util.multiplyTransformMatrices;
+ if (options.angle) {
+ matrix = multiply(matrix, fabric.util.calcRotateMatrix(options));
+ }
+ if (options.scaleX || options.scaleY || options.skewX || options.skewY || options.flipX || options.flipY) {
+ matrix = multiply(matrix, fabric.util.calcDimensionsMatrix(options));
+ }
+ return matrix;
+ },
+
+ /**
+ * Returns a transform matrix that has the same effect of scaleX, scaleY and skewX.
+ * Is deprecated for composeMatrix. Please do not use it.
+ * @static
+ * @deprecated since 3.4.0
+ * @memberOf fabric.util
+ * @param {Number} scaleX
+ * @param {Number} scaleY
+ * @param {Number} skewX
+ * @return {Array[Number]} transform matrix
+ */
customTransformMatrix: function(scaleX, scaleY, skewX) {
- var skewMatrixX = [1, 0, abs(Math.tan(skewX * PiBy180)), 1],
- scaleMatrix = [abs(scaleX), 0, 0, abs(scaleY)];
- return fabric.util.multiplyTransformMatrices(scaleMatrix, skewMatrixX, true);
+ return fabric.util.composeMatrix({ scaleX: scaleX, scaleY: scaleY, skewX: skewX });
},
/**
diff --git a/test/lib/visualTestLoop.js b/test/lib/visualTestLoop.js
index dd82456fd32..89d4528d71e 100644
--- a/test/lib/visualTestLoop.js
+++ b/test/lib/visualTestLoop.js
@@ -49,6 +49,7 @@
var finalName = '/assets/' + filename + '.svg';
return fabric.isLikelyNode ? localPath('/../visual', finalName) : getAbsolutePath('/test/visual' + finalName);
}
+ exports.getAssetName = getAssetName;
function getGoldeName(filename) {
var finalName = '/golden/' + filename;
diff --git a/test/node_test_setup.js b/test/node_test_setup.js
index c5c79803a34..36b8200b959 100644
--- a/test/node_test_setup.js
+++ b/test/node_test_setup.js
@@ -9,6 +9,7 @@ global.visualCallback = {
global.visualTestLoop = require('./lib/visualTestLoop').visualTestLoop;
global.getFixture = require('./lib/visualTestLoop').getFixture;
global.getAsset = require('./lib/visualTestLoop').getAsset;
+global.getAssetName = require('./lib/visualTestLoop').getAssetName;
global.imageDataToChalk = function(imageData) {
// actually this does not work on travis-ci, so commenting it out
return '';
diff --git a/test/unit/gradient.js b/test/unit/gradient.js
index 7c32f1b18fe..dc7d26bce96 100644
--- a/test/unit/gradient.js
+++ b/test/unit/gradient.js
@@ -2,9 +2,10 @@
QUnit.module('fabric.Gradient');
- function createLinearGradient() {
+ function createLinearGradient(units) {
return new fabric.Gradient({
type: 'linear',
+ gradientUnits: units || 'pixels',
coords: {
x1: 0,
y1: 10,
@@ -18,9 +19,10 @@
});
}
- function createRadialGradient() {
+ function createRadialGradient(units) {
return new fabric.Gradient({
type: 'radial',
+ gradientUnits: units || 'pixels',
coords: {
x1: 0,
y1: 10,
@@ -76,7 +78,8 @@
var SVG_RADIAL = '\n\n\n\n';
var SVG_INTERNALRADIUS = '\n\n\n\n';
var SVG_SWAPPED = '\n\n\n\n';
-
+ var SVG_LINEAR_PERCENTAGE = '\n\n\n\n';
+ var SVG_RADIAL_PERCENTAGE = '\n\n\n\n';
QUnit.test('constructor linearGradient', function(assert) {
assert.ok(fabric.Gradient);
@@ -143,7 +146,7 @@
assert.equal(object.coords.x2, gradient.coords.x2);
assert.equal(object.coords.y1, gradient.coords.y1);
assert.equal(object.coords.y2, gradient.coords.y2);
-
+ assert.equal(object.gradientUnits, gradient.gradientUnits);
assert.equal(object.type, gradient.type);
assert.deepEqual(object.gradientTransform, gradient.gradientTransform);
assert.equal(object.colorStops, gradient.colorStops);
@@ -212,11 +215,12 @@
var gradient = fabric.Gradient.fromElement(element, object, '');
assert.ok(gradient instanceof fabric.Gradient);
-
+ assert.equal(gradient.type, 'linear');
assert.equal(gradient.coords.x1, 0);
assert.equal(gradient.coords.y1, 0);
- assert.equal(gradient.coords.x2, 100);
+ assert.equal(gradient.coords.x2, 1);
assert.equal(gradient.coords.y2, 0);
+ assert.equal(gradient.gradientUnits, 'percentage');
assert.equal(gradient.colorStops[0].offset, 1);
assert.equal(gradient.colorStops[1].offset, 0);
@@ -254,10 +258,11 @@
assert.ok(gradient instanceof fabric.Gradient);
- assert.equal(gradient.coords.x1, 20);
- assert.equal(gradient.coords.y1, 0.4);
- assert.equal(gradient.coords.x2, 40000);
- assert.equal(gradient.coords.y2, 40);
+ assert.equal(gradient.coords.x1, 0.1);
+ assert.equal(gradient.coords.y1, 0.002);
+ assert.equal(gradient.coords.x2, 200);
+ assert.equal(gradient.coords.y2, 0.2);
+ assert.equal(gradient.gradientUnits, 'percentage');
});
QUnit.test('fromElement linearGradient with floats percentage - userSpaceOnUse', function(assert) {
@@ -282,15 +287,20 @@
element.appendChild(stop1);
element.appendChild(stop2);
- var object = new fabric.Object({ width: 200, height: 200 });
- var gradient = fabric.Gradient.fromElement(element, object, '');
+ var object = new fabric.Object({left: 10, top: 15, width: 200, height: 200 });
+ var gradient = fabric.Gradient.fromElement(element, object, '', {
+ viewBoxWidth: 400,
+ viewBoxHeight: 300,
+ });
assert.ok(gradient instanceof fabric.Gradient);
-
- assert.equal(gradient.coords.x1, 0.1);
- assert.equal(gradient.coords.y1, 0.002);
+ assert.equal(gradient.gradientUnits, 'pixels');
+ assert.equal(gradient.offsetX, -10);
+ assert.equal(gradient.offsetY, -15);
+ assert.equal(gradient.coords.x1, 40);
+ assert.equal(gradient.coords.y1, 0.6);
assert.equal(gradient.coords.x2, 200);
- assert.equal(gradient.coords.y2, 0.2);
+ assert.equal(gradient.coords.y2, 60);
});
QUnit.test('fromElement linearGradient with Infinity', function(assert) {
@@ -314,14 +324,14 @@
element.appendChild(stop1);
element.appendChild(stop2);
- var object = new fabric.Object({ width: 100, height: 100, top: 0, left: 0 });
+ var object = new fabric.Object({ width: 100, height: 300, top: 20, left: 30 });
var gradient = fabric.Gradient.fromElement(element, object, '');
assert.ok(gradient instanceof fabric.Gradient);
assert.equal(gradient.coords.x1, 0);
- assert.equal(gradient.coords.y1, 100);
- assert.equal(gradient.coords.x2, 100);
+ assert.equal(gradient.coords.y1, 1);
+ assert.equal(gradient.coords.x2, 1);
assert.equal(gradient.coords.y2, 0);
assert.equal(gradient.colorStops[0].offset, 1);
@@ -370,16 +380,16 @@
var object = new fabric.Object({ width: 200, height: 200 });
var gradient = fabric.Gradient.fromElement(element, object, '');
- assert.equal(gradient.coords.x1, 60);
- assert.equal(gradient.coords.y1, 20);
- assert.equal(gradient.coords.x2, 40);
- assert.equal(gradient.coords.y2, 200);
+ assert.equal(gradient.coords.x1, 0.3);
+ assert.equal(gradient.coords.y1, 0.1);
+ assert.equal(gradient.coords.x2, 0.2);
+ assert.equal(gradient.coords.y2, 1);
object = new fabric.Object({ width: 200, height: 200, top: 50, left: 10 });
gradient = fabric.Gradient.fromElement(element, object, '');
- assert.equal(gradient.coords.x1, 70);
- assert.equal(gradient.coords.y1, 70);
- assert.equal(gradient.coords.x2, 50);
- assert.equal(gradient.coords.y2, 250);
+ assert.equal(gradient.coords.x1, 0.3, 'top and left do not change the output');
+ assert.equal(gradient.coords.y1, 0.1, 'top and left do not change the output');
+ assert.equal(gradient.coords.x2, 0.2, 'top and left do not change the output');
+ assert.equal(gradient.coords.y2, 1, 'top and left do not change the output');
});
QUnit.test('fromElement with x1,x2,y1,2 radial', function(assert) {
@@ -395,21 +405,21 @@
var object = new fabric.Object({ width: 200, height: 200 });
var gradient = fabric.Gradient.fromElement(element, object, '');
- assert.equal(gradient.coords.x1, 60, 'should change with width height');
- assert.equal(gradient.coords.y1, 40, 'should change with width height');
- assert.equal(gradient.coords.x2, 20, 'should change with width height');
- assert.equal(gradient.coords.y2, 200, 'should change with width height');
- assert.equal(gradient.coords.r1, 0, 'should change with width height');
- assert.equal(gradient.coords.r2, 200, 'should change with width height');
+ assert.equal(gradient.coords.x1, 0.3, 'should not change with width height');
+ assert.equal(gradient.coords.y1, 0.2, 'should not change with width height');
+ assert.equal(gradient.coords.x2, 0.1, 'should not change with width height');
+ assert.equal(gradient.coords.y2, 1, 'should not change with width height');
+ assert.equal(gradient.coords.r1, 0, 'should not change with width height');
+ assert.equal(gradient.coords.r2, 1, 'should not change with width height');
object = new fabric.Object({ width: 200, height: 200, top: 10, left: 10 });
gradient = fabric.Gradient.fromElement(element, object, '');
- assert.equal(gradient.coords.x1, 70, 'should change with top left');
- assert.equal(gradient.coords.y1, 50, 'should change with top left');
- assert.equal(gradient.coords.x2, 30, 'should change with top left');
- assert.equal(gradient.coords.y2, 210, 'should change with top left');
- assert.equal(gradient.coords.r1, 10, 'should change with top left');
- assert.equal(gradient.coords.r2, 210, 'should change with top left');
+ assert.equal(gradient.coords.x1, 0.3, 'should not change with top left');
+ assert.equal(gradient.coords.y1, 0.2, 'should not change with top left');
+ assert.equal(gradient.coords.x2, 0.1, 'should not change with top left');
+ assert.equal(gradient.coords.y2, 1, 'should not change with top left');
+ assert.equal(gradient.coords.r1, 0, 'should not change with top left');
+ assert.equal(gradient.coords.r2, 1, 'should not change with top left');
});
QUnit.test('fromElement with x1,x2,y1,2 radial userSpaceOnUse', function(assert) {
@@ -469,7 +479,7 @@
assert.equal(gradient.coords.y2, 18, 'should not change with top left');
});
- QUnit.test('fromElement radialGradient', function(assert) {
+ QUnit.test('fromElement radialGradient defaults', function(assert) {
assert.ok(typeof fabric.Gradient.fromElement === 'function');
var element = fabric.document.createElement('radialGradient');
@@ -486,14 +496,16 @@
element.appendChild(stop2);
var object = new fabric.Object({ width: 100, height: 100 });
- var gradient = fabric.Gradient.fromElement(element, object, '');
+ var gradient = fabric.Gradient.fromElement(element, object, '', {});
assert.ok(gradient instanceof fabric.Gradient);
- assert.equal(gradient.coords.x1, 50);
- assert.equal(gradient.coords.y1, 50);
- assert.equal(gradient.coords.x2, 50);
- assert.equal(gradient.coords.y2, 50);
+ assert.equal(gradient.coords.x1, 0.5);
+ assert.equal(gradient.coords.y1, 0.5);
+ assert.equal(gradient.coords.x2, 0.5);
+ assert.equal(gradient.coords.y2, 0.5);
+ assert.equal(gradient.coords.r1, 0);
+ assert.equal(gradient.coords.r2, 0.5);
assert.equal(gradient.colorStops[0].offset, 1);
assert.equal(gradient.colorStops[1].offset, 0);
@@ -519,20 +531,7 @@
element.appendChild(stop2);
element.setAttribute('gradientTransform', 'matrix(3.321 -0.6998 0.4077 1.9347 -440.9168 -408.0598)');
var object = new fabric.Object({ width: 100, height: 100 });
- var gradient = fabric.Gradient.fromElement(element, object, '');
-
- assert.ok(gradient instanceof fabric.Gradient);
-
- assert.equal(gradient.coords.x1, 50);
- assert.equal(gradient.coords.y1, 50);
- assert.equal(gradient.coords.x2, 50);
- assert.equal(gradient.coords.y2, 50);
-
- assert.equal(gradient.colorStops[0].offset, 1);
- assert.equal(gradient.colorStops[1].offset, 0);
-
- assert.equal(gradient.colorStops[0].color, 'rgb(0,0,0)');
- assert.equal(gradient.colorStops[1].color, 'rgb(255,255,255)');
+ var gradient = fabric.Gradient.fromElement(element, object, '', {});
assert.deepEqual(gradient.gradientTransform, [3.321, -0.6998, 0.4077, 1.9347, -440.9168, -408.0598]);
});
@@ -574,7 +573,7 @@
assert.equal(gradient.coords.x1, 0);
assert.equal(gradient.coords.y1, 0);
- assert.equal(gradient.coords.x2, 100);
+ assert.equal(gradient.coords.x2, 1);
assert.equal(gradient.coords.y2, 0);
assert.equal(gradient.colorStops[0].offset, 1);
@@ -629,11 +628,6 @@
assert.ok(gradient instanceof fabric.Gradient);
- assert.equal(gradient.coords.x1, 50);
- assert.equal(gradient.coords.y1, 50);
- assert.equal(gradient.coords.x2, 50);
- assert.equal(gradient.coords.y2, 50);
-
assert.equal(gradient.colorStops[0].offset, 1);
assert.equal(gradient.colorStops[1].offset, 0.75);
assert.equal(gradient.colorStops[2].offset, 0.5);
@@ -770,11 +764,25 @@
assert.equal(gradient.toSVG(obj), SVG_INTERNALRADIUS);
});
- QUnit.test('toSVG radial with r1 > 0', function(assert) {
+ QUnit.test('toSVG radial with r1 > 0 swapped', function(assert) {
fabric.Object.__uid = 0;
var gradient = createRadialGradientSwapped();
var obj = new fabric.Object({ width: 100, height: 100 });
assert.equal(gradient.toSVG(obj), SVG_SWAPPED);
});
+ QUnit.test('toSVG linear objectBoundingBox', function(assert) {
+ fabric.Object.__uid = 0;
+ var gradient = createLinearGradient('percentage');
+ var obj = new fabric.Object({ width: 100, height: 100 });
+ assert.equal(gradient.toSVG(obj), SVG_LINEAR_PERCENTAGE);
+ });
+
+ QUnit.test('toSVG radial objectBoundingBox', function(assert) {
+ fabric.Object.__uid = 0;
+ var gradient = createRadialGradient('percentage');
+ var obj = new fabric.Object({ width: 100, height: 100 });
+ assert.equal(gradient.toSVG(obj), SVG_RADIAL_PERCENTAGE);
+ });
+
})();
diff --git a/test/unit/object_geometry.js b/test/unit/object_geometry.js
index eb61b2f6d86..cd3437a7bb6 100644
--- a/test/unit/object_geometry.js
+++ b/test/unit/object_geometry.js
@@ -435,13 +435,73 @@
});
QUnit.test('_calcDimensionsTransformMatrix', function(assert) {
- var cObj = new fabric.Object({ width: 10, height: 15, strokeWidth: 0 });
+ var cObj = new fabric.Object({ width: 10, height: 15, strokeWidth: 0, scaleX: 2, scaleY: 3, skewY: 10 });
+ assert.ok(typeof cObj._calcDimensionsTransformMatrix === 'function', '_calcDimensionsTransformMatrix should exist');
+ var matrix = cObj._calcDimensionsTransformMatrix();
+ var expected = [
+ 2,
+ 0,
+ 0,
+ 3,
+ 0,
+ 0
+ ];
+ assert.deepEqual(matrix, expected, 'dimensions matrix is equal');
+ });
+
+ QUnit.test('_calcDimensionsTransformMatrix with flipping', function(assert) {
+ var cObj = new fabric.Object({ width: 10, height: 15, strokeWidth: 0, scaleX: 2, scaleY: 3, skewY: 10, flipX: true });
assert.ok(typeof cObj._calcDimensionsTransformMatrix === 'function', '_calcDimensionsTransformMatrix should exist');
+ var matrix = cObj._calcDimensionsTransformMatrix(0, 0, false);
+ var expected = [
+ 2,
+ 0,
+ 0,
+ 3,
+ 0,
+ 0
+ ];
+ assert.deepEqual(matrix, expected, 'dimensions matrix with flipping = false is equal');
+ var matrix2 = cObj._calcDimensionsTransformMatrix(0, 0, true);
+ var expected = [
+ -2,
+ 0,
+ 0,
+ 3,
+ 0,
+ 0
+ ];
+ assert.deepEqual(matrix2, expected, 'dimensions matrix with flipping = true is equal');
});
QUnit.test('_calcRotateMatrix', function(assert) {
- var cObj = new fabric.Object({ width: 10, height: 15, strokeWidth: 0 });
+ var cObj = new fabric.Object({ width: 10, height: 15, strokeWidth: 0, angle: 90 });
assert.ok(typeof cObj._calcRotateMatrix === 'function', '_calcRotateMatrix should exist');
+ var matrix = cObj._calcRotateMatrix();
+ var expected = [
+ 0,
+ 1,
+ -1,
+ 0,
+ 0,
+ 0
+ ];
+ assert.deepEqual(matrix, expected, 'rotate matrix is equal');
+ });
+
+ QUnit.test('_calcTranslateMatrix', function(assert) {
+ var cObj = new fabric.Object({ top: 5, width: 10, height: 15, strokeWidth: 0, angle: 90 });
+ assert.ok(typeof cObj._calcTranslateMatrix === 'function', '_calcTranslateMatrix should exist');
+ var matrix = cObj._calcTranslateMatrix();
+ var expected = [
+ 1,
+ 0,
+ 0,
+ 1,
+ -7.5,
+ 10
+ ];
+ assert.deepEqual(matrix, expected, 'translate matrix is equal');
});
QUnit.test('scaleToWidth', function(assert) {
diff --git a/test/unit/util.js b/test/unit/util.js
index 678fbeaa520..c53fdff870e 100644
--- a/test/unit/util.js
+++ b/test/unit/util.js
@@ -917,6 +917,27 @@
assert.equal(options.translateY, 200, 'imatrix has translateY 200');
});
+ QUnit.test('composeMatrix with defaults', function(assert) {
+ assert.ok(typeof fabric.util.composeMatrix === 'function');
+ var matrix = fabric.util.composeMatrix({
+ scaleX: 2,
+ scaleY: 3,
+ skewX: 28,
+ angle: 11,
+ translateX: 100,
+ translateY: 200,
+ }).map(function(val) {
+ return fabric.util.toFixed(val, 2);
+ });
+ assert.deepEqual(matrix, [1.96, 0.38, 0.47, 3.15, 100, 200], 'default is identity matrix');
+ });
+
+ QUnit.test('composeMatrix with options', function(assert) {
+ assert.ok(typeof fabric.util.composeMatrix === 'function');
+ var matrix = fabric.util.composeMatrix({});
+ assert.deepEqual(matrix, fabric.iMatrix, 'default is identity matrix');
+ });
+
QUnit.test('drawArc', function(assert) {
assert.ok(typeof fabric.util.drawArc === 'function');
var canvas = this.canvas = new fabric.StaticCanvas(null, {enableRetinaScaling: false, width: 600, height: 600});
diff --git a/test/visual/assets/svg_linear_9.svg b/test/visual/assets/svg_linear_9.svg
new file mode 100644
index 00000000000..9cc808c5958
--- /dev/null
+++ b/test/visual/assets/svg_linear_9.svg
@@ -0,0 +1,138 @@
+
diff --git a/test/visual/golden/multipleGradients.png b/test/visual/golden/multipleGradients.png
new file mode 100644
index 00000000000..91502cbf7a4
Binary files /dev/null and b/test/visual/golden/multipleGradients.png differ
diff --git a/test/visual/golden/svg_linear_9.png b/test/visual/golden/svg_linear_9.png
new file mode 100644
index 00000000000..8a9e68a026a
Binary files /dev/null and b/test/visual/golden/svg_linear_9.png differ
diff --git a/test/visual/svg_import.js b/test/visual/svg_import.js
index 5d87a1e0658..18b8dcef99b 100644
--- a/test/visual/svg_import.js
+++ b/test/visual/svg_import.js
@@ -58,6 +58,7 @@
'svg_linear_6',
'svg_linear_7',
'svg_linear_8',
+ 'svg_linear_9',
'svg_radial_1',
'svg_radial_2',
'svg_radial_3',
@@ -76,7 +77,7 @@
'clippath-7',
'clippath-9',
'vector-effect',
- 'svg-with-no-dim-rect'
+ 'svg-with-no-dim-rect',
//'clippath-8',
].map(createTestFromSVG);
diff --git a/test/visual/z_svg_export.js b/test/visual/z_svg_export.js
index 6a6506c8d48..cb9925c097a 100644
--- a/test/visual/z_svg_export.js
+++ b/test/visual/z_svg_export.js
@@ -2,11 +2,14 @@
fabric.enableGLFiltering = false;
fabric.isWebglSupported = false;
var visualTestLoop;
+ var getAssetName;
if (fabric.isLikelyNode) {
visualTestLoop = global.visualTestLoop;
+ getAssetName = global.getAssetName;
}
else {
visualTestLoop = window.visualTestLoop;
+ getAssetName = window.getAssetName;
}
function svgToDataURL(svgStr) {
@@ -396,5 +399,22 @@
width: 210,
height: 230,
});
+
+ function multipleGradients(canvas, callback) {
+ fabric.loadSVGFromURL(getAssetName('svg_linear_9'), function(objects) {
+ var group = fabric.util.groupSVGElements(objects);
+ canvas.add(group);
+ toSVGCanvas(canvas, callback);
+ });
+ }
+
+ tests.push({
+ test: 'Multiple gradients import',
+ code: multipleGradients,
+ golden: 'multipleGradients.png',
+ percentage: 0.06,
+ width: 760,
+ height: 760,
+ });
tests.forEach(visualTestLoop(QUnit));
})();