diff --git a/package.json b/package.json index 7893b0b..5d0c6f5 100644 --- a/package.json +++ b/package.json @@ -21,8 +21,8 @@ "dependencies": { "@electron-toolkit/preload": "^3.0.1", "@electron-toolkit/utils": "^3.0.0", - "@element-plus/icons-vue": "^2.3.1", "@electron/remote": "^2.1.2", + "@element-plus/icons-vue": "^2.3.1", "@vitejs/plugin-vue-jsx": "^4.0.0", "@vueuse/core": "^10.11.0", "crypto-js": "^4.2.0", @@ -30,12 +30,12 @@ "electron-log": "^5.1.7", "electron-updater": "^6.1.7", "element-plus": "^2.7.6", - "fabric": "5.3.0", + "fabric-with-erasing": "^1.0.1", "js-cookie": "^3.0.5", "jsencrypt": "^3.3.2", "jsondiffpatch": "0.6.0", - "pdfjs-dist": "4.4.168", "lodash": "^4.17.21", + "pdfjs-dist": "4.4.168", "pinia": "^2.1.7", "pinia-plugin-persistedstate": "^3.2.1", "spark-md5": "^3.0.2", diff --git a/src/renderer/src/plugins/fabric/event/clickEvent.js b/src/renderer/src/plugins/fabric/event/clickEvent.js deleted file mode 100644 index 5a70293..0000000 --- a/src/renderer/src/plugins/fabric/event/clickEvent.js +++ /dev/null @@ -1,236 +0,0 @@ -/** - * @description: 点击事件 - */ -import { FabricVue } from '@/plugins/fabric' -import * as TYPES from '@/constants/draw' -import * as fabricStore from '@/store/modules/draw' - -export let updateInkHook = null // ((ink: IInk[]) => void) | null - -export class CanvasClickEvent { - isMouseDown = false - isSpaceKeyDown = false - startPoint // fabric.Point | undefined - currentElement = null // The current mouse move draws the element|当前鼠标移动会绘制元素 - - mouseDownTime = 0 - autoDrawInk = [[], [], []] // google auto draw ink Array> - isDrawBasic = false // 是否默认绘制 - - constructor() { - this.initClickEvent() - } - - initClickEvent() { - const canvas = FabricVue.canvas - const useDrawStore = fabricStore.useDrawStore() - const useBoardStore = fabricStore.useBoardStore() - - // 事件:按下鼠标 - canvas?.on('mouse:down', (e) => { - console.log(222222222222222222222222222) - - this.isMouseDown = true - if (this.isSpaceKeyDown) { - return - } - this.startPoint = e.absolutePointer - let currentElement = null - - if (useBoardStore.mode === TYPES.ActionMode.DRAW) { - if (useBoardStore.drawType === TYPES.DrawType.Shape) { - // switch (useShapeStore.shapeStyle) { - // case ShapeStyle.Rect: - // currentElement = new RectShape(e.absolutePointer) - // break - // case ShapeStyle.Circle: - // currentElement = new CircleShape(e.absolutePointer) - // break - // case ShapeStyle.Line: - // currentElement = new LineShape(e.absolutePointer) - // break - // case ShapeStyle.Ellipse: - // currentElement = new EllipseShape(e.absolutePointer) - // break - // case ShapeStyle.Triangle: - // currentElement = new TriangleShape(e.absolutePointer) - // break - // case ShapeStyle.ArrowLine: - // currentElement = new ArrowLineShape(e.absolutePointer) - // break - // case ShapeStyle.ArrowOutline: - // currentElement = new ArrowOutlineShape(e.absolutePointer) - // break - // case ShapeStyle.Cloud: - // currentElement = new CloudShape(e.absolutePointer) - // break - // case ShapeStyle.Tooltips: - // currentElement = new TooltipsShape(e.absolutePointer) - // break - // case ShapeStyle.Lightning: - // currentElement = new LightningShape(e.absolutePointer) - // break - // case ShapeStyle.Close: - // currentElement = new CloseShape(e.absolutePointer) - // break - // case ShapeStyle.Check: - // currentElement = new CheckShap(e.absolutePointer) - // break - // case ShapeStyle.Info: - // currentElement = new InfoShape(e.absolutePointer) - // break - // case ShapeStyle.Backspace: - // currentElement = new BackspaceShape(e.absolutePointer) - // break - // case ShapeStyle.Block: - // currentElement = new BlockShap(e.absolutePointer) - // break - // case ShapeStyle.Speaker: - // currentElement = new SpeakerShape(e.absolutePointer) - // break - // case ShapeStyle.Search: - // currentElement = new SearchShape(e.absolutePointer) - // break - // case ShapeStyle.InfoOutline: - // currentElement = new InfoOutlineShape(e.absolutePointer) - // break - // case ShapeStyle.Heart: - // currentElement = new HeartShape(e.absolutePointer) - // break - // case ShapeStyle.Alert: - // currentElement = new AlertShape(e.absolutePointer) - // break - // default: - // break - // } - } else if (useBoardStore.drawType === TYPES.DrawType.FreeStyle) { - switch (useDrawStore.drawStyle) { - // case TYPES.DrawStyle.Shape: - // currentElement = new ShapeElement() - // break - // case TYPES.DrawStyle.Pixels: - // currentElement = new PixelsElement() - // break - // case TYPES.DrawStyle.Text: - // currentElement = new DrawTextElement() - // break - // case TYPES.DrawStyle.MultiLine: - // currentElement = new MultiLineElement() - // break - // case TYPES.DrawStyle.Reticulate: - // currentElement = new ReticulateElement() - // break - // case TYPES.DrawStyle.Rainbow: - // currentElement = new RainbowElement() - // break - // case TYPES.DrawStyle.Thorn: - // currentElement = new ThornElement() - // break - // case TYPES.DrawStyle.MultiPoint: - // currentElement = new MultiPointElement() - // break - // case TYPES.DrawStyle.Wiggle: - // currentElement = new WiggleElement() - // break - case TYPES.DrawStyle.Basic: - // if (useDrawStore.openAutoDraw) { - // autoDrawData.resetLoadedSVG() - // this.mouseDownTime = new Date().getTime() - // } - this.isDrawBasic = true - break - default: - break - } - } - } - this.currentElement = currentElement - }) - - // 事件:移动鼠标 - canvas?.on('mouse:move', (e) => { - console.log(222222222222222222222222222) - - if (this.isMouseDown) { - // Press space, drag the canvas, stop drawing. - if (this.isSpaceKeyDown) { - canvas.relativePan(new fabric.Point(e.e.movementX, e.e.movementY)) - return - } - - // two touch disabled drawing on mobile - if (FabricVue.evnet?.touchEvent?.isTwoTouch) { - return - } - - if ( - this.mouseDownTime && - e.absolutePointer?.x && - e.absolutePointer?.y - ) { - this.autoDrawInk[0].push(e.absolutePointer?.x) - this.autoDrawInk[1].push(e.absolutePointer?.y) - this.autoDrawInk[2].push(new Date().getTime() - this.mouseDownTime) - } - - if ( - useBoardStore.mode === TYPES.ActionMode.DRAW && this.currentElement - ) { - this.currentElement.addPosition(e.absolutePointer) - } - } - }) - - // 事件:松开鼠标 - canvas?.on('mouse:up', (e) => { - console.log(222222222222222222222222222) - - this.isMouseDown = false - if (this.autoDrawInk?.[0]?.length > 3) { - autoDrawData.addInk([...this.autoDrawInk]) - updateInkHook?.([...autoDrawData.inks]) - this.autoDrawInk = [[], [], []] - this.mouseDownTime = 0 - } - - if (this.currentElement) { - let isDestroy = false - if (this.startPoint && e.absolutePointer) { - const { x: startX, y: startY } = this.startPoint - const { x: endX, y: endY } = e.absolutePointer - if (startX === endX && startY === endY) { - this.currentElement.destroy() - isDestroy = true - } - } - if (!isDestroy) { - if ( - this.currentElement instanceof LineShape || - this.currentElement instanceof ArrowLineShape - ) { - this.currentElement?.mouseUp() - } - FabricVue.history?.saveState() - } - this.currentElement = null - } - // zdg: 基础画笔 保存数据 - if (this.isDrawBasic) FabricVue.history?.saveState() - }) - - canvas?.on('mouse:dblclick', (e) => { - if (e?.absolutePointer) { - const { x, y } = e.absolutePointer - FabricVue.textElement?.loadText(x, y) - } - }) - } - - setSpaceKeyDownState(isSpaceKeyDown) { - this.isSpaceKeyDown = isSpaceKeyDown - } - - changeInkHookFn(fn) { - updateInkHook = fn - } -} \ No newline at end of file diff --git a/src/renderer/src/plugins/fabric/event/index.js b/src/renderer/src/plugins/fabric/event/index.js deleted file mode 100644 index 263d152..0000000 --- a/src/renderer/src/plugins/fabric/event/index.js +++ /dev/null @@ -1,35 +0,0 @@ -import { CanvasClickEvent } from './clickEvent' -// import { ObjectEvent } from './objectEvent' -// import { CanvasTouchEvent } from './touchEvent' -// import { CanvasZoomEvent } from './zoomEvent' -// import { WindowEvent } from './windowEvent' - -export class CanvasEvent { - clickEvent // CanvasClickEvent - zoomEvent // CanvasZoomEvent - objectEvent // ObjectEvent - windowEvent // WindowEvent - touchEvent // CanvasTouchEvent - - constructor() { - const clickEvent = new CanvasClickEvent() - this.clickEvent = clickEvent - - // const zoomEvent = new CanvasZoomEvent() - // this.zoomEvent = zoomEvent - - // const objectEvent = new ObjectEvent() - // this.objectEvent = objectEvent - - // const windowEvent = new WindowEvent() - // this.windowEvent = windowEvent - - // const touchEvent = new CanvasTouchEvent() - // this.touchEvent = touchEvent - } - - removeEvent() { - this.windowEvent.removeWindowEvent() - this.touchEvent.removeTouchEvent() - } -} diff --git a/src/renderer/src/plugins/fabric/history.js b/src/renderer/src/plugins/fabric/history.js deleted file mode 100644 index ab8cf15..0000000 --- a/src/renderer/src/plugins/fabric/history.js +++ /dev/null @@ -1,120 +0,0 @@ -/** - * 历史记录 - */ -import { FabricVue } from '@/plugins/fabric' -import * as commUtils from '@/plugins/fabric/utils' -import * as bgUtils from '@/plugins/fabric/utils/bg' -import { diff, unpatch, patch } from 'jsondiffpatch' -import * as fabricStore from '@/store/modules/draw' - -const initState = {} - - -/** - * Operation History - */ -export class History { - diffs = [] - canvasData = {} - index = 0 - - constructor() { - const canvas = FabricVue.canvas - if (canvas) { - const canvasJson = commUtils.getCanvasJSON() - this.canvasData = cloneDeep(canvasJson ?? {}) - } - } - - // 缓存相关数据json - saveState() { - const canvas = FabricVue?.canvas - if (canvas) { - const useFileStore = fabricStore.useFileStore() - this.diffs = this.diffs.slice(0, this.index) - const canvasJson = commUtils.getCanvasJSON() - const delta = diff(canvasJson, this.canvasData) - this.diffs.push(delta) - - // More than 50 operations, remove initial state - if (this.diffs.length > 50) { - this.diffs.shift() - } else { - this.index++ - } - this.canvasData = cloneDeep(canvasJson ?? {}) - useFileStore.updateBoardData(canvasJson) - } - } - // 撤销 - undo() { - const canvas = FabricVue?.canvas - if (canvas && this.index > 0) { - const useFileStore = fabricStore.useFileStore() - const delta = this.diffs[this.index - 1] - this.index-- - const canvasJson = patch(this.canvasData, delta) - canvas.loadFromJSON(canvasJson, () => { - commUtils.handleCanvasJSONLoaded(canvas) - - canvas.requestRenderAll() - useFileStore.updateBoardData(canvasJson) - this.canvasData = cloneDeep(canvasJson ?? {}) - FabricVue.triggerHook() - - if ((delta)?.backgroundImage) { - handleBackgroundImageWhenCanvasSizeChange() - } - }) - } - } - // 退回|重做 - redo() { - const canvas = FabricVue?.canvas - if (this.index < this.diffs.length && canvas) { - const useFileStore = fabricStore.useFileStore() - const delta = this.diffs[this.index] - this.index++ - const canvasJson = unpatch(this.canvasData, delta) - canvas.loadFromJSON(canvasJson, () => { - handleCanvasJSONLoaded(canvas) - canvas.requestRenderAll() - - useFileStore.updateBoardData(canvasJson) - this.canvasData = cloneDeep(canvasJson ?? {}) - FabricVue.triggerHook() - - if ((delta)?.backgroundImage) { - bgUtils.handleBackgroundImageWhenCanvasSizeChange() - } - }) - } - } - - clean() { - const useFileStore = fabricStore.useFileStore() - const useBoardStore = fabricStore.useBoardStore() - FabricVue?.canvas?.clear() - this.index = 0 - this.diffs = [] - this.canvasData = {} - useFileStore.updateBoardData(initState) - useBoardStore.updateBackgroundColor('#ffffff') - useBoardStore.cleanBackgroundImage() - } - - initHistory() { - const canvas = FabricVue.canvas - if (canvas) { - const canvasJson = commUtils.getCanvasJSON() - this.canvasData = canvasJson - this.index = 0 - this.diffs = [] - } - } -} - -// 对象克隆 -function cloneDeep(obj = {}) { - return JSON.parse(JSON.stringify(obj)) -} \ No newline at end of file diff --git a/src/renderer/src/i18n/en.json b/src/renderer/src/plugins/fabric/i18n/en.json similarity index 100% rename from src/renderer/src/i18n/en.json rename to src/renderer/src/plugins/fabric/i18n/en.json diff --git a/src/renderer/src/i18n/index.js b/src/renderer/src/plugins/fabric/i18n/index.js similarity index 100% rename from src/renderer/src/i18n/index.js rename to src/renderer/src/plugins/fabric/i18n/index.js diff --git a/src/renderer/src/i18n/zh.json b/src/renderer/src/plugins/fabric/i18n/zh.json similarity index 100% rename from src/renderer/src/i18n/zh.json rename to src/renderer/src/plugins/fabric/i18n/zh.json diff --git a/src/renderer/src/plugins/fabric/index.js b/src/renderer/src/plugins/fabric/index.js index 277112f..cb16fb9 100644 --- a/src/renderer/src/plugins/fabric/index.js +++ b/src/renderer/src/plugins/fabric/index.js @@ -1,27 +1,777 @@ /** - * 二次封装fabric + * @description 封装fabric js */ -import { fabric } from 'fabric' -import * as TYPES from '@/constants/draw' -import * as fabricUtils from '@/plugins/fabric/utils/draw' -import * as commUtils from '@/plugins/fabric/utils' -import * as bgUtils from '@/plugins/fabric/utils/bg' -import * as fabricStore from '@/store/modules/draw' -import { History } from '@/plugins/fabric/history' -import { CanvasEvent } from '@/plugins/fabric/event' +// import { fabric } from 'fabric' +import { fabric } from 'fabric-with-erasing' +import { diff, unpatch, patch } from 'jsondiffpatch' -// 节点node -import { renderPencilBrush } from '@/plugins/fabric/nodes/draw' +// 当前使用到的常量|类型(枚举) ============================ +export class TYPES { + static ActionMode = { + DRAW: 'draw', // 画笔模式 + ERASE: 'erase', // 橡皮擦模式 + SELECT: 'select', // 选择模式 + Board: 'board' // 画板模式 + } + // 画笔类型 + static DrawType = { + FreeStyle: 'freeStyle', + Shape: 'shape' + } + // 画笔样式 + static DrawStyle = { + Basic: 'basic', + Rainbow: 'rainbow', + Shape: 'shape', + Material: 'material', + Pixels: 'pixels', + MultiColor: 'multiColor', + Text: 'text', + MultiLine: 'multiLine', + Reticulate: 'reticulate', + MultiPoint: 'multiPoint', + Wiggle: 'wiggle', + Thorn: 'thorn' + } + // 各种形状 + static DrawShape = { + Bubble: 'bubble', + Star: 'star', + Love: 'love', + Butterfly: 'butterfly', + Snow: 'snow', + Music: 'music', + Sun: 'sun', + Moon: 'moon', + Leaf: 'leaf', + Flower: 'flower' + } + // 材质类型 + static MATERIAL_TYPE = { + CRAYON: 'crayon', + CARBON: 'carbon', + CLOTH: 'cloth', + OIL: 'oil', + CRAYON_DARK: 'crayonDark' + } + // 多颜色类型 + static MultiColorType = { + COL: 'col', + ROW: 'row', + CIRCLE: 'circle' + } + // 画笔元素 + static FREESTYLE_ELEMENT_CUSTOM_TYPE = { + IMAGE: 'image', + I_TEXT: 'itext', + RAINBOW: 'rainbow', + SHAPE: 'shape', + PIXELS: 'pixels', + DRAW_TEXT: 'drawText', + MULTI_LINE: 'multiLine', + RETICULATE: 'reticulate', + MULTI_POINT: 'multiPoint', + WIGGLE: 'wiggle', + THORN: 'thorn' + } + // 形状元素 + static SHAPE_ELEMENT_CUSTOM_TYPE = { + SHAPE_LINE: 'shapeLine', + SHAPE_RECT: 'shapeRect', + SHAPE_CIRCLE: 'shapeCircle', + SHAPE_ELLIPSE: 'shapeEllipse', + SHAPE_TRIANGLE: 'shapeTriangle', + SHAPE_ARROW_LINE: 'shapeArrowLine', + SHAPE_ARROW_OUTLINE: 'shapeArrowOutline', + SHAPE_CLOUD: 'shapeCloud', + SHAPE_TOOLTIPS: 'shapeTooltips', + SHAPE_LIGHTNING: 'shapeLightning', + SHAPE_CLOSE: 'shapeClose', + SHAPE_CHECK: 'shapeCheck', + SHAPE_INFO: 'shapeInfo', + SHAPE_BACKSPACE: 'shapeBackspace', + SHAPE_BLOCK: 'shapeBlock', + SHAPE_SPEAKER: 'shapeSpeaker', + SHAPE_SEARCH: 'shapeSearch', + SHAPE_INFO_OUTLINE: 'shapeInfoOutline', + SHAPE_HEART: 'shapeHeart', + SHAPE_ALERT: 'shapeAlert' + } + // 元素类型 + static ELEMENT_CUSTOM_TYPE = { + ...this.FREESTYLE_ELEMENT_CUSTOM_TYPE, + ...this.SHAPE_ELEMENT_CUSTOM_TYPE + } +} +// 公共方法-工具类 +export class Utils { + static lineUtils = { + polygonPositionHandler: (fabricControl, fabricObject) => { + const points = fabricObject.points + const x = points[fabricControl.pointIndex].x - fabricObject.pathOffset.x + const y = points[fabricControl.pointIndex].y - fabricObject.pathOffset.y + return fabric.util.transformPoint( + { x, y }, + fabric.util.multiplyTransformMatrices( + fabricObject.canvas?.viewportTransform, + fabricObject.calcTransformMatrix() + ) + ) + }, + anchorWrapper: (anchorIndex, fn) => { + return function (eventData, transform, x, y ) { + const fabricObject = transform.target + const points = fabricObject.points + const absolutePoint = fabric.util.transformPoint( + { + x: points[anchorIndex].x - fabricObject.pathOffset.x, + y: points[anchorIndex].y - fabricObject.pathOffset.y + }, + fabricObject.calcTransformMatrix() + ) + const actionPerformed = fn(eventData, transform, x, y) + fabricObject._setPositionDimensions({}) + const polygonBaseSize = getObjectSizeWithStroke(fabricObject) + + const newX = + (points[anchorIndex].x - fabricObject.pathOffset.x) / polygonBaseSize.x + const newY = + (points[anchorIndex].y - fabricObject.pathOffset.y) / polygonBaseSize.y + fabricObject.setPositionByOrigin( + absolutePoint, + (newX + 0.5), + (newY + 0.5) + ) + return actionPerformed + } + }, + actionHandler: (transform,x, y) => { + const polygon = transform.target, + currentControl = polygon.controls[polygon.__corner], + mouseLocalPosition = polygon.toLocalPoint( + new fabric.Point(x, y), + 'center', + 'center' + ), + polygonBaseSize = getObjectSizeWithStroke(polygon), + size = polygon._getTransformedDimensions(0, 0), + finalPointPosition = { + x: + (mouseLocalPosition.x * polygonBaseSize.x) / size.x + + polygon.pathOffset.x, + y: + (mouseLocalPosition.y * polygonBaseSize.y) / size.y + + polygon.pathOffset.y + } + + const points = polygon.points + points[currentControl.pointIndex] = finalPointPosition + + return true + } + } + static arrowLineUtils = { + pathPositionHandler: (fabricControl, fabricObject) => { + const paths = fabricObject.path + const x = paths[fabricControl.pointIndex][1] - fabricObject.pathOffset.x + const y = paths[fabricControl.pointIndex][2] - fabricObject.pathOffset.y + return fabric.util.transformPoint( + { x, y }, + fabric.util.multiplyTransformMatrices( + fabricObject.canvas?.viewportTransform, + fabricObject.calcTransformMatrix() + ) + ) + }, + anchorWrapper: (anchorIndex, fn) => { + return function (eventData, transform, x, y) { + const fabricObject = transform.target + const paths = fabricObject.path + const absolutePoint = fabric.util.transformPoint( + { + x: paths[anchorIndex][1] - fabricObject.pathOffset.x, + y: paths[anchorIndex][2] - fabricObject.pathOffset.y + }, + fabricObject.calcTransformMatrix() + ) + const actionPerformed = fn(eventData, transform, x, y) + fabricObject._setPath(fabricObject.path) + const pathBaseSize = getObjectSizeWithStroke(fabricObject) + + const newX = + (paths[anchorIndex][1] - fabricObject.pathOffset.x) / pathBaseSize.x + const newY = + (paths[anchorIndex][2] - fabricObject.pathOffset.y) / pathBaseSize.y + fabricObject.setPositionByOrigin( + absolutePoint, + (newX + 0.5), + (newY + 0.5) + ) + return actionPerformed + } + }, + actionHandler: (transform, x, y) => { + const pathObj = transform.target, + currentControl = pathObj.controls[pathObj.__corner], + mouseLocalPosition = pathObj.toLocalPoint( + new fabric.Point(x, y), + 'center', + 'center' + ), + pathBaseSize = getObjectSizeWithStroke(pathObj), + size = pathObj._getTransformedDimensions(0, 0), + finalPointPosition = { + x: + (mouseLocalPosition.x * pathBaseSize.x) / size.x + pathObj.pathOffset.x, + y: (mouseLocalPosition.y * pathBaseSize.y) / size.y + pathObj.pathOffset.y + } + + const path = pathObj.path + path[currentControl.pointIndex][1] = finalPointPosition.x + path[currentControl.pointIndex][2] = finalPointPosition.y + + if ( + currentControl.pointIndex === path?.length - 5 || + currentControl.pointIndex === path?.length - 6 + ) { + const point1 = path[path.length - 6] + const point2 = path[path.length - 5] + const newPath = calculateArrowSlidePath( + path, + point1[1], + point1[2], + point2[1], + point2[2] + ) + pathObj._setPath(newPath) + } + + return true + } + } + // 获取uuid string|number|all + static uuidv4(str = 'xxxxxxxx') { + if(str=='all') str = 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx' + else if (typeof str == 'number') str = ''.padEnd(str,'x') + return str.replace(/[xy]/g,c=> { + const r=Math.random() * 16 | 0, v = c == 'x' ? r : (r & 0x3 | 0x8); + return v.toString(16); + }) + } + // 设置对象属性 + static setObjectAttr(obj, type) { + const id = Date.now() + obj.set({id, _customType: type}) + } + // 获取fabric-canvas json + static getCanvasJSON(canvas) { + if (canvas) { + return ( + // 返回额外属性 + canvas.toDatalessJSON([ + 'id', + '_customType', + 'perPixelTargetFind', + 'objectCaching' + ]) ?? {} + ) + } + return {} + } + // 克隆对象 + static cloneDeep(obj = {}) { + return JSON.parse(JSON.stringify(obj)) + } + /** + * Handling canvas json loaded data + * Used to initialize undo redo + * @param canvas fabric.Canvas + */ + static handleCanvasJSONLoaded(canvas){ + const this_ = this + canvas.getObjects().forEach((obj) => { + if (obj._customType === TYPES.ELEMENT_CUSTOM_TYPE.SHAPE_LINE) { + const points = obj.points + const lastControl = points.length - 1 + obj.controls = points.reduce(function (acc, point, index) { + acc['p' + index] = new fabric.Control({ + positionHandler: this_.lineUtils.polygonPositionHandler, + actionHandler: this_.lineUtils.anchorWrapper( + index > 0 ? index - 1 : lastControl, + this_.lineUtils.actionHandler + ), + actionName: 'polylineEndPoint', + pointIndex: index + }) + return acc + }, {}) + } + + if (obj._customType === TYPES.ELEMENT_CUSTOM_TYPE.SHAPE_ARROW_LINE) { + const paths = (obj).path + obj.controls = paths + .slice(0, paths.length - 4) + .reduce(function (acc, point, index) { + acc['p' + index] = new fabric.Control({ + positionHandler: this_.arrowLineUtils.pathPositionHandler, + actionHandler: this_.arrowLineUtils.anchorWrapper( + index > 0 ? index - 1 : paths.length - 5, + this_.arrowLineUtils.actionHandler + ), + actionName: 'pathEndPoint', + pointIndex: index + }) + return acc + }, {}) + } + }) + } + // 获取对象大小-宽度 + static getWidth(width, fabricVue) { + return width / fabricVue.canvas?.getZoom() ?? 1 + } +} +// ====== 自由绘画 start ======== +export class RainbowElement{ // 彩虹 + points = [] // fabric.Point[] + group // fabric.Group + constructor() { + this.group = new fabric.Group([], {perPixelTargetFind: true}) + // paintBoard.canvas?.add(group) + // 设置对象属性 {id, type} + Utils.setObjectAttr(this.group, TYPES.ELEMENT_CUSTOM_TYPE.RAINBOW) + } +} +// ====== 自由绘画 end ======== +// 自由绘画 FreeStyle ============================ +// "basic": "基础", +// "rainbow": "彩虹 ", +// "shape": "多形状", +// "material": "素材", +// "pixels": "像素", +// "multiColor": "多色", +// "text": "文字", +// "multiLine": "多线连接", +// "reticulate": "网状", +// "multiPoint": "多点连接", +// "wiggle": "波浪曲线", +// "thorn": "荆棘" +export const FreeStyle = { + // 普通画笔-basic + renderPencilBrush: (fabricVue) => { + const canvas = fabricVue?.canvas + const drawConfig = fabricVue?.drawConfig + const pencilBrush = new fabric.PencilBrush(canvas) + canvas.isDrawingMode = true + canvas.freeDrawingBrush = pencilBrush + canvas.freeDrawingBrush.width = Utils.getWidth(drawConfig.drawWidth, fabricVue) + canvas.freeDrawingBrush.color = drawConfig.drawColors[0] + canvas.freeDrawingBrush.shadow = new fabric.Shadow({ + blur: Utils.getWidth(drawConfig.shadowWidth, fabricVue), + offsetX: 0, + offsetY: 0, + color: drawConfig.shadowColor + }) + }, + // 橡皮擦-设置为-自由绘图画笔 + renderEraserBrush: (fabricVue) => { + const canvas = fabricVue?.canvas + const drawConfig = fabricVue?.drawConfig + const eraserBrush = new fabric.EraserBrush(canvas) + canvas.isDrawingMode = true + canvas.freeDrawingBrush = eraserBrush + canvas.freeDrawingBrush.width = Utils.getWidth(drawConfig.eraserWidth, fabricVue) + canvas.freeDrawingBrush.color = '#FFF' + } +} +// 事件类 +// -- 点击事件 +export class CanvasClickEvent { + updateInkHook + FabricVue // fabric-canvas 对象 + isMouseDown = false // 是否按下鼠标 + isSpaceKeyDown = false // 是否按下空格键 + startPoint // 开始点坐标fabric.Point | undefined + currentElement = null // 当前鼠标移动会绘制元素 The current mouse move draws the element + mouseDownTime = 0 // 鼠标按下时间 + autoDrawInk = [[], [], []] // 自动绘制 google auto draw ink Array> + isDrawBasic = false // 是否默认绘制 + + constructor(fabricVue) { + this.FabricVue = fabricVue + this.initCanvasEvent() + } + // 初始化 + initCanvasEvent() { + const canvas = this.FabricVue?.canvas + if (!canvas) throw new Error('canvas is null') + // canvas?.on('mouse:down', e => this.mouseDown(e)) // 事件:按下鼠标 + // canvas?.on('mouse:move', e => this.mouseMove(e)) // 事件:移动鼠标 + // canvas?.on('mouse:up', e => this.mouseUp(e)) // 事件:抬起鼠标 + // canvas?.on('mouse:dblclick', e => this.mouseDblclick(e)) // 事件:双击鼠标 + canvas?.on('mouse:down', this.mouseDown.bind(this) ) // 事件:按下鼠标 + canvas?.on('mouse:move', this.mouseMove.bind(this)) // 事件:移动鼠标 + canvas?.on('mouse:up', this.mouseUp.bind(this)) // 事件:抬起鼠标 + canvas?.on('mouse:dblclick', this.mouseDblclick.bind(this)) // 事件:双击鼠标 + } + // 事件:按下鼠标 + mouseDown(e) { + this.isMouseDown = true + if (this.isSpaceKeyDown) return + this.startPoint = e.absolutePointer + let currentElement = null + const { boardConfig, drawConfig } = this.FabricVue || {} + const isDraw = boardConfig.mode === TYPES.ActionMode.DRAW + if (isDraw) { + if (boardConfig.drawType == TYPES.DrawType.Shape) { // 形状绘画 + // switch (useShapeStore.shapeStyle) { + // case ShapeStyle.Rect: + // currentElement = new RectShape(e.absolutePointer) + // break + // case ShapeStyle.Circle: + // currentElement = new CircleShape(e.absolutePointer) + // break + // case ShapeStyle.Line: + // currentElement = new LineShape(e.absolutePointer) + // break + // case ShapeStyle.Ellipse: + // currentElement = new EllipseShape(e.absolutePointer) + // break + // case ShapeStyle.Triangle: + // currentElement = new TriangleShape(e.absolutePointer) + // break + // case ShapeStyle.ArrowLine: + // currentElement = new ArrowLineShape(e.absolutePointer) + // break + // case ShapeStyle.ArrowOutline: + // currentElement = new ArrowOutlineShape(e.absolutePointer) + // break + // case ShapeStyle.Cloud: + // currentElement = new CloudShape(e.absolutePointer) + // break + // case ShapeStyle.Tooltips: + // currentElement = new TooltipsShape(e.absolutePointer) + // break + // case ShapeStyle.Lightning: + // currentElement = new LightningShape(e.absolutePointer) + // break + // case ShapeStyle.Close: + // currentElement = new CloseShape(e.absolutePointer) + // break + // case ShapeStyle.Check: + // currentElement = new CheckShap(e.absolutePointer) + // break + // case ShapeStyle.Info: + // currentElement = new InfoShape(e.absolutePointer) + // break + // case ShapeStyle.Backspace: + // currentElement = new BackspaceShape(e.absolutePointer) + // break + // case ShapeStyle.Block: + // currentElement = new BlockShap(e.absolutePointer) + // break + // case ShapeStyle.Speaker: + // currentElement = new SpeakerShape(e.absolutePointer) + // break + // case ShapeStyle.Search: + // currentElement = new SearchShape(e.absolutePointer) + // break + // case ShapeStyle.InfoOutline: + // currentElement = new InfoOutlineShape(e.absolutePointer) + // break + // case ShapeStyle.Heart: + // currentElement = new HeartShape(e.absolutePointer) + // break + // case ShapeStyle.Alert: + // currentElement = new AlertShape(e.absolutePointer) + // break + // default: + // break + // } + } else if (boardConfig.drawType == TYPES.DrawType.FreeStyle) { // 自由绘画 + switch (drawConfig.drawStyle){ + // case TYPES.DrawStyle.Shape: // 多形状 + // currentElement = new ShapeElement() + // break + // case TYPES.DrawStyle.Pixels: // 像素 + // currentElement = new PixelsElement() + // break + // case TYPES.DrawStyle.Text: // 文本 + // currentElement = new DrawTextElement() + // break + // case TYPES.DrawStyle.MultiLine: // 多线 + // currentElement = new MultiLineElement() + // break + // case TYPES.DrawStyle.Reticulate: // 网状 + // currentElement = new ReticulateElement() + // break + // case TYPES.DrawStyle.Rainbow: // 彩虹 + // currentElement = new RainbowElement() + // break + // case TYPES.DrawStyle.Thorn: // 荆棘 + // currentElement = new ThornElement() + // break + // case TYPES.DrawStyle.MultiPoint: // 多点 + // currentElement = new MultiPointElement() + // break + // case TYPES.DrawStyle.Wiggle: // 波浪 + // currentElement = new WiggleElement() + // break + case TYPES.DrawStyle.Basic: // 基础画笔 + // FreeStyle.renderPencilBrush(this.FabricVue) + this.isDrawBasic = true + break + } + } + } + this.currentElement = currentElement + } + // 事件:移动鼠标 + mouseMove(e) { + if (this.isMouseDown) { + // 按下空格键,拖动画布,停止绘画。Press space, drag the canvas, stop drawing. + if (this.isSpaceKeyDown) { + canvas.relativePan(new fabric.Point(e.e.movementX, e.e.movementY)) + return + } + // 两个手指拖动,禁止绘画 two touch disabled drawing on mobile + if (this.FabricVue.evnet?.touchEvent?.isTwoTouch) return + const { boardConfig } = this.FabricVue||{} + const isDraw = boardConfig.mode === TYPES.ActionMode.DRAW && !!this.currentElement + if (isDraw) this.currentElement.addPosition(e.absolutePointer) + } + } + // 事件:抬起鼠标 + mouseUp(e) { + this.isMouseDown = false + if (this.currentElement) { + const { x: startX, y: startY } = this.startPoint + const { x: endX, y: endY } = e.absolutePointer + if (startX === endX && startY === endY) { // 无变化 + this.currentElement.destroy() + } + const isLine = this.currentElement instanceof LineShape || this.currentElement instanceof ArrowLineShape + if (isLine) this.currentElement?.mouseUp() + this.FabricVue?.history?.saveState() + } + if (this.isDrawBasic) { // 基础画笔-无element对象-但是要保存 + this.FabricVue?.history?.saveState() + } + } + // 事件:双击鼠标 + mouseDblclick(e) { + if (e?.absolutePointer) { + const { x, y } = e.absolutePointer + this.FabricVue.textElement?.loadText(x, y) + } + } + // 设置空格键状态 + setSpaceKeyDownState(isSpaceKeyDown) { + this.isSpaceKeyDown = isSpaceKeyDown + } + // 钩子:更新ink + changeInkHookFn(fn) { + this.updateInkHook = fn + } +} +// -- 主事件:canvas +export class CanvasEvent { + FabricVue // fabric-canvas 对象 + clickEvent // 点击事件 CanvasClickEvent + zoomEvent // 缩放事件 CanvasZoomEvent + objectEvent // 对象事件 ObjectEvent + windowEvent // window事件 WindowEvent + touchEvent // 触摸事件 CanvasTouchEvent + constructor(fabricVue) { + this.FabricVue = fabricVue + this.clickEvent = new CanvasClickEvent(fabricVue) + + // const zoomEvent = new CanvasZoomEvent() + // this.zoomEvent = zoomEvent + + // const objectEvent = new ObjectEvent() + // this.objectEvent = objectEvent + + // const windowEvent = new WindowEvent() + // this.windowEvent = windowEvent + + // const touchEvent = new CanvasTouchEvent() + } + // 移除事件 + removeEvent() { + this.windowEvent.removeWindowEvent() + this.touchEvent.removeTouchEvent() + } +} +// 历史类 +export class History { + FabricVue + diffs = [] + canvasData = {} + index = 0 + + constructor(fabricVue) { + this.FabricVue = fabricVue + const canvas = fabricVue.canvas + if (canvas) { + const canvasJson = Utils.getCanvasJSON(canvas) + this.canvasData = Utils.cloneDeep(canvasJson ?? {}) + } + } + // 缓存相关数据json + saveState() { + const canvas = this.FabricVue?.canvas + if (canvas) { + this.diffs = this.diffs.slice(0, this.index) + const canvasJson = Utils.getCanvasJSON() + const delta = diff(canvasJson, this.canvasData) + this.diffs.push(delta) + + // More than 50 operations, remove initial state + if (this.diffs.length > 50) { + this.diffs.shift() + } else { + this.index++ + } + this.canvasData = Utils.cloneDeep(canvasJson ?? {}) + } + } + // 撤销 + undo() { + const canvas = this.FabricVue?.canvas + if (canvas && this.index > 0) { + const delta = this.diffs[this.index - 1] + this.index-- + const canvasJson = patch(this.canvasData, delta) + // 重新加载数据 + canvas.loadFromJSON(canvasJson, () => { + Utils.handleCanvasJSONLoaded(canvas) + canvas.requestRenderAll() // 批量重绘 + this.canvasData = Utils.cloneDeep(canvasJson ?? {}) + // this.FabricVue.triggerHook() // 重新加载-自定义钩子 + // if ((delta)?.backgroundImage) { // 重新加载背景 + // handleBackgroundImageWhenCanvasSizeChange() + // } + }) + } + } + // 退回|重做 + redo() { + const canvas = this.FabricVue?.canvas + if (this.index < this.diffs.length && canvas) { + const delta = this.diffs[this.index] + this.index++ + const canvasJson = unpatch(this.canvasData, delta) + canvas.loadFromJSON(canvasJson, () => { + Utils.handleCanvasJSONLoaded(canvas) + canvas.requestRenderAll() + this.canvasData = Utils.cloneDeep(canvasJson ?? {}) + + // FabricVue.triggerHook() + // if ((delta)?.backgroundImage) { + // bgUtils.handleBackgroundImageWhenCanvasSizeChange() + // } + }) + } + } + + clean() { + this.FabricVue?.canvas?.clear() + this.index = 0 + this.diffs = [] + this.canvasData = {} + } + + initHistory() { + const canvas = this.FabricVue.canvas + if (canvas) { + const canvasJson = Utils.getCanvasJSON() + this.canvasData = canvasJson + this.index = 0 + this.diffs = [] + } + } +} +// 封装fabric-canvas export class fabricVue { - canvas = null // fabric-canvas 对象 - evnet = null // 事件对象 - history = null // 历史记录 - textElement // 文本节点 - hookFn = [] // 钩子 + canvas = null // fabric-canvas 对象 + evnet = null // 事件对象 + history = null // 历史记录 + textElement // 文本节点 + hookFn = [] // 钩子 + defConfig // 默认配置-canvas + drawConfig // 默认配置-画笔 + boardConfig // 默认配置-画板 + publicConfig // 公共属性-配置 + /** 构造函数 */ constructor() { // this.textElement = new TextElement() // 创建文本节点 + const initLanguage = ['en', 'en-US', 'en-us'].includes(navigator.language) ? 'en' : 'zh' + this.drawConfig = { // 默认配置 + drawWidth: 1, + drawColors: ['#000000'], + shadowWidth: 0, + shadowColor: '#000000', + drawTextValue: 'draw', + drawStyle: TYPES.DrawStyle.Basic, + drawShapeCount: 2, + materialType: TYPES.MATERIAL_TYPE.CRAYON, + drawShape: TYPES.DrawShape.Bubble, + eraserWidth: 20, + multiColorType: TYPES.MultiColorType.COL, + textFontFamily: 'Georgia', + openAutoDraw: false, + fontStyles: [], + } + this.boardConfig = { // 默认配置 + mode: TYPES.ActionMode.DRAW, + drawType: TYPES.DrawType.FreeStyle, + language: initLanguage, + canvasWidth: 1, + canvasHeight: 1, + backgroundColor: 'rgba(255, 255, 255, 1)', + backgroundOpacity: 1, + hasBackgroundImage: false, + backgroundImageOpacity: 1, + isObjectCaching: true, + openGuideLine: false, + } + this.defConfig = { // 默认配置-canvas + // 它用于指定选中对象时显示的选择框的颜色 + selectionColor: 'rgba(101, 204, 138, 0.3)', + // 用于控制在组合(group)对象时是否保留每个对象的堆叠顺序 + preserveObjectStacking: true, + // 它用于控制是否在高分辨率显示器(例如 Retina 显示器)上启用图像的像素级缩放 + enableRetinaScaling: true, + // 锁定背景,不受缩放影响 false + backgroundVpt: false, + // 默认全屏模式 + width: window.innerWidth, + height: window.innerHeight, + } + this.publicConfig = { // 公共属性配置 + Object: { // 默认配置-对象上 + // 设置对象边框的颜色(Rect、Circle、Ellipse、Path) + borderColor: '#65CC8A', + // 设置对象角落的颜色(Rect、RoundRect、Circle 和 Ellipse 类型的对象) + cornerColor: '#65CC8A', + // 设置角落的形状(Rect、RoundRect、Circle 和 Ellipse 类型的对象) + // circle rect round [圆形,直角(默认),圆角] + cornerStyle: 'circle', + // 设置边框虚线的样式(Rect、Circle、Ellipse、Path) + borderDashArray: [3, 3], + // 矩形(Rect)、圆角矩形(RoundRect)、圆形(Circle)和椭圆形(Ellipse)对象的角落是否透明 + transparentCorners: false + }, + Line: { + // 设置边缘连接方式为 miter bevel round|尖角(默认) 斜角切割线 圆形连接 + strokeLineJoin: 'round', + // 设置结束连接方式为 butt round square|直角(默认) 圆形 正方形 + strokeLineCap: 'round' + } + } } /** * 初始化canvas @@ -29,189 +779,80 @@ export class fabricVue { * @returns boolean 是否初始化成功 */ initCanvas(canvasEl, option = {}) { - // const useBoardStore = fabricStore.useBoardStore() - return new Promise(async (resolve) => { + return new Promise(async(resolve) => { + if (!canvasEl) resolve(false) this.canvas = new fabric.Canvas(canvasEl, { - selectionColor: 'rgba(101, 204, 138, 0.3)', - preserveObjectStacking: true, - enableRetinaScaling: true, - backgroundVpt: false, - ...option + ...this.defConfig, ...option }) - fabric.Object.prototype.set({ - borderColor: '#65CC8A', - cornerColor: '#65CC8A', - cornerStyle: 'circle', - borderDashArray: [3, 3], - transparentCorners: false - }) - fabric.Line.prototype.strokeLineJoin = 'round' - fabric.Line.prototype.strokeLineCap = 'round' - - // if (isMobile()) { - // brushMouseMixin.initCanvas(this.canvas) - // } - // // 创建辅助线 - // alignGuideLine.init(this.canvas, useBoardStore.openGuideLine) - - this.evnet = new CanvasEvent() // 创建相关事件 - this.handleMode() - - await this.initCanvasStorage() - + fabric.Object.prototype.set(this.publicConfig.Object) + fabric.Line.prototype.set(this.publicConfig.Line) + this.evnet = new CanvasEvent(this) // 创建相关事件 + this.history = new History(this) // 创建历史记录 + // await this.initCanvasStorage() + this.handleMode() // 默认模式 resolve(true) }) } /** - * 销毁canvas + * handle mode of operation + * @param mode current mode */ - removeCanvas() { - if (this.canvas) { - this?.canvas?.dispose() - this.evnet?.removeEvent() - this.canvas = null - } - } - /** - * Initialize the canvas cache - * @description 这里可以做一下初始加载json数据的操作 - */ - initCanvasStorage() { - const useBoardStore = fabricStore.useBoardStore() - const useFileStore = fabricStore.useFileStore() - return new Promise((resolve) => { - setTimeout(() => { - const file = useFileStore.currentFile - if (file && this.canvas) { - this.canvas.clear() - this.canvas.loadFromJSON(file.boardData, () => { - if (this.canvas) { - if (file.viewportTransform) { - this.canvas.setViewportTransform(file.viewportTransform) - } - if (file?.zoom && this.canvas.width && this.canvas.height) { - this.canvas.zoomToPoint( - new fabric.Point( - this.canvas.width / 2, - this.canvas.height / 2 - ), - file.zoom - ) - } - - this.canvas.setWidth(window.innerWidth * (file?.canvasWidth || 1)) - useBoardStore.updateCanvasWidth(file?.canvasWidth || 1) - this.canvas.setHeight(window.innerHeight * (file?.canvasHeight || 1)) - useBoardStore.initBackground() - - const height = file?.canvasHeight || 1 - useBoardStore.canvasHeight = height - this.updateCanvasHeight(height) - - commUtils.handleCanvasJSONLoaded(this.canvas) - - fabric.Object.prototype.set({ - objectCaching: useBoardStore.isObjectCaching - }) - this.canvas.renderAll() - this.triggerHook() - this.history = new History() - } - resolve(true) - }) - } else { - resolve(true) - } - }, 300) - }) - } - /** - * handle mode of operation - * @param mode current mode - */ handleMode(mode) { - if (!this.canvas) { - return - } - const useDrawStore = fabricStore.useDrawStore() - const useBoardStore = fabricStore.useBoardStore() - mode = mode || useBoardStore.mode - let isDrawingMode = false - let selection = false + if (!this.canvas) return + if (!mode) mode = this.boardConfig.mode const objectSet = { selectable: false, hoverCursor: 'default' } - switch (mode) { - case TYPES.ActionMode.DRAW: - if ( - useBoardStore.drawType === TYPES.DrawType.FreeStyle && - [TYPES.DrawStyle.Basic, TYPES.DrawStyle.Material, TYPES.DrawStyle.MultiColor].includes( - useDrawStore.drawStyle - ) - ) { - isDrawingMode = true - this.handleDrawStyle() + case TYPES.ActionMode.DRAW:{ // 绘画 + const isDraw = this.boardConfig.drawType === TYPES.DrawType.FreeStyle + if (isDraw) { + switch(this.drawConfig.drawStyle) { + case TYPES.DrawStyle.Basic: + FreeStyle.renderPencilBrush(this) + break + // case TYPES.DrawStyle.Material: + // this.canvas.isDrawingMode = true + // material.render({}) + // break + // case TYPES.DrawStyle.MultiColor: + // renderMultiColor({}) + // break + default: + this.canvas.isDrawingMode = false + break + } } + break} + case TYPES.ActionMode.ERASE: // 橡皮擦 + FreeStyle.renderEraserBrush(this) this.canvas.discardActiveObject() break - case TYPES.ActionMode.ERASE: - isDrawingMode = true - this.canvas.freeDrawingBrush = new fabric.EraserBrush( - this.canvas - ) - this.canvas.freeDrawingBrush.width = fabricUtils.getEraserWidth() - this.canvas.freeDrawingBrush.color = '#FFF' - this.canvas.discardActiveObject() - break - case TYPES.ActionMode.Board: - case TYPES.ActionMode.SELECT: + case TYPES.ActionMode.Board: // 画板-属性设置 + case TYPES.ActionMode.SELECT: // 选择 objectSet.selectable = true objectSet.hoverCursor = undefined - selection = true + this.canvas.selection = true break default: break } - this.canvas.isDrawingMode = isDrawingMode - this.canvas.selection = selection fabric.Object.prototype.set(objectSet) - this.canvas.forEachObject((obj) => { if (obj._customType === TYPES.ELEMENT_CUSTOM_TYPE.I_TEXT) { obj.selectable = objectSet.selectable obj.hoverCursor = objectSet.hoverCursor } }) - - this.canvas.requestRenderAll() } - - /** - * handle draw style - */ - handleDrawStyle() { - if (!this.canvas) { - return - } - const useDrawStore = fabricStore.useDrawStore() - const drawStyle = useDrawStore.drawStyle - switch (drawStyle) { - case TYPES.DrawStyle.Basic: - renderPencilBrush() - break - case TYPES.DrawStyle.Material: - this.canvas.isDrawingMode = true - material.render({}) - break - case TYPES.DrawStyle.MultiColor: - renderMultiColor({}) - break - default: - this.canvas.isDrawingMode = false - break + // 移除画布 + removeCanvas() { + if (this.canvas) { + this?.canvas?.dispose() + this.evnet?.removeEvent() + this.canvas = null } } /** @@ -269,7 +910,7 @@ export class fabricVue { const copys = targets.map((target) => { return new Promise((resolve) => { target?.clone((cloned) => { - const id = uuidv4() + const id = Utils.uuidv4('all') cloned.set({ left: (cloned?.left || 0) + 10, top: (cloned?.top || 0) + 10, @@ -292,7 +933,7 @@ export class fabricVue { } /** * Moving active objects via fabric's bringForward method - * 通过fabric的bringForward方法移动活动对象 + * 通过fabric的bringForward方法移动活动对象之上 */ bringForWard() { const canvas = this.canvas @@ -304,10 +945,9 @@ export class fabricVue { } } } - /** * Moving active objects via fabric's sendBackwards method - * 通过fabric的sendBackwards方法移动活动对象 + * 通过fabric的sendBackwards方法移动活动对象之下 */ seendBackWard() { const canvas = this.canvas @@ -319,10 +959,7 @@ export class fabricVue { } } } - /** - * Moving active objects via fabric's bringToFront method - * 通过fabric的bringToFront方法移动活动对象 - */ + // bringForWard 方法的简写 移动活动对象之上 bringToFront() { const canvas = this.canvas if (canvas) { @@ -333,10 +970,7 @@ export class fabricVue { } } } - /** - * Moving active objects via fabric's sendToBack method - * 通过fabric的sendToBack方法移动活动对象 - */ + // seendBackWard 方法的简写 移动活动对象之下 sendToBack() { const canvas = this.canvas if (canvas) { @@ -347,7 +981,6 @@ export class fabricVue { } } } - /** * Add hook fn to trigger on update|在更新时添加钩钩fn以触发 * @param fn hook fn () => void @@ -355,7 +988,6 @@ export class fabricVue { addHookFn(fn) { this.hookFn.push(fn) } - /** * remove trigger hook fn | 移除触发钩fn * @param fn hook fn () => void @@ -375,22 +1007,7 @@ export class fabricVue { fn?.() }) } - - updateCanvasWidth(width) { - if (this.canvas) { - this.canvas.setWidth(window.innerWidth * width) - bgUtils.handleBackgroundImageWhenCanvasSizeChange() - // useFileStore.getState().updateCanvasWidth(width) - } - } - updateCanvasHeight(height) { - if (this.canvas) { - const useFileStore = fabricStore.useFileStore() - this.canvas.setHeight(window.innerHeight * height) - useFileStore.updateBoardField('canvasHeight', height) - bgUtils.handleBackgroundImageWhenCanvasSizeChange() - } - } } +export const FabricVue = new fabricVue() +export default FabricVue -export const FabricVue = new fabricVue() \ No newline at end of file diff --git a/src/renderer/src/plugins/fabric/nodes/draw/index.js b/src/renderer/src/plugins/fabric/nodes/draw/index.js deleted file mode 100644 index d4ae072..0000000 --- a/src/renderer/src/plugins/fabric/nodes/draw/index.js +++ /dev/null @@ -1,27 +0,0 @@ -import { fabric } from 'fabric' -import { FabricVue } from '@/plugins/fabric' -import * as fabricUtils from '@/plugins/fabric/utils/draw' -import * as fabricStore from '@/store/modules/draw' -const useDrawStore = fabricStore.useDrawStore() -/** - * 基础 画笔 - * @returns void - */ -export const renderPencilBrush = () => { - const canvas = FabricVue.canvas - if (!canvas) { - return - } - - const pencilBrush = new fabric.PencilBrush(canvas) - canvas.isDrawingMode = true - canvas.freeDrawingBrush = pencilBrush - canvas.freeDrawingBrush.width = fabricUtils.getDrawWidth() - canvas.freeDrawingBrush.color = useDrawStore.drawColors[0] - canvas.freeDrawingBrush.shadow = new fabric.Shadow({ - blur: fabricUtils.getShadowWidth(), - offsetX: 0, - offsetY: 0, - color: useDrawStore.shadowColor - }) -} \ No newline at end of file diff --git a/src/renderer/src/plugins/fabric/nodes/draw/material.js b/src/renderer/src/plugins/fabric/nodes/draw/material.js deleted file mode 100644 index 3fa8343..0000000 --- a/src/renderer/src/plugins/fabric/nodes/draw/material.js +++ /dev/null @@ -1,125 +0,0 @@ - -/** - * @description: 材质 - */ -import { FabricVue } from '@/plugins/fabric' -import * as TYPES from '@/constants/draw' -import * as fabricStore from '@/store/modules/draw' - -export class Material { - initPromise = null // Initialize promise - crayonImage = null - carbonImage = null - clothImage = null - oilImage = null - crayonDarkImage = null - - constructor() { - this.initMaterial() - } - - async initMaterial() { - - this.initPromise = Promise.all([ - this.loadImage(TYPES.MATERIAL_TYPE.CRAYON), - this.loadImage(TYPES.MATERIAL_TYPE.CARBON), - this.loadImage(TYPES.MATERIAL_TYPE.CLOTH), - this.loadImage(TYPES.MATERIAL_TYPE.OIL), - this.loadImage(TYPES.MATERIAL_TYPE.CRAYON_DARK) - ]) - } - - render({materialType, color}) { - const useDrawStore = fabricStore.useDrawStore() - materialType = materialType || useDrawStore.materialType - color = color || useDrawStore.drawColors[0] - this.initPromise?.then(() => { - switch (materialType) { - case TYPES.MATERIAL_TYPE.CRAYON: - this.renderMaterial(this.crayonImage, color) - break - case TYPES.MATERIAL_TYPE.CARBON: - this.renderMaterial(this.carbonImage, color) - break - case TYPES.MATERIAL_TYPE.CLOTH: - this.renderMaterial(this.clothImage, color) - break - case TYPES.MATERIAL_TYPE.OIL: - this.renderMaterial(this.oilImage, color) - break - case TYPES.MATERIAL_TYPE.CRAYON_DARK: - this.renderMaterial(this.crayonDarkImage, color) - break - default: - break - } - }) - } - - renderMaterial(materialImg, color, opacity = 1) { - if (FabricVue.canvas) { - const useDrawStore = fabricStore.useDrawStore() - const patternBrush = new fabric.PatternBrush(FabricVue.canvas) - const patternCanvas = document.createElement('canvas') - patternCanvas.width = patternCanvas.height = 100 - const context = patternCanvas.getContext('2d') - if (context) { - context.fillStyle = color - context.fillRect(0, 0, 100, 100) - if (materialImg) { - context.globalAlpha = opacity - context.drawImage(materialImg, 0, 0, 100, 100) - } - } - patternBrush.getPatternSrc = () => { - return patternCanvas - } - patternBrush.getPatternSrcFunction = () => { - return patternCanvas - } - FabricVue.canvas.freeDrawingBrush = patternBrush - FabricVue.canvas.freeDrawingBrush.width = getDrawWidth() - FabricVue.canvas.freeDrawingBrush.shadow = new fabric.Shadow({ - blur: getShadowWidth(), - offsetX: 0, - offsetY: 0, - color: useDrawStore.shadowColor - }) - } - } - - loadImage(imageName) { - return new Promise((resolve) => { - const img = new Image() - // img.src = formatPublicUrl(`pattern/${imageName}.png`) - img.src = `pattern/${imageName}.png` - img.onload = () => { - switch (imageName) { - case TYPES.MATERIAL_TYPE.CARBON: - this.carbonImage = img - break - case TYPES.MATERIAL_TYPE.CRAYON: - this.crayonImage = img - break - case TYPES.MATERIAL_TYPE.CLOTH: - this.clothImage = img - break - case TYPES.MATERIAL_TYPE.OIL: - this.oilImage = img - break - case TYPES.MATERIAL_TYPE.CRAYON_DARK: - this.crayonDarkImage = img - break - default: - break - } - resolve(true) - } - img.onerror = () => { - resolve(false) - } - }) - } -} - -export const material = new Material() \ No newline at end of file diff --git a/src/renderer/src/plugins/fabric/nodes/draw/multiColor.js b/src/renderer/src/plugins/fabric/nodes/draw/multiColor.js deleted file mode 100644 index a37195c..0000000 --- a/src/renderer/src/plugins/fabric/nodes/draw/multiColor.js +++ /dev/null @@ -1,98 +0,0 @@ -/** - * 多个材质画刷 - * @param {*} params - * @returns - */ -import { FabricVue } from '@/plugins/fabric' -import * as TYPES from '@/constants/draw' -import * as fabricUtils from '@/plugins/fabric/utils/draw' -import * as fabricStore from '@/store/modules/draw' - -const COLOR_WIDTH = 5 - -export const renderMultiColor = (params={}) => { - const canvas = FabricVue.canvas - if (!canvas) { - return - } - const useDrawStore = fabricStore.useDrawStore() - const colors = params?.colors ?? useDrawStore.drawColors - const type = params?.type ?? useDrawStore.multiColorType - - const patternBrush = new fabric.PatternBrush(canvas) - const patternCanvas = document.createElement('canvas') - const context = patternCanvas.getContext('2d') - if (context) { - switch (type) { - case TYPES.MultiColorType.COL: - renderCol(patternCanvas, context, colors) - break - case TYPES.MultiColorType.ROW: - renderRow(patternCanvas, context, colors) - break - case TYPES.MultiColorType.CIRCLE: - renderCircle(patternCanvas, context, colors) - break - default: - break - } - - patternBrush.getPatternSrc = () => { - return patternCanvas - } - patternBrush.getPatternSrcFunction = () => { - return patternCanvas - } - - canvas.isDrawingMode = true - canvas.freeDrawingBrush = patternBrush - canvas.freeDrawingBrush.width = fabricUtils.getDrawWidth() - canvas.freeDrawingBrush.shadow = new fabric.Shadow({ - blur: fabricUtils.getShadowWidth(), - offsetX: 0, - offsetY: 0, - color: useDrawStore.shadowColor - }) - } -} - -function renderCol(canvas, context, colors=[]) { - canvas.width = COLOR_WIDTH * colors.length - canvas.height = 20 - colors.forEach((color, i) => { - context.fillStyle = color - context.fillRect(COLOR_WIDTH * i, 0, COLOR_WIDTH, 20) - }) -} - -function renderRow(canvas, context, colors=[]) { - canvas.width = 20 - canvas.height = COLOR_WIDTH * colors.length - colors.forEach((color, i) => { - context.fillStyle = color - context.fillRect(0, COLOR_WIDTH * i, 20, COLOR_WIDTH) - }) -} - -function renderCircle(canvas, context, colors=[]) { - const radius = 10 - const padding = 5 - const n = colors.length - - const canvasWidth = 2 * padding + n * radius * 2 + (n - 1) * padding - canvas.width = canvasWidth - canvas.height = radius * 2 + 2 * padding - - let x = padding + radius - const y = padding + radius - - // render multi circle - for (let i = 0; i < n; i++) { - context.beginPath() - context.fillStyle = colors[i] - context.arc(x, y, radius, 0, Math.PI * 2) - context.closePath() - context.fill() - x += 2 * radius + padding - } -} \ No newline at end of file diff --git a/src/renderer/src/plugins/fabric/test.js b/src/renderer/src/plugins/fabric/test.js deleted file mode 100644 index 7f49554..0000000 --- a/src/renderer/src/plugins/fabric/test.js +++ /dev/null @@ -1,223 +0,0 @@ -/** - * @description 封装fabric js - */ -import { fabric } from 'fabric' - -// 当前使用到的常量|类型(枚举) -export class TYPES { - static ActionMode = { - DRAW: 'draw', // 画笔模式 - ERASE: 'erase', // 橡皮擦模式 - SELECT: 'select', // 选择模式 - Board: 'board' // 画板模式 - } - // 画笔类型 - static DrawType = { - FreeStyle: 'freeStyle', - Shape: 'shape' - } - // 画笔样式 - static DrawStyle = { - Basic: 'basic', - Rainbow: 'rainbow', - Shape: 'shape', - Material: 'material', - Pixels: 'pixels', - MultiColor: 'multiColor', - Text: 'text', - MultiLine: 'multiLine', - Reticulate: 'reticulate', - MultiPoint: 'multiPoint', - Wiggle: 'wiggle', - Thorn: 'thorn' - } - // 各种形状 - static DrawShape = { - Bubble: 'bubble', - Star: 'star', - Love: 'love', - Butterfly: 'butterfly', - Snow: 'snow', - Music: 'music', - Sun: 'sun', - Moon: 'moon', - Leaf: 'leaf', - Flower: 'flower' - } - // 材质类型 - static MATERIAL_TYPE = { - CRAYON: 'crayon', - CARBON: 'carbon', - CLOTH: 'cloth', - OIL: 'oil', - CRAYON_DARK: 'crayonDark' - } - // 多颜色类型 - static MultiColorType = { - COL: 'col', - ROW: 'row', - CIRCLE: 'circle' - } - // 画笔元素 - static FREESTYLE_ELEMENT_CUSTOM_TYPE = { - IMAGE: 'image', - I_TEXT: 'itext', - RAINBOW: 'rainbow', - SHAPE: 'shape', - PIXELS: 'pixels', - DRAW_TEXT: 'drawText', - MULTI_LINE: 'multiLine', - RETICULATE: 'reticulate', - MULTI_POINT: 'multiPoint', - WIGGLE: 'wiggle', - THORN: 'thorn' - } - // 形状元素 - static SHAPE_ELEMENT_CUSTOM_TYPE = { - SHAPE_LINE: 'shapeLine', - SHAPE_RECT: 'shapeRect', - SHAPE_CIRCLE: 'shapeCircle', - SHAPE_ELLIPSE: 'shapeEllipse', - SHAPE_TRIANGLE: 'shapeTriangle', - SHAPE_ARROW_LINE: 'shapeArrowLine', - SHAPE_ARROW_OUTLINE: 'shapeArrowOutline', - SHAPE_CLOUD: 'shapeCloud', - SHAPE_TOOLTIPS: 'shapeTooltips', - SHAPE_LIGHTNING: 'shapeLightning', - SHAPE_CLOSE: 'shapeClose', - SHAPE_CHECK: 'shapeCheck', - SHAPE_INFO: 'shapeInfo', - SHAPE_BACKSPACE: 'shapeBackspace', - SHAPE_BLOCK: 'shapeBlock', - SHAPE_SPEAKER: 'shapeSpeaker', - SHAPE_SEARCH: 'shapeSearch', - SHAPE_INFO_OUTLINE: 'shapeInfoOutline', - SHAPE_HEART: 'shapeHeart', - SHAPE_ALERT: 'shapeAlert' - } - // 元素类型 - static ELEMENT_CUSTOM_TYPE = { - ...this.FREESTYLE_ELEMENT_CUSTOM_TYPE, - ...this.SHAPE_ELEMENT_CUSTOM_TYPE - } -} -// 自由绘画 FreeStyle -// export class FreeStyle { -// } -export const FreeStyle = { - renderPencilBrush: (canvas) => { - const pencilBrush = new fabric.PencilBrush(canvas) - canvas.isDrawingMode = true - canvas.freeDrawingBrush = pencilBrush - canvas.freeDrawingBrush.width = fabricUtils.getDrawWidth() - canvas.freeDrawingBrush.color = useDrawStore.drawColors[0] - canvas.freeDrawingBrush.shadow = new fabric.Shadow({ - blur: fabricUtils.getShadowWidth(), - offsetX: 0, - offsetY: 0, - color: useDrawStore.shadowColor - }) - } -} -// 封装fabric-canvas -export class fabricVue { - canvas = null // fabric-canvas 对象 - evnet = null // 事件对象 - history = null // 历史记录 - textElement // 文本节点 - hookFn = [] // 钩子 - defConfig // 默认配置-canvas - drawConfig // 默认配置-画笔 - boardConfig // 默认配置-画板 - publicConfig // 公共属性-配置 - - /** 构造函数 */ - constructor() { - // this.textElement = new TextElement() // 创建文本节点 - const initLanguage = ['en', 'en-US', 'en-us'].includes(navigator.language) ? 'en' : 'zh' - this.drawConfig = { // 默认配置 - drawWidth: 1, - drawColors: ['#000000'], - shadowWidth: 0, - shadowColor: '#000000', - drawTextValue: 'draw', - drawStyle: TYPES.DrawStyle.Basic, - drawShapeCount: 2, - materialType: TYPES.MATERIAL_TYPE.CRAYON, - drawShape: TYPES.DrawShape.Bubble, - eraserWidth: 20, - multiColorType: TYPES.MultiColorType.COL, - textFontFamily: 'Georgia', - openAutoDraw: false, - fontStyles: [], - } - this.boardConfig = { // 默认配置 - mode: TYPES.ActionMode.DRAW, - drawType: TYPES.DrawType.FreeStyle, - language: initLanguage, - canvasWidth: 1, - canvasHeight: 1, - backgroundColor: 'rgba(255, 255, 255, 1)', - backgroundOpacity: 1, - hasBackgroundImage: false, - backgroundImageOpacity: 1, - isObjectCaching: true, - openGuideLine: false, - } - this.defConfig = { // 默认配置-canvas - // 它用于指定选中对象时显示的选择框的颜色 - selectionColor: 'rgba(101, 204, 138, 0.3)', - // 用于控制在组合(group)对象时是否保留每个对象的堆叠顺序 - preserveObjectStacking: true, - // 它用于控制是否在高分辨率显示器(例如 Retina 显示器)上启用图像的像素级缩放 - enableRetinaScaling: true, - // 锁定背景,不受缩放影响 false - backgroundVpt: false, - // 默认全屏模式 - width: window.innerWidth, - height: window.innerHeight, - } - this.publicConfig = { // 公共属性配置 - Object: { // 默认配置-对象上 - // 设置对象边框的颜色(Rect、Circle、Ellipse、Path) - borderColor: '#65CC8A', - // 设置对象角落的颜色(Rect、RoundRect、Circle 和 Ellipse 类型的对象) - cornerColor: '#65CC8A', - // 设置角落的形状(Rect、RoundRect、Circle 和 Ellipse 类型的对象) - // circle rect round [圆形,直角(默认),圆角] - cornerStyle: 'circle', - // 设置边框虚线的样式(Rect、Circle、Ellipse、Path) - borderDashArray: [3, 3], - // 矩形(Rect)、圆角矩形(RoundRect)、圆形(Circle)和椭圆形(Ellipse)对象的角落是否透明 - transparentCorners: false - }, - Line: { - // 设置边缘连接方式为 miter bevel round|尖角(默认) 斜角切割线 圆形连接 - strokeLineJoin: 'round', - // 设置结束连接方式为 butt round square|直角(默认) 圆形 正方形 - strokeLineCap: 'round' - } - } - } - /** - * 初始化canvas - * @param {*} canvasEl canvas元素 - * @returns boolean 是否初始化成功 - */ - initCanvas(canvasEl, option = {}) { - return new Promise(async(resolve) => { - if (!canvasEl) resolve(false) - this.canvas = new fabric.Canvas(canvasEl, { - ...this.defConfig, ...option - }) - // this.evnet = new CanvasEvent() // 创建相关事件 - // this.history = new History() // 创建历史记录 - // await this.initCanvasStorage() - resolve(true) - }) - } - -} -export const FabricVue = new fabricVue() -export default FabricVue - diff --git a/src/renderer/src/plugins/fabric/utils/bg.js b/src/renderer/src/plugins/fabric/utils/bg.js deleted file mode 100644 index 61dfd60..0000000 --- a/src/renderer/src/plugins/fabric/utils/bg.js +++ /dev/null @@ -1,77 +0,0 @@ - -/** - * 工具:背景相关的颜色、背景图片 - */ -import { fabric } from 'fabric' -import { FabricVue } from '@/plugins/fabric' -import * as fabricStore from '@/store/modules/draw' - - -/** - * 事件: 缩放变化-更新背景图片 - */ -export const handleBackgroundImageWhenCanvasSizeChange = (isRender = true) => { - const backgroundImage = FabricVue?.canvas?.backgroundImage - if (backgroundImage) { - setBackgroundImage(backgroundImage) - if (isRender) { - FabricVue.canvas?.requestRenderAll() - } - } -} -/** - * 更新 fabric 加载画布背景图片 - * @param {*} data string - * @returns - */ -export const updateCanvasBackgroundImage = (data) => { - const canvas = FabricVue.canvas - if (!canvas) { - return - } - fabric.Image.fromURL( - data, - (image) => { - setBackgroundImage(image) - - canvas.setBackgroundImage(image, () => { - FabricVue.render() - }) - }, - {crossOrigin: 'anonymous'} - ) -} -/** - * 设置-更新背景图片 - * @param {*} image fabric.Image - * @returns - */ -export const setBackgroundImage = (image) => { - const canvas = FabricVue?.canvas - if (!canvas) { - return - } - const useBoardStore = fabricStore.useBoardStore() - const canvasWidth = canvas.getWidth() - const canvasHeight = canvas.getHeight() - - const imgWidth = image.width - const imgHeight = image.height - - const scaleWidth = canvasWidth / imgWidth - const scaleHeight = canvasHeight / imgHeight - - const scale = Math.min(scaleWidth, scaleHeight) - image.scale(scale) - - const imgLeft = canvasWidth / 2 - (imgWidth * scale) / 2 - const imgTop = canvasHeight / 2 - (imgHeight * scale) / 2 - - image.set({ - left: imgLeft, - top: imgTop, - originX: 'left', - originY: 'top', - opacity: useBoardStore.backgroundImageOpacity - }) -} \ No newline at end of file diff --git a/src/renderer/src/plugins/fabric/utils/color.js b/src/renderer/src/plugins/fabric/utils/color.js deleted file mode 100644 index 6e4350e..0000000 --- a/src/renderer/src/plugins/fabric/utils/color.js +++ /dev/null @@ -1,82 +0,0 @@ -/** - * hexToRgba - * @param hex hexadecimal color - * @param alpha - * @returns rgba - */ -export function hexToRgba(hex, alpha = 1) { - const bigint = parseInt(hex.slice(1), 16) - const r = (bigint >> 16) & 255 - const g = (bigint >> 8) & 255 - const b = bigint & 255 - return `rgba(${r}, ${g}, ${b}, ${alpha})` -} - -export function rgbaToHex(rgba) { - if (!rgba) { - return rgba - } - - const values = rgba.match(/\d+/g) - const hex = `#${( - (1 << 24) + - (parseInt(values[0]) << 16) + - (parseInt(values[1]) << 8) + - parseInt(values[2]) - ) - .toString(16) - .slice(1)}` - return hex -} - -/** - * get rgba alpha - * @param rgbaString rgba color - * @returns - */ -export function getAlphaFromRgba(rgbaString) { - const match = rgbaString.match(/rgba?\((\d+),\s*(\d+),\s*(\d+),\s*([\d.]+)\)/) - - if (match) { - return parseFloat(match[4]) - } else { - return 1 - } -} - -/** - * Get the rgba of the new alpha - * @param rgbaColor - * @param newAlpha - * @returns newRgbaColor - */ -export function changeAlpha(rgbaColor, newAlpha) { - const match = rgbaColor.match(/rgba?\((.*?)\)/) - if (!match) { - return rgbaColor - } - - const rgbPart = match[1].split(',').map((i) => i.trim()) - newAlpha = Math.min(1, Math.max(0, newAlpha)) - - const newRgbaColor = `rgba(${rgbPart[0]}, ${rgbPart[1]}, ${rgbPart[2]}, ${newAlpha})` - return newRgbaColor -} - -export function isHexColor(color) { - return /^#([0-9A-Fa-f]{3}){1,2}$/.test(color) -} - -export function isRgbaColor(color) { - return /^rgba?\((\d+,\s*){2}\d+,\s*(0(\.\d+)?|1(\.0)?)\)$/.test(color) -} - -export function getColorFormat(color) { - if (isHexColor(color)) { - return 'hex' - } else if (isRgbaColor(color)) { - return 'rgba' - } else { - return 'unknown' - } -} diff --git a/src/renderer/src/plugins/fabric/utils/draw.js b/src/renderer/src/plugins/fabric/utils/draw.js deleted file mode 100644 index 459cb7c..0000000 --- a/src/renderer/src/plugins/fabric/utils/draw.js +++ /dev/null @@ -1,33 +0,0 @@ -import * as fabricStore from '@/store/modules/draw' -import { FabricVue } from "@/plugins/fabric"; -// import { v4 as uuidv4 } from 'uuid' -const useDrawStore = fabricStore.useDrawStore() - -export const getDrawWidth = (width) => { - return ( - (width ?? useDrawStore.drawWidth) / - (FabricVue.canvas?.getZoom() ?? 1) - ) -} - -export const getEraserWidth = (width) => { - return ( - (width ?? useDrawStore.eraserWidth) / - (FabricVue.canvas?.getZoom() ?? 1) - ) -} - -export const getShadowWidth = (width) => { - return ( - (width ?? useDrawStore.shadowWidth) / - (FabricVue.canvas?.getZoom() ?? 1) - ) -} - -// export const setObjectAttr = (obj, type) => { -// const id = uuidv4() -// obj.set({ -// id, -// _customType: type -// }) -// } diff --git a/src/renderer/src/plugins/fabric/utils/index.js b/src/renderer/src/plugins/fabric/utils/index.js deleted file mode 100644 index 0d98900..0000000 --- a/src/renderer/src/plugins/fabric/utils/index.js +++ /dev/null @@ -1,165 +0,0 @@ -/** - * 工具类 - */ -import { FabricVue } from '@/plugins/fabric' -import * as TYPES from '@/constants/draw' -import * as lineUtils from '@/plugins/fabric/utils/shape/line' -import * as arrowLineUtils from '@/plugins/fabric/utils/shape/arrowLine' - -/** - * compare version - * @param v1 - * @param v2 - * @returns 0: v1 === v2; 1: v1 > v2; -1: v1 < v2 - */ -export const compareVersion = (v1, v2) => { - const v1s = v1.split('.') - const v2s = v2.split('.') - const len = Math.max(v1s.length, v2s.length) - - while (v1s.length < len) { - v1s.push('0') - } - while (v2s.length < len) { - v1s.push('0') - } - - for (let i = 0; i < len; i++) { - const num1 = parseInt(v1s[i]) - const num2 = parseInt(v2s[i]) - - if (num1 > num2) { - return 1 - } else if (num1 < num2) { - return -1 - } - } - return 0 -} - -/** - * get random integer - * @param min min range - * @param max max range - */ -export const getRandomInt = (min, max) => { - return Math.floor(Math.random() * (max - min + 1)) + min -} - -/** - * get random float - * @param min min range - * @param max max range - */ -export const getRandomFloat = (min, max) => { - return Math.random() * (max - min) + min -} - -/** - * handle static file paths in the public folder - * @param originUrl - * @returns publicDir + originUrl - */ -export const formatPublicUrl = (originUrl) => { - if (originUrl && typeof originUrl === 'string') { - return `${import.meta.env.BASE_URL}${originUrl}` - } - return '' -} - -/** - * get the distance between two points - * @param start - * @param end - * @returns distance - */ -export const getDistance = (start, end) => { - return Math.sqrt(Math.pow(start.x - end.x, 2) + Math.pow(start.y - end.y, 2)) -} - -/** - * is it mobile - */ -export const isMobile = () => { - return /phone|pad|pod|iPhone|iPod|ios|iPad|Android|Mobile|BlackBerry|IEMobile|MQQBrowser|JUC|Fennec|wOSBrowser|BrowserNG|WebOS|Symbian|Windows Phone|Mobi|Android|iPhone|iPad/i.test( - navigator.userAgent - ) -} -// 生成UUID -export const getUUID = () => { - const timestamp = Date.now(); - // 时间戳部分 - const timestampPart = timestamp.toString(16).padStart(12, '0'); - - // 生成随机数 - const rnd = crypto.getRandomValues(new Uint8Array(4)); - const randomPart = rnd.map(byte => byte.toString(16).padStart(2, '0')).join(''); - - // 应用变体和版本标识符 - const version = (timestamp >= 0x0100000000000000) ? (timestamp & 0x00000000FFFF0000) : 0x4000; - const variant = (rnd[0] & 0x0F) | 0x80; - - // 合并所有部分并返回 - return `${timestampPart}-${randomPart}-${version.toString(16).padStart(4, '0')}-${variant.toString(16).padStart(4, '0')}-${rnd[3].toString(16).padStart(4, '0')}`; -} - -/** loadCanvas */ -export const getCanvasJSON = () => { - const canvas = FabricVue?.canvas - if (canvas) { - return ( - // 返回额外属性 - canvas.toDatalessJSON([ - 'id', - '_customType', - 'perPixelTargetFind', - 'objectCaching' - ]) ?? {} - ) - } - return {} -} - -/** - * Handling canvas json loaded data - * Used to initialize undo redo - * @param canvas fabric.Canvas - */ -export const handleCanvasJSONLoaded = (canvas) => { - canvas.getObjects().forEach((obj) => { - if (obj._customType === TYPES.ELEMENT_CUSTOM_TYPE.SHAPE_LINE) { - const points = obj.points - const lastControl = points.length - 1 - obj.controls = points.reduce(function (acc, point, index) { - acc['p' + index] = new fabric.Control({ - positionHandler: lineUtils.polygonPositionHandler, - actionHandler: lineUtils.anchorWrapper( - index > 0 ? index - 1 : lastControl, - lineUtils.actionHandler - ), - actionName: 'polylineEndPoint', - pointIndex: index - }) - return acc - }, {}) - } - - if (obj._customType === TYPES.ELEMENT_CUSTOM_TYPE.SHAPE_ARROW_LINE) { - const paths = (obj).path - obj.controls = paths - .slice(0, paths.length - 4) - .reduce(function (acc, point, index) { - acc['p' + index] = new fabric.Control({ - positionHandler: arrowLineUtils.pathPositionHandler, - actionHandler: arrowLineUtils.anchorWrapper( - index > 0 ? index - 1 : paths.length - 5, - arrowLineUtils.actionHandler - ), - actionName: 'pathEndPoint', - pointIndex: index - }) - return acc - }, {}) - } - }) -} \ No newline at end of file diff --git a/src/renderer/src/plugins/fabric/utils/shape/arrowLine.js b/src/renderer/src/plugins/fabric/utils/shape/arrowLine.js deleted file mode 100644 index 1c3a0cf..0000000 --- a/src/renderer/src/plugins/fabric/utils/shape/arrowLine.js +++ /dev/null @@ -1,164 +0,0 @@ -import { fabric } from 'fabric' - -export function calculateArrowSlidePath( - paths, - startX, - startY, - endX, - endY -) { - const angleRad = Math.atan2(endY - startY, endX - startX) - const arrowWidthHalf = 10 - - const arrowLeftX = endX - arrowWidthHalf * Math.cos(angleRad - Math.PI / 6) - const arrowLeftY = endY - arrowWidthHalf * Math.sin(angleRad - Math.PI / 6) - const arrowRightX = endX - arrowWidthHalf * Math.cos(angleRad + Math.PI / 6) - const arrowRightY = endY - arrowWidthHalf * Math.sin(angleRad + Math.PI / 6) - - paths[paths.length - 4][1] = endX - paths[paths.length - 4][2] = endY - paths[paths.length - 3][1] = arrowLeftX - paths[paths.length - 3][2] = arrowLeftY - paths[paths.length - 2][1] = endX - paths[paths.length - 2][2] = endY - paths[paths.length - 1][1] = arrowRightX - paths[paths.length - 1][2] = arrowRightY - - return paths -} - -/** - * - * @param {*} object fabric.Path - * @returns - */ -export function getObjectSizeWithStroke(object) { - const stroke = new fabric.Point( - object.strokeUniform ? 1 / (object.scaleX) : 1, - object.strokeUniform ? 1 / (object.scaleY) : 1 - ).multiply(object.strokeWidth) - return new fabric.Point( - (object.width) + stroke.x, - (object.height) + stroke.y - ) -} - -/** - * - * @param {*} this fabric.Control - * @param {*} dim - * @param {*} finalMatrix - * @param {*} fabricObject fabric.Path - * @returns fabric.Control['positionHandler'] - */ -export const pathPositionHandler = function ( - fabricControl, - dim, - finalMatrix, - fabricObject -) { - const paths = fabricObject.path - const x = paths[fabricControl.pointIndex][1] - fabricObject.pathOffset.x - const y = paths[fabricControl.pointIndex][2] - fabricObject.pathOffset.y - return fabric.util.transformPoint( - { x, y }, - fabric.util.multiplyTransformMatrices( - fabricObject.canvas?.viewportTransform, - fabricObject.calcTransformMatrix() - ) - ) -} - -/** - * - * @param {*} eventData - * @param {*} transform - * @param {*} x - * @param {*} y - * @returns fabric.Control['actionHandler'] - */ -export const actionHandler = function ( - eventData, - transform, - x, - y -) { - const pathObj = transform.target, - currentControl = pathObj.controls[pathObj.__corner], - mouseLocalPosition = pathObj.toLocalPoint( - new fabric.Point(x, y), - 'center', - 'center' - ), - pathBaseSize = getObjectSizeWithStroke(pathObj), - size = pathObj._getTransformedDimensions(0, 0), - finalPointPosition = { - x: - (mouseLocalPosition.x * pathBaseSize.x) / size.x + pathObj.pathOffset.x, - y: (mouseLocalPosition.y * pathBaseSize.y) / size.y + pathObj.pathOffset.y - } - - const path = pathObj.path - path[currentControl.pointIndex][1] = finalPointPosition.x - path[currentControl.pointIndex][2] = finalPointPosition.y - - if ( - currentControl.pointIndex === path?.length - 5 || - currentControl.pointIndex === path?.length - 6 - ) { - const point1 = path[path.length - 6] - const point2 = path[path.length - 5] - const newPath = calculateArrowSlidePath( - path, - point1[1], - point1[2], - point2[1], - point2[2] - ) - pathObj._setPath(newPath) - } - - return true -} - -/** - * - * @param {*} anchorIndex - * @param {*} fn fabric.Control['actionHandler'] - * @returns MouseEvent fabric.Transform - */ -export function anchorWrapper( - anchorIndex, - fn -) { - return function ( - eventData, - transform, - x, - y - ) { - const fabricObject = transform.target - const paths = fabricObject.path - const absolutePoint = fabric.util.transformPoint( - { - x: paths[anchorIndex][1] - fabricObject.pathOffset.x, - y: paths[anchorIndex][2] - fabricObject.pathOffset.y - }, - fabricObject.calcTransformMatrix() - ) - const actionPerformed = fn(eventData, transform, x, y) - fabricObject._setPath(fabricObject.path) - const pathBaseSize = getObjectSizeWithStroke(fabricObject) - - const newX = - (paths[anchorIndex][1] - fabricObject.pathOffset.x) / pathBaseSize.x - const newY = - (paths[anchorIndex][2] - fabricObject.pathOffset.y) / pathBaseSize.y - fabricObject.setPositionByOrigin( - absolutePoint, - (newX + 0.5), - (newY + 0.5) - ) - return actionPerformed - } -} diff --git a/src/renderer/src/plugins/fabric/utils/shape/line.js b/src/renderer/src/plugins/fabric/utils/shape/line.js deleted file mode 100644 index e63fd59..0000000 --- a/src/renderer/src/plugins/fabric/utils/shape/line.js +++ /dev/null @@ -1,109 +0,0 @@ -/** - * 线 - */ -import { fabric } from 'fabric' - -/** - * - * @param {*} object fabric.Polyline - * @returns - */ -export function getObjectSizeWithStroke(object) { - const stroke = new fabric.Point( - object.strokeUniform ? 1 / (object.scaleX) : 1, - object.strokeUniform ? 1 / (object.scaleY) : 1 - ).multiply(object.strokeWidth) - return new fabric.Point( - (object.width) + stroke.x, - (object.height) + stroke.y - ) -} - -/** - * - * @param {*} this fabric.Control - * @param {*} dim - * @param {*} finalMatrix - * @param {*} fabricObject fabric.Polyline - * @returns - */ -export const polygonPositionHandler = function ( - fabricControl, dim, finalMatrix,fabricObject) { - const points = fabricObject.points - const x = points[fabricControl.pointIndex].x - fabricObject.pathOffset.x - const y = points[fabricControl.pointIndex].y - fabricObject.pathOffset.y - return fabric.util.transformPoint( - { x, y }, - fabric.util.multiplyTransformMatrices( - fabricObject.canvas?.viewportTransform, - fabricObject.calcTransformMatrix() - ) - ) -} - -/** - * - * @param {*} eventData - * @param {*} transform - * @param {*} x - * @param {*} y - * @returns fabric.Control['actionHandler'] - */ -export const actionHandler = function ( - eventData, transform,x, y) { - const polygon = transform.target, - currentControl = polygon.controls[polygon.__corner], - mouseLocalPosition = polygon.toLocalPoint( - new fabric.Point(x, y), - 'center', - 'center' - ), - polygonBaseSize = getObjectSizeWithStroke(polygon), - size = polygon._getTransformedDimensions(0, 0), - finalPointPosition = { - x: - (mouseLocalPosition.x * polygonBaseSize.x) / size.x + - polygon.pathOffset.x, - y: - (mouseLocalPosition.y * polygonBaseSize.y) / size.y + - polygon.pathOffset.y - } - - const points = polygon.points - points[currentControl.pointIndex] = finalPointPosition - - return true -} -/** - * - * @param {*} anchorIndex number - * @param {*} fn fabric.Control['actionHandler'] - * @returns function (eventData, transform, x, y ) | MouseEvent fabric.Transform number number - */ -export function anchorWrapper(anchorIndex, fn) { - return function (eventData, transform, x, y ) { - const fabricObject = transform.target - const points = fabricObject.points - const absolutePoint = fabric.util.transformPoint( - { - x: points[anchorIndex].x - fabricObject.pathOffset.x, - y: points[anchorIndex].y - fabricObject.pathOffset.y - }, - fabricObject.calcTransformMatrix() - ) - const actionPerformed = fn(eventData, transform, x, y) - fabricObject._setPositionDimensions({}) - const polygonBaseSize = getObjectSizeWithStroke(fabricObject) - - const newX = - (points[anchorIndex].x - fabricObject.pathOffset.x) / polygonBaseSize.x - const newY = - (points[anchorIndex].y - fabricObject.pathOffset.y) / polygonBaseSize.y - fabricObject.setPositionByOrigin( - absolutePoint, - (newX + 0.5), - (newY + 0.5) - ) - return actionPerformed - } -} \ No newline at end of file diff --git a/src/renderer/src/store/modules/draw.js b/src/renderer/src/store/modules/draw.js deleted file mode 100644 index bf486e1..0000000 --- a/src/renderer/src/store/modules/draw.js +++ /dev/null @@ -1,295 +0,0 @@ -/** - * @description fabric.js 缓存画板 - */ - -import { defineStore } from "pinia"; -import * as TYPES from "@/constants/draw"; -import { FabricVue } from "@/plugins/fabric"; -import * as fabricUtils from '@/plugins/fabric/utils/draw' -import * as colorUtils from '@/plugins/fabric/utils/color' -import * as bgUtils from '@/plugins/fabric/utils/bg' -import * as commUtils from '@/plugins/fabric/utils' -import { material } from '@/plugins/fabric/nodes/draw/material' -import { renderMultiColor } from '@/plugins/fabric/nodes/draw/multiColor' - -// 画板模块-缓存 json相关 -export const BOARD_VERSION = '1.0.0' - -const getFileObj = (data) => ({ - id: commUtils.getUUID(), - title: 'paint-board', - boardVersion: BOARD_VERSION, - boardData: {}, - zoom: 1, - canvasWidth: 1, - canvasHeight: 1 - , ...data -}) -const initFileObj = getFileObj() -export const useFileStore = defineStore( - 'file', - { - state() { - return { - currentId: initFileObj.id, - currentIndex: 0, - currentFile: initFileObj, - files: [ initFileObj ], - } - }, - actions: { - // update file | 更新文件 - updateFile(file = {}) { - if (file?.id != this.currentId) return - this.currentFile = file - this.files[this.currentIndex] = data - }, - // update board data | 更新画板数据 - updateBoardData(data) { - this.currentFile.boardData = data - this.files[this.currentIndex].boardData = data - }, - // update board zoom | 更新画板缩放 - updateBoardZoom(zoom){ - this.currentFile.zoom = zoom - this.files[this.currentIndex].zoom = zoom - }, - // update board field | 更新画板字段 - updateBoardField(key, value) { - if(key == 'id') return - this.currentFile[key] = value - this.files[this.currentIndex][key] = value - }, - // 创建新画板 - createBoard(option = {}) { - delete option?.id - let fileObj = getFileObj(option) - this.currentFile = fileObj - this.currentId = fileObj.id - this.files.push(fileObj) - this.currentIndex = this.files.length - 1 - return fileObj - } - } - } -) - -// 主模块-缓存 -const initLanguage = ['en', 'en-US', 'en-us'].includes(navigator.language) ? 'en' : 'zh' -export const useBoardStore = defineStore( - 'board', - { - state() { - return { - mode: TYPES.ActionMode.DRAW, - drawType: TYPES.DrawType.FreeStyle, - language: initLanguage, - canvasWidth: 1, - canvasHeight: 1, - backgroundColor: 'rgba(255, 255, 255, 1)', - backgroundOpacity: 1, - hasBackgroundImage: false, - backgroundImageOpacity: 1, - isObjectCaching: true, - openGuideLine: false, - } - }, - actions: { - // update mode | 更新模式 - updateMode(mode) { - if (this.mode != mode) FabricVue.handleMode(mode) - this.mode = mode - }, - // update draw type | 更新画笔类型 - updateDrawType(drawType) { - if (this.drawType != drawType){ - this.drawType = drawType - FabricVue.handleMode() - } - }, - // update language | 更新语言 - updateLanguage(language) {this.language = language}, - updateCanvasWidth(width) { - if (this.canvasWidth !== width){ - this.canvasHeight = width - FabricVue.updateCanvasWidth(width) - } - }, - // init background | 初始化背景 - initBackground() { - const backgroundColor = FabricVue?.canvas?.backgroundColor - console.log('xxxx', backgroundColor, FabricVue?.canvas) - if (backgroundColor && typeof backgroundColor === 'string') { - const type = colorUtils.getColorFormat(backgroundColor) - if (type === 'hex') { - const color = colorUtils.hexToRgba(backgroundColor) - const opacity = colorUtils.getAlphaFromRgba(color) - this.backgroundColor = color - this.backgroundOpacity = opacity - } else if (type === 'rgba') { - const opacity = colorUtils.getAlphaFromRgba(backgroundColor) - this.backgroundColor = backgroundColor - this.backgroundOpacity = opacity - } - } else if (FabricVue?.canvas) { - if (this.backgroundColor) { - FabricVue.canvas.backgroundColor = this.backgroundColor - } else { - FabricVue.canvas.backgroundColor = 'rgba(255, 255, 255, 1)' - this.backgroundColor = 'rgba(255, 255, 255, 1)' - this.backgroundOpacity = 1 - } - } - - const backgroundImage = FabricVue?.canvas?.backgroundImage - if (backgroundImage) { - bgUtils.handleBackgroundImageWhenCanvasSizeChange() - this.hasBackgroundImage = true - this.backgroundOpacity = backgroundImage.opacity - } else { - this.hasBackgroundImage = true - this.backgroundOpacity = 1 - } - } - } - } -) - -// 画笔模块-缓存 -export const useDrawStore = defineStore( - 'draw', - { - state() { - return { - drawWidth: 1, - drawColors: ['#000000'], - shadowWidth: 0, - shadowColor: '#000000', - drawTextValue: 'draw', - drawStyle: TYPES.DrawStyle.Basic, - drawShapeCount: 2, - materialType: TYPES.MATERIAL_TYPE.CRAYON, - drawShape: TYPES.DrawShape.Bubble, - eraserWidth: 20, - multiColorType: TYPES.MultiColorType.COL, - textFontFamily: 'Georgia', - openAutoDraw: false, - fontStyles: [], - } - }, - actions: { - // update draw width | 更新画笔宽度 - updateDrawWidth(w) { - const oldW = this.drawWidth - if (oldW != w && FabricVue.canvas) { - FabricVue.canvas.freeDrawingBrush.width = fabricUtils.getDrawWidth(w) - this.drawWidth = w - } - }, - // update draw colors | 更新画笔颜色 - updateDrawColors(drawColors = []) { - const drawStyle = this.drawStyle - switch(drawStyle) { - case TYPES.DrawStyle.Basic: - if (FabricVue.canvas) { - FabricVue.canvas.freeDrawingBrush.color = drawColors[0] - } - break - case TYPES.DrawStyle.Material: - if (this.drawColors[0] != drawColors[0]) { - material.render({}) - } - break - case TYPES.DrawStyle.MultiColor: - renderMultiColor({colors: drawColors}) - break - } - this.drawColors = drawColors - }, - // update shadow width | 更新画笔阴影宽度 - updateShadowWidth(shadowWidth) { - if (FabricVue.canvas) { - (FabricVue.canvas.freeDrawingBrush.shadow).blur = fabricUtils.getShadowWidth(shadowWidth) - } - this.shadowWidth = shadowWidth - }, - // update shadow color | 更新画笔阴影颜色 - updateShadowColor(shadowColor) { - if (FabricVue.canvas) { - (FabricVue.canvas.freeDrawingBrush.shadow).color = shadowColor - } - this.shadowColor = shadowColor - }, - // update shadow Shape | 更新画笔阴影形状 - updateDrawShape(drawShape) { - this.drawShape = drawShape - }, - // update draw style | 更新画笔阴影样式 - updateDrawStyle(drawStyle) { - this.drawStyle = drawStyle - FabricVue.handleDrawStyle() - }, - // update draw shape count | 更新画笔形状数量 - updateDrawShapeCount(drawShapeCount){ - this.drawShapeCount = drawShapeCount - }, - // update draw text value | 更新画笔文字 - updateDrawTextValue(drawTextValue){ - this.drawTextValue = drawTextValue - }, - // update material type | 更新材质类型 - updateMaterialType(materialType) { - if(this.materialType != materialType) { - material.render({materialType}) - this.materialType = materialType - } - }, - // update eraser width | 更新橡皮擦宽度 - updateEraserWidth(eraserWidth) { - const oldW = this.drawWidth - if (oldW != eraserWidth && FabricVue.canvas) { - FabricVue.canvas.freeDrawingBrush.width = fabricUtils.getEraserWidth(eraserWidth) - this.eraserWidth = eraserWidth - } - }, - // update multi color type | 更新多色材质类型 - updateMultiColorType(multiColorType) { - if(this.multiColorType != multiColorType) { - renderMultiColor({type: multiColorType}) - this.multiColorType = multiColorType - } - }, - // update text font family | 更新文字字体 - updateTextFontFamily(fontFamily){ - this.textFontFamily = fontFamily - }, - // update auto draw state | 更新自动绘图状态 - updateAutoDrawState() { - this.openAutoDraw = !this.openAutoDraw - }, - // update font styles | 更新字体样式 - updateFontStyles(type) { - const ind = this.fontStyles.findIndex(item => item === type) - if (ind >= 0) this.fontStyles.splice(ind, 1) - else this.fontStyles.push(type) - }, - } - } -) - - - - -// interface BoardState { -// mode: string // operating mode -// drawType: string // draw type -// language: string // i18n language 'zh' 'en' -// canvasWidth: number // canvas width 0.1 ~ 1 -// canvasHeight: number // canvas height 0.1 ~ 1 -// backgroundColor: string // canvas background color -// backgroundOpacity: number // canvas background color opacity -// hasBackgroundImage: boolean // canvas background image -// backgroundImageOpacity: number // canvas background Image opacity -// isObjectCaching: boolean // fabric objectCaching -// openGuideLine: boolean // does the guide line show -// } \ No newline at end of file diff --git a/src/renderer/src/views/tool/test.vue b/src/renderer/src/views/tool/test.vue index 153d10c..d49b599 100644 --- a/src/renderer/src/views/tool/test.vue +++ b/src/renderer/src/views/tool/test.vue @@ -1,5 +1,6 @@