Skip to content

Commit 0c9a049

Browse files
authored
Unified Text Layout Engine (fresh branch) (#642)
Unified Text Layout engine for canvas and (m)sdf text rendering
2 parents 5b1fa92 + 51efcbd commit 0c9a049

File tree

176 files changed

+1143
-2226
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

176 files changed

+1143
-2226
lines changed

examples/tests/text-baseline.ts

Lines changed: 0 additions & 120 deletions
This file was deleted.

examples/tests/text-vertical-align.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -94,6 +94,7 @@ function generateVerticalAlignTest(
9494
...NODE_PROPS,
9595
text: 'txyz',
9696
textRendererOverride: textRenderer,
97+
maxHeight: CONTAINER_SIZE,
9798
} satisfies Partial<ITextNodeProps>;
9899

99100
const baselineNode = renderer.createTextNode({
@@ -129,6 +130,7 @@ function generateVerticalAlignTest(
129130
...NODE_PROPS,
130131
text: 'abcd\ntxyz',
131132
textRendererOverride: textRenderer,
133+
maxHeight: CONTAINER_SIZE,
132134
} satisfies Partial<ITextNodeProps>;
133135

134136
const baselineNode = renderer.createTextNode({

examples/tests/text.ts

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -115,13 +115,13 @@ export default async function ({
115115
const statusNode = renderer.createTextNode({
116116
text: '',
117117
fontSize: 30,
118-
offsetY: -5,
118+
// offsetY: -5,
119119
zIndex: 100,
120120
parent: testRoot,
121121
});
122122

123123
statusNode.on('loaded', ((target: any, { dimensions }) => {
124-
statusNode.x = renderer.settings.appWidth - dimensions.width;
124+
statusNode.x = renderer.settings.appWidth - dimensions.w;
125125
}) satisfies NodeLoadedEventHandler);
126126

127127
function updateStatus() {
@@ -132,7 +132,6 @@ export default async function ({
132132
`moveStep: ${moveStep}`,
133133
`x: ${msdfTextNode.x}`,
134134
`y: ${msdfTextNode.y}`,
135-
`scrollY: ${msdfTextNode.scrollY}`,
136135
`offsetY: ${msdfTextNode.offsetY}`,
137136
`fontSize: ${Number(msdfTextNode.fontSize).toFixed(1)}`,
138137
`letterSpacing: ${msdfTextNode.letterSpacing}`,
@@ -346,12 +345,12 @@ export default async function ({
346345
* Added offset to the Y position of the text to account for the
347346
* difference in canvas and SDF text rendering
348347
*/
349-
const sdfOffsetY = 6;
348+
const sdfOffsetY = 0;
350349

351-
function getFontProps(fontType: keyof TrFontFaceMap): {
350+
function getFontProps(fontType: string): {
352351
fontFamily: string;
353352
offsetY: number;
354-
textRendererOverride: keyof TextRendererMap;
353+
textRendererOverride: 'sdf' | 'canvas';
355354
} {
356355
if (fontType === 'msdf') {
357356
return {

src/core/CoreTextNode.ts

Lines changed: 4 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -93,21 +93,16 @@ export class CoreTextNode extends CoreNode implements CoreTextNodeProps {
9393
if (this.parentHasRenderTexture) {
9494
this.notifyParentRTTOfUpdate();
9595
}
96-
9796
// ignore 1x1 pixel textures
9897
if (dimensions.w > 1 && dimensions.h > 1) {
9998
this.emit('loaded', {
10099
type: 'texture',
101100
dimensions,
102101
} satisfies NodeTextureLoadedPayload);
103102
}
104-
105103
this.w = this._renderInfo.width;
106104
this.h = this._renderInfo.height;
107-
108-
// Texture was loaded. In case the RAF loop has already stopped, we request
109-
// a render to ensure the texture is rendered.
110-
this.stage.requestRender();
105+
this.setUpdateType(UpdateType.IsRenderable);
111106
};
112107

113108
allowTextGeneration() {
@@ -134,7 +129,7 @@ export class CoreTextNode extends CoreNode implements CoreTextNodeProps {
134129
this._waitingForFont = false;
135130
this._cachedLayout = null; // Invalidate cached layout
136131
this._lastVertexBuffer = null; // Invalidate last vertex buffer
137-
const resp = this.textRenderer.renderText(this.stage, this.textProps);
132+
const resp = this.textRenderer.renderText(this.textProps);
138133
this.handleRenderResult(resp);
139134
this._layoutGenerated = true;
140135
} else if (this._waitingForFont === false) {
@@ -186,7 +181,6 @@ export class CoreTextNode extends CoreNode implements CoreTextNodeProps {
186181
premultiplyAlpha: true,
187182
src: result.imageData as ImageData,
188183
});
189-
190184
// It isn't renderable until the texture is loaded we have to set it to false here to avoid it
191185
// being detected as a renderable default color node in the next frame
192186
// it will be corrected once the texture is loaded
@@ -341,7 +335,7 @@ export class CoreTextNode extends CoreNode implements CoreTextNodeProps {
341335
this.fontHandler.stopWaitingForFont(this.textProps.fontFamily, this);
342336
}
343337
this.textProps.fontFamily = value;
344-
this._layoutGenerated = true;
338+
this._layoutGenerated = false;
345339
this.setUpdateType(UpdateType.Local);
346340
}
347341
}
@@ -353,7 +347,7 @@ export class CoreTextNode extends CoreNode implements CoreTextNodeProps {
353347
set fontStyle(value: TrProps['fontStyle']) {
354348
if (this.textProps.fontStyle !== value) {
355349
this.textProps.fontStyle = value;
356-
this._layoutGenerated = true;
350+
this._layoutGenerated = false;
357351
this.setUpdateType(UpdateType.Local);
358352
}
359353
}
@@ -406,18 +400,6 @@ export class CoreTextNode extends CoreNode implements CoreTextNodeProps {
406400
}
407401
}
408402

409-
get textBaseline(): TrProps['textBaseline'] {
410-
return this.textProps.textBaseline;
411-
}
412-
413-
set textBaseline(value: TrProps['textBaseline']) {
414-
if (this.textProps.textBaseline !== value) {
415-
this.textProps.textBaseline = value;
416-
this._layoutGenerated = false;
417-
this.setUpdateType(UpdateType.Local);
418-
}
419-
}
420-
421403
get verticalAlign(): TrProps['verticalAlign'] {
422404
return this.textProps.verticalAlign;
423405
}

src/core/Stage.ts

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -678,10 +678,9 @@ export class Stage {
678678
textAlign: props.textAlign || 'left',
679679
offsetY: props.offsetY || 0,
680680
letterSpacing: props.letterSpacing || 0,
681-
lineHeight: props.lineHeight || 0,
681+
lineHeight: props.lineHeight || 1.2,
682682
maxLines: props.maxLines || 0,
683-
textBaseline: props.textBaseline || 'alphabetic',
684-
verticalAlign: props.verticalAlign || 'middle',
683+
verticalAlign: props.verticalAlign || 'top',
685684
overflowSuffix: props.overflowSuffix || '...',
686685
wordBreak: props.wordBreak || 'normal',
687686
maxWidth: props.maxWidth || 0,

src/core/shaders/webgl/SdfShader.ts

Lines changed: 1 addition & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,6 @@ const IDENTITY_MATRIX_3x3 = new Float32Array([1, 0, 0, 0, 1, 0, 0, 0, 1]);
2525
*/
2626
export interface SdfShaderProps {
2727
transform: Float32Array;
28-
scrollY: number;
2928
/**
3029
* Color in RGBA format
3130
*
@@ -35,7 +34,6 @@ export interface SdfShaderProps {
3534
color: number;
3635
size: number;
3736
distanceRange: number;
38-
debug: boolean;
3937
}
4038
/**
4139
* SdfShader supports multi-channel and single-channel signed distance field textures.
@@ -53,19 +51,15 @@ export interface SdfShaderProps {
5351
export const Sdf: WebGlShaderType<SdfShaderProps> = {
5452
props: {
5553
transform: IDENTITY_MATRIX_3x3,
56-
scrollY: 0,
5754
color: 0xffffffff,
5855
size: 16,
5956
distanceRange: 1.0,
60-
debug: false,
6157
},
6258
onSdfBind(props) {
6359
this.uniformMatrix3fv('u_transform', props.transform);
64-
this.uniform1f('u_scrollY', props.scrollY);
6560
this.uniform4fa('u_color', getNormalizedRgbaComponents(props.color));
6661
this.uniform1f('u_size', props.size);
6762
this.uniform1f('u_distanceRange', props.distanceRange);
68-
this.uniform1i('u_debug', props.debug ? 1 : 0);
6963
},
7064
vertex: `
7165
# ifdef GL_FRAGMENT_PRECISION_HIGH
@@ -80,14 +74,13 @@ export const Sdf: WebGlShaderType<SdfShaderProps> = {
8074
8175
uniform vec2 u_resolution;
8276
uniform mat3 u_transform;
83-
uniform float u_scrollY;
8477
uniform float u_pixelRatio;
8578
uniform float u_size;
8679
8780
varying vec2 v_texcoord;
8881
8982
void main() {
90-
vec2 scrolledPosition = a_position * u_size - vec2(0, u_scrollY);
83+
vec2 scrolledPosition = a_position * u_size;
9184
vec2 transformedPosition = (u_transform * vec3(scrolledPosition, 1)).xy;
9285
9386
// Calculate screen space with pixel ratio
@@ -118,10 +111,6 @@ export const Sdf: WebGlShaderType<SdfShaderProps> = {
118111
119112
void main() {
120113
vec3 sample = texture2D(u_texture, v_texcoord).rgb;
121-
if (u_debug == 1) {
122-
gl_FragColor = vec4(sample.r, sample.g, sample.b, 1.0);
123-
return;
124-
}
125114
float scaledDistRange = u_distanceRange * u_pixelRatio;
126115
float sigDist = scaledDistRange * (median(sample.r, sample.g, sample.b) - 0.5);
127116
float opacity = clamp(sigDist + 0.5, 0.0, 1.0) * u_color.a;

0 commit comments

Comments
 (0)