@lightningjs/renderer
Version:
Lightning 3 Renderer
111 lines • 4.89 kB
JavaScript
/*
* If not stated otherwise in this file or this component's LICENSE file the
* following copyright and licenses apply:
*
* Copyright 2025 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 { CoreFont, FontState } from './CoreFont.js';
import { normalizeFontMetrics } from './TextLayoutEngine.js';
import { hasZeroWidthSpace } from './Utils.js';
export class CanvasFont extends CoreFont {
measureContext;
type = 'canvas';
url;
constructor(textRenderer, props, measureContext) {
super(textRenderer, props);
this.measureContext = measureContext;
this.url = props.url;
this.metrics = props.metrics;
}
load() {
if (this.state !== FontState.Created) {
return;
}
this.state = FontState.Loading;
const waitingNodes = this.waitingNodes;
new FontFace(this.family, `url(${this.url})`)
.load()
.then((loadedFont) => {
document.fonts.add(loadedFont);
this.onLoaded();
})
.catch((error) => {
this.state = FontState.Failed;
console.error(`Failed to load font: ${this.family}`, error);
this.emit('failed');
throw error;
});
}
measureText(text, letterSpacing) {
if (letterSpacing === 0) {
return this.measureContext.measureText(text).width;
}
if (hasZeroWidthSpace(text) === false) {
return (this.measureContext.measureText(text).width +
letterSpacing * text.length);
}
return text.split('').reduce((acc, char) => {
if (hasZeroWidthSpace(char) === true) {
return acc;
}
return acc + this.measureContext.measureText(char).width + letterSpacing;
}, 0);
}
getMetrics(fontSize) {
let m = this.normalizedMetrics[fontSize];
if (m !== undefined) {
return m;
}
let metrics = this.metrics;
if (metrics === undefined) {
metrics = calculateCanvasMetrics(this.family, fontSize, this.measureContext);
}
m = this.normalizedMetrics[fontSize] = normalizeFontMetrics(metrics, fontSize);
return m;
}
}
function calculateCanvasMetrics(fontFamily, fontSize, measureContext) {
// If the font face doesn't have metrics defined, we fallback to using the
// browser's measureText method to calculate take a best guess at the font
// actual font's metrics.
// - fontBoundingBox[Ascent|Descent] is the best estimate but only supported
// in Chrome 87+ (2020), Firefox 116+ (2023), and Safari 11.1+ (2018).
// - It is an estimate as it can vary between browsers.
// - actualBoundingBox[Ascent|Descent] is less accurate and supported in
// Chrome 77+ (2019), Firefox 74+ (2020), and Safari 11.1+ (2018).
// - If neither are supported, we'll use some default values which will
// get text on the screen but likely not be great.
// NOTE: It's been decided not to rely on fontBoundingBox[Ascent|Descent]
// as it's browser support is limited and it also tends to produce higher than
// expected values. It is instead HIGHLY RECOMMENDED that developers provide
// explicit metrics in the font face definition.
measureContext.font = `normal ${fontSize}px Unknown, ${fontFamily}`;
const metrics = measureContext.measureText('ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz');
console.warn(`Font metrics not provided for Canvas Web font ${fontFamily}. ` +
'Using fallback values. It is HIGHLY recommended you use the latest ' +
'version of the Lightning 3 `msdf-generator` tool to extract the default ' +
'metrics for the font and provide them in the Canvas Web font definition.');
const ascender = metrics.fontBoundingBoxAscent ?? metrics.actualBoundingBoxAscent ?? 0;
const descender = metrics.fontBoundingBoxDescent ?? metrics.actualBoundingBoxDescent ?? 0;
return {
ascender,
descender: -descender,
lineGap: (metrics.emHeightAscent ?? 0) +
(metrics.emHeightDescent ?? 0) -
(ascender + descender),
unitsPerEm: (metrics.emHeightAscent ?? 0) + (metrics.emHeightDescent ?? 0),
};
}
//# sourceMappingURL=CanvasFont.js.map