UNPKG

@pmndrs/uikit

Version:

Build performant 3D user interfaces with Three.js and yoga.

146 lines (145 loc) 5.13 kB
import { computed, effect, signal } from '@preact/signals-core'; import { loadCachedFont } from './cache.js'; import { inter } from '@pmndrs/msdfonts'; const fontWeightNames = { thin: 100, 'extra-light': 200, light: 300, normal: 400, medium: 500, 'semi-bold': 600, bold: 700, 'extra-bold': 800, black: 900, 'extra-black': 950, }; const defaultFontFamiles = { inter, }; export function computedFontFamilies(properties, parent) { return computed(() => { const currentFontFamilies = properties.value.fontFamilies; const inheritedFontFamilies = parent.value?.fontFamilies.value; if (inheritedFontFamilies == null) { return currentFontFamilies; } if (currentFontFamilies == null) { return inheritedFontFamilies; } return { ...inheritedFontFamilies, ...currentFontFamilies, }; }); } export function computedFont(properties, fontFamiliesSignal) { const result = signal(undefined); effect(() => { let fontWeight = properties.value.fontWeight; if (typeof fontWeight === 'string') { fontWeight = parseFloat(fontWeight); if (isNaN(fontWeight)) { fontWeight = properties.value.fontWeight; if (!(fontWeight in fontWeightNames)) { throw new Error(`unknown font weight "${fontWeight}"`); } fontWeight = fontWeightNames[fontWeight]; } } let fontFamily = properties.value.fontFamily; const fontFamilies = fontFamiliesSignal.value ?? defaultFontFamiles; fontFamily ??= Object.keys(fontFamilies)[0]; let fontFamilyWeightMap = fontFamilies[fontFamily]; if (fontFamilyWeightMap == null) { const availableFontFamilyList = Object.keys(fontFamilies); fontFamilyWeightMap = fontFamilies[availableFontFamilyList[0]]; console.error(`unknown font family "${fontFamily}". Available font families are ${availableFontFamilyList.map((name) => `"${name}"`).join(', ')}. Falling back to "${availableFontFamilyList[0]}".`); } const url = getMatchingFontUrl(fontFamilyWeightMap, fontWeight); let aborted = false; loadCachedFont(url, (font) => !aborted && (result.value = font)); return () => (aborted = true); }); return result; } function getMatchingFontUrl(fontFamily, weight) { let distance = Infinity; let result; for (const fontWeight in fontFamily) { const d = Math.abs(weight - getWeightNumber(fontWeight)); if (d === 0) { return fontFamily[fontWeight]; } if (d < distance) { distance = d; result = fontFamily[fontWeight]; } } if (result == null) { throw new Error(`font family has no entries ${fontFamily}`); } return result; } function getWeightNumber(value) { if (value in fontWeightNames) { return fontWeightNames[value]; } const number = parseFloat(value); if (isNaN(number)) { throw new Error(`invalid font weight "${value}"`); } return number; } export class Font { page; glyphInfoMap = new Map(); kerningMap = new Map(); questionmarkGlyphInfo; //needed in the shader: pageWidth; pageHeight; distanceRange; constructor(info, page) { this.page = page; const { scaleW, scaleH, lineHeight } = info.common; this.pageWidth = scaleW; this.pageHeight = scaleH; this.distanceRange = info.distanceField.distanceRange; const { size } = info.info; for (const glyph of info.chars) { glyph.uvX = glyph.x / scaleW; glyph.uvY = glyph.y / scaleH; glyph.uvWidth = glyph.width / scaleW; glyph.uvHeight = glyph.height / scaleH; glyph.width /= size; glyph.height /= size; glyph.xadvance /= size; glyph.xoffset /= size; glyph.yoffset -= lineHeight - size; glyph.yoffset /= size; this.glyphInfoMap.set(glyph.char, glyph); } for (const { first, second, amount } of info.kernings) { this.kerningMap.set(`${first}/${second}`, amount / size); } const questionmarkGlyphInfo = this.glyphInfoMap.get('?'); if (questionmarkGlyphInfo == null) { throw new Error("missing '?' glyph in font"); } this.questionmarkGlyphInfo = questionmarkGlyphInfo; } getGlyphInfo(char) { return (this.glyphInfoMap.get(char) ?? (char == '\n' ? this.glyphInfoMap.get(' ') : this.questionmarkGlyphInfo) ?? this.questionmarkGlyphInfo); } getKerning(firstId, secondId) { return this.kerningMap.get(`${firstId}/${secondId}`) ?? 0; } } export function glyphIntoToUV(info, target, offset) { target[offset + 0] = info.uvX; target[offset + 1] = info.uvY + info.uvHeight; target[offset + 2] = info.uvWidth; target[offset + 3] = -info.uvHeight; }