Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions src/common/CommonTypes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,11 @@ export interface Dimensions {
export type NodeTextLoadedPayload = {
type: 'text';
dimensions: Dimensions;
/**
* Visible glyph extent — from the first line's cap-top to the last
* line's descender bottom. See `CoreTextNode.trimmedHeight`.
*/
trimmedHeight: number;
};

/**
Expand Down
22 changes: 22 additions & 0 deletions src/core/CoreTextNode.ts
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,7 @@ export class CoreTextNode extends CoreNode implements CoreTextNodeProps {
private _renderInfo: TextRenderInfo = {
width: 0,
height: 0,
trimmedHeight: 0,
};

private _type: 'sdf' | 'canvas' = 'sdf'; // Default to SDF renderer
Expand Down Expand Up @@ -289,6 +290,7 @@ export class CoreTextNode extends CoreNode implements CoreTextNodeProps {
w: this._renderInfo.width,
h: this._renderInfo.height,
},
trimmedHeight: this._renderInfo.trimmedHeight,
} satisfies NodeTextLoadedPayload);
};

Expand Down Expand Up @@ -593,4 +595,24 @@ export class CoreTextNode extends CoreNode implements CoreTextNodeProps {
get renderInfo(): TextRenderInfo {
return this._renderInfo;
}

/**
* Visible glyph extent — from the first line's cap-top to the last
* line's descender bottom. Excludes half-leading and the slack above
* the cap-top inside the font's ascender.
*
* @remarks
* Useful with flex `alignItems: 'center'` (or any layout that aligns
* by node `h`) to optically center the glyphs rather than the
* lineHeightPx line-box. Listen for `loaded` and apply with:
*
* ```ts
* text.once('loaded', (n, p) => { text.h = p.trimmedHeight; });
* ```
*
* Zero before the text loads or for empty strings.
*/
get trimmedHeight(): number {
return this._renderInfo.trimmedHeight;
}
}
9 changes: 9 additions & 0 deletions src/core/text-rendering/CanvasTextRenderer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,7 @@ const renderText = (props: CoreTextNodeProps): TextRenderInfo => {
return {
width: 0,
height: 0,
trimmedHeight: 0,
};
}

Expand Down Expand Up @@ -191,10 +192,18 @@ const renderText = (props: CoreTextNodeProps): TextRenderInfo => {
if (canvas.width > 0 && canvas.height > 0) {
imageData = context.getImageData(0, 0, canvasW, canvasH);
}
// Cap-top of first line to descender bottom of last line.
// descender is negative in NormalizedFontMetrics.
const trimmedHeight =
lineAmount > 0
? metrics.capHeight - metrics.descender + (lineAmount - 1) * lineHeightPx
: 0;

return {
imageData,
width: effectiveWidth,
height: effectiveHeight,
trimmedHeight,
remainingLines,
hasRemainingText,
};
Expand Down
13 changes: 13 additions & 0 deletions src/core/text-rendering/SdfTextRenderer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ const renderText = (props: CoreTextNodeProps): TextRenderInfo => {
return {
width: 0,
height: 0,
trimmedHeight: 0,
};
}

Expand All @@ -63,6 +64,7 @@ const renderText = (props: CoreTextNodeProps): TextRenderInfo => {
hasRemainingText: false,
width: layout.width,
height: layout.height,
trimmedHeight: layout.trimmedHeight,
layout,
};
}
Expand All @@ -74,6 +76,7 @@ const renderText = (props: CoreTextNodeProps): TextRenderInfo => {
return {
width: 0,
height: 0,
trimmedHeight: 0,
};
}

Expand All @@ -87,6 +90,7 @@ const renderText = (props: CoreTextNodeProps): TextRenderInfo => {
hasRemainingText: false,
width: layout.width,
height: layout.height,
trimmedHeight: layout.trimmedHeight,
layout,
};
};
Expand Down Expand Up @@ -345,12 +349,21 @@ const generateTextLayout = (
}
}

// Cap-top of first line to descender bottom of last line.
// descender is negative in NormalizedFontMetrics, so subtracting it
// adds the descender depth. Zero when there are no rendered lines.
const trimmedHeight =
lineAmount > 0
? metrics.capHeight - metrics.descender + (lineAmount - 1) * lineHeightPx
: 0;

// Convert final dimensions to pixel space for the layout
return {
glyphs,
distanceRange: fontScale * fontData.distanceField.distanceRange,
width: effectiveWidth * fontScale,
height: effectiveHeight,
trimmedHeight,
fontScale: fontScale,
lineHeight: lineHeightPx,
fontFamily,
Expand Down
20 changes: 20 additions & 0 deletions src/core/text-rendering/TextRenderer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -317,6 +317,11 @@ export interface TextLayout {
* Total text height
*/
height: number;
/**
* Trimmed text height — cap-top of the first line to descender bottom
* of the last line. See `TextRenderInfo.trimmedHeight`.
*/
trimmedHeight: number;
/**
* Font scale factor
*/
Expand Down Expand Up @@ -413,6 +418,21 @@ export interface TextRenderProps {
export interface TextRenderInfo {
width: number;
height: number;
/**
* Height of the visible glyph extent — from the first line's cap-top to
* the last line's descender bottom. Excludes half-leading and the slack
* between the font's ascender and cap-top.
*
* @remarks
* Formula: `capHeight − descender + (lines − 1) × lineHeightPx`
* (descender is negative in font metrics, so subtracting it adds the
* descender depth). For empty text, this is 0.
*
* Use this when you want flex `alignItems: 'center'` (or any layout
* that aligns by node `h`) to optically center the visible glyphs.
* Set `node.h = node.trimmedHeight` after the `loaded` event.
*/
trimmedHeight: number;
hasRemainingText?: boolean;
remainingLines?: number;
imageData?: ImageData | null; // Image data for Canvas Text Renderer
Expand Down
Loading