diff --git a/package.json b/package.json
index 5d0c6f5..4894dc7 100644
--- a/package.json
+++ b/package.json
@@ -30,7 +30,7 @@
"electron-log": "^5.1.7",
"electron-updater": "^6.1.7",
"element-plus": "^2.7.6",
- "fabric-with-erasing": "^1.0.1",
+ "fabric": "^5.3.0",
"js-cookie": "^3.0.5",
"jsencrypt": "^3.3.2",
"jsondiffpatch": "0.6.0",
diff --git a/src/renderer/public/imgs/erase.svg b/src/renderer/public/imgs/erase.svg
deleted file mode 100644
index a746074..0000000
--- a/src/renderer/public/imgs/erase.svg
+++ /dev/null
@@ -1,3 +0,0 @@
-
\ No newline at end of file
diff --git a/src/renderer/src/plugins/fabric/index.js b/src/renderer/src/plugins/fabric/index.js
index 4d6926b..01cf1d7 100644
--- a/src/renderer/src/plugins/fabric/index.js
+++ b/src/renderer/src/plugins/fabric/index.js
@@ -1,10 +1,776 @@
/**
* @description 封装fabric js
*/
-// import { fabric } from 'fabric'
-import { fabric } from 'fabric-with-erasing'
+import { fabric } from 'fabric'
+// import { fabric } from 'fabric-with-erasing'
+// import fabric from './fabric'
+// import * as fabric from 'fabric'
import { diff, unpatch, patch } from 'jsondiffpatch'
+function baseBrush() {
+ /** ERASER_START */
+
+ /**
+ * add `eraser` to enlivened props
+ */
+ fabric.Object.ENLIVEN_PROPS.push('eraser');
+
+ var __drawClipPath = fabric.Object.prototype._drawClipPath;
+ var _needsItsOwnCache = fabric.Object.prototype.needsItsOwnCache;
+ var _toObject = fabric.Object.prototype.toObject;
+ var _getSvgCommons = fabric.Object.prototype.getSvgCommons;
+ var __createBaseClipPathSVGMarkup = fabric.Object.prototype._createBaseClipPathSVGMarkup;
+ var __createBaseSVGMarkup = fabric.Object.prototype._createBaseSVGMarkup;
+
+ fabric.Object.prototype.cacheProperties.push('eraser');
+ fabric.Object.prototype.stateProperties.push('eraser');
+
+ /**
+ * @fires erasing:end
+ */
+ fabric.util.object.extend(fabric.Object.prototype, {
+ /**
+ * Indicates whether this object can be erased by {@link fabric.EraserBrush}
+ * The `deep` option introduces fine grained control over a group's `erasable` property.
+ * When set to `deep` the eraser will erase nested objects if they are erasable, leaving the group and the other objects untouched.
+ * 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.
+ * When set to `false` the eraser will leave all objects including the group untouched.
+ * @tutorial {@link http://fabricjs.com/erasing#erasable_property}
+ * @type boolean | 'deep'
+ * @default true
+ */
+ erasable: true,
+
+ /**
+ * @tutorial {@link http://fabricjs.com/erasing#eraser}
+ * @type fabric.Eraser
+ */
+ eraser: undefined,
+
+ /**
+ * @override
+ * @returns Boolean
+ */
+ needsItsOwnCache: function () {
+ return _needsItsOwnCache.call(this) || !!this.eraser;
+ },
+
+ /**
+ * draw eraser above clip path
+ * @override
+ * @private
+ * @param {CanvasRenderingContext2D} ctx
+ * @param {fabric.Object} clipPath
+ */
+ _drawClipPath: function (ctx, clipPath) {
+ __drawClipPath.call(this, ctx, clipPath);
+ if (this.eraser) {
+ // update eraser size to match instance
+ var size = this._getNonTransformedDimensions();
+ this.eraser.isType('eraser') && this.eraser.set({
+ width: size.x,
+ height: size.y
+ });
+ __drawClipPath.call(this, ctx, this.eraser);
+ }
+ },
+
+ /**
+ * 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 (propertiesToInclude) {
+ var object = _toObject.call(this, ['erasable'].concat(propertiesToInclude));
+ if (this.eraser && !this.eraser.excludeFromExport) {
+ object.eraser = this.eraser.toObject(propertiesToInclude);
+ }
+ return object;
+ },
+
+ /* _TO_SVG_START_ */
+ /**
+ * Returns id attribute for svg output
+ * @override
+ * @return {String}
+ */
+ getSvgCommons: function () {
+ return _getSvgCommons.call(this) + (this.eraser ? 'mask="url(#' + this.eraser.clipPathId + ')" ' : '');
+ },
+
+ /**
+ * create svg markup for eraser
+ * use to achieve erasing for svg, credit: https://travishorn.com/removing-parts-of-shapes-in-svg-b539a89e5649
+ * must be called before object markup creation as it relies on the `clipPathId` property of the mask
+ * @param {Function} [reviver]
+ * @returns
+ */
+ _createEraserSVGMarkup: function (reviver) {
+ if (this.eraser) {
+ this.eraser.clipPathId = 'MASK_' + fabric.Object.__uid++;
+ return [
+ '',
+ this.eraser.toSVG(reviver),
+ '', '\n'
+ ].join('');
+ }
+ return '';
+ },
+
+ /**
+ * @private
+ */
+ _createBaseClipPathSVGMarkup: function (objectMarkup, options) {
+ return [
+ this._createEraserSVGMarkup(options && options.reviver),
+ __createBaseClipPathSVGMarkup.call(this, objectMarkup, options)
+ ].join('');
+ },
+
+ /**
+ * @private
+ */
+ _createBaseSVGMarkup: function (objectMarkup, options) {
+ return [
+ this._createEraserSVGMarkup(options && options.reviver),
+ __createBaseSVGMarkup.call(this, objectMarkup, options)
+ ].join('');
+ }
+ /* _TO_SVG_END_ */
+ });
+
+ var __restoreObjectsState = fabric.Group.prototype._restoreObjectsState;
+ fabric.util.object.extend(fabric.Group.prototype, {
+ /**
+ * @private
+ * @param {fabric.Path} path
+ */
+ _addEraserPathToObjects: function (path) {
+ this._objects.forEach(function (object) {
+ fabric.EraserBrush.prototype._addPathToObjectEraser.call(
+ fabric.EraserBrush.prototype,
+ object,
+ path
+ );
+ });
+ },
+
+ /**
+ * Applies the group's eraser to its objects
+ * @tutorial {@link http://fabricjs.com/erasing#erasable_property}
+ */
+ applyEraserToObjects: function () {
+ var _this = this, eraser = this.eraser;
+ if (eraser) {
+ delete this.eraser;
+ var transform = _this.calcTransformMatrix();
+ eraser.clone(function (eraser) {
+ var clipPath = _this.clipPath;
+ eraser.getObjects('path')
+ .forEach(function (path) {
+ // first we transform the path from the group's coordinate system to the canvas'
+ var originalTransform = fabric.util.multiplyTransformMatrices(
+ transform,
+ path.calcTransformMatrix()
+ );
+ fabric.util.applyTransformToObject(path, originalTransform);
+ if (clipPath) {
+ clipPath.clone(function (_clipPath) {
+ var eraserPath = fabric.EraserBrush.prototype.applyClipPathToPath.call(
+ fabric.EraserBrush.prototype,
+ path,
+ _clipPath,
+ transform
+ );
+ _this._addEraserPathToObjects(eraserPath);
+ }, ['absolutePositioned', 'inverted']);
+ }
+ else {
+ _this._addEraserPathToObjects(path);
+ }
+ });
+ });
+ }
+ },
+
+ /**
+ * Propagate the group's eraser to its objects, crucial for proper functionality of the eraser within the group and nested objects.
+ * @private
+ */
+ _restoreObjectsState: function () {
+ this.erasable === true && this.applyEraserToObjects();
+ return __restoreObjectsState.call(this);
+ }
+ });
+
+ /**
+ * An object's Eraser
+ * @private
+ * @class fabric.Eraser
+ * @extends fabric.Group
+ * @memberof fabric
+ */
+ fabric.Eraser = fabric.util.createClass(fabric.Group, {
+ /**
+ * @readonly
+ * @static
+ */
+ type: 'eraser',
+
+ /**
+ * @default
+ */
+ originX: 'center',
+
+ /**
+ * @default
+ */
+ originY: 'center',
+
+ drawObject: function (ctx) {
+ ctx.save();
+ ctx.fillStyle = 'black';
+ ctx.fillRect(-this.width / 2, -this.height / 2, this.width, this.height);
+ ctx.restore();
+ this.callSuper('drawObject', ctx);
+ },
+
+ /**
+ * eraser should retain size
+ * dimensions should not change when paths are added or removed
+ * handled by {@link fabric.Object#_drawClipPath}
+ * @override
+ * @private
+ */
+ _getBounds: function () {
+ // noop
+ },
+
+ /* _TO_SVG_START_ */
+ /**
+ * Returns svg representation of an instance
+ * use to achieve erasing for svg, credit: https://travishorn.com/removing-parts-of-shapes-in-svg-b539a89e5649
+ * for masking we need to add a white rect before all paths
+ *
+ * @param {Function} [reviver] Method for further parsing of svg representation.
+ * @return {String} svg representation of an instance
+ */
+ _toSVG: function (reviver) {
+ var svgString = ['\n'];
+ var x = -this.width / 2, y = -this.height / 2;
+ var rectSvg = [
+ '\n'
+ ].join('');
+ svgString.push('\t\t', rectSvg);
+ for (var i = 0, len = this._objects.length; i < len; i++) {
+ svgString.push('\t\t', this._objects[i].toSVG(reviver));
+ }
+ svgString.push('\n');
+ return svgString;
+ },
+ /* _TO_SVG_END_ */
+ });
+
+ /**
+ * Returns {@link fabric.Eraser} instance from an object representation
+ * @static
+ * @memberOf fabric.Eraser
+ * @param {Object} object Object to create an Eraser from
+ * @param {Function} [callback] Callback to invoke when an eraser instance is created
+ */
+ fabric.Eraser.fromObject = function (object, callback) {
+ var objects = object.objects;
+ fabric.util.enlivenObjects(objects, function (enlivenedObjects) {
+ var options = fabric.util.object.clone(object, true);
+ delete options.objects;
+ fabric.util.enlivenObjectEnlivables(object, options, function () {
+ callback && callback(new fabric.Eraser(enlivenedObjects, options, true));
+ });
+ });
+ };
+
+ var __renderOverlay = fabric.Canvas.prototype._renderOverlay;
+ /**
+ * @fires erasing:start
+ * @fires erasing:end
+ */
+ 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 clips out the erasing path from canvas
+ * so we need to render it on top of canvas every render
+ * @param {CanvasRenderingContext2D} ctx
+ */
+ _renderOverlay: function (ctx) {
+ __renderOverlay.call(this, ctx);
+ if (this.isErasing() && !this.freeDrawingBrush.inverted) {
+ this.freeDrawingBrush._render();
+ }
+ }
+ });
+
+ /**
+ * EraserBrush class
+ * Supports selective erasing meaning that only erasable objects are affected by the eraser brush.
+ * Supports **inverted** erasing meaning that the brush can "undo" erasing.
+ *
+ * In order to support selective erasing, the brush clips the entire canvas
+ * and then draws all non-erasable objects over the erased path using a pattern brush so to speak (masking).
+ * If brush is **inverted** there is no need to clip canvas. The brush draws all erasable objects without their eraser.
+ * This achieves the desired effect of seeming to erase or unerase only erasable objects.
+ * After erasing is done the created path is added to all intersected objects' `eraser` property.
+ *
+ * In order to update the EraserBrush call `preparePattern`.
+ * It may come in handy when canvas changes during erasing (i.e animations) and you want the eraser to reflect the changes.
+ *
+ * @tutorial {@link http://fabricjs.com/erasing}
+ * @class fabric.EraserBrush
+ * @extends fabric.PencilBrush
+ * @memberof fabric
+ */
+ fabric.EraserBrush = fabric.util.createClass(
+ fabric.PencilBrush,
+ /** @lends fabric.EraserBrush.prototype */ {
+ type: 'eraser',
+
+ /**
+ * When set to `true` the brush will create a visual effect of undoing erasing
+ */
+ inverted: false,
+
+ /**
+ * @private
+ */
+ _isErasing: false,
+
+ /**
+ *
+ * @private
+ * @param {fabric.Object} object
+ * @returns boolean
+ */
+ _isErasable: function (object) {
+ return object.erasable !== false;
+ },
+
+ /**
+ * @private
+ * This is designed to support erasing a collection with both erasable and non-erasable objects.
+ * Iterates over collections to allow nested selective erasing.
+ * Prepares the pattern brush that will draw on the top context to achieve the desired visual effect.
+ * If brush is **NOT** inverted render all non-erasable objects.
+ * If brush is inverted render all erasable objects that have been erased with their clip path inverted.
+ * This will render the erased parts as if they were not erased.
+ *
+ * @param {fabric.Collection} collection
+ * @param {CanvasRenderingContext2D} ctx
+ * @param {{ visibility: fabric.Object[], eraser: fabric.Object[], collection: fabric.Object[] }} restorationContext
+ */
+ _prepareCollectionTraversal: function (collection, ctx, restorationContext) {
+ collection.forEachObject(function (obj) {
+ if (obj.forEachObject && obj.erasable === 'deep') {
+ // traverse
+ this._prepareCollectionTraversal(obj, ctx, restorationContext);
+ }
+ else if (!this.inverted && obj.erasable && obj.visible) {
+ // render only non-erasable objects
+ obj.visible = false;
+ collection.dirty = true;
+ restorationContext.visibility.push(obj);
+ restorationContext.collection.push(collection);
+ }
+ else if (this.inverted && obj.visible) {
+ // render only erasable objects that were erased
+ if (obj.erasable && obj.eraser) {
+ obj.eraser.inverted = true;
+ obj.dirty = true;
+ collection.dirty = true;
+ restorationContext.eraser.push(obj);
+ restorationContext.collection.push(collection);
+ }
+ else {
+ obj.visible = false;
+ collection.dirty = true;
+ restorationContext.visibility.push(obj);
+ restorationContext.collection.push(collection);
+ }
+ }
+ }, this);
+ },
+
+ /**
+ * Prepare the pattern for the erasing brush
+ * This pattern will be drawn on the top context, achieving a visual effect of erasing only erasable objects
+ * @todo decide how overlay color should behave when `inverted === true`, currently draws over it which is undesirable
+ * @private
+ */
+ preparePattern: function () {
+ if (!this._patternCanvas) {
+ this._patternCanvas = fabric.util.createCanvasElement();
+ }
+ var canvas = this._patternCanvas;
+ canvas.width = this.canvas.width;
+ canvas.height = this.canvas.height;
+ var patternCtx = canvas.getContext('2d');
+ if (this.canvas._isRetinaScaling()) {
+ var retinaScaling = this.canvas.getRetinaScaling();
+ this.canvas.__initRetinaScaling(retinaScaling, canvas, patternCtx);
+ }
+ var backgroundImage = this.canvas.backgroundImage,
+ bgErasable = backgroundImage && this._isErasable(backgroundImage),
+ overlayImage = this.canvas.overlayImage,
+ overlayErasable = overlayImage && this._isErasable(overlayImage);
+ if (!this.inverted && ((backgroundImage && !bgErasable) || !!this.canvas.backgroundColor)) {
+ if (bgErasable) { this.canvas.backgroundImage = undefined; }
+ this.canvas._renderBackground(patternCtx);
+ if (bgErasable) { this.canvas.backgroundImage = backgroundImage; }
+ }
+ else if (this.inverted && (backgroundImage && bgErasable)) {
+ var color = this.canvas.backgroundColor;
+ this.canvas.backgroundColor = undefined;
+ this.canvas._renderBackground(patternCtx);
+ this.canvas.backgroundColor = color;
+ }
+ patternCtx.save();
+ patternCtx.transform.apply(patternCtx, this.canvas.viewportTransform);
+ var restorationContext = { visibility: [], eraser: [], collection: [] };
+ this._prepareCollectionTraversal(this.canvas, patternCtx, restorationContext);
+ this.canvas._renderObjects(patternCtx, this.canvas._objects);
+ restorationContext.visibility.forEach(function (obj) { obj.visible = true; });
+ restorationContext.eraser.forEach(function (obj) {
+ obj.eraser.inverted = false;
+ obj.dirty = true;
+ });
+ restorationContext.collection.forEach(function (obj) { obj.dirty = true; });
+ patternCtx.restore();
+ if (!this.inverted && ((overlayImage && !overlayErasable) || !!this.canvas.overlayColor)) {
+ if (overlayErasable) { this.canvas.overlayImage = undefined; }
+ __renderOverlay.call(this.canvas, patternCtx);
+ if (overlayErasable) { this.canvas.overlayImage = overlayImage; }
+ }
+ else if (this.inverted && (overlayImage && overlayErasable)) {
+ var color = this.canvas.overlayColor;
+ this.canvas.overlayColor = undefined;
+ __renderOverlay.call(this.canvas, patternCtx);
+ this.canvas.overlayColor = color;
+ }
+ },
+
+ /**
+ * Sets brush styles
+ * @private
+ * @param {CanvasRenderingContext2D} ctx
+ */
+ _setBrushStyles: function (ctx) {
+ this.callSuper('_setBrushStyles', ctx);
+ ctx.strokeStyle = 'black';
+ },
+
+ /**
+ * **Customiztion**
+ *
+ * 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):
+ * @example
+ * ```
+ * if(ctx === this.canvas.contextTop) {
+ * this.preparePattern();
+ * }
+ * ```
+ *
+ * @override fabric.BaseBrush#_saveAndTransform
+ * @param {CanvasRenderingContext2D} ctx
+ */
+ _saveAndTransform: function (ctx) {
+ this.callSuper('_saveAndTransform', ctx);
+ this._setBrushStyles(ctx);
+ ctx.globalCompositeOperation = ctx === this.canvas.getContext() ? 'destination-out' : 'source-over';
+ },
+
+ /**
+ * We indicate {@link fabric.PencilBrush} to repaint itself if necessary
+ * @returns
+ */
+ needsFullRender: function () {
+ return true;
+ },
+
+ /**
+ *
+ * @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);
+
+ // prepare for erasing
+ this.preparePattern();
+ this._isErasing = true;
+ this.canvas.fire('erasing:start');
+ this._render();
+ },
+
+ /**
+ * Rendering Logic:
+ * 1. Use brush to clip canvas by rendering it on top of canvas (unnecessary if `inverted === true`)
+ * 2. Render brush with canvas pattern on top context
+ *
+ */
+ _render: function () {
+ var ctx;
+ if (!this.inverted) {
+ // clip canvas
+ ctx = this.canvas.getContext();
+ this.callSuper('_render', ctx);
+ }
+ // render brush and mask it with image of non erasables
+ ctx = this.canvas.contextTop;
+ this.canvas.clearContext(ctx);
+ this.callSuper('_render', ctx);
+ ctx.save();
+ var t = this.canvas.getRetinaScaling(), s = 1 / t;
+ ctx.scale(s, s);
+ ctx.globalCompositeOperation = 'source-in';
+ ctx.drawImage(this._patternCanvas, 0, 0);
+ ctx.restore();
+ },
+
+ /**
+ * Creates fabric.Path object
+ * @override
+ * @private
+ * @param {(string|number)[][]} pathData Path data
+ * @return {fabric.Path} Path to add on canvas
+ * @returns
+ */
+ createPath: function (pathData) {
+ var path = this.callSuper('createPath', pathData);
+ path.globalCompositeOperation = this.inverted ? 'source-over' : 'destination-out';
+ path.stroke = this.inverted ? 'white' : 'black';
+ return path;
+ },
+
+ /**
+ * Utility to apply a clip path to a path.
+ * Used to preserve clipping on eraser paths in nested objects.
+ * Called when a group has a clip path that should be applied to the path before applying erasing on the group's objects.
+ * @param {fabric.Path} path The eraser path in canvas coordinate plane
+ * @param {fabric.Object} clipPath The clipPath to apply to the path
+ * @param {number[]} clipPathContainerTransformMatrix The transform matrix of the object that the clip path belongs to
+ * @returns {fabric.Path} path with clip path
+ */
+ applyClipPathToPath: function (path, clipPath, clipPathContainerTransformMatrix) {
+ var pathInvTransform = fabric.util.invertTransform(path.calcTransformMatrix()),
+ clipPathTransform = clipPath.calcTransformMatrix(),
+ transform = clipPath.absolutePositioned ?
+ pathInvTransform :
+ fabric.util.multiplyTransformMatrices(
+ pathInvTransform,
+ clipPathContainerTransformMatrix
+ );
+ // when passing down a clip path it becomes relative to the parent
+ // so we transform it acoordingly and set `absolutePositioned` to false
+ clipPath.absolutePositioned = false;
+ fabric.util.applyTransformToObject(
+ clipPath,
+ fabric.util.multiplyTransformMatrices(
+ transform,
+ clipPathTransform
+ )
+ );
+ // We need to clip `path` with both `clipPath` and it's own clip path if existing (`path.clipPath`)
+ // so in turn `path` erases an object only where it overlaps with all it's clip paths, regardless of how many there are.
+ // this is done because both clip paths may have nested clip paths of their own (this method walks down a collection => this may reccur),
+ // so we can't assign one to the other's clip path property.
+ path.clipPath = path.clipPath ? fabric.util.mergeClipPaths(clipPath, path.clipPath) : clipPath;
+ return path;
+ },
+
+ /**
+ * Utility to apply a clip path to a path.
+ * Used to preserve clipping on eraser paths in nested objects.
+ * Called when a group has a clip path that should be applied to the path before applying erasing on the group's objects.
+ * @param {fabric.Path} path The eraser path
+ * @param {fabric.Object} object The clipPath to apply to path belongs to object
+ * @param {Function} callback Callback to be invoked with the cloned path after applying the clip path
+ */
+ clonePathWithClipPath: function (path, object, callback) {
+ var objTransform = object.calcTransformMatrix();
+ var clipPath = object.clipPath;
+ var _this = this;
+ path.clone(function (_path) {
+ clipPath.clone(function (_clipPath) {
+ callback(_this.applyClipPathToPath(_path, _clipPath, objTransform));
+ }, ['absolutePositioned', 'inverted']);
+ });
+ },
+
+ /**
+ * Adds path to object's eraser, walks down object's descendants if necessary
+ *
+ * @fires erasing:end on object
+ * @param {fabric.Object} obj
+ * @param {fabric.Path} path
+ */
+ _addPathToObjectEraser: function (obj, path) {
+ var _this = this;
+ // object is collection, i.e group
+ if (obj.forEachObject && obj.erasable === 'deep') {
+ var targets = obj._objects.filter(function (_obj) {
+ return _obj.erasable;
+ });
+ if (targets.length > 0 && obj.clipPath) {
+ this.clonePathWithClipPath(path, obj, function (_path) {
+ targets.forEach(function (_obj) {
+ _this._addPathToObjectEraser(_obj, _path);
+ });
+ });
+ }
+ else if (targets.length > 0) {
+ targets.forEach(function (_obj) {
+ _this._addPathToObjectEraser(_obj, path);
+ });
+ }
+ return;
+ }
+ // prepare eraser
+ var eraser = obj.eraser;
+ if (!eraser) {
+ eraser = new fabric.Eraser();
+ obj.eraser = eraser;
+ }
+ // clone and add path
+ path.clone(function (path) {
+ // http://fabricjs.com/using-transformations
+ var desiredTransform = fabric.util.multiplyTransformMatrices(
+ fabric.util.invertTransform(
+ obj.calcTransformMatrix()
+ ),
+ path.calcTransformMatrix()
+ );
+ fabric.util.applyTransformToObject(path, desiredTransform);
+ eraser.addWithUpdate(path);
+ obj.set('dirty', true);
+ obj.fire('erasing:end', {
+ path: path
+ });
+ if (obj.group && Array.isArray(_this.__subTargets)) {
+ _this.__subTargets.push(obj);
+ }
+ });
+ },
+
+ /**
+ * 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',
+ 'overlayImage',
+ ].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);
+ // needed for `intersectsWithObject`
+ path.setCoords();
+ // commense event sequence
+ canvas.fire('before:path:created', { path: path });
+
+ // finalize erasing
+ var drawables = this.applyEraserToCanvas(path);
+ var _this = this;
+ this.__subTargets = [];
+ var targets = [];
+ canvas.forEachObject(function (obj) {
+ if (obj.erasable && obj.intersectsWithObject(path, true, true)) {
+ _this._addPathToObjectEraser(obj, path);
+ targets.push(obj);
+ }
+ });
+ // fire erasing:end
+ canvas.fire('erasing:end', {
+ path: path,
+ targets: targets,
+ subTargets: this.__subTargets,
+ drawables: drawables
+ });
+ delete this.__subTargets;
+
+ canvas.requestRenderAll();
+ this._resetShadow();
+
+ // fire event 'path' created
+ canvas.fire('path:created', { path: path });
+ }
+ }
+ );
+
+ /** ERASER_END */
+}
+baseBrush() // 加载橡皮擦到组件上
// 当前使用到的常量|类型(枚举) ============================
export class TYPES {
static ActionMode = {
@@ -378,10 +1144,21 @@ export const FreeStyle = {
const canvas = fabricVue?.canvas
const drawConfig = fabricVue?.drawConfig
const eraserBrush = new fabric.EraserBrush(canvas)
+ const width = Utils.getWidth(drawConfig.eraserWidth, fabricVue)
canvas.isDrawingMode = true
canvas.freeDrawingBrush = eraserBrush
- canvas.freeDrawingBrush.width = Utils.getWidth(drawConfig.eraserWidth, fabricVue)
+ canvas.freeDrawingBrush.width = width
canvas.freeDrawingBrush.color = '#FFF'
+ // FabricVue.canvas.freeDrawingCursor = `url(/imgs/erase.svg) 10 10,crosshair`
+ canvas.freeDrawingCursor = FreeStyle.reaserSvg(width)
+ },
+ reaserSvg: (width, color = '#ccc') => { // 橡皮擦-鼠标样式(自定义)
+ const svg = `
+ `
+ const svgUrl = `data:image/svg+xml;base64,${btoa(svg)}`
+ return `url(${svgUrl}) ${width/2} ${width}, crosshair`
}
}
// 事件类
@@ -600,8 +1377,8 @@ export class CanvasEvent {
}
// 移除事件
removeEvent() {
- this.windowEvent.removeWindowEvent()
- this.touchEvent.removeTouchEvent()
+ this.windowEvent?.removeWindowEvent()
+ this.touchEvent?.removeTouchEvent()
}
}
// 历史类
@@ -860,7 +1637,7 @@ export class fabricVue {
*/
deleteObject() {
// Disable deletion in text input state
- if (this.textElement.isTextEditing) {
+ if (this.textElement?.isTextEditing) {
return
}
if (this.canvas) {
@@ -1010,5 +1787,4 @@ export class fabricVue {
}
export const FabricVue = new fabricVue()
export default FabricVue
-export const Fabric = fabric
diff --git a/src/renderer/src/views/tool/components/board.vue b/src/renderer/src/views/tool/components/board.vue
index 6a501a4..435112f 100644
--- a/src/renderer/src/views/tool/components/board.vue
+++ b/src/renderer/src/views/tool/components/board.vue
@@ -5,19 +5,21 @@
diff --git a/src/renderer/src/views/tool/sphere.vue b/src/renderer/src/views/tool/sphere.vue
index 51d39dc..3117e88 100644
--- a/src/renderer/src/views/tool/sphere.vue
+++ b/src/renderer/src/views/tool/sphere.vue
@@ -1,6 +1,6 @@
-
+
@@ -47,6 +47,7 @@ const tabChange = (val) => { // 切换tab-change
case 'brush':
break
case 'eraser':
+
break
case 'interact':
break
diff --git a/src/renderer/src/views/tool/test.vue b/src/renderer/src/views/tool/test.vue
index 1deb706..61fcc59 100644
--- a/src/renderer/src/views/tool/test.vue
+++ b/src/renderer/src/views/tool/test.vue
@@ -2,8 +2,8 @@
+