From 373a4506284d21001ac4a39e736e7685714a28e9 Mon Sep 17 00:00:00 2001 From: ShaMan123 Date: Thu, 1 Jul 2021 08:23:56 +0300 Subject: [PATCH 01/37] Update base_brush.class.js --- src/brushes/base_brush.class.js | 48 ++++++++++++++++++++++++++++++--- 1 file changed, 45 insertions(+), 3 deletions(-) diff --git a/src/brushes/base_brush.class.js b/src/brushes/base_brush.class.js index 578907eca8d..6306e9e4720 100644 --- a/src/brushes/base_brush.class.js +++ b/src/brushes/base_brush.class.js @@ -47,7 +47,7 @@ fabric.BaseBrush = fabric.util.createClass(/** @lends fabric.BaseBrush.prototype * @type Number * @default */ - strokeMiterLimit: 10, + strokeMiterLimit: 10, /** * Stroke Dash Array. @@ -61,9 +61,14 @@ fabric.BaseBrush = fabric.util.createClass(/** @lends fabric.BaseBrush.prototype * @type Boolean * @default false */ - limitedToCanvasSize: false, + /** + * Same as fabric.Object `clipPath` property + * originX = 'left', originY = 'top' + */ + clipPath: undefined, + /** * Sets brush styles @@ -117,7 +122,44 @@ fabric.BaseBrush = fabric.util.createClass(/** @lends fabric.BaseBrush.prototype needsFullRender: function() { var color = new fabric.Color(this.color); - return color.getAlpha() < 1 || !!this.shadow; + return color.getAlpha() < 1 || !!this.shadow || (this.clipPath && this.clipPath.isCacheDirty()); + }, + + /** + * @private + * @param {CanvasRenderingContext2D} ctx + */ + drawClipPathOnCache: function (ctx) { + fabric.Object.prototype.drawClipPathOnCache.call(this, ctx); + }, + + /** + * @private + * @param {CanvasRenderingContext2D} ctx + */ + _drawClipPath: function (ctx) { + fabric.Object.prototype._drawClipPath.call(this, ctx); + }, + + /** + * Subclasses should override this method + * @private + * @param {CanvasRenderingContext2D} ctx + */ + render: function (ctx /*eslint-disable-line no-unused-vars*/) { + + }, + + /** + * Render the full state of the brush + * @private + */ + _render: function () { + var ctx = this.canvas.contextTop; + this._saveAndTransform(ctx); + this.render(ctx); + this._drawClipPath(ctx); + ctx.restore(); }, /** From b5516833f93af5cdae84fc592e7b2075fff66329 Mon Sep 17 00:00:00 2001 From: ShaMan123 Date: Thu, 1 Jul 2021 08:24:31 +0300 Subject: [PATCH 02/37] Update circle_brush.class.js --- src/brushes/circle_brush.class.js | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/src/brushes/circle_brush.class.js b/src/brushes/circle_brush.class.js index 93b51ed6b2e..d0eeeafbdf6 100644 --- a/src/brushes/circle_brush.class.js +++ b/src/brushes/circle_brush.class.js @@ -52,17 +52,14 @@ fabric.CircleBrush = fabric.util.createClass(fabric.BaseBrush, /** @lends fabric }, /** - * Render the full state of the brush * @private + * @param {CanvasRenderingContext2D} ctx */ - _render: function() { - var ctx = this.canvas.contextTop, i, len, - points = this.points; - this._saveAndTransform(ctx); + render: function (ctx) { + var i, len, points = this.points; for (i = 0, len = points.length; i < len; i++) { this.dot(ctx, points[i]); } - ctx.restore(); }, /** From cc5374d39dca574be5df83d48315a1239e89240c Mon Sep 17 00:00:00 2001 From: ShaMan123 Date: Thu, 1 Jul 2021 08:24:37 +0300 Subject: [PATCH 03/37] Update pencil_brush.class.js --- src/brushes/pencil_brush.class.js | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/src/brushes/pencil_brush.class.js b/src/brushes/pencil_brush.class.js index 68c7c7f052a..cc69b0dd876 100644 --- a/src/brushes/pencil_brush.class.js +++ b/src/brushes/pencil_brush.class.js @@ -140,13 +140,11 @@ /** * Draw a smooth path on the topCanvas using quadraticCurveTo * @private + * @param {CanvasRenderingContext2D} ctx */ - _render: function() { - var ctx = this.canvas.contextTop, i, len, - p1 = this._points[0], - p2 = this._points[1]; + render: function(ctx) { + var i, len, p1 = this._points[0], p2 = this._points[1]; - this._saveAndTransform(ctx); ctx.beginPath(); //if we only have 2 points in the path and they are the same //it means that the user only clicked the canvas without moving the mouse @@ -173,7 +171,6 @@ // the bezier control point ctx.lineTo(p1.x, p1.y); ctx.stroke(); - ctx.restore(); }, /** From a824e91d4455b4b69e26daa60a999dad14efd420 Mon Sep 17 00:00:00 2001 From: ShaMan123 Date: Thu, 1 Jul 2021 08:25:41 +0300 Subject: [PATCH 04/37] BREAKING CHANGE renamed `render` to `renderChuck` `render` method now renders all the spray to align with all the brushes --- src/brushes/spray_brush.class.js | 44 +++++++++++++++++++++----------- 1 file changed, 29 insertions(+), 15 deletions(-) diff --git a/src/brushes/spray_brush.class.js b/src/brushes/spray_brush.class.js index 50202a4131b..f1f7426249e 100644 --- a/src/brushes/spray_brush.class.js +++ b/src/brushes/spray_brush.class.js @@ -66,7 +66,7 @@ fabric.SprayBrush = fabric.util.createClass( fabric.BaseBrush, /** @lends fabric this._setShadow(); this.addSprayChunk(pointer); - this.render(this.sprayChunkPoints); + this.renderChunk(this.sprayChunkPoints); }, /** @@ -78,7 +78,7 @@ fabric.SprayBrush = fabric.util.createClass( fabric.BaseBrush, /** @lends fabric return; } this.addSprayChunk(pointer); - this.render(this.sprayChunkPoints); + this.renderChunk(this.sprayChunkPoints); }, /** @@ -148,14 +148,22 @@ fabric.SprayBrush = fabric.util.createClass( fabric.BaseBrush, /** @lends fabric }, /** - * Render new chunk of spray brush + * Sets the transformation and fillStyle on given context + * @param {RenderingContext2d} ctx context to render on + * @private */ - render: function(sprayChunk) { - var ctx = this.canvas.contextTop, i, len; + _saveAndTransform: function (ctx) { + this.callSuper('_saveAndTransform', ctx); ctx.fillStyle = this.color; + }, - this._saveAndTransform(ctx); - + /** + * @private + * Render new chunk of spray brush + * @param {CanvasRenderingContext2D} ctx + */ + _renderChunk: function (ctx, sprayChunk) { + var i, len; for (i = 0, len = sprayChunk.length; i < len; i++) { var point = sprayChunk[i]; if (typeof point.opacity !== 'undefined') { @@ -163,22 +171,28 @@ fabric.SprayBrush = fabric.util.createClass( fabric.BaseBrush, /** @lends fabric } ctx.fillRect(point.x, point.y, point.width, point.width); } - ctx.restore(); }, /** - * Render all spray chunks + * @private + * @param {fabric.Object} sprayChunk */ - _render: function() { - var ctx = this.canvas.contextTop, i, ilen; - ctx.fillStyle = this.color; - + renderChunk: function (sprayChunk) { + var ctx = this.canvas.contextTop; this._saveAndTransform(ctx); + this._renderChunk(ctx, sprayChunk); + ctx.restore(); + }, + /** + * Render all spray chunks + * @param {CanvasRenderingContext2D} ctx + */ + render: function(ctx) { + var i, ilen; for (i = 0, ilen = this.sprayChunks.length; i < ilen; i++) { - this.render(this.sprayChunks[i]); + this._renderChunk(ctx, this.sprayChunks[i]); } - ctx.restore(); }, /** From 3cb75e4f03d155f5fb566e6482dc851259bc57e3 Mon Sep 17 00:00:00 2001 From: ShaMan123 Date: Thu, 1 Jul 2021 13:31:50 +0300 Subject: [PATCH 05/37] draw clipPath when adding a brush segment --- src/brushes/circle_brush.class.js | 1 + src/brushes/pencil_brush.class.js | 1 + src/brushes/spray_brush.class.js | 1 + 3 files changed, 3 insertions(+) diff --git a/src/brushes/circle_brush.class.js b/src/brushes/circle_brush.class.js index d0eeeafbdf6..3d9a800a1e0 100644 --- a/src/brushes/circle_brush.class.js +++ b/src/brushes/circle_brush.class.js @@ -30,6 +30,7 @@ fabric.CircleBrush = fabric.util.createClass(fabric.BaseBrush, /** @lends fabric ctx = this.canvas.contextTop; this._saveAndTransform(ctx); this.dot(ctx, point); + this._drawClipPath(ctx); ctx.restore(); }, diff --git a/src/brushes/pencil_brush.class.js b/src/brushes/pencil_brush.class.js index cc69b0dd876..fdcdda3660d 100644 --- a/src/brushes/pencil_brush.class.js +++ b/src/brushes/pencil_brush.class.js @@ -76,6 +76,7 @@ } this.oldEnd = this._drawSegment(ctx, points[length - 2], points[length - 1], true); ctx.stroke(); + this._drawClipPath(ctx); ctx.restore(); } } diff --git a/src/brushes/spray_brush.class.js b/src/brushes/spray_brush.class.js index f1f7426249e..21e49a9be54 100644 --- a/src/brushes/spray_brush.class.js +++ b/src/brushes/spray_brush.class.js @@ -181,6 +181,7 @@ fabric.SprayBrush = fabric.util.createClass( fabric.BaseBrush, /** @lends fabric var ctx = this.canvas.contextTop; this._saveAndTransform(ctx); this._renderChunk(ctx, sprayChunk); + this._drawClipPath(ctx); ctx.restore(); }, From 07fc81e30082cabd77ac3b5e1496b1b7902e4574 Mon Sep 17 00:00:00 2001 From: ShaMan123 Date: Thu, 1 Jul 2021 13:45:29 +0300 Subject: [PATCH 06/37] addClipPathToResult expose `_addClipPathToResult` If there's a clip path set on the brush it is applied to the brush result --- src/brushes/base_brush.class.js | 16 ++++++++++++++++ src/brushes/circle_brush.class.js | 1 + src/brushes/pencil_brush.class.js | 2 +- src/brushes/spray_brush.class.js | 2 ++ 4 files changed, 20 insertions(+), 1 deletion(-) diff --git a/src/brushes/base_brush.class.js b/src/brushes/base_brush.class.js index 6306e9e4720..633ff8b51da 100644 --- a/src/brushes/base_brush.class.js +++ b/src/brushes/base_brush.class.js @@ -141,6 +141,22 @@ fabric.BaseBrush = fabric.util.createClass(/** @lends fabric.BaseBrush.prototype fabric.Object.prototype._drawClipPath.call(this, ctx); }, + /** + * @private + * @param {fabric.Object} result + */ + _addClipPathToResult: function (result) { + if (!this.clipPath) return; + this.clipPath.clone(function (clipPath) { + var desiredTransform = fabric.util.multiplyTransformMatrices( + fabric.util.invertTransform(result.calcTransformMatrix()), + clipPath.calcTransformMatrix() + ); + fabric.util.applyTransformToObject(clipPath, desiredTransform); + result.set('clipPath', clipPath); + }); + }, + /** * Subclasses should override this method * @private diff --git a/src/brushes/circle_brush.class.js b/src/brushes/circle_brush.class.js index 3d9a800a1e0..d59d0e3f4e4 100644 --- a/src/brushes/circle_brush.class.js +++ b/src/brushes/circle_brush.class.js @@ -107,6 +107,7 @@ fabric.CircleBrush = fabric.util.createClass(fabric.BaseBrush, /** @lends fabric } var group = new fabric.Group(circles); group.canvas = this.canvas; + this._addClipPathToResult(group); this.canvas.fire('before:path:created', { path: group }); this.canvas.add(group); diff --git a/src/brushes/pencil_brush.class.js b/src/brushes/pencil_brush.class.js index fdcdda3660d..bd2ff46a2b1 100644 --- a/src/brushes/pencil_brush.class.js +++ b/src/brushes/pencil_brush.class.js @@ -213,7 +213,7 @@ this.shadow.affectStroke = true; path.shadow = new fabric.Shadow(this.shadow); } - + this._addClipPathToResult(group); return path; }, diff --git a/src/brushes/spray_brush.class.js b/src/brushes/spray_brush.class.js index 21e49a9be54..71915784364 100644 --- a/src/brushes/spray_brush.class.js +++ b/src/brushes/spray_brush.class.js @@ -114,6 +114,8 @@ fabric.SprayBrush = fabric.util.createClass( fabric.BaseBrush, /** @lends fabric var group = new fabric.Group(rects); this.shadow && group.set('shadow', new fabric.Shadow(this.shadow)); + this._addClipPathToResult(group); + this.canvas.fire('before:path:created', { path: group }); this.canvas.add(group); this.canvas.fire('path:created', { path: group }); From fc92fad47d435442f01066219d6124a0a1d05254 Mon Sep 17 00:00:00 2001 From: ShaMan123 Date: Thu, 1 Jul 2021 13:54:59 +0300 Subject: [PATCH 07/37] Update pencil_brush.class.js --- src/brushes/pencil_brush.class.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/brushes/pencil_brush.class.js b/src/brushes/pencil_brush.class.js index bd2ff46a2b1..1bca784c2a2 100644 --- a/src/brushes/pencil_brush.class.js +++ b/src/brushes/pencil_brush.class.js @@ -213,7 +213,7 @@ this.shadow.affectStroke = true; path.shadow = new fabric.Shadow(this.shadow); } - this._addClipPathToResult(group); + this._addClipPathToResult(path); return path; }, From 99ff8862731b110c3b51b83eed7f5e00126ca6d3 Mon Sep 17 00:00:00 2001 From: ShaMan123 Date: Thu, 1 Jul 2021 14:33:57 +0300 Subject: [PATCH 08/37] JSDOC --- src/brushes/base_brush.class.js | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/src/brushes/base_brush.class.js b/src/brushes/base_brush.class.js index 633ff8b51da..ecf782573b0 100644 --- a/src/brushes/base_brush.class.js +++ b/src/brushes/base_brush.class.js @@ -64,8 +64,8 @@ fabric.BaseBrush = fabric.util.createClass(/** @lends fabric.BaseBrush.prototype limitedToCanvasSize: false, /** - * Same as fabric.Object `clipPath` property - * originX = 'left', originY = 'top' + * Same as fabric.Object `clipPath` property. + * The clip path is positioned relative to the top left corner of the canvas. */ clipPath: undefined, @@ -142,9 +142,10 @@ fabric.BaseBrush = fabric.util.createClass(/** @lends fabric.BaseBrush.prototype }, /** - * @private - * @param {fabric.Object} result - */ + * Adds the clip path to the resulting object created by the brush + * @private + * @param {fabric.Object} result + */ _addClipPathToResult: function (result) { if (!this.clipPath) return; this.clipPath.clone(function (clipPath) { From 97ee5285ad9d5481e0995b2f30552075dc707d32 Mon Sep 17 00:00:00 2001 From: ShaMan123 Date: Thu, 1 Jul 2021 14:54:05 +0300 Subject: [PATCH 09/37] lint --- src/brushes/base_brush.class.js | 4 +++- src/brushes/spray_brush.class.js | 2 +- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/src/brushes/base_brush.class.js b/src/brushes/base_brush.class.js index ecf782573b0..46abc58ecfd 100644 --- a/src/brushes/base_brush.class.js +++ b/src/brushes/base_brush.class.js @@ -147,7 +147,9 @@ fabric.BaseBrush = fabric.util.createClass(/** @lends fabric.BaseBrush.prototype * @param {fabric.Object} result */ _addClipPathToResult: function (result) { - if (!this.clipPath) return; + if (!this.clipPath) { + return; + } this.clipPath.clone(function (clipPath) { var desiredTransform = fabric.util.multiplyTransformMatrices( fabric.util.invertTransform(result.calcTransformMatrix()), diff --git a/src/brushes/spray_brush.class.js b/src/brushes/spray_brush.class.js index 71915784364..3fddc3580ee 100644 --- a/src/brushes/spray_brush.class.js +++ b/src/brushes/spray_brush.class.js @@ -115,7 +115,7 @@ fabric.SprayBrush = fabric.util.createClass( fabric.BaseBrush, /** @lends fabric var group = new fabric.Group(rects); this.shadow && group.set('shadow', new fabric.Shadow(this.shadow)); this._addClipPathToResult(group); - + this.canvas.fire('before:path:created', { path: group }); this.canvas.add(group); this.canvas.fire('path:created', { path: group }); From 24489c433213acf616dc9a0413e4e96b99afb2fe Mon Sep 17 00:00:00 2001 From: ShaMan123 Date: Thu, 1 Jul 2021 15:00:50 +0300 Subject: [PATCH 10/37] Update CHANGELOG.md --- CHANGELOG.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 6b0a69b3ec1..ce385b5dd3d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,12 @@ - fix(fabric.Object): support `excludeFromExport` set on `clipPath` [#7148](https://github.com/fabricjs/fabric.js/pull/7148). - fix(fabric.Group): support `excludeFromExport` set on objects [#7148](https://github.com/fabricjs/fabric.js/pull/7148). - fix(fabric.StaticCanvas): support `excludeFromExport` set on `backgroundColor`, `overlayColor`, `clipPath` [#7148](https://github.com/fabricjs/fabric.js/pull/7148). +- feat(fabric.BaseBrush): added `clipPath` property [#7175](https://github.com/fabricjs/fabric.js/pull/7175). +- feat(fabric.CircleBrush): added `clipPath` property [#7175](https://github.com/fabricjs/fabric.js/pull/7175). +- feat(fabric.PencilBrush): added `clipPath` property [#7175](https://github.com/fabricjs/fabric.js/pull/7175). +- feat(fabric.PatternBrush): added `clipPath` property [#7175](https://github.com/fabricjs/fabric.js/pull/7175). +- feat(fabric.SprayBrush): added `clipPath` property [#7175](https://github.com/fabricjs/fabric.js/pull/7175). +- Breaking: renamed `fabric.SprayBrush` `render` method to `renderChunk`. ## [4.5.1] From 2640b2eb621cda78addfbebc384bd4272269e6d8 Mon Sep 17 00:00:00 2001 From: ShaMan123 Date: Thu, 1 Jul 2021 15:25:29 +0300 Subject: [PATCH 11/37] BREAKING: rename `render` to `renderAll` To align with this PR --- src/mixins/eraser_brush.mixin.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/mixins/eraser_brush.mixin.js b/src/mixins/eraser_brush.mixin.js index b86baf363f1..62ab2349be5 100644 --- a/src/mixins/eraser_brush.mixin.js +++ b/src/mixins/eraser_brush.mixin.js @@ -285,7 +285,7 @@ initialize: function (canvas) { this.callSuper('initialize', canvas); this._renderBound = this._render.bind(this); - this.render = this.render.bind(this); + this.renderAll = this.renderAll.bind(this); }, /** @@ -566,7 +566,7 @@ /** * @public */ - render: function () { + renderAll: function () { if (this._isErasing) { if (this.isRendering) { this.isRendering = fabric.util.requestAnimFrame(this._renderBound); From 9fea1198179cd9e8ddd217b7b82beafe86a119fc Mon Sep 17 00:00:00 2001 From: ShaMan123 Date: Thu, 1 Jul 2021 15:52:58 +0300 Subject: [PATCH 12/37] handle `absolutePositioned` case --- src/brushes/base_brush.class.js | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/brushes/base_brush.class.js b/src/brushes/base_brush.class.js index 46abc58ecfd..66b71dfd994 100644 --- a/src/brushes/base_brush.class.js +++ b/src/brushes/base_brush.class.js @@ -69,7 +69,6 @@ fabric.BaseBrush = fabric.util.createClass(/** @lends fabric.BaseBrush.prototype */ clipPath: undefined, - /** * Sets brush styles * @private @@ -130,6 +129,12 @@ fabric.BaseBrush = fabric.util.createClass(/** @lends fabric.BaseBrush.prototype * @param {CanvasRenderingContext2D} ctx */ drawClipPathOnCache: function (ctx) { + if (this.clipPath && this.clipPath.absolutePositioned) { + // we remove `absolutePositioned` + // because it has no effect (the brush has no transform) + // and it will try to call `calcTransformMatrix` which will throw an error + delete this.clipPath.absolutePositioned; + } fabric.Object.prototype.drawClipPathOnCache.call(this, ctx); }, From 798199ddff9397fc0e8cc3cb79ddcc52d588e69a Mon Sep 17 00:00:00 2001 From: ShaMan123 Date: Thu, 1 Jul 2021 15:54:37 +0300 Subject: [PATCH 13/37] support `inverted` property --- src/brushes/base_brush.class.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/brushes/base_brush.class.js b/src/brushes/base_brush.class.js index 66b71dfd994..4736685947c 100644 --- a/src/brushes/base_brush.class.js +++ b/src/brushes/base_brush.class.js @@ -162,7 +162,7 @@ fabric.BaseBrush = fabric.util.createClass(/** @lends fabric.BaseBrush.prototype ); fabric.util.applyTransformToObject(clipPath, desiredTransform); result.set('clipPath', clipPath); - }); + }, ['inverted']); }, /** From 8b3053734087c207ecf8e38ec21cf549fb786d69 Mon Sep 17 00:00:00 2001 From: ShaMan123 Date: Thu, 1 Jul 2021 19:13:11 +0300 Subject: [PATCH 14/37] fix(absolutePositioned) --- src/brushes/base_brush.class.js | 26 ++++++++++++++++++-------- 1 file changed, 18 insertions(+), 8 deletions(-) diff --git a/src/brushes/base_brush.class.js b/src/brushes/base_brush.class.js index 4736685947c..3e8d63eaef0 100644 --- a/src/brushes/base_brush.class.js +++ b/src/brushes/base_brush.class.js @@ -65,7 +65,7 @@ fabric.BaseBrush = fabric.util.createClass(/** @lends fabric.BaseBrush.prototype /** * Same as fabric.Object `clipPath` property. - * The clip path is positioned relative to the top left corner of the canvas. + * The clip path is positioned relative to the top left corner of the viewport. */ clipPath: undefined, @@ -124,17 +124,19 @@ fabric.BaseBrush = fabric.util.createClass(/** @lends fabric.BaseBrush.prototype return color.getAlpha() < 1 || !!this.shadow || (this.clipPath && this.clipPath.isCacheDirty()); }, + /** + * needed for `absolutePositioned` `clipPath` + * @private + */ + calcTransformMatrix: function () { + return fabric.util.invertTransform(this.canvas.viewportTransform); + }, + /** * @private * @param {CanvasRenderingContext2D} ctx */ drawClipPathOnCache: function (ctx) { - if (this.clipPath && this.clipPath.absolutePositioned) { - // we remove `absolutePositioned` - // because it has no effect (the brush has no transform) - // and it will try to call `calcTransformMatrix` which will throw an error - delete this.clipPath.absolutePositioned; - } fabric.Object.prototype.drawClipPathOnCache.call(this, ctx); }, @@ -143,7 +145,11 @@ fabric.BaseBrush = fabric.util.createClass(/** @lends fabric.BaseBrush.prototype * @param {CanvasRenderingContext2D} ctx */ _drawClipPath: function (ctx) { + const v = fabric.util.invertTransform(this.canvas.viewportTransform); + ctx.save(); + ctx.transform(v[0], v[1], v[2], v[3], v[4], v[5]); fabric.Object.prototype._drawClipPath.call(this, ctx); + ctx.restore(); }, /** @@ -155,9 +161,13 @@ fabric.BaseBrush = fabric.util.createClass(/** @lends fabric.BaseBrush.prototype if (!this.clipPath) { return; } + var t = result.calcTransformMatrix(); + if (!this.clipPath.absolutePositioned) { + t = fabric.util.multiplyTransformMatrices(this.canvas.viewportTransform, t); + } this.clipPath.clone(function (clipPath) { var desiredTransform = fabric.util.multiplyTransformMatrices( - fabric.util.invertTransform(result.calcTransformMatrix()), + fabric.util.invertTransform(t), clipPath.calcTransformMatrix() ); fabric.util.applyTransformToObject(clipPath, desiredTransform); From add527cfabe33e2783d7da095ef87d27963c6f58 Mon Sep 17 00:00:00 2001 From: ShaMan123 Date: Thu, 1 Jul 2021 19:16:20 +0300 Subject: [PATCH 15/37] Update eraser_brush.mixin.js --- src/mixins/eraser_brush.mixin.js | 23 ++++++++++++++++++++++- 1 file changed, 22 insertions(+), 1 deletion(-) diff --git a/src/mixins/eraser_brush.mixin.js b/src/mixins/eraser_brush.mixin.js index 62ab2349be5..f9a1d2a779b 100644 --- a/src/mixins/eraser_brush.mixin.js +++ b/src/mixins/eraser_brush.mixin.js @@ -288,6 +288,15 @@ this.renderAll = this.renderAll.bind(this); }, + /** + * Disable clip path drawing. + * Clip path must be handled differently when erasing. + * @private + */ + _drawClipPath: function () { + + }, + /** * Used to hide a drawable from the rendering process * @param {fabric.Object} object @@ -484,11 +493,23 @@ renderTopLayer: function () { var canvas = this.canvas; this._drawOverlayOnTop = this.prepareCanvasForLayer('top'); + if (this.clipPath) { + var ctx = canvas.getContext(); + canvas.renderCanvas( + ctx, + canvas.getObjects() + ); + var inverted = this.clipPath.inverted; + this.clipPath.set('inverted', !!inverted); + this.callSuper('_drawClipPath', ctx); + this.clipPath.set('inverted', inverted) + } canvas.renderCanvas( canvas.contextTop, canvas.getObjects() ); this.callSuper('_render'); + this.callSuper('_drawClipPath', ctx); this.restoreCanvasFromLayer('top'); }, @@ -519,7 +540,7 @@ * @returns */ needsFullRender: function () { - return this.callSuper('needsFullRender') || this._drawOverlayOnTop; + return this.callSuper('needsFullRender') || this._drawOverlayOnTop || this.clipPath; }, /** From 5680f1f27ee4a0f5c3d5d1ee9ce700a44b009ed1 Mon Sep 17 00:00:00 2001 From: ShaMan123 Date: Thu, 1 Jul 2021 21:48:54 +0300 Subject: [PATCH 16/37] support clipping eraser brush \ --- dist/fabric.js | 1049 +++++++++++++++++++++++++++--- src/mixins/eraser_brush.mixin.js | 56 +- 2 files changed, 999 insertions(+), 106 deletions(-) diff --git a/dist/fabric.js b/dist/fabric.js index e07aff41848..dcfb6ae8e2f 100644 --- a/dist/fabric.js +++ b/dist/fabric.js @@ -1,4 +1,4 @@ -/* build: `node build.js modules=ALL exclude=gestures,accessors,erasing requirejs minifier=uglifyjs` */ +/* build: `node build.js modules=ALL exclude=gestures,accessors requirejs minifier=uglifyjs` */ /*! Fabric.js Copyright 2008-2015, Printio (Juriy Zaytsev, Maxim Chernyak) */ var fabric = fabric || { version: '4.5.1' }; @@ -2323,6 +2323,18 @@ fabric.CommonMethods = { } } + /** + * + * @param {string} pathString + * @return {(string|number)[][]} An array of SVG path commands + * @example Usage + * parsePath('M 3 4 Q 3 5 2 1 4 0 Q 9 12 2 1 4 0') === [ + * ['M', 3, 4], + * ['Q', 3, 5, 2, 1, 4, 0], + * ['Q', 9, 12, 2, 1, 4, 0], + * ]; + * + */ function parsePath(pathString) { var result = [], coords = [], @@ -2391,6 +2403,46 @@ fabric.CommonMethods = { return result; }; + /** + * + * Converts points to a smooth SVG path + * @param {{ x: number,y: number }[]} points Array of points + * @param {number} [correction] Apply a correction to the path (usually we use `width / 1000`). If value is undefined 0 is used as the correction value. + * @return {(string|number)[][]} An array of SVG path commands + */ + function getSmoothPathFromPoints(points, correction) { + var path = [], i, + p1 = new fabric.Point(points[0].x, points[0].y), + p2 = new fabric.Point(points[1].x, points[1].y), + len = points.length, multSignX = 1, multSignY = 0, manyPoints = len > 2; + correction = correction || 0; + + if (manyPoints) { + multSignX = points[2].x < p2.x ? -1 : points[2].x === p2.x ? 0 : 1; + multSignY = points[2].y < p2.y ? -1 : points[2].y === p2.y ? 0 : 1; + } + path.push(['M', p1.x - multSignX * correction, p1.y - multSignY * correction]); + for (i = 1; i < len; i++) { + if (!p1.eq(p2)) { + var midPoint = p1.midPointFrom(p2); + // p1 is our bezier control point + // midpoint is our endpoint + // start point is p(i-1) value. + path.push(['Q', p1.x, p1.y, midPoint.x, midPoint.y]); + } + p1 = points[i]; + if ((i + 1) < points.length) { + p2 = points[i + 1]; + } + } + if (manyPoints) { + multSignX = p1.x > points[i - 2].x ? 1 : p1.x === points[i - 2].x ? 0 : -1; + multSignY = p1.y > points[i - 2].y ? 1 : p1.y === points[i - 2].y ? 0 : -1; + } + path.push(['L', p1.x + multSignX * correction, p1.y + multSignY * correction]); + return path; + } + /** * Calculate bounding box of a elliptic-arc * @deprecated @@ -2437,6 +2489,7 @@ fabric.CommonMethods = { fabric.util.parsePath = parsePath; fabric.util.makePathSimpler = makePathSimpler; + fabric.util.getSmoothPathFromPoints = getSmoothPathFromPoints; fabric.util.getPathSegmentsInfo = getPathSegmentsInfo; fabric.util.fromArcToBeziers = fromArcToBeziers; /** @@ -9539,7 +9592,7 @@ fabric.ElementsParser = function(elements, callback, options, reviver, parsingOp version: fabric.version, objects: this._toObjects(methodName, propertiesToInclude), }; - if (clipPath) { + if (clipPath && !clipPath.excludeFromExport) { data.clipPath = this._toObject(this.clipPath, methodName, propertiesToInclude); } extend(data, this.__serializeBgOverlay(methodName, propertiesToInclude)); @@ -9582,24 +9635,32 @@ fabric.ElementsParser = function(elements, callback, options, reviver, parsingOp * @private */ __serializeBgOverlay: function(methodName, propertiesToInclude) { - var data = { }, bgImage = this.backgroundImage, overlay = this.overlayImage; + var data = {}, bgImage = this.backgroundImage, overlayImage = this.overlayImage, + bgColor = this.backgroundColor, overlayColor = this.overlayColor; - if (this.backgroundColor) { - data.background = this.backgroundColor.toObject - ? this.backgroundColor.toObject(propertiesToInclude) - : this.backgroundColor; + if (bgColor && bgColor.toObject) { + if (!bgColor.excludeFromExport) { + data.background = bgColor.toObject(propertiesToInclude); + } + } + else if (bgColor) { + data.background = bgColor; } - if (this.overlayColor) { - data.overlay = this.overlayColor.toObject - ? this.overlayColor.toObject(propertiesToInclude) - : this.overlayColor; + if (overlayColor && overlayColor.toObject) { + if (!overlayColor.excludeFromExport) { + data.overlay = overlayColor.toObject(propertiesToInclude); + } } + else if (overlayColor) { + data.overlay = overlayColor; + } + if (bgImage && !bgImage.excludeFromExport) { data.backgroundImage = this._toObject(bgImage, methodName, propertiesToInclude); } - if (overlay && !overlay.excludeFromExport) { - data.overlayImage = this._toObject(overlay, methodName, propertiesToInclude); + if (overlayImage && !overlayImage.excludeFromExport) { + data.overlayImage = this._toObject(overlayImage, methodName, propertiesToInclude); } return data; @@ -10284,7 +10345,7 @@ fabric.BaseBrush = fabric.util.createClass(/** @lends fabric.BaseBrush.prototype * @type Number * @default */ - strokeMiterLimit: 10, + strokeMiterLimit: 10, /** * Stroke Dash Array. @@ -10298,9 +10359,13 @@ fabric.BaseBrush = fabric.util.createClass(/** @lends fabric.BaseBrush.prototype * @type Boolean * @default false */ - limitedToCanvasSize: false, + /** + * Same as fabric.Object `clipPath` property. + * The clip path is positioned relative to the top left corner of the viewport. + */ + clipPath: undefined, /** * Sets brush styles @@ -10354,7 +10419,79 @@ fabric.BaseBrush = fabric.util.createClass(/** @lends fabric.BaseBrush.prototype needsFullRender: function() { var color = new fabric.Color(this.color); - return color.getAlpha() < 1 || !!this.shadow; + return color.getAlpha() < 1 || !!this.shadow || (this.clipPath && this.clipPath.isCacheDirty()); + }, + + /** + * needed for `absolutePositioned` `clipPath` + * @private + */ + calcTransformMatrix: function () { + return fabric.util.invertTransform(this.canvas.viewportTransform); + }, + + /** + * @private + * @param {CanvasRenderingContext2D} ctx + */ + drawClipPathOnCache: function (ctx) { + fabric.Object.prototype.drawClipPathOnCache.call(this, ctx); + }, + + /** + * @private + * @param {CanvasRenderingContext2D} ctx + */ + _drawClipPath: function (ctx) { + const v = fabric.util.invertTransform(this.canvas.viewportTransform); + ctx.save(); + ctx.transform(v[0], v[1], v[2], v[3], v[4], v[5]); + fabric.Object.prototype._drawClipPath.call(this, ctx); + ctx.restore(); + }, + + /** + * Adds the clip path to the resulting object created by the brush + * @private + * @param {fabric.Object} result + */ + _addClipPathToResult: function (result) { + if (!this.clipPath) { + return; + } + var t = result.calcTransformMatrix(); + if (!this.clipPath.absolutePositioned) { + t = fabric.util.multiplyTransformMatrices(this.canvas.viewportTransform, t); + } + this.clipPath.clone(function (clipPath) { + var desiredTransform = fabric.util.multiplyTransformMatrices( + fabric.util.invertTransform(t), + clipPath.calcTransformMatrix() + ); + fabric.util.applyTransformToObject(clipPath, desiredTransform); + result.set('clipPath', clipPath); + }, ['inverted']); + }, + + /** + * Subclasses should override this method + * @private + * @param {CanvasRenderingContext2D} ctx + */ + render: function (ctx /*eslint-disable-line no-unused-vars*/) { + + }, + + /** + * Render the full state of the brush + * @private + */ + _render: function () { + var ctx = this.canvas.contextTop; + this._saveAndTransform(ctx); + this.render(ctx); + this._drawClipPath(ctx); + ctx.restore(); }, /** @@ -10457,6 +10594,7 @@ fabric.BaseBrush = fabric.util.createClass(/** @lends fabric.BaseBrush.prototype } this.oldEnd = this._drawSegment(ctx, points[length - 2], points[length - 1], true); ctx.stroke(); + this._drawClipPath(ctx); ctx.restore(); } } @@ -10521,13 +10659,11 @@ fabric.BaseBrush = fabric.util.createClass(/** @lends fabric.BaseBrush.prototype /** * Draw a smooth path on the topCanvas using quadraticCurveTo * @private + * @param {CanvasRenderingContext2D} ctx */ - _render: function() { - var ctx = this.canvas.contextTop, i, len, - p1 = this._points[0], - p2 = this._points[1]; + render: function(ctx) { + var i, len, p1 = this._points[0], p2 = this._points[1]; - this._saveAndTransform(ctx); ctx.beginPath(); //if we only have 2 points in the path and they are the same //it means that the user only clicked the canvas without moving the mouse @@ -10554,49 +10690,31 @@ fabric.BaseBrush = fabric.util.createClass(/** @lends fabric.BaseBrush.prototype // the bezier control point ctx.lineTo(p1.x, p1.y); ctx.stroke(); - ctx.restore(); }, /** * Converts points to SVG path * @param {Array} points Array of points - * @return {String} SVG path + * @return {(string|number)[][]} SVG path commands */ - convertPointsToSVGPath: function(points) { - var path = [], i, width = this.width / 1000, - p1 = new fabric.Point(points[0].x, points[0].y), - p2 = new fabric.Point(points[1].x, points[1].y), - len = points.length, multSignX = 1, multSignY = 0, manyPoints = len > 2; + convertPointsToSVGPath: function (points) { + var correction = this.width / 1000; + return fabric.util.getSmoothPathFromPoints(points, correction); + }, - if (manyPoints) { - multSignX = points[2].x < p2.x ? -1 : points[2].x === p2.x ? 0 : 1; - multSignY = points[2].y < p2.y ? -1 : points[2].y === p2.y ? 0 : 1; - } - path.push('M ', p1.x - multSignX * width, ' ', p1.y - multSignY * width, ' '); - for (i = 1; i < len; i++) { - if (!p1.eq(p2)) { - var midPoint = p1.midPointFrom(p2); - // p1 is our bezier control point - // midpoint is our endpoint - // start point is p(i-1) value. - path.push('Q ', p1.x, ' ', p1.y, ' ', midPoint.x, ' ', midPoint.y, ' '); - } - p1 = points[i]; - if ((i + 1) < points.length) { - p2 = points[i + 1]; - } - } - if (manyPoints) { - multSignX = p1.x > points[i - 2].x ? 1 : p1.x === points[i - 2].x ? 0 : -1; - multSignY = p1.y > points[i - 2].y ? 1 : p1.y === points[i - 2].y ? 0 : -1; - } - path.push('L ', p1.x + multSignX * width, ' ', p1.y + multSignY * width); - return path; + /** + * @private + * @param {(string|number)[][]} pathData SVG path commands + * @returns {boolean} + */ + _isEmptySVGPath: function (pathData) { + var pathString = pathData.map(function (segment) { return segment.join(' '); }).join(' '); + return pathString === 'M 0 0 Q 0 0 0 0 L 0 0'; }, /** * Creates fabric.Path object to add on canvas - * @param {String} pathData Path data + * @param {(string|number)[][]} pathData Path data * @return {fabric.Path} Path to add on canvas */ createPath: function(pathData) { @@ -10613,7 +10731,7 @@ fabric.BaseBrush = fabric.util.createClass(/** @lends fabric.BaseBrush.prototype this.shadow.affectStroke = true; path.shadow = new fabric.Shadow(this.shadow); } - + this._addClipPathToResult(path); return path; }, @@ -10653,8 +10771,8 @@ fabric.BaseBrush = fabric.util.createClass(/** @lends fabric.BaseBrush.prototype if (this.decimate) { this._points = this.decimatePoints(this._points, this.decimate); } - var pathData = this.convertPointsToSVGPath(this._points).join(''); - if (pathData === 'M 0 0 Q 0 0 0 0 L 0 0') { + var pathData = this.convertPointsToSVGPath(this._points); + if (this._isEmptySVGPath(pathData)) { // do not create 0 width/height paths, as they are // rendered inconsistently across browsers // Firefox 4, for example, renders a dot, @@ -10711,6 +10829,7 @@ fabric.CircleBrush = fabric.util.createClass(fabric.BaseBrush, /** @lends fabric ctx = this.canvas.contextTop; this._saveAndTransform(ctx); this.dot(ctx, point); + this._drawClipPath(ctx); ctx.restore(); }, @@ -10733,17 +10852,14 @@ fabric.CircleBrush = fabric.util.createClass(fabric.BaseBrush, /** @lends fabric }, /** - * Render the full state of the brush * @private + * @param {CanvasRenderingContext2D} ctx */ - _render: function() { - var ctx = this.canvas.contextTop, i, len, - points = this.points; - this._saveAndTransform(ctx); + render: function (ctx) { + var i, len, points = this.points; for (i = 0, len = points.length; i < len; i++) { this.dot(ctx, points[i]); } - ctx.restore(); }, /** @@ -10790,6 +10906,7 @@ fabric.CircleBrush = fabric.util.createClass(fabric.BaseBrush, /** @lends fabric } var group = new fabric.Group(circles); group.canvas = this.canvas; + this._addClipPathToResult(group); this.canvas.fire('before:path:created', { path: group }); this.canvas.add(group); @@ -10893,7 +11010,7 @@ fabric.SprayBrush = fabric.util.createClass( fabric.BaseBrush, /** @lends fabric this._setShadow(); this.addSprayChunk(pointer); - this.render(this.sprayChunkPoints); + this.renderChunk(this.sprayChunkPoints); }, /** @@ -10905,7 +11022,7 @@ fabric.SprayBrush = fabric.util.createClass( fabric.BaseBrush, /** @lends fabric return; } this.addSprayChunk(pointer); - this.render(this.sprayChunkPoints); + this.renderChunk(this.sprayChunkPoints); }, /** @@ -10941,6 +11058,8 @@ fabric.SprayBrush = fabric.util.createClass( fabric.BaseBrush, /** @lends fabric var group = new fabric.Group(rects); this.shadow && group.set('shadow', new fabric.Shadow(this.shadow)); + this._addClipPathToResult(group); + this.canvas.fire('before:path:created', { path: group }); this.canvas.add(group); this.canvas.fire('path:created', { path: group }); @@ -10975,14 +11094,22 @@ fabric.SprayBrush = fabric.util.createClass( fabric.BaseBrush, /** @lends fabric }, /** - * Render new chunk of spray brush + * Sets the transformation and fillStyle on given context + * @param {RenderingContext2d} ctx context to render on + * @private */ - render: function(sprayChunk) { - var ctx = this.canvas.contextTop, i, len; + _saveAndTransform: function (ctx) { + this.callSuper('_saveAndTransform', ctx); ctx.fillStyle = this.color; + }, - this._saveAndTransform(ctx); - + /** + * @private + * Render new chunk of spray brush + * @param {CanvasRenderingContext2D} ctx + */ + _renderChunk: function (ctx, sprayChunk) { + var i, len; for (i = 0, len = sprayChunk.length; i < len; i++) { var point = sprayChunk[i]; if (typeof point.opacity !== 'undefined') { @@ -10990,22 +11117,29 @@ fabric.SprayBrush = fabric.util.createClass( fabric.BaseBrush, /** @lends fabric } ctx.fillRect(point.x, point.y, point.width, point.width); } - ctx.restore(); }, /** - * Render all spray chunks + * @private + * @param {fabric.Object} sprayChunk */ - _render: function() { - var ctx = this.canvas.contextTop, i, ilen; - ctx.fillStyle = this.color; - + renderChunk: function (sprayChunk) { + var ctx = this.canvas.contextTop; this._saveAndTransform(ctx); + this._renderChunk(ctx, sprayChunk); + this._drawClipPath(ctx); + ctx.restore(); + }, + /** + * Render all spray chunks + * @param {CanvasRenderingContext2D} ctx + */ + render: function(ctx) { + var i, ilen; for (i = 0, ilen = this.sprayChunks.length; i < ilen; i++) { - this.render(this.sprayChunks[i]); + this._renderChunk(ctx, this.sprayChunks[i]); } - ctx.restore(); }, /** @@ -14834,7 +14968,7 @@ fabric.util.object.extend(fabric.StaticCanvas.prototype, /** @lends fabric.Stati skewY: toFixed(this.skewY, NUM_FRACTION_DIGITS), }; - if (this.clipPath) { + if (this.clipPath && !this.clipPath.excludeFromExport) { object.clipPath = this.clipPath.toObject(propertiesToInclude); object.clipPath.inverted = this.clipPath.inverted; object.clipPath.absolutePositioned = this.clipPath.absolutePositioned; @@ -19923,13 +20057,17 @@ fabric.util.object.extend(fabric.Object.prototype, /** @lends fabric.Object.prot */ toObject: function(propertiesToInclude) { var _includeDefaultValues = this.includeDefaultValues; - var objsToObject = this._objects.map(function(obj) { - var originalDefaults = obj.includeDefaultValues; - obj.includeDefaultValues = _includeDefaultValues; - var _obj = obj.toObject(propertiesToInclude); - obj.includeDefaultValues = originalDefaults; - return _obj; - }); + var objsToObject = this._objects + .filter(function (obj) { + return !obj.excludeFromExport; + }) + .map(function (obj) { + var originalDefaults = obj.includeDefaultValues; + obj.includeDefaultValues = _includeDefaultValues; + var _obj = obj.toObject(propertiesToInclude); + obj.includeDefaultValues = originalDefaults; + return _obj; + }); var obj = fabric.Object.prototype.toObject.call(this, propertiesToInclude); obj.objects = objsToObject; return obj; @@ -30550,3 +30688,744 @@ fabric.util.object.extend(fabric.IText.prototype, /** @lends fabric.IText.protot } })(); + +(function () { + /** ERASER_START */ + var __setBgOverlayColor = fabric.StaticCanvas.prototype.__setBgOverlayColor; + var ___setBgOverlay = fabric.StaticCanvas.prototype.__setBgOverlay; + var __setSVGBgOverlayColor = fabric.StaticCanvas.prototype._setSVGBgOverlayColor; + fabric.util.object.extend(fabric.StaticCanvas.prototype, { + backgroundColor: undefined, + overlayColor: undefined, + /** + * Create Rect that holds the color to support erasing + * patches {@link CommonMethods#_initGradient} + * @private + * @param {'bakground'|'overlay'} property + * @param {(String|fabric.Pattern|fabric.Rect)} color Color or pattern or rect (in case of erasing) + * @param {Function} callback Callback to invoke when color is set + * @param {Object} options + * @return {fabric.Canvas} instance + * @chainable true + */ + __setBgOverlayColor: function (property, color, callback, options) { + if (color && color.isType && color.isType('rect')) { + // color is already an object + this[property] = color; + color.set(options); + callback && callback(this[property]); + } + else { + var _this = this; + var cb = function () { + _this[property] = new fabric.Rect(fabric.util.object.extend({ + width: _this.width, + height: _this.height, + fill: _this[property], + }, options)); + callback && callback(_this[property]); + }; + __setBgOverlayColor.call(this, property, color, cb); + // invoke cb in case of gradient + // see {@link CommonMethods#_initGradient} + if (color && color.colorStops && !(color instanceof fabric.Gradient)) { + cb(); + } + } + + return this; + }, + + setBackgroundColor: function (backgroundColor, callback, options) { + return this.__setBgOverlayColor('backgroundColor', backgroundColor, callback, options); + }, + + setOverlayColor: function (overlayColor, callback, options) { + return this.__setBgOverlayColor('overlayColor', overlayColor, callback, options); + }, + + /** + * patch serialization - from json + * background/overlay properties could be objects if parsed by this mixin or could be legacy values + * @private + * @param {String} property Property to set (backgroundImage, overlayImage, backgroundColor, overlayColor) + * @param {(Object|String)} value Value to set + * @param {Object} loaded Set loaded property to true if property is set + * @param {Object} callback Callback function to invoke after property is set + */ + __setBgOverlay: function (property, value, loaded, callback) { + var _this = this; + + if ((property === 'backgroundColor' || property === 'overlayColor') && + (value && typeof value === 'object' && value.type === 'rect')) { + fabric.util.enlivenObjects([value], function (enlivedObject) { + _this[property] = enlivedObject[0]; + loaded[property] = true; + callback && callback(); + }); + } + else { + ___setBgOverlay.call(this, property, value, loaded, callback); + } + }, + + /** + * patch serialization - to svg + * background/overlay properties could be objects if parsed by this mixin or could be legacy values + * @private + */ + _setSVGBgOverlayColor: function (markup, property, reviver) { + var filler = this[property + 'Color']; + if (filler && filler.isType && filler.isType('rect')) { + var excludeFromExport = filler.excludeFromExport || (this[property] && this[property].excludeFromExport); + if (filler && !excludeFromExport && filler.toSVG) { + markup.push(filler.toSVG(reviver)); + } + } + else { + __setSVGBgOverlayColor.call(this, markup, property, reviver); + } + }, + + /** + * @private + * @param {CanvasRenderingContext2D} ctx Context to render on + * @param {string} property 'background' or 'overlay' + */ + _renderBackgroundOrOverlay: function (ctx, property) { + var fill = this[property + 'Color'], object = this[property + 'Image'], + v = this.viewportTransform, needsVpt = this[property + 'Vpt']; + if (!fill && !object) { + return; + } + if (fill || object) { + ctx.save(); + if (needsVpt) { + ctx.transform(v[0], v[1], v[2], v[3], v[4], v[5]); + } + fill && fill.render(ctx); + object && object.render(ctx); + ctx.restore(); + } + }, + }); + + var _toObject = fabric.Object.prototype.toObject; + var __createBaseSVGMarkup = fabric.Object.prototype._createBaseSVGMarkup; + fabric.util.object.extend(fabric.Object.prototype, { + /** + * Indicates whether this object can be erased by {@link fabric.EraserBrush} + * @type boolean + * @default true + */ + erasable: true, + + /** + * + * @returns {fabric.Group | null} + */ + getEraser: function () { + return this.clipPath && this.clipPath.eraser ? this.clipPath : null; + }, + + /** + * Returns an object representation of an instance + * @param {Array} [propertiesToInclude] Any properties that you might want to additionally include in the output + * @return {Object} Object representation of an instance + */ + toObject: function (additionalProperties) { + return _toObject.call(this, ['erasable'].concat(additionalProperties)); + }, + + /** + * use to achieve erasing for svg + * credit: https://travishorn.com/removing-parts-of-shapes-in-svg-b539a89e5649 + * @param {Function} reviver + * @returns {string} markup + */ + eraserToSVG: function (options) { + var eraser = this.getEraser(); + if (eraser) { + var fill = eraser._objects[0].fill; + eraser._objects[0].fill = 'white'; + eraser.clipPathId = 'CLIPPATH_' + fabric.Object.__uid++; + var commons = [ + 'id="' + eraser.clipPathId + '"', + /*options.additionalTransform ? ' transform="' + options.additionalTransform + '" ' : ''*/ + ].join(' '); + var objectMarkup = ['', '', eraser.toSVG(options.reviver), '', '']; + eraser._objects[0].fill = fill; + return objectMarkup.join('\n'); + } + return ''; + }, + + /** + * use to achieve erasing for svg, override + * @param {string[]} objectMarkup + * @param {Object} options + * @returns + */ + _createBaseSVGMarkup: function (objectMarkup, options) { + var eraser = this.getEraser(); + if (eraser) { + var eraserMarkup = this.eraserToSVG(options); + this.clipPath = null; + var markup = __createBaseSVGMarkup.call(this, objectMarkup, options); + this.clipPath = eraser; + return [ + eraserMarkup, + markup.replace('>', 'mask="url(#' + eraser.clipPathId + ')" >') + ].join('\n'); + } + else { + return __createBaseSVGMarkup.call(this, objectMarkup, options); + } + } + }); + + var _groupToObject = fabric.Group.prototype.toObject; + fabric.util.object.extend(fabric.Group.prototype, { + /** + * Returns an object representation of an instance + * @param {Array} [propertiesToInclude] Any properties that you might want to additionally include in the output + * @return {Object} Object representation of an instance + */ + toObject: function (additionalProperties) { + return _groupToObject.call(this, ['eraser'].concat(additionalProperties)); + } + }); + + fabric.util.object.extend(fabric.Canvas.prototype, { + /** + * Used by {@link #renderAll} + * @returns boolean + */ + isErasing: function () { + return ( + this.isDrawingMode && + this.freeDrawingBrush && + this.freeDrawingBrush.type === 'eraser' && + this.freeDrawingBrush._isErasing + ); + }, + + /** + * While erasing, the brush is in charge of rendering the canvas + * It uses both layers to achieve diserd erasing effect + * + * @returns fabric.Canvas + */ + renderAll: function () { + if (this.contextTopDirty && !this._groupSelector && !this.isDrawingMode) { + this.clearContext(this.contextTop); + this.contextTopDirty = false; + } + // while erasing the brush is in charge of rendering the canvas so we return + if (this.isErasing()) { + this.freeDrawingBrush._render(); + return; + } + if (this.hasLostContext) { + this.renderTopLayer(this.contextTop); + } + var canvasToDrawOn = this.contextContainer; + this.renderCanvas(canvasToDrawOn, this._chooseObjectsToRender()); + return this; + } + }); + + + /** + * EraserBrush class + * Supports selective erasing meaning that only erasable objects are affected by the eraser brush. + * In order to support selective erasing all non erasable objects are rendered on the main/bottom ctx + * while the entire canvas is rendered on the top ctx. + * Canvas bakground/overlay image/color are handled as well. + * When erasing occurs, the path clips the top ctx and reveals the bottom ctx. + * This achieves the desired effect of seeming to erase only erasable objects. + * After erasing is done the created path is added to all intersected objects' `clipPath` property. + * + * + * @class fabric.EraserBrush + * @extends fabric.PencilBrush + */ + fabric.EraserBrush = fabric.util.createClass( + fabric.PencilBrush, + /** @lends fabric.EraserBrush.prototype */ { + type: 'eraser', + + /** + * Indicates that the ctx is ready and rendering can begin. + * Used to prevent a race condition caused by {@link fabric.EraserBrush#onMouseMove} firing before {@link fabric.EraserBrush#onMouseDown} has completed + * + * @private + */ + _ready: false, + + /** + * @private + */ + _drawOverlayOnTop: false, + + /** + * @private + */ + _isErasing: false, + + initialize: function (canvas) { + this.callSuper('initialize', canvas); + this._renderBound = this._render.bind(this); + this.renderAll = this.renderAll.bind(this); + }, + + /** + * Disable clip path drawing. + * Clip path must be handled differently when erasing. + * @private + */ + _drawClipPath: function () { + + }, + + /** + * Used to hide a drawable from the rendering process + * @param {fabric.Object} object + */ + hideObject: function (object) { + if (object) { + object._originalOpacity = object.opacity; + object.set({ opacity: 0 }); + } + }, + + /** + * Restores hiding an object + * {@link fabric.EraserBrush#hideObject} + * @param {fabric.Object} object + */ + restoreObjectVisibility: function (object) { + if (object && object._originalOpacity) { + object.set({ opacity: object._originalOpacity }); + object._originalOpacity = undefined; + } + }, + + /** + * Drawing Logic For background drawables: (`backgroundImage`, `backgroundColor`) + * 1. if erasable = true: + * we need to hide the drawable on the bottom ctx so when the brush is erasing it will clip the top ctx and reveal white space underneath + * 2. if erasable = false: + * we need to draw the drawable only on the bottom ctx so the brush won't affect it + * @param {'bottom' | 'top' | 'overlay'} layer + */ + prepareCanvasBackgroundForLayer: function (layer) { + if (layer === 'overlay') { + return; + } + var canvas = this.canvas; + var image = canvas.get('backgroundImage'); + var color = canvas.get('backgroundColor'); + var erasablesOnLayer = layer === 'top'; + if (image && image.erasable === !erasablesOnLayer) { + this.hideObject(image); + } + if (color && color.erasable === !erasablesOnLayer) { + this.hideObject(color); + } + }, + + /** + * Drawing Logic For overlay drawables (`overlayImage`, `overlayColor`) + * We must draw on top ctx to be on top of visible canvas + * 1. if erasable = true: + * we need to draw the drawable on the top ctx as a normal object + * 2. if erasable = false: + * we need to draw the drawable on top of the brush, + * this means we need to repaint for every stroke + * + * @param {'bottom' | 'top' | 'overlay'} layer + * @returns boolean render overlay above brush + */ + prepareCanvasOverlayForLayer: function (layer) { + var canvas = this.canvas; + var image = canvas.get('overlayImage'); + var color = canvas.get('overlayColor'); + if (layer === 'bottom') { + this.hideObject(image); + this.hideObject(color); + return false; + }; + var erasablesOnLayer = layer === 'top'; + var renderOverlayOnTop = (image && !image.erasable) || (color && !color.erasable); + if (image && image.erasable === !erasablesOnLayer) { + this.hideObject(image); + } + if (color && color.erasable === !erasablesOnLayer) { + this.hideObject(color); + } + return renderOverlayOnTop; + }, + + /** + * @private + */ + restoreCanvasDrawables: function () { + var canvas = this.canvas; + this.restoreObjectVisibility(canvas.get('backgroundImage')); + this.restoreObjectVisibility(canvas.get('backgroundColor')); + this.restoreObjectVisibility(canvas.get('overlayImage')); + this.restoreObjectVisibility(canvas.get('overlayColor')); + }, + + /** + * @private + * This is designed to support erasing a group with both erasable and non-erasable objects. + * Iterates over collections to allow nested selective erasing. + * Used by {@link fabric.EraserBrush#prepareCanvasObjectsForLayer} + * to prepare the bottom layer by hiding erasable nested objects + * + * @param {fabric.Collection} collection + */ + prepareCollectionTraversal: function (collection) { + var _this = this; + collection.forEachObject(function (obj) { + if (obj.forEachObject) { + _this.prepareCollectionTraversal(obj); + } + else { + if (obj.erasable) { + _this.hideObject(obj); + } + } + }); + }, + + /** + * @private + * Used by {@link fabric.EraserBrush#prepareCanvasObjectsForLayer} + * to reverse the action of {@link fabric.EraserBrush#prepareCollectionTraversal} + * + * @param {fabric.Collection} collection + */ + restoreCollectionTraversal: function (collection) { + var _this = this; + collection.forEachObject(function (obj) { + if (obj.forEachObject) { + _this.restoreCollectionTraversal(obj); + } + else { + _this.restoreObjectVisibility(obj); + } + }); + }, + + /** + * @private + * This is designed to support erasing a group with both erasable and non-erasable objects. + * + * @param {'bottom' | 'top' | 'overlay'} layer + */ + prepareCanvasObjectsForLayer: function (layer) { + if (layer !== 'bottom') { return; } + this.prepareCollectionTraversal(this.canvas); + }, + + /** + * @private + * @param {'bottom' | 'top' | 'overlay'} layer + */ + restoreCanvasObjectsFromLayer: function (layer) { + if (layer !== 'bottom') { return; } + this.restoreCollectionTraversal(this.canvas); + }, + + /** + * @private + * @param {'bottom' | 'top' | 'overlay'} layer + * @returns boolean render overlay above brush + */ + prepareCanvasForLayer: function (layer) { + this.prepareCanvasBackgroundForLayer(layer); + this.prepareCanvasObjectsForLayer(layer); + return this.prepareCanvasOverlayForLayer(layer); + }, + + /** + * @private + * @param {'bottom' | 'top' | 'overlay'} layer + */ + restoreCanvasFromLayer: function (layer) { + this.restoreCanvasDrawables(); + this.restoreCanvasObjectsFromLayer(layer); + }, + + /** + * Render all non-erasable objects on bottom layer with the exception of overlays to avoid being clipped by the brush. + * Groups are rendered for nested selective erasing, non-erasable objects are visible while erasable objects are not. + */ + renderBottomLayer: function () { + var canvas = this.canvas; + this.prepareCanvasForLayer('bottom'); + canvas.renderCanvas( + canvas.getContext(), + canvas.getObjects().filter(function (obj) { + return !obj.erasable || obj.isType('group'); + }) + ); + this.restoreCanvasFromLayer('bottom'); + }, + + /** + * 1. Render all objects on top layer, erasable and non-erasable + * This is important for cases such as overlapping objects, the background object erasable and the foreground object not erasable. + * 2. Render the brush + */ + renderTopLayer: function () { + var canvas = this.canvas, ctx = canvas.contextTop, inverted = this.clipPath.inverted; + this._drawOverlayOnTop = this.prepareCanvasForLayer('top'); + if (this.clipPath) { + var mainContext = canvas.getContext(); + canvas.renderCanvas( + mainContext, + canvas.getObjects() + ); + this.clipPath.set('inverted', !!inverted); + this.callSuper('_saveAndTransform', mainContext); + this.callSuper('_drawClipPath', mainContext); + mainContext.restore(); + this.clipPath.set('inverted', inverted); + } + canvas.renderCanvas( + ctx, + canvas.getObjects() + ); + this.clipPath.set('inverted', !!inverted); + this.callSuper('_render'); + this.clipPath.set('inverted', inverted); + this.restoreCanvasFromLayer('top'); + }, + + /** + * Render all non-erasable overlays on top of the brush so that they won't get erased + */ + renderOverlay: function () { + this.prepareCanvasForLayer('overlay'); + var canvas = this.canvas; + var ctx = canvas.contextTop; + this._saveAndTransform(ctx); + canvas._renderOverlay(ctx); + ctx.restore(); + this.restoreCanvasFromLayer('overlay'); + }, + + /** + * @extends @class fabric.BaseBrush + * @param {CanvasRenderingContext2D} ctx + */ + _saveAndTransform: function (ctx) { + this.callSuper('_saveAndTransform', ctx); + ctx.globalCompositeOperation = 'destination-out'; + }, + + /** + * We indicate {@link fabric.PencilBrush} to repaint itself if necessary + * @returns + */ + needsFullRender: function () { + return this.callSuper('needsFullRender') || this._drawOverlayOnTop || this.clipPath; + }, + + /** + * + * @param {fabric.Point} pointer + * @param {fabric.IEvent} options + * @returns + */ + onMouseDown: function (pointer, options) { + if (!this.canvas._isMainEvent(options.e)) { + return; + } + this._prepareForDrawing(pointer); + // capture coordinates immediately + // this allows to draw dots (when movement never occurs) + this._captureDrawingPath(pointer); + + this._isErasing = true; + this.canvas.fire('erasing:start'); + this._ready = true; + this._render(); + }, + + /** + * Rendering is done in 4 steps: + * 1. Draw all non-erasable objects on bottom ctx with the exception of overlays {@link fabric.EraserBrush#renderBottomLayer} + * 2. Draw all objects on top ctx including erasable drawables {@link fabric.EraserBrush#renderTopLayer} + * 3. Draw eraser {@link fabric.PencilBrush#_render} at {@link fabric.EraserBrush#renderTopLayer} + * 4. Draw non-erasable overlays {@link fabric.EraserBrush#renderOverlay} + * + * @param {fabric.Canvas} canvas + */ + _render: function () { + if (!this._ready) { + return; + } + this.isRendering = 1; + this.renderBottomLayer(); + this.renderTopLayer(); + this.renderOverlay(); + this.isRendering = 0; + }, + + /** + * @public + */ + renderAll: function () { + if (this._isErasing) { + if (this.isRendering) { + this.isRendering = fabric.util.requestAnimFrame(this._renderBound); + } + else { + this._render(); + } + return true; + } + return false; + }, + + /** + * Adds path to existing clipPath of object + * + * @param {fabric.Object} obj + * @param {fabric.Path} path + */ + _addPathToObjectEraser: function (obj, path) { + var clipObject; + var _this = this; + // object is collection, i.e group + if (obj.forEachObject) { + obj.forEachObject(function (_obj) { + if (_obj.erasable) { + _this._addPathToObjectEraser(_obj, path); + } + }); + return; + } + if (!obj.getEraser()) { + var size = obj._getNonTransformedDimensions(); + var rect = new fabric.Rect({ + width: size.x, + height: size.y, + clipPath: obj.clipPath, + originX: 'center', + originY: 'center' + }); + clipObject = new fabric.Group([rect], { + eraser: true + }); + } + else { + clipObject = obj.clipPath; + } + + path.clone(function (path) { + path.globalCompositeOperation = 'destination-out'; + // http://fabricjs.com/using-transformations + var desiredTransform = fabric.util.multiplyTransformMatrices( + fabric.util.invertTransform( + obj.calcTransformMatrix() + ), + path.calcTransformMatrix() + ); + fabric.util.applyTransformToObject(path, desiredTransform); + clipObject.addWithUpdate(path); + obj.set({ + clipPath: clipObject, + dirty: true + }); + }); + }, + + /** + * Add the eraser path to canvas drawables' clip paths + * + * @param {fabric.Canvas} source + * @param {fabric.Canvas} path + * @returns {Object} canvas drawables that were erased by the path + */ + applyEraserToCanvas: function (path) { + var canvas = this.canvas; + var drawables = {}; + [ + 'backgroundImage', + 'backgroundColor', + 'overlayImage', + 'overlayColor', + ].forEach(function (prop) { + var drawable = canvas[prop]; + if (drawable && drawable.erasable) { + this._addPathToObjectEraser(drawable, path); + drawables[prop] = drawable; + } + }, this); + return drawables; + }, + + /** + * On mouseup after drawing the path on contextTop canvas + * we use the points captured to create an new fabric path object + * and add it to every intersected erasable object. + */ + _finalizeAndAddPath: function () { + var ctx = this.canvas.contextTop, canvas = this.canvas; + ctx.closePath(); + if (this.decimate) { + this._points = this.decimatePoints(this._points, this.decimate); + } + + // clear + canvas.clearContext(canvas.contextTop); + this._isErasing = false; + + var pathData = this._points && this._points.length > 1 ? + this.convertPointsToSVGPath(this._points) : + null; + if (!pathData || this._isEmptySVGPath(pathData)) { + canvas.fire('erasing:end'); + // do not create 0 width/height paths, as they are + // rendered inconsistently across browsers + // Firefox 4, for example, renders a dot, + // whereas Chrome 10 renders nothing + canvas.requestRenderAll(); + return; + } + + var path = this.createPath(pathData); + canvas.fire('before:path:created', { path: path }); + + // finalize erasing + var drawables = this.applyEraserToCanvas(path); + var _this = this; + var targets = []; + canvas.forEachObject(function (obj) { + if (obj.erasable && obj.intersectsWithObject(path, true)) { + _this._addPathToObjectEraser(obj, path); + targets.push(obj); + } + }); + + canvas.fire('erasing:end', { path: path, targets: targets, drawables: drawables }); + + canvas.requestRenderAll(); + path.setCoords(); + this._resetShadow(); + + // fire event 'path' created + canvas.fire('path:created', { path: path }); + } + } + ); + + /** ERASER_END */ +})(); + diff --git a/src/mixins/eraser_brush.mixin.js b/src/mixins/eraser_brush.mixin.js index f9a1d2a779b..e06a3727e52 100644 --- a/src/mixins/eraser_brush.mixin.js +++ b/src/mixins/eraser_brush.mixin.js @@ -10,7 +10,7 @@ * Create Rect that holds the color to support erasing * patches {@link CommonMethods#_initGradient} * @private - * @param {'bakground'|'overlay'} property + * @param {'background'|'overlay'} property * @param {(String|fabric.Pattern|fabric.Rect)} color Color or pattern or rect (in case of erasing) * @param {Function} callback Callback to invoke when color is set * @param {Object} options @@ -248,9 +248,9 @@ /** * EraserBrush class * Supports selective erasing meaning that only erasable objects are affected by the eraser brush. - * In order to support selective erasing all non erasable objects are rendered on the main/bottom ctx + * In order to support selective erasing all non erasable objects and all clipped out objects are rendered on the main/bottom ctx * while the entire canvas is rendered on the top ctx. - * Canvas bakground/overlay image/color are handled as well. + * Canvas background/overlay image/color are handled as well. * When erasing occurs, the path clips the top ctx and reveals the bottom ctx. * This achieves the desired effect of seeming to erase only erasable objects. * After erasing is done the created path is added to all intersected objects' `clipPath` property. @@ -485,31 +485,43 @@ this.restoreCanvasFromLayer('bottom'); }, + /** + * Render the inverted view of the clip path on the bottom layer. + * Clipped out objects are treated as non-erasable because they are rendered outside the erasable area. + * Achieves an effect of erasing only the objects inside the clipped area. + * @private + */ + renderClippedOutLayer: function () { + if (!this.clipPath) { + return; + } + var canvas = this.canvas, ctx = canvas.getContext(), inverted = this.clipPath.inverted; + this.prepareCanvasForLayer('top'); + canvas.renderCanvas( + ctx, + canvas.getObjects() + ); + this.clipPath.set('inverted', !!!inverted); + this.callSuper('_saveAndTransform', ctx); + this.callSuper('_drawClipPath', ctx); + ctx.restore(); + this.clipPath.set('inverted', inverted); + this.restoreCanvasFromLayer('top'); + }, + /** * 1. Render all objects on top layer, erasable and non-erasable * This is important for cases such as overlapping objects, the background object erasable and the foreground object not erasable. * 2. Render the brush */ renderTopLayer: function () { - var canvas = this.canvas; + var canvas = this.canvas, ctx = canvas.contextTop; this._drawOverlayOnTop = this.prepareCanvasForLayer('top'); - if (this.clipPath) { - var ctx = canvas.getContext(); - canvas.renderCanvas( - ctx, - canvas.getObjects() - ); - var inverted = this.clipPath.inverted; - this.clipPath.set('inverted', !!inverted); - this.callSuper('_drawClipPath', ctx); - this.clipPath.set('inverted', inverted) - } canvas.renderCanvas( - canvas.contextTop, + ctx, canvas.getObjects() ); this.callSuper('_render'); - this.callSuper('_drawClipPath', ctx); this.restoreCanvasFromLayer('top'); }, @@ -540,7 +552,7 @@ * @returns */ needsFullRender: function () { - return this.callSuper('needsFullRender') || this._drawOverlayOnTop || this.clipPath; + return this.callSuper('needsFullRender') || this._drawOverlayOnTop; }, /** @@ -567,9 +579,10 @@ /** * Rendering is done in 4 steps: * 1. Draw all non-erasable objects on bottom ctx with the exception of overlays {@link fabric.EraserBrush#renderBottomLayer} - * 2. Draw all objects on top ctx including erasable drawables {@link fabric.EraserBrush#renderTopLayer} - * 3. Draw eraser {@link fabric.PencilBrush#_render} at {@link fabric.EraserBrush#renderTopLayer} - * 4. Draw non-erasable overlays {@link fabric.EraserBrush#renderOverlay} + * 2. Draw all clipped out objects on bottom ctx {@link fabric.EraserBrush#renderClippedOutLayer} + * 3. Draw all objects on top ctx including erasable drawables {@link fabric.EraserBrush#renderTopLayer} + * 4. Draw eraser {@link fabric.PencilBrush#_render} at {@link fabric.EraserBrush#renderTopLayer} + * 5. Draw non-erasable overlays {@link fabric.EraserBrush#renderOverlay} * * @param {fabric.Canvas} canvas */ @@ -579,6 +592,7 @@ } this.isRendering = 1; this.renderBottomLayer(); + this.renderClippedOutLayer(); this.renderTopLayer(); this.renderOverlay(); this.isRendering = 0; From ceaf13d647e7a8bdcaca77745a56ff44a4042405 Mon Sep 17 00:00:00 2001 From: ShaMan123 Date: Thu, 1 Jul 2021 21:50:39 +0300 Subject: [PATCH 17/37] lint --- src/brushes/base_brush.class.js | 2 +- src/mixins/eraser_brush.mixin.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/brushes/base_brush.class.js b/src/brushes/base_brush.class.js index 3e8d63eaef0..05cf6ec3048 100644 --- a/src/brushes/base_brush.class.js +++ b/src/brushes/base_brush.class.js @@ -145,7 +145,7 @@ fabric.BaseBrush = fabric.util.createClass(/** @lends fabric.BaseBrush.prototype * @param {CanvasRenderingContext2D} ctx */ _drawClipPath: function (ctx) { - const v = fabric.util.invertTransform(this.canvas.viewportTransform); + var v = fabric.util.invertTransform(this.canvas.viewportTransform); ctx.save(); ctx.transform(v[0], v[1], v[2], v[3], v[4], v[5]); fabric.Object.prototype._drawClipPath.call(this, ctx); diff --git a/src/mixins/eraser_brush.mixin.js b/src/mixins/eraser_brush.mixin.js index e06a3727e52..ae3cd9c33c3 100644 --- a/src/mixins/eraser_brush.mixin.js +++ b/src/mixins/eraser_brush.mixin.js @@ -294,7 +294,7 @@ * @private */ _drawClipPath: function () { - + }, /** From 251a5dfa7705da1177355786d43777ca989c0df1 Mon Sep 17 00:00:00 2001 From: ShaMan123 Date: Thu, 1 Jul 2021 21:59:14 +0300 Subject: [PATCH 18/37] revert dist --- dist/fabric.js | 1049 ++++-------------------------------------------- 1 file changed, 85 insertions(+), 964 deletions(-) diff --git a/dist/fabric.js b/dist/fabric.js index dcfb6ae8e2f..e07aff41848 100644 --- a/dist/fabric.js +++ b/dist/fabric.js @@ -1,4 +1,4 @@ -/* build: `node build.js modules=ALL exclude=gestures,accessors requirejs minifier=uglifyjs` */ +/* build: `node build.js modules=ALL exclude=gestures,accessors,erasing requirejs minifier=uglifyjs` */ /*! Fabric.js Copyright 2008-2015, Printio (Juriy Zaytsev, Maxim Chernyak) */ var fabric = fabric || { version: '4.5.1' }; @@ -2323,18 +2323,6 @@ fabric.CommonMethods = { } } - /** - * - * @param {string} pathString - * @return {(string|number)[][]} An array of SVG path commands - * @example Usage - * parsePath('M 3 4 Q 3 5 2 1 4 0 Q 9 12 2 1 4 0') === [ - * ['M', 3, 4], - * ['Q', 3, 5, 2, 1, 4, 0], - * ['Q', 9, 12, 2, 1, 4, 0], - * ]; - * - */ function parsePath(pathString) { var result = [], coords = [], @@ -2403,46 +2391,6 @@ fabric.CommonMethods = { return result; }; - /** - * - * Converts points to a smooth SVG path - * @param {{ x: number,y: number }[]} points Array of points - * @param {number} [correction] Apply a correction to the path (usually we use `width / 1000`). If value is undefined 0 is used as the correction value. - * @return {(string|number)[][]} An array of SVG path commands - */ - function getSmoothPathFromPoints(points, correction) { - var path = [], i, - p1 = new fabric.Point(points[0].x, points[0].y), - p2 = new fabric.Point(points[1].x, points[1].y), - len = points.length, multSignX = 1, multSignY = 0, manyPoints = len > 2; - correction = correction || 0; - - if (manyPoints) { - multSignX = points[2].x < p2.x ? -1 : points[2].x === p2.x ? 0 : 1; - multSignY = points[2].y < p2.y ? -1 : points[2].y === p2.y ? 0 : 1; - } - path.push(['M', p1.x - multSignX * correction, p1.y - multSignY * correction]); - for (i = 1; i < len; i++) { - if (!p1.eq(p2)) { - var midPoint = p1.midPointFrom(p2); - // p1 is our bezier control point - // midpoint is our endpoint - // start point is p(i-1) value. - path.push(['Q', p1.x, p1.y, midPoint.x, midPoint.y]); - } - p1 = points[i]; - if ((i + 1) < points.length) { - p2 = points[i + 1]; - } - } - if (manyPoints) { - multSignX = p1.x > points[i - 2].x ? 1 : p1.x === points[i - 2].x ? 0 : -1; - multSignY = p1.y > points[i - 2].y ? 1 : p1.y === points[i - 2].y ? 0 : -1; - } - path.push(['L', p1.x + multSignX * correction, p1.y + multSignY * correction]); - return path; - } - /** * Calculate bounding box of a elliptic-arc * @deprecated @@ -2489,7 +2437,6 @@ fabric.CommonMethods = { fabric.util.parsePath = parsePath; fabric.util.makePathSimpler = makePathSimpler; - fabric.util.getSmoothPathFromPoints = getSmoothPathFromPoints; fabric.util.getPathSegmentsInfo = getPathSegmentsInfo; fabric.util.fromArcToBeziers = fromArcToBeziers; /** @@ -9592,7 +9539,7 @@ fabric.ElementsParser = function(elements, callback, options, reviver, parsingOp version: fabric.version, objects: this._toObjects(methodName, propertiesToInclude), }; - if (clipPath && !clipPath.excludeFromExport) { + if (clipPath) { data.clipPath = this._toObject(this.clipPath, methodName, propertiesToInclude); } extend(data, this.__serializeBgOverlay(methodName, propertiesToInclude)); @@ -9635,32 +9582,24 @@ fabric.ElementsParser = function(elements, callback, options, reviver, parsingOp * @private */ __serializeBgOverlay: function(methodName, propertiesToInclude) { - var data = {}, bgImage = this.backgroundImage, overlayImage = this.overlayImage, - bgColor = this.backgroundColor, overlayColor = this.overlayColor; + var data = { }, bgImage = this.backgroundImage, overlay = this.overlayImage; - if (bgColor && bgColor.toObject) { - if (!bgColor.excludeFromExport) { - data.background = bgColor.toObject(propertiesToInclude); - } - } - else if (bgColor) { - data.background = bgColor; + if (this.backgroundColor) { + data.background = this.backgroundColor.toObject + ? this.backgroundColor.toObject(propertiesToInclude) + : this.backgroundColor; } - if (overlayColor && overlayColor.toObject) { - if (!overlayColor.excludeFromExport) { - data.overlay = overlayColor.toObject(propertiesToInclude); - } + if (this.overlayColor) { + data.overlay = this.overlayColor.toObject + ? this.overlayColor.toObject(propertiesToInclude) + : this.overlayColor; } - else if (overlayColor) { - data.overlay = overlayColor; - } - if (bgImage && !bgImage.excludeFromExport) { data.backgroundImage = this._toObject(bgImage, methodName, propertiesToInclude); } - if (overlayImage && !overlayImage.excludeFromExport) { - data.overlayImage = this._toObject(overlayImage, methodName, propertiesToInclude); + if (overlay && !overlay.excludeFromExport) { + data.overlayImage = this._toObject(overlay, methodName, propertiesToInclude); } return data; @@ -10345,7 +10284,7 @@ fabric.BaseBrush = fabric.util.createClass(/** @lends fabric.BaseBrush.prototype * @type Number * @default */ - strokeMiterLimit: 10, + strokeMiterLimit: 10, /** * Stroke Dash Array. @@ -10359,13 +10298,9 @@ fabric.BaseBrush = fabric.util.createClass(/** @lends fabric.BaseBrush.prototype * @type Boolean * @default false */ + limitedToCanvasSize: false, - /** - * Same as fabric.Object `clipPath` property. - * The clip path is positioned relative to the top left corner of the viewport. - */ - clipPath: undefined, /** * Sets brush styles @@ -10419,79 +10354,7 @@ fabric.BaseBrush = fabric.util.createClass(/** @lends fabric.BaseBrush.prototype needsFullRender: function() { var color = new fabric.Color(this.color); - return color.getAlpha() < 1 || !!this.shadow || (this.clipPath && this.clipPath.isCacheDirty()); - }, - - /** - * needed for `absolutePositioned` `clipPath` - * @private - */ - calcTransformMatrix: function () { - return fabric.util.invertTransform(this.canvas.viewportTransform); - }, - - /** - * @private - * @param {CanvasRenderingContext2D} ctx - */ - drawClipPathOnCache: function (ctx) { - fabric.Object.prototype.drawClipPathOnCache.call(this, ctx); - }, - - /** - * @private - * @param {CanvasRenderingContext2D} ctx - */ - _drawClipPath: function (ctx) { - const v = fabric.util.invertTransform(this.canvas.viewportTransform); - ctx.save(); - ctx.transform(v[0], v[1], v[2], v[3], v[4], v[5]); - fabric.Object.prototype._drawClipPath.call(this, ctx); - ctx.restore(); - }, - - /** - * Adds the clip path to the resulting object created by the brush - * @private - * @param {fabric.Object} result - */ - _addClipPathToResult: function (result) { - if (!this.clipPath) { - return; - } - var t = result.calcTransformMatrix(); - if (!this.clipPath.absolutePositioned) { - t = fabric.util.multiplyTransformMatrices(this.canvas.viewportTransform, t); - } - this.clipPath.clone(function (clipPath) { - var desiredTransform = fabric.util.multiplyTransformMatrices( - fabric.util.invertTransform(t), - clipPath.calcTransformMatrix() - ); - fabric.util.applyTransformToObject(clipPath, desiredTransform); - result.set('clipPath', clipPath); - }, ['inverted']); - }, - - /** - * Subclasses should override this method - * @private - * @param {CanvasRenderingContext2D} ctx - */ - render: function (ctx /*eslint-disable-line no-unused-vars*/) { - - }, - - /** - * Render the full state of the brush - * @private - */ - _render: function () { - var ctx = this.canvas.contextTop; - this._saveAndTransform(ctx); - this.render(ctx); - this._drawClipPath(ctx); - ctx.restore(); + return color.getAlpha() < 1 || !!this.shadow; }, /** @@ -10594,7 +10457,6 @@ fabric.BaseBrush = fabric.util.createClass(/** @lends fabric.BaseBrush.prototype } this.oldEnd = this._drawSegment(ctx, points[length - 2], points[length - 1], true); ctx.stroke(); - this._drawClipPath(ctx); ctx.restore(); } } @@ -10659,11 +10521,13 @@ fabric.BaseBrush = fabric.util.createClass(/** @lends fabric.BaseBrush.prototype /** * Draw a smooth path on the topCanvas using quadraticCurveTo * @private - * @param {CanvasRenderingContext2D} ctx */ - render: function(ctx) { - var i, len, p1 = this._points[0], p2 = this._points[1]; + _render: function() { + var ctx = this.canvas.contextTop, i, len, + p1 = this._points[0], + p2 = this._points[1]; + this._saveAndTransform(ctx); ctx.beginPath(); //if we only have 2 points in the path and they are the same //it means that the user only clicked the canvas without moving the mouse @@ -10690,31 +10554,49 @@ fabric.BaseBrush = fabric.util.createClass(/** @lends fabric.BaseBrush.prototype // the bezier control point ctx.lineTo(p1.x, p1.y); ctx.stroke(); + ctx.restore(); }, /** * Converts points to SVG path * @param {Array} points Array of points - * @return {(string|number)[][]} SVG path commands + * @return {String} SVG path */ - convertPointsToSVGPath: function (points) { - var correction = this.width / 1000; - return fabric.util.getSmoothPathFromPoints(points, correction); - }, + convertPointsToSVGPath: function(points) { + var path = [], i, width = this.width / 1000, + p1 = new fabric.Point(points[0].x, points[0].y), + p2 = new fabric.Point(points[1].x, points[1].y), + len = points.length, multSignX = 1, multSignY = 0, manyPoints = len > 2; - /** - * @private - * @param {(string|number)[][]} pathData SVG path commands - * @returns {boolean} - */ - _isEmptySVGPath: function (pathData) { - var pathString = pathData.map(function (segment) { return segment.join(' '); }).join(' '); - return pathString === 'M 0 0 Q 0 0 0 0 L 0 0'; + if (manyPoints) { + multSignX = points[2].x < p2.x ? -1 : points[2].x === p2.x ? 0 : 1; + multSignY = points[2].y < p2.y ? -1 : points[2].y === p2.y ? 0 : 1; + } + path.push('M ', p1.x - multSignX * width, ' ', p1.y - multSignY * width, ' '); + for (i = 1; i < len; i++) { + if (!p1.eq(p2)) { + var midPoint = p1.midPointFrom(p2); + // p1 is our bezier control point + // midpoint is our endpoint + // start point is p(i-1) value. + path.push('Q ', p1.x, ' ', p1.y, ' ', midPoint.x, ' ', midPoint.y, ' '); + } + p1 = points[i]; + if ((i + 1) < points.length) { + p2 = points[i + 1]; + } + } + if (manyPoints) { + multSignX = p1.x > points[i - 2].x ? 1 : p1.x === points[i - 2].x ? 0 : -1; + multSignY = p1.y > points[i - 2].y ? 1 : p1.y === points[i - 2].y ? 0 : -1; + } + path.push('L ', p1.x + multSignX * width, ' ', p1.y + multSignY * width); + return path; }, /** * Creates fabric.Path object to add on canvas - * @param {(string|number)[][]} pathData Path data + * @param {String} pathData Path data * @return {fabric.Path} Path to add on canvas */ createPath: function(pathData) { @@ -10731,7 +10613,7 @@ fabric.BaseBrush = fabric.util.createClass(/** @lends fabric.BaseBrush.prototype this.shadow.affectStroke = true; path.shadow = new fabric.Shadow(this.shadow); } - this._addClipPathToResult(path); + return path; }, @@ -10771,8 +10653,8 @@ fabric.BaseBrush = fabric.util.createClass(/** @lends fabric.BaseBrush.prototype if (this.decimate) { this._points = this.decimatePoints(this._points, this.decimate); } - var pathData = this.convertPointsToSVGPath(this._points); - if (this._isEmptySVGPath(pathData)) { + var pathData = this.convertPointsToSVGPath(this._points).join(''); + if (pathData === 'M 0 0 Q 0 0 0 0 L 0 0') { // do not create 0 width/height paths, as they are // rendered inconsistently across browsers // Firefox 4, for example, renders a dot, @@ -10829,7 +10711,6 @@ fabric.CircleBrush = fabric.util.createClass(fabric.BaseBrush, /** @lends fabric ctx = this.canvas.contextTop; this._saveAndTransform(ctx); this.dot(ctx, point); - this._drawClipPath(ctx); ctx.restore(); }, @@ -10852,14 +10733,17 @@ fabric.CircleBrush = fabric.util.createClass(fabric.BaseBrush, /** @lends fabric }, /** + * Render the full state of the brush * @private - * @param {CanvasRenderingContext2D} ctx */ - render: function (ctx) { - var i, len, points = this.points; + _render: function() { + var ctx = this.canvas.contextTop, i, len, + points = this.points; + this._saveAndTransform(ctx); for (i = 0, len = points.length; i < len; i++) { this.dot(ctx, points[i]); } + ctx.restore(); }, /** @@ -10906,7 +10790,6 @@ fabric.CircleBrush = fabric.util.createClass(fabric.BaseBrush, /** @lends fabric } var group = new fabric.Group(circles); group.canvas = this.canvas; - this._addClipPathToResult(group); this.canvas.fire('before:path:created', { path: group }); this.canvas.add(group); @@ -11010,7 +10893,7 @@ fabric.SprayBrush = fabric.util.createClass( fabric.BaseBrush, /** @lends fabric this._setShadow(); this.addSprayChunk(pointer); - this.renderChunk(this.sprayChunkPoints); + this.render(this.sprayChunkPoints); }, /** @@ -11022,7 +10905,7 @@ fabric.SprayBrush = fabric.util.createClass( fabric.BaseBrush, /** @lends fabric return; } this.addSprayChunk(pointer); - this.renderChunk(this.sprayChunkPoints); + this.render(this.sprayChunkPoints); }, /** @@ -11058,8 +10941,6 @@ fabric.SprayBrush = fabric.util.createClass( fabric.BaseBrush, /** @lends fabric var group = new fabric.Group(rects); this.shadow && group.set('shadow', new fabric.Shadow(this.shadow)); - this._addClipPathToResult(group); - this.canvas.fire('before:path:created', { path: group }); this.canvas.add(group); this.canvas.fire('path:created', { path: group }); @@ -11094,22 +10975,14 @@ fabric.SprayBrush = fabric.util.createClass( fabric.BaseBrush, /** @lends fabric }, /** - * Sets the transformation and fillStyle on given context - * @param {RenderingContext2d} ctx context to render on - * @private + * Render new chunk of spray brush */ - _saveAndTransform: function (ctx) { - this.callSuper('_saveAndTransform', ctx); + render: function(sprayChunk) { + var ctx = this.canvas.contextTop, i, len; ctx.fillStyle = this.color; - }, - /** - * @private - * Render new chunk of spray brush - * @param {CanvasRenderingContext2D} ctx - */ - _renderChunk: function (ctx, sprayChunk) { - var i, len; + this._saveAndTransform(ctx); + for (i = 0, len = sprayChunk.length; i < len; i++) { var point = sprayChunk[i]; if (typeof point.opacity !== 'undefined') { @@ -11117,29 +10990,22 @@ fabric.SprayBrush = fabric.util.createClass( fabric.BaseBrush, /** @lends fabric } ctx.fillRect(point.x, point.y, point.width, point.width); } - }, - - /** - * @private - * @param {fabric.Object} sprayChunk - */ - renderChunk: function (sprayChunk) { - var ctx = this.canvas.contextTop; - this._saveAndTransform(ctx); - this._renderChunk(ctx, sprayChunk); - this._drawClipPath(ctx); ctx.restore(); }, /** * Render all spray chunks - * @param {CanvasRenderingContext2D} ctx */ - render: function(ctx) { - var i, ilen; + _render: function() { + var ctx = this.canvas.contextTop, i, ilen; + ctx.fillStyle = this.color; + + this._saveAndTransform(ctx); + for (i = 0, ilen = this.sprayChunks.length; i < ilen; i++) { - this._renderChunk(ctx, this.sprayChunks[i]); + this.render(this.sprayChunks[i]); } + ctx.restore(); }, /** @@ -14968,7 +14834,7 @@ fabric.util.object.extend(fabric.StaticCanvas.prototype, /** @lends fabric.Stati skewY: toFixed(this.skewY, NUM_FRACTION_DIGITS), }; - if (this.clipPath && !this.clipPath.excludeFromExport) { + if (this.clipPath) { object.clipPath = this.clipPath.toObject(propertiesToInclude); object.clipPath.inverted = this.clipPath.inverted; object.clipPath.absolutePositioned = this.clipPath.absolutePositioned; @@ -20057,17 +19923,13 @@ fabric.util.object.extend(fabric.Object.prototype, /** @lends fabric.Object.prot */ toObject: function(propertiesToInclude) { var _includeDefaultValues = this.includeDefaultValues; - var objsToObject = this._objects - .filter(function (obj) { - return !obj.excludeFromExport; - }) - .map(function (obj) { - var originalDefaults = obj.includeDefaultValues; - obj.includeDefaultValues = _includeDefaultValues; - var _obj = obj.toObject(propertiesToInclude); - obj.includeDefaultValues = originalDefaults; - return _obj; - }); + var objsToObject = this._objects.map(function(obj) { + var originalDefaults = obj.includeDefaultValues; + obj.includeDefaultValues = _includeDefaultValues; + var _obj = obj.toObject(propertiesToInclude); + obj.includeDefaultValues = originalDefaults; + return _obj; + }); var obj = fabric.Object.prototype.toObject.call(this, propertiesToInclude); obj.objects = objsToObject; return obj; @@ -30688,744 +30550,3 @@ fabric.util.object.extend(fabric.IText.prototype, /** @lends fabric.IText.protot } })(); - -(function () { - /** ERASER_START */ - var __setBgOverlayColor = fabric.StaticCanvas.prototype.__setBgOverlayColor; - var ___setBgOverlay = fabric.StaticCanvas.prototype.__setBgOverlay; - var __setSVGBgOverlayColor = fabric.StaticCanvas.prototype._setSVGBgOverlayColor; - fabric.util.object.extend(fabric.StaticCanvas.prototype, { - backgroundColor: undefined, - overlayColor: undefined, - /** - * Create Rect that holds the color to support erasing - * patches {@link CommonMethods#_initGradient} - * @private - * @param {'bakground'|'overlay'} property - * @param {(String|fabric.Pattern|fabric.Rect)} color Color or pattern or rect (in case of erasing) - * @param {Function} callback Callback to invoke when color is set - * @param {Object} options - * @return {fabric.Canvas} instance - * @chainable true - */ - __setBgOverlayColor: function (property, color, callback, options) { - if (color && color.isType && color.isType('rect')) { - // color is already an object - this[property] = color; - color.set(options); - callback && callback(this[property]); - } - else { - var _this = this; - var cb = function () { - _this[property] = new fabric.Rect(fabric.util.object.extend({ - width: _this.width, - height: _this.height, - fill: _this[property], - }, options)); - callback && callback(_this[property]); - }; - __setBgOverlayColor.call(this, property, color, cb); - // invoke cb in case of gradient - // see {@link CommonMethods#_initGradient} - if (color && color.colorStops && !(color instanceof fabric.Gradient)) { - cb(); - } - } - - return this; - }, - - setBackgroundColor: function (backgroundColor, callback, options) { - return this.__setBgOverlayColor('backgroundColor', backgroundColor, callback, options); - }, - - setOverlayColor: function (overlayColor, callback, options) { - return this.__setBgOverlayColor('overlayColor', overlayColor, callback, options); - }, - - /** - * patch serialization - from json - * background/overlay properties could be objects if parsed by this mixin or could be legacy values - * @private - * @param {String} property Property to set (backgroundImage, overlayImage, backgroundColor, overlayColor) - * @param {(Object|String)} value Value to set - * @param {Object} loaded Set loaded property to true if property is set - * @param {Object} callback Callback function to invoke after property is set - */ - __setBgOverlay: function (property, value, loaded, callback) { - var _this = this; - - if ((property === 'backgroundColor' || property === 'overlayColor') && - (value && typeof value === 'object' && value.type === 'rect')) { - fabric.util.enlivenObjects([value], function (enlivedObject) { - _this[property] = enlivedObject[0]; - loaded[property] = true; - callback && callback(); - }); - } - else { - ___setBgOverlay.call(this, property, value, loaded, callback); - } - }, - - /** - * patch serialization - to svg - * background/overlay properties could be objects if parsed by this mixin or could be legacy values - * @private - */ - _setSVGBgOverlayColor: function (markup, property, reviver) { - var filler = this[property + 'Color']; - if (filler && filler.isType && filler.isType('rect')) { - var excludeFromExport = filler.excludeFromExport || (this[property] && this[property].excludeFromExport); - if (filler && !excludeFromExport && filler.toSVG) { - markup.push(filler.toSVG(reviver)); - } - } - else { - __setSVGBgOverlayColor.call(this, markup, property, reviver); - } - }, - - /** - * @private - * @param {CanvasRenderingContext2D} ctx Context to render on - * @param {string} property 'background' or 'overlay' - */ - _renderBackgroundOrOverlay: function (ctx, property) { - var fill = this[property + 'Color'], object = this[property + 'Image'], - v = this.viewportTransform, needsVpt = this[property + 'Vpt']; - if (!fill && !object) { - return; - } - if (fill || object) { - ctx.save(); - if (needsVpt) { - ctx.transform(v[0], v[1], v[2], v[3], v[4], v[5]); - } - fill && fill.render(ctx); - object && object.render(ctx); - ctx.restore(); - } - }, - }); - - var _toObject = fabric.Object.prototype.toObject; - var __createBaseSVGMarkup = fabric.Object.prototype._createBaseSVGMarkup; - fabric.util.object.extend(fabric.Object.prototype, { - /** - * Indicates whether this object can be erased by {@link fabric.EraserBrush} - * @type boolean - * @default true - */ - erasable: true, - - /** - * - * @returns {fabric.Group | null} - */ - getEraser: function () { - return this.clipPath && this.clipPath.eraser ? this.clipPath : null; - }, - - /** - * Returns an object representation of an instance - * @param {Array} [propertiesToInclude] Any properties that you might want to additionally include in the output - * @return {Object} Object representation of an instance - */ - toObject: function (additionalProperties) { - return _toObject.call(this, ['erasable'].concat(additionalProperties)); - }, - - /** - * use to achieve erasing for svg - * credit: https://travishorn.com/removing-parts-of-shapes-in-svg-b539a89e5649 - * @param {Function} reviver - * @returns {string} markup - */ - eraserToSVG: function (options) { - var eraser = this.getEraser(); - if (eraser) { - var fill = eraser._objects[0].fill; - eraser._objects[0].fill = 'white'; - eraser.clipPathId = 'CLIPPATH_' + fabric.Object.__uid++; - var commons = [ - 'id="' + eraser.clipPathId + '"', - /*options.additionalTransform ? ' transform="' + options.additionalTransform + '" ' : ''*/ - ].join(' '); - var objectMarkup = ['', '', eraser.toSVG(options.reviver), '', '']; - eraser._objects[0].fill = fill; - return objectMarkup.join('\n'); - } - return ''; - }, - - /** - * use to achieve erasing for svg, override - * @param {string[]} objectMarkup - * @param {Object} options - * @returns - */ - _createBaseSVGMarkup: function (objectMarkup, options) { - var eraser = this.getEraser(); - if (eraser) { - var eraserMarkup = this.eraserToSVG(options); - this.clipPath = null; - var markup = __createBaseSVGMarkup.call(this, objectMarkup, options); - this.clipPath = eraser; - return [ - eraserMarkup, - markup.replace('>', 'mask="url(#' + eraser.clipPathId + ')" >') - ].join('\n'); - } - else { - return __createBaseSVGMarkup.call(this, objectMarkup, options); - } - } - }); - - var _groupToObject = fabric.Group.prototype.toObject; - fabric.util.object.extend(fabric.Group.prototype, { - /** - * Returns an object representation of an instance - * @param {Array} [propertiesToInclude] Any properties that you might want to additionally include in the output - * @return {Object} Object representation of an instance - */ - toObject: function (additionalProperties) { - return _groupToObject.call(this, ['eraser'].concat(additionalProperties)); - } - }); - - fabric.util.object.extend(fabric.Canvas.prototype, { - /** - * Used by {@link #renderAll} - * @returns boolean - */ - isErasing: function () { - return ( - this.isDrawingMode && - this.freeDrawingBrush && - this.freeDrawingBrush.type === 'eraser' && - this.freeDrawingBrush._isErasing - ); - }, - - /** - * While erasing, the brush is in charge of rendering the canvas - * It uses both layers to achieve diserd erasing effect - * - * @returns fabric.Canvas - */ - renderAll: function () { - if (this.contextTopDirty && !this._groupSelector && !this.isDrawingMode) { - this.clearContext(this.contextTop); - this.contextTopDirty = false; - } - // while erasing the brush is in charge of rendering the canvas so we return - if (this.isErasing()) { - this.freeDrawingBrush._render(); - return; - } - if (this.hasLostContext) { - this.renderTopLayer(this.contextTop); - } - var canvasToDrawOn = this.contextContainer; - this.renderCanvas(canvasToDrawOn, this._chooseObjectsToRender()); - return this; - } - }); - - - /** - * EraserBrush class - * Supports selective erasing meaning that only erasable objects are affected by the eraser brush. - * In order to support selective erasing all non erasable objects are rendered on the main/bottom ctx - * while the entire canvas is rendered on the top ctx. - * Canvas bakground/overlay image/color are handled as well. - * When erasing occurs, the path clips the top ctx and reveals the bottom ctx. - * This achieves the desired effect of seeming to erase only erasable objects. - * After erasing is done the created path is added to all intersected objects' `clipPath` property. - * - * - * @class fabric.EraserBrush - * @extends fabric.PencilBrush - */ - fabric.EraserBrush = fabric.util.createClass( - fabric.PencilBrush, - /** @lends fabric.EraserBrush.prototype */ { - type: 'eraser', - - /** - * Indicates that the ctx is ready and rendering can begin. - * Used to prevent a race condition caused by {@link fabric.EraserBrush#onMouseMove} firing before {@link fabric.EraserBrush#onMouseDown} has completed - * - * @private - */ - _ready: false, - - /** - * @private - */ - _drawOverlayOnTop: false, - - /** - * @private - */ - _isErasing: false, - - initialize: function (canvas) { - this.callSuper('initialize', canvas); - this._renderBound = this._render.bind(this); - this.renderAll = this.renderAll.bind(this); - }, - - /** - * Disable clip path drawing. - * Clip path must be handled differently when erasing. - * @private - */ - _drawClipPath: function () { - - }, - - /** - * Used to hide a drawable from the rendering process - * @param {fabric.Object} object - */ - hideObject: function (object) { - if (object) { - object._originalOpacity = object.opacity; - object.set({ opacity: 0 }); - } - }, - - /** - * Restores hiding an object - * {@link fabric.EraserBrush#hideObject} - * @param {fabric.Object} object - */ - restoreObjectVisibility: function (object) { - if (object && object._originalOpacity) { - object.set({ opacity: object._originalOpacity }); - object._originalOpacity = undefined; - } - }, - - /** - * Drawing Logic For background drawables: (`backgroundImage`, `backgroundColor`) - * 1. if erasable = true: - * we need to hide the drawable on the bottom ctx so when the brush is erasing it will clip the top ctx and reveal white space underneath - * 2. if erasable = false: - * we need to draw the drawable only on the bottom ctx so the brush won't affect it - * @param {'bottom' | 'top' | 'overlay'} layer - */ - prepareCanvasBackgroundForLayer: function (layer) { - if (layer === 'overlay') { - return; - } - var canvas = this.canvas; - var image = canvas.get('backgroundImage'); - var color = canvas.get('backgroundColor'); - var erasablesOnLayer = layer === 'top'; - if (image && image.erasable === !erasablesOnLayer) { - this.hideObject(image); - } - if (color && color.erasable === !erasablesOnLayer) { - this.hideObject(color); - } - }, - - /** - * Drawing Logic For overlay drawables (`overlayImage`, `overlayColor`) - * We must draw on top ctx to be on top of visible canvas - * 1. if erasable = true: - * we need to draw the drawable on the top ctx as a normal object - * 2. if erasable = false: - * we need to draw the drawable on top of the brush, - * this means we need to repaint for every stroke - * - * @param {'bottom' | 'top' | 'overlay'} layer - * @returns boolean render overlay above brush - */ - prepareCanvasOverlayForLayer: function (layer) { - var canvas = this.canvas; - var image = canvas.get('overlayImage'); - var color = canvas.get('overlayColor'); - if (layer === 'bottom') { - this.hideObject(image); - this.hideObject(color); - return false; - }; - var erasablesOnLayer = layer === 'top'; - var renderOverlayOnTop = (image && !image.erasable) || (color && !color.erasable); - if (image && image.erasable === !erasablesOnLayer) { - this.hideObject(image); - } - if (color && color.erasable === !erasablesOnLayer) { - this.hideObject(color); - } - return renderOverlayOnTop; - }, - - /** - * @private - */ - restoreCanvasDrawables: function () { - var canvas = this.canvas; - this.restoreObjectVisibility(canvas.get('backgroundImage')); - this.restoreObjectVisibility(canvas.get('backgroundColor')); - this.restoreObjectVisibility(canvas.get('overlayImage')); - this.restoreObjectVisibility(canvas.get('overlayColor')); - }, - - /** - * @private - * This is designed to support erasing a group with both erasable and non-erasable objects. - * Iterates over collections to allow nested selective erasing. - * Used by {@link fabric.EraserBrush#prepareCanvasObjectsForLayer} - * to prepare the bottom layer by hiding erasable nested objects - * - * @param {fabric.Collection} collection - */ - prepareCollectionTraversal: function (collection) { - var _this = this; - collection.forEachObject(function (obj) { - if (obj.forEachObject) { - _this.prepareCollectionTraversal(obj); - } - else { - if (obj.erasable) { - _this.hideObject(obj); - } - } - }); - }, - - /** - * @private - * Used by {@link fabric.EraserBrush#prepareCanvasObjectsForLayer} - * to reverse the action of {@link fabric.EraserBrush#prepareCollectionTraversal} - * - * @param {fabric.Collection} collection - */ - restoreCollectionTraversal: function (collection) { - var _this = this; - collection.forEachObject(function (obj) { - if (obj.forEachObject) { - _this.restoreCollectionTraversal(obj); - } - else { - _this.restoreObjectVisibility(obj); - } - }); - }, - - /** - * @private - * This is designed to support erasing a group with both erasable and non-erasable objects. - * - * @param {'bottom' | 'top' | 'overlay'} layer - */ - prepareCanvasObjectsForLayer: function (layer) { - if (layer !== 'bottom') { return; } - this.prepareCollectionTraversal(this.canvas); - }, - - /** - * @private - * @param {'bottom' | 'top' | 'overlay'} layer - */ - restoreCanvasObjectsFromLayer: function (layer) { - if (layer !== 'bottom') { return; } - this.restoreCollectionTraversal(this.canvas); - }, - - /** - * @private - * @param {'bottom' | 'top' | 'overlay'} layer - * @returns boolean render overlay above brush - */ - prepareCanvasForLayer: function (layer) { - this.prepareCanvasBackgroundForLayer(layer); - this.prepareCanvasObjectsForLayer(layer); - return this.prepareCanvasOverlayForLayer(layer); - }, - - /** - * @private - * @param {'bottom' | 'top' | 'overlay'} layer - */ - restoreCanvasFromLayer: function (layer) { - this.restoreCanvasDrawables(); - this.restoreCanvasObjectsFromLayer(layer); - }, - - /** - * Render all non-erasable objects on bottom layer with the exception of overlays to avoid being clipped by the brush. - * Groups are rendered for nested selective erasing, non-erasable objects are visible while erasable objects are not. - */ - renderBottomLayer: function () { - var canvas = this.canvas; - this.prepareCanvasForLayer('bottom'); - canvas.renderCanvas( - canvas.getContext(), - canvas.getObjects().filter(function (obj) { - return !obj.erasable || obj.isType('group'); - }) - ); - this.restoreCanvasFromLayer('bottom'); - }, - - /** - * 1. Render all objects on top layer, erasable and non-erasable - * This is important for cases such as overlapping objects, the background object erasable and the foreground object not erasable. - * 2. Render the brush - */ - renderTopLayer: function () { - var canvas = this.canvas, ctx = canvas.contextTop, inverted = this.clipPath.inverted; - this._drawOverlayOnTop = this.prepareCanvasForLayer('top'); - if (this.clipPath) { - var mainContext = canvas.getContext(); - canvas.renderCanvas( - mainContext, - canvas.getObjects() - ); - this.clipPath.set('inverted', !!inverted); - this.callSuper('_saveAndTransform', mainContext); - this.callSuper('_drawClipPath', mainContext); - mainContext.restore(); - this.clipPath.set('inverted', inverted); - } - canvas.renderCanvas( - ctx, - canvas.getObjects() - ); - this.clipPath.set('inverted', !!inverted); - this.callSuper('_render'); - this.clipPath.set('inverted', inverted); - this.restoreCanvasFromLayer('top'); - }, - - /** - * Render all non-erasable overlays on top of the brush so that they won't get erased - */ - renderOverlay: function () { - this.prepareCanvasForLayer('overlay'); - var canvas = this.canvas; - var ctx = canvas.contextTop; - this._saveAndTransform(ctx); - canvas._renderOverlay(ctx); - ctx.restore(); - this.restoreCanvasFromLayer('overlay'); - }, - - /** - * @extends @class fabric.BaseBrush - * @param {CanvasRenderingContext2D} ctx - */ - _saveAndTransform: function (ctx) { - this.callSuper('_saveAndTransform', ctx); - ctx.globalCompositeOperation = 'destination-out'; - }, - - /** - * We indicate {@link fabric.PencilBrush} to repaint itself if necessary - * @returns - */ - needsFullRender: function () { - return this.callSuper('needsFullRender') || this._drawOverlayOnTop || this.clipPath; - }, - - /** - * - * @param {fabric.Point} pointer - * @param {fabric.IEvent} options - * @returns - */ - onMouseDown: function (pointer, options) { - if (!this.canvas._isMainEvent(options.e)) { - return; - } - this._prepareForDrawing(pointer); - // capture coordinates immediately - // this allows to draw dots (when movement never occurs) - this._captureDrawingPath(pointer); - - this._isErasing = true; - this.canvas.fire('erasing:start'); - this._ready = true; - this._render(); - }, - - /** - * Rendering is done in 4 steps: - * 1. Draw all non-erasable objects on bottom ctx with the exception of overlays {@link fabric.EraserBrush#renderBottomLayer} - * 2. Draw all objects on top ctx including erasable drawables {@link fabric.EraserBrush#renderTopLayer} - * 3. Draw eraser {@link fabric.PencilBrush#_render} at {@link fabric.EraserBrush#renderTopLayer} - * 4. Draw non-erasable overlays {@link fabric.EraserBrush#renderOverlay} - * - * @param {fabric.Canvas} canvas - */ - _render: function () { - if (!this._ready) { - return; - } - this.isRendering = 1; - this.renderBottomLayer(); - this.renderTopLayer(); - this.renderOverlay(); - this.isRendering = 0; - }, - - /** - * @public - */ - renderAll: function () { - if (this._isErasing) { - if (this.isRendering) { - this.isRendering = fabric.util.requestAnimFrame(this._renderBound); - } - else { - this._render(); - } - return true; - } - return false; - }, - - /** - * Adds path to existing clipPath of object - * - * @param {fabric.Object} obj - * @param {fabric.Path} path - */ - _addPathToObjectEraser: function (obj, path) { - var clipObject; - var _this = this; - // object is collection, i.e group - if (obj.forEachObject) { - obj.forEachObject(function (_obj) { - if (_obj.erasable) { - _this._addPathToObjectEraser(_obj, path); - } - }); - return; - } - if (!obj.getEraser()) { - var size = obj._getNonTransformedDimensions(); - var rect = new fabric.Rect({ - width: size.x, - height: size.y, - clipPath: obj.clipPath, - originX: 'center', - originY: 'center' - }); - clipObject = new fabric.Group([rect], { - eraser: true - }); - } - else { - clipObject = obj.clipPath; - } - - path.clone(function (path) { - path.globalCompositeOperation = 'destination-out'; - // http://fabricjs.com/using-transformations - var desiredTransform = fabric.util.multiplyTransformMatrices( - fabric.util.invertTransform( - obj.calcTransformMatrix() - ), - path.calcTransformMatrix() - ); - fabric.util.applyTransformToObject(path, desiredTransform); - clipObject.addWithUpdate(path); - obj.set({ - clipPath: clipObject, - dirty: true - }); - }); - }, - - /** - * Add the eraser path to canvas drawables' clip paths - * - * @param {fabric.Canvas} source - * @param {fabric.Canvas} path - * @returns {Object} canvas drawables that were erased by the path - */ - applyEraserToCanvas: function (path) { - var canvas = this.canvas; - var drawables = {}; - [ - 'backgroundImage', - 'backgroundColor', - 'overlayImage', - 'overlayColor', - ].forEach(function (prop) { - var drawable = canvas[prop]; - if (drawable && drawable.erasable) { - this._addPathToObjectEraser(drawable, path); - drawables[prop] = drawable; - } - }, this); - return drawables; - }, - - /** - * On mouseup after drawing the path on contextTop canvas - * we use the points captured to create an new fabric path object - * and add it to every intersected erasable object. - */ - _finalizeAndAddPath: function () { - var ctx = this.canvas.contextTop, canvas = this.canvas; - ctx.closePath(); - if (this.decimate) { - this._points = this.decimatePoints(this._points, this.decimate); - } - - // clear - canvas.clearContext(canvas.contextTop); - this._isErasing = false; - - var pathData = this._points && this._points.length > 1 ? - this.convertPointsToSVGPath(this._points) : - null; - if (!pathData || this._isEmptySVGPath(pathData)) { - canvas.fire('erasing:end'); - // do not create 0 width/height paths, as they are - // rendered inconsistently across browsers - // Firefox 4, for example, renders a dot, - // whereas Chrome 10 renders nothing - canvas.requestRenderAll(); - return; - } - - var path = this.createPath(pathData); - canvas.fire('before:path:created', { path: path }); - - // finalize erasing - var drawables = this.applyEraserToCanvas(path); - var _this = this; - var targets = []; - canvas.forEachObject(function (obj) { - if (obj.erasable && obj.intersectsWithObject(path, true)) { - _this._addPathToObjectEraser(obj, path); - targets.push(obj); - } - }); - - canvas.fire('erasing:end', { path: path, targets: targets, drawables: drawables }); - - canvas.requestRenderAll(); - path.setCoords(); - this._resetShadow(); - - // fire event 'path' created - canvas.fire('path:created', { path: path }); - } - } - ); - - /** ERASER_END */ -})(); - From a3eb5d736a4b592281f212e6e681ae037669b20d Mon Sep 17 00:00:00 2001 From: ShaMan123 Date: Thu, 1 Jul 2021 22:02:56 +0300 Subject: [PATCH 19/37] Update CHANGELOG.md --- CHANGELOG.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index db02c253dbf..349b4cdf6f2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,7 +12,9 @@ - feat(fabric.PencilBrush): added `clipPath` property [#7175](https://github.com/fabricjs/fabric.js/pull/7175). - feat(fabric.PatternBrush): added `clipPath` property [#7175](https://github.com/fabricjs/fabric.js/pull/7175). - feat(fabric.SprayBrush): added `clipPath` property [#7175](https://github.com/fabricjs/fabric.js/pull/7175). +- feat(fabric.EraserBrush): added `clipPath` property [#7175](https://github.com/fabricjs/fabric.js/pull/7175). - Breaking: renamed `fabric.SprayBrush` `render` method to `renderChunk`. +- Breaking: renamed `fabric.EraserBrush` `render` method to `renderAll`. ## [4.5.1] From 5747e0279ef5a4b1c9467e8bd5895965ef9aab9a Mon Sep 17 00:00:00 2001 From: ShaMan123 Date: Thu, 1 Jul 2021 22:46:55 +0300 Subject: [PATCH 20/37] fix rendering overlay with clip path --- src/mixins/eraser_brush.mixin.js | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/mixins/eraser_brush.mixin.js b/src/mixins/eraser_brush.mixin.js index ae3cd9c33c3..01dc169c018 100644 --- a/src/mixins/eraser_brush.mixin.js +++ b/src/mixins/eraser_brush.mixin.js @@ -521,7 +521,10 @@ ctx, canvas.getObjects() ); - this.callSuper('_render'); + this._saveAndTransform(ctx); + this.render(ctx); + this.callSuper('_drawClipPath', ctx); + ctx.restore(); this.restoreCanvasFromLayer('top'); }, From 1ebccf471b35f75dcc4a9c353bc63947216ac4fd Mon Sep 17 00:00:00 2001 From: ShaMan123 Date: Thu, 1 Jul 2021 22:55:38 +0300 Subject: [PATCH 21/37] Update base_brush.class.js --- src/brushes/base_brush.class.js | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/brushes/base_brush.class.js b/src/brushes/base_brush.class.js index 05cf6ec3048..7b910e29c17 100644 --- a/src/brushes/base_brush.class.js +++ b/src/brushes/base_brush.class.js @@ -145,6 +145,10 @@ fabric.BaseBrush = fabric.util.createClass(/** @lends fabric.BaseBrush.prototype * @param {CanvasRenderingContext2D} ctx */ _drawClipPath: function (ctx) { + if (!this.clipPath) { + return; + } + this.clipPath.canvas = this.canvas; var v = fabric.util.invertTransform(this.canvas.viewportTransform); ctx.save(); ctx.transform(v[0], v[1], v[2], v[3], v[4], v[5]); From 368dd713afd18f5f6fead6f6a99e14f61d173091 Mon Sep 17 00:00:00 2001 From: ShaMan123 Date: Fri, 2 Jul 2021 07:38:00 +0300 Subject: [PATCH 22/37] Update eraser_brush.mixin.js --- src/mixins/eraser_brush.mixin.js | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/src/mixins/eraser_brush.mixin.js b/src/mixins/eraser_brush.mixin.js index 01dc169c018..fbdb13dad27 100644 --- a/src/mixins/eraser_brush.mixin.js +++ b/src/mixins/eraser_brush.mixin.js @@ -476,12 +476,15 @@ renderBottomLayer: function () { var canvas = this.canvas; this.prepareCanvasForLayer('bottom'); + var group = new fabric.Group(canvas.getObjects().filter(function (obj) { + return !obj.erasable || obj.isType('group'); + }), { clipPath: this.clipPath }); + group.canvas = this.canvas; canvas.renderCanvas( canvas.getContext(), - canvas.getObjects().filter(function (obj) { - return !obj.erasable || obj.isType('group'); - }) + [group] ); + group.ungroupOnCanvas() this.restoreCanvasFromLayer('bottom'); }, @@ -492,6 +495,7 @@ * @private */ renderClippedOutLayer: function () { + return if (!this.clipPath) { return; } From 85a63171c0cd0d81fb924c8f49391f921ec3bbf3 Mon Sep 17 00:00:00 2001 From: ShaMan123 Date: Sun, 16 Jan 2022 15:47:58 +0200 Subject: [PATCH 23/37] Update base_brush.class.js --- src/brushes/base_brush.class.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/brushes/base_brush.class.js b/src/brushes/base_brush.class.js index ae6441fb9aa..92681c297cf 100644 --- a/src/brushes/base_brush.class.js +++ b/src/brushes/base_brush.class.js @@ -182,7 +182,7 @@ fabric.BaseBrush = fabric.util.createClass(/** @lends fabric.BaseBrush.prototype * @private * @param {CanvasRenderingContext2D} ctx */ - render: function (ctx /*eslint-disable-line no-unused-vars*/) { + render: function (ctx /* eslint-disable-line no-unused-vars */) { }, From 2442eedcae65686fe16d35505da70fa758d94113 Mon Sep 17 00:00:00 2001 From: ShaMan123 Date: Mon, 17 Jan 2022 14:27:29 +0200 Subject: [PATCH 24/37] Update pencil_brush.class.js --- src/brushes/pencil_brush.class.js | 1 - 1 file changed, 1 deletion(-) diff --git a/src/brushes/pencil_brush.class.js b/src/brushes/pencil_brush.class.js index 5e6b31170cb..7d806652740 100644 --- a/src/brushes/pencil_brush.class.js +++ b/src/brushes/pencil_brush.class.js @@ -176,7 +176,6 @@ p1 = this._points[0], p2 = this._points[1]; ctx = ctx || this.canvas.contextTop; - this._saveAndTransform(ctx); ctx.beginPath(); //if we only have 2 points in the path and they are the same //it means that the user only clicked the canvas without moving the mouse From f927143410361d876a9db73348e0b89762cc110c Mon Sep 17 00:00:00 2001 From: ShaMan123 Date: Thu, 17 Feb 2022 13:33:56 +0200 Subject: [PATCH 25/37] Update base_brush.class.js --- src/brushes/base_brush.class.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/brushes/base_brush.class.js b/src/brushes/base_brush.class.js index d40d53c95e3..026765f8c20 100644 --- a/src/brushes/base_brush.class.js +++ b/src/brushes/base_brush.class.js @@ -150,7 +150,7 @@ fabric.BaseBrush = fabric.util.createClass(/** @lends fabric.BaseBrush.prototype var v = fabric.util.invertTransform(this.canvas.viewportTransform); ctx.save(); ctx.transform(v[0], v[1], v[2], v[3], v[4], v[5]); - fabric.Object.prototype._drawClipPath.call(this, ctx); + fabric.Object.prototype._drawClipPath.call(this, ctx, this.clipPath); ctx.restore(); }, From 8e20486c5183d2d117681516118a72118b536396 Mon Sep 17 00:00:00 2001 From: ShaMan123 Date: Tue, 26 Apr 2022 14:14:49 +0300 Subject: [PATCH 26/37] update code to master --- src/brushes/base_brush.class.js | 35 +++++++++++++++++-------------- src/brushes/circle_brush.class.js | 2 +- src/brushes/pencil_brush.class.js | 2 +- src/brushes/spray_brush.class.js | 2 +- 4 files changed, 22 insertions(+), 19 deletions(-) diff --git a/src/brushes/base_brush.class.js b/src/brushes/base_brush.class.js index 026765f8c20..06f8d4ae7a8 100644 --- a/src/brushes/base_brush.class.js +++ b/src/brushes/base_brush.class.js @@ -133,24 +133,26 @@ fabric.BaseBrush = fabric.util.createClass(/** @lends fabric.BaseBrush.prototype /** * @private * @param {CanvasRenderingContext2D} ctx + * @param {fabric.Object} clipPath */ - drawClipPathOnCache: function (ctx) { - fabric.Object.prototype.drawClipPathOnCache.call(this, ctx); + drawClipPathOnCache: function (ctx, clipPath) { + fabric.Object.prototype.drawClipPathOnCache.call(this, ctx, clipPath); }, /** * @private * @param {CanvasRenderingContext2D} ctx + * @param {fabric.Object} clipPath */ - _drawClipPath: function (ctx) { - if (!this.clipPath) { + _drawClipPath: function (ctx, clipPath) { + if (!clipPath) { return; } - this.clipPath.canvas = this.canvas; + clipPath.canvas = this.canvas; var v = fabric.util.invertTransform(this.canvas.viewportTransform); ctx.save(); ctx.transform(v[0], v[1], v[2], v[3], v[4], v[5]); - fabric.Object.prototype._drawClipPath.call(this, ctx, this.clipPath); + fabric.Object.prototype._drawClipPath.call(this, ctx, clipPath); ctx.restore(); }, @@ -161,20 +163,21 @@ fabric.BaseBrush = fabric.util.createClass(/** @lends fabric.BaseBrush.prototype */ _addClipPathToResult: function (result) { if (!this.clipPath) { - return; + return Promise.resolve(); } var t = result.calcTransformMatrix(); if (!this.clipPath.absolutePositioned) { t = fabric.util.multiplyTransformMatrices(this.canvas.viewportTransform, t); } - this.clipPath.clone(function (clipPath) { - var desiredTransform = fabric.util.multiplyTransformMatrices( - fabric.util.invertTransform(t), - clipPath.calcTransformMatrix() - ); - fabric.util.applyTransformToObject(clipPath, desiredTransform); - result.set('clipPath', clipPath); - }, ['inverted']); + return this.clipPath.clone(['inverted']) + .then(function (clipPath) { + var desiredTransform = fabric.util.multiplyTransformMatrices( + fabric.util.invertTransform(t), + clipPath.calcTransformMatrix() + ); + fabric.util.applyTransformToObject(clipPath, desiredTransform); + result.set('clipPath', clipPath); + }); }, /** @@ -194,7 +197,7 @@ fabric.BaseBrush = fabric.util.createClass(/** @lends fabric.BaseBrush.prototype var ctx = this.canvas.contextTop; this._saveAndTransform(ctx); this.render(ctx); - this._drawClipPath(ctx); + this._drawClipPath(ctx, this.clipPath); ctx.restore(); }, diff --git a/src/brushes/circle_brush.class.js b/src/brushes/circle_brush.class.js index d59d0e3f4e4..9f47b0f3d2c 100644 --- a/src/brushes/circle_brush.class.js +++ b/src/brushes/circle_brush.class.js @@ -30,7 +30,7 @@ fabric.CircleBrush = fabric.util.createClass(fabric.BaseBrush, /** @lends fabric ctx = this.canvas.contextTop; this._saveAndTransform(ctx); this.dot(ctx, point); - this._drawClipPath(ctx); + this._drawClipPath(ctx, this.clipPath); ctx.restore(); }, diff --git a/src/brushes/pencil_brush.class.js b/src/brushes/pencil_brush.class.js index 7d806652740..654703ef11b 100644 --- a/src/brushes/pencil_brush.class.js +++ b/src/brushes/pencil_brush.class.js @@ -98,7 +98,7 @@ } this.oldEnd = this._drawSegment(ctx, points[length - 2], points[length - 1], true); ctx.stroke(); - this._drawClipPath(ctx); + this._drawClipPath(ctx, this.clipPath); ctx.restore(); } } diff --git a/src/brushes/spray_brush.class.js b/src/brushes/spray_brush.class.js index 3fddc3580ee..c4ef9c6085a 100644 --- a/src/brushes/spray_brush.class.js +++ b/src/brushes/spray_brush.class.js @@ -183,7 +183,7 @@ fabric.SprayBrush = fabric.util.createClass( fabric.BaseBrush, /** @lends fabric var ctx = this.canvas.contextTop; this._saveAndTransform(ctx); this._renderChunk(ctx, sprayChunk); - this._drawClipPath(ctx); + this._drawClipPath(ctx, this.clipPath); ctx.restore(); }, From a8c1818f6d0e915ab64b745312ff01e5e15d9309 Mon Sep 17 00:00:00 2001 From: ShaMan123 Date: Tue, 26 Apr 2022 14:36:57 +0300 Subject: [PATCH 27/37] async --- src/brushes/base_brush.class.js | 5 ++--- src/brushes/circle_brush.class.js | 24 ++++++++++++------------ src/brushes/pattern_brush.class.js | 4 ++-- src/brushes/pencil_brush.class.js | 26 ++++++++++++-------------- src/brushes/spray_brush.class.js | 27 ++++++++++++++------------- src/mixins/eraser_brush.mixin.js | 14 +++++++------- 6 files changed, 49 insertions(+), 51 deletions(-) diff --git a/src/brushes/base_brush.class.js b/src/brushes/base_brush.class.js index 06f8d4ae7a8..1fe51143278 100644 --- a/src/brushes/base_brush.class.js +++ b/src/brushes/base_brush.class.js @@ -204,10 +204,9 @@ fabric.BaseBrush = fabric.util.createClass(/** @lends fabric.BaseBrush.prototype /** * Removes brush shadow styles * @private + * @param {CanvasRenderingContext2D} ctx */ - _resetShadow: function() { - var ctx = this.canvas.contextTop; - + _resetShadow: function(ctx) { ctx.shadowColor = ''; ctx.shadowBlur = ctx.shadowOffsetX = ctx.shadowOffsetY = 0; }, diff --git a/src/brushes/circle_brush.class.js b/src/brushes/circle_brush.class.js index 9f47b0f3d2c..c88f44d93a0 100644 --- a/src/brushes/circle_brush.class.js +++ b/src/brushes/circle_brush.class.js @@ -105,18 +105,18 @@ fabric.CircleBrush = fabric.util.createClass(fabric.BaseBrush, /** @lends fabric circles.push(circle); } - var group = new fabric.Group(circles); - group.canvas = this.canvas; - this._addClipPathToResult(group); - - this.canvas.fire('before:path:created', { path: group }); - this.canvas.add(group); - this.canvas.fire('path:created', { path: group }); - - this.canvas.clearContext(this.canvas.contextTop); - this._resetShadow(); - this.canvas.renderOnAddRemove = originalRenderOnAddRemove; - this.canvas.requestRenderAll(); + var canvas = this.canvas, ctx = canvas.contextTop; + var group = new fabric.Group(circles, { canvas: canvas }); + this._addClipPathToResult(group) + .then(function () { + canvas.fire('before:path:created', { path: group }); + canvas.add(group); + canvas.fire('path:created', { path: group }); + canvas.clearContext(ctx); + this._resetShadow(ctx); + canvas.renderOnAddRemove = originalRenderOnAddRemove; + canvas.requestRenderAll(); + }.bind(this)); }, /** diff --git a/src/brushes/pattern_brush.class.js b/src/brushes/pattern_brush.class.js index 5d5fa0d842c..83aaa21020c 100644 --- a/src/brushes/pattern_brush.class.js +++ b/src/brushes/pattern_brush.class.js @@ -47,8 +47,8 @@ fabric.PatternBrush = fabric.util.createClass(fabric.PencilBrush, /** @lends fab /** * Creates path */ - createPath: function(pathData) { - var path = this.callSuper('createPath', pathData), + createPath: async function(pathData) { + var path = await this.callSuper('createPath', pathData), topLeft = path._getLeftTopCoords().scalarAdd(path.strokeWidth / 2); path.stroke = new fabric.Pattern({ diff --git a/src/brushes/pencil_brush.class.js b/src/brushes/pencil_brush.class.js index 654703ef11b..7ff6d77c1df 100644 --- a/src/brushes/pencil_brush.class.js +++ b/src/brushes/pencil_brush.class.js @@ -229,7 +229,7 @@ * @param {(string|number)[][]} pathData Path data * @return {fabric.Path} Path to add on canvas */ - createPath: function(pathData) { + createPath: async function(pathData) { var path = new fabric.Path(pathData, { fill: null, stroke: this.color, @@ -243,7 +243,7 @@ this.shadow.affectStroke = true; path.shadow = new fabric.Shadow(this.shadow); } - this._addClipPathToResult(path); + await this._addClipPathToResult(path); return path; }, @@ -277,8 +277,8 @@ * we use the points captured to create an new fabric path object * and add it to the fabric canvas. */ - _finalizeAndAddPath: function() { - var ctx = this.canvas.contextTop; + _finalizeAndAddPath: async function() { + var canvas = this.canvas, ctx = canvas.contextTop; ctx.closePath(); if (this.decimate) { this._points = this.decimatePoints(this._points, this.decimate); @@ -289,21 +289,19 @@ // rendered inconsistently across browsers // Firefox 4, for example, renders a dot, // whereas Chrome 10 renders nothing - this.canvas.requestRenderAll(); + canvas.requestRenderAll(); return; } - - var path = this.createPath(pathData); - this.canvas.clearContext(this.canvas.contextTop); - this.canvas.fire('before:path:created', { path: path }); - this.canvas.add(path); - this.canvas.requestRenderAll(); + var path = await this.createPath(pathData); + canvas.clearContext(ctx); + canvas.fire('before:path:created', { path: path }); + canvas.add(path); + canvas.requestRenderAll(); path.setCoords(); - this._resetShadow(); - + this._resetShadow(ctx); // fire event 'path' created - this.canvas.fire('path:created', { path: path }); + canvas.fire('path:created', { path: path }); } }); })(); diff --git a/src/brushes/spray_brush.class.js b/src/brushes/spray_brush.class.js index c4ef9c6085a..d60b1ef2169 100644 --- a/src/brushes/spray_brush.class.js +++ b/src/brushes/spray_brush.class.js @@ -84,9 +84,10 @@ fabric.SprayBrush = fabric.util.createClass( fabric.BaseBrush, /** @lends fabric /** * Invoked on mouse up */ - onMouseUp: function() { - var originalRenderOnAddRemove = this.canvas.renderOnAddRemove; - this.canvas.renderOnAddRemove = false; + onMouseUp: function () { + var canvas = this.canvas, ctx = canvas.contextTop; + var originalRenderOnAddRemove = canvas.renderOnAddRemove; + canvas.renderOnAddRemove = false; var rects = []; @@ -114,16 +115,16 @@ fabric.SprayBrush = fabric.util.createClass( fabric.BaseBrush, /** @lends fabric var group = new fabric.Group(rects); this.shadow && group.set('shadow', new fabric.Shadow(this.shadow)); - this._addClipPathToResult(group); - - this.canvas.fire('before:path:created', { path: group }); - this.canvas.add(group); - this.canvas.fire('path:created', { path: group }); - - this.canvas.clearContext(this.canvas.contextTop); - this._resetShadow(); - this.canvas.renderOnAddRemove = originalRenderOnAddRemove; - this.canvas.requestRenderAll(); + this._addClipPathToResult(group) + .then(function () { + canvas.fire('before:path:created', { path: group }); + canvas.add(group); + canvas.fire('path:created', { path: group }); + canvas.clearContext(ctx); + this._resetShadow(ctx); + canvas.renderOnAddRemove = originalRenderOnAddRemove; + canvas.requestRenderAll(); + }.bind(this)); }, /** diff --git a/src/mixins/eraser_brush.mixin.js b/src/mixins/eraser_brush.mixin.js index 4400e5aca41..eade1a361e1 100644 --- a/src/mixins/eraser_brush.mixin.js +++ b/src/mixins/eraser_brush.mixin.js @@ -557,8 +557,8 @@ * @return {fabric.Path} Path to add on canvas * @returns */ - createPath: function (pathData) { - var path = this.callSuper('createPath', pathData); + createPath: async function (pathData) { + var path = await this.callSuper('createPath', pathData); path.globalCompositeOperation = this.inverted ? 'source-over' : 'destination-out'; path.stroke = this.inverted ? 'white' : 'black'; return path; @@ -714,15 +714,15 @@ * we use the points captured to create an new fabric path object * and add it to every intersected erasable object. */ - _finalizeAndAddPath: function () { - var ctx = this.canvas.contextTop, canvas = this.canvas; + _finalizeAndAddPath: async function () { + var canvas = this.canvas, ctx = canvas.contextTop; ctx.closePath(); if (this.decimate) { this._points = this.decimatePoints(this._points, this.decimate); } // clear - canvas.clearContext(canvas.contextTop); + canvas.clearContext(ctx); this._isErasing = false; var pathData = this._points && this._points.length > 1 ? @@ -738,7 +738,7 @@ return; } - var path = this.createPath(pathData); + var path = await this.createPath(pathData); // needed for `intersectsWithObject` path.setCoords(); // commense event sequence @@ -765,7 +765,7 @@ })); canvas.requestRenderAll(); - _this._resetShadow(); + _this._resetShadow(ctx); // fire event 'path' created canvas.fire('path:created', { path: path }); From fddbae79deaf146f48be32e14cdc112dba723458 Mon Sep 17 00:00:00 2001 From: ShaMan123 Date: Tue, 26 Apr 2022 14:39:52 +0300 Subject: [PATCH 28/37] async --- src/brushes/circle_brush.class.js | 28 ++++++++++++++++------------ src/brushes/spray_brush.class.js | 22 ++++++++++++---------- 2 files changed, 28 insertions(+), 22 deletions(-) diff --git a/src/brushes/circle_brush.class.js b/src/brushes/circle_brush.class.js index c88f44d93a0..2e383125bda 100644 --- a/src/brushes/circle_brush.class.js +++ b/src/brushes/circle_brush.class.js @@ -84,7 +84,14 @@ fabric.CircleBrush = fabric.util.createClass(fabric.BaseBrush, /** @lends fabric /** * Invoked on mouse up */ - onMouseUp: function() { + onMouseUp: function () { + this._finalizeAndAddPath(); + }, + + /** + * @private + */ + _finalizeAndAddPath: async function () { var originalRenderOnAddRemove = this.canvas.renderOnAddRemove, i, len; this.canvas.renderOnAddRemove = false; @@ -102,21 +109,18 @@ fabric.CircleBrush = fabric.util.createClass(fabric.BaseBrush, /** @lends fabric }); this.shadow && (circle.shadow = new fabric.Shadow(this.shadow)); - circles.push(circle); } var canvas = this.canvas, ctx = canvas.contextTop; var group = new fabric.Group(circles, { canvas: canvas }); - this._addClipPathToResult(group) - .then(function () { - canvas.fire('before:path:created', { path: group }); - canvas.add(group); - canvas.fire('path:created', { path: group }); - canvas.clearContext(ctx); - this._resetShadow(ctx); - canvas.renderOnAddRemove = originalRenderOnAddRemove; - canvas.requestRenderAll(); - }.bind(this)); + await this._addClipPathToResult(group); + canvas.fire('before:path:created', { path: group }); + canvas.add(group); + canvas.fire('path:created', { path: group }); + canvas.clearContext(ctx); + this._resetShadow(ctx); + canvas.renderOnAddRemove = originalRenderOnAddRemove; + canvas.requestRenderAll(); }, /** diff --git a/src/brushes/spray_brush.class.js b/src/brushes/spray_brush.class.js index d60b1ef2169..7556df43cab 100644 --- a/src/brushes/spray_brush.class.js +++ b/src/brushes/spray_brush.class.js @@ -85,6 +85,10 @@ fabric.SprayBrush = fabric.util.createClass( fabric.BaseBrush, /** @lends fabric * Invoked on mouse up */ onMouseUp: function () { + this._finalizeAndAddPath(); + }, + + _finalizeAndAddPath: async function () { var canvas = this.canvas, ctx = canvas.contextTop; var originalRenderOnAddRemove = canvas.renderOnAddRemove; canvas.renderOnAddRemove = false; @@ -115,16 +119,14 @@ fabric.SprayBrush = fabric.util.createClass( fabric.BaseBrush, /** @lends fabric var group = new fabric.Group(rects); this.shadow && group.set('shadow', new fabric.Shadow(this.shadow)); - this._addClipPathToResult(group) - .then(function () { - canvas.fire('before:path:created', { path: group }); - canvas.add(group); - canvas.fire('path:created', { path: group }); - canvas.clearContext(ctx); - this._resetShadow(ctx); - canvas.renderOnAddRemove = originalRenderOnAddRemove; - canvas.requestRenderAll(); - }.bind(this)); + await this._addClipPathToResult(group); + canvas.fire('before:path:created', { path: group }); + canvas.add(group); + canvas.fire('path:created', { path: group }); + canvas.clearContext(ctx); + this._resetShadow(ctx); + canvas.renderOnAddRemove = originalRenderOnAddRemove; + canvas.requestRenderAll(); }, /** From c36649f49d9b36e3d97075e207619d9cfc1d51d0 Mon Sep 17 00:00:00 2001 From: ShaMan123 Date: Tue, 26 Apr 2022 14:50:36 +0300 Subject: [PATCH 29/37] rename: BaseBrush `render` <-> `_render` now conforms with Object --- src/brushes/base_brush.class.js | 6 +++--- src/brushes/circle_brush.class.js | 4 ++-- src/brushes/pencil_brush.class.js | 6 +++--- src/brushes/spray_brush.class.js | 2 +- src/canvas.class.js | 2 +- src/mixins/eraser_brush.mixin.js | 10 +++++----- test/unit/canvas_events.js | 16 ++++++++-------- 7 files changed, 23 insertions(+), 23 deletions(-) diff --git a/src/brushes/base_brush.class.js b/src/brushes/base_brush.class.js index 1fe51143278..33874f7c03e 100644 --- a/src/brushes/base_brush.class.js +++ b/src/brushes/base_brush.class.js @@ -185,7 +185,7 @@ fabric.BaseBrush = fabric.util.createClass(/** @lends fabric.BaseBrush.prototype * @private * @param {CanvasRenderingContext2D} ctx */ - render: function (ctx /* eslint-disable-line no-unused-vars */) { + _render: function (ctx /* eslint-disable-line no-unused-vars */) { }, @@ -193,10 +193,10 @@ fabric.BaseBrush = fabric.util.createClass(/** @lends fabric.BaseBrush.prototype * Render the full state of the brush * @private */ - _render: function () { + render: function () { var ctx = this.canvas.contextTop; this._saveAndTransform(ctx); - this.render(ctx); + this._render(ctx); this._drawClipPath(ctx, this.clipPath); ctx.restore(); }, diff --git a/src/brushes/circle_brush.class.js b/src/brushes/circle_brush.class.js index 2e383125bda..baffb6b2b8d 100644 --- a/src/brushes/circle_brush.class.js +++ b/src/brushes/circle_brush.class.js @@ -56,7 +56,7 @@ fabric.CircleBrush = fabric.util.createClass(fabric.BaseBrush, /** @lends fabric * @private * @param {CanvasRenderingContext2D} ctx */ - render: function (ctx) { + _render: function (ctx) { var i, len, points = this.points; for (i = 0, len = points.length; i < len; i++) { this.dot(ctx, points[i]); @@ -74,7 +74,7 @@ fabric.CircleBrush = fabric.util.createClass(fabric.BaseBrush, /** @lends fabric if (this.needsFullRender()) { this.canvas.clearContext(this.canvas.contextTop); this.addPoint(pointer); - this._render(); + this.render(); } else { this.drawDot(pointer); diff --git a/src/brushes/pencil_brush.class.js b/src/brushes/pencil_brush.class.js index 7ff6d77c1df..8fb24c5cfe9 100644 --- a/src/brushes/pencil_brush.class.js +++ b/src/brushes/pencil_brush.class.js @@ -66,7 +66,7 @@ // capture coordinates immediately // this allows to draw dots (when movement never occurs) this._captureDrawingPath(pointer); - this._render(); + this.render(); }, /** @@ -86,7 +86,7 @@ // redraw curve // clear top canvas this.canvas.clearContext(this.canvas.contextTop); - this._render(); + this.render(); } else { var points = this._points, length = points.length, ctx = this.canvas.contextTop; @@ -171,7 +171,7 @@ * @private * @param {CanvasRenderingContext2D} [ctx] */ - render: function(ctx) { + _render: function(ctx) { var i, len, p1 = this._points[0], p2 = this._points[1]; diff --git a/src/brushes/spray_brush.class.js b/src/brushes/spray_brush.class.js index 7556df43cab..f342deac814 100644 --- a/src/brushes/spray_brush.class.js +++ b/src/brushes/spray_brush.class.js @@ -194,7 +194,7 @@ fabric.SprayBrush = fabric.util.createClass( fabric.BaseBrush, /** @lends fabric * Render all spray chunks * @param {CanvasRenderingContext2D} ctx */ - render: function(ctx) { + _render: function(ctx) { var i, ilen; for (i = 0, ilen = this.sprayChunks.length; i < ilen; i++) { this._renderChunk(ctx, this.sprayChunks[i]); diff --git a/src/canvas.class.js b/src/canvas.class.js index 4fe6fa45601..2e0e34a4f44 100644 --- a/src/canvas.class.js +++ b/src/canvas.class.js @@ -479,7 +479,7 @@ renderTopLayer: function(ctx) { ctx.save(); if (this.isDrawingMode && this._isCurrentlyDrawing) { - this.freeDrawingBrush && this.freeDrawingBrush._render(); + this.freeDrawingBrush && this.freeDrawingBrush.render(); this.contextTopDirty = true; } // we render the top context - last object diff --git a/src/mixins/eraser_brush.mixin.js b/src/mixins/eraser_brush.mixin.js index eade1a361e1..52736b214b1 100644 --- a/src/mixins/eraser_brush.mixin.js +++ b/src/mixins/eraser_brush.mixin.js @@ -309,7 +309,7 @@ */ _renderOverlay: function (ctx) { __renderOverlay.call(this, ctx); - this.isErasing() && this.freeDrawingBrush._render(); + this.isErasing() && this.freeDrawingBrush.render(); } }); @@ -523,7 +523,7 @@ this.preparePattern(); this._isErasing = true; this.canvas.fire('erasing:start'); - this._render(); + this.render(); }, /** @@ -532,15 +532,15 @@ * 2. Render brush with canvas pattern on top context * */ - _render: function () { + render: function () { var ctx; // clip canvas ctx = this.canvas.getContext(); - this.callSuper('_render', ctx); + this.callSuper('render', ctx); // render brush and mask it with pattern ctx = this.canvas.contextTop; this.canvas.clearContext(ctx); - this.callSuper('_render', ctx); + this.callSuper('render', ctx); ctx.save(); var t = this.canvas.getRetinaScaling(), s = 1 / t; ctx.scale(s, s); diff --git a/test/unit/canvas_events.js b/test/unit/canvas_events.js index 6b89d6b56f4..60786feec97 100644 --- a/test/unit/canvas_events.js +++ b/test/unit/canvas_events.js @@ -291,16 +291,16 @@ QUnit.test('setDimensions and active brush', function(assert) { var prepareFor = false; var rendered = false; - var canva = new fabric.Canvas(null, { width: 500, height: 500 }); + var canvas = new fabric.Canvas(null, { width: 500, height: 500 }); var brush = new fabric.PencilBrush({ color: 'red', width: 4 }); - canva.isDrawingMode = true; - canva.freeDrawingBrush = brush; - canva._isCurrentlyDrawing = true; - brush._render = function() { rendered = true; }; + canvas.isDrawingMode = true; + canvas.freeDrawingBrush = brush; + canvas._isCurrentlyDrawing = true; + brush.render = function() { rendered = true; }; brush._setBrushStyles = function() { prepareFor = true; }; - canva.setDimensions({ width: 200, height: 200 }); - canva.renderAll(); - assert.equal(rendered, true, 'the brush called the _render method'); + canvas.setDimensions({ width: 200, height: 200 }); + canvas.renderAll(); + assert.equal(rendered, true, 'the brush called the render method'); assert.equal(prepareFor, true, 'the brush called the _setBrushStyles method'); }); From 2d4aef7d3d643fde9288273e4ac9f13cf0da0a32 Mon Sep 17 00:00:00 2001 From: ShaMan123 Date: Tue, 26 Apr 2022 14:57:23 +0300 Subject: [PATCH 30/37] lint --- .eslintrc.json | 3 +++ src/brushes/base_brush.class.js | 2 +- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/.eslintrc.json b/.eslintrc.json index f3d848c6348..22b337c2af2 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -2,6 +2,9 @@ "env": { "browser": true }, + "parserOptions": { + "ecmaVersion": 2017 + }, "globals": { "Promise": true, "define": true, diff --git a/src/brushes/base_brush.class.js b/src/brushes/base_brush.class.js index 33874f7c03e..b909464a93c 100644 --- a/src/brushes/base_brush.class.js +++ b/src/brushes/base_brush.class.js @@ -185,7 +185,7 @@ fabric.BaseBrush = fabric.util.createClass(/** @lends fabric.BaseBrush.prototype * @private * @param {CanvasRenderingContext2D} ctx */ - _render: function (ctx /* eslint-disable-line no-unused-vars */) { + _render: function (ctx) { /* eslint-disable-line no-unused-vars */ }, From 126514bb5112711ceeeae2dd1f8be9ba1c5c13ea Mon Sep 17 00:00:00 2001 From: ShaMan123 Date: Tue, 26 Apr 2022 15:03:46 +0300 Subject: [PATCH 31/37] async test --- test/unit/brushes.js | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/test/unit/brushes.js b/test/unit/brushes.js index 4efd687e84d..8a4691e4dd2 100644 --- a/test/unit/brushes.js +++ b/test/unit/brushes.js @@ -115,7 +115,8 @@ ); assert.equal(brush._points.length, 4, '2 points have been discarded'); }); - QUnit.test('fabric pencil brush multiple points not discarded', function(assert) { + QUnit.test('fabric pencil brush multiple points not discarded', function (assert) { + assert.expect(4); var fireBeforePathCreatedEvent = false; var firePathCreatedEvent = false; var added = null; @@ -125,6 +126,12 @@ canvas.on('path:created', function(opt) { firePathCreatedEvent = true; added = opt.path; + assert.equal(fireBeforePathCreatedEvent, true, 'before:path:created event is fired'); + assert.equal(firePathCreatedEvent, true, 'path:created event is fired'); + assert.ok(added instanceof fabric.Path, 'a path is added'); + assert.ok(added.path.length, 6, 'path has 6 steps'); + + canvas.off(); }); var brush = new fabric.PencilBrush(canvas); var pointer = canvas.getPointer({ clientX: 10, clientY: 10}); @@ -136,11 +143,6 @@ brush.onMouseMove(pointer2, { e: {} }); brush.onMouseMove(pointer3, { e: {} }); brush.onMouseUp({ e: {} }); - assert.equal(fireBeforePathCreatedEvent, true, 'before:path:created event is fired'); - assert.equal(firePathCreatedEvent, true, 'path:created event is fired'); - assert.ok(added instanceof fabric.Path, 'a path is added'); - assert.ok(added.path.length, 6, 'path has 6 steps'); - canvas.off(); }); }); }); From 60b58294f4b94ce22fc18c920ada8e6f326d346e Mon Sep 17 00:00:00 2001 From: ShaMan123 Date: Tue, 26 Apr 2022 15:10:35 +0300 Subject: [PATCH 32/37] Update base_brush.class.js --- src/brushes/base_brush.class.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/brushes/base_brush.class.js b/src/brushes/base_brush.class.js index b909464a93c..40f22a91605 100644 --- a/src/brushes/base_brush.class.js +++ b/src/brushes/base_brush.class.js @@ -185,7 +185,7 @@ fabric.BaseBrush = fabric.util.createClass(/** @lends fabric.BaseBrush.prototype * @private * @param {CanvasRenderingContext2D} ctx */ - _render: function (ctx) { /* eslint-disable-line no-unused-vars */ + _render: function (ctx) { // eslint-disable-line no-unused-vars }, From 3f0f328f5e76e566bfb2a901f628355dad1bb24c Mon Sep 17 00:00:00 2001 From: ShaMan123 Date: Thu, 12 May 2022 09:20:58 +0300 Subject: [PATCH 33/37] fix(): `absolutePositioned` --- src/brushes/base_brush.class.js | 26 ++++++++++++-------------- 1 file changed, 12 insertions(+), 14 deletions(-) diff --git a/src/brushes/base_brush.class.js b/src/brushes/base_brush.class.js index 40f22a91605..c6caee4fe79 100644 --- a/src/brushes/base_brush.class.js +++ b/src/brushes/base_brush.class.js @@ -66,6 +66,7 @@ fabric.BaseBrush = fabric.util.createClass(/** @lends fabric.BaseBrush.prototype /** * Same as fabric.Object `clipPath` property. * The clip path is positioned relative to the top left corner of the viewport. + * The `absolutePositioned` property renders the clip path w/o viewport transform. */ clipPath: undefined, @@ -88,7 +89,7 @@ fabric.BaseBrush = fabric.util.createClass(/** @lends fabric.BaseBrush.prototype * @param {RenderingContext2d} ctx context to render on * @private */ - _saveAndTransform: function(ctx) { + _saveAndTransform: function (ctx) { var v = this.canvas.viewportTransform; ctx.save(); ctx.transform(v[0], v[1], v[2], v[3], v[4], v[5]); @@ -98,15 +99,15 @@ fabric.BaseBrush = fabric.util.createClass(/** @lends fabric.BaseBrush.prototype * Sets brush shadow styles * @private */ - _setShadow: function() { + _setShadow: function () { if (!this.shadow) { return; } var canvas = this.canvas, - shadow = this.shadow, - ctx = canvas.contextTop, - zoom = canvas.getZoom(); + shadow = this.shadow, + ctx = canvas.contextTop, + zoom = canvas.getZoom(); if (canvas && canvas._isRetinaScaling()) { zoom *= fabric.devicePixelRatio; } @@ -117,7 +118,7 @@ fabric.BaseBrush = fabric.util.createClass(/** @lends fabric.BaseBrush.prototype ctx.shadowOffsetY = shadow.offsetY * zoom; }, - needsFullRender: function() { + needsFullRender: function () { var color = new fabric.Color(this.color); return color.getAlpha() < 1 || !!this.shadow || (this.clipPath && this.clipPath.isCacheDirty()); }, @@ -127,7 +128,7 @@ fabric.BaseBrush = fabric.util.createClass(/** @lends fabric.BaseBrush.prototype * @private */ calcTransformMatrix: function () { - return fabric.util.invertTransform(this.canvas.viewportTransform); + return this.canvas.viewportTransform; }, /** @@ -148,10 +149,7 @@ fabric.BaseBrush = fabric.util.createClass(/** @lends fabric.BaseBrush.prototype if (!clipPath) { return; } - clipPath.canvas = this.canvas; - var v = fabric.util.invertTransform(this.canvas.viewportTransform); ctx.save(); - ctx.transform(v[0], v[1], v[2], v[3], v[4], v[5]); fabric.Object.prototype._drawClipPath.call(this, ctx, clipPath); ctx.restore(); }, @@ -166,8 +164,8 @@ fabric.BaseBrush = fabric.util.createClass(/** @lends fabric.BaseBrush.prototype return Promise.resolve(); } var t = result.calcTransformMatrix(); - if (!this.clipPath.absolutePositioned) { - t = fabric.util.multiplyTransformMatrices(this.canvas.viewportTransform, t); + if (this.clipPath.absolutePositioned) { + t = fabric.util.multiplyTransformMatrices(this.calcTransformMatrix(), t); } return this.clipPath.clone(['inverted']) .then(function (clipPath) { @@ -206,7 +204,7 @@ fabric.BaseBrush = fabric.util.createClass(/** @lends fabric.BaseBrush.prototype * @private * @param {CanvasRenderingContext2D} ctx */ - _resetShadow: function(ctx) { + _resetShadow: function (ctx) { ctx.shadowColor = ''; ctx.shadowBlur = ctx.shadowOffsetX = ctx.shadowOffsetY = 0; }, @@ -216,7 +214,7 @@ fabric.BaseBrush = fabric.util.createClass(/** @lends fabric.BaseBrush.prototype * @param {Object} pointer * @private */ - _isOutSideCanvas: function(pointer) { + _isOutSideCanvas: function (pointer) { return pointer.x < 0 || pointer.x > this.canvas.getWidth() || pointer.y < 0 || pointer.y > this.canvas.getHeight(); } }); From 082b063a438b48f2142f4bbcf21a7e6c03280470 Mon Sep 17 00:00:00 2001 From: ShaMan123 Date: Thu, 12 May 2022 09:30:35 +0300 Subject: [PATCH 34/37] Revert whitespace This reverts whitespace commited by 3f0f328f5e76e566bfb2a901f628355dad1bb24c. --- src/brushes/base_brush.class.js | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/src/brushes/base_brush.class.js b/src/brushes/base_brush.class.js index c6caee4fe79..39fcde151ee 100644 --- a/src/brushes/base_brush.class.js +++ b/src/brushes/base_brush.class.js @@ -89,7 +89,7 @@ fabric.BaseBrush = fabric.util.createClass(/** @lends fabric.BaseBrush.prototype * @param {RenderingContext2d} ctx context to render on * @private */ - _saveAndTransform: function (ctx) { + _saveAndTransform: function(ctx) { var v = this.canvas.viewportTransform; ctx.save(); ctx.transform(v[0], v[1], v[2], v[3], v[4], v[5]); @@ -99,15 +99,15 @@ fabric.BaseBrush = fabric.util.createClass(/** @lends fabric.BaseBrush.prototype * Sets brush shadow styles * @private */ - _setShadow: function () { + _setShadow: function() { if (!this.shadow) { return; } var canvas = this.canvas, - shadow = this.shadow, - ctx = canvas.contextTop, - zoom = canvas.getZoom(); + shadow = this.shadow, + ctx = canvas.contextTop, + zoom = canvas.getZoom(); if (canvas && canvas._isRetinaScaling()) { zoom *= fabric.devicePixelRatio; } @@ -118,7 +118,7 @@ fabric.BaseBrush = fabric.util.createClass(/** @lends fabric.BaseBrush.prototype ctx.shadowOffsetY = shadow.offsetY * zoom; }, - needsFullRender: function () { + needsFullRender: function() { var color = new fabric.Color(this.color); return color.getAlpha() < 1 || !!this.shadow || (this.clipPath && this.clipPath.isCacheDirty()); }, @@ -204,7 +204,7 @@ fabric.BaseBrush = fabric.util.createClass(/** @lends fabric.BaseBrush.prototype * @private * @param {CanvasRenderingContext2D} ctx */ - _resetShadow: function (ctx) { + _resetShadow: function(ctx) { ctx.shadowColor = ''; ctx.shadowBlur = ctx.shadowOffsetX = ctx.shadowOffsetY = 0; }, @@ -214,7 +214,7 @@ fabric.BaseBrush = fabric.util.createClass(/** @lends fabric.BaseBrush.prototype * @param {Object} pointer * @private */ - _isOutSideCanvas: function (pointer) { + _isOutSideCanvas: function(pointer) { return pointer.x < 0 || pointer.x > this.canvas.getWidth() || pointer.y < 0 || pointer.y > this.canvas.getHeight(); } }); From b52caa0c8811055ac7634363cf82c883fcc271b8 Mon Sep 17 00:00:00 2001 From: ShaMan123 Date: Thu, 12 May 2022 09:53:19 +0300 Subject: [PATCH 35/37] Update .eslintrc.json --- .eslintrc.json | 1 + 1 file changed, 1 insertion(+) diff --git a/.eslintrc.json b/.eslintrc.json index 22b337c2af2..8278e7a1dbb 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -7,6 +7,7 @@ }, "globals": { "Promise": true, + "async": true, "define": true, "eventjs": true, "exports": true, From 0e4b9ef2224eefe46636ebf4df5e52a6210a3fd3 Mon Sep 17 00:00:00 2001 From: ShaMan123 Date: Thu, 12 May 2022 10:01:03 +0300 Subject: [PATCH 36/37] Revert "Update .eslintrc.json" This reverts commit b52caa0c8811055ac7634363cf82c883fcc271b8. --- .eslintrc.json | 1 - 1 file changed, 1 deletion(-) diff --git a/.eslintrc.json b/.eslintrc.json index 8278e7a1dbb..22b337c2af2 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -7,7 +7,6 @@ }, "globals": { "Promise": true, - "async": true, "define": true, "eventjs": true, "exports": true, From f6669abc2ef2d18babc44f1e1d72ad6dea99f79e Mon Sep 17 00:00:00 2001 From: ShaMan123 Date: Thu, 12 May 2022 10:06:51 +0300 Subject: [PATCH 37/37] Update .eslintrc.json --- .eslintrc.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.eslintrc.json b/.eslintrc.json index 22b337c2af2..227c3444c3a 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -3,7 +3,7 @@ "browser": true }, "parserOptions": { - "ecmaVersion": 2017 + "ecmaVersion": 8 }, "globals": { "Promise": true,