Merge pull request #135 from Hufe921/feature/performance

Feature/performance
pr675
Hufe 3 years ago committed by GitHub
commit 33475a252b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -10,16 +10,16 @@ describe('菜单-打印', () => {
it('打印', () => { it('打印', () => {
cy.getEditor().then((editor: Editor) => { cy.getEditor().then(async (editor: Editor) => {
const imageList2 = editor.command.getImage() const imageList2 = await editor.command.getImage()
expect(imageList2.length).to.eq(2) expect(imageList2.length).to.eq(2)
editor.command.executeSelectAll() editor.command.executeSelectAll()
editor.command.executeBackspace() editor.command.executeBackspace()
cy.wait(200).then(() => { cy.wait(200).then(async () => {
const imageList1 = editor.command.getImage() const imageList1 = await editor.command.getImage()
expect(imageList1.length).to.eq(1) expect(imageList1.length).to.eq(1)
}) })
}) })

@ -381,7 +381,7 @@ export class Command {
return Command.changeImageDisplay(element, display) return Command.changeImageDisplay(element, display)
} }
public getImage(): string[] { public getImage(): Promise<string[]> {
return Command.getImage() return Command.getImage()
} }

@ -118,7 +118,7 @@ export class CommandAdapt {
const isCollapsed = startIndex === endIndex const isCollapsed = startIndex === endIndex
this.draw.render({ this.draw.render({
curIndex: isCollapsed ? startIndex : undefined, curIndex: isCollapsed ? startIndex : undefined,
isComputeRowList: false, isCompute: false,
isSubmitHistory: false, isSubmitHistory: false,
isSetCursor: isCollapsed isSetCursor: isCollapsed
}) })
@ -327,7 +327,10 @@ export class CommandAdapt {
selection.forEach(el => { selection.forEach(el => {
el.color = payload el.color = payload
}) })
this.draw.render({ isSetCursor: false }) this.draw.render({
isSetCursor: false,
isCompute: false
})
} }
public highlight(payload: string) { public highlight(payload: string) {
@ -338,7 +341,10 @@ export class CommandAdapt {
selection.forEach(el => { selection.forEach(el => {
el.highlight = payload el.highlight = payload
}) })
this.draw.render({ isSetCursor: false }) this.draw.render({
isSetCursor: false,
isCompute: false
})
} }
public rowFlex(payload: RowFlex) { public rowFlex(payload: RowFlex) {
@ -1092,7 +1098,7 @@ export class CommandAdapt {
const { endIndex } = this.range.getRange() const { endIndex } = this.range.getRange()
this.draw.render({ this.draw.render({
curIndex: endIndex, curIndex: endIndex,
isComputeRowList: false isCompute: false
}) })
} }
@ -1112,7 +1118,7 @@ export class CommandAdapt {
const { endIndex } = this.range.getRange() const { endIndex } = this.range.getRange()
this.draw.render({ this.draw.render({
curIndex: endIndex, curIndex: endIndex,
isComputeRowList: false isCompute: false
}) })
} }
@ -1173,7 +1179,8 @@ export class CommandAdapt {
options.watermark.font = payload.font || font options.watermark.font = payload.font || font
this.draw.render({ this.draw.render({
isSetCursor: false, isSetCursor: false,
isSubmitHistory: false isSubmitHistory: false,
isCompute: false
}) })
} }
@ -1185,7 +1192,8 @@ export class CommandAdapt {
options.watermark = { ...defaultWatermarkOption } options.watermark = { ...defaultWatermarkOption }
this.draw.render({ this.draw.render({
isSetCursor: false, isSetCursor: false,
isSubmitHistory: false isSubmitHistory: false,
isCompute: false
}) })
} }
} }
@ -1229,7 +1237,9 @@ export class CommandAdapt {
if (index === null) return if (index === null) return
this.draw.render({ this.draw.render({
isSetCursor: false, isSetCursor: false,
isSubmitHistory: false isSubmitHistory: false,
isCompute: false,
isLazy: false
}) })
} }
@ -1238,7 +1248,9 @@ export class CommandAdapt {
if (index === null) return if (index === null) return
this.draw.render({ this.draw.render({
isSetCursor: false, 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 const { width, height, scale } = this.options
if (scale !== 1) { if (scale !== 1) {
this.draw.setPageScale(1) this.draw.setPageScale(1)
} }
printImageBase64(this.draw.getDataURL(), width, height) const base64List = await this.draw.getDataURL()
printImageBase64(base64List, width, height)
if (scale !== 1) { if (scale !== 1) {
this.draw.setPageScale(scale) this.draw.setPageScale(scale)
} }
@ -1393,7 +1406,7 @@ export class CommandAdapt {
}) })
} }
public getImage(): string[] { public getImage(): Promise<string[]> {
return this.draw.getDataURL() return this.draw.getDataURL()
} }

@ -1,11 +1,11 @@
import { version } from '../../../../package.json' import { version } from '../../../../package.json'
import { ZERO } from '../../dataset/constant/Common' import { ZERO } from '../../dataset/constant/Common'
import { RowFlex } from '../../dataset/enum/Row' 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 { IEditorOption, IEditorResult } from '../../interface/Editor'
import { IElement, IElementMetrics, IElementPosition, IElementFillRect, IElementStyle } from '../../interface/Element' import { IElement, IElementMetrics, IElementPosition, IElementFillRect, IElementStyle } from '../../interface/Element'
import { IRow, IRowElement } from '../../interface/Row' import { IRow, IRowElement } from '../../interface/Row'
import { deepClone, getUUID } from '../../utils' import { deepClone, getUUID, nextTick } from '../../utils'
import { Cursor } from '../cursor/Cursor' import { Cursor } from '../cursor/Cursor'
import { CanvasEvent } from '../event/CanvasEvent' import { CanvasEvent } from '../event/CanvasEvent'
import { GlobalEvent } from '../event/GlobalEvent' import { GlobalEvent } from '../event/GlobalEvent'
@ -49,6 +49,7 @@ import { IMargin } from '../../interface/Margin'
import { BlockParticle } from './particle/block/BlockParticle' import { BlockParticle } from './particle/block/BlockParticle'
import { EDITOR_COMPONENT, EDITOR_PREFIX } from '../../dataset/constant/Editor' import { EDITOR_COMPONENT, EDITOR_PREFIX } from '../../dataset/constant/Editor'
import { I18n } from '../i18n/I18n' import { I18n } from '../i18n/I18n'
import { ImageObserver } from '../observer/ImageObserver'
export class Draw { export class Draw {
@ -96,12 +97,15 @@ export class Draw {
private workerManager: WorkerManager private workerManager: WorkerManager
private scrollObserver: ScrollObserver private scrollObserver: ScrollObserver
private selectionObserver: SelectionObserver private selectionObserver: SelectionObserver
private imageObserver: ImageObserver
private rowList: IRow[] private rowList: IRow[]
private pageRowList: IRow[][]
private painterStyle: IElementStyle | null private painterStyle: IElementStyle | null
private painterOptions: IPainterOptions | null private painterOptions: IPainterOptions | null
private visiblePageNoList: number[] private visiblePageNoList: number[]
private intersectionPageNo: number private intersectionPageNo: number
private lazyRenderIntersectionObserver: IntersectionObserver | null
constructor( constructor(
rootContainer: HTMLElement, rootContainer: HTMLElement,
@ -153,6 +157,7 @@ export class Draw {
this.scrollObserver = new ScrollObserver(this) this.scrollObserver = new ScrollObserver(this)
this.selectionObserver = new SelectionObserver() this.selectionObserver = new SelectionObserver()
this.imageObserver = new ImageObserver()
this.canvasEvent = new CanvasEvent(this) this.canvasEvent = new CanvasEvent(this)
this.cursor = new Cursor(this, this.canvasEvent) this.cursor = new Cursor(this, this.canvasEvent)
@ -163,10 +168,12 @@ export class Draw {
this.workerManager = new WorkerManager(this) this.workerManager = new WorkerManager(this)
this.rowList = [] this.rowList = []
this.pageRowList = []
this.painterStyle = null this.painterStyle = null
this.painterOptions = null this.painterOptions = null
this.visiblePageNoList = [] this.visiblePageNoList = []
this.intersectionPageNo = 0 this.intersectionPageNo = 0
this.lazyRenderIntersectionObserver = null
this.render({ isSetCursor: false }) this.render({ isSetCursor: false })
} }
@ -290,6 +297,14 @@ export class Draw {
return this.pageList return this.pageList
} }
public getRowList(): IRow[] {
return this.rowList
}
public getPageRowList(): IRow[][] {
return this.pageRowList
}
public getCtx(): CanvasRenderingContext2D { public getCtx(): CanvasRenderingContext2D {
return this.ctxList[this.pageNo] return this.ctxList[this.pageNo]
} }
@ -405,6 +420,10 @@ export class Draw {
return this.workerManager return this.workerManager
} }
public getImageObserver(): ImageObserver {
return this.imageObserver
}
public getI18n(): I18n { public getI18n(): I18n {
return this.i18n return this.i18n
} }
@ -413,7 +432,14 @@ export class Draw {
return this.rowList.length return this.rowList.length
} }
public getDataURL(): string[] { public async getDataURL(): Promise<string[]> {
this.render({
isLazy: false,
isCompute: false,
isSetCursor: false,
isSubmitHistory: false
})
await this.imageObserver.allSettled()
return this.pageList.map(c => c.toDataURL()) return this.pageList.map(c => c.toDataURL())
} }
@ -599,7 +625,6 @@ export class Draw {
private _computeRowList(innerWidth: number, elementList: IElement[]) { private _computeRowList(innerWidth: number, elementList: IElement[]) {
const { defaultSize, defaultRowMargin, scale, tdPadding, defaultTabWidth } = this.options const { defaultSize, defaultRowMargin, scale, tdPadding, defaultTabWidth } = this.options
const defaultBasicRowMarginHeight = this.getDefaultBasicRowMarginHeight() const defaultBasicRowMarginHeight = this.getDefaultBasicRowMarginHeight()
const tdGap = tdPadding * 2
const canvas = document.createElement('canvas') const canvas = document.createElement('canvas')
const ctx = canvas.getContext('2d') as CanvasRenderingContext2D const ctx = canvas.getContext('2d') as CanvasRenderingContext2D
const rowList: IRow[] = [] const rowList: IRow[] = []
@ -609,6 +634,7 @@ export class Draw {
height: 0, height: 0,
ascent: 0, ascent: 0,
elementList: [], elementList: [],
startIndex: 0,
rowFlex: elementList?.[1]?.rowFlex rowFlex: elementList?.[1]?.rowFlex
}) })
} }
@ -642,6 +668,7 @@ export class Draw {
} }
metrics.boundingBoxAscent = 0 metrics.boundingBoxAscent = 0
} else if (element.type === ElementType.TABLE) { } else if (element.type === ElementType.TABLE) {
const tdGap = tdPadding * 2
// 计算表格行列 // 计算表格行列
this.tableParticle.computeRowColInfo(element) this.tableParticle.computeRowColInfo(element)
// 计算表格内元素信息 // 计算表格内元素信息
@ -820,6 +847,7 @@ export class Draw {
rowList.push({ rowList.push({
width: metrics.width, width: metrics.width,
height, height,
startIndex: i,
elementList: [rowElement], elementList: [rowElement],
ascent, ascent,
rowFlex: elementList[i + 1]?.rowFlex, rowFlex: elementList[i + 1]?.rowFlex,
@ -841,6 +869,45 @@ export class Draw {
return rowList 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) { private _drawRichText(ctx: CanvasRenderingContext2D) {
this.underline.render(ctx) this.underline.render(ctx)
this.strikeout.render(ctx) this.strikeout.render(ctx)
@ -848,25 +915,13 @@ export class Draw {
this.textParticle.complete() this.textParticle.complete()
} }
private _drawRow(ctx: CanvasRenderingContext2D, payload: IDrawRowPayload): IDrawRowResult { private _drawRow(ctx: CanvasRenderingContext2D, payload: IDrawRowPayload) {
const { positionList, rowList, pageNo, startX, startY, startIndex, innerWidth } = payload const { rowList, pageNo, positionList, startIndex } = payload
const { scale, tdPadding } = this.options const { scale, tdPadding } = this.options
const { isCrossRowCol, tableId } = this.range.getRange() const { isCrossRowCol, tableId } = this.range.getRange()
const tdGap = tdPadding * 2
let x = startX
let y = startY
let index = startIndex let index = startIndex
for (let i = 0; i < rowList.length; i++) { for (let i = 0; i < rowList.length; i++) {
const curRow = rowList[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 = { const rangeRecord: IElementFillRect = {
x: 0, x: 0,
@ -878,28 +933,13 @@ export class Draw {
for (let j = 0; j < curRow.elementList.length; j++) { for (let j = 0; j < curRow.elementList.length; j++) {
const element = curRow.elementList[j] const element = curRow.elementList[j]
const metrics = element.metrics const metrics = element.metrics
const offsetY = // 当前元素位置信息
(element.imgDisplay !== ImageDisplay.INLINE && element.type === ElementType.IMAGE) const {
|| element.type === ElementType.LATEX
? curRow.ascent - metrics.height
: curRow.ascent
const positionItem: IElementPosition = {
pageNo,
index,
value: element.value,
rowNo: i,
metrics,
ascent: offsetY, ascent: offsetY,
lineHeight: curRow.height,
isLastLetter: j === curRow.elementList.length - 1,
coordinate: { coordinate: {
leftTop: [x, y], leftTop: [x, y]
leftBottom: [x, y + curRow.height],
rightTop: [x + metrics.width, y],
rightBottom: [x + metrics.width, y + curRow.height]
} }
} } = positionList[curRow.startIndex + j]
positionList.push(positionItem)
// 元素绘制 // 元素绘制
if (element.type === ElementType.IMAGE) { if (element.type === ElementType.IMAGE) {
this._drawRichText(ctx) this._drawRichText(ctx)
@ -1004,30 +1044,22 @@ export class Draw {
} }
} }
index++ index++
x += metrics.width
// 绘制表格内元素 // 绘制表格内元素
if (element.type === ElementType.TABLE) { if (element.type === ElementType.TABLE) {
const tdGap = tdPadding * 2
for (let t = 0; t < element.trList!.length; t++) { for (let t = 0; t < element.trList!.length; t++) {
const tr = element.trList![t] const tr = element.trList![t]
for (let d = 0; d < tr.tdList!.length; d++) { for (let d = 0; d < tr.tdList!.length; d++) {
const td = tr.tdList[d] const td = tr.tdList[d]
td.positionList = [] this._drawRow(ctx, {
const drawRowResult = this._drawRow(ctx, { positionList: td.positionList!,
positionList: td.positionList,
rowList: td.rowList!, rowList: td.rowList!,
pageNo, pageNo,
startIndex: 0, startIndex: 0,
startX: (td.x! + tdPadding) * scale + tablePreX,
startY: td.y! * scale + tablePreY,
innerWidth: (td.width! - tdGap) * scale 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) this.range.render(ctx, x, y, width, height)
} }
if (isCrossRowCol && tableRangeElement && tableRangeElement.id === tableId) { if (isCrossRowCol && tableRangeElement && tableRangeElement.id === tableId) {
const { coordinate: { leftTop: [x, y] } } = positionList[curRow.startIndex]
this.tableParticle.drawRange(ctx, tableRangeElement, x, y) this.tableParticle.drawRange(ctx, tableRangeElement, x, y)
} }
x = startX
y += curRow.height
} }
return { x, y, index }
} }
private _clearPage(pageNo: number) { private _clearPage(pageNo: number) {
@ -1055,31 +1085,22 @@ export class Draw {
private _drawPage(positionList: IElementPosition[], rowList: IRow[], pageNo: number) { private _drawPage(positionList: IElementPosition[], rowList: IRow[], pageNo: number) {
const { pageMode } = this.options const { pageMode } = this.options
const margins = this.getMargins()
const innerWidth = this.getInnerWidth() const innerWidth = this.getInnerWidth()
const ctx = this.ctxList[pageNo] const ctx = this.ctxList[pageNo]
this._clearPage(pageNo) this._clearPage(pageNo)
// 绘制背景 // 绘制背景
this.background.render(ctx) this.background.render(ctx)
// 绘制页边距 // 绘制页边距
const leftTopPoint: [number, number] = [margins[3], margins[0]]
this.margin.render(ctx) this.margin.render(ctx)
// 渲染元素 // 渲染元素
let x = leftTopPoint[0] const index = rowList[0].startIndex
let y = leftTopPoint[1] this._drawRow(ctx, {
let index = positionList.length
const drawRowResult = this._drawRow(ctx, {
positionList, positionList,
rowList, rowList,
pageNo, pageNo,
startIndex: index, startIndex: index,
startX: x,
startY: y,
innerWidth innerWidth
}) })
x = drawRowResult.x
y = drawRowResult.y
index = drawRowResult.index
// 绘制页眉 // 绘制页眉
this.header.render(ctx) 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((<HTMLCanvasElement>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) { public render(payload?: IDrawOption) {
const { pageMode } = this.options
const { const {
isSubmitHistory = true, isSubmitHistory = true,
isSetCursor = true, isSetCursor = true,
isComputeRowList = true isCompute = true,
isLazy = true
} = payload || {} } = payload || {}
let { curIndex } = payload || {} let { curIndex } = payload || {}
const height = this.getHeight()
const innerWidth = this.getInnerWidth() const innerWidth = this.getInnerWidth()
// 计算行信息 // 计算文档信息
if (isComputeRowList) { if (isCompute) {
// 行信息
this.rowList = this._computeRowList(innerWidth, this.elementList) this.rowList = this._computeRowList(innerWidth, this.elementList)
// 页面信息
this.pageRowList = this._computePageList()
// 位置信息
this.position.computePositionList()
// 搜索信息
const searchKeyword = this.search.getSearchKeyword() const searchKeyword = this.search.getSearchKeyword()
if (searchKeyword) { if (searchKeyword) {
this.search.compute(searchKeyword) this.search.compute(searchKeyword)
} }
} }
// 清除光标等副作用 // 清除光标等副作用
this.imageObserver.clearAll()
this.cursor.recoveryCursor() this.cursor.recoveryCursor()
this.position.setPositionList([]) // 创建纸张
const positionList = this.position.getOriginalPositionList() const positionList = this.position.getOriginalPositionList()
// 按页渲染 for (let i = 0; i < this.pageRowList.length; i++) {
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++) {
if (!this.pageList[i]) { if (!this.pageList[i]) {
this._createPage(i) this._createPage(i)
} }
const rowList = pageRowList[i] }
this._drawPage(positionList, rowList, i) // 绘制元素
if (isLazy) {
this._lazyRender()
} else {
this._immediateRender()
} }
// 移除多余页 // 移除多余页
setTimeout(() => { nextTick(() => {
const curPageCount = pageRowList.length const curPageCount = this.pageRowList.length
const prePageCount = this.pageList.length const prePageCount = this.pageList.length
if (prePageCount > curPageCount) { if (prePageCount > curPageCount) {
const deleteCount = prePageCount - curPageCount const deleteCount = prePageCount - curPageCount
@ -1203,12 +1222,11 @@ export class Draw {
self.render({ curIndex, isSubmitHistory: false }) self.render({ curIndex, isSubmitHistory: false })
}) })
} }
// 信息变动回调 // 信息变动回调
setTimeout(() => { nextTick(() => {
// 页面尺寸改变 // 页面尺寸改变
if (this.listener.pageSizeChange) { if (this.listener.pageSizeChange) {
this.listener.pageSizeChange(pageRowList.length) this.listener.pageSizeChange(this.pageRowList.length)
} }
// 文档内容改变 // 文档内容改变
if (this.listener.contentChange && isSubmitHistory) { if (this.listener.contentChange && isSubmitHistory) {

@ -261,7 +261,11 @@ export class Search {
const searchMatchIndexList = this.getSearchNavigateIndexList() const searchMatchIndexList = this.getSearchNavigateIndexList()
if (searchMatchIndexList.includes(s)) { if (searchMatchIndexList.includes(s)) {
ctx.fillStyle = searchNavigateMatchColor ctx.fillStyle = searchNavigateMatchColor
this.searchNavigateScrollIntoView(position) // 是否是第一个字符,则移动到可视范围
const preSearchMatch = this.searchMatchList[s - 1]
if (!preSearchMatch || preSearchMatch.groupId !== searchMatch.groupId) {
this.searchNavigateScrollIntoView(position)
}
} else { } else {
ctx.fillStyle = searchMatchColor ctx.fillStyle = searchMatchColor
} }

@ -4,14 +4,20 @@ import { Draw } from '../Draw'
export class ImageParticle { export class ImageParticle {
private draw: Draw
protected options: Required<IEditorOption> protected options: Required<IEditorOption>
protected imageCache: Map<string, HTMLImageElement> protected imageCache: Map<string, HTMLImageElement>
constructor(draw: Draw) { constructor(draw: Draw) {
this.draw = draw
this.options = draw.getOptions() this.options = draw.getOptions()
this.imageCache = new Map() this.imageCache = new Map()
} }
protected addImageObserver(promise: Promise<unknown>) {
this.draw.getImageObserver().add(promise)
}
public render(ctx: CanvasRenderingContext2D, element: IElement, x: number, y: number) { public render(ctx: CanvasRenderingContext2D, element: IElement, x: number, y: number) {
const { scale } = this.options const { scale } = this.options
const width = element.width! * scale const width = element.width! * scale
@ -20,12 +26,19 @@ export class ImageParticle {
const img = this.imageCache.get(element.id!)! const img = this.imageCache.get(element.id!)!
ctx.drawImage(img, x, y, width, height) ctx.drawImage(img, x, y, width, height)
} else { } else {
const img = new Image() const imageLoadPromise = new Promise((resolve, reject) => {
img.src = element.value const img = new Image()
img.onload = () => { img.src = element.value
ctx.drawImage(img, x, y, width, height) img.onload = () => {
this.imageCache.set(element.id!, img) ctx.drawImage(img, x, y, width, height)
} this.imageCache.set(element.id!, img)
resolve(element)
}
img.onerror = (error) => {
reject(error)
}
})
this.addImageObserver(imageLoadPromise)
} }
} }

@ -21,12 +21,19 @@ export class LaTexParticle extends ImageParticle {
const img = this.imageCache.get(element.value)! const img = this.imageCache.get(element.value)!
ctx.drawImage(img, x, y, width, height) ctx.drawImage(img, x, y, width, height)
} else { } else {
const img = new Image() const laTexLoadPromise = new Promise((resolve, reject) => {
img.src = element.laTexSVG! const img = new Image()
img.onload = () => { img.src = element.laTexSVG!
ctx.drawImage(img, x, y, width, height) img.onload = () => {
this.imageCache.set(element.value, img) ctx.drawImage(img, x, y, width, height)
} this.imageCache.set(element.value, img)
resolve(element)
}
img.onerror = (error) => {
reject(error)
}
})
this.addImageObserver(laTexLoadPromise)
} }
} }

@ -123,7 +123,7 @@ export class CanvasEvent {
this.draw.render({ this.draw.render({
isSubmitHistory: false, isSubmitHistory: false,
isSetCursor: false, isSetCursor: false,
isComputeRowList: false isCompute: false
}) })
} }

@ -44,7 +44,7 @@ function dblclick(host: CanvasEvent) {
draw.render({ draw.render({
isSubmitHistory: false, isSubmitHistory: false,
isSetCursor: false, isSetCursor: false,
isComputeRowList: false isCompute: false
}) })
} }
@ -87,7 +87,7 @@ function threeClick(host: CanvasEvent) {
draw.render({ draw.render({
isSubmitHistory: false, isSubmitHistory: false,
isSetCursor: false, isSetCursor: false,
isComputeRowList: false isCompute: false
}) })
} }

@ -116,7 +116,7 @@ export function keydown(evt: KeyboardEvent, host: CanvasEvent) {
curIndex: isCollapsed ? anchorStartIndex : undefined, curIndex: isCollapsed ? anchorStartIndex : undefined,
isSetCursor: isCollapsed, isSetCursor: isCollapsed,
isSubmitHistory: false, isSubmitHistory: false,
isComputeRowList: false isCompute: false
}) })
evt.preventDefault() evt.preventDefault()
} }
@ -150,7 +150,7 @@ export function keydown(evt: KeyboardEvent, host: CanvasEvent) {
curIndex: isCollapsed ? anchorStartIndex : undefined, curIndex: isCollapsed ? anchorStartIndex : undefined,
isSetCursor: isCollapsed, isSetCursor: isCollapsed,
isSubmitHistory: false, isSubmitHistory: false,
isComputeRowList: false isCompute: false
}) })
evt.preventDefault() evt.preventDefault()
} }
@ -229,7 +229,7 @@ export function keydown(evt: KeyboardEvent, host: CanvasEvent) {
curIndex: isCollapsed ? anchorStartIndex : undefined, curIndex: isCollapsed ? anchorStartIndex : undefined,
isSetCursor: isCollapsed, isSetCursor: isCollapsed,
isSubmitHistory: false, isSubmitHistory: false,
isComputeRowList: false isCompute: false
}) })
} }
} else if (isMod(evt) && evt.key === KeyMap.Z) { } else if (isMod(evt) && evt.key === KeyMap.Z) {

@ -79,7 +79,7 @@ export function mousedown(evt: MouseEvent, host: CanvasEvent) {
curIndex, curIndex,
isSubmitHistory: isSetCheckbox, isSubmitHistory: isSetCheckbox,
isSetCursor: !isDirectHitImage && !isDirectHitCheckbox, isSetCursor: !isDirectHitImage && !isDirectHitCheckbox,
isComputeRowList: false isCompute: false
}) })
} }
// 预览工具组件 // 预览工具组件

@ -73,6 +73,6 @@ export function mousemove(evt: MouseEvent, host: CanvasEvent) {
draw.render({ draw.render({
isSubmitHistory: false, isSubmitHistory: false,
isSetCursor: false, isSetCursor: false,
isComputeRowList: false isCompute: false
}) })
} }

@ -0,0 +1,21 @@
export class ImageObserver {
private promiseList: Promise<unknown>[]
constructor() {
this.promiseList = []
}
public add(payload: Promise<unknown>) {
this.promiseList.push(payload)
}
public clearAll() {
this.promiseList = []
}
public allSettled() {
return Promise.allSettled(this.promiseList)
}
}

@ -1,6 +1,7 @@
import { ElementType } from '../..' import { ElementType, RowFlex } from '../..'
import { ZERO } from '../../dataset/constant/Common' 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 { IEditorOption } from '../../interface/Editor'
import { IElementPosition } from '../../interface/Element' import { IElementPosition } from '../../interface/Element'
import { ICurrentPosition, IGetPositionByXYPayload, IPositionContext } from '../../interface/Position' import { ICurrentPosition, IGetPositionByXYPayload, IPositionContext } from '../../interface/Position'
@ -45,6 +46,106 @@ export class Position {
this.positionList = payload 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) { public setCursorPosition(position: IElementPosition | null) {
this.cursorPosition = position this.cursorPosition = position
} }

@ -5,7 +5,8 @@ export interface IDrawOption {
curIndex?: number; curIndex?: number;
isSetCursor?: boolean; isSetCursor?: boolean;
isSubmitHistory?: boolean; isSubmitHistory?: boolean;
isComputeRowList?: boolean; isCompute?: boolean;
isLazy?: boolean;
} }
export interface IDrawImagePayload { export interface IDrawImagePayload {
@ -19,17 +20,9 @@ export interface IDrawRowPayload {
rowList: IRow[]; rowList: IRow[];
pageNo: number; pageNo: number;
startIndex: number; startIndex: number;
startX: number;
startY: number;
innerWidth: number; innerWidth: number;
} }
export interface IDrawRowResult {
x: number;
y: number;
index: number;
}
export interface IPainterOptions { export interface IPainterOptions {
isDblclick: boolean; isDblclick: boolean;
} }

@ -1,5 +1,6 @@
import { IElement } from '..' import { IElement } from '..'
import { IElementPosition } from './Element' import { IElementPosition } from './Element'
import { IRow } from './Row'
import { ITd } from './table/Td' import { ITd } from './table/Td'
export interface ICurrentPosition { export interface ICurrentPosition {
@ -37,4 +38,20 @@ export interface IPositionContext {
tdId?: string; tdId?: string;
trId?: string; trId?: string;
tableId?: 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;
} }

@ -11,6 +11,7 @@ export interface IRow {
height: number; height: number;
ascent: number; ascent: number;
rowFlex?: RowFlex; rowFlex?: RowFlex;
startIndex: number;
isPageBreak?: boolean; isPageBreak?: boolean;
elementList: IRowElement[]; elementList: IRowElement[];
} }

@ -111,4 +111,10 @@ export function mergeObject<T>(source: T, target: T): T {
target.push(...source) target.push(...source)
} }
return target return target
}
export function nextTick(fn: Function) {
setTimeout(() => {
fn()
}, 0)
} }
Loading…
Cancel
Save