UNPKG

@tencentcloud/roomkit-electron-vue3

Version:

<h1 align="center"> TUIRoomKit</h1> Conference (TUIRoomKit) is a product suitable for multi-person audio and video conversation scenarios such as business meetings, webinars, and online education. By integrating this product, you can add room management,

716 lines (647 loc) 18.5 kB
import { fabric } from 'fabric'; import { Canvas as ICanvas, Image as IImage, Object as IObject, ILineOptions, IImageOptions, IRectOptions, ICircleOptions, ITextOptions, IEllipseOptions, IEvent, ICanvasOptions, ITriangleOptions, Circle, Ellipse, Line, } from 'fabric/fabric-impl'; import EventEmitter from './../emitter'; import Arrow from './arrow'; // import initHotKeys from './initHotKeys'; import initControls from './initControls'; import initControlsRotate from './initControlsRotate'; import eventBus from '../../../hooks/useMitt'; import { DrawingTool, ShapeOptions, ToolSettings } from './../type'; interface FabricEvents { 'object:added': IEvent; 'object:modified': IEvent; 'object:removed': IEvent; 'mouse:down': IEvent; 'mouse:move': IEvent; 'mouse:up': IEvent; 'after:render': IEvent; 'push-canvas-to-stack': IEvent; [key: string | symbol]: IEvent | any | undefined; } class FabricCanvas extends EventEmitter<FabricEvents> { private canvas: ICanvas; private currentShape: IObject | null = null; private drawingTool: DrawingTool = DrawingTool.None; private isDrawing = false; private startX = 0; private startY = 0; private endX = 0; private endY = 0; private options: ShapeOptions = { strokeWidth: 5, stroke: '#22262E', fill: 'transparent', lineDash: [], opacity: 1, }; private images: string[] = []; private curImageIndex = 0; private isValidEraser = false; private isValidSelection = false; constructor(canvasId: string) { super(); this.canvas = new fabric.Canvas(canvasId, { isDrawingMode: true, selection: false, includeDefaultValues: false, perPixelTargetFind: true, }); this.setDrawingTool(DrawingTool.Pencil); // initHotKeys(this.canvas, this); initControls(this.canvas); initControlsRotate(this.canvas); this.initEvent(); } public getCanvas(): ICanvas { return this.canvas; } public renderCanvas(data: any) { this.loadFromJSON(data, () => { this.renderAll(); }); } public setBackgroundColor(color: string): void { this.canvas.setBackgroundColor(color, () => { this.canvas.renderAll(); }); } public setBackgroundImage(imageUrl: string, options?: IImageOptions): void { fabric.Image.fromURL(imageUrl, (image: IImage) => { const canvasWidth = this.canvas.getWidth(); const canvasHeight = this.canvas.getHeight(); const scale = canvasHeight / (image.height as number); const imageWidth = (image.width as number) * scale; image.set({ scaleX: scale, scaleY: scale, top: 0, left: (canvasWidth - imageWidth) / 2, }); this.canvas.setBackgroundImage( image, () => { this.canvas.renderAll(); }, options ); }); } public addObject(object: IObject): void { this.canvas.add(object); } public removeObject(object: IObject): void { this.canvas.remove(object); } public removeAllObject() { this.canvas.getObjects().forEach(obj => { this.canvas.remove(obj); }); } public getObjects(): IObject[] { return this.canvas.getObjects(); } public getActiveObject(): IObject | undefined | null { return this.canvas.getActiveObject(); } public setActiveObject(object: IObject): void { this.canvas.setActiveObject(object); } public resetActiveObject(): void { const allObjects = this.canvas.getObjects(); const activeSelection = new fabric.ActiveSelection(allObjects, { canvas: this.canvas, }); this.canvas.setActiveObject(activeSelection); this.canvas.discardActiveObject(); } public setWidth(value: number | string): void { this.canvas.setWidth(value); } public setHeight(value: number | string): void { this.canvas.setHeight(value); } public exitTextEditing(): void { const activeObject = this.canvas.getActiveObject() as any; if (activeObject && activeObject.type === 'i-text') { activeObject.exitEditing(); } } public setObjectsSelectable(objectsSelectable: boolean) { this.canvas.getObjects().forEach((obj: any) => { obj.selectable = objectsSelectable; obj.evented = objectsSelectable; }); this.canvas.perPixelTargetFind = !objectsSelectable; this.canvas.selection = objectsSelectable; if (!objectsSelectable) { this.canvas.renderAll(); } } public setEvented() { this.canvas.getObjects().forEach((obj: any) => { obj.evented = true; }); this.canvas.renderAll(); } public clearCanvas() { this.canvas.clear(); } public reloadCanvas() { this.clearCanvas(); this.canvas.renderAll(); eventBus.emit('reload-canvas'); this.setDrawingTool(DrawingTool.Pencil); this.currentShape = null; } public setDrawingTool(tool: DrawingTool) { if (this.drawingTool === tool) return; this.canvas.isDrawingMode = false; this.drawingTool = tool; this.canvas.discardActiveObject(); this.setObjectsSelectable(false); switch (tool) { case DrawingTool.Pencil: this.drawFreeDraw(); break; case DrawingTool.Eraser: this.setEvented(); this.setEraser(); break; case DrawingTool.Select: this.setObjectsSelectable(true); this.resetActiveObject(); this.canvas.defaultCursor = 'move'; break; case DrawingTool.Arrow: this.canvas.defaultCursor = 'crosshair'; break; case DrawingTool.Text: this.canvas.defaultCursor = 'text'; break; default: this.canvas.defaultCursor = 'crosshair'; break; } } public setOptions(toolSetting: ToolSettings) { this.options = toolSetting.shapeOptions!; this.setPencilBrushOptions(); } public setTextOptions(): ITextOptions { const options = { fontSize: this.options.strokeWidth, fill: this.options.stroke, padding: 5, selectable: false, evented: false, }; return options; } public setPencilBrushOptions(): void { this.canvas.freeDrawingBrush.color = this.options.stroke; this.canvas.freeDrawingBrush.width = this.options.strokeWidth; } public discardActiveObject() { this.canvas.discardActiveObject(); } public drawRect(options: IRectOptions): void { const rect = new fabric.Rect({ ...this.options, selectable: false, evented: false, strokeUniform: true, noScaleCache: false, strokeDashArray: this.options.lineDash, ...options, }); this.canvas.add(rect); this.currentShape = rect; this.canvas.defaultCursor = 'crosshair'; } public drawTriangle(options: ITriangleOptions): void { const triangle = new fabric.Triangle({ ...this.options, selectable: false, evented: false, strokeUniform: true, noScaleCache: false, strokeDashArray: this.options.lineDash, ...options, }); this.canvas.add(triangle); this.currentShape = triangle; this.canvas.defaultCursor = 'crosshair'; } public drawCircle(options: ICircleOptions): void { const circle = new fabric.Circle({ ...this.options, selectable: false, evented: false, strokeUniform: true, noScaleCache: false, strokeDashArray: this.options.lineDash, ...options, }); this.canvas.add(circle); this.currentShape = circle; this.canvas.defaultCursor = 'crosshair'; } public drawEllipse(options: IEllipseOptions): void { const ellipse = new fabric.Ellipse({ ...this.options, selectable: false, evented: false, strokeUniform: true, noScaleCache: false, strokeDashArray: this.options.lineDash, ...options, }); this.canvas.add(ellipse); this.currentShape = ellipse; this.canvas.defaultCursor = 'crosshair'; } public drawLine( x1: number, y1: number, x2: number, y2: number, options?: ILineOptions ): void { const line = new fabric.Line([x1, y1, x2, y2], { ...this.options, ...options, selectable: false, strokeDashArray: this.options.lineDash, evented: false, strokeUniform: true, noScaleCache: false, }); this.canvas.add(line); this.currentShape = line; this.canvas.defaultCursor = 'crosshair'; } public drawArrow( x1: number, y1: number, x2: number, y2: number, options?: ILineOptions ): void { const customOptions = { ...options, arrowWidth: options?.arrowWidth ?? this.options.strokeWidth, arrowHeight: options?.arrowHeight ?? this.options.strokeWidth, }; const arrow = new Arrow([x1, y1, x2, y2], { ...this.options, ...customOptions, selectable: false, evented: false, strokeUniform: true, noScaleCache: false, }); this.canvas.add(arrow); this.currentShape = arrow; this.canvas.defaultCursor = 'crosshair'; } public drawFreeDraw() { this.canvas.freeDrawingBrush = new fabric.PencilBrush(this.canvas); this.canvas.freeDrawingBrush.color = this.options.stroke; this.canvas.freeDrawingBrush.width = this.options.strokeWidth; this.canvas.freeDrawingCursor = 'default'; this.canvas.isDrawingMode = true; this.currentShape = null; this.canvas.on('path:created', (event: any) => { const { path } = event; if (path) { path.set({ perPixelTargetFind: true, objectCaching: false, statefullCache: true, strokeUniform: true, noScaleCache: false, subdivisionScale: 100, }); this.canvas.renderAll(); } }); } public drawText(text: string, options?: ITextOptions): void { const textObj = new fabric.IText(text, { fontSize: this.options.strokeWidth, fill: this.options.stroke, padding: 5, selectable: false, evented: false, ...options, }); this.canvas.add(textObj); this.canvas.defaultCursor = 'text'; this.currentShape = textObj; textObj.enterEditing(); textObj.hiddenTextarea.focus(); textObj.on('editing:exited', () => { if (textObj.text.length > 0) { this.emit('push-canvas-to-stack', null); } else { this.canvas.remove(textObj); } }); this.setActiveObject(textObj); } public insertImage(url: string, options?: IImageOptions): void { fabric.Image.fromURL(url, (img: IImage) => { if (options) { img.set(options); } else { const canvasWidth = this.canvas.getWidth(); const canvasHeight = this.canvas.getHeight(); const imageWidth = (img.width as number) * (img.scaleX as number); const imageHeight = (img.height as number) * (img.scaleY as number); const aspectRatio = imageWidth / imageHeight; const halfCanvasWidth = canvasWidth / 2; const halfCanvasHeight = canvasHeight / 2; let scaledWidth; let scaledHeight; if (aspectRatio > 1) { scaledWidth = halfCanvasWidth; scaledHeight = scaledWidth / aspectRatio; } else { scaledHeight = halfCanvasHeight; scaledWidth = scaledHeight * aspectRatio; } const scale = scaledWidth / imageWidth; img.scale(scale); const left = (canvasWidth - scaledWidth) / 2; const top = (canvasHeight - scaledHeight) / 2; img.set({ left, top }); } this.canvas.add(img); this.setDrawingTool(DrawingTool.Select); this.setActiveObject(img); this.emit('push-canvas-to-stack', null); this.canvas.requestRenderAll(); this.emit('insert-images', null); }); } public insertPPT(urls: string[]): void { this.images = urls; this.setCurrentScense(0); this.emit('insert:images', urls); } public setCurrentScense(index: number): void { this.curImageIndex = index; this.setBackgroundImage(this.images[this.curImageIndex]); this.emit('current:image', index); } public setEraser(): void { this.canvas.renderAll(); } private initEvent() { this.canvas.on('mouse:down', this.onMouseDown.bind(this)); this.canvas.on('mouse:move', this.onMouseMove.bind(this)); this.canvas.on('mouse:up', this.onMouseUp.bind(this)); this.canvas.on('object:moving', this.onObjectMoving.bind(this)); this.canvas.on('object:scaling', this.onObjectScaling.bind(this)); this.canvas.on('object:rotating', this.onObjectRotating.bind(this)); eventBus.on('exitTextEditing', this.exitTextEditing.bind(this)); } private onMouseDown(event: IEvent) { if (this.drawingTool === DrawingTool.Eraser) { this.isDrawing = true; const target = this.canvas.findTarget(event.e, false); if (target) { target.group?.removeWithUpdate(target) || this.canvas.remove(target); this.isValidEraser = true; } this.canvas.renderAll(); return; } const activeObject = this.canvas.getActiveObject(); if (!event.pointer || activeObject) return; this.isDrawing = true; const { x, y } = event.pointer; this.startX = x; this.startY = y; this.endX = this.startX; this.endY = this.startY; switch (this.drawingTool) { case DrawingTool.Pencil: this.setPencilBrushOptions(); break; case DrawingTool.Rectangle: this.drawRect({ left: x, top: y, width: 0, height: 0, }); break; case DrawingTool.Triangle: this.drawTriangle({ left: x, top: y, width: 0, height: 0, }); break; case DrawingTool.Circle: this.drawCircle({ left: x, top: y, radius: 0, }); break; case DrawingTool.Ellipse: this.drawEllipse({ left: x, top: y, rx: 0, ry: 0, }); break; case DrawingTool.Line: this.drawLine(x, y, x, y); break; case DrawingTool.Arrow: this.drawArrow(x, y, x, y, { arrowWidth: this.options.strokeWidth, arrowHeight: this.options.strokeWidth, }); break; case DrawingTool.Text: this.drawText('', { options: this.setTextOptions(), left: x, top: y }); break; default: break; } } private onMouseMove(event: IEvent) { if (!event.pointer) { return; } const { x, y } = event.pointer; this.endX = x; this.endY = y; if (this.drawingTool === DrawingTool.Eraser && this.isDrawing) { const target = this.canvas.findTarget(event.e, false); if (target) { target.group?.removeWithUpdate(target) || this.canvas.remove(target); this.isValidEraser = true; } this.canvas.renderAll(); } if (!this.isDrawing || !this.currentShape) { return; } const width = Math.abs(x - this.startX); const height = Math.abs(y - this.startY); const left = Math.min(this.startX, x); const top = Math.min(this.startY, y); switch (this.drawingTool) { case DrawingTool.Rectangle: this.currentShape.set({ left, top, width, height, }); break; case DrawingTool.Triangle: this.currentShape.set({ left, top, width, height, }); break; case DrawingTool.Circle: { const radius = Math.sqrt(width * width + height * height) / 2; (this.currentShape as Circle).set({ left, top, radius, }); } break; case DrawingTool.Ellipse: (this.currentShape as Ellipse).set({ left, top, rx: width / 2, ry: height / 2, }); break; case DrawingTool.Line: (this.currentShape as Line).set({ x2: x, y2: y, }); break; case DrawingTool.Arrow: (this.currentShape as Arrow).set({ x2: x, y2: y, }); break; default: break; } this.currentShape.setCoords(); this.canvas.renderAll(); } private onMouseUp() { if (this.isNeedPushToStack()) { this.emit('push-canvas-to-stack', null); } this.isDrawing = false; this.currentShape = null; this.isValidEraser = false; this.isValidSelection = false; } private onObjectMoving(event: IEvent) { this.canvas.renderAll(); this.isValidSelection = true; } private onObjectScaling(event: IEvent) { this.canvas.renderAll(); this.isValidSelection = true; } private onObjectRotating(event: IEvent) { this.canvas.renderAll(); this.isValidSelection = true; } private isNeedPushToStack() { if ( this.drawingTool === DrawingTool.Text || (this.isValidEraser === false && this.drawingTool === DrawingTool.Eraser) || (this.isValidSelection === false && this.drawingTool === DrawingTool.Pointer) || (this.isValidSelection === false && this.drawingTool === DrawingTool.Laser) ) { return false; } if (this.startX === this.endX && this.startY === this.endY) { if (this.currentShape !== null) { this.canvas.remove(this.currentShape); return false; } } return true; } public toDataURL(options?: ICanvasOptions) { return this.canvas.toDataURL(options); } public toJSON() { return this.canvas.toJSON(); } public loadFromJSON(json: any, callback: any, reviver?: any): ICanvas { return this.canvas.loadFromJSON(json, callback, reviver); } public renderAll(): ICanvas { return this.canvas.renderAll(); } public requestRenderAll() { return this.canvas.requestRenderAll(); } public zoom(ratio = 1) { const point = new fabric.Point( (this.canvas.width as number) / 2, (this.canvas.height as number) / 2 ); this.canvas.zoomToPoint(point, ratio); } public getZoom(): number { return this.canvas.getZoom(); } public zoomIn() { this.zoom(this.canvas.getZoom() * 1.1); } public zoomOut() { this.zoom(this.canvas.getZoom() / 1.1); } public destroy() { this.removeAllListeners(); this.canvas.dispose(); } } export default FabricCanvas;