@lightningjs/renderer
Version:
Lightning 3 Renderer
546 lines (520 loc) • 15.5 kB
text/typescript
/*
* If not stated otherwise in this file or this component's LICENSE file the
* following copyright and licenses apply:
*
* Copyright 2023 Comcast Cable Communications Management, LLC.
*
* Licensed under the Apache License, Version 2.0 (the License);
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import type { CoreTextNode, CoreTextNodeProps } from '../CoreTextNode.js';
import type { WebGlCtxTexture } from '../renderers/webgl/WebGlCtxTexture.js';
import type { Stage } from '../Stage.js';
import type { CoreRenderer } from '../renderers/CoreRenderer.js';
import type { RectWithValid } from '../lib/utils.js';
import type { Dimensions } from '../../common/CommonTypes.js';
// Text baseline and vertical align types
export type TextBaseline =
| 'alphabetic'
| 'hanging'
| 'middle'
| 'ideographic'
| 'bottom';
export type TextVerticalAlign = 'top' | 'middle' | 'bottom';
export type TextRenderers = 'canvas' | 'sdf';
/**
* Structure mapping font family names to a set of font faces.
*/
export interface FontFamilyMap {
[familyName: string]: FontFace;
}
/**
* Font metrics used for layout and default line height calculations.
*/
export interface FontMetrics {
/**
* The distance, in font units, from the baseline to the highest point of the font.
*/
ascender: number;
/**
* The distance, in font units, from the baseline to the lowest point of the font.
*/
descender: number;
/**
* The additional space used in the calculation of the default line height in font units.
*/
lineGap: number;
/**
* The number of font units per 1 EM.
*/
unitsPerEm: number;
}
/**
* Normalized font metrics where values are expressed as a fraction of 1 EM.
*/
export interface NormalizedFontMetrics {
/**
* The distance, as a fraction of 1 EM, from the baseline to the highest point of the font.
*/
ascender: number;
/**
* The distance, as a fraction of 1 EM, from the baseline to the lowest point of the font.
*/
descender: number;
/**
* The additional space used in the calculation of the default line height as a fraction of 1 EM
*/
lineGap: number;
}
/**
* Text renderer properties that are used in resolving appropriate font faces
*
* @remarks
* Extended by {@link TrProps}
*/
export interface TrFontProps {
/**
* Font Family
*
* @internalRemarks
* `fontFamily` is defined currently as single string, but in the future we may want to
* support multiple font family fallbacks, as this is supported by CSS / Canvas2d. We can
* do this in a backwards compatible way by unioning an array of strings to the
* `fontFamily` property.
*/
fontFamily: string;
/**
* Font Style
*
* @remarks
* The font style to use when looking up the font face. This can be one of the
* following strings:
* - `'normal'`
* - `'italic'`
* - `'oblique'`
*/
fontStyle: 'normal' | 'italic' | 'oblique';
/**
* Font Size
*
* @remarks
* The font size to use when looking up the font face.
*
* The font size is specified in pixels and is the height of the font's
* em-square. The em-square is essentially the height of the capital letters
* for the font. The actual height of the text can be larger than the
* specified font size, as the font may have ascenders and descenders that
* extend beyond the em-square.
*
* @default 16
*/
fontSize: number;
}
export interface TrProps extends TrFontProps {
/**
* Text to display
*
* @default ''
*/
text: string;
/**
* Text alignment
*
* @remarks
* Alignment of the text relative to it's contained bounds. For best results,
* use {@link contain} mode `'width'` or `'both'` and a set an explicit
* {@link width} for the text to be aligned within.
*
* @default 'left'
*/
textAlign: 'left' | 'center' | 'right';
/**
* Color of text
*
* @remarks
* The color value is a number in the format 0xRRGGBBAA, where RR is the red
* component, GG is the green component, BB is the blue component, and AA is
* the alpha component.
*
* @default 0xffffffff (opaque white)
*/
color: number;
x: number;
y: number;
maxWidth: number;
maxHeight: number;
/**
* Vertical offset for text
*
* @remarks
* The vertical offset of the text.
*
* @default 0
*/
offsetY: number;
/**
* Letter spacing for text (in pixels)
*
* @remarks
* This property sets additional (or reduced, if value is negative) spacing
* between characters in the text.
*
* @default 0
*/
letterSpacing: number;
/**
* Line height for text (in pixels)
*
* @remarks
* This property sets the height of each line. If set to `undefined`, the
* line height will be calculated based on the font and font size to be the
* minimal height required to completely contain a line of text.
*
* See: https://github.com/lightning-js/renderer/issues/170
*
* @default `undefined`
*/
lineHeight: number;
/**
* Max lines for text
*
* @remarks
* This property sets max number of lines of a text paragraph.
* Not yet implemented in the SDF renderer.
*
* @default 0
*/
maxLines: number;
/**
* Vertical Align for text when lineHeight > fontSize
*
* @remarks
* This property sets the vertical align of the text.
* Not yet implemented in the SDF renderer.
*
* @default middle
*/
verticalAlign: TextVerticalAlign;
/**
* Overflow Suffix for text
*
* @remarks
* The suffix to be added when text is cropped due to overflow.
* Not yet implemented in the SDF renderer.
*
* @default "..."
*/
overflowSuffix: string;
/**
* Word Break for text
*
* @remarks
* This property sets how words should break when reaching the end of a line.
*
* - `'overflow'`: Uses the Css/HTML normal word-break behavior, generally not used in app development.
* - `'break-all'`: To prevent overflow, word breaks should happen between any two characters.
* - `'break-word'`: To prevent overflow, word breaks should happen between words. If words are too long word breaks happen between any two characters.
*
* @default "break-word"
*/
wordBreak: 'overflow' | 'break-all' | 'break-word';
/**
* contain mode for text
*
* @remarks
*
* This property sets how the text should be contained within its bounding box.
*
* - 'width': The text is contained within the specified maxWidth, horizontal position of text will adjust according to {@link textAlign}.
* - 'height': The text is contained within the specified maxHeight, vertical position of text will adjust according to {@link verticalAlign}.
* - 'both': The text is contained within both the specified maxWidth and maxHeight.
* - 'none': The text is not contained within any bounding box.
*
* @default 'none'
*/
contain: 'width' | 'height' | 'both' | 'none';
/**
* Enable BB-code rich text parsing for this text node.
*
* @remarks
*
* When `true`, the {@link text} string is parsed for BB-code tags before
* layout and rendering. Supported tags:
*
* - `[b]…[/b]` — bold
* - `[i]…[/i]` — italic
* - `[u]…[/u]` — underline
* - `[s]…[/s]` — strikethrough
* - `[color=0xRRGGBBAA]…[/color]` — inline color (must use the renderer's
* internal `0xRRGGBBAA` hex format, e.g. `[color=0xff0000ff]`)
*
* Tags are nestable and may be combined freely. Unrecognised or malformed
* tags are emitted as literal text. Mis-nested closing tags implicitly close
* any intervening open tags.
*
* When `false` (the default), the `text` string is rendered as-is with no
* tag parsing, and there is no performance overhead.
*
* ### Renderer differences
*
* **Canvas renderer** — bold and italic use the CSS `bold`/`italic` font
* keywords; a matching font face must be registered for best results.
*
* **SDF renderer** — bold is a per-vertex SDF threshold shift (fake bold,
* no extra atlas required); italic is a layout-time vertex shear of tan(14°)
* relative to the alphabetic baseline (fake italic, no extra atlas required).
*
* @see {@link https://github.com/lightning-js/renderer/blob/main/docs/rich-text.md | Rich Text docs}
*
* @default false
*/
richText: boolean;
}
/**
* Glyph layout information for WebGL rendering
*/
export interface GlyphLayout {
/**
* X position relative to text origin
*/
x: number;
/**
* Y position relative to text origin
*/
y: number;
/**
* Width of glyph in font units
*/
width: number;
/**
* Height of glyph in font units
*/
height: number;
/**
* Atlas texture coordinates (normalized 0-1)
*/
atlasX: number;
atlasY: number;
atlasWidth: number;
atlasHeight: number;
}
/**
* Complete text layout information for caching
*/
export interface TextLayout {
/**
* Packed per-glyph design-unit records consumed by the renderer's batched
* SDF write paths (one record per quad, including decoration quads).
*
* plain (8 floats): x, y, w, h, u, v, uw, vh
* rich (12 floats): x, y, w, h, u, v, uw, vh, shearTop, shearBot, packed_span_color, style
*
* `shearTop` / `shearBot` are the per-corner x-deltas of the italic lean;
* `u = -1.0` with `uw = 0` marks a solid-fill decoration quad.
*/
glyphs: Float32Array;
/**
* Number of glyph records (quads) in `glyphs`.
*/
glyphCount: number;
/**
* Total quad count (glyphs + decoration rects). Equals `glyphCount`; kept
* for backward compatibility with callers that read the quad total.
*/
totalQuadCount: number;
/**
* Whether this layout was generated for richText mode.
* Determines the vertex format: false = 4 floats/vertex (plain), true = 6 floats/vertex (rich).
*/
richText: boolean;
/**
* Total text width
*/
width: number;
/**
* Total text height
*/
height: number;
/**
* Font scale factor
*/
fontScale: number;
/**
* Line height
*/
lineHeight: number;
/**
* Font family used
*/
fontFamily: string;
/**
* distanceRange used
*/
distanceRange: number;
/**
* number of lines that exceeded maxHeight and were truncated
*/
remainingLines: number;
/**
* Whether there is remaining text that exceeded maxHeight and was truncated
*/
hasRemainingText: boolean;
}
export interface FontLoadOptions {
fontFamily: string;
metrics?: FontMetrics;
// For Canvas/traditional font loading
fontUrl?: string;
// For SDF/atlas-based font loading
atlasUrl?: string;
atlasDataUrl?: string;
}
/**
* Measure Width of Text function to be defined in font handlers, used in TextLayoutEngine
*/
export type MeasureTextFn = (
text: string,
fontFamily: string,
letterSpacing: number,
) => number;
export interface FontHandler {
init: (
c: CanvasRenderingContext2D | OffscreenCanvasRenderingContext2D,
) => void;
type: 'canvas' | 'sdf';
isFontLoaded: (fontFamily: string) => boolean;
loadFont: (stage: Stage, options: FontLoadOptions) => Promise<void>;
waitingForFont: (fontFamily: string, node: CoreTextNode) => void;
stopWaitingForFont: (fontFamily: string, node: CoreTextNode) => void;
getFontFamilies: () => FontFamilyMap;
canRenderFont: (trProps: TrProps) => boolean;
getFontMetrics: (
fontFamily: string,
fontSize: number,
) => NormalizedFontMetrics;
measureText: MeasureTextFn;
}
export interface TextRenderProps {
fontFamily: string;
fontSize: number;
color: number;
offsetY: number;
worldAlpha: number;
globalTransform: Float32Array;
clippingRect: RectWithValid;
width: number;
height: number;
parentHasRenderTexture: boolean;
framebufferDimensions: Dimensions | null;
stage: Stage;
/** Optional SDF vertex cache — passed by CoreTextNode for the cache-hit fast path. */
sdfCache?: SdfVertexCache;
}
/**
* Cached SDF vertex data for a single text node.
* When nothing changes (layout, transform, color, alpha), the pre-transformed
* vertex Float32Array can be mem-copied into the shared SDF buffer instead of
* re-computing per-glyph matrix transforms each frame.
*/
export interface SdfVertexCache {
/** Pre-transformed vertex Float32Array in the target SDF buffer's GPU layout (null = cache miss). */
vertices: Float32Array | null;
/** Number of glyphs (quads) in the cached data. */
glyphCount: number;
/** RGBA color at the time the cache was built. */
color: number;
/** worldAlpha at the time the cache was built. */
alpha: number;
/** The 6 relevant transform matrix components [m0,m1,m3,m4,m6,m7]. */
transform: Float32Array;
/** Reference to the TextLayout the cache was built from. */
layoutRef: TextLayout | null;
/**
* Quad index in the shared SDF buffer where the cached vertices were last
* written. A cache hit that would land at a different offset than this (a
* render-list reorder or layout change elsewhere in the buffer) means the
* GPU copy is stale at both the old and new offsets, so the buffer must be
* re-uploaded even though every write was a bit-exact mem-copy.
*/
lastStartQuad: number;
/**
* Whether the last write this node made to the shared buffer was NOT a
* bit-exact re-write of the bytes the GPU holds at `lastStartQuad` (e.g.
* the translated fast path shifted the positions). Forces a re-upload the
* next time the node takes the exact cache-hit path.
*/
lastWriteDirty: boolean;
}
export interface RenderInfo {
width: number;
height: number;
hasRemainingText: boolean;
remainingLines: number;
}
export type SdfRenderInfo = RenderInfo & {
type: 'sdf';
layout: TextLayout;
atlasTexture: WebGlCtxTexture;
};
export type CanvasRenderInfo = RenderInfo & {
type: 'canvas';
imageData: ImageBitmap | HTMLCanvasElement;
};
export type TextRenderInfo = SdfRenderInfo | CanvasRenderInfo;
export interface TextRenderer {
type: 'canvas' | 'sdf';
font: FontHandler;
renderText: (props: CoreTextNodeProps) => TextRenderInfo;
renderQuads: (
renderer: CoreRenderer,
layout: TextLayout,
vertexBuffer: Float32Array | null,
renderProps: TextRenderProps,
) => void;
init: (stage: Stage) => void;
clearCache: () => void;
}
/**
* Text line struct for text mapping
* 0 - text
* 1 - width
* 2 - truncated
* 3 - line offset x
* 4 - line offset y
*/
export type TextLineStruct = [string, number, boolean, number, number];
/**
* Wrapped lines struct for text mapping
* 0 - line structs
* 1 - remaining lines
* 2 - remaining text
*/
export type WrappedLinesStruct = [TextLineStruct[], number, boolean];
/**
* Wrapped lines struct for text mapping
* 0 - line structs
* 1 - remaining lines
* 2 - remaining text
* 3 - bare line height
* 4 - line height pixels
* 5 - effective width
* 6 - effective height
*/
export type TextLayoutStruct = [
TextLineStruct[],
number,
boolean,
number,
number,
number,
number,
];