diff --git a/cypress/integration/menus/print.spec.ts b/cypress/integration/menus/print.spec.ts index 4d8923d..110d178 100644 --- a/cypress/integration/menus/print.spec.ts +++ b/cypress/integration/menus/print.spec.ts @@ -10,16 +10,16 @@ describe('菜单-打印', () => { it('打印', () => { - cy.getEditor().then((editor: Editor) => { - const imageList2 = editor.command.getImage() + cy.getEditor().then(async (editor: Editor) => { + const imageList2 = await editor.command.getImage() expect(imageList2.length).to.eq(2) editor.command.executeSelectAll() editor.command.executeBackspace() - cy.wait(200).then(() => { - const imageList1 = editor.command.getImage() + cy.wait(200).then(async () => { + const imageList1 = await editor.command.getImage() expect(imageList1.length).to.eq(1) }) }) diff --git a/src/editor/core/command/Command.ts b/src/editor/core/command/Command.ts index 9187e7a..a35f5e6 100644 --- a/src/editor/core/command/Command.ts +++ b/src/editor/core/command/Command.ts @@ -381,7 +381,7 @@ export class Command { return Command.changeImageDisplay(element, display) } - public getImage(): string[] { + public getImage(): Promise { return Command.getImage() } diff --git a/src/editor/core/command/CommandAdapt.ts b/src/editor/core/command/CommandAdapt.ts index 5bf7aab..a596655 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 }) @@ -327,7 +327,10 @@ export class CommandAdapt { selection.forEach(el => { el.color = payload }) - this.draw.render({ isSetCursor: false }) + this.draw.render({ + isSetCursor: false, + isCompute: false + }) } public highlight(payload: string) { @@ -338,7 +341,10 @@ export class CommandAdapt { selection.forEach(el => { el.highlight = payload }) - this.draw.render({ isSetCursor: false }) + this.draw.render({ + isSetCursor: false, + isCompute: false + }) } public rowFlex(payload: RowFlex) { @@ -1092,7 +1098,7 @@ export class CommandAdapt { const { endIndex } = this.range.getRange() this.draw.render({ curIndex: endIndex, - isComputeRowList: false + isCompute: false }) } @@ -1112,7 +1118,7 @@ export class CommandAdapt { const { endIndex } = this.range.getRange() this.draw.render({ curIndex: endIndex, - isComputeRowList: false + isCompute: false }) } @@ -1173,7 +1179,8 @@ export class CommandAdapt { options.watermark.font = payload.font || font this.draw.render({ isSetCursor: false, - isSubmitHistory: false + isSubmitHistory: false, + isCompute: false }) } @@ -1185,7 +1192,8 @@ export class CommandAdapt { options.watermark = { ...defaultWatermarkOption } this.draw.render({ isSetCursor: false, - isSubmitHistory: false + isSubmitHistory: false, + isCompute: false }) } } @@ -1229,7 +1237,9 @@ export class CommandAdapt { if (index === null) return this.draw.render({ isSetCursor: false, - isSubmitHistory: false + isSubmitHistory: false, + isCompute: false, + isLazy: false }) } @@ -1238,7 +1248,9 @@ export class CommandAdapt { if (index === null) return this.draw.render({ isSetCursor: false, - isSubmitHistory: false + isSubmitHistory: false, + isCompute: false, + isLazy: false }) } @@ -1352,12 +1364,13 @@ export class CommandAdapt { }) } - public print() { + public async print() { const { width, height, scale } = this.options if (scale !== 1) { this.draw.setPageScale(1) } - printImageBase64(this.draw.getDataURL(), width, height) + const base64List = await this.draw.getDataURL() + printImageBase64(base64List, width, height) if (scale !== 1) { this.draw.setPageScale(scale) } @@ -1393,7 +1406,7 @@ export class CommandAdapt { }) } - public getImage(): string[] { + public getImage(): Promise { return this.draw.getDataURL() } diff --git a/src/editor/core/draw/Draw.ts b/src/editor/core/draw/Draw.ts index 377db26..71c7814 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' @@ -49,6 +49,7 @@ import { IMargin } from '../../interface/Margin' import { BlockParticle } from './particle/block/BlockParticle' import { EDITOR_COMPONENT, EDITOR_PREFIX } from '../../dataset/constant/Editor' import { I18n } from '../i18n/I18n' +import { ImageObserver } from '../observer/ImageObserver' export class Draw { @@ -96,12 +97,15 @@ export class Draw { private workerManager: WorkerManager private scrollObserver: ScrollObserver private selectionObserver: SelectionObserver + private imageObserver: ImageObserver private rowList: IRow[] + private pageRowList: IRow[][] private painterStyle: IElementStyle | null private painterOptions: IPainterOptions | null private visiblePageNoList: number[] private intersectionPageNo: number + private lazyRenderIntersectionObserver: IntersectionObserver | null constructor( rootContainer: HTMLElement, @@ -153,6 +157,7 @@ export class Draw { this.scrollObserver = new ScrollObserver(this) this.selectionObserver = new SelectionObserver() + this.imageObserver = new ImageObserver() this.canvasEvent = new CanvasEvent(this) this.cursor = new Cursor(this, this.canvasEvent) @@ -163,10 +168,12 @@ export class Draw { this.workerManager = new WorkerManager(this) this.rowList = [] + this.pageRowList = [] this.painterStyle = null this.painterOptions = null this.visiblePageNoList = [] this.intersectionPageNo = 0 + this.lazyRenderIntersectionObserver = null this.render({ isSetCursor: false }) } @@ -290,6 +297,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] } @@ -405,6 +420,10 @@ export class Draw { return this.workerManager } + public getImageObserver(): ImageObserver { + return this.imageObserver + } + public getI18n(): I18n { return this.i18n } @@ -413,7 +432,14 @@ export class Draw { return this.rowList.length } - public getDataURL(): string[] { + public async getDataURL(): Promise { + this.render({ + isLazy: false, + isCompute: false, + isSetCursor: false, + isSubmitHistory: false + }) + await this.imageObserver.allSettled() return this.pageList.map(c => c.toDataURL()) } @@ -599,7 +625,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[] = [] @@ -609,6 +634,7 @@ export class Draw { height: 0, ascent: 0, elementList: [], + startIndex: 0, rowFlex: elementList?.[1]?.rowFlex }) } @@ -642,6 +668,7 @@ export class Draw { } metrics.boundingBoxAscent = 0 } else if (element.type === ElementType.TABLE) { + const tdGap = tdPadding * 2 // 计算表格行列 this.tableParticle.computeRowColInfo(element) // 计算表格内元素信息 @@ -820,6 +847,7 @@ export class Draw { rowList.push({ width: metrics.width, height, + startIndex: i, elementList: [rowElement], ascent, rowFlex: elementList[i + 1]?.rowFlex, @@ -841,6 +869,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) @@ -848,25 +915,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, @@ -878,28 +933,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) @@ -1004,30 +1044,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 } } // 绘制富文本及文字 @@ -1038,12 +1070,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) { @@ -1055,31 +1085,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) // 绘制页码 @@ -1094,73 +1115,71 @@ export class Draw { } } + private _lazyRender() { + const positionList = this.position.getOriginalPositionList() + this.lazyRenderIntersectionObserver?.disconnect() + this.lazyRenderIntersectionObserver = new IntersectionObserver((entries) => { + entries.forEach(entry => { + if (entry.isIntersecting) { + const index = Number((entry.target).dataset.index) + this._drawPage(positionList, this.pageRowList[index], index) + } + }) + }) + this.pageList.forEach(el => { + this.lazyRenderIntersectionObserver!.observe(el) + }) + } + + private _immediateRender() { + const positionList = this.position.getOriginalPositionList() + for (let i = 0; i < this.pageRowList.length; i++) { + this._drawPage(positionList, this.pageRowList[i], i) + } + } + public render(payload?: IDrawOption) { - const { pageMode } = this.options const { isSubmitHistory = true, isSetCursor = true, - isComputeRowList = true + isCompute = true, + isLazy = 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.imageObserver.clearAll() 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++) { + 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 (isLazy) { + this._lazyRender() + } else { + this._immediateRender() } // 移除多余页 - setTimeout(() => { - const curPageCount = pageRowList.length + nextTick(() => { + const curPageCount = this.pageRowList.length const prePageCount = this.pageList.length if (prePageCount > curPageCount) { const deleteCount = prePageCount - curPageCount @@ -1203,12 +1222,11 @@ export class Draw { self.render({ curIndex, isSubmitHistory: false }) }) } - // 信息变动回调 - setTimeout(() => { + nextTick(() => { // 页面尺寸改变 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/draw/interactive/Search.ts b/src/editor/core/draw/interactive/Search.ts index b4548a2..349c388 100644 --- a/src/editor/core/draw/interactive/Search.ts +++ b/src/editor/core/draw/interactive/Search.ts @@ -261,7 +261,11 @@ export class Search { const searchMatchIndexList = this.getSearchNavigateIndexList() if (searchMatchIndexList.includes(s)) { ctx.fillStyle = searchNavigateMatchColor - this.searchNavigateScrollIntoView(position) + // 是否是第一个字符,则移动到可视范围 + const preSearchMatch = this.searchMatchList[s - 1] + if (!preSearchMatch || preSearchMatch.groupId !== searchMatch.groupId) { + this.searchNavigateScrollIntoView(position) + } } else { ctx.fillStyle = searchMatchColor } diff --git a/src/editor/core/draw/particle/ImageParticle.ts b/src/editor/core/draw/particle/ImageParticle.ts index 9cca801..0268528 100644 --- a/src/editor/core/draw/particle/ImageParticle.ts +++ b/src/editor/core/draw/particle/ImageParticle.ts @@ -4,14 +4,20 @@ import { Draw } from '../Draw' export class ImageParticle { + private draw: Draw protected options: Required protected imageCache: Map constructor(draw: Draw) { + this.draw = draw this.options = draw.getOptions() this.imageCache = new Map() } + protected addImageObserver(promise: Promise) { + this.draw.getImageObserver().add(promise) + } + public render(ctx: CanvasRenderingContext2D, element: IElement, x: number, y: number) { const { scale } = this.options const width = element.width! * scale @@ -20,12 +26,19 @@ export class ImageParticle { const img = this.imageCache.get(element.id!)! ctx.drawImage(img, x, y, width, height) } else { - const img = new Image() - img.src = element.value - img.onload = () => { - ctx.drawImage(img, x, y, width, height) - this.imageCache.set(element.id!, img) - } + const imageLoadPromise = new Promise((resolve, reject) => { + const img = new Image() + img.src = element.value + img.onload = () => { + ctx.drawImage(img, x, y, width, height) + this.imageCache.set(element.id!, img) + resolve(element) + } + img.onerror = (error) => { + reject(error) + } + }) + this.addImageObserver(imageLoadPromise) } } diff --git a/src/editor/core/draw/particle/latex/LaTexParticle.ts b/src/editor/core/draw/particle/latex/LaTexParticle.ts index f35a490..36cf9d9 100644 --- a/src/editor/core/draw/particle/latex/LaTexParticle.ts +++ b/src/editor/core/draw/particle/latex/LaTexParticle.ts @@ -21,12 +21,19 @@ export class LaTexParticle extends ImageParticle { const img = this.imageCache.get(element.value)! ctx.drawImage(img, x, y, width, height) } else { - const img = new Image() - img.src = element.laTexSVG! - img.onload = () => { - ctx.drawImage(img, x, y, width, height) - this.imageCache.set(element.value, img) - } + const laTexLoadPromise = new Promise((resolve, reject) => { + const img = new Image() + img.src = element.laTexSVG! + img.onload = () => { + ctx.drawImage(img, x, y, width, height) + this.imageCache.set(element.value, img) + resolve(element) + } + img.onerror = (error) => { + reject(error) + } + }) + this.addImageObserver(laTexLoadPromise) } } 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/observer/ImageObserver.ts b/src/editor/core/observer/ImageObserver.ts new file mode 100644 index 0000000..589ff09 --- /dev/null +++ b/src/editor/core/observer/ImageObserver.ts @@ -0,0 +1,21 @@ +export class ImageObserver { + + private promiseList: Promise[] + + constructor() { + this.promiseList = [] + } + + public add(payload: Promise) { + this.promiseList.push(payload) + } + + public clearAll() { + this.promiseList = [] + } + + public allSettled() { + return Promise.allSettled(this.promiseList) + } + +} \ 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..2642751 100644 --- a/src/editor/interface/Draw.ts +++ b/src/editor/interface/Draw.ts @@ -5,7 +5,8 @@ export interface IDrawOption { curIndex?: number; isSetCursor?: boolean; isSubmitHistory?: boolean; - isComputeRowList?: boolean; + isCompute?: boolean; + isLazy?: boolean; } export interface IDrawImagePayload { @@ -19,17 +20,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