grunt-webfonts
Version:
SVG to webfont converter for Grunt
226 lines (198 loc) • 5.36 kB
JavaScript
/**
* grunt-webfont: Node.js engine
*
* @requires ttfautohint 1.00+ (optional)
* @license
* Copyright Andrey Chalkin <L2jLiga> and Artem Sapegin (http://sapegin.me). All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://github.com/L2jLiga/grunt-webfonts/LICENSE
*/
;
const fs = require('node:fs');
const path = require('node:path');
const async = require('async');
const temp = require('temp');
const exec = require('node:child_process').exec;
const SVGIcons2SVGFontStream = require('svgicons2svgfont');
const svg2ttf = require('svg2ttf');
const ttf2eot = require('ttf2eot');
const ttf2woff = require('ttf2woff');
module.exports = function nodeEngine(o, allDone) {
const logger = o.logger;
const wf = require('../util/util');
const fonts = {};
const generators = {
svg(done) {
let font = '';
svgFilesToStreams(o.files, (streams) => {
const fontStream = new SVGIcons2SVGFontStream({
fontName: o.fontFamilyName,
fontHeight: o.fontHeight,
descent: o.descent,
centerHorizontally: o.centerHorizontally,
normalize: o.normalize,
round: o.round,
log: logger.verbose.bind(logger),
error: logger.error.bind(logger),
});
fontStream.on('data', (chunk) => {
font += chunk.toString('utf8');
});
fontStream.on('finish', () => {
fonts.svg = font;
done(font);
});
streams.forEach((glyphStream) => fontStream.write(glyphStream));
fontStream.end();
});
},
ttf(done) {
getFont('svg', (svgFont) => {
let font = svg2ttf(svgFont, {});
font = Buffer.from(font.buffer);
autohintTtfFont(font, (hintedFont) => {
// ttfautohint is optional
if (hintedFont) {
font = hintedFont;
}
fonts.ttf = font;
done(font);
});
});
},
woff(done) {
getFont('ttf', (ttfFont) => {
let font = ttf2woff(new Uint8Array(ttfFont), {});
font = Buffer.from(font);
fonts.woff = font;
done(font);
});
},
woff2(done) {
// Will be converted from TTF later
getFont('ttf', () => done());
},
eot(done) {
getFont('ttf', (ttfFont) => {
let font = ttf2eot(new Uint8Array(ttfFont));
font = Buffer.from(font);
fonts.eot = font;
done(font);
});
},
};
// Font types
const steps = [];
o.types.forEach((type) => {
steps.push(createFontWriter(type));
});
// Run!
async.waterfall(steps, allDone);
/**
* Get font
*
* @param {String} type
* @param {Function} done
*/
function getFont(type, done) {
if (fonts[type]) {
done(fonts[type]);
} else {
generators[type](done);
}
}
/**
* Create font writer
* @param {String} type
* @return {Function}
*/
function createFontWriter(type) {
return (done) => {
getFont(type, (font) => {
if (font) fs.writeFileSync(wf.getFontPath(o, type), font);
done();
});
};
}
/**
* Convert svg files to streams
*
* @param {Array} files
* @param {Function} done
*/
function svgFilesToStreams(files, done) {
async.map(files, (file, fileDone) => {
/**
* File streamed
*
* @param {String} name
* @param {Stream} stream
*/
function fileStreamed(name, stream) {
stream.metadata = {
unicode: [String.fromCodePoint(o.codepoints[name])],
name,
};
if (o.ligaturesOnly) {
stream.metadata.unicode = [name];
} else if (o.addLigatures) {
stream.metadata.unicode.push(name);
}
fileDone(null, stream);
}
const idx = files.indexOf(file);
const name = o.glyphs[idx];
fileStreamed(name, fs.createReadStream(file, 'utf-8'));
}, (err, streams) => {
if (err) {
logger.error('Can’t stream SVG file.\n\n' + err);
allDone(false);
} else {
done(streams);
}
});
}
/**
* Call to autohint
*
* @param {File} font
* @param {Function} done
*/
function autohintTtfFont(font, done) {
const tempDir = temp.mkdirSync();
const originalFilepath = path.join(tempDir, 'font.ttf');
const hintedFilepath = path.join(tempDir, 'hinted.ttf');
if (!o.autoHint) {
done(false);
return;
}
// Save original font to temporary directory
fs.writeFileSync(originalFilepath, font);
// Run ttfautohint
const args = [
'ttfautohint',
'--symbol',
'--fallback-script=latn',
'--windows-compatibility',
'--no-info',
originalFilepath,
hintedFilepath,
].join(' ');
exec(args, {maxBuffer: o.execMaxBuffer}, (err) => {
if (err) {
if (err.code === 127) {
logger.verbose('Hinting skipped, ttfautohint not found.');
done(false);
return;
}
logger.error('Can’t run ttfautohint.\n\n' + err.message);
done(false);
return;
}
// Read hinted font back
const hintedFont = fs.readFileSync(hintedFilepath);
done(hintedFont);
});
}
};