material-design-icons-iconfont
Version:
Material Design icons DX
251 lines (199 loc) • 10.1 kB
JavaScript
const fs = require('fs');
const path = require('path');
const mkdirp = require('mkdirp');
const {promisify} = require('util');
const sass = require('node-sass');
const request = require('request');
const rp = require('request-promise-native');
const fontkit = require('fontkit');
const sha256File = require('sha256-file');
const crypto = require('crypto');
const sortByKeys = require('sort-keys');
const DIST_DIR_PATH = path.resolve(__dirname, '..', 'dist');
const FONTS_DIR_PATH = path.resolve(DIST_DIR_PATH, 'fonts');
const SRC_DIR_PATH = path.resolve(__dirname, '..', 'src');
const USER_AGENTS = `
Mozilla/5.0 (X11; U; Linux x86_64; en-US; rv:1.9.1.3) Gecko/20090913 Firefox/3.5.3
Mozilla/5.0 (Windows; U; Windows NT 6.1; en; rv:1.9.1.3) Gecko/20090824 Firefox/3.5.3 (.NET CLR 3.5.30729)
Mozilla/5.0 (Windows; U; Windows NT 5.2; en-US; rv:1.9.1.3) Gecko/20090824 Firefox/3.5.3 (.NET CLR 3.5.30729)
Mozilla/5.0 (Windows; U; Windows NT 6.1; en-US; rv:1.9.1.1) Gecko/20090718 Firefox/3.5.1
Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US) AppleWebKit/532.1 (KHTML, like Gecko) Chrome/4.0.219.6 Safari/532.1
Mozilla/4.0 (compatible; MSIE 8.0; Windows NT 6.1; WOW64; Trident/4.0; SLCC2; .NET CLR 2.0.50727; InfoPath.2)
Mozilla/4.0 (compatible; MSIE 8.0; Windows NT 6.0; Trident/4.0; SLCC1; .NET CLR 2.0.50727; .NET CLR 1.1.4322; .NET CLR 3.5.30729; .NET CLR 3.0.30729)
Mozilla/4.0 (compatible; MSIE 8.0; Windows NT 5.2; Win64; x64; Trident/4.0)
Mozilla/4.0 (compatible; MSIE 8.0; Windows NT 5.1; Trident/4.0; SV1; .NET CLR 2.0.50727; InfoPath.2)Mozilla/5.0 (Windows; U; MSIE 7.0; Windows NT 6.0; en-US)
Mozilla/4.0 (compatible; MSIE 6.1; Windows XP)
Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/42.0.2311.135 Safari/537.36 Edge/12.246
Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/41.0.2227.0 Safari/537.36
Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10_6_7; da-dk) AppleWebKit/533.21.1 (KHTML, like Gecko) Version/5.0.5 Safari/533.21.1
Opera/9.80 (Windows NT 6.1; U; es-ES) Presto/2.9.181 Version/12.00
Mozilla/5.0 (Linux; U; Android 2.3.4; fr-fr; HTC Desire Build/GRJ22) AppleWebKit/533.1 (KHTML, like Gecko) Version/4.0 Mobile Safari/533.1
Mozilla/5.0 (Linux; Android 7.0; SAMSUNG SM-N920C Build/NRD90M) AppleWebKit/537.36 (KHTML, like Gecko) SamsungBrowser/6.2 Chrome/56.0.2924.87 Mobile Safari/537.36
Mozilla/5.0 (Windows NT 6.1) AppleWebKit/537.31 (KHTML, like Gecko) Chrome/26.0.1410.43 BIDUBrowser/2.x Safari/537.31
Mozilla/5.0 (Linux; U; Android 4.4.2; zh-cn; GT-I9500 Build/KOT49H) AppleWebKit/537.36 (KHTML, like Gecko)Version/4.0 MQQBrowser/5.0 QQ-URL-Manager Mobile Safari/537.36
Mozilla/5.0 (Android 8.0.0; Tablet; rv:57.0) Gecko/57.0 Firefox/57.0
Mozilla/5.0 (Android 8.1.0; Mobile; rv:61.0) Gecko/61.0 Firefox/61.0
`.split('\n').filter(x => !!x).map(x => x.trim());
async function readFontLigatures(fontFilePath) {
let font = await promisify(fontkit.open)(fontFilePath);
console.log(`processing version="${font.version}" postscript name="${font.postscriptName}" full name="${font.fullName}" family name="${font.familyName}" copyright="${font.copyright}" subfamily name="${font.subfamilyName}"`)
let lookupList = font.GSUB.lookupList.toArray();
let lookupListIndexes = font.GSUB.featureList[0].feature.lookupListIndexes;
let result = {};
lookupListIndexes.forEach(index => {
let subTable = lookupList[index].subTables[0];
let leadingCharacters = [];
subTable.coverage.rangeRecords.forEach((coverage) => {
for (let i = coverage.start; i <= coverage.end; i++) {
let character = font.stringsForGlyph(i)[0];
leadingCharacters.push(character);
}
});
let ligatureSets = subTable.ligatureSets.toArray();
ligatureSets.forEach((ligatureSet, ligatureSetIndex) => {
let leadingCharacter = leadingCharacters[ligatureSetIndex];
ligatureSet.forEach(ligature => {
let character = font.stringsForGlyph(ligature.glyph)[0];
let characterCode = character.charCodeAt(0).toString(16).toUpperCase();
// see https://github.com/jossef/material-design-icons-iconfont/pull/57
if (characterCode === 'DBFF') {
characterCode = 'EBFF'
}
let ligatureText = ligature
.components
.map(x => font.stringsForGlyph(x)[0])
.join('');
ligatureText = leadingCharacter + ligatureText;
result[ligatureText] = characterCode;
});
});
});
return result;
}
function downloadFile(url, outputFilePath) {
return new Promise((resolve, reject) => {
let file = fs.createWriteStream(outputFilePath);
let r = request.get(url);
let onError = (error) => {
fs.unlink(outputFilePath, () => {
reject((error && error.message) || error);
});
};
r.on('response', (response) => {
let hasError = (400 <= response.statusCode) && (response.statusCode < 600);
if (hasError) {
return onError(`Invalid response status code ${response.statusCode}`);
}
r.pipe(file);
});
file.on('finish', () => file.close(resolve));
r.on('error', onError);
file.on('error', onError);
});
}
async function writeVariablesSCSSFile(outputFilePath, fontLigatures) {
let keys = Object.keys(fontLigatures);
keys.sort();
let lines = keys.map(key => `\t"${key}": ${fontLigatures[key].toLowerCase()},`);
let content = `$material-icons-codepoints: () !default;
$material-icons-codepoints: map-merge((
${lines.join('\n')}
), $material-icons-codepoints);`;
await promisify(fs.writeFile)(outputFilePath, content);
}
async function buildScss(sourceScssFilePath, outputCssFilePath) {
console.log(`generating css file "${outputCssFilePath}" ...`);
let content = await promisify(sass.render)({
file: sourceScssFilePath,
});
await promisify(fs.writeFile)(outputCssFilePath, content.css);
}
function calculateFileHash(filePath) {
return new Promise((resolve, reject) => {
sha256File(filePath, function (error, hash) {
if (error) {
return reject(error);
}
resolve({path: filePath, hash: hash})
});
});
}
function calculateDirTreeHash(directoryPath, result = undefined) {
return new Promise((resolve, reject) => {
if (result === undefined) {
result = {};
}
result[directoryPath] = '';
fs.readdir(directoryPath, (err, fileNames) => {
if (err) {
return reject(err);
}
fileNames = fileNames.sort();
let filePaths = fileNames.map(fileName => path.join(directoryPath, fileName));
let promises = filePaths.map(filePath => {
if (fs.lstatSync(filePath).isDirectory()) {
return calculateDirTreeHash(filePath, result)
}
return calculateFileHash(filePath)
});
Promise.all(promises).then((hashedFiles) => {
hashedFiles.forEach(hashedFile => {
if (hashedFile.hash) {
result[hashedFile.path] = hashedFile.hash;
}
})
resolve(result);
}).catch(reject);
});
});
}
async function calculateDirHash(directoryPath) {
let treeHash = await calculateDirTreeHash(directoryPath);
treeHash = sortByKeys(treeHash, {deep: true});
treeHash = JSON.stringify(treeHash);
return crypto.createHash('sha256').update(treeHash).digest('hex');
}
async function updateAndBuild() {
if (!await promisify(fs.exists)(FONTS_DIR_PATH)) {
await promisify(mkdirp)(FONTS_DIR_PATH);
}
let url = 'https://fonts.googleapis.com/icon?family=Material+Icons';
let fontName = 'MaterialIcons-Regular';
let fontsUrls = {};
await Promise.all(USER_AGENTS.map(async userAgent => {
let options = {
url: url,
headers: {
'User-Agent': userAgent
}
};
let content = await rp.get(options);
let matches = /url\((.*?)\)/g.exec(content);
let fontUrl = matches[1];
let extension = fontUrl.substr(fontUrl.lastIndexOf('.') + 1);
fontsUrls[extension] = fontUrl;
}));
await Promise.all(Object.keys(fontsUrls).map(async extension => {
let url = fontsUrls[extension];
let outputFilePath = path.resolve(FONTS_DIR_PATH, `${fontName}.${extension}`);
console.log(`downloading "${outputFilePath}" ...`);
await downloadFile(url, outputFilePath);
if (extension === 'ttf') {
let fontLigatures = await readFontLigatures(outputFilePath);
let outputLigaturesJsonFilePath = path.resolve(FONTS_DIR_PATH, `${fontName}.json`);
console.log(`extracted mapping to "${outputLigaturesJsonFilePath}" ...`);
await promisify(fs.writeFile)(outputLigaturesJsonFilePath, JSON.stringify(fontLigatures, null, 4));
let outputVariablesScssFilePath = path.resolve(SRC_DIR_PATH, '_variables.scss');
console.log(`generating codepoints to "${outputVariablesScssFilePath}" ...`);
await writeVariablesSCSSFile(outputVariablesScssFilePath, fontLigatures);
let sourceScssFilePath = path.resolve(SRC_DIR_PATH, 'material-design-icons.scss');
let outputCssFilePath = path.resolve(DIST_DIR_PATH, 'material-design-icons.css');
await buildScss(sourceScssFilePath, outputCssFilePath);
}
}));
}
module.exports.updateAndBuild = updateAndBuild;
module.exports.calculateDirHash = calculateDirHash;
module.exports.calculateFileHash = calculateFileHash;
module.exports.DIST_DIR_PATH = DIST_DIR_PATH;
module.exports.SRC_DIR_PATH = SRC_DIR_PATH;