@lightningjs/renderer
Version:
Lightning 3 Renderer
337 lines • 11.7 kB
JavaScript
/*
* 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 { CoreNode, CoreNodeRenderState, UpdateType, } from './CoreNode.js';
export class CoreTextNode extends CoreNode {
textRenderer;
fontHandler;
_layoutGenerated = false;
// SDF layout caching for performance
_cachedLayout = null;
_lastVertexBuffer = null;
// Text renderer properties - stored directly on the node
textProps;
_renderInfo = {
width: 0,
height: 0,
};
_type = 'sdf'; // Default to SDF renderer
constructor(stage, props, textRenderer) {
super(stage, props);
this.textRenderer = textRenderer;
this.fontHandler = textRenderer.font;
this._type = textRenderer.type;
// Initialize text properties from props
// Props are guaranteed to have all defaults resolved by Stage.createTextNode
this.textProps = props;
this.setUpdateType(UpdateType.All);
}
onTextureLoaded = (_, dimensions) => {
// If parent has a render texture, flag that we need to update
if (this.parentHasRenderTexture) {
this.notifyParentRTTOfUpdate();
}
// ignore 1x1 pixel textures
if (dimensions.width > 1 && dimensions.height > 1) {
this.emit('loaded', {
type: 'texture',
dimensions,
});
}
this.width = this._renderInfo.width;
this.height = this._renderInfo.height;
// Texture was loaded. In case the RAF loop has already stopped, we request
// a render to ensure the texture is rendered.
this.stage.requestRender();
};
/**
* Override CoreNode's update method to handle text-specific updates
*/
update(delta, parentClippingRect) {
if ((this.props.parent?.isRenderable === true &&
this._layoutGenerated === false) ||
(this.textProps.forceLoad === true &&
this._layoutGenerated === false &&
this.fontHandler.isFontLoaded(this.textProps.fontFamily) === true)) {
this._cachedLayout = null; // Invalidate cached layout
this._lastVertexBuffer = null; // Invalidate last vertex buffer
const resp = this.textRenderer.renderText(this.stage, this.textProps);
this.handleRenderResult(resp);
this._layoutGenerated = true;
}
// First run the standard CoreNode update
super.update(delta, parentClippingRect);
}
/**
* Override is renderable check for SDF text nodes
*/
updateIsRenderable() {
// SDF text nodes are always renderable if they have a valid layout
if (this._type === 'canvas') {
super.updateIsRenderable();
return;
}
// For SDF, check if we have a cached layout
this.setRenderable(this._cachedLayout !== null);
}
/**
* Handle the result of text rendering for both Canvas and SDF renderers
*/
handleRenderResult(result) {
// Host paths on top
const textRendererType = this._type;
let width = result.width;
let height = result.height;
// Handle Canvas renderer (uses ImageData)
if (textRendererType === 'canvas') {
if (result.imageData === undefined) {
this.emit('failed', {
type: 'text',
error: new Error('Canvas text rendering failed, no image data returned'),
});
return;
}
this.texture = this.stage.txManager.createTexture('ImageTexture', {
premultiplyAlpha: true,
src: result.imageData,
});
// It isn't renderable until the texture is loaded we have to set it to false here to avoid it
// being detected as a renderable default color node in the next frame
// it will be corrected once the texture is loaded
this.setRenderable(false);
if (this.renderState > CoreNodeRenderState.OutOfBounds) {
// We do want the texture to load immediately
this.texture.setRenderableOwner(this, true);
}
}
// Handle SDF renderer (uses layout caching)
if (textRendererType === 'sdf') {
this._cachedLayout = result.layout || null;
this.setRenderable(true);
this.props.width = width;
this.props.height = height;
this.setUpdateType(UpdateType.Local);
}
this._renderInfo = result;
this.emit('loaded', {
type: 'text',
dimensions: {
width: width,
height: height,
},
});
}
/**
* Override renderQuads to handle SDF vs Canvas rendering
*/
renderQuads(renderer) {
// Canvas renderer: use standard texture rendering via CoreNode
if (this._type === 'canvas') {
super.renderQuads(renderer);
return;
}
// Early return if no cached data
if (!this._cachedLayout) {
return;
}
if (this._lastVertexBuffer === null) {
this._lastVertexBuffer = this.textRenderer.addQuads(this._cachedLayout);
}
const props = this.textProps;
this.textRenderer.renderQuads(renderer, this._cachedLayout, this._lastVertexBuffer, {
fontFamily: this.textProps.fontFamily,
fontSize: props.fontSize,
color: this.props.color || 0xffffffff,
offsetY: props.offsetY,
worldAlpha: this.worldAlpha,
globalTransform: this.globalTransform.getFloatArr(),
clippingRect: this.clippingRect,
width: this.props.width,
height: this.props.height,
parentHasRenderTexture: this.parentHasRenderTexture,
framebufferDimensions: this.parentHasRenderTexture === true
? this.parentFramebufferDimensions
: null,
stage: this.stage,
});
}
get maxWidth() {
return this.textProps.maxWidth;
}
set maxWidth(value) {
if (this.textProps.maxWidth !== value) {
this.textProps.maxWidth = value;
this._layoutGenerated = false;
this.setUpdateType(UpdateType.Local);
}
}
// Property getters and setters
get maxHeight() {
return this.textProps.maxHeight;
}
set maxHeight(value) {
if (this.textProps.maxHeight !== value) {
this.textProps.maxHeight = value;
this._layoutGenerated = false;
this.setUpdateType(UpdateType.Local);
}
}
get text() {
return this.textProps.text;
}
set text(value) {
if (this.textProps.text !== value) {
this.textProps.text = value;
this._layoutGenerated = false;
this.setUpdateType(UpdateType.Local);
}
}
get fontSize() {
return this.textProps.fontSize;
}
set fontSize(value) {
if (this.textProps.fontSize !== value) {
this.textProps.fontSize = value;
this._layoutGenerated = false;
this.setUpdateType(UpdateType.Local);
}
}
get fontFamily() {
return this.textProps.fontFamily;
}
set fontFamily(value) {
if (this.textProps.fontFamily !== value) {
this.textProps.fontFamily = value;
this._layoutGenerated = true;
this.setUpdateType(UpdateType.Local);
}
}
get fontStyle() {
return this.textProps.fontStyle;
}
set fontStyle(value) {
if (this.textProps.fontStyle !== value) {
this.textProps.fontStyle = value;
this._layoutGenerated = true;
this.setUpdateType(UpdateType.Local);
}
}
get textAlign() {
return this.textProps.textAlign;
}
set textAlign(value) {
if (this.textProps.textAlign !== value) {
this.textProps.textAlign = value;
this._layoutGenerated = false;
this.setUpdateType(UpdateType.Local);
}
}
get letterSpacing() {
return this.textProps.letterSpacing;
}
set letterSpacing(value) {
if (this.textProps.letterSpacing !== value) {
this.textProps.letterSpacing = value;
this._layoutGenerated = false;
this.setUpdateType(UpdateType.Local);
}
}
get lineHeight() {
return this.textProps.lineHeight;
}
set lineHeight(value) {
if (this.textProps.lineHeight !== value) {
this.textProps.lineHeight = value;
this._layoutGenerated = false;
this.setUpdateType(UpdateType.Local);
}
}
get maxLines() {
return this.textProps.maxLines;
}
set maxLines(value) {
if (this.textProps.maxLines !== value) {
this.textProps.maxLines = value;
this._layoutGenerated = false;
this.setUpdateType(UpdateType.Local);
}
}
get textBaseline() {
return this.textProps.textBaseline;
}
set textBaseline(value) {
if (this.textProps.textBaseline !== value) {
this.textProps.textBaseline = value;
this._layoutGenerated = false;
this.setUpdateType(UpdateType.Local);
}
}
get verticalAlign() {
return this.textProps.verticalAlign;
}
set verticalAlign(value) {
if (this.textProps.verticalAlign !== value) {
this.textProps.verticalAlign = value;
this._layoutGenerated = false;
this.setUpdateType(UpdateType.Local);
}
}
get overflowSuffix() {
return this.textProps.overflowSuffix;
}
set overflowSuffix(value) {
if (this.textProps.overflowSuffix !== value) {
this.textProps.overflowSuffix = value;
this._layoutGenerated = false;
this.setUpdateType(UpdateType.Local);
}
}
get wordBreak() {
return this.textProps.wordBreak;
}
set wordBreak(value) {
if (this.textProps.wordBreak !== value) {
this.textProps.wordBreak = value;
this._layoutGenerated = false;
this.setUpdateType(UpdateType.Local);
}
}
get offsetY() {
return this.textProps.offsetY;
}
set offsetY(value) {
if (this.textProps.offsetY !== value) {
this.textProps.offsetY = value;
this._layoutGenerated = false;
this.setUpdateType(UpdateType.Local);
}
}
get forceLoad() {
return this.textProps.forceLoad;
}
set forceLoad(value) {
if (this.textProps.forceLoad !== value) {
this.textProps.forceLoad = value;
this.setUpdateType(UpdateType.Local);
}
}
get renderInfo() {
return this._renderInfo;
}
}
//# sourceMappingURL=CoreTextNode.js.map