// Builds small self-hosted variable fonts (Latin + Turkish + symbols used in posts) into public/fonts/. // Sources: @fontsource-variable/{rubik,inter} (SIL OFL 1.1). Usage: npm run fonts import { mkdirSync, readFileSync, writeFileSync } from 'node:fs'; import { createRequire } from 'node:module'; import subsetFont from 'subset-font'; const require = createRequire(import.meta.url); const ranges = [ [0x20, 0x7e], // Basic Latin [0xa0, 0xff], // Latin-1 (ç ö ü · ° × …) [0x11e, 0x11f], // Ğ ğ [0x130, 0x131], // İ ı [0x15e, 0x15f], // Ş ş [0x394, 0x394], // Δ [0x3a9, 0x3a9], // Ω [0x3bb, 0x3bc], // λ μ [0x2013, 0x2014], // – — [0x2018, 0x201e], // ‘ ’ ‚ “ ” „ [0x2022, 0x2022], // • [0x2026, 0x2026], // … [0x2032, 0x2033], // ′ ″ [0x20ba, 0x20ba], // ₺ [0x2122, 0x2122], // ™ [0x2190, 0x2193], // ← ↑ → ↓ [0x2212, 0x2212], // − [0x2248, 0x2248], // ≈ [0x2264, 0x2265], // ≤ ≥ ]; const text = ranges .flatMap(([a, b]) => Array.from({ length: b - a + 1 }, (_, i) => String.fromCodePoint(a + i))) .join(''); // Fontsource ships each family split by unicode-range (latin / latin-ext). Each part is subset // on its own and keeps Fontsource's unicode-range, so browsers fetch only the parts a page needs. const families = [ { pkg: '@fontsource-variable/rubik', family: 'Rubik Variable', slug: 'rubik', wght: [400, 900] }, { pkg: '@fontsource-variable/inter', family: 'Inter Variable', slug: 'inter', wght: [400, 700] }, ]; const parts = ['latin', 'latin-ext']; /** unicode-range of one Fontsource part, read from the package's own CSS. */ function unicodeRange(pkg, slug, part) { const css = readFileSync(require.resolve(`${pkg}/index.css`), 'utf8'); const block = css .split('@font-face') .find((b) => b.includes(`${slug}-${part}-wght-normal.woff2`)); const m = block?.match(/unicode-range:\s*([^;]+);/); if (!m) throw new Error(`unicode-range not found for ${slug} ${part}`); return m[1].trim(); } mkdirSync('public/fonts', { recursive: true }); const faces = []; for (const f of families) { for (const part of parts) { const source = readFileSync( require.resolve(`${f.pkg}/files/${f.slug}-${part}-wght-normal.woff2`), ); const out = await subsetFont(source, text, { targetFormat: 'woff2', variationAxes: { wght: { min: f.wght[0], max: f.wght[1] } }, }); const file = `${f.slug}-${part}.woff2`; writeFileSync(`public/fonts/${file}`, out); console.log(`${file}: ${source.length} → ${out.length} bytes`); faces.push(`@font-face { font-family: '${f.family}'; font-style: normal; font-display: swap; font-weight: ${f.wght[0]} ${f.wght[1]}; src: url('/fonts/${file}') format('woff2-variations'); unicode-range: ${unicodeRange(f.pkg, f.slug, part)}; }`); } } writeFileSync( 'src/styles/fonts.css', `/* Generated by scripts/subset-fonts.mjs — do not edit. Fonts: SIL OFL 1.1 (Rubik, Inter). */\n${faces.join('\n')}\n`, ); console.log('Wrote src/styles/fonts.css');