From 8910c7cf0a5d74f6ec46615bbc106773b3147cdc Mon Sep 17 00:00:00 2001 From: Hufe921 Date: Tue, 28 Feb 2023 15:58:42 +0800 Subject: [PATCH] improve:position compute separate from draw row --- src/editor/core/command/CommandAdapt.ts | 6 +- src/editor/core/draw/Draw.ts | 205 +++++++++----------- src/editor/core/event/CanvasEvent.ts | 2 +- src/editor/core/event/handlers/click.ts | 4 +- src/editor/core/event/handlers/keydown.ts | 6 +- src/editor/core/event/handlers/mousedown.ts | 2 +- src/editor/core/event/handlers/mousemove.ts | 2 +- src/editor/core/position/Position.ts | 105 +++++++++- src/editor/interface/Draw.ts | 10 +- src/editor/interface/Position.ts | 17 ++ src/editor/interface/Row.ts | 1 + src/editor/utils/index.ts | 6 + 12 files changed, 235 insertions(+), 131 deletions(-) diff --git a/src/editor/core/command/CommandAdapt.ts b/src/editor/core/command/CommandAdapt.ts index 5bf7aab..5972543 100644 --- a/src/editor/core/command/CommandAdapt.ts +++ b/src/editor/core/command/CommandAdapt.ts @@ -118,7 +118,7 @@ export class CommandAdapt { const isCollapsed = startIndex === endIndex this.draw.render({ curIndex: isCollapsed ? startIndex : undefined, - isComputeRowList: false, + isCompute: false, isSubmitHistory: false, isSetCursor: isCollapsed }) @@ -1092,7 +1092,7 @@ export class CommandAdapt { const { endIndex } = this.range.getRange() this.draw.render({ curIndex: endIndex, - isComputeRowList: false + isCompute: false }) } @@ -1112,7 +1112,7 @@ export class CommandAdapt { const { endIndex } = this.range.getRange() this.draw.render({ curIndex: endIndex, - isComputeRowList: false + isCompute: false }) } diff --git a/src/editor/core/draw/Draw.ts b/src/editor/core/draw/Draw.ts index ab9160c..e7b2eb8 100644 --- a/src/editor/core/draw/Draw.ts +++ b/src/editor/core/draw/Draw.ts @@ -1,11 +1,11 @@ import { version } from '../../../../package.json' import { ZERO } from '../../dataset/constant/Common' import { RowFlex } from '../../dataset/enum/Row' -import { IDrawOption, IDrawRowPayload, IDrawRowResult, IPainterOptions } from '../../interface/Draw' +import { IDrawOption, IDrawRowPayload, IPainterOptions } from '../../interface/Draw' import { IEditorOption, IEditorResult } from '../../interface/Editor' import { IElement, IElementMetrics, IElementPosition, IElementFillRect, IElementStyle } from '../../interface/Element' import { IRow, IRowElement } from '../../interface/Row' -import { deepClone, getUUID } from '../../utils' +import { deepClone, getUUID, nextTick } from '../../utils' import { Cursor } from '../cursor/Cursor' import { CanvasEvent } from '../event/CanvasEvent' import { GlobalEvent } from '../event/GlobalEvent' @@ -98,6 +98,7 @@ export class Draw { private selectionObserver: SelectionObserver private rowList: IRow[] + private pageRowList: IRow[][] private painterStyle: IElementStyle | null private painterOptions: IPainterOptions | null private visiblePageNoList: number[] @@ -163,6 +164,7 @@ export class Draw { this.workerManager = new WorkerManager(this) this.rowList = [] + this.pageRowList = [] this.painterStyle = null this.painterOptions = null this.visiblePageNoList = [] @@ -290,6 +292,14 @@ export class Draw { return this.pageList } + public getRowList(): IRow[] { + return this.rowList + } + + public getPageRowList(): IRow[][] { + return this.pageRowList + } + public getCtx(): CanvasRenderingContext2D { return this.ctxList[this.pageNo] } @@ -596,7 +606,6 @@ export class Draw { private _computeRowList(innerWidth: number, elementList: IElement[]) { const { defaultSize, defaultRowMargin, scale, tdPadding, defaultTabWidth } = this.options const defaultBasicRowMarginHeight = this.getDefaultBasicRowMarginHeight() - const tdGap = tdPadding * 2 const canvas = document.createElement('canvas') const ctx = canvas.getContext('2d') as CanvasRenderingContext2D const rowList: IRow[] = [] @@ -606,6 +615,7 @@ export class Draw { height: 0, ascent: 0, elementList: [], + startIndex: 0, rowFlex: elementList?.[1]?.rowFlex }) } @@ -639,6 +649,7 @@ export class Draw { } metrics.boundingBoxAscent = 0 } else if (element.type === ElementType.TABLE) { + const tdGap = tdPadding * 2 // 计算表格行列 this.tableParticle.computeRowColInfo(element) // 计算表格内元素信息 @@ -817,6 +828,7 @@ export class Draw { rowList.push({ width: metrics.width, height, + startIndex: i, elementList: [rowElement], ascent, rowFlex: elementList[i + 1]?.rowFlex, @@ -838,6 +850,45 @@ export class Draw { return rowList } + private _computePageList(): IRow[][] { + const pageRowList: IRow[][] = [[]] + const { pageMode } = this.options + const height = this.getHeight() + const margins = this.getMargins() + const marginHeight = margins[0] + margins[2] + let pageHeight = marginHeight + let pageNo = 0 + if (pageMode === PageMode.CONTINUITY) { + pageRowList[0] = this.rowList + // 重置高度 + pageHeight += this.rowList.reduce((pre, cur) => pre + cur.height, 0) + const dpr = window.devicePixelRatio + const pageDom = this.pageList[0] + const pageDomHeight = Number(pageDom.style.height.replace('px', '')) + if (pageHeight > pageDomHeight) { + pageDom.style.height = `${pageHeight}px` + pageDom.height = pageHeight * dpr + } else { + const reduceHeight = pageHeight < height ? height : pageHeight + pageDom.style.height = `${reduceHeight}px` + pageDom.height = reduceHeight * dpr + } + } else { + for (let i = 0; i < this.rowList.length; i++) { + const row = this.rowList[i] + if (row.height + pageHeight > height || this.rowList[i - 1]?.isPageBreak) { + pageHeight = marginHeight + row.height + pageRowList.push([row]) + pageNo++ + } else { + pageHeight += row.height + pageRowList[pageNo].push(row) + } + } + } + return pageRowList + } + private _drawRichText(ctx: CanvasRenderingContext2D) { this.underline.render(ctx) this.strikeout.render(ctx) @@ -845,25 +896,13 @@ export class Draw { this.textParticle.complete() } - private _drawRow(ctx: CanvasRenderingContext2D, payload: IDrawRowPayload): IDrawRowResult { - const { positionList, rowList, pageNo, startX, startY, startIndex, innerWidth } = payload + private _drawRow(ctx: CanvasRenderingContext2D, payload: IDrawRowPayload) { + const { rowList, pageNo, positionList, startIndex } = payload const { scale, tdPadding } = this.options const { isCrossRowCol, tableId } = this.range.getRange() - const tdGap = tdPadding * 2 - let x = startX - let y = startY let index = startIndex for (let i = 0; i < rowList.length; i++) { const curRow = rowList[i] - // 计算行偏移量(行居中、居右) - if (curRow.rowFlex === RowFlex.CENTER) { - x += (innerWidth - curRow.width) / 2 - } else if (curRow.rowFlex === RowFlex.RIGHT) { - x += innerWidth - curRow.width - } - // 当前td所在位置 - const tablePreX = x - const tablePreY = y // 选区绘制记录 const rangeRecord: IElementFillRect = { x: 0, @@ -875,28 +914,13 @@ export class Draw { for (let j = 0; j < curRow.elementList.length; j++) { const element = curRow.elementList[j] const metrics = element.metrics - const offsetY = - (element.imgDisplay !== ImageDisplay.INLINE && element.type === ElementType.IMAGE) - || element.type === ElementType.LATEX - ? curRow.ascent - metrics.height - : curRow.ascent - const positionItem: IElementPosition = { - pageNo, - index, - value: element.value, - rowNo: i, - metrics, + // 当前元素位置信息 + const { ascent: offsetY, - lineHeight: curRow.height, - isLastLetter: j === curRow.elementList.length - 1, coordinate: { - leftTop: [x, y], - leftBottom: [x, y + curRow.height], - rightTop: [x + metrics.width, y], - rightBottom: [x + metrics.width, y + curRow.height] + leftTop: [x, y] } - } - positionList.push(positionItem) + } = positionList[curRow.startIndex + j] // 元素绘制 if (element.type === ElementType.IMAGE) { this._drawRichText(ctx) @@ -1001,30 +1025,22 @@ export class Draw { } } index++ - x += metrics.width // 绘制表格内元素 if (element.type === ElementType.TABLE) { + const tdGap = tdPadding * 2 for (let t = 0; t < element.trList!.length; t++) { const tr = element.trList![t] for (let d = 0; d < tr.tdList!.length; d++) { const td = tr.tdList[d] - td.positionList = [] - const drawRowResult = this._drawRow(ctx, { - positionList: td.positionList, + this._drawRow(ctx, { + positionList: td.positionList!, rowList: td.rowList!, pageNo, startIndex: 0, - startX: (td.x! + tdPadding) * scale + tablePreX, - startY: td.y! * scale + tablePreY, innerWidth: (td.width! - tdGap) * scale }) - x = drawRowResult.x - y = drawRowResult.y } } - // 恢复初始x、y - x = tablePreX - y = tablePreY } } // 绘制富文本及文字 @@ -1035,12 +1051,10 @@ export class Draw { this.range.render(ctx, x, y, width, height) } if (isCrossRowCol && tableRangeElement && tableRangeElement.id === tableId) { + const { coordinate: { leftTop: [x, y] } } = positionList[curRow.startIndex] this.tableParticle.drawRange(ctx, tableRangeElement, x, y) } - x = startX - y += curRow.height } - return { x, y, index } } private _clearPage(pageNo: number) { @@ -1052,31 +1066,22 @@ export class Draw { private _drawPage(positionList: IElementPosition[], rowList: IRow[], pageNo: number) { const { pageMode } = this.options - const margins = this.getMargins() const innerWidth = this.getInnerWidth() const ctx = this.ctxList[pageNo] this._clearPage(pageNo) // 绘制背景 this.background.render(ctx) // 绘制页边距 - const leftTopPoint: [number, number] = [margins[3], margins[0]] this.margin.render(ctx) // 渲染元素 - let x = leftTopPoint[0] - let y = leftTopPoint[1] - let index = positionList.length - const drawRowResult = this._drawRow(ctx, { + const index = rowList[0].startIndex + this._drawRow(ctx, { positionList, rowList, pageNo, startIndex: index, - startX: x, - startY: y, innerWidth }) - x = drawRowResult.x - y = drawRowResult.y - index = drawRowResult.index // 绘制页眉 this.header.render(ctx) // 绘制页码 @@ -1092,72 +1097,54 @@ export class Draw { } public render(payload?: IDrawOption) { - const { pageMode } = this.options const { isSubmitHistory = true, isSetCursor = true, - isComputeRowList = true + isCompute = true } = payload || {} let { curIndex } = payload || {} - const height = this.getHeight() const innerWidth = this.getInnerWidth() - // 计算行信息 - if (isComputeRowList) { + // 计算文档信息 + if (isCompute) { + // 行信息 this.rowList = this._computeRowList(innerWidth, this.elementList) + // 页面信息 + this.pageRowList = this._computePageList() + // 位置信息 + this.position.computePositionList() + // 搜索信息 const searchKeyword = this.search.getSearchKeyword() if (searchKeyword) { this.search.compute(searchKeyword) } } - // 清除光标等副作用 + // 清除光标 this.cursor.recoveryCursor() - this.position.setPositionList([]) - const positionList = this.position.getOriginalPositionList() - // 按页渲染 - const margins = this.getMargins() - const marginHeight = margins[0] + margins[2] - let pageHeight = marginHeight - let pageNo = 0 - const pageRowList: IRow[][] = [[]] - if (pageMode === PageMode.CONTINUITY) { - pageRowList[0] = this.rowList - // 重置高度 - pageHeight += this.rowList.reduce((pre, cur) => pre + cur.height, 0) - const dpr = window.devicePixelRatio - const pageDom = this.pageList[0] - const pageDomHeight = Number(pageDom.style.height.replace('px', '')) - if (pageHeight > pageDomHeight) { - pageDom.style.height = `${pageHeight}px` - pageDom.height = pageHeight * dpr - } else { - const reduceHeight = pageHeight < height ? height : pageHeight - pageDom.style.height = `${reduceHeight}px` - pageDom.height = reduceHeight * dpr - } - } else { - for (let i = 0; i < this.rowList.length; i++) { - const row = this.rowList[i] - if (row.height + pageHeight > height || this.rowList[i - 1]?.isPageBreak) { - pageHeight = marginHeight + row.height - pageRowList.push([row]) - pageNo++ - } else { - pageHeight += row.height - pageRowList[pageNo].push(row) - } - } - } // 绘制元素 - for (let i = 0; i < pageRowList.length; i++) { + const positionList = this.position.getOriginalPositionList() + const { visiblePageNoList } = this.scrollObserver.getPageVisibleInfo() + const minPageNo = visiblePageNoList[0] + const maxPageNo = visiblePageNoList[visiblePageNoList.length - 1] + for (let i = 0; i < this.pageRowList.length; i++) { if (!this.pageList[i]) { this._createPage(i) } - const rowList = pageRowList[i] - this._drawPage(positionList, rowList, i) + // 优先绘制可见页 + if (i >= minPageNo && i <= maxPageNo) { + this._drawPage(positionList, this.pageRowList[i], i) + } } + // 渲染后续不可见页 + nextTick(() => { + for (let i = 0; i < this.pageRowList.length; i++) { + if (i < minPageNo || i > maxPageNo) { + this._drawPage(positionList, this.pageRowList[i], i) + } + } + }) // 移除多余页 - setTimeout(() => { - const curPageCount = pageRowList.length + nextTick(() => { + const curPageCount = this.pageRowList.length const prePageCount = this.pageList.length if (prePageCount > curPageCount) { const deleteCount = prePageCount - curPageCount @@ -1205,7 +1192,7 @@ export class Draw { setTimeout(() => { // 页面尺寸改变 if (this.listener.pageSizeChange) { - this.listener.pageSizeChange(pageRowList.length) + this.listener.pageSizeChange(this.pageRowList.length) } // 文档内容改变 if (this.listener.contentChange && isSubmitHistory) { diff --git a/src/editor/core/event/CanvasEvent.ts b/src/editor/core/event/CanvasEvent.ts index dec3272..7f4da3d 100644 --- a/src/editor/core/event/CanvasEvent.ts +++ b/src/editor/core/event/CanvasEvent.ts @@ -123,7 +123,7 @@ export class CanvasEvent { this.draw.render({ isSubmitHistory: false, isSetCursor: false, - isComputeRowList: false + isCompute: false }) } diff --git a/src/editor/core/event/handlers/click.ts b/src/editor/core/event/handlers/click.ts index ee1387b..5aa61bd 100644 --- a/src/editor/core/event/handlers/click.ts +++ b/src/editor/core/event/handlers/click.ts @@ -44,7 +44,7 @@ function dblclick(host: CanvasEvent) { draw.render({ isSubmitHistory: false, isSetCursor: false, - isComputeRowList: false + isCompute: false }) } @@ -87,7 +87,7 @@ function threeClick(host: CanvasEvent) { draw.render({ isSubmitHistory: false, isSetCursor: false, - isComputeRowList: false + isCompute: false }) } diff --git a/src/editor/core/event/handlers/keydown.ts b/src/editor/core/event/handlers/keydown.ts index b09c4d9..17cc8c7 100644 --- a/src/editor/core/event/handlers/keydown.ts +++ b/src/editor/core/event/handlers/keydown.ts @@ -116,7 +116,7 @@ export function keydown(evt: KeyboardEvent, host: CanvasEvent) { curIndex: isCollapsed ? anchorStartIndex : undefined, isSetCursor: isCollapsed, isSubmitHistory: false, - isComputeRowList: false + isCompute: false }) evt.preventDefault() } @@ -150,7 +150,7 @@ export function keydown(evt: KeyboardEvent, host: CanvasEvent) { curIndex: isCollapsed ? anchorStartIndex : undefined, isSetCursor: isCollapsed, isSubmitHistory: false, - isComputeRowList: false + isCompute: false }) evt.preventDefault() } @@ -229,7 +229,7 @@ export function keydown(evt: KeyboardEvent, host: CanvasEvent) { curIndex: isCollapsed ? anchorStartIndex : undefined, isSetCursor: isCollapsed, isSubmitHistory: false, - isComputeRowList: false + isCompute: false }) } } else if (isMod(evt) && evt.key === KeyMap.Z) { diff --git a/src/editor/core/event/handlers/mousedown.ts b/src/editor/core/event/handlers/mousedown.ts index bd9ba12..7a476bf 100644 --- a/src/editor/core/event/handlers/mousedown.ts +++ b/src/editor/core/event/handlers/mousedown.ts @@ -79,7 +79,7 @@ export function mousedown(evt: MouseEvent, host: CanvasEvent) { curIndex, isSubmitHistory: isSetCheckbox, isSetCursor: !isDirectHitImage && !isDirectHitCheckbox, - isComputeRowList: false + isCompute: false }) } // 预览工具组件 diff --git a/src/editor/core/event/handlers/mousemove.ts b/src/editor/core/event/handlers/mousemove.ts index bdde7a6..3286e48 100644 --- a/src/editor/core/event/handlers/mousemove.ts +++ b/src/editor/core/event/handlers/mousemove.ts @@ -73,6 +73,6 @@ export function mousemove(evt: MouseEvent, host: CanvasEvent) { draw.render({ isSubmitHistory: false, isSetCursor: false, - isComputeRowList: false + isCompute: false }) } \ No newline at end of file diff --git a/src/editor/core/position/Position.ts b/src/editor/core/position/Position.ts index 3128088..2e398d5 100644 --- a/src/editor/core/position/Position.ts +++ b/src/editor/core/position/Position.ts @@ -1,6 +1,7 @@ -import { ElementType } from '../..' +import { ElementType, RowFlex } from '../..' import { ZERO } from '../../dataset/constant/Common' -import { ControlComponent } from '../../dataset/enum/Control' +import { ControlComponent, ImageDisplay } from '../../dataset/enum/Control' +import { IComputePageRowPositionPayload, IComputePageRowPositionResult } from '../../interface/Position' import { IEditorOption } from '../../interface/Editor' import { IElementPosition } from '../../interface/Element' import { ICurrentPosition, IGetPositionByXYPayload, IPositionContext } from '../../interface/Position' @@ -45,6 +46,106 @@ export class Position { this.positionList = payload } + private computePageRowPosition(payload: IComputePageRowPositionPayload): IComputePageRowPositionResult { + const { positionList, rowList, pageNo, startX, startY, startIndex, innerWidth } = payload + const { scale, tdPadding } = this.options + let x = startX + let y = startY + let index = startIndex + for (let i = 0; i < rowList.length; i++) { + const curRow = rowList[i] + // 计算行偏移量(行居中、居右) + if (curRow.rowFlex === RowFlex.CENTER) { + x += (innerWidth - curRow.width) / 2 + } else if (curRow.rowFlex === RowFlex.RIGHT) { + x += innerWidth - curRow.width + } + // 当前td所在位置 + const tablePreX = x + const tablePreY = y + for (let j = 0; j < curRow.elementList.length; j++) { + const element = curRow.elementList[j] + const metrics = element.metrics + const offsetY = + (element.imgDisplay !== ImageDisplay.INLINE && element.type === ElementType.IMAGE) + || element.type === ElementType.LATEX + ? curRow.ascent - metrics.height + : curRow.ascent + const positionItem: IElementPosition = { + pageNo, + index, + value: element.value, + rowNo: i, + metrics, + ascent: offsetY, + lineHeight: curRow.height, + isLastLetter: j === curRow.elementList.length - 1, + coordinate: { + leftTop: [x, y], + leftBottom: [x, y + curRow.height], + rightTop: [x + metrics.width, y], + rightBottom: [x + metrics.width, y + curRow.height] + } + } + positionList.push(positionItem) + index++ + x += metrics.width + // 绘制表格内元素 + if (element.type === ElementType.TABLE) { + const tdGap = tdPadding * 2 + for (let t = 0; t < element.trList!.length; t++) { + const tr = element.trList![t] + for (let d = 0; d < tr.tdList!.length; d++) { + const td = tr.tdList[d] + td.positionList = [] + const drawRowResult = this.computePageRowPosition({ + positionList: td.positionList, + rowList: td.rowList!, + pageNo, + startIndex: 0, + startX: (td.x! + tdPadding) * scale + tablePreX, + startY: td.y! * scale + tablePreY, + innerWidth: (td.width! - tdGap) * scale + }) + x = drawRowResult.x + y = drawRowResult.y + } + } + // 恢复初始x、y + x = tablePreX + y = tablePreY + } + } + x = startX + y += curRow.height + } + return { x, y, index } + } + + public computePositionList() { + // 置空原位置信息 + this.positionList = [] + // 按每页行计算 + const innerWidth = this.draw.getInnerWidth() + const pageRowList = this.draw.getPageRowList() + const margins = this.draw.getMargins() + const startX = margins[3] + const startY = margins[0] + for (let i = 0; i < pageRowList.length; i++) { + const rowList = pageRowList[i] + const startIndex = rowList[0].startIndex + this.computePageRowPosition({ + positionList: this.positionList, + rowList, + pageNo: i, + startIndex, + startX, + startY, + innerWidth + }) + } + } + public setCursorPosition(position: IElementPosition | null) { this.cursorPosition = position } diff --git a/src/editor/interface/Draw.ts b/src/editor/interface/Draw.ts index 082ecc3..45114e5 100644 --- a/src/editor/interface/Draw.ts +++ b/src/editor/interface/Draw.ts @@ -5,7 +5,7 @@ export interface IDrawOption { curIndex?: number; isSetCursor?: boolean; isSubmitHistory?: boolean; - isComputeRowList?: boolean; + isCompute?: boolean; } export interface IDrawImagePayload { @@ -19,17 +19,9 @@ export interface IDrawRowPayload { rowList: IRow[]; pageNo: number; startIndex: number; - startX: number; - startY: number; innerWidth: number; } -export interface IDrawRowResult { - x: number; - y: number; - index: number; -} - export interface IPainterOptions { isDblclick: boolean; } \ No newline at end of file diff --git a/src/editor/interface/Position.ts b/src/editor/interface/Position.ts index 5b94abe..9170383 100644 --- a/src/editor/interface/Position.ts +++ b/src/editor/interface/Position.ts @@ -1,5 +1,6 @@ import { IElement } from '..' import { IElementPosition } from './Element' +import { IRow } from './Row' import { ITd } from './table/Td' export interface ICurrentPosition { @@ -37,4 +38,20 @@ export interface IPositionContext { tdId?: string; trId?: string; tableId?: string; +} + +export interface IComputePageRowPositionPayload { + positionList: IElementPosition[]; + rowList: IRow[]; + pageNo: number; + startIndex: number; + startX: number; + startY: number; + innerWidth: number; +} + +export interface IComputePageRowPositionResult { + x: number; + y: number; + index: number; } \ No newline at end of file diff --git a/src/editor/interface/Row.ts b/src/editor/interface/Row.ts index c2ea7c6..b196caf 100644 --- a/src/editor/interface/Row.ts +++ b/src/editor/interface/Row.ts @@ -11,6 +11,7 @@ export interface IRow { height: number; ascent: number; rowFlex?: RowFlex; + startIndex: number; isPageBreak?: boolean; elementList: IRowElement[]; } diff --git a/src/editor/utils/index.ts b/src/editor/utils/index.ts index a6383ab..a5510b2 100644 --- a/src/editor/utils/index.ts +++ b/src/editor/utils/index.ts @@ -111,4 +111,10 @@ export function mergeObject(source: T, target: T): T { target.push(...source) } return target +} + +export function nextTick(fn: Function) { + setTimeout(() => { + fn() + }, 0) } \ No newline at end of file