#!/usr/bin/env node /** * Regenerate the committed country-flag font subset. * * npm run subset-flags (or: node website/tools/subset-flag-font.mjs) * * Extracts every flag emoji used in website HTML/locales, subsets * assets/TwemojiCountryFlags.woff2 down to exactly those flags via Python * fontTools (harfbuzz-based JS subsetters drop the COLR color tables and * produce invisible flags), and writes: * * assets/TwemojiCountryFlags.subset.woff2 (committed) * assets/TwemojiCountryFlags.subset.json (committed manifest) * * The website build (build.cjs) is pure Node: it only validates the * manifest and fails when a flag was added without re-running this tool. * * Requires python3 with fonttools + brotli. Override the interpreter with * the PYTHON env var, e.g. PYTHON=.venv/bin/python npm run subset-flags */ import { execFileSync } from 'node:child_process'; import crypto from 'node:crypto'; import fs from 'node:fs'; import path from 'node:path'; import { fileURLToPath } from 'node:url'; import { createRequire } from 'node:module'; const require = createRequire(import.meta.url); const websiteDir = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..'); const { collectFlagSourceFiles, extractUsedFlags, flagToCountryCode } = require(path.join(websiteDir, 'flag-font-utils.cjs')); const sourceFont = path.join(websiteDir, 'assets', 'TwemojiCountryFlags.woff2'); const outputFont = path.join(websiteDir, 'assets', 'TwemojiCountryFlags.subset.woff2'); const manifestPath = path.join(websiteDir, 'assets', 'TwemojiCountryFlags.subset.json'); const python = process.env.PYTHON || 'python3'; try { execFileSync(python, ['-c', 'import fontTools.subset, brotli'], { stdio: 'pipe' }); } catch { console.error(`error: ${python} lacks fonttools/brotli.`); console.error('Install them (e.g. "pip3 install fonttools brotli") or point PYTHON at an interpreter that has them:'); console.error(' PYTHON=/path/to/venv/bin/python npm run subset-flags'); process.exit(1); } const flags = extractUsedFlags(collectFlagSourceFiles(websiteDir)); if (flags.length === 0) { console.error('error: no flag emoji found in website sources — extraction is broken'); process.exit(1); } const codes = flags.map(flagToCountryCode); console.log(`Found ${codes.length} flags: ${codes.join(' ')}`); execFileSync(python, [ path.join(websiteDir, 'tools', 'subset_flag_font.py'), sourceFont, outputFont, codes.join(',') ], { stdio: 'inherit' }); const sha = buf => crypto.createHash('sha256').update(buf).digest('hex'); const manifest = { comment: 'Generated by tools/subset-flag-font.mjs — do not edit. build.cjs fails when site flags and this manifest diverge.', flags: codes, sourceFontSha256: sha(fs.readFileSync(sourceFont)), subsetSha256: sha(fs.readFileSync(outputFont)) }; fs.writeFileSync(manifestPath, JSON.stringify(manifest, null, 2) + '\n'); const before = fs.statSync(sourceFont).size; const after = fs.statSync(outputFont).size; console.log(`Wrote ${path.relative(process.cwd(), outputFont)} (${(after / 1024).toFixed(1)} KB, -${((1 - after / before) * 100).toFixed(0)}% vs source) + manifest.`); console.log('Commit both files.');