From 57869bd9d84eef2864719fd278e68c88858f6bc5 Mon Sep 17 00:00:00 2001 From: Andrea Bogazzi Date: Sun, 12 Jan 2020 14:37:49 +0100 Subject: [PATCH] fix(gradient, text): ISSUE-6014 ISSUE-6077 support percentage gradient on text (#6090) --- src/gradient.class.js | 28 ++++++++++++++++----------- src/shapes/text.class.js | 18 ++++++++++++++++++ test/visual/golden/text7.png | Bin 0 -> 10699 bytes test/visual/text.js | 36 +++++++++++++++++++++++++++++++++++ 4 files changed, 71 insertions(+), 11 deletions(-) create mode 100644 test/visual/golden/text7.png diff --git a/src/gradient.class.js b/src/gradient.class.js index 720ad9768e9..fa0dd7b95c8 100644 --- a/src/gradient.class.js +++ b/src/gradient.class.js @@ -317,28 +317,34 @@ /** * Returns an instance of CanvasGradient * @param {CanvasRenderingContext2D} ctx Context to render on + * @param {fabric.Object} object the fabric.Object for which the gradient is * @return {CanvasGradient} */ - toLive: function(ctx) { - var gradient, coords = fabric.util.object.clone(this.coords), i, len; + toLive: function(ctx, object) { + var gradient, coords = fabric.util.object.clone(this.coords), i, len, + x1 = coords.x1, y1 = coords.y1, x2 = coords.x2, y2 = coords.y2, + stops = this.colorStops; if (!this.type) { return; } + if (object instanceof fabric.Text && this.gradientUnits === 'percentage') { + x1 *= object.width; + y1 *= object.height; + x2 *= object.width; + y2 *= object.height; + } if (this.type === 'linear') { - gradient = ctx.createLinearGradient( - coords.x1, coords.y1, coords.x2, coords.y2); + gradient = ctx.createLinearGradient(x1, y1, x2, y2); } else if (this.type === 'radial') { - gradient = ctx.createRadialGradient( - coords.x1, coords.y1, coords.r1, coords.x2, coords.y2, coords.r2); + gradient = ctx.createRadialGradient(x1, y1, coords.r1, x2, y2, coords.r2); } - - for (i = 0, len = this.colorStops.length; i < len; i++) { - var color = this.colorStops[i].color, - opacity = this.colorStops[i].opacity, - offset = this.colorStops[i].offset; + for (i = 0, len = stops.length; i < len; i++) { + var color = stops[i].color, + opacity = stops[i].opacity, + offset = stops[i].offset; if (typeof opacity !== 'undefined') { color = new fabric.Color(color).setAlpha(opacity).toRgba(); diff --git a/src/shapes/text.class.js b/src/shapes/text.class.js index 233fd15538d..ada5d009c4e 100644 --- a/src/shapes/text.class.js +++ b/src/shapes/text.class.js @@ -817,6 +817,24 @@ return -this.height / 2; }, + /** + * @private + * @param {CanvasRenderingContext2D} ctx Context to render on + * @param {Object} filler fabric.Pattern or fabric.Gradient + * @return {Object} offset.offsetX offset for text rendering + * @return {Object} offset.offsetY offset for text rendering + */ + _applyPatternGradientTransform: function(ctx, filler) { + if (!filler || !filler.toLive) { + return { offsetX: 0, offsetY: 0 }; + } + var offsetX = -this.width / 2 + filler.offsetX || 0, + offsetY = -this.height / 2 + filler.offsetY || 0; + + ctx.transform(1, 0, 0, 1, offsetX, offsetY); + return { offsetX: offsetX, offsetY: offsetY }; + }, + /** * @private * @param {CanvasRenderingContext2D} ctx Context to render on diff --git a/test/visual/golden/text7.png b/test/visual/golden/text7.png new file mode 100644 index 0000000000000000000000000000000000000000..10198be0f1d498cd28a164e923b4ef712a985120 GIT binary patch literal 10699 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