Files
flaticon-uicons/build-fonts.js
T
ubunteroz ae66c2e34c Initial commit: Flaticon UIcons package with local font build system
Add 50,000+ icon font package sourced from Flaticon API with local
webfonts and interactive icon explorer.

Features:
- 50,492 icons across 15 style variations (weight × corner)
- Self-hosted webfonts (TTF, WOFF, WOFF2)
- Interactive icon explorer with search and filters
- FontForge-based build pipeline for generating fonts from SVGs
- Drop-in CSS with class-based icon usage

Build scripts:
- scripts/build-font.py - Standalone FontForge Python script
- build-fonts.js - Node.js orchestrator for font generation
- update-icon-list.js - Fetch icon metadata from Flaticon API
- build-icons-js.js - Generate browser-ready icon dataset

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Co-Authored-By: Z.ai GLM 4.7 <noreply@z.ai>
2026-01-24 10:58:23 +08:00

172 lines
5.7 KiB
JavaScript

const fs = require('fs');
const path = require('path');
const { spawnSync } = require('child_process');
const PREFIX_CONFIG = {
rs: { label: 'Regular Straight', file: 'regular-straight' },
rr: { label: 'Regular Rounded', file: 'regular-rounded' },
bs: { label: 'Bold Straight', file: 'bold-straight' },
br: { label: 'Bold Rounded', file: 'bold-rounded' },
ss: { label: 'Solid Straight', file: 'solid-straight' },
sr: { label: 'Solid Rounded', file: 'solid-rounded' },
ts: { label: 'Thin Straight', file: 'thin-straight' },
tr: { label: 'Thin Rounded', file: 'thin-rounded' },
rc: { label: 'Regular Chubby', file: 'regular-chubby' },
sc: { label: 'Solid Chubby', file: 'solid-chubby' },
tc: { label: 'Thin Chubby', file: 'thin-chubby' },
ds: { label: 'Duotone Straight', file: 'duotone-straight' },
dr: { label: 'Duotone Rounded', file: 'duotone-rounded' },
dc: { label: 'Duotone Chubby', file: 'duotone-chubby' },
brands: { label: 'Brands', file: 'brands' }
};
function runFontForge({ prefix, outputDir, clean }) {
const config = PREFIX_CONFIG[prefix] || { label: prefix, file: prefix };
const args = [
'scripts/build-font.py',
prefix,
outputDir,
clean ? 'true' : 'false',
config.label,
config.file
];
const result = spawnSync('python3', args, {
stdio: 'inherit'
});
if (result.status !== 0) {
process.exit(result.status || 1);
}
}
function buildCss({ order, mapping, outputPath }) {
const lines = [];
lines.push('/*!');
lines.push(' * Flaticon Icon Fonts - Local Build');
lines.push(' * Generated from Flaticon icon API');
lines.push(' */');
lines.push('');
lines.push('[class^="fi-"], [class*=" fi-"] {');
lines.push(' display: inline-block;');
lines.push(' font-style: normal;');
lines.push(' font-weight: normal !important;');
lines.push(' font-variant: normal;');
lines.push(' text-transform: none;');
lines.push(' line-height: 1;');
lines.push(' -webkit-font-smoothing: antialiased;');
lines.push(' -moz-osx-font-smoothing: grayscale;');
lines.push('}');
lines.push('');
order.forEach(prefix => {
const config = PREFIX_CONFIG[prefix] || { label: prefix, file: prefix };
const label = config.label;
const fileSuffix = config.file;
const names = mapping[prefix] || [];
lines.push(`/* ${label} (${prefix}) */`);
lines.push('@font-face {');
lines.push(` font-family: "flaticon-${fileSuffix}";`);
lines.push(
` src: url("./webfonts/flaticon-${fileSuffix}.woff2") format("woff2"),`
);
lines.push(
` url("./webfonts/flaticon-${fileSuffix}.woff") format("woff"),`
);
lines.push(
` url("./webfonts/flaticon-${fileSuffix}.ttf") format("truetype");`
);
lines.push(' font-display: swap;');
lines.push('}');
lines.push('');
lines.push(
`i[class^="fi-${prefix}-"]:before, i[class*=" fi-${prefix}-"]:before,`
);
lines.push(
`span[class^="fi-${prefix}-"]:before, span[class*=" fi-${prefix}-"]:before {`
);
lines.push(` font-family: flaticon-${fileSuffix} !important;`);
lines.push(' font-style: normal;');
lines.push(' font-weight: normal !important;');
lines.push('}');
lines.push('');
const baseCodepoint = 0xe001;
names.forEach((name, index) => {
const codepoint = baseCodepoint + index;
lines.push(`.fi-${prefix}-${name}:before {`);
lines.push(` content: "\\${codepoint.toString(16).padStart(4, '0')}";`);
lines.push('}');
});
lines.push('');
});
lines.push('.fi { display: inline-block; font-style: normal; }');
lines.push('');
fs.writeFileSync(outputPath, lines.join('\n'));
}
function loadIcons() {
const raw = fs.readFileSync(path.join(__dirname, 'data', 'all_icons.json'), 'utf8');
const icons = JSON.parse(raw);
const iconsByPrefix = {};
icons.forEach(icon => {
const prefix = icon.prefix;
const name = icon.name;
if (!prefix || !name) return;
if (!iconsByPrefix[prefix]) iconsByPrefix[prefix] = new Set();
iconsByPrefix[prefix].add(name);
});
const result = {};
Object.keys(iconsByPrefix).forEach(prefix => {
result[prefix] = Array.from(iconsByPrefix[prefix]).sort();
});
return result;
}
function parseArgs(argv) {
const args = {};
for (let i = 0; i < argv.length; i += 1) {
const arg = argv[i];
if (!arg.startsWith('--')) continue;
const key = arg.slice(2);
const value = argv[i + 1] && !argv[i + 1].startsWith('--') ? argv[i + 1] : true;
if (value !== true) i += 1;
args[key] = value;
}
return args;
}
function main() {
const args = parseArgs(process.argv.slice(2));
const prefixes = args.prefix ? String(args.prefix).split(',') : Object.keys(PREFIX_CONFIG);
const clean = Boolean(args.clean);
const outputDir = args.outputDir || 'fonts/webfonts';
const cssPath = args.css || 'fonts/flaticon.css';
const iconsByPrefix = loadIcons();
const buildOrder = Object.keys(PREFIX_CONFIG).filter(prefix => prefixes.includes(prefix));
buildOrder.forEach(prefix => {
if (!iconsByPrefix[prefix] || iconsByPrefix[prefix].length === 0) {
console.warn(`Skipping ${prefix}: no icons found.`);
return;
}
runFontForge({ prefix, outputDir, clean });
});
buildCss({
order: buildOrder,
mapping: iconsByPrefix,
outputPath: cssPath
});
console.log('Done.');
}
main();