From 22c9b10c33f5fdc58899a09b5bbea533be68c6d1 Mon Sep 17 00:00:00 2001 From: zhouxinyu Date: Tue, 17 Dec 2024 20:07:54 +0800 Subject: [PATCH] feat: support multi break-line --- packages/vrender-core/src/graphic/richtext.ts | 23 +++++++-- .../src/plugins/builtin-plugin/edit-module.ts | 49 ++++++++++++------- .../builtin-plugin/richtext-edit-plugin.ts | 6 +-- .../browser/src/pages/richtext-editor.ts | 2 +- 4 files changed, 53 insertions(+), 27 deletions(-) diff --git a/packages/vrender-core/src/graphic/richtext.ts b/packages/vrender-core/src/graphic/richtext.ts index 21b68213c..750ba4cbf 100644 --- a/packages/vrender-core/src/graphic/richtext.ts +++ b/packages/vrender-core/src/graphic/richtext.ts @@ -478,6 +478,9 @@ export class RichText extends Graphic implements IRic this._frameCache?.icons ); const wrapper = new Wrapper(frame); + // @since 0.22.0 + // 如果可编辑的话,则支持多换行符 + wrapper.newLine = editable; if (disableAutoWrapLine) { let lineCount = 0; let skip = false; @@ -508,11 +511,11 @@ export class RichText extends Graphic implements IRic } } else { for (let i = 0; i < paragraphs.length; i++) { - if (i === paragraphs.length - 1) { - wrapper.newLine = true; - } + // if (i === paragraphs.length - 1) { + // wrapper.newLine = true; + // } wrapper.deal(paragraphs[i]); - wrapper.newLine = false; + // wrapper.newLine = false; } } @@ -540,6 +543,18 @@ export class RichText extends Graphic implements IRic }); } + // 处理空行 + if (editable) { + frame.lines.forEach(item => { + const lastParagraphs = item.paragraphs; + item.paragraphs = item.paragraphs.filter(p => (p as any).text !== ''); + if (item.paragraphs.length === 0 && lastParagraphs.length) { + (lastParagraphs[0] as any).text = '\n'; + item.paragraphs.push(lastParagraphs[0]); + } + }); + } + this._frameCache = frame; // this.bindIconEvent(); diff --git a/packages/vrender-core/src/plugins/builtin-plugin/edit-module.ts b/packages/vrender-core/src/plugins/builtin-plugin/edit-module.ts index 8ff772330..95dfd2f7c 100644 --- a/packages/vrender-core/src/plugins/builtin-plugin/edit-module.ts +++ b/packages/vrender-core/src/plugins/builtin-plugin/edit-module.ts @@ -1,13 +1,6 @@ import { application } from '../../application'; import type { IRichText, IRichTextCharacter, IRichTextParagraphCharacter } from '../../interface'; -let isMac = false; -try { - isMac = navigator.platform.toUpperCase().indexOf('MAC') >= 0; -} catch (err) { - // ignore -} - function getMaxConfigIndexIgnoreLinebreak(textConfig: IRichTextCharacter[]) { let idx = 0; for (let i = 0; i < textConfig.length; i++) { @@ -20,22 +13,27 @@ function getMaxConfigIndexIgnoreLinebreak(textConfig: IRichTextCharacter[]) { } /** - * 找到cursorIndex所在的textConfig的位置,忽略换行符 + * 找到cursorIndex所在的textConfig的位置,忽略单个换行符,连续换行符的时候只忽略第一个 * @param textConfig * @param cursorIndex * @returns */ -export function findConfigIndex(textConfig: IRichTextCharacter[], cursorIndex: number): number { +export function findConfigIndexByCursorIdx(textConfig: IRichTextCharacter[], cursorIndex: number): number { let index = 0; // 小于0是在最前面了 if (cursorIndex < 0) { return -1; } let idx = Math.round(cursorIndex); + let lastLineBreak = true; for (index = 0; index < textConfig.length; index++) { const c = textConfig[index] as IRichTextParagraphCharacter; - if (c.text !== '\n') { + if (c.text === '\n') { + idx -= Number(lastLineBreak); + lastLineBreak = true; + } else { idx--; + lastLineBreak = false; } if (idx < 0) { break; @@ -49,16 +47,29 @@ export function findConfigIndex(textConfig: IRichTextCharacter[], cursorIndex: n return Math.min(index, textConfig.length - 1); } -export function textConfigIgnoreLinebreakIdxToCursorIdx(textConfig: IRichTextCharacter[], cursorIndex: number): number { +export function findCursorIdxByConfigIndex(textConfig: IRichTextCharacter[], configIndex: number): number { let index = 0; - for (let i = 0; i < cursorIndex; i++) { + // 仅有一个\n,那不算 + // 如果有连续的\n,那就少算一个 + let lastLineBreak = true; + let delta = 0; + for (let i = 0; i <= configIndex + delta; i++) { const c = textConfig[i] as IRichTextParagraphCharacter; - if (c.text !== '\n') { + if (c.text === '\n') { + index += Number(lastLineBreak); + // 第一个换行符当做不存在 + delta += 1 - Number(lastLineBreak); + lastLineBreak = true; + } else { index++; + lastLineBreak = false; + // 回归 + delta = 0; } } + index = Math.max(index - 1, 0); // 正常Cursor是放在右边的,但如果回退到换行符了,那就放在左侧 - if ((textConfig[cursorIndex] as any)?.text === '\n') { + if ((textConfig[configIndex] as any)?.text === '\n') { index -= 0.1; } else { index += 0.1; @@ -124,7 +135,7 @@ export class EditModule { const config = textConfig[0]; textConfig.unshift({ fill: 'black', ...config, text: '' }); } else { - const cursorIndex = findConfigIndex(textConfig, this.cursorIndex); + const cursorIndex = findConfigIndexByCursorIdx(textConfig, this.cursorIndex); const lastConfig = textConfig[cursorIndex]; textConfig.splice(cursorIndex + 1, 0, { ...lastConfig, text: '' }); } @@ -135,7 +146,7 @@ export class EditModule { this.isComposing = false; // 拆分上一次的内容 const { textConfig = [] } = this.currRt.attribute; - const configIdx = findConfigIndex(textConfig, this.cursorIndex + 1); + const configIdx = findConfigIndexByCursorIdx(textConfig, this.cursorIndex + 1); const lastConfig = textConfig[configIdx]; textConfig.splice(configIdx, 1); @@ -187,9 +198,9 @@ export class EditModule { // 转换成基于textConfig的 // let delta = 0; - startIdx = findConfigIndex(textConfig, startIdx); + startIdx = findConfigIndexByCursorIdx(textConfig, startIdx); // delta = this.selectionStartCursorIdx - startIdx; - endIdx = findConfigIndex(textConfig, endIdx); + endIdx = findConfigIndexByCursorIdx(textConfig, endIdx); // console.log(startIdx, delta, endIdx); let idxDelta = 0; @@ -238,7 +249,7 @@ export class EditModule { } this.currRt.setAttributes({ textConfig }); - this.cursorIndex = textConfigIgnoreLinebreakIdxToCursorIdx(textConfig, startIdx); + this.cursorIndex = findCursorIdxByConfigIndex(textConfig, startIdx); this.cursorIndex += idxDelta; if (!this.isComposing) { diff --git a/packages/vrender-core/src/plugins/builtin-plugin/richtext-edit-plugin.ts b/packages/vrender-core/src/plugins/builtin-plugin/richtext-edit-plugin.ts index cb56d80b4..681e630f5 100644 --- a/packages/vrender-core/src/plugins/builtin-plugin/richtext-edit-plugin.ts +++ b/packages/vrender-core/src/plugins/builtin-plugin/richtext-edit-plugin.ts @@ -19,7 +19,7 @@ import type { ITimeline } from '../../interface'; import { Animate, DefaultTicker, DefaultTimeline } from '../../animate'; -import { EditModule, findConfigIndex } from './edit-module'; +import { EditModule, findConfigIndexByCursorIdx } from './edit-module'; import { application } from '../../application'; import { getWordStartEndIdx } from '../../graphic/richtext/utils'; // import { testLetter, testLetter2 } from '../../graphic/richtext/utils'; @@ -48,8 +48,8 @@ class Selection { return ''; } const config = this.rt.attribute.textConfig as any; - const startIdx = findConfigIndex(config, Math.ceil(minCursorIdx)); - const endIdx = findConfigIndex(config, Math.floor(maxCursorIdx)); + const startIdx = findConfigIndexByCursorIdx(config, Math.ceil(minCursorIdx)); + const endIdx = findConfigIndexByCursorIdx(config, Math.floor(maxCursorIdx)); let str = ''; for (let i = startIdx; i <= endIdx; i++) { str += config[i].text; diff --git a/packages/vrender/__tests__/browser/src/pages/richtext-editor.ts b/packages/vrender/__tests__/browser/src/pages/richtext-editor.ts index 05e23ac77..39abbfe4a 100644 --- a/packages/vrender/__tests__/browser/src/pages/richtext-editor.ts +++ b/packages/vrender/__tests__/browser/src/pages/richtext-editor.ts @@ -39,7 +39,7 @@ export const page = () => { // "textAlign": "center", textConfig: [ { - text: 'and this is our world, \nthat we call life', + text: 'and', fontSize: 16, lineHeight: 26, textAlign: 'center',