UNPKG

@lightningjs/renderer

Version:
157 lines 6.03 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 { assertTruthy } from '../../utils.js'; import * as CanvasFontHandler from './CanvasFontHandler.js'; import { hasZeroWidthSpace } from './Utils.js'; import { mapTextLayout } from './TextLayoutEngine.js'; const MAX_TEXTURE_DIMENSION = 4096; const type = 'canvas'; let canvas = null; let context = null; // Separate canvas and context for text measurements let measureCanvas = null; let measureContext = null; // Cache for text layout calculations const layoutCache = new Map(); // Initialize the Text Renderer const init = (stage) => { const dpr = stage.options.devicePhysicalPixelRatio; // Drawing canvas and context canvas = stage.platform.createCanvas(); context = canvas.getContext('2d', { willReadFrequently: true }); context.setTransform(dpr, 0, 0, dpr, 0, 0); context.textRendering = 'optimizeSpeed'; // Separate measuring canvas and context measureCanvas = stage.platform.createCanvas(); measureContext = measureCanvas.getContext('2d'); measureContext.setTransform(dpr, 0, 0, dpr, 0, 0); measureContext.textRendering = 'optimizeSpeed'; // Set up a minimal size for the measuring canvas since we only use it for measurements measureCanvas.width = 1; measureCanvas.height = 1; CanvasFontHandler.init(context, measureContext); }; /** * Canvas text renderer * * @param stage - Stage instance for font resolution * @param props - Text rendering properties * @returns Object containing ImageData and dimensions */ const renderText = (props) => { assertTruthy(canvas, 'Canvas is not initialized'); assertTruthy(context, 'Canvas context is not available'); assertTruthy(measureContext, 'Canvas measureContext is not available'); // Extract already normalized properties const { text, fontFamily, fontStyle, fontSize, textAlign, maxLines, lineHeight, verticalAlign, overflowSuffix, maxWidth, maxHeight, wordBreak, } = props; const font = `${fontStyle} ${fontSize}px Unknown, ${fontFamily}`; // Get font metrics and calculate line height measureContext.font = font; measureContext.textBaseline = 'hanging'; const metrics = CanvasFontHandler.getFontMetrics(fontFamily, fontSize); const letterSpacing = props.letterSpacing; const [lines, remainingLines, hasRemainingText, bareLineHeight, lineHeightPx, effectiveWidth, effectiveHeight,] = mapTextLayout(CanvasFontHandler.measureText, metrics, text, textAlign, fontFamily, lineHeight, overflowSuffix, wordBreak, letterSpacing, maxLines, maxWidth, maxHeight); const lineAmount = lines.length; const canvasW = Math.ceil(effectiveWidth); const canvasH = Math.ceil(effectiveHeight); canvas.width = canvasW; canvas.height = canvasH; context.fillStyle = 'white'; context.font = font; context.textBaseline = 'hanging'; // Performance optimization for large fonts if (fontSize >= 128) { context.globalAlpha = 0.01; context.fillRect(0, 0, 0.01, 0.01); context.globalAlpha = 1.0; } for (let i = 0; i < lineAmount; i++) { const line = lines[i]; const textLine = line[0]; let currentX = Math.ceil(line[3]); const currentY = Math.ceil(line[4]); if (letterSpacing === 0) { context.fillText(textLine, currentX, currentY); } else { const textLineLength = textLine.length; for (let j = 0; j < textLineLength; j++) { const char = textLine.charAt(j); if (hasZeroWidthSpace(char) === true) { continue; } context.fillText(char, currentX, currentY); currentX += CanvasFontHandler.measureText(char, fontFamily, letterSpacing); } } } // Extract image data let imageData = null; if (canvas.width > 0 && canvas.height > 0) { imageData = context.getImageData(0, 0, canvasW, canvasH); } return { imageData, width: effectiveWidth, height: effectiveHeight, remainingLines, hasRemainingText, }; }; /** * Generate a cache key for text layout calculations */ function generateLayoutCacheKey(text, fontFamily, fontSize, fontStyle, wordWrap, wordWrapWidth, letterSpacing, maxLines, overflowSuffix) { return `${text}-${fontFamily}-${fontSize}-${fontStyle}-${wordWrap}-${wordWrapWidth}-${letterSpacing}-${maxLines}-${overflowSuffix}`; } /** * Clear layout cache for memory management */ const clearLayoutCache = () => { layoutCache.clear(); }; /** * Add quads for rendering (Canvas doesn't use quads) */ const addQuads = () => { // Canvas renderer doesn't use quad-based rendering // Return null for interface compatibility return null; }; /** * Render quads for Canvas renderer (Canvas doesn't use quad-based rendering) */ const renderQuads = () => { // Canvas renderer doesn't use quad-based rendering // This method is for interface compatibility only }; /** * Canvas Text Renderer - implements TextRenderer interface */ const CanvasTextRenderer = { type, font: CanvasFontHandler, renderText, addQuads, renderQuads, init, clearLayoutCache, }; export default CanvasTextRenderer; //# sourceMappingURL=CanvasTextRenderer.js.map