ae66c2e34c
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>
172 lines
5.7 KiB
JavaScript
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();
|