From d391c6336887f7af9bf71ca58b7b6f37df950669 Mon Sep 17 00:00:00 2001 From: Andrea Bogazzi Date: Mon, 22 Jul 2019 12:58:28 +0200 Subject: [PATCH 01/19] a test file --- test/visual/assets/svg_linear_9.svg | 74 +++++++++++++++++++++++++++++ 1 file changed, 74 insertions(+) create mode 100644 test/visual/assets/svg_linear_9.svg diff --git a/test/visual/assets/svg_linear_9.svg b/test/visual/assets/svg_linear_9.svg new file mode 100644 index 00000000000..7ed432c543b --- /dev/null +++ b/test/visual/assets/svg_linear_9.svg @@ -0,0 +1,74 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + From f23bf1ac092cc4d520d2c2738152d6ba695c1a00 Mon Sep 17 00:00:00 2001 From: Andrea Bogazzi Date: Sat, 10 Aug 2019 09:49:47 +0200 Subject: [PATCH 02/19] improvements --- dist/fabric.js | 310 ++++++++++++++++++---------- src/elements_parser.js | 2 +- src/gradient.class.js | 155 ++++++++------ src/mixins/object_geometry.mixin.js | 26 +-- src/parser.js | 8 +- src/shapes/object.class.js | 9 +- src/util/misc.js | 65 +++++- test/visual/assets/svg_linear_9.svg | 25 ++- 8 files changed, 390 insertions(+), 210 deletions(-) diff --git a/dist/fabric.js b/dist/fabric.js index b232fcd53c3..f87fb988ae1 100644 --- a/dist/fabric.js +++ b/dist/fabric.js @@ -781,9 +781,15 @@ fabric.CommonMethods = { /** * Returns coordinates of points's bounding rectangle (left, top, width, height) * @param {Array} points 4 points array + * @param {Array} transform 6 number trasnform matrix * @return {Object} Object with left, top, width, height properties */ - makeBoundingBoxFromPoints: function(points) { + makeBoundingBoxFromPoints: function(points, transform) { + if (transform) { + points = points.map(function(point) { + return fabric.util.transformPoint(point, 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), @@ -1274,7 +1280,7 @@ fabric.CommonMethods = { }, /** - * Decomposes standard 2x2 matrix into transform componentes + * Decomposes standard 2x3 matrix into transform componentes * @static * @memberOf fabric.util * @param {Array} a transformMatrix @@ -1297,6 +1303,61 @@ fabric.CommonMethods = { }; }, + 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]; + }, + + calcDimensionsMatrix: function(options) { + var scaleX = typeof options.scaleX === 'undefined' ? 1 : options.scaleX, + scaleY = typeof options.scaleY === 'undefined' ? 1 : options.scaleY, + scaleMatrix = [scaleX, 0, 0, scaleY, 0, 0]; + if (options.skewX) { + scaleMatrix = fabric.util.multiplyTransformMatrices( + scaleMatrix, + [1, 0, Math.tan(fabric.util.degreesToRadians(options.skewX)), 1], + true); + } + if (options.skewY) { + scaleMatrix = fabric.util.multiplyTransformMatrices( + scaleMatrix, + [1, Math.tan(fabric.util.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 + * @static + * @memberOf fabric.util + * @param {Object} options + * @param {Number} [options.angle] + * @param {Number} [options.scaleX] + * @param {Number} [options.scaleY] + * @param {Number} [options.skewX] + * @param {Number} [options.skewX] + * @param {Number} [options.translateX] + * @param {Number} [options.translateY] + * @return {Array[Number]} transform matrix + */ + componeMatrix: function(options) { + var matrix = [1, 0, 0, 1, options.translateX || 0, options.translateY || 0]; + if (options.angle) { + matrix = fabric.util.multiplyTransformMatrices(matrix, fabric.util.calcRotateMatrix(options)); + } + if (options.scaleX || options.scaleY || options.skewX || options.skewY) { + matrix = fabric.util.multiplyTransformMatrices(matrix, fabric.util.calcDimensionsMatrix(options)); + } + return matrix; + }, + customTransformMatrix: function(scaleX, scaleY, skewX) { var skewMatrixX = [1, 0, abs(Math.tan(skewX * PiBy180)), 1], scaleMatrix = [abs(scaleX), 0, 0, abs(scaleY)]; @@ -2674,6 +2735,10 @@ if (typeof console !== 'undefined') { return false; } + function defaultEasing(t, b, c, d) { + return -c * Math.cos(t / d * (Math.PI / 2)) + c + b; + } + /** * Changes value from one to another within certain period of time, invoking callbacks as value is being changed. * @memberOf fabric.util @@ -2698,7 +2763,7 @@ if (typeof console !== 'undefined') { onChange = options.onChange || noop, abort = options.abort || noop, onComplete = options.onComplete || noop, - easing = options.easing || function(t, b, c, d) {return -c * Math.cos(t / d * (Math.PI / 2)) + c + b;}, + easing = options.easing || defaultEasing, startValue = 'startValue' in options ? options.startValue : 0, endValue = 'endValue' in options ? options.endValue : 100, byValue = options.byValue || endValue - startValue; @@ -2708,24 +2773,26 @@ if (typeof console !== 'undefined') { (function tick(ticktime) { // TODO: move abort call after calculation // and pass (current,valuePerc, timePerc) as arguments - if (abort()) { - onComplete(endValue, 1, 1); - return; - } time = ticktime || +new Date(); var currentTime = time > finish ? duration : (time - start), timePerc = currentTime / duration, current = easing(currentTime, startValue, byValue, duration), valuePerc = Math.abs((current - startValue) / byValue); - onChange(current, valuePerc, timePerc); + if (abort()) { + onComplete(endValue, 1, 1); + return; + } if (time > finish) { - options.onComplete && options.onComplete(); + onChange(endValue, 1, 1); + onComplete(endValue, 1, 1); return; } - requestAnimFrame(tick); + else { + onChange(current, valuePerc, timePerc); + requestAnimFrame(tick); + } })(start); }); - } var _requestAnimFrame = fabric.window.requestAnimationFrame || @@ -3773,12 +3840,14 @@ if (typeof console !== 'undefined') { 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); @@ -3937,7 +4006,7 @@ if (typeof console !== 'undefined') { 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)); } }); @@ -4329,8 +4398,8 @@ fabric.ElementsParser = function(elements, callback, options, reviver, parsingOp var _this = this; return function(obj) { var _options; - _this.resolveGradient(obj, 'fill'); - _this.resolveGradient(obj, 'stroke'); + _this.resolveGradient(obj, el, 'fill'); + _this.resolveGradient(obj, el, 'stroke'); if (obj instanceof fabric.Image && obj._originalElement) { _options = obj.parsePreserveAspectRatioAttribute(el); } @@ -4352,10 +4421,12 @@ fabric.ElementsParser = function(elements, callback, options, reviver, parsingOp return fabric[storage][this.svgUid][id]; }; - proto.resolveGradient = function(obj, property) { + proto.resolveGradient = function(obj, el, property) { var gradientDef = this.extractPropertyDefinition(obj, property, 'gradientDefs'); if (gradientDef) { - obj.set(property, fabric.Gradient.fromElement(gradientDef, obj)); + var opacityAttr = el.getAttribute(property + '-opacity'); + var gradient = fabric.Gradient.fromElement(gradientDef, obj, opacityAttr, this.options); + obj.set(property, gradient); } }; @@ -5571,7 +5642,7 @@ fabric.ElementsParser = function(elements, callback, options, reviver, parsingOp (function() { /* _FROM_SVG_START_ */ - function getColorStop(el) { + function getColorStop(el, multiplier) { var style = el.getAttribute('style'), offset = el.getAttribute('offset') || 0, color, colorAlpha, opacity, i; @@ -5611,7 +5682,7 @@ fabric.ElementsParser = function(elements, callback, options, reviver, parsingOp color = new fabric.Color(color); colorAlpha = color.getAlpha(); opacity = isNaN(parseFloat(opacity)) ? 1 : parseFloat(opacity); - opacity *= colorAlpha; + opacity *= colorAlpha * multiplier; return { offset: offset, @@ -5665,18 +5736,59 @@ fabric.ElementsParser = function(elements, callback, options, reviver, parsingOp */ offsetY: 0, + /** + * A transform matrix to apply to the gradient before paiting. + * It shares the same name with the svg property but behaves differently. + * @type Array[Number] + * @default null + */ + gradientTransform: null, + + /** + * coordinates units for coords. + * If `pixels`, the number of cords 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 + * @param {Object} [options.gradientUnits] gradient units + * @param {Object} [options.offsetX] SVG import compatibility + * @param {Object} [options.offsetY] SVG import compatibility + * @param {Object} options.coords contains the coords of the gradient + * @param {Array[Object]} options.colorStops contains the colorstops. + * @param {Number} [options.coords.x1] + * @param {Number} [options.coords.y1] + * @param {Number} [options.coords.x2] + * @param {Number} [options.coords.y2] + * @param {Number} [options.coords.r1] + * @param {Number} [options.coords.r2] * @return {fabric.Gradient} thisArg */ initialize: function(options) { options || (options = { }); - 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]; + }); + + !this.id && (this.id = fabric.Object.__uid++); coords = { x1: options.coords.x1 || 0, @@ -5689,13 +5801,9 @@ fabric.ElementsParser = function(elements, callback, options, reviver, parsingOp 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; }, /** @@ -5872,11 +5980,12 @@ fabric.ElementsParser = function(elements, callback, options, reviver, parsingOp * @memberOf fabric.Gradient * @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. * @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) { + fromElement: function(el, instance, opacityAttr, svgOptions) { /** * @example: * @@ -5910,45 +6019,53 @@ fabric.ElementsParser = function(elements, callback, options, reviver, parsingOp * */ + var multiplier = parseFloat(opacityAttr) / (/%$/.test(opacityAttr) ? 100 : 1); + multiplier = multiplier < 0 ? 0 : multiplier > 1 ? 1 : multiplier; + if (isNaN(multiplier)) { + multiplier = 1; + } + 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); } for (i = colorStopEls.length; i--; ) { - colorStops.push(getColorStop(colorStopEls[i])); + 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_ */ @@ -5962,7 +6079,7 @@ fabric.ElementsParser = function(elements, callback, options, reviver, parsingOp */ forObject: function(obj, options) { options || (options = { }); - _convertPercentUnitsToValues(obj, options.coords, 'userSpaceOnUse'); + __convertPercentUnitsToValues(obj, options.coords); return new fabric.Gradient(options); } }); @@ -5970,47 +6087,33 @@ fabric.ElementsParser = function(elements, callback, options, reviver, parsingOp /** * @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; + }); } })(); @@ -13951,10 +14054,17 @@ fabric.util.object.extend(fabric.StaticCanvas.prototype, /** @lends fabric.Stati 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); + // 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]); } + console.log(filler) return { offsetX: offsetX, offsetY: offsetY }; }, @@ -15271,11 +15381,7 @@ fabric.util.object.extend(fabric.StaticCanvas.prototype, /** @lends fabric.Stati * @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); }, /** @@ -15339,22 +15445,14 @@ fabric.util.object.extend(fabric.StaticCanvas.prototype, /** @lends fabric.Stati }, _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 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..2c2ebc5acc7 100644 --- a/src/gradient.class.js +++ b/src/gradient.class.js @@ -95,18 +95,59 @@ */ offsetY: 0, + /** + * A transform matrix to apply to the gradient before paiting. + * It shares the same name with the svg property but behaves differently. + * @type Array[Number] + * @default null + */ + gradientTransform: null, + + /** + * coordinates units for coords. + * If `pixels`, the number of cords 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 + * @param {Object} [options.gradientUnits] gradient units + * @param {Object} [options.offsetX] SVG import compatibility + * @param {Object} [options.offsetY] SVG import compatibility + * @param {Object} options.coords contains the coords of the gradient + * @param {Array[Object]} options.colorStops contains the colorstops. + * @param {Number} [options.coords.x1] + * @param {Number} [options.coords.y1] + * @param {Number} [options.coords.x2] + * @param {Number} [options.coords.y2] + * @param {Number} [options.coords.r1] + * @param {Number} [options.coords.r2] * @return {fabric.Gradient} thisArg */ initialize: function(options) { options || (options = { }); - 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]; + }); + + !this.id && (this.id = fabric.Object.__uid++); coords = { x1: options.coords.x1 || 0, @@ -119,13 +160,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; }, /** @@ -307,7 +344,7 @@ * @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 +386,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,20 +405,26 @@ 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_ */ @@ -399,7 +438,7 @@ */ forObject: function(obj, options) { options || (options = { }); - _convertPercentUnitsToValues(obj, options.coords, 'userSpaceOnUse'); + __convertPercentUnitsToValues(obj, options.coords); return new fabric.Gradient(options); } }); @@ -407,46 +446,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..bb00ace97f5 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); }, /** @@ -546,22 +542,14 @@ }, _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 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..7c157980ba4 100644 --- a/src/shapes/object.class.js +++ b/src/shapes/object.class.js @@ -1474,10 +1474,17 @@ 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); + // 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]); } + console.log(filler) return { offsetX: offsetX, offsetY: offsetY }; }, diff --git a/src/util/misc.js b/src/util/misc.js index 8e9a85381b0..db1023e8fed 100644 --- a/src/util/misc.js +++ b/src/util/misc.js @@ -165,9 +165,15 @@ /** * Returns coordinates of points's bounding rectangle (left, top, width, height) * @param {Array} points 4 points array + * @param {Array} transform 6 number trasnform matrix * @return {Object} Object with left, top, width, height properties */ - makeBoundingBoxFromPoints: function(points) { + makeBoundingBoxFromPoints: function(points, transform) { + if (transform) { + points = points.map(function(point) { + return fabric.util.transformPoint(point, 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 +664,7 @@ }, /** - * Decomposes standard 2x2 matrix into transform componentes + * Decomposes standard 2x3 matrix into transform componentes * @static * @memberOf fabric.util * @param {Array} a transformMatrix @@ -681,6 +687,61 @@ }; }, + 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]; + }, + + calcDimensionsMatrix: function(options) { + var scaleX = typeof options.scaleX === 'undefined' ? 1 : options.scaleX, + scaleY = typeof options.scaleY === 'undefined' ? 1 : options.scaleY, + scaleMatrix = [scaleX, 0, 0, scaleY, 0, 0]; + if (options.skewX) { + scaleMatrix = fabric.util.multiplyTransformMatrices( + scaleMatrix, + [1, 0, Math.tan(fabric.util.degreesToRadians(options.skewX)), 1], + true); + } + if (options.skewY) { + scaleMatrix = fabric.util.multiplyTransformMatrices( + scaleMatrix, + [1, Math.tan(fabric.util.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 + * @static + * @memberOf fabric.util + * @param {Object} options + * @param {Number} [options.angle] + * @param {Number} [options.scaleX] + * @param {Number} [options.scaleY] + * @param {Number} [options.skewX] + * @param {Number} [options.skewX] + * @param {Number} [options.translateX] + * @param {Number} [options.translateY] + * @return {Array[Number]} transform matrix + */ + componeMatrix: function(options) { + var matrix = [1, 0, 0, 1, options.translateX || 0, options.translateY || 0]; + if (options.angle) { + matrix = fabric.util.multiplyTransformMatrices(matrix, fabric.util.calcRotateMatrix(options)); + } + if (options.scaleX || options.scaleY || options.skewX || options.skewY) { + matrix = fabric.util.multiplyTransformMatrices(matrix, fabric.util.calcDimensionsMatrix(options)); + } + return matrix; + }, + customTransformMatrix: function(scaleX, scaleY, skewX) { var skewMatrixX = [1, 0, abs(Math.tan(skewX * PiBy180)), 1], scaleMatrix = [abs(scaleX), 0, 0, abs(scaleY)]; diff --git a/test/visual/assets/svg_linear_9.svg b/test/visual/assets/svg_linear_9.svg index 7ed432c543b..6f6c64c28d6 100644 --- a/test/visual/assets/svg_linear_9.svg +++ b/test/visual/assets/svg_linear_9.svg @@ -56,19 +56,18 @@ + - - - - - + + + + - - - - - - - - + + + + + + + From 001218dd3abab4961ccb261dcb759b48344c8ef6 Mon Sep 17 00:00:00 2001 From: Andrea Bogazzi Date: Sat, 10 Aug 2019 09:52:24 +0200 Subject: [PATCH 03/19] improvements --- dist/fabric.js | 310 +++++++++++++++++-------------------------------- 1 file changed, 106 insertions(+), 204 deletions(-) diff --git a/dist/fabric.js b/dist/fabric.js index f87fb988ae1..b232fcd53c3 100644 --- a/dist/fabric.js +++ b/dist/fabric.js @@ -781,15 +781,9 @@ fabric.CommonMethods = { /** * Returns coordinates of points's bounding rectangle (left, top, width, height) * @param {Array} points 4 points array - * @param {Array} transform 6 number trasnform matrix * @return {Object} Object with left, top, width, height properties */ - makeBoundingBoxFromPoints: function(points, transform) { - if (transform) { - points = points.map(function(point) { - return fabric.util.transformPoint(point, transform); - }); - } + makeBoundingBoxFromPoints: function(points) { 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), @@ -1280,7 +1274,7 @@ fabric.CommonMethods = { }, /** - * Decomposes standard 2x3 matrix into transform componentes + * Decomposes standard 2x2 matrix into transform componentes * @static * @memberOf fabric.util * @param {Array} a transformMatrix @@ -1303,61 +1297,6 @@ fabric.CommonMethods = { }; }, - 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]; - }, - - calcDimensionsMatrix: function(options) { - var scaleX = typeof options.scaleX === 'undefined' ? 1 : options.scaleX, - scaleY = typeof options.scaleY === 'undefined' ? 1 : options.scaleY, - scaleMatrix = [scaleX, 0, 0, scaleY, 0, 0]; - if (options.skewX) { - scaleMatrix = fabric.util.multiplyTransformMatrices( - scaleMatrix, - [1, 0, Math.tan(fabric.util.degreesToRadians(options.skewX)), 1], - true); - } - if (options.skewY) { - scaleMatrix = fabric.util.multiplyTransformMatrices( - scaleMatrix, - [1, Math.tan(fabric.util.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 - * @static - * @memberOf fabric.util - * @param {Object} options - * @param {Number} [options.angle] - * @param {Number} [options.scaleX] - * @param {Number} [options.scaleY] - * @param {Number} [options.skewX] - * @param {Number} [options.skewX] - * @param {Number} [options.translateX] - * @param {Number} [options.translateY] - * @return {Array[Number]} transform matrix - */ - componeMatrix: function(options) { - var matrix = [1, 0, 0, 1, options.translateX || 0, options.translateY || 0]; - if (options.angle) { - matrix = fabric.util.multiplyTransformMatrices(matrix, fabric.util.calcRotateMatrix(options)); - } - if (options.scaleX || options.scaleY || options.skewX || options.skewY) { - matrix = fabric.util.multiplyTransformMatrices(matrix, fabric.util.calcDimensionsMatrix(options)); - } - return matrix; - }, - customTransformMatrix: function(scaleX, scaleY, skewX) { var skewMatrixX = [1, 0, abs(Math.tan(skewX * PiBy180)), 1], scaleMatrix = [abs(scaleX), 0, 0, abs(scaleY)]; @@ -2735,10 +2674,6 @@ if (typeof console !== 'undefined') { return false; } - function defaultEasing(t, b, c, d) { - return -c * Math.cos(t / d * (Math.PI / 2)) + c + b; - } - /** * Changes value from one to another within certain period of time, invoking callbacks as value is being changed. * @memberOf fabric.util @@ -2763,7 +2698,7 @@ if (typeof console !== 'undefined') { onChange = options.onChange || noop, abort = options.abort || noop, onComplete = options.onComplete || noop, - easing = options.easing || defaultEasing, + easing = options.easing || function(t, b, c, d) {return -c * Math.cos(t / d * (Math.PI / 2)) + c + b;}, startValue = 'startValue' in options ? options.startValue : 0, endValue = 'endValue' in options ? options.endValue : 100, byValue = options.byValue || endValue - startValue; @@ -2773,26 +2708,24 @@ if (typeof console !== 'undefined') { (function tick(ticktime) { // TODO: move abort call after calculation // and pass (current,valuePerc, timePerc) as arguments + if (abort()) { + onComplete(endValue, 1, 1); + return; + } time = ticktime || +new Date(); var currentTime = time > finish ? duration : (time - start), timePerc = currentTime / duration, current = easing(currentTime, startValue, byValue, duration), valuePerc = Math.abs((current - startValue) / byValue); - if (abort()) { - onComplete(endValue, 1, 1); - return; - } + onChange(current, valuePerc, timePerc); if (time > finish) { - onChange(endValue, 1, 1); - onComplete(endValue, 1, 1); + options.onComplete && options.onComplete(); return; } - else { - onChange(current, valuePerc, timePerc); - requestAnimFrame(tick); - } + requestAnimFrame(tick); })(start); }); + } var _requestAnimFrame = fabric.window.requestAnimationFrame || @@ -3840,14 +3773,12 @@ if (typeof console !== 'undefined') { 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); @@ -4006,7 +3937,7 @@ if (typeof console !== 'undefined') { recursivelyParseGradientsXlink(doc, referencedGradient); } gradientsAttrs.forEach(function(attr) { - if (referencedGradient && !gradient.hasAttribute(attr) && referencedGradient.hasAttribute(attr)) { + if (!gradient.hasAttribute(attr)) { gradient.setAttribute(attr, referencedGradient.getAttribute(attr)); } }); @@ -4398,8 +4329,8 @@ fabric.ElementsParser = function(elements, callback, options, reviver, parsingOp var _this = this; return function(obj) { var _options; - _this.resolveGradient(obj, el, 'fill'); - _this.resolveGradient(obj, el, 'stroke'); + _this.resolveGradient(obj, 'fill'); + _this.resolveGradient(obj, 'stroke'); if (obj instanceof fabric.Image && obj._originalElement) { _options = obj.parsePreserveAspectRatioAttribute(el); } @@ -4421,12 +4352,10 @@ fabric.ElementsParser = function(elements, callback, options, reviver, parsingOp return fabric[storage][this.svgUid][id]; }; - proto.resolveGradient = function(obj, el, property) { + proto.resolveGradient = function(obj, property) { var gradientDef = this.extractPropertyDefinition(obj, property, 'gradientDefs'); if (gradientDef) { - var opacityAttr = el.getAttribute(property + '-opacity'); - var gradient = fabric.Gradient.fromElement(gradientDef, obj, opacityAttr, this.options); - obj.set(property, gradient); + obj.set(property, fabric.Gradient.fromElement(gradientDef, obj)); } }; @@ -5642,7 +5571,7 @@ fabric.ElementsParser = function(elements, callback, options, reviver, parsingOp (function() { /* _FROM_SVG_START_ */ - function getColorStop(el, multiplier) { + function getColorStop(el) { var style = el.getAttribute('style'), offset = el.getAttribute('offset') || 0, color, colorAlpha, opacity, i; @@ -5682,7 +5611,7 @@ fabric.ElementsParser = function(elements, callback, options, reviver, parsingOp color = new fabric.Color(color); colorAlpha = color.getAlpha(); opacity = isNaN(parseFloat(opacity)) ? 1 : parseFloat(opacity); - opacity *= colorAlpha * multiplier; + opacity *= colorAlpha; return { offset: offset, @@ -5736,59 +5665,18 @@ fabric.ElementsParser = function(elements, callback, options, reviver, parsingOp */ offsetY: 0, - /** - * A transform matrix to apply to the gradient before paiting. - * It shares the same name with the svg property but behaves differently. - * @type Array[Number] - * @default null - */ - gradientTransform: null, - - /** - * coordinates units for coords. - * If `pixels`, the number of cords 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.type] gradient type - * @param {Object} [options.gradientUnits] gradient units - * @param {Object} [options.offsetX] SVG import compatibility - * @param {Object} [options.offsetY] SVG import compatibility - * @param {Object} options.coords contains the coords of the gradient - * @param {Array[Object]} options.colorStops contains the colorstops. - * @param {Number} [options.coords.x1] - * @param {Number} [options.coords.y1] - * @param {Number} [options.coords.x2] - * @param {Number} [options.coords.y2] - * @param {Number} [options.coords.r1] - * @param {Number} [options.coords.r2] + * @param {Object} [options] Options object with type, coords, gradientUnits and colorStops * @return {fabric.Gradient} thisArg */ initialize: function(options) { options || (options = { }); - var coords = { }, _this = this; + var coords = { }; - // sets everything, then coords and colorstops get sets again - Object.keys(options).forEach(function(option) { - _this[option] = options[option]; - }); - - !this.id && (this.id = fabric.Object.__uid++); + this.id = fabric.Object.__uid++; + this.type = options.type || 'linear'; coords = { x1: options.coords.x1 || 0, @@ -5801,9 +5689,13 @@ fabric.ElementsParser = function(elements, callback, options, reviver, parsingOp 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; }, /** @@ -5980,12 +5872,11 @@ fabric.ElementsParser = function(elements, callback, options, reviver, parsingOp * @memberOf fabric.Gradient * @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. * @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, svgOptions) { + fromElement: function(el, instance) { /** * @example: * @@ -6019,53 +5910,45 @@ fabric.ElementsParser = function(elements, callback, options, reviver, parsingOp * */ - var multiplier = parseFloat(opacityAttr) / (/%$/.test(opacityAttr) ? 100 : 1); - multiplier = multiplier < 0 ? 0 : multiplier > 1 ? 1 : multiplier; - if (isNaN(multiplier)) { - multiplier = 1; - } - var colorStopEls = el.getElementsByTagName('stop'), type, - gradientUnits = el.getAttribute('gradientUnits') === 'userSpaceOnUse' ? - 'pixels' : 'percentage', - gradientTransform = el.getAttribute('gradientTransform') || '', + gradientUnits = el.getAttribute('gradientUnits') || 'objectBoundingBox', + gradientTransform = el.getAttribute('gradientTransform'), colorStops = [], - coords, i, offsetX = 0, offsetY = 0, - transformMatrix; + coords, ellipseMatrix, i; + 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); } for (i = colorStopEls.length; i--; ) { - colorStops.push(getColorStop(colorStopEls[i], multiplier)); + colorStops.push(getColorStop(colorStopEls[i])); } - transformMatrix = fabric.parseTransformAttribute(gradientTransform); - - __convertPercentUnitsToValues(instance, coords, svgOptions, gradientUnits); - - if (gradientUnits === 'pixels') { - offsetX = -instance.left; - offsetY = -instance.top; - } + ellipseMatrix = _convertPercentUnitsToValues(instance, coords, gradientUnits); var gradient = new fabric.Gradient({ - id: el.getAttribute('id'), type: type, coords: coords, colorStops: colorStops, - gradientUnits: gradientUnits, - gradientTransform: transformMatrix, - offsetX: offsetX, - offsetY: offsetY, + offsetX: -instance.left, + offsetY: -instance.top }); + if (gradientTransform || ellipseMatrix !== '') { + gradient.gradientTransform = fabric.parseTransformAttribute((gradientTransform || '') + ellipseMatrix); + } + return gradient; }, /* _FROM_SVG_END_ */ @@ -6079,7 +5962,7 @@ fabric.ElementsParser = function(elements, callback, options, reviver, parsingOp */ forObject: function(obj, options) { options || (options = { }); - __convertPercentUnitsToValues(obj, options.coords); + _convertPercentUnitsToValues(obj, options.coords, 'userSpaceOnUse'); return new fabric.Gradient(options); } }); @@ -6087,33 +5970,47 @@ fabric.ElementsParser = function(elements, callback, options, reviver, parsingOp /** * @private */ - function __convertPercentUnitsToValues(instance, options, svgOptions, gradientUnits) { - var propValue, finalValue; - Object.keys(options).forEach(function(prop) { - propValue = options[prop]; - if (propValue === 'Infinity') { - finalValue = 1; + 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; } - else if (propValue === '-Infinity') { - finalValue = 0; + propValue = parseFloat(options[prop], 10); + if (typeof options[prop] === 'string' && /^(\d+\.\d+)%|(\d+)%$/.test(options[prop])) { + multFactor = 0.01; } else { - 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; - } - } - } + multFactor = 1; } - options[prop] = finalValue; - }); + 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; + } + } + return ellipseMatrix; } })(); @@ -14054,17 +13951,10 @@ fabric.util.object.extend(fabric.StaticCanvas.prototype, /** @lends fabric.Stati 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); - } + ctx.translate(offsetX, offsetY); if (t) { ctx.transform(t[0], t[1], t[2], t[3], t[4], t[5]); } - console.log(filler) return { offsetX: offsetX, offsetY: offsetY }; }, @@ -15381,7 +15271,11 @@ fabric.util.object.extend(fabric.StaticCanvas.prototype, /** @lends fabric.Stati * @return {Array} rotation matrix for the object */ _calcRotateMatrix: function() { - return fabric.util.calcRotateMatrix(this); + 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(); }, /** @@ -15445,14 +15339,22 @@ fabric.util.object.extend(fabric.StaticCanvas.prototype, /** @lends fabric.Stati }, _calcDimensionsTransformMatrix: function(skewX, skewY, flipping) { - return fabric.util.calcDimensionsMatrix({ - skewX: skewX, - skewY: skewY, - scaleX: this.scaleX * (flipping && this.flipX ? -1 : 1), - scaleY: this.scaleY * (flipping && this.flipY ? -1 : 1) - }); + 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; }, + /* * Calculate object dimensions from its properties * @private From d029fc8c12158d7e4db482de1986477a114e78f7 Mon Sep 17 00:00:00 2001 From: Andrea Bogazzi Date: Sat, 10 Aug 2019 09:57:29 +0200 Subject: [PATCH 04/19] improvements --- src/mixins/object_geometry.mixin.js | 7 ++----- src/util/misc.js | 2 +- 2 files changed, 3 insertions(+), 6 deletions(-) diff --git a/src/mixins/object_geometry.mixin.js b/src/mixins/object_geometry.mixin.js index bb00ace97f5..fb0a746f1e6 100644 --- a/src/mixins/object_geometry.mixin.js +++ b/src/mixins/object_geometry.mixin.js @@ -613,12 +613,9 @@ x: dimX, y: dimY }], - i, transformMatrix = this._calcDimensionsTransformMatrix(skewX, skewY, false), + 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); + bbox = fabric.util.makeBoundingBoxFromPoints(points, transformMatrix); return this._finalizeDimensions(bbox.width, bbox.height); }, diff --git a/src/util/misc.js b/src/util/misc.js index db1023e8fed..b175bf58ce5 100644 --- a/src/util/misc.js +++ b/src/util/misc.js @@ -165,7 +165,7 @@ /** * Returns coordinates of points's bounding rectangle (left, top, width, height) * @param {Array} points 4 points array - * @param {Array} transform 6 number trasnform matrix + * @param {Array} [transform] 6 number trasnform matrix * @return {Object} Object with left, top, width, height properties */ makeBoundingBoxFromPoints: function(points, transform) { From fe3d5a80c5affbe174cf29bacf19e58bc54fcf12 Mon Sep 17 00:00:00 2001 From: Andrea Bogazzi Date: Sat, 10 Aug 2019 13:23:02 +0200 Subject: [PATCH 05/19] fixed tests --- test/unit/gradient.js | 113 +++++++++++++++++++----------------------- 1 file changed, 52 insertions(+), 61 deletions(-) diff --git a/test/unit/gradient.js b/test/unit/gradient.js index 7c32f1b18fe..3e61d25cd34 100644 --- a/test/unit/gradient.js +++ b/test/unit/gradient.js @@ -212,11 +212,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 +255,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 +284,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 +321,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 +377,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 +402,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 +476,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 +493,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 +528,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 +570,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 +625,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); From 2cdc4431af4d8ad8a1fdfafb1bc8ddad0735c681 Mon Sep 17 00:00:00 2001 From: Andrea Bogazzi Date: Sun, 11 Aug 2019 08:50:49 +0200 Subject: [PATCH 06/19] fix lint --- dist/fabric.js | 316 ++++++++++++++++++++++++------------- src/shapes/object.class.js | 1 - 2 files changed, 205 insertions(+), 112 deletions(-) diff --git a/dist/fabric.js b/dist/fabric.js index b232fcd53c3..dfee4bb3817 100644 --- a/dist/fabric.js +++ b/dist/fabric.js @@ -781,9 +781,15 @@ fabric.CommonMethods = { /** * Returns coordinates of points's bounding rectangle (left, top, width, height) * @param {Array} points 4 points array + * @param {Array} [transform] 6 number trasnform matrix * @return {Object} Object with left, top, width, height properties */ - makeBoundingBoxFromPoints: function(points) { + makeBoundingBoxFromPoints: function(points, transform) { + if (transform) { + points = points.map(function(point) { + return fabric.util.transformPoint(point, 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), @@ -1274,7 +1280,7 @@ fabric.CommonMethods = { }, /** - * Decomposes standard 2x2 matrix into transform componentes + * Decomposes standard 2x3 matrix into transform componentes * @static * @memberOf fabric.util * @param {Array} a transformMatrix @@ -1297,6 +1303,61 @@ fabric.CommonMethods = { }; }, + 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]; + }, + + calcDimensionsMatrix: function(options) { + var scaleX = typeof options.scaleX === 'undefined' ? 1 : options.scaleX, + scaleY = typeof options.scaleY === 'undefined' ? 1 : options.scaleY, + scaleMatrix = [scaleX, 0, 0, scaleY, 0, 0]; + if (options.skewX) { + scaleMatrix = fabric.util.multiplyTransformMatrices( + scaleMatrix, + [1, 0, Math.tan(fabric.util.degreesToRadians(options.skewX)), 1], + true); + } + if (options.skewY) { + scaleMatrix = fabric.util.multiplyTransformMatrices( + scaleMatrix, + [1, Math.tan(fabric.util.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 + * @static + * @memberOf fabric.util + * @param {Object} options + * @param {Number} [options.angle] + * @param {Number} [options.scaleX] + * @param {Number} [options.scaleY] + * @param {Number} [options.skewX] + * @param {Number} [options.skewX] + * @param {Number} [options.translateX] + * @param {Number} [options.translateY] + * @return {Array[Number]} transform matrix + */ + componeMatrix: function(options) { + var matrix = [1, 0, 0, 1, options.translateX || 0, options.translateY || 0]; + if (options.angle) { + matrix = fabric.util.multiplyTransformMatrices(matrix, fabric.util.calcRotateMatrix(options)); + } + if (options.scaleX || options.scaleY || options.skewX || options.skewY) { + matrix = fabric.util.multiplyTransformMatrices(matrix, fabric.util.calcDimensionsMatrix(options)); + } + return matrix; + }, + customTransformMatrix: function(scaleX, scaleY, skewX) { var skewMatrixX = [1, 0, abs(Math.tan(skewX * PiBy180)), 1], scaleMatrix = [abs(scaleX), 0, 0, abs(scaleY)]; @@ -2674,6 +2735,10 @@ if (typeof console !== 'undefined') { return false; } + function defaultEasing(t, b, c, d) { + return -c * Math.cos(t / d * (Math.PI / 2)) + c + b; + } + /** * Changes value from one to another within certain period of time, invoking callbacks as value is being changed. * @memberOf fabric.util @@ -2698,7 +2763,7 @@ if (typeof console !== 'undefined') { onChange = options.onChange || noop, abort = options.abort || noop, onComplete = options.onComplete || noop, - easing = options.easing || function(t, b, c, d) {return -c * Math.cos(t / d * (Math.PI / 2)) + c + b;}, + easing = options.easing || defaultEasing, startValue = 'startValue' in options ? options.startValue : 0, endValue = 'endValue' in options ? options.endValue : 100, byValue = options.byValue || endValue - startValue; @@ -2708,24 +2773,26 @@ if (typeof console !== 'undefined') { (function tick(ticktime) { // TODO: move abort call after calculation // and pass (current,valuePerc, timePerc) as arguments - if (abort()) { - onComplete(endValue, 1, 1); - return; - } time = ticktime || +new Date(); var currentTime = time > finish ? duration : (time - start), timePerc = currentTime / duration, current = easing(currentTime, startValue, byValue, duration), valuePerc = Math.abs((current - startValue) / byValue); - onChange(current, valuePerc, timePerc); + if (abort()) { + onComplete(endValue, 1, 1); + return; + } if (time > finish) { - options.onComplete && options.onComplete(); + onChange(endValue, 1, 1); + onComplete(endValue, 1, 1); return; } - requestAnimFrame(tick); + else { + onChange(current, valuePerc, timePerc); + requestAnimFrame(tick); + } })(start); }); - } var _requestAnimFrame = fabric.window.requestAnimationFrame || @@ -3773,12 +3840,14 @@ if (typeof console !== 'undefined') { 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); @@ -3937,7 +4006,7 @@ if (typeof console !== 'undefined') { 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)); } }); @@ -4329,8 +4398,8 @@ fabric.ElementsParser = function(elements, callback, options, reviver, parsingOp var _this = this; return function(obj) { var _options; - _this.resolveGradient(obj, 'fill'); - _this.resolveGradient(obj, 'stroke'); + _this.resolveGradient(obj, el, 'fill'); + _this.resolveGradient(obj, el, 'stroke'); if (obj instanceof fabric.Image && obj._originalElement) { _options = obj.parsePreserveAspectRatioAttribute(el); } @@ -4352,10 +4421,12 @@ fabric.ElementsParser = function(elements, callback, options, reviver, parsingOp return fabric[storage][this.svgUid][id]; }; - proto.resolveGradient = function(obj, property) { + proto.resolveGradient = function(obj, el, property) { var gradientDef = this.extractPropertyDefinition(obj, property, 'gradientDefs'); if (gradientDef) { - obj.set(property, fabric.Gradient.fromElement(gradientDef, obj)); + var opacityAttr = el.getAttribute(property + '-opacity'); + var gradient = fabric.Gradient.fromElement(gradientDef, obj, opacityAttr, this.options); + obj.set(property, gradient); } }; @@ -5571,7 +5642,7 @@ fabric.ElementsParser = function(elements, callback, options, reviver, parsingOp (function() { /* _FROM_SVG_START_ */ - function getColorStop(el) { + function getColorStop(el, multiplier) { var style = el.getAttribute('style'), offset = el.getAttribute('offset') || 0, color, colorAlpha, opacity, i; @@ -5611,7 +5682,7 @@ fabric.ElementsParser = function(elements, callback, options, reviver, parsingOp color = new fabric.Color(color); colorAlpha = color.getAlpha(); opacity = isNaN(parseFloat(opacity)) ? 1 : parseFloat(opacity); - opacity *= colorAlpha; + opacity *= colorAlpha * multiplier; return { offset: offset, @@ -5665,18 +5736,59 @@ fabric.ElementsParser = function(elements, callback, options, reviver, parsingOp */ offsetY: 0, + /** + * A transform matrix to apply to the gradient before paiting. + * It shares the same name with the svg property but behaves differently. + * @type Array[Number] + * @default null + */ + gradientTransform: null, + + /** + * coordinates units for coords. + * If `pixels`, the number of cords 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 + * @param {Object} [options.gradientUnits] gradient units + * @param {Object} [options.offsetX] SVG import compatibility + * @param {Object} [options.offsetY] SVG import compatibility + * @param {Object} options.coords contains the coords of the gradient + * @param {Array[Object]} options.colorStops contains the colorstops. + * @param {Number} [options.coords.x1] + * @param {Number} [options.coords.y1] + * @param {Number} [options.coords.x2] + * @param {Number} [options.coords.y2] + * @param {Number} [options.coords.r1] + * @param {Number} [options.coords.r2] * @return {fabric.Gradient} thisArg */ initialize: function(options) { options || (options = { }); - 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]; + }); + + !this.id && (this.id = fabric.Object.__uid++); coords = { x1: options.coords.x1 || 0, @@ -5689,13 +5801,9 @@ fabric.ElementsParser = function(elements, callback, options, reviver, parsingOp 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; }, /** @@ -5872,11 +5980,12 @@ fabric.ElementsParser = function(elements, callback, options, reviver, parsingOp * @memberOf fabric.Gradient * @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. * @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) { + fromElement: function(el, instance, opacityAttr, svgOptions) { /** * @example: * @@ -5910,45 +6019,53 @@ fabric.ElementsParser = function(elements, callback, options, reviver, parsingOp * */ + var multiplier = parseFloat(opacityAttr) / (/%$/.test(opacityAttr) ? 100 : 1); + multiplier = multiplier < 0 ? 0 : multiplier > 1 ? 1 : multiplier; + if (isNaN(multiplier)) { + multiplier = 1; + } + 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); } for (i = colorStopEls.length; i--; ) { - colorStops.push(getColorStop(colorStopEls[i])); + 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_ */ @@ -5962,7 +6079,7 @@ fabric.ElementsParser = function(elements, callback, options, reviver, parsingOp */ forObject: function(obj, options) { options || (options = { }); - _convertPercentUnitsToValues(obj, options.coords, 'userSpaceOnUse'); + __convertPercentUnitsToValues(obj, options.coords); return new fabric.Gradient(options); } }); @@ -5970,47 +6087,33 @@ fabric.ElementsParser = function(elements, callback, options, reviver, parsingOp /** * @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; + }); } })(); @@ -13951,7 +14054,13 @@ fabric.util.object.extend(fabric.StaticCanvas.prototype, /** @lends fabric.Stati 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); + // 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]); } @@ -15271,11 +15380,7 @@ fabric.util.object.extend(fabric.StaticCanvas.prototype, /** @lends fabric.Stati * @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); }, /** @@ -15339,22 +15444,14 @@ fabric.util.object.extend(fabric.StaticCanvas.prototype, /** @lends fabric.Stati }, _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 @@ -15418,12 +15515,9 @@ fabric.util.object.extend(fabric.StaticCanvas.prototype, /** @lends fabric.Stati x: dimX, y: dimY }], - i, transformMatrix = this._calcDimensionsTransformMatrix(skewX, skewY, false), + 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); + bbox = fabric.util.makeBoundingBoxFromPoints(points, transformMatrix); return this._finalizeDimensions(bbox.width, bbox.height); }, diff --git a/src/shapes/object.class.js b/src/shapes/object.class.js index 7c157980ba4..5b30db3b7df 100644 --- a/src/shapes/object.class.js +++ b/src/shapes/object.class.js @@ -1484,7 +1484,6 @@ if (t) { ctx.transform(t[0], t[1], t[2], t[3], t[4], t[5]); } - console.log(filler) return { offsetX: offsetX, offsetY: offsetY }; }, From 78f594bd516d57c0197ff7c750ded044b7750a63 Mon Sep 17 00:00:00 2001 From: Andrea Bogazzi Date: Sun, 11 Aug 2019 08:50:57 +0200 Subject: [PATCH 07/19] fix lint --- dist/fabric.js | 316 +++++++++++++++++-------------------------------- 1 file changed, 111 insertions(+), 205 deletions(-) diff --git a/dist/fabric.js b/dist/fabric.js index dfee4bb3817..b232fcd53c3 100644 --- a/dist/fabric.js +++ b/dist/fabric.js @@ -781,15 +781,9 @@ fabric.CommonMethods = { /** * Returns coordinates of points's bounding rectangle (left, top, width, height) * @param {Array} points 4 points array - * @param {Array} [transform] 6 number trasnform matrix * @return {Object} Object with left, top, width, height properties */ - makeBoundingBoxFromPoints: function(points, transform) { - if (transform) { - points = points.map(function(point) { - return fabric.util.transformPoint(point, transform); - }); - } + makeBoundingBoxFromPoints: function(points) { 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), @@ -1280,7 +1274,7 @@ fabric.CommonMethods = { }, /** - * Decomposes standard 2x3 matrix into transform componentes + * Decomposes standard 2x2 matrix into transform componentes * @static * @memberOf fabric.util * @param {Array} a transformMatrix @@ -1303,61 +1297,6 @@ fabric.CommonMethods = { }; }, - 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]; - }, - - calcDimensionsMatrix: function(options) { - var scaleX = typeof options.scaleX === 'undefined' ? 1 : options.scaleX, - scaleY = typeof options.scaleY === 'undefined' ? 1 : options.scaleY, - scaleMatrix = [scaleX, 0, 0, scaleY, 0, 0]; - if (options.skewX) { - scaleMatrix = fabric.util.multiplyTransformMatrices( - scaleMatrix, - [1, 0, Math.tan(fabric.util.degreesToRadians(options.skewX)), 1], - true); - } - if (options.skewY) { - scaleMatrix = fabric.util.multiplyTransformMatrices( - scaleMatrix, - [1, Math.tan(fabric.util.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 - * @static - * @memberOf fabric.util - * @param {Object} options - * @param {Number} [options.angle] - * @param {Number} [options.scaleX] - * @param {Number} [options.scaleY] - * @param {Number} [options.skewX] - * @param {Number} [options.skewX] - * @param {Number} [options.translateX] - * @param {Number} [options.translateY] - * @return {Array[Number]} transform matrix - */ - componeMatrix: function(options) { - var matrix = [1, 0, 0, 1, options.translateX || 0, options.translateY || 0]; - if (options.angle) { - matrix = fabric.util.multiplyTransformMatrices(matrix, fabric.util.calcRotateMatrix(options)); - } - if (options.scaleX || options.scaleY || options.skewX || options.skewY) { - matrix = fabric.util.multiplyTransformMatrices(matrix, fabric.util.calcDimensionsMatrix(options)); - } - return matrix; - }, - customTransformMatrix: function(scaleX, scaleY, skewX) { var skewMatrixX = [1, 0, abs(Math.tan(skewX * PiBy180)), 1], scaleMatrix = [abs(scaleX), 0, 0, abs(scaleY)]; @@ -2735,10 +2674,6 @@ if (typeof console !== 'undefined') { return false; } - function defaultEasing(t, b, c, d) { - return -c * Math.cos(t / d * (Math.PI / 2)) + c + b; - } - /** * Changes value from one to another within certain period of time, invoking callbacks as value is being changed. * @memberOf fabric.util @@ -2763,7 +2698,7 @@ if (typeof console !== 'undefined') { onChange = options.onChange || noop, abort = options.abort || noop, onComplete = options.onComplete || noop, - easing = options.easing || defaultEasing, + easing = options.easing || function(t, b, c, d) {return -c * Math.cos(t / d * (Math.PI / 2)) + c + b;}, startValue = 'startValue' in options ? options.startValue : 0, endValue = 'endValue' in options ? options.endValue : 100, byValue = options.byValue || endValue - startValue; @@ -2773,26 +2708,24 @@ if (typeof console !== 'undefined') { (function tick(ticktime) { // TODO: move abort call after calculation // and pass (current,valuePerc, timePerc) as arguments + if (abort()) { + onComplete(endValue, 1, 1); + return; + } time = ticktime || +new Date(); var currentTime = time > finish ? duration : (time - start), timePerc = currentTime / duration, current = easing(currentTime, startValue, byValue, duration), valuePerc = Math.abs((current - startValue) / byValue); - if (abort()) { - onComplete(endValue, 1, 1); - return; - } + onChange(current, valuePerc, timePerc); if (time > finish) { - onChange(endValue, 1, 1); - onComplete(endValue, 1, 1); + options.onComplete && options.onComplete(); return; } - else { - onChange(current, valuePerc, timePerc); - requestAnimFrame(tick); - } + requestAnimFrame(tick); })(start); }); + } var _requestAnimFrame = fabric.window.requestAnimationFrame || @@ -3840,14 +3773,12 @@ if (typeof console !== 'undefined') { 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); @@ -4006,7 +3937,7 @@ if (typeof console !== 'undefined') { recursivelyParseGradientsXlink(doc, referencedGradient); } gradientsAttrs.forEach(function(attr) { - if (referencedGradient && !gradient.hasAttribute(attr) && referencedGradient.hasAttribute(attr)) { + if (!gradient.hasAttribute(attr)) { gradient.setAttribute(attr, referencedGradient.getAttribute(attr)); } }); @@ -4398,8 +4329,8 @@ fabric.ElementsParser = function(elements, callback, options, reviver, parsingOp var _this = this; return function(obj) { var _options; - _this.resolveGradient(obj, el, 'fill'); - _this.resolveGradient(obj, el, 'stroke'); + _this.resolveGradient(obj, 'fill'); + _this.resolveGradient(obj, 'stroke'); if (obj instanceof fabric.Image && obj._originalElement) { _options = obj.parsePreserveAspectRatioAttribute(el); } @@ -4421,12 +4352,10 @@ fabric.ElementsParser = function(elements, callback, options, reviver, parsingOp return fabric[storage][this.svgUid][id]; }; - proto.resolveGradient = function(obj, el, property) { + proto.resolveGradient = function(obj, property) { var gradientDef = this.extractPropertyDefinition(obj, property, 'gradientDefs'); if (gradientDef) { - var opacityAttr = el.getAttribute(property + '-opacity'); - var gradient = fabric.Gradient.fromElement(gradientDef, obj, opacityAttr, this.options); - obj.set(property, gradient); + obj.set(property, fabric.Gradient.fromElement(gradientDef, obj)); } }; @@ -5642,7 +5571,7 @@ fabric.ElementsParser = function(elements, callback, options, reviver, parsingOp (function() { /* _FROM_SVG_START_ */ - function getColorStop(el, multiplier) { + function getColorStop(el) { var style = el.getAttribute('style'), offset = el.getAttribute('offset') || 0, color, colorAlpha, opacity, i; @@ -5682,7 +5611,7 @@ fabric.ElementsParser = function(elements, callback, options, reviver, parsingOp color = new fabric.Color(color); colorAlpha = color.getAlpha(); opacity = isNaN(parseFloat(opacity)) ? 1 : parseFloat(opacity); - opacity *= colorAlpha * multiplier; + opacity *= colorAlpha; return { offset: offset, @@ -5736,59 +5665,18 @@ fabric.ElementsParser = function(elements, callback, options, reviver, parsingOp */ offsetY: 0, - /** - * A transform matrix to apply to the gradient before paiting. - * It shares the same name with the svg property but behaves differently. - * @type Array[Number] - * @default null - */ - gradientTransform: null, - - /** - * coordinates units for coords. - * If `pixels`, the number of cords 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.type] gradient type - * @param {Object} [options.gradientUnits] gradient units - * @param {Object} [options.offsetX] SVG import compatibility - * @param {Object} [options.offsetY] SVG import compatibility - * @param {Object} options.coords contains the coords of the gradient - * @param {Array[Object]} options.colorStops contains the colorstops. - * @param {Number} [options.coords.x1] - * @param {Number} [options.coords.y1] - * @param {Number} [options.coords.x2] - * @param {Number} [options.coords.y2] - * @param {Number} [options.coords.r1] - * @param {Number} [options.coords.r2] + * @param {Object} [options] Options object with type, coords, gradientUnits and colorStops * @return {fabric.Gradient} thisArg */ initialize: function(options) { options || (options = { }); - var coords = { }, _this = this; + var coords = { }; - // sets everything, then coords and colorstops get sets again - Object.keys(options).forEach(function(option) { - _this[option] = options[option]; - }); - - !this.id && (this.id = fabric.Object.__uid++); + this.id = fabric.Object.__uid++; + this.type = options.type || 'linear'; coords = { x1: options.coords.x1 || 0, @@ -5801,9 +5689,13 @@ fabric.ElementsParser = function(elements, callback, options, reviver, parsingOp 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; }, /** @@ -5980,12 +5872,11 @@ fabric.ElementsParser = function(elements, callback, options, reviver, parsingOp * @memberOf fabric.Gradient * @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. * @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, svgOptions) { + fromElement: function(el, instance) { /** * @example: * @@ -6019,53 +5910,45 @@ fabric.ElementsParser = function(elements, callback, options, reviver, parsingOp * */ - var multiplier = parseFloat(opacityAttr) / (/%$/.test(opacityAttr) ? 100 : 1); - multiplier = multiplier < 0 ? 0 : multiplier > 1 ? 1 : multiplier; - if (isNaN(multiplier)) { - multiplier = 1; - } - var colorStopEls = el.getElementsByTagName('stop'), type, - gradientUnits = el.getAttribute('gradientUnits') === 'userSpaceOnUse' ? - 'pixels' : 'percentage', - gradientTransform = el.getAttribute('gradientTransform') || '', + gradientUnits = el.getAttribute('gradientUnits') || 'objectBoundingBox', + gradientTransform = el.getAttribute('gradientTransform'), colorStops = [], - coords, i, offsetX = 0, offsetY = 0, - transformMatrix; + coords, ellipseMatrix, i; + 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); } for (i = colorStopEls.length; i--; ) { - colorStops.push(getColorStop(colorStopEls[i], multiplier)); + colorStops.push(getColorStop(colorStopEls[i])); } - transformMatrix = fabric.parseTransformAttribute(gradientTransform); - - __convertPercentUnitsToValues(instance, coords, svgOptions, gradientUnits); - - if (gradientUnits === 'pixels') { - offsetX = -instance.left; - offsetY = -instance.top; - } + ellipseMatrix = _convertPercentUnitsToValues(instance, coords, gradientUnits); var gradient = new fabric.Gradient({ - id: el.getAttribute('id'), type: type, coords: coords, colorStops: colorStops, - gradientUnits: gradientUnits, - gradientTransform: transformMatrix, - offsetX: offsetX, - offsetY: offsetY, + offsetX: -instance.left, + offsetY: -instance.top }); + if (gradientTransform || ellipseMatrix !== '') { + gradient.gradientTransform = fabric.parseTransformAttribute((gradientTransform || '') + ellipseMatrix); + } + return gradient; }, /* _FROM_SVG_END_ */ @@ -6079,7 +5962,7 @@ fabric.ElementsParser = function(elements, callback, options, reviver, parsingOp */ forObject: function(obj, options) { options || (options = { }); - __convertPercentUnitsToValues(obj, options.coords); + _convertPercentUnitsToValues(obj, options.coords, 'userSpaceOnUse'); return new fabric.Gradient(options); } }); @@ -6087,33 +5970,47 @@ fabric.ElementsParser = function(elements, callback, options, reviver, parsingOp /** * @private */ - function __convertPercentUnitsToValues(instance, options, svgOptions, gradientUnits) { - var propValue, finalValue; - Object.keys(options).forEach(function(prop) { - propValue = options[prop]; - if (propValue === 'Infinity') { - finalValue = 1; + 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; } - else if (propValue === '-Infinity') { - finalValue = 0; + propValue = parseFloat(options[prop], 10); + if (typeof options[prop] === 'string' && /^(\d+\.\d+)%|(\d+)%$/.test(options[prop])) { + multFactor = 0.01; } else { - 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; - } - } - } + multFactor = 1; } - options[prop] = finalValue; - }); + 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; + } + } + return ellipseMatrix; } })(); @@ -14054,13 +13951,7 @@ fabric.util.object.extend(fabric.StaticCanvas.prototype, /** @lends fabric.Stati 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); - } + ctx.translate(offsetX, offsetY); if (t) { ctx.transform(t[0], t[1], t[2], t[3], t[4], t[5]); } @@ -15380,7 +15271,11 @@ fabric.util.object.extend(fabric.StaticCanvas.prototype, /** @lends fabric.Stati * @return {Array} rotation matrix for the object */ _calcRotateMatrix: function() { - return fabric.util.calcRotateMatrix(this); + 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(); }, /** @@ -15444,14 +15339,22 @@ fabric.util.object.extend(fabric.StaticCanvas.prototype, /** @lends fabric.Stati }, _calcDimensionsTransformMatrix: function(skewX, skewY, flipping) { - return fabric.util.calcDimensionsMatrix({ - skewX: skewX, - skewY: skewY, - scaleX: this.scaleX * (flipping && this.flipX ? -1 : 1), - scaleY: this.scaleY * (flipping && this.flipY ? -1 : 1) - }); + 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; }, + /* * Calculate object dimensions from its properties * @private @@ -15515,9 +15418,12 @@ fabric.util.object.extend(fabric.StaticCanvas.prototype, /** @lends fabric.Stati x: dimX, y: dimY }], - transformMatrix = this._calcDimensionsTransformMatrix(skewX, skewY, false), + i, transformMatrix = this._calcDimensionsTransformMatrix(skewX, skewY, false), bbox; - bbox = fabric.util.makeBoundingBoxFromPoints(points, transformMatrix); + for (i = 0; i < points.length; i++) { + points[i] = fabric.util.transformPoint(points[i], transformMatrix); + } + bbox = fabric.util.makeBoundingBoxFromPoints(points); return this._finalizeDimensions(bbox.width, bbox.height); }, From 46f60e45d2e41be7148331ff6a7a50ca551c3b25 Mon Sep 17 00:00:00 2001 From: Andrea Bogazzi Date: Sun, 11 Aug 2019 16:54:52 +0200 Subject: [PATCH 08/19] changes to jsdoc --- src/gradient.class.js | 41 ++++++++++++++++++++++++++------------ src/shapes/object.class.js | 4 +++- src/util/misc.js | 2 +- 3 files changed, 32 insertions(+), 15 deletions(-) diff --git a/src/gradient.class.js b/src/gradient.class.js index 2c2ebc5acc7..ac74594db7c 100644 --- a/src/gradient.class.js +++ b/src/gradient.class.js @@ -96,8 +96,10 @@ offsetY: 0, /** - * A transform matrix to apply to the gradient before paiting. - * It shares the same name with the svg property but behaves differently. + * 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 */ @@ -105,7 +107,7 @@ /** * coordinates units for coords. - * If `pixels`, the number of cords are in the same unit of width/ height. + * 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 @@ -123,24 +125,25 @@ /** * Constructor * @param {Object} options Options object with type, coords, gradientUnits and colorStops - * @param {Object} [options.type] gradient type + * @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 {Object} options.coords contains the coords of the gradient * @param {Array[Object]} options.colorStops contains the colorstops. - * @param {Number} [options.coords.x1] - * @param {Number} [options.coords.y1] - * @param {Number} [options.coords.x2] - * @param {Number} [options.coords.y2] - * @param {Number} [options.coords.r1] - * @param {Number} [options.coords.r2] + * @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 = { }, _this = this; + var coords, _this = this; // sets everything, then coords and colorstops get sets again Object.keys(options).forEach(function(option) { @@ -340,6 +343,12 @@ * @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 @@ -431,14 +440,20 @@ /** * 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); + __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); } }); diff --git a/src/shapes/object.class.js b/src/shapes/object.class.js index 5b30db3b7df..b6299c0c6a0 100644 --- a/src/shapes/object.class.js +++ b/src/shapes/object.class.js @@ -1772,6 +1772,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 @@ -1786,6 +1787,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', { @@ -1830,7 +1832,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 b175bf58ce5..90cea5a99a4 100644 --- a/src/util/misc.js +++ b/src/util/misc.js @@ -664,7 +664,7 @@ }, /** - * Decomposes standard 2x3 matrix into transform componentes + * Decomposes standard 2x3 matrix into transform components * @static * @memberOf fabric.util * @param {Array} a transformMatrix From 69f9dff7590cb6f4b4fdd1d0ea721046ba969eaa Mon Sep 17 00:00:00 2001 From: Andrea Bogazzi Date: Sun, 11 Aug 2019 17:45:05 +0200 Subject: [PATCH 09/19] more jsdocs and deprecations --- src/mixins/object_interactivity.mixin.js | 6 +- src/shapes/object.class.js | 2 +- src/util/misc.js | 82 +++++++++++++++++++----- 3 files changed, 71 insertions(+), 19 deletions(-) diff --git a/src/mixins/object_interactivity.mixin.js b/src/mixins/object_interactivity.mixin.js index e1229d49349..257350a8a7c 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.componeMatrix({ + 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/shapes/object.class.js b/src/shapes/object.class.js index b6299c0c6a0..fb90c90c36c 100644 --- a/src/shapes/object.class.js +++ b/src/shapes/object.class.js @@ -1474,7 +1474,7 @@ 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); } diff --git a/src/util/misc.js b/src/util/misc.js index 90cea5a99a4..521604d42e2 100644 --- a/src/util/misc.js +++ b/src/util/misc.js @@ -165,14 +165,14 @@ /** * Returns coordinates of points's bounding rectangle (left, top, width, height) * @param {Array} points 4 points array - * @param {Array} [transform] 6 number trasnform matrix + * @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, transform) { if (transform) { - points = points.map(function(point) { - return fabric.util.transformPoint(point, 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), @@ -687,6 +687,16 @@ }; }, + /** + * 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(); @@ -697,20 +707,45 @@ 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 = [scaleX, 0, 0, scaleY, 0, 0]; + 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 = fabric.util.multiplyTransformMatrices( + scaleMatrix = multiply( scaleMatrix, - [1, 0, Math.tan(fabric.util.degreesToRadians(options.skewX)), 1], + [1, 0, Math.tan(degreesToRadians(options.skewX)), 1], true); } if (options.skewY) { - scaleMatrix = fabric.util.multiplyTransformMatrices( + scaleMatrix = multiply( scaleMatrix, - [1, Math.tan(fabric.util.degreesToRadians(options.skewY)), 0, 1], + [1, Math.tan(degreesToRadians(options.skewY)), 0, 1], true); } return scaleMatrix; @@ -718,13 +753,16 @@ /** * Returns a transform matrix starting from an object of the same kind of - * the one returned from qrDecompose + * 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] @@ -732,20 +770,30 @@ * @return {Array[Number]} transform matrix */ componeMatrix: function(options) { - var matrix = [1, 0, 0, 1, options.translateX || 0, options.translateY || 0]; + var matrix = [1, 0, 0, 1, options.translateX || 0, options.translateY || 0], + multiply = fabric.util.multiplyTransformMatrices; if (options.angle) { - matrix = fabric.util.multiplyTransformMatrices(matrix, fabric.util.calcRotateMatrix(options)); + matrix = multiply(matrix, fabric.util.calcRotateMatrix(options)); } - if (options.scaleX || options.scaleY || options.skewX || options.skewY) { - matrix = fabric.util.multiplyTransformMatrices(matrix, fabric.util.calcDimensionsMatrix(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.componeMatrix({ scaleX: scaleX, scaleY: scaleY, skewX: skewX }); }, /** From 05aec061347037d22bc747e0e6e7e5d378e3fe78 Mon Sep 17 00:00:00 2001 From: Andrea Bogazzi Date: Sun, 11 Aug 2019 17:57:24 +0200 Subject: [PATCH 10/19] some small extra test --- src/util/misc.js | 4 ++-- test/unit/util.js | 21 +++++++++++++++++++++ 2 files changed, 23 insertions(+), 2 deletions(-) diff --git a/src/util/misc.js b/src/util/misc.js index 521604d42e2..99130ab0669 100644 --- a/src/util/misc.js +++ b/src/util/misc.js @@ -769,7 +769,7 @@ * @param {Number} [options.translateY] * @return {Array[Number]} transform matrix */ - componeMatrix: function(options) { + composeMatrix: function(options) { var matrix = [1, 0, 0, 1, options.translateX || 0, options.translateY || 0], multiply = fabric.util.multiplyTransformMatrices; if (options.angle) { @@ -793,7 +793,7 @@ * @return {Array[Number]} transform matrix */ customTransformMatrix: function(scaleX, scaleY, skewX) { - return fabric.util.componeMatrix({ scaleX: scaleX, scaleY: scaleY, skewX: skewX }); + return fabric.util.composeMatrix({ scaleX: scaleX, scaleY: scaleY, skewX: skewX }); }, /** 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}); From 01fe62a5704676ca12fefcf54ca40bd42628f967 Mon Sep 17 00:00:00 2001 From: Andrea Bogazzi Date: Sun, 11 Aug 2019 18:26:52 +0200 Subject: [PATCH 11/19] fixed typo --- src/gradient.class.js | 1 + src/mixins/object_geometry.mixin.js | 20 ++++++++++---------- src/mixins/object_interactivity.mixin.js | 2 +- src/util/misc.js | 1 - 4 files changed, 12 insertions(+), 12 deletions(-) diff --git a/src/gradient.class.js b/src/gradient.class.js index ac74594db7c..40959b3f0dc 100644 --- a/src/gradient.class.js +++ b/src/gradient.class.js @@ -197,6 +197,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); diff --git a/src/mixins/object_geometry.mixin.js b/src/mixins/object_geometry.mixin.js index fb0a746f1e6..160fab8fad8 100644 --- a/src/mixins/object_geometry.mixin.js +++ b/src/mixins/object_geometry.mixin.js @@ -523,22 +523,22 @@ 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; }, _calcDimensionsTransformMatrix: function(skewX, skewY, flipping) { diff --git a/src/mixins/object_interactivity.mixin.js b/src/mixins/object_interactivity.mixin.js index 257350a8a7c..1b3a2e847da 100644 --- a/src/mixins/object_interactivity.mixin.js +++ b/src/mixins/object_interactivity.mixin.js @@ -195,7 +195,7 @@ drawBordersInGroup: function(ctx, options, styleOverride) { styleOverride = styleOverride || {}; var p = this._getNonTransformedDimensions(), - matrix = fabric.util.componeMatrix({ + matrix = fabric.util.composeMatrix({ scaleX: options.scaleX, scaleY: options.scaleY, skewX: options.skewX diff --git a/src/util/misc.js b/src/util/misc.js index 99130ab0669..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; From 5e2e8459ffe641c4e99ad4219403d41a8763e372 Mon Sep 17 00:00:00 2001 From: Andrea Bogazzi Date: Sun, 11 Aug 2019 20:00:06 +0200 Subject: [PATCH 12/19] more tests --- src/mixins/object_geometry.mixin.js | 18 ++++++-- test/unit/gradient.js | 4 +- test/unit/object_geometry.js | 64 ++++++++++++++++++++++++++++- 3 files changed, 80 insertions(+), 6 deletions(-) diff --git a/src/mixins/object_geometry.mixin.js b/src/mixins/object_geometry.mixin.js index 160fab8fad8..f35b337b801 100644 --- a/src/mixins/object_geometry.mixin.js +++ b/src/mixins/object_geometry.mixin.js @@ -541,6 +541,14 @@ 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) { return fabric.util.calcDimensionsMatrix({ skewX: skewX, @@ -613,9 +621,13 @@ x: dimX, y: dimY }], - transformMatrix = this._calcDimensionsTransformMatrix(skewX, skewY, false), - bbox; - bbox = fabric.util.makeBoundingBoxFromPoints(points, transformMatrix); + 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/test/unit/gradient.js b/test/unit/gradient.js index 3e61d25cd34..87fb85d07f4 100644 --- a/test/unit/gradient.js +++ b/test/unit/gradient.js @@ -143,7 +143,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); @@ -641,6 +641,8 @@ assert.equal(gradient.colorStops[3].opacity, 1); }); + + QUnit.test('forObject linearGradient', function(assert) { assert.ok(typeof fabric.Gradient.forObject === 'function'); 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) { From 54f20dec370894dcdc24e06eb3577b63a296e97f Mon Sep 17 00:00:00 2001 From: Andrea Bogazzi Date: Sun, 11 Aug 2019 20:11:34 +0200 Subject: [PATCH 13/19] fix some gradients export --- src/gradient.class.js | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) diff --git a/src/gradient.class.js b/src/gradient.class.js index 40959b3f0dc..b37f40dc3be 100644 --- a/src/gradient.class.js +++ b/src/gradient.class.js @@ -216,23 +216,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) + '" '; From 695c13a457120a55aec52db4ad36658d9cb797e2 Mon Sep 17 00:00:00 2001 From: Andrea Bogazzi Date: Mon, 12 Aug 2019 17:44:37 +0200 Subject: [PATCH 14/19] added more code --- src/gradient.class.js | 7 +++++- src/shapes/object.class.js | 51 +++++++++++++++++++++++++++++++++++++- 2 files changed, 56 insertions(+), 2 deletions(-) diff --git a/src/gradient.class.js b/src/gradient.class.js index b37f40dc3be..a909d23c818 100644 --- a/src/gradient.class.js +++ b/src/gradient.class.js @@ -150,7 +150,12 @@ _this[option] = options[option]; }); - !this.id && (this.id = fabric.Object.__uid++); + if (this.id) { + this.id += '_' + fabric.Object.__uid++; + } + else { + this.id = fabric.Object.__uid++; + } coords = { x1: options.coords.x1 || 0, diff --git a/src/shapes/object.class.js b/src/shapes/object.class.js index fb90c90c36c..fd449dab63f 100644 --- a/src/shapes/object.class.js +++ b/src/shapes/object.class.js @@ -1533,6 +1533,10 @@ ctx.restore(); }, + /** + * @private + * @param {CanvasRenderingContext2D} ctx Context to render on + */ _renderStroke: function(ctx) { if (!this.stroke || this.strokeWidth === 0) { return; @@ -1547,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 From c2a697a0b205327fbcb99bdeebe68891b27d594e Mon Sep 17 00:00:00 2001 From: Andrea Bogazzi Date: Mon, 12 Aug 2019 18:15:53 +0200 Subject: [PATCH 15/19] lint in tests --- test/unit/gradient.js | 2 -- 1 file changed, 2 deletions(-) diff --git a/test/unit/gradient.js b/test/unit/gradient.js index 87fb85d07f4..df6db63b405 100644 --- a/test/unit/gradient.js +++ b/test/unit/gradient.js @@ -641,8 +641,6 @@ assert.equal(gradient.colorStops[3].opacity, 1); }); - - QUnit.test('forObject linearGradient', function(assert) { assert.ok(typeof fabric.Gradient.forObject === 'function'); From 7931ee6e6418ff9d3e378afba4358112c1779797 Mon Sep 17 00:00:00 2001 From: Andrea Bogazzi Date: Mon, 12 Aug 2019 18:20:07 +0200 Subject: [PATCH 16/19] added a visual test --- test/visual/assets/svg_linear_9.svg | 151 ++++++++++++++++++++-------- test/visual/golden/svg_linear_9.png | Bin 0 -> 72089 bytes test/visual/svg_import.js | 3 +- 3 files changed, 110 insertions(+), 44 deletions(-) create mode 100644 test/visual/golden/svg_linear_9.png diff --git a/test/visual/assets/svg_linear_9.svg b/test/visual/assets/svg_linear_9.svg index 6f6c64c28d6..9cc808c5958 100644 --- a/test/visual/assets/svg_linear_9.svg +++ b/test/visual/assets/svg_linear_9.svg @@ -1,68 +1,105 @@ - + - - + + + + + + + + + + + + + - + + + - + - - - - + - - - - + - - - - + - - - - + - - - - + - - - - + - - - - + - - - - + - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - + @@ -70,4 +107,32 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/test/visual/golden/svg_linear_9.png b/test/visual/golden/svg_linear_9.png new file mode 100644 index 0000000000000000000000000000000000000000..8a9e68a026ac7c3e6ccf25d67aa7ed49e73fd103 GIT binary patch literal 72089 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 Date: Mon, 12 Aug 2019 18:51:48 +0200 Subject: [PATCH 17/19] added a visual test for svg export --- test/lib/visualTestLoop.js | 1 + test/node_test_setup.js | 1 + test/visual/golden/multipleGradients.png | Bin 0 -> 72040 bytes test/visual/z_svg_export.js | 21 ++++++++++++++++----- 4 files changed, 18 insertions(+), 5 deletions(-) create mode 100644 test/visual/golden/multipleGradients.png 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/visual/golden/multipleGradients.png b/test/visual/golden/multipleGradients.png new file mode 100644 index 0000000000000000000000000000000000000000..91502cbf7a420f5b0de43cebc84fccdff35639cf GIT binary patch literal 72040 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 literal 0 HcmV?d00001 diff --git a/test/visual/z_svg_export.js b/test/visual/z_svg_export.js index 6a6506c8d48..4924f4f7a32 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) { @@ -388,13 +391,21 @@ }); } + 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: 'Group with opacity and shadow', - code: group1, - golden: 'group-svg-1.png', + test: 'Multiple gradients import', + code: multipleGradients, + golden: 'multipleGradients.png', percentage: 0.06, - width: 210, - height: 230, + width: 760, + height: 760, }); tests.forEach(visualTestLoop(QUnit)); })(); From 14db4d737941f76b99f8cdb5d487e00edf1d02f3 Mon Sep 17 00:00:00 2001 From: Andrea Bogazzi Date: Mon, 12 Aug 2019 18:57:16 +0200 Subject: [PATCH 18/19] but i killed a test --- test/visual/z_svg_export.js | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/test/visual/z_svg_export.js b/test/visual/z_svg_export.js index 4924f4f7a32..cb9925c097a 100644 --- a/test/visual/z_svg_export.js +++ b/test/visual/z_svg_export.js @@ -391,6 +391,15 @@ }); } + tests.push({ + test: 'Group with opacity and shadow', + code: group1, + golden: 'group-svg-1.png', + percentage: 0.06, + width: 210, + height: 230, + }); + function multipleGradients(canvas, callback) { fabric.loadSVGFromURL(getAssetName('svg_linear_9'), function(objects) { var group = fabric.util.groupSVGElements(objects); From b8a8c324c401763246529917a0e96b4425cfaadb Mon Sep 17 00:00:00 2001 From: Andrea Bogazzi Date: Mon, 12 Aug 2019 19:21:34 +0200 Subject: [PATCH 19/19] added a simple UT --- test/unit/gradient.js | 25 +++++++++++++++++++++---- 1 file changed, 21 insertions(+), 4 deletions(-) diff --git a/test/unit/gradient.js b/test/unit/gradient.js index df6db63b405..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); @@ -761,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); + }); + })();