From da89c2e83717717028633c0380ffed5e7393df1e Mon Sep 17 00:00:00 2001 From: Marcel Danilewicz Date: Fri, 12 Jul 2024 13:33:23 +0200 Subject: [PATCH 1/8] Add wrapWord option, no implementation yet --- src/core/CoreTextNode.ts | 11 +++++++++++ src/core/Stage.ts | 1 + .../renderers/SdfTextRenderer/SdfTextRenderer.ts | 2 ++ .../renderers/SdfTextRenderer/internal/layoutText.ts | 1 + src/core/text-rendering/renderers/TextRenderer.ts | 9 +++++++++ 5 files changed, 24 insertions(+) diff --git a/src/core/CoreTextNode.ts b/src/core/CoreTextNode.ts index c2507833..d22121c7 100644 --- a/src/core/CoreTextNode.ts +++ b/src/core/CoreTextNode.ts @@ -104,6 +104,7 @@ export class CoreTextNode extends CoreNode implements CoreTextNodeProps { textBaseline: props.textBaseline, verticalAlign: props.verticalAlign, overflowSuffix: props.overflowSuffix, + wrapWord: props.wrapWord, }); this.textRenderer = resolvedTextRenderer; this.trState = textRendererState; @@ -346,6 +347,16 @@ export class CoreTextNode extends CoreNode implements CoreTextNodeProps { } } + get wrapWord(): CoreTextNodeProps['wrapWord'] { + return this.trState.props.wrapWord; + } + + set wrapWord(value: CoreTextNodeProps['wrapWord']) { + if (this.textRenderer.set.wrapWord) { + this.textRenderer.set.wrapWord(this.trState, value); + } + } + get debug(): CoreTextNodeProps['debug'] { return this.trState.props.debug; } diff --git a/src/core/Stage.ts b/src/core/Stage.ts index 14358503..1ccc4c2c 100644 --- a/src/core/Stage.ts +++ b/src/core/Stage.ts @@ -496,6 +496,7 @@ export class Stage { overflowSuffix: props.overflowSuffix ?? '...', debug: props.debug ?? {}, shaderProps: null, + wrapWord: props.wrapWord ?? false, }; return new CoreTextNode(this, resolvedProps); diff --git a/src/core/text-rendering/renderers/SdfTextRenderer/SdfTextRenderer.ts b/src/core/text-rendering/renderers/SdfTextRenderer/SdfTextRenderer.ts index 382279b6..55baf527 100644 --- a/src/core/text-rendering/renderers/SdfTextRenderer/SdfTextRenderer.ts +++ b/src/core/text-rendering/renderers/SdfTextRenderer/SdfTextRenderer.ts @@ -431,6 +431,7 @@ export class SdfTextRenderer extends TextRenderer { scrollable, overflowSuffix, maxLines, + wrapWord, } = state.props; // scrollY only has an effect when contain === 'both' and scrollable === true @@ -569,6 +570,7 @@ export class SdfTextRenderer extends TextRenderer { scrollable, overflowSuffix, maxLines, + wrapWord, ); state.bufferUploaded = false; diff --git a/src/core/text-rendering/renderers/SdfTextRenderer/internal/layoutText.ts b/src/core/text-rendering/renderers/SdfTextRenderer/internal/layoutText.ts index a8e1cc06..ebb9ed34 100644 --- a/src/core/text-rendering/renderers/SdfTextRenderer/internal/layoutText.ts +++ b/src/core/text-rendering/renderers/SdfTextRenderer/internal/layoutText.ts @@ -56,6 +56,7 @@ export function layoutText( scrollable: TrProps['scrollable'], overflowSuffix: TrProps['overflowSuffix'], maxLines: TrProps['maxLines'], + wrapWord: TrProps['wrapWord'], ): { bufferNumFloats: number; bufferNumQuads: number; diff --git a/src/core/text-rendering/renderers/TextRenderer.ts b/src/core/text-rendering/renderers/TextRenderer.ts index cc655a8f..22b1733d 100644 --- a/src/core/text-rendering/renderers/TextRenderer.ts +++ b/src/core/text-rendering/renderers/TextRenderer.ts @@ -221,6 +221,12 @@ export interface TrProps extends TrFontProps { * @default 'none' */ contain: 'none' | 'width' | 'both'; + /** + * Word wrap option + * + * @default false + */ + wrapWord: boolean; width: number; height: number; /** @@ -370,6 +376,9 @@ const trPropSetterDefaults: TrPropSetters = { contain: (state, value) => { state.props.contain = value; }, + wrapWord: (state, value) => { + state.props.wrapWord = value; + }, offsetY: (state, value) => { state.props.offsetY = value; }, From 98b45c92c192ea84f459113ea01c3a5b90c895a6 Mon Sep 17 00:00:00 2001 From: Marcel Danilewicz Date: Wed, 17 Jul 2024 13:31:08 +0200 Subject: [PATCH 2/8] Implement wrapWord for sdf text renderer --- .../SdfTextRenderer/internal/layoutText.ts | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/src/core/text-rendering/renderers/SdfTextRenderer/internal/layoutText.ts b/src/core/text-rendering/renderers/SdfTextRenderer/internal/layoutText.ts index ebb9ed34..37e4decf 100644 --- a/src/core/text-rendering/renderers/SdfTextRenderer/internal/layoutText.ts +++ b/src/core/text-rendering/renderers/SdfTextRenderer/internal/layoutText.ts @@ -293,6 +293,24 @@ export function layoutText( maxX = Math.max(maxX, quadX + glyph.width); curX += glyph.xAdvance; } + if ( + wrapWord && + charEndX + glyph.width >= lineVertexW && + contain != 'none' + ) { + if (curLineBufferStart !== -1 && lineIsWithinWindow) { + bufferLineInfos.push({ + bufferStart: curLineBufferStart, + bufferEnd: bufferOffset, + }); + curLineBufferStart = -1; + } + curX = 0; + curY += vertexLineHeight; + curLineIndex++; + lastWord.codepointIndex = -1; + xStartLastWordBoundary = 0; + } } else { // Unmapped character From 8a939ad882f12af0980b178b0f281640a1a2b44f Mon Sep 17 00:00:00 2001 From: Marcel Danilewicz Date: Fri, 26 Jul 2024 13:59:13 +0200 Subject: [PATCH 3/8] Change wrapWord to string literal --- src/core/Stage.ts | 2 +- .../renderers/SdfTextRenderer/internal/layoutText.ts | 2 +- src/core/text-rendering/renderers/TextRenderer.ts | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/core/Stage.ts b/src/core/Stage.ts index 1ccc4c2c..5af553c2 100644 --- a/src/core/Stage.ts +++ b/src/core/Stage.ts @@ -496,7 +496,7 @@ export class Stage { overflowSuffix: props.overflowSuffix ?? '...', debug: props.debug ?? {}, shaderProps: null, - wrapWord: props.wrapWord ?? false, + wrapWord: props.wrapWord ?? 'normal', }; return new CoreTextNode(this, resolvedProps); diff --git a/src/core/text-rendering/renderers/SdfTextRenderer/internal/layoutText.ts b/src/core/text-rendering/renderers/SdfTextRenderer/internal/layoutText.ts index 37e4decf..b209e252 100644 --- a/src/core/text-rendering/renderers/SdfTextRenderer/internal/layoutText.ts +++ b/src/core/text-rendering/renderers/SdfTextRenderer/internal/layoutText.ts @@ -294,7 +294,7 @@ export function layoutText( curX += glyph.xAdvance; } if ( - wrapWord && + wrapWord == 'break' && charEndX + glyph.width >= lineVertexW && contain != 'none' ) { diff --git a/src/core/text-rendering/renderers/TextRenderer.ts b/src/core/text-rendering/renderers/TextRenderer.ts index 22b1733d..f908293a 100644 --- a/src/core/text-rendering/renderers/TextRenderer.ts +++ b/src/core/text-rendering/renderers/TextRenderer.ts @@ -224,9 +224,9 @@ export interface TrProps extends TrFontProps { /** * Word wrap option * - * @default false + * @default normal */ - wrapWord: boolean; + wrapWord: 'normal' | 'break'; width: number; height: number; /** From 4f4768028b2c29c3313ab70e58cb7bdbc1c2f61c Mon Sep 17 00:00:00 2001 From: Marcel Danilewicz Date: Mon, 29 Jul 2024 13:34:18 +0200 Subject: [PATCH 4/8] Implement wrapWord for canvas text renderer --- .../renderers/CanvasTextRenderer.ts | 1 + .../renderers/LightningTextTextureRenderer.ts | 15 +++++++++++++++ 2 files changed, 16 insertions(+) diff --git a/src/core/text-rendering/renderers/CanvasTextRenderer.ts b/src/core/text-rendering/renderers/CanvasTextRenderer.ts index b6c15979..6fffe553 100644 --- a/src/core/text-rendering/renderers/CanvasTextRenderer.ts +++ b/src/core/text-rendering/renderers/CanvasTextRenderer.ts @@ -417,6 +417,7 @@ export class CanvasTextRenderer extends TextRenderer { ].join(' '), textColor: getNormalizedRgbaComponents(state.props.color), offsetY: state.props.offsetY, + wordBreak: state.props.wrapWord == 'break', wordWrap: state.props.contain !== 'none', wordWrapWidth: state.props.contain === 'none' ? undefined : state.props.width, diff --git a/src/core/text-rendering/renderers/LightningTextTextureRenderer.ts b/src/core/text-rendering/renderers/LightningTextTextureRenderer.ts index 27c01d82..ea25e226 100644 --- a/src/core/text-rendering/renderers/LightningTextTextureRenderer.ts +++ b/src/core/text-rendering/renderers/LightningTextTextureRenderer.ts @@ -323,6 +323,7 @@ export class LightningTextTextureRenderer { wordWrapWidth, letterSpacing, textIndent, + this._settings.wordBreak, ); } else { linesInfo = { l: this._settings.text.split(/(?:\r\n|\r|\n)/), n: [] }; @@ -347,6 +348,7 @@ export class LightningTextTextureRenderer { wordWrapWidth - w, letterSpacing, textIndent, + this._settings.wordBreak, ); usedLines[usedLines.length - 1] = `${al.l[0]!}${ this._settings.overflowSuffix @@ -681,6 +683,7 @@ export class LightningTextTextureRenderer { wordWrapWidth: number, letterSpacing: number, indent = 0, + wordBreak: boolean, ) { // Greedy wrapping algorithm that will wrap words as the line grows longer. // than its horizontal bounds. @@ -720,6 +723,18 @@ export class LightningTextTextureRenderer { realNewlines.push(allLines.length); } } + if (wordBreak) { + for (let i = 0; i < allLines.length; i++) { + let newline = ''; + while (this.measureText(allLines[i]!, letterSpacing) > wordWrapWidth) { + newline = allLines[i]!.slice(-1) + newline; + allLines[i] = allLines[i]!.slice(0, -1); + } + if (newline.length > 0) { + allLines.splice(i + 1, 0, newline); + } + } + } return { l: allLines, n: realNewlines }; } From c7f8c9c75513a834ba274378ef9662ac1ac230bc Mon Sep 17 00:00:00 2001 From: Marcel Danilewicz Date: Mon, 29 Jul 2024 14:22:57 +0200 Subject: [PATCH 5/8] Add wrapWord parameter to text-contain example --- examples/tests/text-contain.ts | 21 ++++++++++++++++++--- 1 file changed, 18 insertions(+), 3 deletions(-) diff --git a/examples/tests/text-contain.ts b/examples/tests/text-contain.ts index 5ab83480..eb370d59 100644 --- a/examples/tests/text-contain.ts +++ b/examples/tests/text-contain.ts @@ -54,7 +54,8 @@ export default async function test(settings: ExampleSettings) { fontFamily: 'Ubuntu', textRendererOverride: 'sdf', fontSize: 20, - text: `Lorem ipsum dolor sit e + text: `LoremipsumdolorsiteConsecteturadipiscingelit.Vivamusid. +Lorem ipsum dolor sit e Consectetur adipiscing elit. Vivamus id. Suspendisse sollicitudin posuere felis. Vivamus consectetur ex magna, non mollis.`, @@ -132,6 +133,7 @@ Vivamus consectetur ex magna, non mollis.`, // SDF, contain none text1.textRendererOverride = 'sdf'; text1.contain = 'none'; + text1.wrapWord = 'normal'; text1.width = 0; text1.height = 0; }, @@ -153,11 +155,17 @@ Vivamus consectetur ex magna, non mollis.`, // SDF, contain both (1 pixel larger to show another line) text1.height = 204; }, + () => { + // SDF, contain both (1 pixel larger to show another line), wrap word + text1.contain = 'width'; + text1.wrapWord = 'break'; + }, + () => { // Canvas, contain none text1.textRendererOverride = 'canvas'; text1.contain = 'none'; - text1.width = 0; + (text1.wrapWord = 'normal'), (text1.width = 0); text1.height = 0; }, () => { @@ -180,6 +188,11 @@ Vivamus consectetur ex magna, non mollis.`, // Canvas, contain both (1 pixel larger to show another line) text1.height = 204; }, + () => { + // Canvas, contain both (1 pixel larger to show another line), wrap word + text1.contain = 'width'; + text1.wrapWord = 'break'; + }, ]; /** * Run the next mutation in the list @@ -202,6 +215,7 @@ Vivamus consectetur ex magna, non mollis.`, text1.contain, text1.width, text1.height, + text1.wrapWord, ); indexInfo.text = (i + 1).toString(); textSetDimsInfo.text = `Set size: ${Math.round(text1.width)}x${Math.round( @@ -237,6 +251,7 @@ function makeHeader( contain: string, width: number, height: number, + wrapWord: string, ) { - return `${renderer}, contain = ${contain}`; + return `${renderer}, contain = ${contain}, wrapWord = ${wrapWord}`; } From 856cc7c6b9a6e7627d05d360eceaddf8dd696160 Mon Sep 17 00:00:00 2001 From: Marcel Danilewicz Date: Tue, 30 Jul 2024 12:15:04 +0200 Subject: [PATCH 6/8] Improve breaking algorithm --- .../renderers/LightningTextTextureRenderer.ts | 23 ++++++++----------- 1 file changed, 10 insertions(+), 13 deletions(-) diff --git a/src/core/text-rendering/renderers/LightningTextTextureRenderer.ts b/src/core/text-rendering/renderers/LightningTextTextureRenderer.ts index ea25e226..e3874276 100644 --- a/src/core/text-rendering/renderers/LightningTextTextureRenderer.ts +++ b/src/core/text-rendering/renderers/LightningTextTextureRenderer.ts @@ -699,6 +699,16 @@ export class LightningTextTextureRenderer { const wordWidth = this.measureText(words[j]!, letterSpacing); const wordWidthWithSpace = wordWidth + this.measureText(' ', letterSpacing); + if (wordWidthWithSpace > wordWrapWidth && wordBreak) { + let remainder = ''; + while (this.measureText(words[j]!) > wordWrapWidth) { + remainder = words[j]!.slice(-1) + remainder; + words[j] = words[j]!.slice(0, -1); + } + if (remainder.length > 0) { + words.splice(j + 1, 0, remainder); + } + } if (j === 0 || wordWidthWithSpace > spaceLeft) { // Skip printing the newline if it's the first word of the line that is. // greater than the word wrap width. @@ -723,19 +733,6 @@ export class LightningTextTextureRenderer { realNewlines.push(allLines.length); } } - if (wordBreak) { - for (let i = 0; i < allLines.length; i++) { - let newline = ''; - while (this.measureText(allLines[i]!, letterSpacing) > wordWrapWidth) { - newline = allLines[i]!.slice(-1) + newline; - allLines[i] = allLines[i]!.slice(0, -1); - } - if (newline.length > 0) { - allLines.splice(i + 1, 0, newline); - } - } - } - return { l: allLines, n: realNewlines }; } From 3c98b210df0083ae49de12a520eb100cdea108ac Mon Sep 17 00:00:00 2001 From: Marcel Danilewicz Date: Wed, 31 Jul 2024 12:46:46 +0200 Subject: [PATCH 7/8] Change order of conditions --- .../text-rendering/renderers/LightningTextTextureRenderer.ts | 2 +- .../renderers/SdfTextRenderer/internal/layoutText.ts | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/core/text-rendering/renderers/LightningTextTextureRenderer.ts b/src/core/text-rendering/renderers/LightningTextTextureRenderer.ts index e3874276..4b9f2b3c 100644 --- a/src/core/text-rendering/renderers/LightningTextTextureRenderer.ts +++ b/src/core/text-rendering/renderers/LightningTextTextureRenderer.ts @@ -699,7 +699,7 @@ export class LightningTextTextureRenderer { const wordWidth = this.measureText(words[j]!, letterSpacing); const wordWidthWithSpace = wordWidth + this.measureText(' ', letterSpacing); - if (wordWidthWithSpace > wordWrapWidth && wordBreak) { + if (wordBreak && wordWidthWithSpace > wordWrapWidth) { let remainder = ''; while (this.measureText(words[j]!) > wordWrapWidth) { remainder = words[j]!.slice(-1) + remainder; diff --git a/src/core/text-rendering/renderers/SdfTextRenderer/internal/layoutText.ts b/src/core/text-rendering/renderers/SdfTextRenderer/internal/layoutText.ts index b209e252..ead0f897 100644 --- a/src/core/text-rendering/renderers/SdfTextRenderer/internal/layoutText.ts +++ b/src/core/text-rendering/renderers/SdfTextRenderer/internal/layoutText.ts @@ -295,8 +295,8 @@ export function layoutText( } if ( wrapWord == 'break' && - charEndX + glyph.width >= lineVertexW && - contain != 'none' + contain != 'none' && + charEndX + glyph.width >= lineVertexW ) { if (curLineBufferStart !== -1 && lineIsWithinWindow) { bufferLineInfos.push({ From dab52d231d02dccd1e24c1c1a1028f559b94ec6a Mon Sep 17 00:00:00 2001 From: Marcel Danilewicz Date: Thu, 1 Aug 2024 13:38:49 +0200 Subject: [PATCH 8/8] Align line breaking algorithm for wrapWord --- .../text-rendering/renderers/LightningTextTextureRenderer.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/core/text-rendering/renderers/LightningTextTextureRenderer.ts b/src/core/text-rendering/renderers/LightningTextTextureRenderer.ts index 4b9f2b3c..e530c764 100644 --- a/src/core/text-rendering/renderers/LightningTextTextureRenderer.ts +++ b/src/core/text-rendering/renderers/LightningTextTextureRenderer.ts @@ -694,7 +694,7 @@ export class LightningTextTextureRenderer { const resultLines = []; let result = ''; let spaceLeft = wordWrapWidth - indent; - const words = lines[i]!.split(' '); + const words = wordBreak ? [lines[i]!] : lines[i]!.split(' '); for (let j = 0; j < words.length; j++) { const wordWidth = this.measureText(words[j]!, letterSpacing); const wordWidthWithSpace =