Skip to content

Commit 6b2c304

Browse files
ZyieGoodBoyDigital
andauthored
feat: adds text splitting functionality (#11496)
* feat: add Text.split / BitmapText.split * Feat: Adds origin point for container transformations Adds an 'origin' property to the container, providing a way to specify the point around which the container rotates and scales without affecting its position. This allows for more flexible and intuitive control over container transformations, particularly when rotating or scaling around a specific point. * update API * add warning and tests * ensure origin works with scaled containers * Update name * Apply suggestions from code review Co-authored-by: Mat Groves <[email protected]> --------- Co-authored-by: Mat Groves <[email protected]>
1 parent 23dca4b commit 6b2c304

17 files changed

+1712
-0
lines changed

src/scene/SceneMixins.d.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,11 @@ declare global
7070
interface HTMLText {}
7171
interface HTMLTextOptions {}
7272

73+
interface SplitText {}
74+
interface SplitTextOptions {}
75+
interface SplitBitmapText {}
76+
interface SplitBitmapTextOptions {}
77+
7378
}
7479
}
7580

src/scene/index.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -128,6 +128,7 @@ export * from './text-bitmap/BitmapFontManager';
128128
export * from './text-bitmap/BitmapText';
129129
export * from './text-bitmap/BitmapTextPipe';
130130
export * from './text-bitmap/DynamicBitmapFont';
131+
export * from './text-bitmap/utils/bitmapTextSplit';
131132
export * from './text-bitmap/utils/getBitmapTextLayout';
132133
export * from './text-bitmap/utils/resolveCharacters';
133134
export * from './text-html/BatchableHTMLText';
@@ -145,6 +146,10 @@ export * from './text-html/utils/loadFontCSS';
145146
export * from './text-html/utils/loadSVGImage';
146147
export * from './text-html/utils/measureHtmlText';
147148
export * from './text-html/utils/textStyleToCSS';
149+
export * from './text-split/AbstractSplitText';
150+
export * from './text-split/SplitBitmapText';
151+
export * from './text-split/SplitText';
152+
export * from './text-split/types';
148153
export * from './text/AbstractText';
149154
export * from './text/canvas/BatchableText';
150155
export * from './text/canvas/CanvasTextGenerator';
@@ -158,6 +163,7 @@ export * from './text/sdfShader/shader-bits/localUniformMSDFBit';
158163
export * from './text/sdfShader/shader-bits/mSDFBit';
159164
export * from './text/Text';
160165
export * from './text/TextStyle';
166+
export * from './text/utils/canvasTextSplit';
161167
export * from './text/utils/generateTextStyleKey';
162168
export * from './text/utils/getPo2TextureFromSource';
163169
export * from './text/utils/updateTextBounds';
Lines changed: 111 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,111 @@
1+
import { Container } from '../../container/Container';
2+
import { CanvasTextMetrics } from '../../text/canvas/CanvasTextMetrics';
3+
import { type TextStyle } from '../../text/TextStyle';
4+
import { type SplitOptions } from '../../text-split/SplitText';
5+
import { type TextSplitOutput } from '../../text-split/types';
6+
import { BitmapFontManager } from '../BitmapFontManager';
7+
import { BitmapText } from '../BitmapText';
8+
import { getBitmapTextLayout } from './getBitmapTextLayout';
9+
10+
/**
11+
* Splits a Text object into segments based on the text's layout and style,
12+
* and adds these segments as individual Text objects to a specified container.
13+
*
14+
* This function handles word wrapping, alignment, and letter spacing,
15+
* ensuring that each segment is rendered correctly according to the original text's style.
16+
* @param options - Configuration options for the text split operation.
17+
* @returns An array of Text objects representing the split segments.
18+
* @internal
19+
*/
20+
export function bitmapTextSplit(
21+
options: Pick<SplitOptions, 'text' | 'style'> & { chars: BitmapText[] },
22+
): TextSplitOutput<BitmapText>
23+
{
24+
const { text, style, chars: existingChars } = options;
25+
const textStyle = style as TextStyle;
26+
const font = BitmapFontManager.getFont(text, textStyle);
27+
28+
const segments = CanvasTextMetrics.graphemeSegmenter(text);
29+
const layout = getBitmapTextLayout(segments, textStyle, font, true);
30+
const scale = layout.scale;
31+
const chars: BitmapText[] = [];
32+
const words: Container[] = [];
33+
const lines: Container[] = [];
34+
35+
let yOffset = 0;
36+
37+
for (const line of layout.lines)
38+
{
39+
// if the line is empty, skip it
40+
if (line.chars.length === 0) continue;
41+
42+
const lineContainer = new Container({ label: 'line' });
43+
44+
lineContainer.y = yOffset;
45+
lines.push(lineContainer);
46+
47+
let currentWordContainer = new Container({ label: 'word' });
48+
let currentWordStartIndex = 0;
49+
50+
for (let i = 0; i < line.chars.length; i++)
51+
{
52+
const char = line.chars[i];
53+
54+
if (!char) continue;
55+
56+
const charData = font.chars[char];
57+
58+
if (!charData) continue;
59+
60+
const isSpace = char === ' ';
61+
const isLastChar = i === line.chars.length - 1;
62+
63+
let charInstance: BitmapText;
64+
65+
if (existingChars.length > 0)
66+
{
67+
charInstance = existingChars.shift();
68+
charInstance.text = char;
69+
charInstance.style = textStyle;
70+
charInstance.label = `char-${char}`;
71+
charInstance.x = (line.charPositions[i]! * scale) - (line.charPositions[currentWordStartIndex]! * scale);
72+
}
73+
else
74+
{
75+
// Create a new BitmapText instance if no existing one is available
76+
charInstance = new BitmapText({
77+
text: char,
78+
style: textStyle,
79+
label: `char-${char}`,
80+
x: (line.charPositions[i]! * scale) - (line.charPositions[currentWordStartIndex]! * scale),
81+
});
82+
}
83+
84+
if (!isSpace)
85+
{
86+
chars.push(charInstance);
87+
// Add to word container
88+
currentWordContainer.addChild(charInstance);
89+
}
90+
91+
// Handle word breaks
92+
if (isSpace || isLastChar)
93+
{
94+
if (currentWordContainer.children.length > 0)
95+
{
96+
currentWordContainer.x = line.charPositions[currentWordStartIndex]! * scale;
97+
words.push(currentWordContainer);
98+
lineContainer.addChild(currentWordContainer);
99+
100+
// Start new word container
101+
currentWordContainer = new Container({ label: 'word' });
102+
currentWordStartIndex = i + 1;
103+
}
104+
}
105+
}
106+
107+
yOffset += font.lineHeight * scale;
108+
}
109+
110+
return { chars, lines, words };
111+
}

0 commit comments

Comments
 (0)