fabric 4.5.1-browser → 5.0.0-browser

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (49) hide show
  1. package/CHANGELOG.md +78 -0
  2. package/CONTRIBUTING.md +14 -0
  3. package/HEADER.js +1 -1
  4. package/README.md +3 -6
  5. package/SECURITY.md +5 -0
  6. package/build.js +1 -0
  7. package/dist/fabric.js +1160 -668
  8. package/dist/fabric.min.js +1 -1
  9. package/lib/event.js +7 -3
  10. package/package.json +9 -3
  11. package/publish-next.js +15 -0
  12. package/publish.js +3 -0
  13. package/src/brushes/base_brush.class.js +3 -5
  14. package/src/brushes/pattern_brush.class.js +7 -5
  15. package/src/brushes/pencil_brush.class.js +49 -37
  16. package/src/canvas.class.js +27 -57
  17. package/src/filters/saturate_filter.class.js +9 -1
  18. package/src/filters/vibrance_filter.class.js +122 -0
  19. package/src/mixins/animation.mixin.js +20 -25
  20. package/src/mixins/canvas_events.mixin.js +30 -59
  21. package/src/mixins/canvas_grouping.mixin.js +4 -4
  22. package/src/mixins/collection.mixin.js +11 -2
  23. package/src/mixins/eraser_brush.mixin.js +495 -452
  24. package/src/mixins/itext_behavior.mixin.js +7 -1
  25. package/src/mixins/itext_key_behavior.mixin.js +7 -1
  26. package/src/mixins/object_geometry.mixin.js +5 -35
  27. package/src/mixins/object_interactivity.mixin.js +18 -7
  28. package/src/mixins/object_straightening.mixin.js +4 -9
  29. package/src/mixins/observable.mixin.js +22 -0
  30. package/src/parser.js +13 -9
  31. package/src/shapes/circle.class.js +22 -19
  32. package/src/shapes/ellipse.class.js +2 -2
  33. package/src/shapes/group.class.js +24 -32
  34. package/src/shapes/image.class.js +3 -25
  35. package/src/shapes/itext.class.js +10 -0
  36. package/src/shapes/line.class.js +5 -21
  37. package/src/shapes/object.class.js +67 -32
  38. package/src/shapes/path.class.js +17 -18
  39. package/src/shapes/polygon.class.js +11 -10
  40. package/src/shapes/polyline.class.js +33 -24
  41. package/src/shapes/rect.class.js +0 -18
  42. package/src/shapes/text.class.js +120 -58
  43. package/src/shapes/triangle.class.js +0 -15
  44. package/src/static_canvas.class.js +43 -20
  45. package/src/util/animate.js +149 -16
  46. package/src/util/animate_color.js +2 -1
  47. package/src/util/lang_object.js +5 -1
  48. package/src/util/misc.js +193 -42
  49. package/src/util/path.js +89 -52
