import { readFile } from 'node:fs/promises'; import path from 'node:path'; import type { Font } from 'satori'; import { SANS, SERIF } from './layout'; /** * Font binaries for the panel renderer, read from `public/fonts` rather than * from `node_modules`. * * The rendered pixels feed a content hash that decides whether the e-ink panel * redraws, so they have to be reproducible. Vendored files make that true; an * install tree or a CDN would not. */ /** * The sans families the panel can be set in. * * More than one exists because choosing a typeface for a 1-bit panel is done * by looking at it thresholded, not by reading its specimen: anti-aliasing is * what most screen faces rely on, and there is none here. */ export const SANS_FAMILIES = { inter: ['inter-400.woff', 'inter-600.woff', 'inter-700.woff'], atkinson: ['atkinson-400.woff', 'atkinson-700.woff', 'atkinson-700.woff'], plex: ['plex-400.woff', 'plex-600.woff', 'plex-700.woff'], } as const; export type SansFamily = keyof typeof SANS_FAMILIES; const WEIGHTS: Font['weight'][] = [400, 600, 700]; const cache = new Map(); export async function loadScreenFonts(family: SansFamily = 'inter'): Promise { const hit = cache.get(family); if (hit) { return hit; } const directory = path.join(process.cwd(), 'public', 'fonts'); const files = SANS_FAMILIES[family]; const fonts = await Promise.all([ ...files.map(async (file, index) => ({ name: SANS, data: await readFile(path.join(directory, file)), weight: WEIGHTS[index] ?? 400, style: 'normal' as const, })), (async () => ({ name: SERIF, data: await readFile(path.join(directory, 'source-serif-600.woff')), weight: 600 as Font['weight'], style: 'normal' as const, }))(), ]); cache.set(family, fonts); return fonts; }