From b9f2b92cde3de35f2d0e2c68695bfee3f309e614 Mon Sep 17 00:00:00 2001 From: yulei Date: Sun, 18 Aug 2024 11:41:36 +0800 Subject: [PATCH 01/21] =?UTF-8?q?feat:=20=E5=AE=9E=E7=8E=B0=E8=A1=A8?= =?UTF-8?q?=E6=A0=BC=E8=B7=A8=E9=A1=B5=E6=8B=86=E5=88=86=20#41?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/editor/core/draw/Draw.ts | 342 +++++++++++++++++++++++++++---- src/editor/interface/table/Td.ts | 1 + src/editor/interface/table/Tr.ts | 2 + src/editor/utils/element.ts | 76 ++++++- 4 files changed, 383 insertions(+), 38 deletions(-) diff --git a/src/editor/core/draw/Draw.ts b/src/editor/core/draw/Draw.ts index f0ca51375..d73a0f32a 100644 --- a/src/editor/core/draw/Draw.ts +++ b/src/editor/core/draw/Draw.ts @@ -102,6 +102,7 @@ import { PUNCTUATION_REG } from '../../dataset/constant/Regular' import { LineBreakParticle } from './particle/LineBreakParticle' import { MouseObserver } from '../observer/MouseObserver' import { LineNumber } from './frame/LineNumber' +import { ITd } from '../../interface/table/Td' export class Draw { private container: HTMLDivElement @@ -1265,6 +1266,24 @@ export class Draw { // 表格分页处理进度:https://github.com/Hufe921/canvas-editor/issues/41 // 查看后续表格是否属于同一个源表格-存在即合并 if (element.pagingId) { + // 为当前表格构建一个虚拟表格 + const virtualTable = Array.from( + { length: element.trList!.length }, + () => new Array(element.colgroup!.length) + ) as Array> + element.trList!.forEach((tr, trIndex) => { + let tdIndex = 0 + while (virtualTable[trIndex][tdIndex] === null) { + tdIndex++ + } + tr.tdList.forEach(td => { + virtualTable[trIndex][tdIndex] = td + for (let i = 1; i < td.rowspan; i++) { + virtualTable[trIndex + i][tdIndex] = null + } + tdIndex += td.colspan + }) + }) let tableIndex = i + 1 let combineCount = 0 while (tableIndex < elementList.length) { @@ -1273,8 +1292,57 @@ export class Draw { const nexTrList = nextElement.trList!.filter( tr => !tr.pagingRepeat ) - element.trList!.push(...nexTrList) - element.height! += nextElement.height! + // 判断后续表格第一行是拆分出来的还是从原表格挪到下一页的 + const isNextTrSplit = + element.trList![element.trList!.length - 1].id === + nexTrList[0].pagingOriginId + let tdIndex = 0 + const mergedTds: ITd[] = [] + nexTrList[0].tdList.forEach(td => { + let targetTd + // 如果虚拟表格最后一行对应位置有单元格,则其就为目标单元格,否则向上查找 + if (virtualTable[virtualTable.length - 1][tdIndex]) { + targetTd = virtualTable[virtualTable.length - 1][tdIndex] + } else { + for (let i = virtualTable.length - 2; i >= 0; i--) { + if (virtualTable[i][tdIndex]) { + targetTd = virtualTable[i][tdIndex] + break + } + } + } + if (targetTd) { + if (targetTd.id === td.pagingOriginId) { + targetTd.value.push(...td.value) + if (isNextTrSplit) { + targetTd.rowspan = targetTd.rowspan + td.rowspan - 1 + } else { + targetTd.rowspan = targetTd.rowspan + td.rowspan + mergedTds.push(td) + } + } + tdIndex += targetTd.colspan + } + }) + nexTrList[0].tdList = nexTrList[0].tdList.filter(td => { + const isNotMerged = mergedTds.every( + mergedTd => mergedTd.id !== td.id + ) + delete td.pagingOriginId + return isNotMerged + }) + while (nexTrList.length > 0) { + const lastTr = element.trList![element.trList!.length - 1] + const nextTr = nexTrList.shift()! + if (lastTr.id === nextTr.pagingOriginId) { + lastTr.height += nextTr.pagingOriginHeight || 0 + } else { + nextTr.height = nextTr.pagingOriginHeight || nextTr.height + element.trList!.push(nextTr) + } + delete nextTr.pagingOriginHeight + delete nextTr.pagingOriginId + } tableIndex++ combineCount++ } else { @@ -1384,7 +1452,7 @@ export class Draw { } } // 当前剩余高度是否能容下当前表格第一行(可拆分)的高度,排除掉表头类型 - const rowMarginHeight = rowMargin * 2 * scale + const rowMarginHeight = rowMargin * 2 if ( curPagePreHeight + element.trList![0].height! + rowMarginHeight > height || @@ -1396,50 +1464,251 @@ export class Draw { // 表格高度超过页面高度开始截断行 if (curPagePreHeight + rowMarginHeight + elementHeight > height) { const trList = element.trList! - // 计算需要移除的行数 - let deleteStart = 0 - let deleteCount = 0 - let preTrHeight = 0 - // 大于一行时再拆分避免循环 - if (trList.length > 1) { - for (let r = 0; r < trList.length; r++) { - const tr = trList[r] - const trHeight = tr.height * scale - if ( - curPagePreHeight + rowMarginHeight + preTrHeight + trHeight > - height - ) { - // 当前行存在跨行中断-暂时忽略分页 - const rowColCount = tr.tdList.reduce( - (pre, cur) => pre + cur.colspan, - 0 - ) - if (element.colgroup?.length !== rowColCount) { - deleteCount = 0 + /** 在哪一行截断表格 */ + let splitTrIndex = -1 + /** 被截断的行在截断线之前的高度 */ + let splitTrPreHeight = 0 + // 根据表格行列数构造出一个虚拟表格 + const virtualTable = Array.from({ length: trList.length }, () => + new Array(element.colgroup?.length).fill(undefined) + ) as Array> + trList.forEach(tr => { + tr.tdList.forEach(td => { + virtualTable[td.rowIndex!][td.colIndex!] = td + }) + }) + // 加上表格上方的行间距 + curPagePreHeight += rowMargin + for (const [trIndex, tr] of trList.entries()) { + // 找到需要截断的行,以及该行在截断线之前的高度 + if ( + !tr.pagingRepeat && // 标题行不可截断 + curPagePreHeight + rowMargin + tr.height * scale > height + ) { + splitTrIndex = trIndex + splitTrPreHeight = height - curPagePreHeight - rowMargin // 额外减去被拆分的表格到下方的行间距 + break + } else { + curPagePreHeight += tr.height * scale + } + } + if (splitTrIndex > -1) { + // 构建出一个行作为被截断出来的新行 + const cloneTr = new Array(element.colgroup!.length) as Array< + ITd | undefined + > + // 判断目标行是否可截断 + const allowSplitTr = + splitTrPreHeight > trList[splitTrIndex].minHeight! * scale && // 最小行高区间内不可截断 + trList[splitTrIndex].tdList.every( + // 如果截断线穿过该行所有单元格中第一个排版行,此时该行也不可截断 + td => td.rowList![0].height < splitTrPreHeight + ) + if (!allowSplitTr) { + splitTrPreHeight = 0 + } + // 遍历虚拟表格中的截断行,如果某个列的位置有td,表明截断的是这个td;如果该位置没有td,且该位置不是被列合并单元格所占,此时向上查找实际被截断的td + let preMergedEndIndex = -1 + let splitTrReduceHeight = + trList[splitTrIndex].height - splitTrPreHeight / scale // 被拆分的行减少的高度 + virtualTable[splitTrIndex].forEach((td, tdIndex) => { + if (tdIndex <= preMergedEndIndex) { + return + } + // 虚拟表格中截断行的当前索引位置是否有td + let hasTdAtCurIndex = false + // 找到被截断的td + let splitTd + if (td) { + splitTd = td + hasTdAtCurIndex = true + preMergedEndIndex = td.colIndex! + td.colspan - 1 + } else { + for (let i = splitTrIndex; i >= 0; i--) { + if (virtualTable[i][tdIndex]) { + splitTd = virtualTable[i][tdIndex] + preMergedEndIndex = + splitTd!.colIndex! + splitTd!.colspan - 1 + break + } } - break + } + if (splitTd) { + cloneTr[tdIndex] = deepClone(splitTd) + // 如果tr可拆分,根据截断位置,将td中的内容拆分到新行中 + // 如果tr不可拆分,但当前位置td是跨行单元格,同样需要拆分 + if (allowSplitTr || splitTd.rowspan > 1) { + cloneTr[tdIndex]!.pagingOriginId = splitTd.id + cloneTr[tdIndex]!.id = getUUID() + // 计算当前td在截断线之前的高度(默认为被截断行在截断线之前的高度,若td跨行,则加上其他行高度) + let splitTdPreHeight = splitTrPreHeight + for (let i = splitTd.rowIndex!; i < splitTrIndex; i++) { + splitTdPreHeight += trList[i].height * scale + } + // 根据td中已排版的内容的高度,计算出哪些内容需要拆分到新行中 + let splitTdPreRowHeight = 0 + let splitTdRowIndex = -1 + for (const [rowIndex, row] of splitTd.rowList!.entries()) { + if ( + row.height + + splitTdPreRowHeight + + tdPaddingHeight * scale > + splitTdPreHeight + ) { + splitTdRowIndex = rowIndex + break + } else { + splitTdPreRowHeight += row.height + } + } + if (splitTdRowIndex > -1) { + // 拆分td中的内容 + cloneTr[tdIndex]!.rowList = + splitTd.rowList!.splice(splitTdRowIndex) + cloneTr[tdIndex]!.value = cloneTr[tdIndex]!.rowList!.map( + row => row.elementList + ).flat() + splitTd.value = splitTd + .rowList!.map(row => row.elementList) + .flat() + // 计算被拆分的单元格高度减少了多少 + const splitTdReduceMainHeight = cloneTr[ + tdIndex + ]!.rowList!.reduce((ret, row) => { + return ret + row.height / scale + }, 0) + splitTd.mainHeight! -= splitTdReduceMainHeight + // 如果目标行可拆分,计算出行减小的高度 + if ( + allowSplitTr && + splitTd.rowspan === 1 && + splitTdRowIndex > 0 + ) { + splitTrReduceHeight = Math.min( + splitTd.height! - splitTdPreRowHeight / scale, + splitTrReduceHeight + ) + } + } else { + // 执行到这里表示当前td虽然被截断,但是内容高度未达到截断线位置 + // FIXME: 此时拆分出来的单元格为空,value中不应有元素,否则会影响单元格可编辑性以及单元格尺寸 + cloneTr[tdIndex]!.value = [{ value: ZERO }] + if (allowSplitTr && splitTd.rowspan === 1) { + splitTrReduceHeight = Math.min( + splitTd.height! - splitTdPreHeight / scale, + splitTrReduceHeight + ) + } + } + // 更新td的rowspan + if (hasTdAtCurIndex) { + cloneTr[tdIndex]!.rowspan = splitTd.rowspan + splitTd.rowspan = 1 + } else { + cloneTr[tdIndex]!.rowspan = + splitTd.rowspan - splitTrIndex + splitTd.rowIndex! + splitTd.rowspan = splitTrIndex - splitTd.rowIndex! + 1 + // 如果tr不可截断,且当前td是跨行单元格,由于tr会被挪到后续表格,因此需要将跨行单元格的rowspan减1 + if (!allowSplitTr && splitTd.rowspan > 1) { + splitTd.rowspan-- + } + } + } + } + }) + // 构造新行 + const newTr = deepClone(trList[splitTrIndex]) + newTr.tdList = cloneTr.filter(td => td !== undefined) as ITd[] + if (allowSplitTr) { + newTr.pagingOriginId = newTr.id + newTr.id = getUUID() + trList[splitTrIndex].height -= splitTrReduceHeight + trList[splitTrIndex].tdList.forEach(td => { + td.realHeight! -= splitTrReduceHeight + td.height! -= splitTrReduceHeight + }) + // 记录拆分出来的新行的原始高度 + newTr.pagingOriginHeight = splitTrReduceHeight + newTr.height = Math.max( + splitTrReduceHeight + tdPaddingHeight, + newTr.minHeight! + ) + } else { + newTr.id = getUUID() + // 记录被挪到下一页的行的原始高度 + newTr.pagingOriginHeight = newTr.height + } + // 构造出后续表格的行 + const cloneTrList = trList.splice(splitTrIndex + 1) + cloneTrList.unshift(newTr) + // 如果当前表格行不可从中间拆分,此行将被挪至后续表格,因此将其从当前表格中移除 + if (!allowSplitTr) { + trList.splice(splitTrIndex, 1) + } + let totalHeight = 0 + // 更新后续表格首行行高 + const crossRowTds: ITd[] = [] + newTr.tdList.forEach(td => { + if (td.rowspan === 1) { + const mainHeight = + td.rowList!.reduce( + (ret, row) => ret + row.height / scale, + 0 + ) + tdPaddingHeight + newTr.height = Math.max(mainHeight, newTr.height!) } else { - deleteStart = r + 1 - deleteCount = trList.length - deleteStart - preTrHeight += trHeight + crossRowTds.push(td) + } + }) + totalHeight += newTr.height + const groupTds = crossRowTds.reduce((ret, td) => { + const key = td.rowspan + if (ret[key]) { + ret[key].push(td) + } else { + ret[key] = [td] + } + return ret + }, {} as { [key: number]: ITd[] }) + const maxRowspan = Math.max( + ...Object.keys(groupTds).map(parseInt) + ) + // 计算出跨行单元格最大内容高度,如果高于其rowspan区间的行高之和,增加rowspan区间中最后一行的行高 + for (let i = 2; i <= maxRowspan; i++) { + const tds = groupTds[i] + if (tds) { + let maxMainHeight = 0 + tds.forEach(td => { + const mainHeight = + td.rowList!.reduce( + (ret, row) => ret + row.height / scale, + 0 + ) + tdPaddingHeight + maxMainHeight = Math.max(mainHeight, maxMainHeight) + }) + if (maxMainHeight > totalHeight) { + // 记录此行原始高度,便于合并时还原行高 + cloneTrList[i - 1].pagingOriginHeight = + cloneTrList[i - 1].height + cloneTrList[i - 1].height += maxMainHeight - totalHeight + } + totalHeight += maxMainHeight } } - } - if (deleteCount) { - const cloneTrList = trList.splice(deleteStart, deleteCount) - const cloneTrHeight = cloneTrList.reduce( - (pre, cur) => pre + cur.height, + const totalOriginHeight = cloneTrList.reduce( + (ret, tr) => ret + (tr.pagingOriginHeight || tr.height), 0 ) + // 追加拆分表格 const pagingId = element.pagingId || getUUID() element.pagingId = pagingId - element.height -= cloneTrHeight - metrics.height -= cloneTrHeight - metrics.boundingBoxDescent -= cloneTrHeight - // 追加拆分表格 + element.height -= totalOriginHeight + metrics.height -= totalOriginHeight * scale + metrics.boundingBoxDescent -= totalOriginHeight * scale const cloneElement = deepClone(element) cloneElement.pagingId = pagingId cloneElement.pagingIndex = element.pagingIndex! + 1 + this.tableParticle.computeRowColInfo(element) // 处理分页重复表头 const repeatTrList = trList.filter(tr => tr.pagingRepeat) if (repeatTrList.length) { @@ -1453,6 +1722,7 @@ export class Draw { } } // 表格经过分页处理-需要处理上下文 + // FIXME: 分页后上下文有误 if (element.pagingId) { const positionContext = this.position.getPositionContext() if (positionContext.isTable) { diff --git a/src/editor/interface/table/Td.ts b/src/editor/interface/table/Td.ts index 0535990b8..cc292255e 100644 --- a/src/editor/interface/table/Td.ts +++ b/src/editor/interface/table/Td.ts @@ -29,4 +29,5 @@ export interface ITd { mainHeight?: number // 内容 + 内边距高度 realHeight?: number // 真实高度(包含跨列) realMinHeight?: number // 真实最小高度(包含跨列) + pagingOriginId?: string // 被拆分到下一页的单元格的原始id } diff --git a/src/editor/interface/table/Tr.ts b/src/editor/interface/table/Tr.ts index 4b74afd4e..da70795c2 100644 --- a/src/editor/interface/table/Tr.ts +++ b/src/editor/interface/table/Tr.ts @@ -6,4 +6,6 @@ export interface ITr { tdList: ITd[] minHeight?: number pagingRepeat?: boolean // 在各页顶端以标题行的形式重复出现 + pagingOriginHeight?: number // 被拆分到下一页的行的原始高度 + pagingOriginId?: string // 被拆分到下一页的行的原始id } diff --git a/src/editor/utils/element.ts b/src/editor/utils/element.ts index a6e2789ec..26fcaaf92 100644 --- a/src/editor/utils/element.ts +++ b/src/editor/utils/element.ts @@ -599,13 +599,85 @@ export function zipElementList( } else if (element.type === ElementType.TABLE) { // 分页表格先进行合并 if (element.pagingId) { + // 为当前表格构建一个虚拟表格 + const virtualTable = Array.from( + { length: element.trList!.length }, + () => new Array(element.colgroup!.length) + ) as Array> + element.trList!.forEach((tr, trIndex) => { + let tdIndex = 0 + while (virtualTable[trIndex][tdIndex] === null) { + tdIndex++ + } + tr.tdList.forEach(td => { + virtualTable[trIndex][tdIndex] = td + for (let i = 1; i < td.rowspan; i++) { + virtualTable[trIndex + i][tdIndex] = null + } + tdIndex += td.colspan + }) + }) let tableIndex = e + 1 let combineCount = 0 while (tableIndex < elementList.length) { const nextElement = elementList[tableIndex] if (nextElement.pagingId === element.pagingId) { - element.height! += nextElement.height! - element.trList!.push(...nextElement.trList!) + const nexTrList = nextElement.trList!.filter( + tr => !tr.pagingRepeat + ) + // 判断后续表格第一行是拆分出来的还是从原表格挪到下一页的 + const isNextTrSplit = + element.trList![element.trList!.length - 1].id === + nexTrList[0].pagingOriginId + // 遍历后续表格首行中的单元格,在虚拟表格中找到其对应单元格 + let tdIndex = 0 + const mergedTds: ITd[] = [] + nexTrList[0].tdList.forEach(td => { + let targetTd + // 如果虚拟表格最后一行对应位置有单元格,则其就为目标单元格,否则向上查找 + if (virtualTable[virtualTable.length - 1][tdIndex]) { + targetTd = virtualTable[virtualTable.length - 1][tdIndex] + } else { + for (let i = virtualTable.length - 2; i >= 0; i--) { + if (virtualTable[i][tdIndex]) { + targetTd = virtualTable[i][tdIndex] + break + } + } + } + if (targetTd) { + if (targetTd.id === td.pagingOriginId) { + targetTd.value.push(...td.value) + if (isNextTrSplit) { + targetTd.rowspan = targetTd.rowspan + td.rowspan - 1 + } else { + targetTd.rowspan = targetTd.rowspan + td.rowspan + mergedTds.push(td) + } + } + tdIndex += targetTd.colspan + } + }) + nexTrList[0].tdList = nexTrList[0].tdList.filter(td => { + const isNotMerged = mergedTds.every( + mergedTd => mergedTd.id !== td.id + ) + delete td.pagingOriginId + return isNotMerged + }) + // 更新行高,逐行合并 + while (nexTrList.length > 0) { + const lastTr = element.trList![element.trList!.length - 1] + const nextTr = nexTrList.shift()! + if (lastTr.id === nextTr.pagingOriginId) { + lastTr.height += nextTr.pagingOriginHeight || 0 + } else { + nextTr.height = nextTr.pagingOriginHeight || nextTr.height + element.trList!.push(nextTr) + } + delete nextTr.pagingOriginHeight + delete nextTr.pagingOriginId + } tableIndex++ combineCount++ } else { From 1afd0450b96193030f73a6063ecbb6c69dbd1a34 Mon Sep 17 00:00:00 2001 From: yulei Date: Mon, 19 Aug 2024 14:33:03 +0800 Subject: [PATCH 02/21] =?UTF-8?q?fix:=20=E4=BF=AE=E5=A4=8D=E5=90=88?= =?UTF-8?q?=E5=B9=B6=E8=A1=A8=E6=A0=BC=E6=97=B6=E8=99=9A=E6=8B=9F=E8=A1=A8?= =?UTF-8?q?=E6=A0=BC=E6=9E=84=E5=BB=BA=E9=94=99=E8=AF=AF=E7=9A=84=E9=97=AE?= =?UTF-8?q?=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/editor/core/draw/Draw.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/editor/core/draw/Draw.ts b/src/editor/core/draw/Draw.ts index d73a0f32a..2e604bfcf 100644 --- a/src/editor/core/draw/Draw.ts +++ b/src/editor/core/draw/Draw.ts @@ -1273,10 +1273,10 @@ export class Draw { ) as Array> element.trList!.forEach((tr, trIndex) => { let tdIndex = 0 - while (virtualTable[trIndex][tdIndex] === null) { - tdIndex++ - } tr.tdList.forEach(td => { + while (virtualTable[trIndex][tdIndex] === null) { + tdIndex++ + } virtualTable[trIndex][tdIndex] = td for (let i = 1; i < td.rowspan; i++) { virtualTable[trIndex + i][tdIndex] = null From eb7a87b4a8c2144e6c52a21f8502b98a3318c578 Mon Sep 17 00:00:00 2001 From: yulei Date: Mon, 19 Aug 2024 14:50:43 +0800 Subject: [PATCH 03/21] =?UTF-8?q?fix:=20=E4=BF=AE=E5=A4=8DzipElementList?= =?UTF-8?q?=E4=B8=AD=E8=B7=A8=E9=A1=B5=E8=A1=A8=E6=A0=BC=E7=BB=93=E6=9E=84?= =?UTF-8?q?=E5=A4=84=E7=90=86=E5=BC=82=E5=B8=B8=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/editor/utils/element.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/editor/utils/element.ts b/src/editor/utils/element.ts index 26fcaaf92..04ffdfe7f 100644 --- a/src/editor/utils/element.ts +++ b/src/editor/utils/element.ts @@ -606,10 +606,10 @@ export function zipElementList( ) as Array> element.trList!.forEach((tr, trIndex) => { let tdIndex = 0 - while (virtualTable[trIndex][tdIndex] === null) { - tdIndex++ - } tr.tdList.forEach(td => { + while (virtualTable[trIndex][tdIndex] === null) { + tdIndex++ + } virtualTable[trIndex][tdIndex] = td for (let i = 1; i < td.rowspan; i++) { virtualTable[trIndex + i][tdIndex] = null From cab3d2416516a8363c183e96bc3ad6b0e8369ea4 Mon Sep 17 00:00:00 2001 From: yulei Date: Mon, 19 Aug 2024 14:58:38 +0800 Subject: [PATCH 04/21] =?UTF-8?q?fix:=20=E4=B8=8D=E5=86=8D=E6=94=B9?= =?UTF-8?q?=E5=8F=98=E6=95=B4=E8=A1=8C=E6=8C=AA=E5=88=B0=E4=B8=8B=E4=B8=80?= =?UTF-8?q?=E9=A1=B5=E7=9A=84=E8=A1=A8=E6=A0=BC=E8=A1=8Cid?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/editor/core/draw/Draw.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/src/editor/core/draw/Draw.ts b/src/editor/core/draw/Draw.ts index 2e604bfcf..221516723 100644 --- a/src/editor/core/draw/Draw.ts +++ b/src/editor/core/draw/Draw.ts @@ -1634,7 +1634,6 @@ export class Draw { newTr.minHeight! ) } else { - newTr.id = getUUID() // 记录被挪到下一页的行的原始高度 newTr.pagingOriginHeight = newTr.height } From 4b18e7dface7c7f548451963b6e8aee1d3149217 Mon Sep 17 00:00:00 2001 From: yulei Date: Wed, 21 Aug 2024 21:43:40 +0800 Subject: [PATCH 05/21] =?UTF-8?q?fix:=20=E9=99=90=E5=AE=9A=E8=A1=A8?= =?UTF-8?q?=E6=A0=BC=E8=A1=8CminHeight=E6=9C=80=E5=A4=A7=E4=B8=8D=E8=83=BD?= =?UTF-8?q?=E8=B6=85=E8=BF=87=E9=A1=B5=E9=9D=A2=E5=86=85=E5=AE=B9=E5=8C=BA?= =?UTF-8?q?=E5=9F=9F=E9=AB=98=E5=BA=A6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/editor/core/draw/Draw.ts | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) diff --git a/src/editor/core/draw/Draw.ts b/src/editor/core/draw/Draw.ts index 221516723..98c29a182 100644 --- a/src/editor/core/draw/Draw.ts +++ b/src/editor/core/draw/Draw.ts @@ -1422,6 +1422,21 @@ export class Draw { }) } } + // 检查行的minHeight,最大只能为页面主内容区域高度 + const height = this.getHeight() + const marginHeight = this.getMainOuterHeight() + const emptyMainHeight = (height - marginHeight - rowMargin * 2) / scale + trList.forEach(tr => { + if (tr.minHeight && tr.minHeight > emptyMainHeight) { + const reduceHeight = tr.minHeight - emptyMainHeight + tr.height -= reduceHeight + tr.minHeight -= reduceHeight + tr.tdList.forEach(td => { + td.realMinHeight && (td.realMinHeight -= reduceHeight) + td.realHeight && (td.realHeight -= reduceHeight) + }) + } + }) // 需要重新计算表格内值 this.tableParticle.computeRowColInfo(element) // 计算出表格高度 @@ -1437,8 +1452,6 @@ export class Draw { metrics.boundingBoxAscent = -rowMargin // 表格分页处理(拆分表格) if (isPagingMode) { - const height = this.getHeight() - const marginHeight = this.getMainOuterHeight() let curPagePreHeight = marginHeight for (let r = 0; r < rowList.length; r++) { const row = rowList[r] From 5ad8c3e6f233498ea011731a64f2f637a929fa96 Mon Sep 17 00:00:00 2001 From: yulei Date: Wed, 21 Aug 2024 22:19:53 +0800 Subject: [PATCH 06/21] =?UTF-8?q?refactor:=20=E7=AE=80=E5=8C=96=E8=A1=A8?= =?UTF-8?q?=E6=A0=BC=E8=A1=8CminHeight=E6=9C=80=E5=A4=A7=E5=80=BC=E7=9A=84?= =?UTF-8?q?=E5=A4=84=E7=90=86?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/editor/core/draw/Draw.ts | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/src/editor/core/draw/Draw.ts b/src/editor/core/draw/Draw.ts index 98c29a182..6542891ff 100644 --- a/src/editor/core/draw/Draw.ts +++ b/src/editor/core/draw/Draw.ts @@ -1428,12 +1428,11 @@ export class Draw { const emptyMainHeight = (height - marginHeight - rowMargin * 2) / scale trList.forEach(tr => { if (tr.minHeight && tr.minHeight > emptyMainHeight) { - const reduceHeight = tr.minHeight - emptyMainHeight - tr.height -= reduceHeight - tr.minHeight -= reduceHeight + tr.height = emptyMainHeight + tr.minHeight = emptyMainHeight tr.tdList.forEach(td => { - td.realMinHeight && (td.realMinHeight -= reduceHeight) - td.realHeight && (td.realHeight -= reduceHeight) + td.realHeight = emptyMainHeight + td.realMinHeight = emptyMainHeight }) } }) From 35103ca69ce2d567c2af501eaef1c839accb47ed Mon Sep 17 00:00:00 2001 From: yulei Date: Wed, 21 Aug 2024 22:54:53 +0800 Subject: [PATCH 07/21] =?UTF-8?q?refactor:=20=E7=AE=80=E5=8C=96=E8=A1=A8?= =?UTF-8?q?=E6=A0=BC=E8=A1=8CminHeight=E6=9C=80=E5=A4=A7=E5=80=BC=E7=9A=84?= =?UTF-8?q?=E5=A4=84=E7=90=86?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/editor/core/draw/Draw.ts | 21 +++++++-------------- 1 file changed, 7 insertions(+), 14 deletions(-) diff --git a/src/editor/core/draw/Draw.ts b/src/editor/core/draw/Draw.ts index 6542891ff..b004543fa 100644 --- a/src/editor/core/draw/Draw.ts +++ b/src/editor/core/draw/Draw.ts @@ -1263,6 +1263,9 @@ export class Draw { } else if (element.type === ElementType.TABLE) { const tdPaddingWidth = tdPadding[1] + tdPadding[3] const tdPaddingHeight = tdPadding[0] + tdPadding[2] + const height = this.getHeight() + const marginHeight = this.getMainOuterHeight() + const emptyMainHeight = (height - marginHeight - rowMargin * 2) / scale // 表格分页处理进度:https://github.com/Hufe921/canvas-editor/issues/41 // 查看后续表格是否属于同一个源表格-存在即合并 if (element.pagingId) { @@ -1360,6 +1363,10 @@ export class Draw { const trList = element.trList! for (let t = 0; t < trList.length; t++) { const tr = trList[t] + // 检查行的minHeight,最大只能为页面主内容区域高度 + if (tr.minHeight && tr.minHeight > emptyMainHeight) { + tr.minHeight = emptyMainHeight + } for (let d = 0; d < tr.tdList.length; d++) { const td = tr.tdList[d] const rowList = this.computeRowList({ @@ -1422,20 +1429,6 @@ export class Draw { }) } } - // 检查行的minHeight,最大只能为页面主内容区域高度 - const height = this.getHeight() - const marginHeight = this.getMainOuterHeight() - const emptyMainHeight = (height - marginHeight - rowMargin * 2) / scale - trList.forEach(tr => { - if (tr.minHeight && tr.minHeight > emptyMainHeight) { - tr.height = emptyMainHeight - tr.minHeight = emptyMainHeight - tr.tdList.forEach(td => { - td.realHeight = emptyMainHeight - td.realMinHeight = emptyMainHeight - }) - } - }) // 需要重新计算表格内值 this.tableParticle.computeRowColInfo(element) // 计算出表格高度 From 861dd48b0ac07e7a6f625d227eb79e1a8e953384 Mon Sep 17 00:00:00 2001 From: yulei Date: Thu, 22 Aug 2024 10:09:13 +0800 Subject: [PATCH 08/21] =?UTF-8?q?fix:=20=E4=BF=AE=E5=A4=8D=E8=A1=A8?= =?UTF-8?q?=E6=A0=BC=E8=A1=8CminHeight=E6=92=91=E6=BB=A1=E9=A1=B5=E9=9D=A2?= =?UTF-8?q?=E6=97=B6=EF=BC=8C=E5=86=85=E5=AE=B9=E8=B6=85=E5=87=BA=E9=A1=B5?= =?UTF-8?q?=E9=9D=A2=E9=AB=98=E5=BA=A6=E4=BC=9A=E5=AF=BC=E8=87=B4=E5=8D=A1?= =?UTF-8?q?=E6=AD=BB=E7=9A=84=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/editor/core/draw/Draw.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/editor/core/draw/Draw.ts b/src/editor/core/draw/Draw.ts index b004543fa..f60b70a1d 100644 --- a/src/editor/core/draw/Draw.ts +++ b/src/editor/core/draw/Draw.ts @@ -1504,7 +1504,7 @@ export class Draw { > // 判断目标行是否可截断 const allowSplitTr = - splitTrPreHeight > trList[splitTrIndex].minHeight! * scale && // 最小行高区间内不可截断 + splitTrPreHeight >= trList[splitTrIndex].minHeight! * scale && // 最小行高区间内不可截断 trList[splitTrIndex].tdList.every( // 如果截断线穿过该行所有单元格中第一个排版行,此时该行也不可截断 td => td.rowList![0].height < splitTrPreHeight From 473b22a96dda5bd322bfa5ff7e28461e87e3822b Mon Sep 17 00:00:00 2001 From: yulei Date: Mon, 26 Aug 2024 14:34:27 +0800 Subject: [PATCH 09/21] =?UTF-8?q?fix:=20=E4=BF=AE=E5=A4=8D=E8=A1=A8?= =?UTF-8?q?=E6=A0=BC=E5=89=8D=E7=AC=AC=E4=B8=80=E8=A1=8C=E6=98=AF=E7=A9=BA?= =?UTF-8?q?=E8=A1=8C=E6=97=B6=EF=BC=8C=E6=8B=96=E6=8B=BD=E8=A1=8C=E8=B6=85?= =?UTF-8?q?=E8=BF=87=E9=A1=B5=E9=AB=98=E4=BC=9A=E5=AF=BC=E8=87=B4=E5=A4=9A?= =?UTF-8?q?=E5=87=BA=E4=B8=80=E5=BC=A0=E7=A9=BA=E7=99=BD=E9=A1=B5=E7=9A=84?= =?UTF-8?q?=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/editor/core/draw/Draw.ts | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/editor/core/draw/Draw.ts b/src/editor/core/draw/Draw.ts index f60b70a1d..1c6422271 100644 --- a/src/editor/core/draw/Draw.ts +++ b/src/editor/core/draw/Draw.ts @@ -1363,9 +1363,13 @@ export class Draw { const trList = element.trList! for (let t = 0; t < trList.length; t++) { const tr = trList[t] - // 检查行的minHeight,最大只能为页面主内容区域高度 + // 行的minHeight最大只能为页面主内容区域高度(如果表格前第一行是空行,由于此时表格不能换页,因此需减掉空行的高度) if (tr.minHeight && tr.minHeight > emptyMainHeight) { - tr.minHeight = emptyMainHeight + if (i === 1 && elementList[0].value === ZERO) { + tr.minHeight = emptyMainHeight - rowList[0].height + } else { + tr.minHeight = emptyMainHeight + } } for (let d = 0; d < tr.tdList.length; d++) { const td = tr.tdList[d] From 51b0edb2ef7b63e151efe9281e783185cd6607e6 Mon Sep 17 00:00:00 2001 From: yulei Date: Thu, 29 Aug 2024 09:32:22 +0800 Subject: [PATCH 10/21] =?UTF-8?q?fix:=20=E4=BF=AE=E5=A4=8D=E8=A1=A8?= =?UTF-8?q?=E6=A0=BC=E8=B7=A8=E9=A1=B5=E6=8B=86=E5=88=86=E5=90=8E=E7=9A=84?= =?UTF-8?q?=E5=86=85=E5=AE=B9=E6=97=A0=E6=B3=95=E6=A1=86=E9=80=89=E7=9A=84?= =?UTF-8?q?=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/editor/core/draw/Draw.ts | 25 ++++++++++++++++++++++--- 1 file changed, 22 insertions(+), 3 deletions(-) diff --git a/src/editor/core/draw/Draw.ts b/src/editor/core/draw/Draw.ts index 1c6422271..59f41a836 100644 --- a/src/editor/core/draw/Draw.ts +++ b/src/editor/core/draw/Draw.ts @@ -1339,6 +1339,14 @@ export class Draw { const nextTr = nexTrList.shift()! if (lastTr.id === nextTr.pagingOriginId) { lastTr.height += nextTr.pagingOriginHeight || 0 + // 更新id关联 + lastTr.tdList.forEach(td => { + td.value.forEach(v => { + v.tdId = td.id + v.trId = lastTr.id + v.tableId = element.id + }) + }) } else { nextTr.height = nextTr.pagingOriginHeight || nextTr.height element.trList!.push(nextTr) @@ -1547,7 +1555,8 @@ export class Draw { // 如果tr可拆分,根据截断位置,将td中的内容拆分到新行中 // 如果tr不可拆分,但当前位置td是跨行单元格,同样需要拆分 if (allowSplitTr || splitTd.rowspan > 1) { - cloneTr[tdIndex]!.pagingOriginId = splitTd.id + cloneTr[tdIndex]!.pagingOriginId = + splitTd.pagingOriginId || splitTd.id cloneTr[tdIndex]!.id = getUUID() // 计算当前td在截断线之前的高度(默认为被截断行在截断线之前的高度,若td跨行,则加上其他行高度) let splitTdPreHeight = splitTrPreHeight @@ -1629,7 +1638,7 @@ export class Draw { const newTr = deepClone(trList[splitTrIndex]) newTr.tdList = cloneTr.filter(td => td !== undefined) as ITd[] if (allowSplitTr) { - newTr.pagingOriginId = newTr.id + newTr.pagingOriginId = newTr.pagingOriginId || newTr.id newTr.id = getUUID() trList[splitTrIndex].height -= splitTrReduceHeight trList[splitTrIndex].tdList.forEach(td => { @@ -1714,9 +1723,20 @@ export class Draw { metrics.height -= totalOriginHeight * scale metrics.boundingBoxDescent -= totalOriginHeight * scale const cloneElement = deepClone(element) + cloneElement.id = getUUID() cloneElement.pagingId = pagingId cloneElement.pagingIndex = element.pagingIndex! + 1 this.tableParticle.computeRowColInfo(element) + // 更新拆分出来的新表格中的id关联信息 + cloneTrList.forEach(tr => { + tr.tdList.forEach(td => { + td.value.forEach(v => { + v.tdId = td.id + v.trId = tr.id + v.tableId = cloneElement.id + }) + }) + }) // 处理分页重复表头 const repeatTrList = trList.filter(tr => tr.pagingRepeat) if (repeatTrList.length) { @@ -1725,7 +1745,6 @@ export class Draw { cloneTrList.unshift(...cloneRepeatTrList) } cloneElement.trList = cloneTrList - cloneElement.id = getUUID() this.spliceElementList(elementList, i + 1, 0, cloneElement) } } From d288c489b219c5b334b49df58f7e8c77bf32dd5c Mon Sep 17 00:00:00 2001 From: yulei Date: Thu, 29 Aug 2024 17:24:29 +0800 Subject: [PATCH 11/21] =?UTF-8?q?fix:=20=E4=BF=AE=E5=A4=8D=E8=A1=A8?= =?UTF-8?q?=E6=A0=BC=E8=B7=A8=E9=A1=B5=E6=8B=86=E5=88=86=E5=90=8E=E5=85=89?= =?UTF-8?q?=E6=A0=87=E5=8F=8A=E4=BD=8D=E7=BD=AE=E4=B8=8A=E4=B8=8B=E6=96=87?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/editor/core/draw/Draw.ts | 71 ++++++++++++++--------- src/editor/core/draw/frame/Footer.ts | 2 +- src/editor/core/draw/frame/Header.ts | 2 +- src/editor/core/draw/frame/Placeholder.ts | 2 +- src/editor/interface/Draw.ts | 1 + 5 files changed, 48 insertions(+), 30 deletions(-) diff --git a/src/editor/core/draw/Draw.ts b/src/editor/core/draw/Draw.ts index 59f41a836..1230c5004 100644 --- a/src/editor/core/draw/Draw.ts +++ b/src/editor/core/draw/Draw.ts @@ -1190,6 +1190,7 @@ export class Draw { table: { tdPadding }, defaultTabWidth } = this.options + let curIndex = payload.curIndex const defaultBasicRowMarginHeight = this.getDefaultBasicRowMarginHeight() const canvas = document.createElement('canvas') const ctx = canvas.getContext('2d') as CanvasRenderingContext2D @@ -1381,7 +1382,7 @@ export class Draw { } for (let d = 0; d < tr.tdList.length; d++) { const td = tr.tdList[d] - const rowList = this.computeRowList({ + const { rowList } = this.computeRowList({ innerWidth: (td.width! - tdPaddingWidth) * scale, elementList: td.value, isPagingMode @@ -1471,7 +1472,9 @@ export class Draw { // 当前剩余高度是否能容下当前表格第一行(可拆分)的高度,排除掉表头类型 const rowMarginHeight = rowMargin * 2 if ( - curPagePreHeight + element.trList![0].height! + rowMarginHeight > + curPagePreHeight + + element.trList![0].height! * scale + + rowMarginHeight > height || (element.pagingIndex !== 0 && element.trList![0].pagingRepeat) ) { @@ -1748,32 +1751,43 @@ export class Draw { this.spliceElementList(elementList, i + 1, 0, cloneElement) } } - // 表格经过分页处理-需要处理上下文 - // FIXME: 分页后上下文有误 + // 表格经过分页处理-需要处理上下文和选区 if (element.pagingId) { const positionContext = this.position.getPositionContext() - if (positionContext.isTable) { - // 查找光标所在表格索引(根据trId搜索) - let newPositionContextIndex = -1 - let newPositionContextTrIndex = -1 - let tableIndex = i - while (tableIndex < elementList.length) { - const curElement = elementList[tableIndex] - if (curElement.pagingId !== element.pagingId) break - const trIndex = curElement.trList!.findIndex( - r => r.id === positionContext.trId + if ( + positionContext.isTable && + positionContext.tableId === element.id + ) { + const trIndex = element.trList!.findIndex( + r => + r.pagingOriginId === positionContext.trId || + r.id === positionContext.trId + ) + if (~trIndex) { + const tr = element.trList![trIndex] + const tdIndex = tr.tdList!.findIndex( + d => + d.pagingOriginId === positionContext.tdId || + d.id === positionContext.tdId ) - if (~trIndex) { - newPositionContextIndex = tableIndex - newPositionContextTrIndex = trIndex - break + if (~tdIndex) { + const td = tr.tdList![tdIndex] + if (curIndex !== undefined && curIndex > -1) { + if (td.value[curIndex]) { + positionContext.index = i + positionContext.trIndex = trIndex + positionContext.tdIndex = tdIndex + positionContext.trId = tr.id + positionContext.tdId = td.id + this.range.setRange(curIndex, curIndex) + } else { + positionContext.tableId = elementList[i + 1].id + curIndex -= td.value.length + } + } } - tableIndex++ - } - if (~newPositionContextIndex) { - positionContext.index = newPositionContextIndex - positionContext.trIndex = newPositionContextTrIndex - this.position.setPositionContext(positionContext) + } else { + positionContext.tableId = elementList[i + 1].id } } } @@ -2020,7 +2034,7 @@ export class Draw { } } } - return rowList + return { rowList, curIndex } } private _computePageList(): IRow[][] { @@ -2629,11 +2643,14 @@ export class Draw { } } // 行信息 - this.rowList = this.computeRowList({ + const { rowList, curIndex: newIndex } = this.computeRowList({ isPagingMode, innerWidth, - elementList: this.elementList + elementList: this.elementList, + curIndex }) + this.rowList = rowList + curIndex = newIndex // 页面信息 this.pageRowList = this._computePageList() // 位置信息 diff --git a/src/editor/core/draw/frame/Footer.ts b/src/editor/core/draw/frame/Footer.ts index 763a0c950..726cd9b26 100644 --- a/src/editor/core/draw/frame/Footer.ts +++ b/src/editor/core/draw/frame/Footer.ts @@ -58,7 +58,7 @@ export class Footer { this.rowList = this.draw.computeRowList({ innerWidth, elementList: this.elementList - }) + }).rowList } private _computePositionList() { diff --git a/src/editor/core/draw/frame/Header.ts b/src/editor/core/draw/frame/Header.ts index 7d399001a..69fabd249 100644 --- a/src/editor/core/draw/frame/Header.ts +++ b/src/editor/core/draw/frame/Header.ts @@ -58,7 +58,7 @@ export class Header { this.rowList = this.draw.computeRowList({ innerWidth, elementList: this.elementList - }) + }).rowList } private _computePositionList() { diff --git a/src/editor/core/draw/frame/Placeholder.ts b/src/editor/core/draw/frame/Placeholder.ts index a6e4eb129..146d82d49 100644 --- a/src/editor/core/draw/frame/Placeholder.ts +++ b/src/editor/core/draw/frame/Placeholder.ts @@ -42,7 +42,7 @@ export class Placeholder { this.rowList = this.draw.computeRowList({ innerWidth, elementList: this.elementList - }) + }).rowList } private _computePositionList() { diff --git a/src/editor/interface/Draw.ts b/src/editor/interface/Draw.ts index 06483fb32..fbafc4cf6 100644 --- a/src/editor/interface/Draw.ts +++ b/src/editor/interface/Draw.ts @@ -70,4 +70,5 @@ export interface IComputeRowListPayload { innerWidth: number elementList: IElement[] isPagingMode?: boolean + curIndex?: number } From f6b9173ba78ac97c3714610f240e86c3b21f7a52 Mon Sep 17 00:00:00 2001 From: yulei Date: Fri, 30 Aug 2024 16:11:42 +0800 Subject: [PATCH 12/21] =?UTF-8?q?fix:=20=E4=BF=AE=E5=A4=8D=E8=A1=A8?= =?UTF-8?q?=E6=A0=BC=E8=B7=A8=E9=A1=B5=E6=8B=86=E5=88=86=E5=90=8E=EF=BC=8C?= =?UTF-8?q?=E5=9C=A8=E5=85=B6=E4=B8=AD=E5=BD=95=E5=85=A5=E4=B8=AD=E6=96=87?= =?UTF-8?q?=E5=BC=82=E5=B8=B8=E7=9A=84=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/editor/core/event/handlers/input.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/editor/core/event/handlers/input.ts b/src/editor/core/event/handlers/input.ts index 215d2085e..ea3b86f73 100644 --- a/src/editor/core/event/handlers/input.ts +++ b/src/editor/core/event/handlers/input.ts @@ -82,7 +82,8 @@ export function input(data: string, host: CanvasEvent) { } if (isComposing) { host.compositionInfo = { - elementList, + // 当在跨页表格中输入中文时,由于render方法会先合并然后再次拆分表格,因此需重新获取elementList + elementList: draw.getElementList(), value: text, startIndex: curIndex - inputData.length, endIndex: curIndex From 4c3a479e2b03f07e53459581bceabde44386e053 Mon Sep 17 00:00:00 2001 From: yulei Date: Mon, 2 Sep 2024 11:29:18 +0800 Subject: [PATCH 13/21] =?UTF-8?q?fix:=20=E4=BF=AE=E5=A4=8D=E5=9C=A8?= =?UTF-8?q?=E8=A1=A8=E6=A0=BC=E8=B7=A8=E9=A1=B5=E5=A4=84=E8=BE=93=E5=85=A5?= =?UTF-8?q?=E4=B8=AD=E6=96=87=E6=97=B6=E7=9A=84=E5=BC=82=E5=B8=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/editor/core/event/handlers/input.ts | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/editor/core/event/handlers/input.ts b/src/editor/core/event/handlers/input.ts index d6a64c1a7..d4d7ced69 100644 --- a/src/editor/core/event/handlers/input.ts +++ b/src/editor/core/event/handlers/input.ts @@ -22,6 +22,10 @@ export function input(data: string, host: CanvasEvent) { if (!isComposing) { const cursor = draw.getCursor() cursor.clearAgentDomValue() + } else if (position.getPositionContext().isTable) { + // TODO: 后续支持表格中合成输入时显示合成字符 + // 在表格中合成输入时,跳过后续对合成字符的处理,避免在表格中以composing状态跨页导致的问题 + return } const { TEXT, HYPERLINK, SUBSCRIPT, SUPERSCRIPT, DATE } = ElementType const text = data.replaceAll(`\n`, ZERO) @@ -88,8 +92,7 @@ export function input(data: string, host: CanvasEvent) { } if (isComposing) { host.compositionInfo = { - // 当在跨页表格中输入中文时,由于render方法会先合并然后再次拆分表格,因此需重新获取elementList - elementList: draw.getElementList(), + elementList, value: text, startIndex: curIndex - inputData.length, endIndex: curIndex From 4785c8327451ce1ed10d48d4daec08fc8aa0b20d Mon Sep 17 00:00:00 2001 From: yulei Date: Mon, 2 Sep 2024 14:29:40 +0800 Subject: [PATCH 14/21] =?UTF-8?q?fix:=20fix:=20=E4=BF=AE=E5=A4=8D=E8=B7=A8?= =?UTF-8?q?=E9=A1=B5=E6=8B=86=E5=88=86=E5=87=BA=E7=9A=84=E5=8D=95=E5=85=83?= =?UTF-8?q?=E6=A0=BC=E4=B8=AD=E5=8F=AF=E8=83=BD=E5=87=BA=E7=8E=B0=E9=A2=84?= =?UTF-8?q?=E6=9C=9F=E5=A4=96=E7=9A=84=E9=9B=B6=E5=AE=BD=E5=AD=97=E7=AC=A6?= =?UTF-8?q?=EF=BC=8C=E4=BB=8E=E8=80=8C=E5=BD=B1=E5=93=8D=E5=8D=95=E5=85=83?= =?UTF-8?q?=E6=A0=BC=E5=8F=AF=E7=BC=96=E8=BE=91=E6=80=A7=E5=8F=8A=E5=B0=BA?= =?UTF-8?q?=E5=AF=B8=E7=9A=84=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/editor/core/draw/Draw.ts | 4 +--- src/editor/core/draw/particle/ListParticle.ts | 3 +++ 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/src/editor/core/draw/Draw.ts b/src/editor/core/draw/Draw.ts index 179ca9185..65b15cd34 100644 --- a/src/editor/core/draw/Draw.ts +++ b/src/editor/core/draw/Draw.ts @@ -104,7 +104,6 @@ import { MouseObserver } from '../observer/MouseObserver' import { LineNumber } from './frame/LineNumber' import { ITd } from '../../interface/table/Td' import { PageBorder } from './frame/PageBorder' -import { ITd } from '../../interface/table/Td' export class Draw { private container: HTMLDivElement @@ -1652,8 +1651,7 @@ export class Draw { } } else { // 执行到这里表示当前td虽然被截断,但是内容高度未达到截断线位置 - // FIXME: 此时拆分出来的单元格为空,value中不应有元素,否则会影响单元格可编辑性以及单元格尺寸 - cloneTr[tdIndex]!.value = [{ value: ZERO }] + cloneTr[tdIndex]!.value = [] if (allowSplitTr && splitTd.rowspan === 1) { splitTrReduceHeight = Math.min( splitTd.height! - splitTdPreHeight / scale, diff --git a/src/editor/core/draw/particle/ListParticle.ts b/src/editor/core/draw/particle/ListParticle.ts index f4e1c498a..a3091093a 100644 --- a/src/editor/core/draw/particle/ListParticle.ts +++ b/src/editor/core/draw/particle/ListParticle.ts @@ -102,6 +102,9 @@ export class ListParticle { ): Map { const listStyleMap = new Map() let start = 0 + if (!elementList[start]) { + return listStyleMap + } let curListId = elementList[start].listId let curElementList: IElement[] = [] const elementLength = elementList.length From 7fe338a6335aaae9fb08f0041606eb8bfbe0be27 Mon Sep 17 00:00:00 2001 From: yulei Date: Mon, 2 Sep 2024 16:25:32 +0800 Subject: [PATCH 15/21] =?UTF-8?q?fix:=20=E4=BF=AE=E5=A4=8D=E7=A9=BA?= =?UTF-8?q?=E5=8D=95=E5=85=83=E6=A0=BC=E8=B7=A8=E9=A1=B5=E6=8B=86=E5=88=86?= =?UTF-8?q?=E5=BC=82=E5=B8=B8=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/editor/core/draw/Draw.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/editor/core/draw/Draw.ts b/src/editor/core/draw/Draw.ts index 65b15cd34..1b6c77b07 100644 --- a/src/editor/core/draw/Draw.ts +++ b/src/editor/core/draw/Draw.ts @@ -1561,7 +1561,8 @@ export class Draw { splitTrPreHeight >= trList[splitTrIndex].minHeight! * scale && // 最小行高区间内不可截断 trList[splitTrIndex].tdList.every( // 如果截断线穿过该行所有单元格中第一个排版行,此时该行也不可截断 - td => td.rowList![0].height < splitTrPreHeight + td => + !td.rowList![0] || td.rowList![0].height < splitTrPreHeight ) if (!allowSplitTr) { splitTrPreHeight = 0 From 9ca6bb5290fce44623d3cf55433a4f88e7215da0 Mon Sep 17 00:00:00 2001 From: yulei Date: Fri, 6 Sep 2024 10:57:19 +0800 Subject: [PATCH 16/21] =?UTF-8?q?fix:=20=E6=8E=92=E7=89=88=E8=BF=87?= =?UTF-8?q?=E7=A8=8B=E4=B8=AD=E5=90=88=E5=B9=B6=E8=B7=A8=E9=A1=B5=E8=A1=A8?= =?UTF-8?q?=E6=A0=BC=E6=97=B6=E4=BF=AE=E6=AD=A3=E4=BD=8D=E7=BD=AE=E4=B8=8A?= =?UTF-8?q?=E4=B8=8B=E6=96=87?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/editor/core/draw/Draw.ts | 68 ++++++++++++++++++++++++++++++------ 1 file changed, 58 insertions(+), 10 deletions(-) diff --git a/src/editor/core/draw/Draw.ts b/src/editor/core/draw/Draw.ts index e8e679c0a..3c6b2bb51 100644 --- a/src/editor/core/draw/Draw.ts +++ b/src/editor/core/draw/Draw.ts @@ -1303,6 +1303,7 @@ export class Draw { } metrics.boundingBoxAscent = 0 } else if (element.type === ElementType.TABLE) { + const positionContext = this.position.getPositionContext() const tdPaddingWidth = tdPadding[1] + tdPadding[3] const tdPaddingHeight = tdPadding[0] + tdPadding[2] const height = this.getHeight() @@ -1311,6 +1312,48 @@ export class Draw { // 表格分页处理进度:https://github.com/Hufe921/canvas-editor/issues/41 // 查看后续表格是否属于同一个源表格-存在即合并 if (element.pagingId) { + // TODO: 优化:一旦修正了上下文,后续表格无需再做上下文相关判断 + if (positionContext.isTable) { + // 如果位置上下文在当前表格或其拆分出的子表格中,则修正上下文(仅修正了tdId和tableId,后续拆分表格时会以此进行二次修正) + const preTables: IElement[] = [] + for (let index = i; index < elementList.length; index++) { + const table = elementList[index] + if (table.pagingId !== element.pagingId) { + break + } else { + let positionContextFixed = false + table.trList?.forEach(tr => + tr.tdList.forEach(td => { + if (td.id === positionContext.tdId) { + positionContext.tdId = td.pagingOriginId || td.id + positionContext.tableId = element.id + if (curIndex !== undefined && curIndex > -1) { + while (preTables.length > 0) { + const preTable = preTables.pop() + preTable?.trList?.forEach(preTr => + preTr.tdList.forEach(preTd => { + if ( + preTd.pagingOriginId === td.pagingOriginId || + preTd.id === td.pagingOriginId + ) { + curIndex! += preTd.value.length + } + }) + ) + } + } + positionContextFixed = true + } + }) + ) + if (positionContextFixed) { + break + } else { + preTables.push(table) + } + } + } + } // 为当前表格构建一个虚拟表格 const virtualTable = Array.from( { length: element.trList!.length }, @@ -1794,19 +1837,18 @@ export class Draw { } // 表格经过分页处理-需要处理上下文和选区 if (element.pagingId) { - const positionContext = this.position.getPositionContext() if ( positionContext.isTable && positionContext.tableId === element.id ) { - const trIndex = element.trList!.findIndex( - r => - r.pagingOriginId === positionContext.trId || - r.id === positionContext.trId - ) - if (~trIndex) { + let positionContextFixed = false + for ( + let trIndex = 0; + trIndex < element.trList!.length; + trIndex++ + ) { const tr = element.trList![trIndex] - const tdIndex = tr.tdList!.findIndex( + const tdIndex = tr.tdList.findIndex( d => d.pagingOriginId === positionContext.tdId || d.id === positionContext.tdId @@ -1821,14 +1863,20 @@ export class Draw { positionContext.trId = tr.id positionContext.tdId = td.id this.range.setRange(curIndex, curIndex) + positionContextFixed = true + break } else { - positionContext.tableId = elementList[i + 1].id curIndex -= td.value.length } + } else if (td.id === positionContext.tdId) { + positionContextFixed = true } } - } else { + } + if (!positionContextFixed) { positionContext.tableId = elementList[i + 1].id + } else { + this.position.setPositionContext(positionContext) } } } From f0fdede395a7a3a1e15185620a107f11143defc3 Mon Sep 17 00:00:00 2001 From: yulei Date: Fri, 6 Sep 2024 11:37:51 +0800 Subject: [PATCH 17/21] =?UTF-8?q?fix:=20=E4=BF=AE=E5=A4=8D=E4=BD=8D?= =?UTF-8?q?=E7=BD=AE=E4=B8=8A=E4=B8=8B=E6=96=87=E5=9C=A8=E8=A1=A8=E6=A0=BC?= =?UTF-8?q?=E4=B8=AD=E6=97=B6=E5=88=86=E9=A1=B5=E6=A8=A1=E5=BC=8F=E5=88=87?= =?UTF-8?q?=E6=8D=A2=E4=B8=BA=E8=BF=9E=E9=A1=B5=E6=A8=A1=E5=BC=8F=E6=8A=A5?= =?UTF-8?q?=E9=94=99?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/editor/core/draw/Draw.ts | 29 +++++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) diff --git a/src/editor/core/draw/Draw.ts b/src/editor/core/draw/Draw.ts index 3c6b2bb51..3a867c2cd 100644 --- a/src/editor/core/draw/Draw.ts +++ b/src/editor/core/draw/Draw.ts @@ -1880,6 +1880,35 @@ export class Draw { } } } + } else { + // 连页模式下,修正位置上下文和选区(因为先前合并表格时只修正了部分上下文) + if ( + element.pagingId && + positionContext.isTable && + positionContext.tableId === element.id + ) { + outer: for ( + let trIndex = 0; + trIndex < element.trList!.length; + trIndex++ + ) { + const tr = element.trList![trIndex] + for (let tdIndex = 0; tdIndex < tr.tdList.length; tdIndex++) { + const td = tr.tdList[tdIndex] + if (td.id === positionContext.tdId) { + positionContext.index = i + positionContext.tdIndex = tdIndex + positionContext.trId = tr.id + positionContext.trIndex = trIndex + this.position.setPositionContext(positionContext) + if (curIndex !== undefined && curIndex > -1) { + this.range.setRange(curIndex, curIndex) + } + break outer + } + } + } + } } } else if (element.type === ElementType.SEPARATOR) { const { From 2fcbbe08695484f352ba0a3d7c6110d65566ee24 Mon Sep 17 00:00:00 2001 From: yulei Date: Fri, 20 Sep 2024 10:53:00 +0800 Subject: [PATCH 18/21] =?UTF-8?q?feat:=20=E4=B8=8A=E6=96=B9=E6=8F=92?= =?UTF-8?q?=E5=85=A5=E6=96=B0=E8=A1=8C=E5=8A=9F=E8=83=BD=E6=94=AF=E6=8C=81?= =?UTF-8?q?=E8=A1=A8=E6=A0=BC=E8=B7=A8=E9=A1=B5=E5=9C=BA=E6=99=AF?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/editor/core/draw/Draw.ts | 44 ++++++++---- .../core/draw/particle/table/TableOperate.ts | 71 +++++++++++++------ 2 files changed, 81 insertions(+), 34 deletions(-) diff --git a/src/editor/core/draw/Draw.ts b/src/editor/core/draw/Draw.ts index 378c889c0..be449864c 100644 --- a/src/editor/core/draw/Draw.ts +++ b/src/editor/core/draw/Draw.ts @@ -1339,39 +1339,54 @@ export class Draw { // 查看后续表格是否属于同一个源表格-存在即合并 if (element.pagingId) { // TODO: 优化:一旦修正了上下文,后续表格无需再做上下文相关判断 + // 位置上下文中的信息是表格拆分后记录的,这里需要将其先修正为表格合并后的上下文,后续拆分表格时基于此再次修正位置上下文 if (positionContext.isTable) { - // 如果位置上下文在当前表格或其拆分出的子表格中,则修正上下文(仅修正了tdId和tableId,后续拆分表格时会以此进行二次修正) + // 如果位置上下文在当前表格或其拆分出的子表格中,则修正上下文(仅修正了tdId和tableId,后续拆分表格时会基于此进行二次修正) const preTables: IElement[] = [] - for (let index = i; index < elementList.length; index++) { + outer: for (let index = i; index < elementList.length; index++) { const table = elementList[index] if (table.pagingId !== element.pagingId) { break } else { let positionContextFixed = false - table.trList?.forEach(tr => - tr.tdList.forEach(td => { + for (let r = 0; r < table.trList!.length; r++) { + for (let d = 0; d < table.trList![r].tdList.length; d++) { + const td = table.trList![r].tdList[d] + // 此时位置上下文中的tdId是拆分后的tdId,将其修正为原始td的id if (td.id === positionContext.tdId) { positionContext.tdId = td.pagingOriginId || td.id positionContext.tableId = element.id - if (curIndex !== undefined && curIndex > -1) { + if ( + td.pagingOriginId && // 位置上下文指向的td是跨页拆分出来的时才需要修正curIndex + curIndex !== undefined && + curIndex > -1 + ) { + // 找到同源表格中与位置上下文td同源的单元格,根据其内容长度修正curIndex while (preTables.length > 0) { const preTable = preTables.pop() - preTable?.trList?.forEach(preTr => - preTr.tdList.forEach(preTd => { + preTable!.trList!.forEach(preTr => { + for ( + let preTdIndex = 0; + preTdIndex < preTr.tdList.length; + preTdIndex++ + ) { + const preTd = preTr.tdList[preTdIndex] if ( preTd.pagingOriginId === td.pagingOriginId || preTd.id === td.pagingOriginId ) { curIndex! += preTd.value.length + break } - }) - ) + } + }) } } positionContextFixed = true + break outer } - }) - ) + } + } if (positionContextFixed) { break } else { @@ -1668,8 +1683,11 @@ export class Draw { if (splitTd) { cloneTr[tdIndex] = deepClone(splitTd) // 如果tr可拆分,根据截断位置,将td中的内容拆分到新行中 - // 如果tr不可拆分,但当前位置td是跨行单元格,同样需要拆分 - if (allowSplitTr || splitTd.rowspan > 1) { + // 如果tr不可拆分,但目标td不在当前行且是跨行单元格,同样需要拆分td内容 + if ( + allowSplitTr || + (splitTd.rowspan > 1 && !hasTdAtCurIndex) + ) { cloneTr[tdIndex]!.pagingOriginId = splitTd.pagingOriginId || splitTd.id cloneTr[tdIndex]!.id = getUUID() diff --git a/src/editor/core/draw/particle/table/TableOperate.ts b/src/editor/core/draw/particle/table/TableOperate.ts index 5baaba969..867de1430 100644 --- a/src/editor/core/draw/particle/table/TableOperate.ts +++ b/src/editor/core/draw/particle/table/TableOperate.ts @@ -100,11 +100,28 @@ export class TableOperate { public insertTableTopRow() { const positionContext = this.position.getPositionContext() if (!positionContext.isTable) return - const { index, trIndex, tableId } = positionContext + let { index, trIndex, tableId } = positionContext const originalElementList = this.draw.getOriginalElementList() - const element = originalElementList[index!] - const curTrList = element.trList! - const curTr = curTrList[trIndex!] + let element = originalElementList[index!] + let curTrList = element.trList! + let curTr = curTrList[trIndex!] + // 如果位置上下文指向的行是跨页拆分出来的,找到此行在之前页中对应的真实起始行信息 + if (curTr.pagingOriginId) { + outer: for (let i = index! - 1; i >= 0; i--) { + const preTrList = originalElementList[i].trList! + for (let r = preTrList.length - 1; r >= 0; r--) { + if (!preTrList[r].pagingOriginId) { + element = originalElementList[i] + curTrList = element.trList! + curTr = preTrList[r] + index = i + trIndex = r + tableId = element.id + break outer + } + } + } + } // 之前跨行的增加跨行数 if (curTr.tdList.length < element.colgroup!.length) { const curTrNo = curTr.tdList[0].rowIndex! @@ -127,30 +144,42 @@ export class TableOperate { } for (let t = 0; t < curTr.tdList.length; t++) { const curTd = curTr.tdList[t] - const newTdId = getUUID() - newTr.tdList.push({ - id: newTdId, - rowspan: 1, - colspan: curTd.colspan, - value: [ - { - value: ZERO, - size: 16, - tableId, - trId: newTrId, - tdId: newTdId - } - ] - }) + // 如果当前单元格是跨页拆分出来的,将其挪到新行中,并增加跨行数 + if (curTd.pagingOriginId) { + newTr.tdList.push({ + ...curTd, + rowspan: curTd.rowspan + 1, + value: curTd.value.map(v => ({ ...v, trId: newTrId })) + }) + } else { + const newTdId = getUUID() + newTr.tdList.push({ + id: newTdId, + rowspan: 1, + colspan: curTd.colspan, + value: [ + { + value: ZERO, + size: 16, + tableId, + trId: newTrId, + tdId: newTdId + } + ] + }) + } } + // 移除被挪到新行中的跨页拆分单元格 + curTr.tdList = curTr.tdList.filter(td => !td.pagingOriginId) curTrList.splice(trIndex!, 0, newTr) // 重新设置上下文 + const tdIndex = newTr.tdList.findIndex(td => !td.pagingOriginId) this.position.setPositionContext({ isTable: true, index, trIndex, - tdIndex: 0, - tdId: newTr.tdList[0].id, + tdIndex, + tdId: newTr.tdList[tdIndex].id, trId: newTr.id, tableId }) From 73a1df546f0508e369dbcb765198429924fdba17 Mon Sep 17 00:00:00 2001 From: yulei Date: Mon, 23 Sep 2024 17:22:58 +0800 Subject: [PATCH 19/21] =?UTF-8?q?feat:=20=E4=B8=8B=E6=96=B9=E6=8F=92?= =?UTF-8?q?=E5=85=A5=E6=96=B0=E8=A1=8C=E5=8A=9F=E8=83=BD=E6=94=AF=E6=8C=81?= =?UTF-8?q?=E8=A1=A8=E6=A0=BC=E8=B7=A8=E9=A1=B5=E5=9C=BA=E6=99=AF?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../core/draw/particle/table/TableOperate.ts | 91 +++++++++++++++---- 1 file changed, 72 insertions(+), 19 deletions(-) diff --git a/src/editor/core/draw/particle/table/TableOperate.ts b/src/editor/core/draw/particle/table/TableOperate.ts index 867de1430..c2bf0b73e 100644 --- a/src/editor/core/draw/particle/table/TableOperate.ts +++ b/src/editor/core/draw/particle/table/TableOperate.ts @@ -192,13 +192,42 @@ export class TableOperate { public insertTableBottomRow() { const positionContext = this.position.getPositionContext() if (!positionContext.isTable) return - const { index, trIndex, tableId } = positionContext + let { index, trIndex, tableId } = positionContext const originalElementList = this.draw.getOriginalElementList() const element = originalElementList[index!] - const curTrList = element.trList! - const curTr = curTrList[trIndex!] - const anchorTr = - curTrList.length - 1 === trIndex ? curTr : curTrList[trIndex! + 1] + let curTrList = element.trList! + let curTr = curTrList[trIndex!] + let nextElementIndex = index! + 1 + let nextElement = originalElementList[nextElementIndex] + let anchorTr = + curTrList.length - 1 === trIndex + ? nextElement && + nextElement.pagingId && + nextElement.pagingId === element.pagingId + ? nextElement.trList![0] // 如果当前行是表格最后一行,且下一页的表格是由当前表格拆分出来的,此时修正锚定行为后续表格首行 + : curTr + : curTrList[trIndex! + 1] + // 如果位置上下文指向的行是最后一行,且经历过跨页拆分,此时需找到由此行拆分出来的最后一行的信息 + while ( + curTrList.length - 1 === trIndex && + element.pagingId && + nextElement && + nextElement.pagingId && + nextElement.pagingId === element.pagingId && + nextElement.trList![0].pagingOriginId && + [curTr.id, curTr.pagingOriginId].includes( + nextElement.trList![0].pagingOriginId + ) + ) { + curTrList = nextElement.trList! + trIndex = 0 + curTr = nextElement.trList![trIndex] + index = nextElementIndex + tableId = nextElement.id + anchorTr = + curTrList.length - 1 === trIndex ? curTr : curTrList[trIndex! + 1] + nextElement = originalElementList[++nextElementIndex] + } // 之前/当前行跨行的增加跨行数 if (anchorTr.tdList.length < element.colgroup!.length) { const curTrNo = anchorTr.tdList[0].rowIndex! @@ -219,23 +248,47 @@ export class TableOperate { id: newTrId, tdList: [] } + // 判断锚定行是否是跨页拆分时整行挪到下一页的 + const isAnchorTrMovedToNextPage = + !anchorTr.pagingOriginId && curTrList.every(tr => tr.id !== anchorTr.id) for (let t = 0; t < anchorTr.tdList.length; t++) { const curTd = anchorTr.tdList[t] - const newTdId = getUUID() - newTr.tdList.push({ - id: newTdId, - rowspan: 1, - colspan: curTd.colspan, - value: [ - { - value: ZERO, - size: 16, - tableId, - trId: newTrId, - tdId: newTdId + // 如果锚定行是整行挪到下一页的,那么其中有pagingOriginId的单元格就是跨行单元格,此时找到其原始单元格,增加跨行数 + if (isAnchorTrMovedToNextPage && curTd.pagingOriginId) { + outer: for (let i = index!; i >= 0; i--) { + const element = originalElementList[i] + if (element.trList) { + for (let r = element.trList.length - 1; r >= 0; r--) { + const tr = element.trList[r] + for (let d = tr.tdList.length - 1; d >= 0; d--) { + const td = tr.tdList[d] + if (td.id === curTd.pagingOriginId) { + td.rowspan += 1 + break outer + } + } + } + } else { + break } - ] - }) + } + } else { + const newTdId = getUUID() + newTr.tdList.push({ + id: newTdId, + rowspan: 1, + colspan: curTd.colspan, + value: [ + { + value: ZERO, + size: 16, + tableId, + trId: newTrId, + tdId: newTdId + } + ] + }) + } } curTrList.splice(trIndex! + 1, 0, newTr) // 重新设置上下文 From d2c712f65a7d701873246bd312c68c2a4467b839 Mon Sep 17 00:00:00 2001 From: yulei Date: Thu, 26 Sep 2024 11:31:11 +0800 Subject: [PATCH 20/21] =?UTF-8?q?refactor:=20=E9=87=8D=E6=9E=84=E8=A1=A8?= =?UTF-8?q?=E6=A0=BC=E8=B7=A8=E9=A1=B5=E8=A1=8C=E5=88=97=E6=93=8D=E4=BD=9C?= =?UTF-8?q?=E5=AE=9E=E7=8E=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/editor/core/draw/Draw.ts | 175 +------- .../core/draw/particle/table/TableOperate.ts | 420 +++++++++--------- .../core/draw/particle/table/TableParticle.ts | 191 ++++++++ 3 files changed, 417 insertions(+), 369 deletions(-) diff --git a/src/editor/core/draw/Draw.ts b/src/editor/core/draw/Draw.ts index be449864c..23e3f5362 100644 --- a/src/editor/core/draw/Draw.ts +++ b/src/editor/core/draw/Draw.ts @@ -1329,7 +1329,6 @@ export class Draw { } metrics.boundingBoxAscent = 0 } else if (element.type === ElementType.TABLE) { - const positionContext = this.position.getPositionContext() const tdPaddingWidth = tdPadding[1] + tdPadding[3] const tdPaddingHeight = tdPadding[0] + tdPadding[2] const height = this.getHeight() @@ -1337,162 +1336,15 @@ export class Draw { const emptyMainHeight = (height - marginHeight - rowMargin * 2) / scale // 表格分页处理进度:https://github.com/Hufe921/canvas-editor/issues/41 // 查看后续表格是否属于同一个源表格-存在即合并 - if (element.pagingId) { - // TODO: 优化:一旦修正了上下文,后续表格无需再做上下文相关判断 - // 位置上下文中的信息是表格拆分后记录的,这里需要将其先修正为表格合并后的上下文,后续拆分表格时基于此再次修正位置上下文 - if (positionContext.isTable) { - // 如果位置上下文在当前表格或其拆分出的子表格中,则修正上下文(仅修正了tdId和tableId,后续拆分表格时会基于此进行二次修正) - const preTables: IElement[] = [] - outer: for (let index = i; index < elementList.length; index++) { - const table = elementList[index] - if (table.pagingId !== element.pagingId) { - break - } else { - let positionContextFixed = false - for (let r = 0; r < table.trList!.length; r++) { - for (let d = 0; d < table.trList![r].tdList.length; d++) { - const td = table.trList![r].tdList[d] - // 此时位置上下文中的tdId是拆分后的tdId,将其修正为原始td的id - if (td.id === positionContext.tdId) { - positionContext.tdId = td.pagingOriginId || td.id - positionContext.tableId = element.id - if ( - td.pagingOriginId && // 位置上下文指向的td是跨页拆分出来的时才需要修正curIndex - curIndex !== undefined && - curIndex > -1 - ) { - // 找到同源表格中与位置上下文td同源的单元格,根据其内容长度修正curIndex - while (preTables.length > 0) { - const preTable = preTables.pop() - preTable!.trList!.forEach(preTr => { - for ( - let preTdIndex = 0; - preTdIndex < preTr.tdList.length; - preTdIndex++ - ) { - const preTd = preTr.tdList[preTdIndex] - if ( - preTd.pagingOriginId === td.pagingOriginId || - preTd.id === td.pagingOriginId - ) { - curIndex! += preTd.value.length - break - } - } - }) - } - } - positionContextFixed = true - break outer - } - } - } - if (positionContextFixed) { - break - } else { - preTables.push(table) - } - } - } - } - // 为当前表格构建一个虚拟表格 - const virtualTable = Array.from( - { length: element.trList!.length }, - () => new Array(element.colgroup!.length) - ) as Array> - element.trList!.forEach((tr, trIndex) => { - let tdIndex = 0 - tr.tdList.forEach(td => { - while (virtualTable[trIndex][tdIndex] === null) { - tdIndex++ - } - virtualTable[trIndex][tdIndex] = td - for (let i = 1; i < td.rowspan; i++) { - virtualTable[trIndex + i][tdIndex] = null - } - tdIndex += td.colspan - }) + const { curIndex: newIndex, positionContext } = + this.tableParticle.mergeSplittedTable({ + element, + elementList, + index: i, + curIndex, + mergeForward: true }) - let tableIndex = i + 1 - let combineCount = 0 - while (tableIndex < elementList.length) { - const nextElement = elementList[tableIndex] - if (nextElement.pagingId === element.pagingId) { - const nexTrList = nextElement.trList!.filter( - tr => !tr.pagingRepeat - ) - // 判断后续表格第一行是拆分出来的还是从原表格挪到下一页的 - const isNextTrSplit = - element.trList![element.trList!.length - 1].id === - nexTrList[0].pagingOriginId - let tdIndex = 0 - const mergedTds: ITd[] = [] - nexTrList[0].tdList.forEach(td => { - let targetTd - // 如果虚拟表格最后一行对应位置有单元格,则其就为目标单元格,否则向上查找 - if (virtualTable[virtualTable.length - 1][tdIndex]) { - targetTd = virtualTable[virtualTable.length - 1][tdIndex] - } else { - for (let i = virtualTable.length - 2; i >= 0; i--) { - if (virtualTable[i][tdIndex]) { - targetTd = virtualTable[i][tdIndex] - break - } - } - } - if (targetTd) { - if (targetTd.id === td.pagingOriginId) { - targetTd.value.push(...td.value) - if (isNextTrSplit) { - targetTd.rowspan = targetTd.rowspan + td.rowspan - 1 - } else { - targetTd.rowspan = targetTd.rowspan + td.rowspan - mergedTds.push(td) - } - } - tdIndex += targetTd.colspan - } - }) - nexTrList[0].tdList = nexTrList[0].tdList.filter(td => { - const isNotMerged = mergedTds.every( - mergedTd => mergedTd.id !== td.id - ) - delete td.pagingOriginId - return isNotMerged - }) - while (nexTrList.length > 0) { - const lastTr = element.trList![element.trList!.length - 1] - const nextTr = nexTrList.shift()! - if (lastTr.id === nextTr.pagingOriginId) { - lastTr.height += nextTr.pagingOriginHeight || 0 - // 更新id关联 - lastTr.tdList.forEach(td => { - td.value.forEach(v => { - v.tdId = td.id - v.trId = lastTr.id - v.tableId = element.id - }) - }) - } else { - nextTr.height = nextTr.pagingOriginHeight || nextTr.height - element.trList!.push(nextTr) - } - delete nextTr.pagingOriginHeight - delete nextTr.pagingOriginId - } - tableIndex++ - combineCount++ - } else { - break - } - } - if (combineCount) { - elementList.splice(i + 1, combineCount) - } - } - element.pagingIndex = element.pagingIndex ?? 0 - // 计算表格行列 - this.tableParticle.computeRowColInfo(element) + curIndex = newIndex // 计算表格内元素信息 const trList = element.trList! for (let t = 0; t < trList.length; t++) { @@ -1598,13 +1450,14 @@ export class Draw { // 当前剩余高度是否能容下当前表格第一行(可拆分)的高度,排除掉表头类型 const rowMarginHeight = rowMargin * 2 if ( + // 如果当前页已占用的高度 + 表格第一行高度 + 上下行间距 > 纸张高度 curPagePreHeight + element.trList![0].height! * scale + rowMarginHeight > height || - (element.pagingIndex !== 0 && element.trList![0].pagingRepeat) + (element.pagingIndex !== 0 && element.trList![0].pagingRepeat) // 或者当前表格是被拆分出来的拼接子表格(非第一个),且子表格第一行是标题行 ) { - // 无可拆分行则切换至新页 + // 切换至新页 curPagePreHeight = marginHeight } // 表格高度超过页面高度开始截断行 @@ -1683,7 +1536,7 @@ export class Draw { if (splitTd) { cloneTr[tdIndex] = deepClone(splitTd) // 如果tr可拆分,根据截断位置,将td中的内容拆分到新行中 - // 如果tr不可拆分,但目标td不在当前行且是跨行单元格,同样需要拆分td内容 + // 如果tr不可拆分,但要拆分的td不在当前行且是跨行单元格,同样需要拆分 if ( allowSplitTr || (splitTd.rowspan > 1 && !hasTdAtCurIndex) @@ -1766,12 +1619,13 @@ export class Draw { } } }) - // 构造新行 + // 构造新行,更新拆分行的行高 const newTr = deepClone(trList[splitTrIndex]) newTr.tdList = cloneTr.filter(td => td !== undefined) as ITd[] if (allowSplitTr) { newTr.pagingOriginId = newTr.pagingOriginId || newTr.id newTr.id = getUUID() + // 更新被拆分的行和其中单元格的高度 trList[splitTrIndex].height -= splitTrReduceHeight trList[splitTrIndex].tdList.forEach(td => { td.realHeight! -= splitTrReduceHeight @@ -1779,6 +1633,7 @@ export class Draw { }) // 记录拆分出来的新行的原始高度 newTr.pagingOriginHeight = splitTrReduceHeight + // 更新新行高度 newTr.height = Math.max( splitTrReduceHeight + tdPaddingHeight, newTr.minHeight! diff --git a/src/editor/core/draw/particle/table/TableOperate.ts b/src/editor/core/draw/particle/table/TableOperate.ts index c2bf0b73e..824606398 100644 --- a/src/editor/core/draw/particle/table/TableOperate.ts +++ b/src/editor/core/draw/particle/table/TableOperate.ts @@ -16,6 +16,7 @@ import { RangeManager } from '../../../range/RangeManager' import { Draw } from '../../Draw' import { TableParticle } from './TableParticle' import { TableTool } from './TableTool' +import { IPositionContext } from '../../../../interface/Position' export class TableOperate { private draw: Draw @@ -98,30 +99,21 @@ export class TableOperate { } public insertTableTopRow() { - const positionContext = this.position.getPositionContext() - if (!positionContext.isTable) return - let { index, trIndex, tableId } = positionContext + const originalPositionContext = this.position.getPositionContext() + if (!originalPositionContext.isTable) return + const { index: originalIndex } = originalPositionContext const originalElementList = this.draw.getOriginalElementList() - let element = originalElementList[index!] - let curTrList = element.trList! - let curTr = curTrList[trIndex!] - // 如果位置上下文指向的行是跨页拆分出来的,找到此行在之前页中对应的真实起始行信息 - if (curTr.pagingOriginId) { - outer: for (let i = index! - 1; i >= 0; i--) { - const preTrList = originalElementList[i].trList! - for (let r = preTrList.length - 1; r >= 0; r--) { - if (!preTrList[r].pagingOriginId) { - element = originalElementList[i] - curTrList = element.trList! - curTr = preTrList[r] - index = i - trIndex = r - tableId = element.id - break outer - } - } - } - } + const originalElement = originalElementList[originalIndex!] + // 插入新行前先合并跨页表格 + const { element, index, positionContext } = + this.tableParticle.mergeSplittedTable({ + element: originalElement, + index: originalIndex!, + elementList: originalElementList + }) + const { trIndex, tableId } = positionContext! + const curTrList = element.trList! + const curTr = curTrList[trIndex!] // 之前跨行的增加跨行数 if (curTr.tdList.length < element.colgroup!.length) { const curTrNo = curTr.tdList[0].rowIndex! @@ -144,90 +136,56 @@ export class TableOperate { } for (let t = 0; t < curTr.tdList.length; t++) { const curTd = curTr.tdList[t] - // 如果当前单元格是跨页拆分出来的,将其挪到新行中,并增加跨行数 - if (curTd.pagingOriginId) { - newTr.tdList.push({ - ...curTd, - rowspan: curTd.rowspan + 1, - value: curTd.value.map(v => ({ ...v, trId: newTrId })) - }) - } else { - const newTdId = getUUID() - newTr.tdList.push({ - id: newTdId, - rowspan: 1, - colspan: curTd.colspan, - value: [ - { - value: ZERO, - size: 16, - tableId, - trId: newTrId, - tdId: newTdId - } - ] - }) - } + const newTdId = getUUID() + newTr.tdList.push({ + id: newTdId, + rowspan: 1, + colspan: curTd.colspan, + value: [ + { + value: ZERO, + size: 16, + tableId, + trId: newTrId, + tdId: newTdId + } + ] + }) } - // 移除被挪到新行中的跨页拆分单元格 - curTr.tdList = curTr.tdList.filter(td => !td.pagingOriginId) curTrList.splice(trIndex!, 0, newTr) // 重新设置上下文 - const tdIndex = newTr.tdList.findIndex(td => !td.pagingOriginId) this.position.setPositionContext({ isTable: true, index, trIndex, - tdIndex, - tdId: newTr.tdList[tdIndex].id, + tdIndex: 0, + tdId: newTr.tdList[0].id, trId: newTr.id, tableId }) this.range.setRange(0, 0) // 重新渲染 this.draw.render({ curIndex: 0 }) - this.tableTool.render() } public insertTableBottomRow() { - const positionContext = this.position.getPositionContext() - if (!positionContext.isTable) return - let { index, trIndex, tableId } = positionContext + const originalPositionContext = this.position.getPositionContext() + if (!originalPositionContext.isTable) return + const { index: originalIndex } = originalPositionContext const originalElementList = this.draw.getOriginalElementList() - const element = originalElementList[index!] - let curTrList = element.trList! - let curTr = curTrList[trIndex!] - let nextElementIndex = index! + 1 - let nextElement = originalElementList[nextElementIndex] - let anchorTr = - curTrList.length - 1 === trIndex - ? nextElement && - nextElement.pagingId && - nextElement.pagingId === element.pagingId - ? nextElement.trList![0] // 如果当前行是表格最后一行,且下一页的表格是由当前表格拆分出来的,此时修正锚定行为后续表格首行 - : curTr - : curTrList[trIndex! + 1] - // 如果位置上下文指向的行是最后一行,且经历过跨页拆分,此时需找到由此行拆分出来的最后一行的信息 - while ( - curTrList.length - 1 === trIndex && - element.pagingId && - nextElement && - nextElement.pagingId && - nextElement.pagingId === element.pagingId && - nextElement.trList![0].pagingOriginId && - [curTr.id, curTr.pagingOriginId].includes( - nextElement.trList![0].pagingOriginId - ) - ) { - curTrList = nextElement.trList! - trIndex = 0 - curTr = nextElement.trList![trIndex] - index = nextElementIndex - tableId = nextElement.id - anchorTr = - curTrList.length - 1 === trIndex ? curTr : curTrList[trIndex! + 1] - nextElement = originalElementList[++nextElementIndex] - } + const originalElement = originalElementList[originalIndex!] + // 插入新行前先合并跨页表格 + const { element, index, positionContext } = + this.tableParticle.mergeSplittedTable({ + element: originalElement, + index: originalIndex!, + elementList: originalElementList + }) + const { trIndex, tableId } = positionContext! + const curTrList = element.trList! + const curTr = curTrList[trIndex!] + const anchorTr = + curTrList.length - 1 === trIndex ? curTr : curTrList[trIndex! + 1] // 之前/当前行跨行的增加跨行数 if (anchorTr.tdList.length < element.colgroup!.length) { const curTrNo = anchorTr.tdList[0].rowIndex! @@ -248,47 +206,23 @@ export class TableOperate { id: newTrId, tdList: [] } - // 判断锚定行是否是跨页拆分时整行挪到下一页的 - const isAnchorTrMovedToNextPage = - !anchorTr.pagingOriginId && curTrList.every(tr => tr.id !== anchorTr.id) for (let t = 0; t < anchorTr.tdList.length; t++) { const curTd = anchorTr.tdList[t] - // 如果锚定行是整行挪到下一页的,那么其中有pagingOriginId的单元格就是跨行单元格,此时找到其原始单元格,增加跨行数 - if (isAnchorTrMovedToNextPage && curTd.pagingOriginId) { - outer: for (let i = index!; i >= 0; i--) { - const element = originalElementList[i] - if (element.trList) { - for (let r = element.trList.length - 1; r >= 0; r--) { - const tr = element.trList[r] - for (let d = tr.tdList.length - 1; d >= 0; d--) { - const td = tr.tdList[d] - if (td.id === curTd.pagingOriginId) { - td.rowspan += 1 - break outer - } - } - } - } else { - break + const newTdId = getUUID() + newTr.tdList.push({ + id: newTdId, + rowspan: 1, + colspan: curTd.colspan, + value: [ + { + value: ZERO, + size: 16, + tableId, + trId: newTrId, + tdId: newTdId } - } - } else { - const newTdId = getUUID() - newTr.tdList.push({ - id: newTdId, - rowspan: 1, - colspan: curTd.colspan, - value: [ - { - value: ZERO, - size: 16, - tableId, - trId: newTrId, - tdId: newTdId - } - ] - }) - } + ] + }) } curTrList.splice(trIndex! + 1, 0, newTr) // 重新设置上下文 @@ -304,39 +238,67 @@ export class TableOperate { this.range.setRange(0, 0) // 重新渲染 this.draw.render({ curIndex: 0 }) - this.tableTool.render() } public insertTableLeftCol() { - const positionContext = this.position.getPositionContext() - if (!positionContext.isTable) return - const { index, tdIndex, tableId } = positionContext + const originalPositionContext = this.position.getPositionContext() + if (!originalPositionContext.isTable) return + const { index: originalIndex } = originalPositionContext const originalElementList = this.draw.getOriginalElementList() - const element = originalElementList[index!] + const originalElement = originalElementList[originalIndex!] + // 插入新列前先合并跨页表格 + const { element, positionContext } = this.tableParticle.mergeSplittedTable({ + element: originalElement, + index: originalIndex!, + elementList: originalElementList + }) + const { trIndex, tdIndex, tableId } = positionContext const curTrList = element.trList! - const curTdIndex = tdIndex! - // 增加列 - for (let t = 0; t < curTrList.length; t++) { - const tr = curTrList[t] - const tdId = getUUID() - tr.tdList.splice(curTdIndex, 0, { - id: tdId, - rowspan: 1, - colspan: 1, - value: [ - { - value: ZERO, - size: 16, - tableId, - trId: tr.id, - tdId + const curTdColIndex = curTrList[trIndex!].tdList[tdIndex!].colIndex! + let newPositionContext: IPositionContext | null = null + // 逐行添加单元格 + curTrList.forEach((tr, trIndex) => { + for (let d = 0; d < tr.tdList.length; d++) { + const td = tr.tdList[d] + if ( + // 之前跨列的增加跨列数 + td.colIndex! < curTdColIndex && + td.colIndex! + td.colspan > curTdColIndex + ) { + td.colspan += 1 + break + } else if (td.colIndex! >= curTdColIndex) { + const tdId = getUUID() + tr.tdList.splice(d, 0, { + id: tdId, + rowspan: 1, + colspan: 1, + value: [ + { + value: ZERO, + size: 16, + tableId, + trId: tr.id, + tdId + } + ] + }) + if (!newPositionContext) { + newPositionContext = { + ...positionContext, + tdIndex: d, + tdId, + trIndex, + trId: tr.id + } } - ] - }) - } + break + } + } + }) // 重新计算宽度 const colgroup = element.colgroup! - colgroup.splice(curTdIndex, 0, { + colgroup.splice(curTdColIndex, 0, { width: this.options.table.defaultColMinWidth }) const colgroupWidth = colgroup.reduce((pre, cur) => pre + cur.width, 0) @@ -349,51 +311,74 @@ export class TableOperate { } } // 重新设置上下文 - this.position.setPositionContext({ - isTable: true, - index, - trIndex: 0, - tdIndex: curTdIndex, - tdId: curTrList[0].tdList[curTdIndex].id, - trId: curTrList[0].id, - tableId - }) + newPositionContext && this.position.setPositionContext(newPositionContext) this.range.setRange(0, 0) // 重新渲染 this.draw.render({ curIndex: 0 }) - this.tableTool.render() } public insertTableRightCol() { - const positionContext = this.position.getPositionContext() - if (!positionContext.isTable) return - const { index, tdIndex, tableId } = positionContext + const originalPositionContext = this.position.getPositionContext() + if (!originalPositionContext.isTable) return + const { index: originalIndex } = originalPositionContext const originalElementList = this.draw.getOriginalElementList() - const element = originalElementList[index!] + const originalElement = originalElementList[originalIndex!] + // 插入新列前先合并跨页表格 + const { element, positionContext } = this.tableParticle.mergeSplittedTable({ + element: originalElement, + index: originalIndex!, + elementList: originalElementList + }) + const { trIndex, tdIndex, tableId } = positionContext const curTrList = element.trList! - const curTdIndex = tdIndex! + 1 - // 增加列 - for (let t = 0; t < curTrList.length; t++) { - const tr = curTrList[t] - const tdId = getUUID() - tr.tdList.splice(curTdIndex, 0, { - id: tdId, - rowspan: 1, - colspan: 1, - value: [ - { - value: ZERO, - size: 16, - tableId, - trId: tr.id, - tdId + const curTd = curTrList[trIndex!].tdList[tdIndex!] + const newColIndex = curTd.colIndex! + curTd.colspan + let newPositionContext: IPositionContext | null = null + // 逐行添加单元格 + curTrList.forEach((tr, trIndex) => { + for (let d = 0; d < tr.tdList.length; d++) { + const td = tr.tdList[d] + if ( + // 之前跨列的增加跨列数 + td.colIndex! < newColIndex && + td.colIndex! + td.colspan > newColIndex + ) { + td.colspan += 1 + break + } else if (td.colIndex! + td.colspan >= newColIndex) { + const tdId = getUUID() + const newTdIndex = + td.colIndex! + td.colspan === newColIndex ? d + 1 : d + tr.tdList.splice(newTdIndex, 0, { + id: tdId, + rowspan: 1, + colspan: 1, + value: [ + { + value: ZERO, + size: 16, + tableId, + trId: tr.id, + tdId + } + ] + }) + if (!newPositionContext) { + newPositionContext = { + ...positionContext, + tdIndex: newTdIndex, + tdId, + trIndex, + trId: tr.id + } } - ] - }) - } + break + } + } + }) // 重新计算宽度 const colgroup = element.colgroup! - colgroup.splice(curTdIndex, 0, { + colgroup.splice(newColIndex, 0, { width: this.options.table.defaultColMinWidth }) const colgroupWidth = colgroup.reduce((pre, cur) => pre + cur.width, 0) @@ -406,27 +391,25 @@ export class TableOperate { } } // 重新设置上下文 - this.position.setPositionContext({ - isTable: true, - index, - trIndex: 0, - tdIndex: curTdIndex, - tdId: curTrList[0].tdList[curTdIndex].id, - trId: curTrList[0].id, - tableId - }) + newPositionContext && this.position.setPositionContext(newPositionContext) this.range.setRange(0, 0) // 重新渲染 this.draw.render({ curIndex: 0 }) - this.tableTool.render() } public deleteTableRow() { - const positionContext = this.position.getPositionContext() - if (!positionContext.isTable) return - const { index, trIndex, tdIndex } = positionContext + const originalPositionContext = this.position.getPositionContext() + if (!originalPositionContext.isTable) return + const { index: originalIndex } = originalPositionContext const originalElementList = this.draw.getOriginalElementList() - const element = originalElementList[index!] + const originalElement = originalElementList[originalIndex!] + // 删除表格行前先合并跨页表格 + const { element, positionContext } = this.tableParticle.mergeSplittedTable({ + index: originalIndex!, + element: originalElement, + elementList: originalElementList + }) + const { trIndex, tdIndex } = positionContext const trList = element.trList! const curTr = trList[trIndex!] const curTdRowIndex = curTr.tdList[tdIndex!].rowIndex! @@ -450,9 +433,14 @@ export class TableOperate { for (let d = 0; d < curTr.tdList.length; d++) { const td = curTr.tdList[d] if (td.rowspan > 1) { + // 根据列索引,找到被删除的行中的跨行单元格在下一行的对应位置,补上单元格 const tdId = getUUID() const nextTr = trList[trIndex! + 1] - nextTr.tdList.splice(d, 0, { + const nextTdArray: ITd[] = new Array(element.colgroup!.length) + nextTr.tdList.forEach(nextTd => { + nextTdArray[nextTd.colIndex!] = nextTd + }) + nextTdArray[td.colIndex!] = { id: tdId, rowspan: td.rowspan - 1, colspan: td.colspan, @@ -465,7 +453,8 @@ export class TableOperate { tdId } ] - }) + } + nextTr.tdList = nextTdArray.filter(item => item !== undefined) } } // 删除当前行 @@ -483,11 +472,18 @@ export class TableOperate { } public deleteTableCol() { - const positionContext = this.position.getPositionContext() - if (!positionContext.isTable) return - const { index, tdIndex, trIndex } = positionContext + const originalPositionContext = this.position.getPositionContext() + if (!originalPositionContext.isTable) return + const { index: originalIndex } = originalPositionContext const originalElementList = this.draw.getOriginalElementList() - const element = originalElementList[index!] + const originalElement = originalElementList[originalIndex!] + // 删除表格列前先合并跨页表格 + const { element, positionContext } = this.tableParticle.mergeSplittedTable({ + element: originalElement, + index: originalIndex!, + elementList: originalElementList + }) + const { trIndex, tdIndex } = positionContext const curTrList = element.trList! const curTd = curTrList[trIndex!].tdList[tdIndex!] const curColIndex = curTd.colIndex! @@ -528,9 +524,15 @@ export class TableOperate { } public deleteTable() { - const positionContext = this.position.getPositionContext() - if (!positionContext.isTable) return + const originalPositionContext = this.position.getPositionContext() + if (!originalPositionContext.isTable) return const originalElementList = this.draw.getOriginalElementList() + // 删除表格前先合并跨页表格 + const { positionContext } = this.tableParticle.mergeSplittedTable({ + element: originalElementList[originalPositionContext.index!], + index: originalPositionContext.index!, + elementList: originalElementList + }) originalElementList.splice(positionContext.index!, 1) const curIndex = positionContext.index! - 1 this.position.setPositionContext({ diff --git a/src/editor/core/draw/particle/table/TableParticle.ts b/src/editor/core/draw/particle/table/TableParticle.ts index 39d914afb..85a69b8a5 100644 --- a/src/editor/core/draw/particle/table/TableParticle.ts +++ b/src/editor/core/draw/particle/table/TableParticle.ts @@ -17,6 +17,14 @@ interface IDrawTableBorderOption { isDrawFullBorder?: boolean } +interface IMergeSplittedTablePayload { + elementList: IElement[] + element: IElement + index: number + curIndex?: number + mergeForward?: boolean +} + export class TableParticle { private draw: Draw private range: RangeManager @@ -457,4 +465,187 @@ export class TableParticle { this._drawBackgroundColor(ctx, element, startX, startY) this._drawBorder(ctx, element, startX, startY) } + + public mergeSplittedTable(payload: IMergeSplittedTablePayload) { + let { element, index: i, curIndex } = payload + const { elementList, mergeForward = false } = payload + const position = this.draw.getPosition() + const positionContext = position.getPositionContext() + if (element.pagingId) { + // 如果当前表格是拆分出来的,找到起始表格 + if (!mergeForward && element.pagingIndex! > 0) { + i = i - element.pagingIndex! + element = elementList[i] + } + let positionContextChanged = false + // 位置上下文中的信息是表格拆分后记录的,这里需要将其先修正为表格合并后的上下文。渲染前排版拆分表格时将基于此再次修正位置上下文。 + if (positionContext.isTable) { + // 如果位置上下文在当前表格或其拆分出的子表格中,则修正位置上下文(这里未调用setPositionContext是为了避免触发位置上下文改变的事件) + const preTables: IElement[] = [] + outer: for (let index = i; index < elementList.length; index++) { + const table = elementList[index] + if (table.pagingId !== element.pagingId) { + break + } else if (positionContext.tableId === table.id) { + for (let r = 0; r < table.trList!.length; r++) { + for (let d = 0; d < table.trList![r].tdList.length; d++) { + const td = table.trList![r].tdList[d] + // 此时位置上下文中的tdId是拆分后的tdId,将其修正为原始td的id + // 由于后续合并表格可能导致单元格索引、行索引等变化,因此这里只修正了部分位置上下文 + if (td.id === positionContext.tdId) { + positionContext.tdId = td.pagingOriginId || td.id + positionContext.tableId = element.id + positionContext.index = i + positionContextChanged = true + if ( + td.pagingOriginId && // 位置上下文指向的td是跨页拆分出来的时才需要修正curIndex + curIndex !== undefined && + curIndex > -1 + ) { + // 找到同源表格中与位置上下文td同源的单元格,根据其内容长度修正curIndex + while (preTables.length > 0) { + const preTable = preTables.pop() + preTable!.trList!.forEach(preTr => { + for ( + let preTdIndex = 0; + preTdIndex < preTr.tdList.length; + preTdIndex++ + ) { + const preTd = preTr.tdList[preTdIndex] + if ( + preTd.pagingOriginId === td.pagingOriginId || + preTd.id === td.pagingOriginId + ) { + curIndex! += preTd.value.length + break + } + } + }) + } + } + break outer + } + } + } + } else { + preTables.push(table) + } + } + } + // 为当前表格构建一个虚拟表格 + const virtualTable = Array.from( + { length: element.trList!.length }, + () => new Array(element.colgroup!.length) + ) as Array> + element.trList!.forEach((tr, trIndex) => { + let tdIndex = 0 + tr.tdList.forEach(td => { + while (virtualTable[trIndex][tdIndex] === null) { + tdIndex++ + } + virtualTable[trIndex][tdIndex] = td + for (let i = 1; i < td.rowspan; i++) { + virtualTable[trIndex + i][tdIndex] = null + } + tdIndex += td.colspan + }) + }) + // 处理后续表格的合并 + let tableIndex = i + 1 + let combineCount = 0 + while (tableIndex < elementList.length) { + const nextElement = elementList[tableIndex] + if (nextElement.pagingId === element.pagingId) { + const nexTrList = nextElement.trList!.filter(tr => !tr.pagingRepeat) + // 判断后续表格第一行是拆分出来的还是从原表格挪到下一页的 + const isNextTrSplit = + element.trList![element.trList!.length - 1].id === + nexTrList[0].pagingOriginId + // 遍历后续表格首行中的单元格,在虚拟表格中找到其对应单元格 + let tdIndex = 0 + const mergedTds: ITd[] = [] + nexTrList[0].tdList.forEach(td => { + let targetTd + // 如果虚拟表格最后一行对应位置有单元格,则其就为目标单元格,否则向上查找 + if (virtualTable[virtualTable.length - 1][tdIndex]) { + targetTd = virtualTable[virtualTable.length - 1][tdIndex] + } else { + for (let i = virtualTable.length - 2; i >= 0; i--) { + if (virtualTable[i][tdIndex]) { + targetTd = virtualTable[i][tdIndex] + break + } + } + } + if (targetTd) { + if (targetTd.id === td.pagingOriginId) { + targetTd.value.push(...td.value) + if (isNextTrSplit) { + targetTd.rowspan = targetTd.rowspan + td.rowspan - 1 + } else { + targetTd.rowspan = targetTd.rowspan + td.rowspan + mergedTds.push(td) + } + } + tdIndex += targetTd.colspan + } + }) + nexTrList[0].tdList = nexTrList[0].tdList.filter(td => { + const isNotMerged = mergedTds.every( + mergedTd => mergedTd.id !== td.id + ) + delete td.pagingOriginId + return isNotMerged + }) + // 更新行高,逐行合并 + while (nexTrList.length > 0) { + const lastTr = element.trList![element.trList!.length - 1] + const nextTr = nexTrList.shift()! + if (lastTr.id === nextTr.pagingOriginId) { + lastTr.height += nextTr.pagingOriginHeight || 0 + // 更新合并内容的id关联关系 + lastTr.tdList.forEach(td => { + td.value.forEach(v => { + v.tdId = td.id + v.trId = lastTr.id + v.tableId = element.id + }) + }) + } else { + nextTr.height = nextTr.pagingOriginHeight || nextTr.height + element.trList!.push(nextTr) + } + delete nextTr.pagingOriginHeight + delete nextTr.pagingOriginId + } + tableIndex++ + combineCount++ + } else { + break + } + } + if (combineCount) { + elementList.splice(i + 1, combineCount) + } + // 合并表格后继续修正位置上下文 + if (positionContextChanged) { + outer: for (let r = 0; r < element.trList!.length; r++) { + const tr = element.trList![r] + for (let d = 0; d < tr.tdList.length; d++) { + const td = tr.tdList[d] + if (td.id === positionContext.tdId) { + positionContext.tdIndex = d + positionContext.trIndex = r + positionContext.trId = tr.id + break outer + } + } + } + } + element.pagingIndex = element.pagingIndex ?? 0 + // 计算表格行列(单元格行列索引、高宽、单元格相对于表格的坐标等信息) + this.computeRowColInfo(element) + } + return { curIndex, element, index: i, positionContext } + } } From 3b6ed88ed66d2ea740ef0125e6517c6f744e56ce Mon Sep 17 00:00:00 2001 From: yulei Date: Wed, 9 Oct 2024 15:27:30 +0800 Subject: [PATCH 21/21] =?UTF-8?q?fix:=20=E8=A1=A8=E6=A0=BC=E6=8E=92?= =?UTF-8?q?=E7=89=88=E5=89=8D=E9=87=8D=E6=96=B0=E8=AE=A1=E7=AE=97=E8=A1=8C?= =?UTF-8?q?=E5=88=97=E7=B4=A2=E5=BC=95=EF=BC=8C=E4=BB=A5=E8=A7=A3=E5=86=B3?= =?UTF-8?q?=E6=89=A7=E8=A1=8C=E6=8F=92=E5=85=A5=E8=A1=8C=E7=AD=89=E6=93=8D?= =?UTF-8?q?=E4=BD=9C=E5=90=8E=E5=8F=AF=E8=83=BD=E5=87=BA=E7=8E=B0=E7=9A=84?= =?UTF-8?q?=E6=B8=B2=E6=9F=93=E5=BC=82=E5=B8=B8=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/editor/core/draw/Draw.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/editor/core/draw/Draw.ts b/src/editor/core/draw/Draw.ts index 4f5e2874d..b31144d0a 100644 --- a/src/editor/core/draw/Draw.ts +++ b/src/editor/core/draw/Draw.ts @@ -1345,6 +1345,8 @@ export class Draw { mergeForward: true }) curIndex = newIndex + // 计算更新单元格行列索引等信息 + this.tableParticle.computeRowColInfo(element) // 计算表格内元素信息 const trList = element.trList! for (let t = 0; t < trList.length; t++) {