@lightningjs/renderer
Version:
Lightning 3 Renderer
157 lines • 6.03 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 { 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