UNPKG

svgtofont

Version:

Converts SVG to TTF/EOT/WOFF/WOFF2/SVG format fonts.

321 lines 12 kB
import { SVGIcons2SVGFontStream } from 'svgicons2svgfont'; import fs from 'fs-extra'; import path from 'path'; import color from 'colors-cli'; import { load } from 'cheerio'; import svg2ttf from 'svg2ttf'; import ttf2eot from 'ttf2eot'; import ttf2woff from 'ttf2woff'; import ttf2woff2 from 'ttf2woff2'; import nunjucks from 'nunjucks'; import { merge } from 'auto-config-loader'; import { log } from './log.js'; let UnicodeObj = {}; /** * Unicode Private Use Area start. * https://en.wikipedia.org/wiki/Private_Use_Areas */ let startUnicode = 0xea01; /** * SVG to SVG font */ export function createSVG(options = {}) { startUnicode = options.startUnicode; UnicodeObj = {}; return new Promise(async (resolve, reject) => { const fontStream = new SVGIcons2SVGFontStream({ ...options.svgicons2svgfont }); function writeFontStream(svgPath) { // file name let _name = path.basename(svgPath, ".svg"); const glyph = fs.createReadStream(svgPath); const curUnicode = String.fromCharCode(startUnicode); const [_curUnicode, _startUnicode] = options.getIconUnicode ? (options.getIconUnicode(_name, curUnicode, startUnicode) || [curUnicode]) : [curUnicode]; if (_startUnicode) startUnicode = _startUnicode; const unicode = [_curUnicode]; if (curUnicode === _curUnicode && (!_startUnicode || startUnicode === _startUnicode)) startUnicode++; UnicodeObj[_name] = unicode[0]; if (!!options.useNameAsUnicode) { unicode[0] = _name; UnicodeObj[_name] = _name; } if (!!options.addLigatures) { unicode.push(_name); } glyph.metadata = { unicode, name: _name }; fontStream.write(glyph); } const DIST_PATH = path.join(options.dist, options.fontName + ".svg"); // Setting the font destination fontStream.pipe(fs.createWriteStream(DIST_PATH)) .on("finish", () => { log.log(`${color.green('SUCCESS')} ${color.blue_bt('SVG')} font successfully created!\n ╰┈▶ ${DIST_PATH}`); resolve(UnicodeObj); }) .on("error", (err) => { if (err) { reject(err); } }); filterSvgFiles(options.src).forEach((svg) => { if (typeof svg !== 'string') return false; writeFontStream(svg); }); // Do not forget to end the stream fontStream.end(); }); } /** * Converts a string to pascal case. * * @example * * ```js * toPascalCase('some_database_field_name'); // 'SomeDatabaseFieldName' * toPascalCase('Some label that needs to be pascalized'); * // 'SomeLabelThatNeedsToBePascalized' * toPascalCase('some-javascript-property'); // 'SomeJavascriptProperty' * toPascalCase('some-mixed_string with spaces_underscores-and-hyphens'); * // 'SomeMixedStringWithSpacesUnderscoresAndHyphens' * ``` */ export const toPascalCase = (str) => str .match(/[A-Z]{2,}(?=[A-Z][a-z]+[0-9]*|\b)|[A-Z]?[a-z]+[0-9]*|[A-Z]|[0-9]+/g) .map(x => x.charAt(0).toUpperCase() + x.slice(1).toLowerCase()) .join(''); /* * Filter svg files * @return {Array} svg files */ export function filterSvgFiles(svgFolderPath) { let files = fs.readdirSync(svgFolderPath, 'utf-8'); let svgArr = []; if (!files) { throw new Error(`Error! Svg folder is empty.${svgFolderPath}`); } for (let i in files) { if (typeof files[i] !== 'string' || path.extname(files[i]) !== '.svg') continue; if (!~svgArr.indexOf(files[i])) { svgArr.push(path.join(svgFolderPath, files[i])); } } return svgArr; } export function snakeToUppercase(str) { return str.split(/[-_]/) .map(partial => partial.charAt(0).toUpperCase() + partial.slice(1)) .join(''); } /** * Create typescript declarations for icon classnames */ export async function createTypescript(options) { const tsOptions = options.typescript === true ? {} : options.typescript; const uppercaseFontName = snakeToUppercase(options.fontName); const { extension = 'd.ts', enumName = uppercaseFontName } = tsOptions; const DIST_PATH = path.join(options.dist, `${options.fontName}.${extension}`); const fileNames = filterSvgFiles(options.src).map(svgPath => path.basename(svgPath, path.extname(svgPath))); await fs.writeFile(DIST_PATH, [ `export enum ${enumName} {`, ...fileNames.map(name => ` ${snakeToUppercase(name)} = "${options.classNamePrefix}-${name}",`), '}', `export type ${enumName}Classname = ${fileNames.map(name => `"${options.classNamePrefix}-${name}"`).join(' | ')}`, `export type ${enumName}Icon = ${fileNames.map(name => `"${name}"`).join(' | ')}`, `export const ${enumName}Prefix = "${options.classNamePrefix}-"`, ].join('\n')); log.log(`${color.green('SUCCESS')} Created ${DIST_PATH}`); } /** * SVG font to TTF */ export function createTTF(options = {}) { return new Promise((resolve, reject) => { options.svg2ttf = options.svg2ttf || {}; const DIST_PATH = path.join(options.dist, options.fontName + ".ttf"); let ttf = svg2ttf(fs.readFileSync(path.join(options.dist, options.fontName + ".svg"), "utf8"), options.svg2ttf); const ttfBuf = Buffer.from(ttf.buffer); fs.writeFile(DIST_PATH, ttfBuf, (err) => { if (err) { return reject(err); } log.log(`${color.green('SUCCESS')} ${color.blue_bt('TTF')} font successfully created!\n ╰┈▶ ${DIST_PATH}`); resolve(ttfBuf); }); }); } ; /** * TTF font to EOT */ export function createEOT(options = {}, ttf) { return new Promise((resolve, reject) => { const DIST_PATH = path.join(options.dist, options.fontName + '.eot'); const eot = Buffer.from(ttf2eot(ttf).buffer); fs.writeFile(DIST_PATH, eot, (err) => { if (err) { return reject(err); } log.log(`${color.green('SUCCESS')} ${color.blue_bt('EOT')} font successfully created!\n ╰┈▶ ${DIST_PATH}`); resolve(eot); }); }); } ; /** * TTF font to WOFF */ export function createWOFF(options = {}, ttf) { return new Promise((resolve, reject) => { const DIST_PATH = path.join(options.dist, options.fontName + ".woff"); const woff = Buffer.from(ttf2woff(ttf).buffer); fs.writeFile(DIST_PATH, woff, (err) => { if (err) { return reject(err); } log.log(`${color.green('SUCCESS')} ${color.blue_bt('WOFF')} font successfully created!\n ╰┈▶ ${DIST_PATH}`); resolve(woff); }); }); } ; /** * TTF font to WOFF2 */ export function createWOFF2(options = {}, ttf) { return new Promise((resolve, reject) => { const DIST_PATH = path.join(options.dist, options.fontName + ".woff2"); const woff2 = Buffer.from(ttf2woff2(ttf).buffer); fs.writeFile(DIST_PATH, woff2, (err) => { if (err) { return reject(err); } log.log(`${color.green('SUCCESS')} ${color.blue_bt('WOFF2')} font successfully created!\n ╰┈▶ ${DIST_PATH}`); resolve({ path: DIST_PATH }); }); }); } ; /** * Create SVG Symbol */ export function createSvgSymbol(options = {}) { const DIST_PATH = path.join(options.dist, `${options.fontName}.symbol.svg`); const $ = load('<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="0" height="0" style="display:none;"></svg>'); return new Promise((resolve, reject) => { filterSvgFiles(options.src).forEach(svgPath => { const fileName = path.basename(svgPath, path.extname(svgPath)); const file = fs.readFileSync(svgPath, "utf8"); const svgNode = $(file); const symbolNode = $("<symbol></symbol>"); symbolNode.attr("viewBox", svgNode.attr("viewBox")); symbolNode.attr("id", `${options.classNamePrefix}-${fileName}`); symbolNode.append(svgNode.html()); $('svg').append(symbolNode); }); fs.writeFile(DIST_PATH, $.html("svg"), (err) => { if (err) { return reject(err); } log.log(`${color.green('SUCCESS')} ${color.blue_bt('Svg Symbol')} font successfully created!\n ╰┈▶ ${DIST_PATH}`); resolve({ path: DIST_PATH, svg: $.html("svg") }); }); }); } ; // As we are processing css files, we need to eacape HTML entities. const safeNunjucks = nunjucks.configure({ autoescape: false }); /** * Copy template files */ export async function copyTemplate(inDir, outDir, { _opts, ...vars }) { const files = await fs.readdir(inDir, { withFileTypes: true }); const context = { ...(_opts.templateVars || {}), ...vars, cssPath: _opts.cssPath || '', filename: _opts.fileName || vars.fontname, }; await fs.ensureDir(outDir); for (const file of files) { if (!file.isFile()) continue; if (_opts.include && !(new RegExp(_opts.include)).test(file.name)) continue; let newFileName = file.name.replace(/\.template$/, '').replace(/^_/, ''); for (const key in context) newFileName = newFileName.replace(`{{${key}}}`, `${context[key]}`); const template = await fs.readFile(path.join(inDir, file.name), 'utf8'); const content = safeNunjucks.renderString(template, context); const filePath = path.join(outDir, newFileName); await fs.writeFile(filePath, content); log.log(`${color.green('SUCCESS')} Created ${filePath} `); } } ; /** * Create HTML */ export function createHTML(templatePath, data) { return nunjucks.renderString(fs.readFileSync(templatePath, 'utf8'), { ...data, Date: Date, JSON: JSON, Math: Math, Number: Number, Object: Object, RegExp: RegExp, String: String, typeof: (v) => typeof v, }); } ; export function generateFontFaceCSS(fontName, cssPath, timestamp, excludeFormat, hasTimestamp = true) { const timestamString = hasTimestamp === true ? `?t=${timestamp}` : (typeof hasTimestamp == 'string' ? `?t=${hasTimestamp}` : undefined); const formats = [ { ext: 'eot', format: 'embedded-opentype', ieFix: true }, { ext: 'woff2', format: 'woff2' }, { ext: 'woff', format: 'woff' }, { ext: 'ttf', format: 'truetype' }, { ext: 'svg', format: 'svg' } ]; let cssString = ` font-family: "${fontName}";\n`; if (!excludeFormat.includes('eot')) { cssString += ` src: url('${cssPath}${fontName}.eot${timestamString || ''}'); /* IE9*/\n`; } cssString += ' src: '; const srcParts = formats .filter(format => !excludeFormat.includes(format.ext)) .map(format => { if (format.ext === 'eot') { return `url('${cssPath}${fontName}.eot${timestamString || '?'}#iefix') format('${format.format}') /* IE6-IE8 */`; } return `url('${cssPath}${fontName}.${format.ext}${timestamString || ''}') format('${format.format}')`; }); cssString += srcParts.join(',\n ') + ';'; return cssString; } export const getDefaultOptions = (options) => { return merge({ dist: path.resolve(process.cwd(), 'fonts'), src: path.resolve(process.cwd(), 'svg'), startUnicode: 0xea01, svg2ttf: {}, svgicons2svgfont: { fontName: 'iconfont', }, fontName: 'iconfont', symbolNameDelimiter: '-', }, options); }; //# sourceMappingURL=utils.js.map