UNPKG

@lightningjs/renderer

Version:
142 lines 5.02 kB
/* * 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 { buildGlyphMap, buildKerningTable, } from './SdfFontHandler.js'; import { normalizeFontMetrics } from './TextLayoutEngine.js'; import { hasZeroWidthSpace } from './Utils.js'; export class SdfFont extends CoreFont { stage; type = 'sdf'; atlasUrl; atlasDataUrl; atlasTexture; glyphMap; kerningTable; data; constructor(textRenderer, props, stage) { super(textRenderer, props); this.stage = stage; this.atlasUrl = props.atlasUrl; this.atlasDataUrl = props.atlasDataUrl; } load() { new Promise(async () => { const atlasData = await fetch(this.atlasDataUrl); if (atlasData.ok === false) { this.hardFail(`Failed to load font data: ${atlasData.statusText}`); } const fontData = (await atlasData.json()); if (fontData.chars === undefined) { this.hardFail('Invalid SDF font data format'); } const atlasTexture = this.stage.txManager.createTexture('ImageTexture', { src: this.atlasUrl, premultiplyAlpha: false, }); atlasTexture.setRenderableOwner(this.family, true); atlasTexture.preventCleanup = true; atlasTexture.on('loaded', () => { this.onLoaded(); }); atlasTexture.on('failed', (error) => { console.error(`Failed to load SDF font: ${this.family}`, error); this.emit('failed'); }); this.atlasTexture = atlasTexture; this.processFontData(fontData); }); } hardFail(message) { this.state = FontState.Failed; throw new Error(message); } processFontData(fontData) { this.glyphMap = buildGlyphMap(fontData.chars); this.kerningTable = buildKerningTable(fontData.kernings); this.metrics = this.metrics || fontData.lightningMetrics || { ascender: 800, descender: -200, lineGap: 200, unitsPerEm: 1000, }; this.data = fontData; } measureText(text, letterSpacing) { if (text.length === 1) { const char = text.charAt(0); const codepoint = text.codePointAt(0); if (codepoint === undefined) return 0; if (hasZeroWidthSpace(char) === true) return 0; const glyph = this.getGlyph(codepoint); if (glyph === null) return 0; return glyph.xadvance + letterSpacing; } let width = 0; let prevCodepoint = 0; for (let i = 0; i < text.length; i++) { const char = text.charAt(i); const codepoint = text.codePointAt(i); if (codepoint === undefined) continue; // Skip zero-width spaces in width calculations if (hasZeroWidthSpace(char)) { continue; } const glyph = this.getGlyph(codepoint); if (glyph === null) continue; let advance = glyph.xadvance; // Add kerning if there's a previous character if (prevCodepoint !== 0) { const kerning = this.getKerning(prevCodepoint, codepoint); advance += kerning; } width += advance + letterSpacing; prevCodepoint = codepoint; } return width; } getMetrics(fontSize) { let m = this.normalizedMetrics[fontSize]; if (m !== undefined) { return m; } m = this.normalizedMetrics[fontSize] = normalizeFontMetrics(this.metrics, fontSize); return m; } getGlyph(codepoint) { const gm = this.glyphMap; return gm[codepoint] || gm[63] || null; } getKerning(firstGlyph, secondGlyph) { const seconds = this.kerningTable[secondGlyph]; return (seconds !== undefined && seconds[firstGlyph]) || 0; } getAtlas() { return this.atlasTexture; } getData() { return this.data; } } //# sourceMappingURL=SdfFont.js.map