Merge pull request #135 from Hufe921/feature/performance
Feature/performance
This commit is contained in:
@@ -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)
|
||||
})
|
||||
})
|
||||
|
||||
@@ -381,7 +381,7 @@ export class Command {
|
||||
return Command.changeImageDisplay(element, display)
|
||||
}
|
||||
|
||||
public getImage(): string[] {
|
||||
public getImage(): Promise<string[]> {
|
||||
return Command.getImage()
|
||||
}
|
||||
|
||||
|
||||
@@ -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<string[]> {
|
||||
return this.draw.getDataURL()
|
||||
}
|
||||
|
||||
|
||||
+129
-111
@@ -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<string[]> {
|
||||
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((<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) {
|
||||
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) {
|
||||
|
||||
@@ -261,7 +261,11 @@ export class Search {
|
||||
const searchMatchIndexList = this.getSearchNavigateIndexList()
|
||||
if (searchMatchIndexList.includes(s)) {
|
||||
ctx.fillStyle = searchNavigateMatchColor
|
||||
// 是否是第一个字符,则移动到可视范围
|
||||
const preSearchMatch = this.searchMatchList[s - 1]
|
||||
if (!preSearchMatch || preSearchMatch.groupId !== searchMatch.groupId) {
|
||||
this.searchNavigateScrollIntoView(position)
|
||||
}
|
||||
} else {
|
||||
ctx.fillStyle = searchMatchColor
|
||||
}
|
||||
|
||||
@@ -4,14 +4,20 @@ import { Draw } from '../Draw'
|
||||
|
||||
export class ImageParticle {
|
||||
|
||||
private draw: Draw
|
||||
protected options: Required<IEditorOption>
|
||||
protected imageCache: Map<string, HTMLImageElement>
|
||||
|
||||
constructor(draw: Draw) {
|
||||
this.draw = draw
|
||||
this.options = draw.getOptions()
|
||||
this.imageCache = new Map()
|
||||
}
|
||||
|
||||
protected addImageObserver(promise: Promise<unknown>) {
|
||||
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 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)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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 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)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -123,7 +123,7 @@ export class CanvasEvent {
|
||||
this.draw.render({
|
||||
isSubmitHistory: false,
|
||||
isSetCursor: false,
|
||||
isComputeRowList: false
|
||||
isCompute: false
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -79,7 +79,7 @@ export function mousedown(evt: MouseEvent, host: CanvasEvent) {
|
||||
curIndex,
|
||||
isSubmitHistory: isSetCheckbox,
|
||||
isSetCursor: !isDirectHitImage && !isDirectHitCheckbox,
|
||||
isComputeRowList: false
|
||||
isCompute: false
|
||||
})
|
||||
}
|
||||
// 预览工具组件
|
||||
|
||||
@@ -73,6 +73,6 @@ export function mousemove(evt: MouseEvent, host: CanvasEvent) {
|
||||
draw.render({
|
||||
isSubmitHistory: 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 { 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
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
@@ -1,5 +1,6 @@
|
||||
import { IElement } from '..'
|
||||
import { IElementPosition } from './Element'
|
||||
import { IRow } from './Row'
|
||||
import { ITd } from './table/Td'
|
||||
|
||||
export interface ICurrentPosition {
|
||||
@@ -38,3 +39,19 @@ export interface IPositionContext {
|
||||
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;
|
||||
}
|
||||
@@ -11,6 +11,7 @@ export interface IRow {
|
||||
height: number;
|
||||
ascent: number;
|
||||
rowFlex?: RowFlex;
|
||||
startIndex: number;
|
||||
isPageBreak?: boolean;
|
||||
elementList: IRowElement[];
|
||||
}
|
||||
|
||||
@@ -112,3 +112,9 @@ export function mergeObject<T>(source: T, target: T): T {
|
||||
}
|
||||
return target
|
||||
}
|
||||
|
||||
export function nextTick(fn: Function) {
|
||||
setTimeout(() => {
|
||||
fn()
|
||||
}, 0)
|
||||
}
|
||||
Reference in New Issue
Block a user