UNPKG

@lightningjs/renderer

Version:
337 lines 11.7 kB
/* * 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