@@ -1,210 +1,289 @@
1
1
  (function () {
2
2
  /** ERASER_START */
3
- var __setBgOverlayColor = fabric.StaticCanvas.prototype.__setBgOverlayColor;
4
- var ___setBgOverlay = fabric.StaticCanvas.prototype.__setBgOverlay;
5
- var __setSVGBgOverlayColor = fabric.StaticCanvas.prototype._setSVGBgOverlayColor;
6
- fabric.util.object.extend(fabric.StaticCanvas.prototype, {
7
- backgroundColor: undefined,
8
- overlayColor: undefined,
3
+
4
+ /**
5
+ * add `eraser` to enlivened props
6
+ */
7
+ fabric.Object.ENLIVEN_PROPS.push('eraser');
8
+
9
+ var __drawClipPath = fabric.Object.prototype._drawClipPath;
10
+ var _needsItsOwnCache = fabric.Object.prototype.needsItsOwnCache;
11
+ var _toObject = fabric.Object.prototype.toObject;
12
+ var _getSvgCommons = fabric.Object.prototype.getSvgCommons;
13
+ var __createBaseClipPathSVGMarkup = fabric.Object.prototype._createBaseClipPathSVGMarkup;
14
+ var __createBaseSVGMarkup = fabric.Object.prototype._createBaseSVGMarkup;
15
+ /**
16
+ * @fires erasing:end
17
+ */
18
+ fabric.util.object.extend(fabric.Object.prototype, {
9
19
  /**
10
- * Create Rect that holds the color to support erasing
11
- * patches {@link CommonMethods#_initGradient}
12
- * @private
13
- * @param {'bakground'|'overlay'} property
14
- * @param {(String|fabric.Pattern|fabric.Rect)} color Color or pattern or rect (in case of erasing)
15
- * @param {Function} callback Callback to invoke when color is set
16
- * @param {Object} options
17
- * @return {fabric.Canvas} instance
18
- * @chainable true
20
+ * Indicates whether this object can be erased by {@link fabric.EraserBrush}
21
+ * The `deep` option introduces fine grained control over a group's `erasable` property.
22
+ * When set to `deep` the eraser will erase nested objects if they are erasable, leaving the group and the other objects untouched.
23
+ * When set to `true` the eraser will erase the entire group. Once the group changes the eraser is propagated to its children for proper functionality.
24
+ * When set to `false` the eraser will leave all objects including the group untouched.
25
+ * @tutorial {@link http://fabricjs.com/erasing#erasable_property}
26
+ * @type boolean | 'deep'
27
+ * @default true
19
28
  */
20
- __setBgOverlayColor: function (property, color, callback, options) {
21
- if (color && color.isType && color.isType('rect')) {
22
- // color is already an object
23
- this[property] = color;
24
- color.set(options);
25
- callback && callback(this[property]);
26
- }
27
- else {
28
- var _this = this;
29
- var cb = function () {
30
- _this[property] = new fabric.Rect(fabric.util.object.extend({
31
- width: _this.width,
32
- height: _this.height,
33
- fill: _this[property],
34
- }, options));
35
- callback && callback(_this[property]);
36
- };
37
- __setBgOverlayColor.call(this, property, color, cb);
38
- // invoke cb in case of gradient
39
- // see {@link CommonMethods#_initGradient}
40
- if (color && color.colorStops && !(color instanceof fabric.Gradient)) {
41
- cb();
42
- }
43
- }
44
-
45
- return this;
46
- },
29
+ erasable: true,
47
30
 
48
- setBackgroundColor: function (backgroundColor, callback, options) {
49
- return this.__setBgOverlayColor('backgroundColor', backgroundColor, callback, options);
50
- },
31
+ /**
32
+ * @tutorial {@link http://fabricjs.com/erasing#eraser}
33
+ * @type fabric.Eraser
34
+ */
35
+ eraser: undefined,
51
36
 
52
- setOverlayColor: function (overlayColor, callback, options) {
53
- return this.__setBgOverlayColor('overlayColor', overlayColor, callback, options);
37
+ /**
38
+ * @override
39
+ * @returns Boolean
40
+ */
41
+ needsItsOwnCache: function () {
42
+ return _needsItsOwnCache.call(this) || !!this.eraser;
54
43
  },
55
44
 
56
45
  /**
57
- * patch serialization - from json
58
- * background/overlay properties could be objects if parsed by this mixin or could be legacy values
46
+ * draw eraser above clip path
47
+ * @override
59
48
  * @private
60
- * @param {String} property Property to set (backgroundImage, overlayImage, backgroundColor, overlayColor)
61
- * @param {(Object|String)} value Value to set
62
- * @param {Object} loaded Set loaded property to true if property is set
63
- * @param {Object} callback Callback function to invoke after property is set
49
+ * @param {CanvasRenderingContext2D} ctx
50
+ * @param {fabric.Object} clipPath
64
51
  */
65
- __setBgOverlay: function (property, value, loaded, callback) {
66
- var _this = this;
67
-
68
- if ((property === 'backgroundColor' || property === 'overlayColor') &&
69
- (value && typeof value === 'object' && value.type === 'rect')) {
70
- fabric.util.enlivenObjects([value], function (enlivedObject) {
71
- _this[property] = enlivedObject[0];
72
- loaded[property] = true;
73
- callback && callback();
52
+ _drawClipPath: function (ctx, clipPath) {
53
+ __drawClipPath.call(this, ctx, clipPath);
54
+ if (this.eraser) {
55
+ // update eraser size to match instance
56
+ var size = this._getNonTransformedDimensions();
57
+ this.eraser.isType('eraser') && this.eraser.set({
58
+ width: size.x,
59
+ height: size.y
74
60
  });
75
- }
76
- else {
77
- ___setBgOverlay.call(this, property, value, loaded, callback);
61
+ __drawClipPath.call(this, ctx, this.eraser);
78
62
  }
79
63
  },
80
64
 
81
65
  /**
82
- * patch serialization - to svg
83
- * background/overlay properties could be objects if parsed by this mixin or could be legacy values
84
- * @private
66
+ * Returns an object representation of an instance
67
+ * @param {Array} [propertiesToInclude] Any properties that you might want to additionally include in the output
68
+ * @return {Object} Object representation of an instance
85
69
  */
86
- _setSVGBgOverlayColor: function (markup, property, reviver) {
87
- var filler = this[property + 'Color'];
88
- if (filler && filler.isType && filler.isType('rect')) {
89
- var excludeFromExport = filler.excludeFromExport || (this[property] && this[property].excludeFromExport);
90
- if (filler && !excludeFromExport && filler.toSVG) {
91
- markup.push(filler.toSVG(reviver));
92
- }
93
- }
94
- else {
95
- __setSVGBgOverlayColor.call(this, markup, property, reviver);
70
+ toObject: function (propertiesToInclude) {
71
+ var object = _toObject.call(this, ['erasable'].concat(propertiesToInclude));
72
+ if (this.eraser && !this.eraser.excludeFromExport) {
73
+ object.eraser = this.eraser.toObject(propertiesToInclude);
96
74
  }
75
+ return object;
97
76
  },
98
77
 
78
+ /* _TO_SVG_START_ */
99
79
  /**
100
- * @private
101
- * @param {CanvasRenderingContext2D} ctx Context to render on
102
- * @param {string} property 'background' or 'overlay'
80
+ * Returns id attribute for svg output
81
+ * @override
82
+ * @return {String}
103
83
  */
104
- _renderBackgroundOrOverlay: function (ctx, property) {
105
- var fill = this[property + 'Color'], object = this[property + 'Image'],
106
- v = this.viewportTransform, needsVpt = this[property + 'Vpt'];
107
- if (!fill && !object) {
108
- return;
109
- }
110
- if (fill || object) {
111
- ctx.save();
112
- if (needsVpt) {
113
- ctx.transform(v[0], v[1], v[2], v[3], v[4], v[5]);
114
- }
115
- fill && fill.render(ctx);
116
- object && object.render(ctx);
117
- ctx.restore();
118
- }
84
+ getSvgCommons: function () {
85
+ return _getSvgCommons.call(this) + (this.eraser ? 'mask="url(#' + this.eraser.clipPathId + ')" ' : '');
119
86
  },
120
- });
121
87
 
122
- var _toObject = fabric.Object.prototype.toObject;
123
- var __createBaseSVGMarkup = fabric.Object.prototype._createBaseSVGMarkup;
124
- fabric.util.object.extend(fabric.Object.prototype, {
125
88
  /**
126
- * Indicates whether this object can be erased by {@link fabric.EraserBrush}
127
- * @type boolean
128
- * @default true
89
+ * create svg markup for eraser
90
+ * use <mask> to achieve erasing for svg, credit: https://travishorn.com/removing-parts-of-shapes-in-svg-b539a89e5649
91
+ * must be called before object markup creation as it relies on the `clipPathId` property of the mask
92
+ * @param {Function} [reviver]
93
+ * @returns
129
94
  */
130
- erasable: true,
95
+ _createEraserSVGMarkup: function (reviver) {
96
+ if (this.eraser) {
97
+ this.eraser.clipPathId = 'MASK_' + fabric.Object.__uid++;
98
+ return [
99
+ '<mask id="', this.eraser.clipPathId, '" >',
100
+ this.eraser.toSVG(reviver),
101
+ '</mask>', '\n'
102
+ ].join('');
103
+ }
104
+ return '';
105
+ },
131
106
 
132
107
  /**
133
- *
134
- * @returns {fabric.Group | null}
108
+ * @private
135
109
  */
136
- getEraser: function () {
137
- return this.clipPath && this.clipPath.eraser ? this.clipPath : null;
110
+ _createBaseClipPathSVGMarkup: function (objectMarkup, options) {
111
+ return [
112
+ this._createEraserSVGMarkup(options && options.reviver),
113
+ __createBaseClipPathSVGMarkup.call(this, objectMarkup, options)
114
+ ].join('');
138
115
  },
139
116
 
140
117
  /**
141
- * Returns an object representation of an instance
142
- * @param {Array} [propertiesToInclude] Any properties that you might want to additionally include in the output
143
- * @return {Object} Object representation of an instance
118
+ * @private
119
+ */
120
+ _createBaseSVGMarkup: function (objectMarkup, options) {
121
+ return [
122
+ this._createEraserSVGMarkup(options && options.reviver),
123
+ __createBaseSVGMarkup.call(this, objectMarkup, options)
124
+ ].join('');
125
+ }
126
+ /* _TO_SVG_END_ */
127
+ });
128
+
129
+ var __restoreObjectsState = fabric.Group.prototype._restoreObjectsState;
130
+ fabric.util.object.extend(fabric.Group.prototype, {
131
+ /**
132
+ * @private
133
+ * @param {fabric.Path} path
144
134
  */
145
- toObject: function (additionalProperties) {
146
- return _toObject.call(this, ['erasable'].concat(additionalProperties));
135
+ _addEraserPathToObjects: function (path) {
136
+ this._objects.forEach(function (object) {
137
+ fabric.EraserBrush.prototype._addPathToObjectEraser.call(
138
+ fabric.EraserBrush.prototype,
139
+ object,
140
+ path
141
+ );
142
+ });
147
143
  },
148
144
 
149
145
  /**
150
- * use <mask> to achieve erasing for svg
151
- * credit: https://travishorn.com/removing-parts-of-shapes-in-svg-b539a89e5649
152
- * @param {Function} reviver
153
- * @returns {string} markup
146
+ * Applies the group's eraser to its objects
147
+ * @tutorial {@link http://fabricjs.com/erasing#erasable_property}
154
148
  */
155
- eraserToSVG: function (options) {
156
- var eraser = this.getEraser();
149
+ applyEraserToObjects: function () {
150
+ var _this = this, eraser = this.eraser;
157
151
  if (eraser) {
158
- var fill = eraser._objects[0].fill;
159
- eraser._objects[0].fill = 'white';
160
- eraser.clipPathId = 'CLIPPATH_' + fabric.Object.__uid++;
161
- var commons = [
162
- 'id="' + eraser.clipPathId + '"',
163
- /*options.additionalTransform ? ' transform="' + options.additionalTransform + '" ' : ''*/
164
- ].join(' ');
165
- var objectMarkup = ['<defs>', '<mask ' + commons + ' >', eraser.toSVG(options.reviver), '</mask>', '</defs>'];
166
- eraser._objects[0].fill = fill;
167
- return objectMarkup.join('\n');
152
+ delete this.eraser;
153
+ var transform = _this.calcTransformMatrix();
154
+ eraser.clone(function (eraser) {
155
+ var clipPath = _this.clipPath;
156
+ eraser.getObjects('path')
157
+ .forEach(function (path) {
158
+ // first we transform the path from the group's coordinate system to the canvas'
159
+ var originalTransform = fabric.util.multiplyTransformMatrices(
160
+ transform,
161
+ path.calcTransformMatrix()
162
+ );
163
+ fabric.util.applyTransformToObject(path, originalTransform);
164
+ if (clipPath) {
165
+ clipPath.clone(function (_clipPath) {
166
+ var eraserPath = fabric.EraserBrush.prototype.applyClipPathToPath.call(
167
+ fabric.EraserBrush.prototype,
168
+ path,
169
+ _clipPath,
170
+ transform
171
+ );
172
+ _this._addEraserPathToObjects(eraserPath);
173
+ }, ['absolutePositioned', 'inverted']);
174
+ }
175
+ else {
176
+ _this._addEraserPathToObjects(path);
177
+ }
178
+ });
179
+ });
168
180
  }
169
- return '';
170
181
  },
171
182
 
172
183
  /**
173
- * use <mask> to achieve erasing for svg, override <clipPath>
174
- * @param {string[]} objectMarkup
175
- * @param {Object} options
176
- * @returns
184
+ * Propagate the group's eraser to its objects, crucial for proper functionality of the eraser within the group and nested objects.
185
+ * @private
177
186
  */
178
- _createBaseSVGMarkup: function (objectMarkup, options) {
179
- var eraser = this.getEraser();
180
- if (eraser) {
181
- var eraserMarkup = this.eraserToSVG(options);
182
- this.clipPath = null;
183
- var markup = __createBaseSVGMarkup.call(this, objectMarkup, options);
184
- this.clipPath = eraser;
185
- return [
186
- eraserMarkup,
187
- markup.replace('>', 'mask="url(#' + eraser.clipPathId + ')" >')
188
- ].join('\n');
189
- }
190
- else {
191
- return __createBaseSVGMarkup.call(this, objectMarkup, options);
192
- }
187
+ _restoreObjectsState: function () {
188
+ this.erasable === true && this.applyEraserToObjects();
189
+ return __restoreObjectsState.call(this);
193
190
  }
194
191
  });
195
192
 
196
- var _groupToObject = fabric.Group.prototype.toObject;
197
- fabric.util.object.extend(fabric.Group.prototype, {
193
+ /**
194
+ * An object's Eraser
195
+ * @private
196
+ * @class fabric.Eraser
197
+ * @extends fabric.Group
198
+ * @memberof fabric
199
+ */
200
+ fabric.Eraser = fabric.util.createClass(fabric.Group, {
198
201
  /**
199
- * Returns an object representation of an instance
200
- * @param {Array} [propertiesToInclude] Any properties that you might want to additionally include in the output
201
- * @return {Object} Object representation of an instance
202
+ * @readonly
203
+ * @static
202
204
  */
203
- toObject: function (additionalProperties) {
204
- return _groupToObject.call(this, ['eraser'].concat(additionalProperties));
205
- }
205
+ type: 'eraser',
206
+
207
+ /**
208
+ * @default
209
+ */
210
+ originX: 'center',
211
+
212
+ /**
213
+ * @default
214
+ */
215
+ originY: 'center',
216
+
217
+ drawObject: function (ctx) {
218
+ ctx.save();
219
+ ctx.fillStyle = 'black';
220
+ ctx.fillRect(-this.width / 2, -this.height / 2, this.width, this.height);
221
+ ctx.restore();
222
+ this.callSuper('drawObject', ctx);
223
+ },
224
+
225
+ /**
226
+ * eraser should retain size
227
+ * dimensions should not change when paths are added or removed
228
+ * handled by {@link fabric.Object#_drawClipPath}
229
+ * @override
230
+ * @private
231
+ */
232
+ _getBounds: function () {
233
+ // noop
234
+ },
235
+
236
+ /* _TO_SVG_START_ */
237
+ /**
238
+ * Returns svg representation of an instance
239
+ * use <mask> to achieve erasing for svg, credit: https://travishorn.com/removing-parts-of-shapes-in-svg-b539a89e5649
240
+ * for masking we need to add a white rect before all paths
241
+ *
242
+ * @param {Function} [reviver] Method for further parsing of svg representation.
243
+ * @return {String} svg representation of an instance
244
+ */
245
+ _toSVG: function (reviver) {
246
+ var svgString = ['<g ', 'COMMON_PARTS', ' >\n'];
247
+ var x = -this.width / 2, y = -this.height / 2;
248
+ var rectSvg = [
249
+ '<rect ', 'fill="white" ',
250
+ 'x="', x, '" y="', y,
251
+ '" width="', this.width, '" height="', this.height,
252
+ '" />\n'
253
+ ].join('');
254
+ svgString.push('\t\t', rectSvg);
255
+ for (var i = 0, len = this._objects.length; i < len; i++) {
256
+ svgString.push('\t\t', this._objects[i].toSVG(reviver));
257
+ }
258
+ svgString.push('</g>\n');
259
+ return svgString;
260
+ },
261
+ /* _TO_SVG_END_ */
206
262
  });
207
263
 
264
+ /**
265
+ * Returns {@link fabric.Eraser} instance from an object representation
266
+ * @static
267
+ * @memberOf fabric.Eraser
268
+ * @param {Object} object Object to create an Eraser from
269
+ * @param {Function} [callback] Callback to invoke when an eraser instance is created
270
+ */
271
+ fabric.Eraser.fromObject = function (object, callback) {
272
+ var objects = object.objects;
273
+ fabric.util.enlivenObjects(objects, function (enlivenedObjects) {
274
+ var options = fabric.util.object.clone(object, true);
275
+ delete options.objects;
276
+ fabric.util.enlivenObjectEnlivables(object, options, function () {
277
+ callback && callback(new fabric.Eraser(enlivenedObjects, options, true));
278
+ });
279
+ });
280
+ };
281
+
282
+ var __renderOverlay = fabric.Canvas.prototype._renderOverlay;
283
+ /**
284
+ * @fires erasing:start
285
+ * @fires erasing:end
286
+ */
208
287
  fabric.util.object.extend(fabric.Canvas.prototype, {
209
288
  /**
210
289
  * Used by {@link #renderAll}
@@ -220,44 +299,36 @@
220
299
  },
221
300
 
222
301
  /**
223
- * While erasing, the brush is in charge of rendering the canvas
224
- * It uses both layers to achieve diserd erasing effect
225
- *
226
- * @returns fabric.Canvas
302
+ * While erasing the brush clips out the erasing path from canvas
303
+ * so we need to render it on top of canvas every render
304
+ * @param {CanvasRenderingContext2D} ctx
227
305
  */
228
- renderAll: function () {
229
- if (this.contextTopDirty && !this._groupSelector && !this.isDrawingMode) {
230
- this.clearContext(this.contextTop);
231
- this.contextTopDirty = false;
232
- }
233
- // while erasing the brush is in charge of rendering the canvas so we return
234
- if (this.isErasing()) {
306
+ _renderOverlay: function (ctx) {
307
+ __renderOverlay.call(this, ctx);
308
+ if (this.isErasing() && !this.freeDrawingBrush.inverted) {
235
309
  this.freeDrawingBrush._render();
236
- return;
237
310
  }
238
- if (this.hasLostContext) {
239
- this.renderTopLayer(this.contextTop);
240
- }
241
- var canvasToDrawOn = this.contextContainer;
242
- this.renderCanvas(canvasToDrawOn, this._chooseObjectsToRender());
243
- return this;
244
311
  }
245
312
  });
246
313
 
247
-
248
314
  /**
249
315
  * EraserBrush class
250
316
  * Supports selective erasing meaning that only erasable objects are affected by the eraser brush.
251
- * In order to support selective erasing all non erasable objects are rendered on the main/bottom ctx
252
- * while the entire canvas is rendered on the top ctx.
253
- * Canvas bakground/overlay image/color are handled as well.
254
- * When erasing occurs, the path clips the top ctx and reveals the bottom ctx.
255
- * This achieves the desired effect of seeming to erase only erasable objects.
256
- * After erasing is done the created path is added to all intersected objects' `clipPath` property.
317
+ * Supports **inverted** erasing meaning that the brush can "undo" erasing.
257
318
  *
319
+ * In order to support selective erasing, the brush clips the entire canvas
320
+ * and then draws all non-erasable objects over the erased path using a pattern brush so to speak (masking).
321
+ * If brush is **inverted** there is no need to clip canvas. The brush draws all erasable objects without their eraser.
322
+ * This achieves the desired effect of seeming to erase or unerase only erasable objects.
323
+ * After erasing is done the created path is added to all intersected objects' `eraser` property.
258
324
  *
325
+ * In order to update the EraserBrush call `preparePattern`.
326
+ * It may come in handy when canvas changes during erasing (i.e animations) and you want the eraser to reflect the changes.
327
+ *
328
+ * @tutorial {@link http://fabricjs.com/erasing}
259
329
  * @class fabric.EraserBrush
260
330
  * @extends fabric.PencilBrush
331
+ * @memberof fabric
261
332
  */
262
333
  fabric.EraserBrush = fabric.util.createClass(
263
334
  fabric.PencilBrush,
@@ -265,253 +336,156 @@
265
336
  type: 'eraser',
266
337
 
267
338
  /**
268
- * Indicates that the ctx is ready and rendering can begin.
269
- * Used to prevent a race condition caused by {@link fabric.EraserBrush#onMouseMove} firing before {@link fabric.EraserBrush#onMouseDown} has completed
270
- *
271
- * @private
272
- */
273
- _ready: false,
274
-
275
- /**
276
- * @private
339
+ * When set to `true` the brush will create a visual effect of undoing erasing
277
340
  */
278
- _drawOverlayOnTop: false,
341
+ inverted: false,
279
342
 
280
343
  /**
281
344
  * @private
282
345
  */
283
346
  _isErasing: false,
284
347
 
285
- initialize: function (canvas) {
286
- this.callSuper('initialize', canvas);
287
- this._renderBound = this._render.bind(this);
288
- this.render = this.render.bind(this);
289
- },
290
-
291
348
  /**
292
- * Used to hide a drawable from the rendering process
293
- * @param {fabric.Object} object
294
- */
295
- hideObject: function (object) {
296
- if (object) {
297
- object._originalOpacity = object.opacity;
298
- object.set({ opacity: 0 });
299
- }
300
- },
301
-
302
- /**
303
- * Restores hiding an object
304
- * {@link fabric.EraserBrush#hideObject}
305
- * @param {fabric.Object} object
306
- */
307
- restoreObjectVisibility: function (object) {
308
- if (object && object._originalOpacity) {
309
- object.set({ opacity: object._originalOpacity });
310
- object._originalOpacity = undefined;
311
- }
312
- },
313
-
314
- /**
315
- * Drawing Logic For background drawables: (`backgroundImage`, `backgroundColor`)
316
- * 1. if erasable = true:
317
- * 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
318
- * 2. if erasable = false:
319
- * we need to draw the drawable only on the bottom ctx so the brush won't affect it
320
- * @param {'bottom' | 'top' | 'overlay'} layer
321
- */
322
- prepareCanvasBackgroundForLayer: function (layer) {
323
- if (layer === 'overlay') {
324
- return;
325
- }
326
- var canvas = this.canvas;
327
- var image = canvas.get('backgroundImage');
328
- var color = canvas.get('backgroundColor');
329
- var erasablesOnLayer = layer === 'top';
330
- if (image && image.erasable === !erasablesOnLayer) {
331
- this.hideObject(image);
332
- }
333
- if (color && color.erasable === !erasablesOnLayer) {
334
- this.hideObject(color);
335
- }
336
- },
337
-
338
- /**
339
- * Drawing Logic For overlay drawables (`overlayImage`, `overlayColor`)
340
- * We must draw on top ctx to be on top of visible canvas
341
- * 1. if erasable = true:
342
- * we need to draw the drawable on the top ctx as a normal object
343
- * 2. if erasable = false:
344
- * we need to draw the drawable on top of the brush,
345
- * this means we need to repaint for every stroke
346
349
  *
347
- * @param {'bottom' | 'top' | 'overlay'} layer
348
- * @returns boolean render overlay above brush
349
- */
350
- prepareCanvasOverlayForLayer: function (layer) {
351
- var canvas = this.canvas;
352
- var image = canvas.get('overlayImage');
353
- var color = canvas.get('overlayColor');
354
- if (layer === 'bottom') {
355
- this.hideObject(image);
356
- this.hideObject(color);
357
- return false;
358
- };
359
- var erasablesOnLayer = layer === 'top';
360
- var renderOverlayOnTop = (image && !image.erasable) || (color && !color.erasable);
361
- if (image && image.erasable === !erasablesOnLayer) {
362
- this.hideObject(image);
363
- }
364
- if (color && color.erasable === !erasablesOnLayer) {
365
- this.hideObject(color);
366
- }
367
- return renderOverlayOnTop;
368
- },
369
-
370
- /**
371
350
  * @private
351
+ * @param {fabric.Object} object
352
+ * @returns boolean
372
353
  */
373
- restoreCanvasDrawables: function () {
374
- var canvas = this.canvas;
375
- this.restoreObjectVisibility(canvas.get('backgroundImage'));
376
- this.restoreObjectVisibility(canvas.get('backgroundColor'));
377
- this.restoreObjectVisibility(canvas.get('overlayImage'));
378
- this.restoreObjectVisibility(canvas.get('overlayColor'));
354
+ _isErasable: function (object) {
355
+ return object.erasable !== false;
379
356
  },
380
357
 
381
358
  /**
382
359
  * @private
383
- * This is designed to support erasing a group with both erasable and non-erasable objects.
360
+ * This is designed to support erasing a collection with both erasable and non-erasable objects.
384
361
  * Iterates over collections to allow nested selective erasing.
385
- * Used by {@link fabric.EraserBrush#prepareCanvasObjectsForLayer}
386
- * to prepare the bottom layer by hiding erasable nested objects
362
+ * Prepares the pattern brush that will draw on the top context to achieve the desired visual effect.
363
+ * If brush is **NOT** inverted render all non-erasable objects.
364
+ * If brush is inverted render all erasable objects that have been erased with their clip path inverted.
365
+ * This will render the erased parts as if they were not erased.
387
366
  *
388
367
  * @param {fabric.Collection} collection
368
+ * @param {CanvasRenderingContext2D} ctx
369
+ * @param {{ visibility: fabric.Object[], eraser: fabric.Object[], collection: fabric.Object[] }} restorationContext
389
370
  */
390
- prepareCollectionTraversal: function (collection) {
391
- var _this = this;
371
+ _prepareCollectionTraversal: function (collection, ctx, restorationContext) {
392
372
  collection.forEachObject(function (obj) {
393
- if (obj.forEachObject) {
394
- _this.prepareCollectionTraversal(obj);
373
+ if (obj.forEachObject && obj.erasable === 'deep') {
374
+ // traverse
375
+ this._prepareCollectionTraversal(obj, ctx, restorationContext);
395
376
  }
396
- else {
397
- if (obj.erasable) {
398
- _this.hideObject(obj);
377
+ else if (!this.inverted && obj.erasable && obj.visible) {
378
+ // render only non-erasable objects
379
+ obj.visible = false;
380
+ collection.dirty = true;
381
+ restorationContext.visibility.push(obj);
382
+ restorationContext.collection.push(collection);
383
+ }
384
+ else if (this.inverted && obj.visible) {
385
+ // render only erasable objects that were erased
386
+ if (obj.erasable && obj.eraser) {
387
+ obj.eraser.inverted = true;
388
+ obj.dirty = true;
389
+ collection.dirty = true;
390
+ restorationContext.eraser.push(obj);
391
+ restorationContext.collection.push(collection);
392
+ }
393
+ else {
394
+ obj.visible = false;
395
+ collection.dirty = true;
396
+ restorationContext.visibility.push(obj);
397
+ restorationContext.collection.push(collection);
399
398
  }
400
399
  }
401
- });
400
+ }, this);
402
401
  },
403
402
 
404
403
  /**
404
+ * Prepare the pattern for the erasing brush
405
+ * This pattern will be drawn on the top context, achieving a visual effect of erasing only erasable objects
406
+ * @todo decide how overlay color should behave when `inverted === true`, currently draws over it which is undesirable
405
407
  * @private
406
- * Used by {@link fabric.EraserBrush#prepareCanvasObjectsForLayer}
407
- * to reverse the action of {@link fabric.EraserBrush#prepareCollectionTraversal}
408
- *
409
- * @param {fabric.Collection} collection
410
408
  */
411
- restoreCollectionTraversal: function (collection) {
412
- var _this = this;
413
- collection.forEachObject(function (obj) {
414
- if (obj.forEachObject) {
415
- _this.restoreCollectionTraversal(obj);
416
- }
417
- else {
418
- _this.restoreObjectVisibility(obj);
419
- }
409
+ preparePattern: function () {
410
+ if (!this._patternCanvas) {
411
+ this._patternCanvas = fabric.util.createCanvasElement();
412
+ }
413
+ var canvas = this._patternCanvas;
414
+ canvas.width = this.canvas.width;
415
+ canvas.height = this.canvas.height;
416
+ var patternCtx = canvas.getContext('2d');
417
+ if (this.canvas._isRetinaScaling()) {
418
+ var retinaScaling = this.canvas.getRetinaScaling();
419
+ this.canvas.__initRetinaScaling(retinaScaling, canvas, patternCtx);
420
+ }
421
+ var backgroundImage = this.canvas.backgroundImage,
422
+ bgErasable = backgroundImage && this._isErasable(backgroundImage),
423
+ overlayImage = this.canvas.overlayImage,
424
+ overlayErasable = overlayImage && this._isErasable(overlayImage);
425
+ if (!this.inverted && ((backgroundImage && !bgErasable) || !!this.canvas.backgroundColor)) {
426
+ if (bgErasable) { this.canvas.backgroundImage = undefined; }
427
+ this.canvas._renderBackground(patternCtx);
428
+ if (bgErasable) { this.canvas.backgroundImage = backgroundImage; }
429
+ }
430
+ else if (this.inverted && (backgroundImage && bgErasable)) {
431
+ var color = this.canvas.backgroundColor;
432
+ this.canvas.backgroundColor = undefined;
433
+ this.canvas._renderBackground(patternCtx);
434
+ this.canvas.backgroundColor = color;
435
+ }
436
+ patternCtx.save();
437
+ patternCtx.transform.apply(patternCtx, this.canvas.viewportTransform);
438
+ var restorationContext = { visibility: [], eraser: [], collection: [] };
439
+ this._prepareCollectionTraversal(this.canvas, patternCtx, restorationContext);
440
+ this.canvas._renderObjects(patternCtx, this.canvas._objects);
441
+ restorationContext.visibility.forEach(function (obj) { obj.visible = true; });
442
+ restorationContext.eraser.forEach(function (obj) {
443
+ obj.eraser.inverted = false;
444
+ obj.dirty = true;
420
445
  });
446
+ restorationContext.collection.forEach(function (obj) { obj.dirty = true; });
447
+ patternCtx.restore();
448
+ if (!this.inverted && ((overlayImage && !overlayErasable) || !!this.canvas.overlayColor)) {
449
+ if (overlayErasable) { this.canvas.overlayImage = undefined; }
450
+ __renderOverlay.call(this.canvas, patternCtx);
451
+ if (overlayErasable) { this.canvas.overlayImage = overlayImage; }
452
+ }
453
+ else if (this.inverted && (overlayImage && overlayErasable)) {
454
+ var color = this.canvas.overlayColor;
455
+ this.canvas.overlayColor = undefined;
456
+ __renderOverlay.call(this.canvas, patternCtx);
457
+ this.canvas.overlayColor = color;
458
+ }
421
459
  },
422
460
 
423
461
  /**
462
+ * Sets brush styles
424
463
  * @private
425
- * This is designed to support erasing a group with both erasable and non-erasable objects.
426
- *
427
- * @param {'bottom' | 'top' | 'overlay'} layer
428
- */
429
- prepareCanvasObjectsForLayer: function (layer) {
430
- if (layer !== 'bottom') { return; }
431
- this.prepareCollectionTraversal(this.canvas);
432
- },
433
-
434
- /**
435
- * @private
436
- * @param {'bottom' | 'top' | 'overlay'} layer
437
- */
438
- restoreCanvasObjectsFromLayer: function (layer) {
439
- if (layer !== 'bottom') { return; }
440
- this.restoreCollectionTraversal(this.canvas);
441
- },
442
-
443
- /**
444
- * @private
445
- * @param {'bottom' | 'top' | 'overlay'} layer
446
- * @returns boolean render overlay above brush
447
- */
448
- prepareCanvasForLayer: function (layer) {
449
- this.prepareCanvasBackgroundForLayer(layer);
450
- this.prepareCanvasObjectsForLayer(layer);
451
- return this.prepareCanvasOverlayForLayer(layer);
452
- },
453
-
454
- /**
455
- * @private
456
- * @param {'bottom' | 'top' | 'overlay'} layer
457
- */
458
- restoreCanvasFromLayer: function (layer) {
459
- this.restoreCanvasDrawables();
460
- this.restoreCanvasObjectsFromLayer(layer);
461
- },
462
-
463
- /**
464
- * Render all non-erasable objects on bottom layer with the exception of overlays to avoid being clipped by the brush.
465
- * Groups are rendered for nested selective erasing, non-erasable objects are visible while erasable objects are not.
466
- */
467
- renderBottomLayer: function () {
468
- var canvas = this.canvas;
469
- this.prepareCanvasForLayer('bottom');
470
- canvas.renderCanvas(
471
- canvas.getContext(),
472
- canvas.getObjects().filter(function (obj) {
473
- return !obj.erasable || obj.isType('group');
474
- })
475
- );
476
- this.restoreCanvasFromLayer('bottom');
477
- },
478
-
479
- /**
480
- * 1. Render all objects on top layer, erasable and non-erasable
481
- * This is important for cases such as overlapping objects, the background object erasable and the foreground object not erasable.
482
- * 2. Render the brush
483
- */
484
- renderTopLayer: function () {
485
- var canvas = this.canvas;
486
- this._drawOverlayOnTop = this.prepareCanvasForLayer('top');
487
- canvas.renderCanvas(
488
- canvas.contextTop,
489
- canvas.getObjects()
490
- );
491
- this.callSuper('_render');
492
- this.restoreCanvasFromLayer('top');
493
- },
494
-
495
- /**
496
- * Render all non-erasable overlays on top of the brush so that they won't get erased
464
+ * @param {CanvasRenderingContext2D} ctx
497
465
  */
498
- renderOverlay: function () {
499
- this.prepareCanvasForLayer('overlay');
500
- var canvas = this.canvas;
501
- var ctx = canvas.contextTop;
502
- this._saveAndTransform(ctx);
503
- canvas._renderOverlay(ctx);
504
- ctx.restore();
505
- this.restoreCanvasFromLayer('overlay');
466
+ _setBrushStyles: function (ctx) {
467
+ this.callSuper('_setBrushStyles', ctx);
468
+ ctx.strokeStyle = 'black';
506
469
  },
507
470
 
508
471
  /**
509
- * @extends @class fabric.BaseBrush
472
+ * **Customiztion**
473
+ *
474
+ * if you need the eraser to update on each render (i.e animating during erasing) override this method by **adding** the following (performance may suffer):
475
+ * @example
476
+ * ```
477
+ * if(ctx === this.canvas.contextTop) {
478
+ * this.preparePattern();
479
+ * }
480
+ * ```
481
+ *
482
+ * @override fabric.BaseBrush#_saveAndTransform
510
483
  * @param {CanvasRenderingContext2D} ctx
511
484
  */
512
485
  _saveAndTransform: function (ctx) {
513
486
  this.callSuper('_saveAndTransform', ctx);
514
- ctx.globalCompositeOperation = 'destination-out';
487
+ this._setBrushStyles(ctx);
488
+ ctx.globalCompositeOperation = ctx === this.canvas.getContext() ? 'destination-out' : 'source-over';
515
489
  },
516
490
 
517
491
  /**
@@ -519,7 +493,7 @@
519
493
  * @returns
520
494
  */
521
495
  needsFullRender: function () {
522
- return this.callSuper('needsFullRender') || this._drawOverlayOnTop;
496
+ return true;
523
497
  },
524
498
 
525
499
  /**
@@ -537,85 +511,144 @@
537
511
  // this allows to draw dots (when movement never occurs)
538
512
  this._captureDrawingPath(pointer);
539
513
 
514
+ // prepare for erasing
515
+ this.preparePattern();
540
516
  this._isErasing = true;
541
517
  this.canvas.fire('erasing:start');
542
- this._ready = true;
543
518
  this._render();
544
519
  },
545
520
 
546
521
  /**
547
- * Rendering is done in 4 steps:
548
- * 1. Draw all non-erasable objects on bottom ctx with the exception of overlays {@link fabric.EraserBrush#renderBottomLayer}
549
- * 2. Draw all objects on top ctx including erasable drawables {@link fabric.EraserBrush#renderTopLayer}
550
- * 3. Draw eraser {@link fabric.PencilBrush#_render} at {@link fabric.EraserBrush#renderTopLayer}
551
- * 4. Draw non-erasable overlays {@link fabric.EraserBrush#renderOverlay}
522
+ * Rendering Logic:
523
+ * 1. Use brush to clip canvas by rendering it on top of canvas (unnecessary if `inverted === true`)
524
+ * 2. Render brush with canvas pattern on top context
552
525
  *
553
- * @param {fabric.Canvas} canvas
554
526
  */
555
527
  _render: function () {
556
- if (!this._ready) {
557
- return;
528
+ var ctx;
529
+ if (!this.inverted) {
530
+ // clip canvas
531
+ ctx = this.canvas.getContext();
532
+ this.callSuper('_render', ctx);
558
533
  }
559
- this.isRendering = 1;
560
- this.renderBottomLayer();
561
- this.renderTopLayer();
562
- this.renderOverlay();
563
- this.isRendering = 0;
534
+ // render brush and mask it with image of non erasables
535
+ ctx = this.canvas.contextTop;
536
+ this.canvas.clearContext(ctx);
537
+ this.callSuper('_render', ctx);
538
+ ctx.save();
539
+ var t = this.canvas.getRetinaScaling(), s = 1 / t;
540
+ ctx.scale(s, s);
541
+ ctx.globalCompositeOperation = 'source-in';
542
+ ctx.drawImage(this._patternCanvas, 0, 0);
543
+ ctx.restore();
564
544
  },
565
545
 
566
546
  /**
567
- * @public
547
+ * Creates fabric.Path object
548
+ * @override
549
+ * @private
550
+ * @param {(string|number)[][]} pathData Path data
551
+ * @return {fabric.Path} Path to add on canvas
552
+ * @returns
568
553
  */
569
- render: function () {
570
- if (this._isErasing) {
571
- if (this.isRendering) {
572
- this.isRendering = fabric.util.requestAnimFrame(this._renderBound);
573
- }
574
- else {
575
- this._render();
576
- }
577
- return true;
578
- }
579
- return false;
554
+ createPath: function (pathData) {
555
+ var path = this.callSuper('createPath', pathData);
556
+ path.globalCompositeOperation = this.inverted ? 'source-over' : 'destination-out';
557
+ path.stroke = this.inverted ? 'white' : 'black';
558
+ return path;
559
+ },
560
+
561
+ /**
562
+ * Utility to apply a clip path to a path.
563
+ * Used to preserve clipping on eraser paths in nested objects.
564
+ * Called when a group has a clip path that should be applied to the path before applying erasing on the group's objects.
565
+ * @param {fabric.Path} path The eraser path in canvas coordinate plane
566
+ * @param {fabric.Object} clipPath The clipPath to apply to the path
567
+ * @param {number[]} clipPathContainerTransformMatrix The transform matrix of the object that the clip path belongs to
568
+ * @returns {fabric.Path} path with clip path
569
+ */
570
+ applyClipPathToPath: function (path, clipPath, clipPathContainerTransformMatrix) {
571
+ var pathInvTransform = fabric.util.invertTransform(path.calcTransformMatrix()),
572
+ clipPathTransform = clipPath.calcTransformMatrix(),
573
+ transform = clipPath.absolutePositioned ?
574
+ pathInvTransform :
575
+ fabric.util.multiplyTransformMatrices(
576
+ pathInvTransform,
577
+ clipPathContainerTransformMatrix
578
+ );
579
+ // when passing down a clip path it becomes relative to the parent
580
+ // so we transform it acoordingly and set `absolutePositioned` to false
581
+ clipPath.absolutePositioned = false;
582
+ fabric.util.applyTransformToObject(
583
+ clipPath,
584
+ fabric.util.multiplyTransformMatrices(
585
+ transform,
586
+ clipPathTransform
587
+ )
588
+ );
589
+ // We need to clip `path` with both `clipPath` and it's own clip path if existing (`path.clipPath`)
590
+ // so in turn `path` erases an object only where it overlaps with all it's clip paths, regardless of how many there are.
591
+ // this is done because both clip paths may have nested clip paths of their own (this method walks down a collection => this may reccur),
592
+ // so we can't assign one to the other's clip path property.
593
+ path.clipPath = path.clipPath ? fabric.util.mergeClipPaths(clipPath, path.clipPath) : clipPath;
594
+ return path;
595
+ },
596
+
597
+ /**
598
+ * Utility to apply a clip path to a path.
599
+ * Used to preserve clipping on eraser paths in nested objects.
600
+ * Called when a group has a clip path that should be applied to the path before applying erasing on the group's objects.
601
+ * @param {fabric.Path} path The eraser path
602
+ * @param {fabric.Object} object The clipPath to apply to path belongs to object
603
+ * @param {Function} callback Callback to be invoked with the cloned path after applying the clip path
604
+ */
605
+ clonePathWithClipPath: function (path, object, callback) {
606
+ var objTransform = object.calcTransformMatrix();
607
+ var clipPath = object.clipPath;
608
+ var _this = this;
609
+ path.clone(function (_path) {
610
+ clipPath.clone(function (_clipPath) {
611
+ callback(_this.applyClipPathToPath(_path, _clipPath, objTransform));
612
+ }, ['absolutePositioned', 'inverted']);
613
+ });
580
614
  },
581
615
 
582
616
  /**
583
- * Adds path to existing clipPath of object
617
+ * Adds path to object's eraser, walks down object's descendants if necessary
584
618
  *
619
+ * @fires erasing:end on object
585
620
  * @param {fabric.Object} obj
586
621
  * @param {fabric.Path} path
587
622
  */
588
623
  _addPathToObjectEraser: function (obj, path) {
589
- var clipObject;
590
624
  var _this = this;
591
625
  // object is collection, i.e group
592
- if (obj.forEachObject) {
593
- obj.forEachObject(function (_obj) {
594
- if (_obj.erasable) {
595
- _this._addPathToObjectEraser(_obj, path);
596
- }
626
+ if (obj.forEachObject && obj.erasable === 'deep') {
627
+ var targets = obj._objects.filter(function (_obj) {
628
+ return _obj.erasable;
597
629
  });
630
+ if (targets.length > 0 && obj.clipPath) {
631
+ this.clonePathWithClipPath(path, obj, function (_path) {
632
+ targets.forEach(function (_obj) {
633
+ _this._addPathToObjectEraser(_obj, _path);
634
+ });
635
+ });
636
+ }
637
+ else if (targets.length > 0) {
638
+ targets.forEach(function (_obj) {
639
+ _this._addPathToObjectEraser(_obj, path);
640
+ });
641
+ }
598
642
  return;
599
643
  }
600
- if (!obj.getEraser()) {
601
- var size = obj._getNonTransformedDimensions();
602
- var rect = new fabric.Rect({
603
- width: size.x,
604
- height: size.y,
605
- clipPath: obj.clipPath,
606
- originX: 'center',
607
- originY: 'center'
608
- });
609
- clipObject = new fabric.Group([rect], {
610
- eraser: true
611
- });
612
- }
613
- else {
614
- clipObject = obj.clipPath;
644
+ // prepare eraser
645
+ var eraser = obj.eraser;
646
+ if (!eraser) {
647
+ eraser = new fabric.Eraser();
648
+ obj.eraser = eraser;
615
649
  }
616
-
650
+ // clone and add path
617
651
  path.clone(function (path) {
618
- path.globalCompositeOperation = 'destination-out';
619
652
  // http://fabricjs.com/using-transformations
620
653
  var desiredTransform = fabric.util.multiplyTransformMatrices(
621
654
  fabric.util.invertTransform(
@@ -624,11 +657,14 @@
624
657
  path.calcTransformMatrix()
625
658
  );
626
659
  fabric.util.applyTransformToObject(path, desiredTransform);
627
- clipObject.addWithUpdate(path);
628
- obj.set({
629
- clipPath: clipObject,
630
- dirty: true
660
+ eraser.addWithUpdate(path);
661
+ obj.set('dirty', true);
662
+ obj.fire('erasing:end', {
663
+ path: path
631
664
  });
665
+ if (obj.group && Array.isArray(_this.__subTargets)) {
666
+ _this.__subTargets.push(obj);
667
+ }
632
668
  });
633
669
  },
634
670
 
@@ -644,9 +680,7 @@
644
680
  var drawables = {};
645
681
  [
646
682
  'backgroundImage',
647
- 'backgroundColor',
648
683
  'overlayImage',
649
- 'overlayColor',
650
684
  ].forEach(function (prop) {
651
685
  var drawable = canvas[prop];
652
686
  if (drawable && drawable.erasable) {
@@ -674,9 +708,9 @@
674
708
  this._isErasing = false;
675
709
 
676
710
  var pathData = this._points && this._points.length > 1 ?
677
- this.convertPointsToSVGPath(this._points).join('') :
678
- 'M 0 0 Q 0 0 0 0 L 0 0';
679
- if (pathData === 'M 0 0 Q 0 0 0 0 L 0 0') {
711
+ this.convertPointsToSVGPath(this._points) :
712
+ null;
713
+ if (!pathData || this._isEmptySVGPath(pathData)) {
680
714
  canvas.fire('erasing:end');
681
715
  // do not create 0 width/height paths, as they are
682
716
  // rendered inconsistently across browsers
@@ -687,23 +721,32 @@
687
721
  }
688
722
 
689
723
  var path = this.createPath(pathData);
724
+ // needed for `intersectsWithObject`
725
+ path.setCoords();
726
+ // commense event sequence
690
727
  canvas.fire('before:path:created', { path: path });
691
728
 
692
729
  // finalize erasing
693
730
  var drawables = this.applyEraserToCanvas(path);
694
731
  var _this = this;
732
+ this.__subTargets = [];
695
733
  var targets = [];
696
734
  canvas.forEachObject(function (obj) {
697
- if (obj.erasable && obj.intersectsWithObject(path)) {
735
+ if (obj.erasable && obj.intersectsWithObject(path, true, true)) {
698
736
  _this._addPathToObjectEraser(obj, path);
699
737
  targets.push(obj);
700
738
  }
701
739
  });
702
-
703
- canvas.fire('erasing:end', { path: path, targets: targets, drawables: drawables });
740
+ // fire erasing:end
741
+ canvas.fire('erasing:end', {
742
+ path: path,
743
+ targets: targets,
744
+ subTargets: this.__subTargets,
745
+ drawables: drawables
746
+ });
747
+ delete this.__subTargets;
704
748
 
705
749
  canvas.requestRenderAll();
706
- path.setCoords();
707
750
  this._resetShadow();
708
751
 
709
752
  // fire event 'path' created