From c7b88327898f95932abd4369ac726e35a51528dc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=BB=84=E4=BA=91=E9=A3=9E?= Date: Tue, 23 Nov 2021 21:45:01 +0800 Subject: [PATCH] =?UTF-8?q?feat:=E4=BC=98=E5=8C=96=E6=B8=B2=E6=9F=93?= =?UTF-8?q?=E6=80=A7=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/editor/core/draw/Draw.ts | 82 ++++++++++++------- src/editor/core/draw/particle/TextParticle.ts | 59 +++++++++++++ src/editor/core/event/CanvasEvent.ts | 44 ++++++++-- src/editor/index.ts | 9 +- src/editor/interface/Draw.ts | 1 + src/editor/interface/Row.ts | 3 +- 6 files changed, 155 insertions(+), 43 deletions(-) create mode 100644 src/editor/core/draw/particle/TextParticle.ts diff --git a/src/editor/core/draw/Draw.ts b/src/editor/core/draw/Draw.ts index d97096a..2c43d72 100644 --- a/src/editor/core/draw/Draw.ts +++ b/src/editor/core/draw/Draw.ts @@ -20,6 +20,7 @@ import { Strikeout } from "./richtext/Strikeout" import { Underline } from "./richtext/Underline" import { ElementType } from "../../dataset/enum/Element" import { ImageParticle } from "./particle/ImageParticle" +import { TextParticle } from "./particle/TextParticle" export class Draw { @@ -40,8 +41,9 @@ export class Draw { private highlight: Highlight private historyManager: HistoryManager private imageParticle: ImageParticle + private textParticle: TextParticle - private rowCount: number + private rowList: IRow[] private painterStyle: IElementStyle | null private searchMatchList: number[][] | null @@ -68,6 +70,7 @@ export class Draw { this.strikeout = new Strikeout(ctx, options) this.highlight = new Highlight(ctx, options) this.imageParticle = new ImageParticle(canvas, ctx, options, this) + this.textParticle = new TextParticle(ctx) const canvasEvent = new CanvasEvent(canvas, this) this.cursor = new Cursor(canvas, this, canvasEvent) @@ -75,7 +78,7 @@ export class Draw { const globalEvent = new GlobalEvent(canvas, this, canvasEvent) globalEvent.register() - this.rowCount = 0 + this.rowList = [] this.painterStyle = null this.searchMatchList = null @@ -115,7 +118,7 @@ export class Draw { } public getRowCount(): number { - return this.rowCount + return this.rowList.length } public getDataURL(): string { @@ -155,25 +158,13 @@ export class Draw { return `${el.italic ? 'italic ' : ''}${el.bold ? 'bold ' : ''}${el.size || defaultSize}px ${el.font || defaultFont}` } - public render(payload?: IDrawOption) { - let { curIndex, isSubmitHistory = true, isSetCursor = true } = payload || {} - // 清除光标等副作用 - this.cursor.recoveryCursor() - this.ctx.clearRect(0, 0, this.canvas.width, this.canvas.height) - this.position.setPositionList([]) - const positionList = this.position.getPositionList() - // 基础信息 + private computeRowList() { const { defaultSize } = this.options const canvasRect = this.canvas.getBoundingClientRect() - // 绘制背景 - this.background.render(canvasRect) - // 绘制页边距 const { width } = canvasRect const { margins, defaultRowMargin, defaultBasicRowMarginHeight } = this.options const leftTopPoint: [number, number] = [margins[3], margins[0]] const rightTopPoint: [number, number] = [width - margins[1], margins[0]] - this.margin.render(canvasRect) - // 计算行信息 const rowList: IRow[] = [] if (this.elementList.length) { rowList.push({ @@ -213,13 +204,17 @@ export class Draw { this.ctx.font = this.getFont(element) const fontMetrics = this.ctx.measureText(element.value) metrics.width = fontMetrics.width - metrics.boundingBoxAscent = i === 0 ? defaultSize : fontMetrics.actualBoundingBoxAscent + metrics.boundingBoxAscent = element.value === ZERO ? defaultSize : fontMetrics.actualBoundingBoxAscent metrics.boundingBoxDescent = fontMetrics.actualBoundingBoxDescent } const ascent = metrics.boundingBoxAscent + rowMargin const descent = metrics.boundingBoxDescent + rowMargin const height = ascent + descent - const rowElement: IRowElement = { ...element, metrics } + const rowElement: IRowElement = { + ...element, + metrics, + style: this.ctx.font + } // 超过限定宽度 if (curRow.width + metrics.width > innerWidth || (i !== 0 && element.value === ZERO)) { rowList.push({ @@ -243,12 +238,39 @@ export class Draw { } this.ctx.restore() } + this.rowList = rowList + } + + public render(payload?: IDrawOption) { + let { + curIndex, + isSubmitHistory = true, + isSetCursor = true, + isComputeRowList = true + } = payload || {} + // 清除光标等副作用 + this.cursor.recoveryCursor() + this.ctx.clearRect(0, 0, this.canvas.width, this.canvas.height) + this.position.setPositionList([]) + const positionList = this.position.getPositionList() + // 基础信息 + const canvasRect = this.canvas.getBoundingClientRect() + // 绘制背景 + this.background.render(canvasRect) + // 绘制页边距 + const { margins } = this.options + const leftTopPoint: [number, number] = [margins[3], margins[0]] + this.margin.render(canvasRect) + // 计算行信息 + if (isComputeRowList) { + this.computeRowList() + } // 渲染元素 let x = leftTopPoint[0] let y = leftTopPoint[1] let index = 0 - for (let i = 0; i < rowList.length; i++) { - const curRow = rowList[i] + for (let i = 0; i < this.rowList.length; i++) { + const curRow = this.rowList[i] // 计算行偏移量(行居左、居中、居右) if (curRow.rowFlex && curRow.rowFlex !== RowFlex.LEFT) { const canvasInnerWidth = this.canvas.width - margins[1] - margins[3] @@ -259,15 +281,8 @@ export class Draw { } } for (let j = 0; j < curRow.elementList.length; j++) { - this.ctx.save() const element = curRow.elementList[j] const metrics = element.metrics - if (!element.type || element.type === ElementType.TEXT) { - this.ctx.font = this.getFont(element) - if (element.color) { - this.ctx.fillStyle = element.color - } - } const offsetY = element.type === ElementType.IMAGE ? curRow.ascent - element.height! : curRow.ascent @@ -301,9 +316,10 @@ export class Draw { } // 元素绘制 if (element.type === ElementType.IMAGE) { + this.textParticle.complete() this.imageParticle.render(element, x, y + offsetY) } else { - this.ctx.fillText(element.value, x, y + offsetY) + this.textParticle.record(element, x, y + offsetY) } // 选区绘制 const { startIndex, endIndex } = this.range.getRange() @@ -312,11 +328,12 @@ export class Draw { } index++ x += metrics.width - this.ctx.restore() } + this.textParticle.complete() x = leftTopPoint[0] y += curRow.height } + // 搜索匹配绘制 if (this.searchMatchList) { this.search.render() @@ -336,9 +353,12 @@ export class Draw { const height = Math.ceil(leftBottom[1] + (leftBottom[1] - leftTop[1]) + margins[2]) this.canvas.height = height this.canvas.style.height = `${height}px` - this.render({ curIndex, isSubmitHistory: false }) + this.render({ + curIndex, + isSubmitHistory: false, + isComputeRowList: false + }) } - this.rowCount = rowList.length // 历史记录用于undo、redo if (isSubmitHistory) { const self = this diff --git a/src/editor/core/draw/particle/TextParticle.ts b/src/editor/core/draw/particle/TextParticle.ts new file mode 100644 index 0000000..9a96381 --- /dev/null +++ b/src/editor/core/draw/particle/TextParticle.ts @@ -0,0 +1,59 @@ +import { IRowElement } from "../../../interface/Row" + +export class TextParticle { + + private ctx: CanvasRenderingContext2D + private curX: number + private curY: number + private text: string + private curStyle: string + private curColor?: string + + constructor(ctx: CanvasRenderingContext2D) { + this.ctx = ctx + this.curX = -1 + this.curY = -1 + this.text = '' + this.curStyle = '' + } + + public complete() { + this.render() + this.text = '' + } + + public record(element: IRowElement, x: number, y: number) { + // 主动完成的重设起始点 + if (!this.text) { + this.setCurXY(x, y) + } + // 样式发生改变 + if ( + (this.curStyle && element.style !== this.curStyle) || + (element.color !== this.curColor) + ) { + this.complete() + this.setCurXY(x, y) + } + this.text += element.value + this.curStyle = element.style + this.curColor = element.color + } + + private setCurXY(x: number, y: number) { + this.curX = x + this.curY = y + } + + private render() { + if (!this.text || !~this.curX || !~this.curX) return + this.ctx.save() + this.ctx.font = this.curStyle + if (this.curColor) { + this.ctx.fillStyle = this.curColor + } + this.ctx.fillText(this.text, this.curX, this.curY) + this.ctx.restore() + } + +} \ No newline at end of file diff --git a/src/editor/core/event/CanvasEvent.ts b/src/editor/core/event/CanvasEvent.ts index fd6f5fb..edc8a58 100644 --- a/src/editor/core/event/CanvasEvent.ts +++ b/src/editor/core/event/CanvasEvent.ts @@ -82,7 +82,8 @@ export class CanvasEvent { // 绘制 this.draw.render({ isSubmitHistory: false, - isSetCursor: false + isSetCursor: false, + isComputeRowList: false }) } @@ -95,7 +96,12 @@ export class CanvasEvent { const isDirectHitImage = isDirectHit && isImage if (~index) { this.range.setRange(index, index) - this.draw.render({ curIndex: index, isSubmitHistory: false, isSetCursor: !isDirectHitImage }) + this.draw.render({ + curIndex: index, + isSubmitHistory: false, + isSetCursor: !isDirectHitImage, + isComputeRowList: false + }) } // 图片尺寸拖拽组件 this.imageParticle.clearResizer() @@ -151,13 +157,21 @@ export class CanvasEvent { if (index > 0) { const curIndex = index - 1 this.range.setRange(curIndex, curIndex) - this.draw.render({ curIndex, isSubmitHistory: false }) + this.draw.render({ + curIndex, + isSubmitHistory: false, + isComputeRowList: false + }) } } else if (evt.key === KeyMap.Right) { if (index < position.length - 1) { const curIndex = index + 1 this.range.setRange(curIndex, curIndex) - this.draw.render({ curIndex, isSubmitHistory: false }) + this.draw.render({ + curIndex, + isSubmitHistory: false, + isComputeRowList: false + }) } } else if (evt.key === KeyMap.Up || evt.key === KeyMap.Down) { const { rowNo, index, coordinate: { leftTop, rightTop } } = cursorPosition @@ -194,7 +208,11 @@ export class CanvasEvent { } const curIndex = maxIndex this.range.setRange(curIndex, curIndex) - this.draw.render({ curIndex, isSubmitHistory: false }) + this.draw.render({ + curIndex, + isSubmitHistory: false, + isComputeRowList: false + }) } } else if (evt.ctrlKey && evt.key === KeyMap.Z) { this.historyManager.undo() @@ -216,7 +234,11 @@ export class CanvasEvent { } } else if (evt.ctrlKey && evt.key === KeyMap.A) { this.range.setRange(0, position.length - 1) - this.draw.render({ isSubmitHistory: false, isSetCursor: false }) + this.draw.render({ + isSubmitHistory: false, + isSetCursor: false, + isComputeRowList: false + }) } } @@ -233,10 +255,16 @@ export class CanvasEvent { const inputData: IElement[] = data.split('').map(value => ({ value })) + let start = 0 if (isCollspace) { - elementList.splice(index + 1, 0, ...inputData) + start = index + 1 } else { - elementList.splice(startIndex + 1, endIndex - startIndex, ...inputData) + start = startIndex + 1 + elementList.splice(startIndex + 1, endIndex - startIndex) + } + // 禁止直接使用解构存在性能问题 + for (let i = 0; i < inputData.length; i++) { + elementList.splice(start + i, 0, inputData[i]) } const curIndex = (isCollspace ? index : startIndex) + inputData.length this.range.setRange(curIndex, curIndex) diff --git a/src/editor/index.ts b/src/editor/index.ts index 5760a70..545fdf1 100644 --- a/src/editor/index.ts +++ b/src/editor/index.ts @@ -47,12 +47,15 @@ export default class Editor { value: ZERO }) } - elementList.forEach(el => { + for (let i = 0; i < elementList.length; i++) { + const el = elementList[i] if (el.value === '\n') { el.value = ZERO } - el.id = getUUID() - }) + if (el.type === ElementType.IMAGE) { + el.id = getUUID() + } + } // 监听 this.listener = new Listener() // 启动 diff --git a/src/editor/interface/Draw.ts b/src/editor/interface/Draw.ts index 0dfd1cc..3e36f3f 100644 --- a/src/editor/interface/Draw.ts +++ b/src/editor/interface/Draw.ts @@ -2,6 +2,7 @@ export interface IDrawOption { curIndex?: number; isSetCursor?: boolean; isSubmitHistory?: boolean; + isComputeRowList?: boolean; } export interface IDrawImagePayload { diff --git a/src/editor/interface/Row.ts b/src/editor/interface/Row.ts index 30a5337..76fd70a 100644 --- a/src/editor/interface/Row.ts +++ b/src/editor/interface/Row.ts @@ -2,7 +2,8 @@ import { RowFlex } from "../dataset/enum/Row" import { IElement, IElementMetrics } from "./Element" export type IRowElement = IElement & { - metrics: IElementMetrics + metrics: IElementMetrics; + style: string; } export interface IRow {