first commit
This commit is contained in:
479
build/node_modules/favicons/helpers.js
generated
vendored
Normal file
479
build/node_modules/favicons/helpers.js
generated
vendored
Normal file
@@ -0,0 +1,479 @@
|
||||
/* eslint camelcase: 0, no-shadow: 0 */
|
||||
|
||||
const path = require('path'),
|
||||
fs = require('fs'),
|
||||
_ = require('underscore'),
|
||||
color = require('tinycolor2'),
|
||||
cheerio = require('cheerio'),
|
||||
colors = require('colors'),
|
||||
jsonxml = require('jsontoxml'),
|
||||
sizeOf = require('image-size'),
|
||||
async = require('async'),
|
||||
mkdirp = require('mkdirp'),
|
||||
Jimp = require('jimp'),
|
||||
svg2png = require('svg2png'),
|
||||
File = require('vinyl'),
|
||||
Reflect = require('harmony-reflect'),
|
||||
NRC = require('node-rest-client').Client,
|
||||
PLATFORM_OPTIONS = require('./config/platform-options.json'),
|
||||
ANDROID_BASE_SIZE = 36,
|
||||
IOS_BASE_SIZE = 57,
|
||||
IOS_STARTUP_BASE_SIZE = 320,
|
||||
COAST_BASE_SIZE = 228,
|
||||
FIREFOX_BASE_SIZE = 60;
|
||||
|
||||
(() => {
|
||||
|
||||
'use strict';
|
||||
|
||||
const xmlconfig = { prettyPrint: true, xmlHeader: true, indent: ' ' },
|
||||
client = new NRC(),
|
||||
HEX_MAX = 255,
|
||||
NON_EXISTANT = -1,
|
||||
ROTATE_DEGREES = 90,
|
||||
HTTP_SUCCESS = 200;
|
||||
|
||||
client.setMaxListeners(0);
|
||||
|
||||
function helpers (options) {
|
||||
|
||||
function contains (array, element) {
|
||||
return array.indexOf(element.toLowerCase()) > NON_EXISTANT;
|
||||
}
|
||||
|
||||
function relative (directory) {
|
||||
return path.join(options.path, directory).replace(/\\/g, '/');
|
||||
}
|
||||
|
||||
function print (context, message) {
|
||||
let newMessage = '';
|
||||
|
||||
if (options.logging && message) {
|
||||
_.each(message.split(' '), (item) => {
|
||||
newMessage += ` ${ ((/^\d+x\d+$/gm).test(item) ? colors.magenta(item) : item) }`;
|
||||
});
|
||||
console.log(`${ colors.green('[Favicons]') } ${ context.yellow }:${ newMessage }...`);
|
||||
}
|
||||
}
|
||||
|
||||
function readFile (filepath, callback) {
|
||||
fs.readFile(filepath, callback);
|
||||
}
|
||||
|
||||
function updateDocument (document, code, tags, next) {
|
||||
const $ = cheerio.load(document, { decodeEntities: false }),
|
||||
target = $('head').length > 0 ? $('head') : $.root(),
|
||||
newCode = cheerio.load(code.join('\n'), { decodeEntities: false });
|
||||
|
||||
async.each(tags, (platform, callback) => {
|
||||
async.forEachOf(platform, (tag, selector, cb) => {
|
||||
if (options.replace) {
|
||||
$(selector).remove();
|
||||
} else if ($(selector).length) {
|
||||
newCode(selector).remove();
|
||||
}
|
||||
return cb(null);
|
||||
}, callback);
|
||||
}, (error) => {
|
||||
target.append(newCode.html());
|
||||
return next(error, $.html().replace(/^\s*$[\n\r]{1,}/gm, ''));
|
||||
});
|
||||
}
|
||||
|
||||
function preparePlatformOptions (platform, options, baseOptions) {
|
||||
if (typeof options !== 'object') {
|
||||
options = {};
|
||||
}
|
||||
|
||||
_.each(options, (value, key) => {
|
||||
const platformOptionsRef = PLATFORM_OPTIONS[key];
|
||||
|
||||
if (typeof platformOptionsRef === 'undefined' || platformOptionsRef.platforms.indexOf(platform) === -1) {
|
||||
return Reflect.deleteProperty(options, key);
|
||||
}
|
||||
});
|
||||
|
||||
_.each(PLATFORM_OPTIONS, ({ platforms, defaultTo }, key) => {
|
||||
if (typeof options[key] === 'undefined' && platforms.indexOf(platform) !== -1) {
|
||||
options[key] = defaultTo;
|
||||
}
|
||||
});
|
||||
|
||||
if (typeof options.background === 'boolean') {
|
||||
|
||||
if (platform === 'android' && !options.background) {
|
||||
options.background = 'transparent';
|
||||
} else {
|
||||
options.background = baseOptions.background;
|
||||
}
|
||||
}
|
||||
|
||||
if (platform === 'android' && options.background !== 'transparent') {
|
||||
options.disableTransparency = true;
|
||||
}
|
||||
|
||||
return options;
|
||||
}
|
||||
|
||||
return {
|
||||
|
||||
General: {
|
||||
preparePlatformOptions,
|
||||
background: (hex) => {
|
||||
print('General:background', `Parsing colour ${ hex }`);
|
||||
const rgba = color(hex).toRgb();
|
||||
|
||||
return Jimp.rgbaToInt(rgba.r, rgba.g, rgba.b, rgba.a * HEX_MAX);
|
||||
},
|
||||
source: (source, callback) => {
|
||||
let sourceset = [];
|
||||
|
||||
print('General:source', `Source type is ${ typeof source }`);
|
||||
if (!source || !source.length) {
|
||||
return callback('No source provided');
|
||||
} else if (Buffer.isBuffer(source)) {
|
||||
sourceset = [{ size: sizeOf(source), file: source }];
|
||||
return callback(null, sourceset);
|
||||
} else if (Array.isArray(source)) {
|
||||
async.each(source, (file, cb) =>
|
||||
readFile(file, (error, buffer) => {
|
||||
if (error) {
|
||||
return cb(error);
|
||||
}
|
||||
|
||||
sourceset.push({
|
||||
size: sizeOf(buffer),
|
||||
file: buffer
|
||||
});
|
||||
cb(null);
|
||||
}),
|
||||
(error) =>
|
||||
callback(error || sourceset.length ? null : 'Favicons source is invalid', sourceset)
|
||||
);
|
||||
} else if (typeof source === 'string') {
|
||||
readFile(source, (error, buffer) => {
|
||||
if (error) {
|
||||
return callback(error);
|
||||
}
|
||||
|
||||
sourceset = [{ size: sizeOf(buffer), file: buffer }];
|
||||
return callback(null, sourceset);
|
||||
});
|
||||
} else {
|
||||
return callback('Invalid source type provided');
|
||||
}
|
||||
},
|
||||
/* eslint no-underscore-dangle: 0 */
|
||||
vinyl: (object, input) => {
|
||||
const output = new File({
|
||||
path: object.name,
|
||||
contents: Buffer.isBuffer(object.contents) ? object.contents : new Buffer(object.contents)
|
||||
});
|
||||
|
||||
// gulp-cache support
|
||||
if (typeof input._cachedKey !== 'undefined') {
|
||||
output._cachedKey = input._cachedKey;
|
||||
}
|
||||
|
||||
return output;
|
||||
}
|
||||
},
|
||||
|
||||
HTML: {
|
||||
parse: (html, callback) => {
|
||||
print('HTML:parse', 'HTML found, parsing and modifying source');
|
||||
const $ = cheerio.load(html),
|
||||
link = $('*').is('link'),
|
||||
attribute = link ? 'href' : 'content',
|
||||
value = $('*').first().attr(attribute);
|
||||
|
||||
if (path.extname(value)) {
|
||||
$('*').first().attr(attribute, relative(value));
|
||||
} else if (value.slice(0, 1) === '#') {
|
||||
$('*').first().attr(attribute, options.background);
|
||||
} else if (html.indexOf('application-name') !== NON_EXISTANT || html.indexOf('apple-mobile-web-app-title') !== NON_EXISTANT) {
|
||||
$('*').first().attr(attribute, options.appName);
|
||||
}
|
||||
return callback(null, $.html());
|
||||
},
|
||||
update: (document, code, tags, callback) => {
|
||||
const encoding = { encoding: 'utf8' };
|
||||
|
||||
async.waterfall([
|
||||
(cb) =>
|
||||
mkdirp(path.dirname(document), cb),
|
||||
(made, cb) =>
|
||||
fs.readFile(document, encoding, (error, data) => cb(null, error ? null : data)),
|
||||
(data, cb) =>
|
||||
(data ? updateDocument(data, code, tags, cb) : cb(null, code.join('\n'))),
|
||||
(html, cb) =>
|
||||
fs.writeFile(document, html, options, cb)
|
||||
], callback);
|
||||
}
|
||||
},
|
||||
|
||||
Files: {
|
||||
create: (properties, name, platformOptions, callback) => {
|
||||
print('Files:create', `Creating file: ${ name }`);
|
||||
if (name === 'manifest.json') {
|
||||
properties.name = options.appName;
|
||||
properties.short_name = options.appName;
|
||||
properties.description = options.appDescription;
|
||||
properties.dir = options.dir;
|
||||
properties.lang = options.lang;
|
||||
properties.display = options.display;
|
||||
properties.orientation = options.orientation;
|
||||
properties.start_url = options.start_url;
|
||||
properties.background_color = options.background;
|
||||
properties.theme_color = options.theme_color;
|
||||
_.map(properties.icons, (icon) => (icon.src = relative(icon.src)));
|
||||
properties = JSON.stringify(properties, null, 2);
|
||||
} else if (name === 'manifest.webapp') {
|
||||
properties.version = options.version;
|
||||
properties.name = options.appName;
|
||||
properties.description = options.appDescription;
|
||||
properties.developer.name = options.developerName;
|
||||
properties.developer.url = options.developerURL;
|
||||
properties.icons = _.mapObject(properties.icons, (property) => relative(property));
|
||||
properties = JSON.stringify(properties, null, 2);
|
||||
} else if (name === 'browserconfig.xml') {
|
||||
_.map(properties[0].children[0].children[0].children, (property) => {
|
||||
if (property.name === 'TileColor') {
|
||||
property.text = platformOptions.background;
|
||||
} else {
|
||||
property.attrs.src = relative(property.attrs.src);
|
||||
}
|
||||
});
|
||||
properties = jsonxml(properties, xmlconfig);
|
||||
} else if (name === 'yandex-browser-manifest.json') {
|
||||
properties.version = options.version;
|
||||
properties.api_version = 1;
|
||||
properties.layout.logo = relative(properties.layout.logo);
|
||||
properties.layout.color = platformOptions.background;
|
||||
properties = JSON.stringify(properties, null, 2);
|
||||
} else if (/\.html$/.test(name)) {
|
||||
properties = properties.join('\n');
|
||||
}
|
||||
return callback(null, { name, contents: properties });
|
||||
}
|
||||
},
|
||||
|
||||
Images: {
|
||||
create: (properties, background, callback) => {
|
||||
let jimp = null;
|
||||
|
||||
print('Image:create', `Creating empty ${ properties.width }x${ properties.height } canvas with ${ (properties.transparent ? 'transparent' : background) } background`);
|
||||
jimp = new Jimp(properties.width, properties.height, properties.transparent ? 0x00000000 : background, (error, canvas) =>
|
||||
callback(error, canvas, jimp));
|
||||
},
|
||||
read: (file, callback) => {
|
||||
print('Image:read', `Reading file: ${ file.buffer }`);
|
||||
return Jimp.read(file, callback);
|
||||
},
|
||||
nearest: (sourceset, properties, offset, callback) => {
|
||||
print('Image:nearest', `Find nearest icon to ${ properties.width }x${ properties.height } with offset ${ offset }`);
|
||||
|
||||
const offsetSize = offset * 2,
|
||||
width = properties.width - offsetSize,
|
||||
height = properties.height - offsetSize,
|
||||
sideSize = Math.max(width, height),
|
||||
svgSource = _.find(sourceset, (source) => source.size.type === 'svg');
|
||||
|
||||
let nearestIcon = sourceset[0],
|
||||
nearestSideSize = Math.max(nearestIcon.size.width, nearestIcon.size.height);
|
||||
|
||||
if (svgSource) {
|
||||
print('Image:nearest', `SVG source will be saved as ${ width }x${ height }`);
|
||||
svg2png(svgSource.file, { height, width })
|
||||
.then((resizedBuffer) => callback(null, {
|
||||
size: sizeOf(resizedBuffer),
|
||||
file: resizedBuffer
|
||||
}))
|
||||
.catch(callback);
|
||||
} else {
|
||||
_.each(sourceset, (icon) => {
|
||||
const max = Math.max(icon.size.width, icon.size.height);
|
||||
|
||||
if ((nearestSideSize > max || nearestSideSize < sideSize) && max >= sideSize) {
|
||||
nearestIcon = icon;
|
||||
nearestSideSize = max;
|
||||
}
|
||||
});
|
||||
|
||||
return callback(null, nearestIcon);
|
||||
}
|
||||
},
|
||||
resize: (image, properties, offset, callback) => {
|
||||
print('Images:resize', `Resizing image to contain in ${ properties.width }x${ properties.height } with offset ${ offset }`);
|
||||
const offsetSize = offset * 2;
|
||||
|
||||
if (properties.rotate) {
|
||||
print('Images:resize', `Rotating image by ${ROTATE_DEGREES}`);
|
||||
image.rotate(ROTATE_DEGREES, false);
|
||||
}
|
||||
|
||||
image.contain(properties.width - offsetSize, properties.height - offsetSize, Jimp.HORIZONTAL_ALIGN_CENTER | Jimp.VERTICAL_ALIGN_MIDDLE);
|
||||
return callback(null, image);
|
||||
},
|
||||
composite: (canvas, image, properties, offset, maximum, callback) => {
|
||||
const circle = path.join(__dirname, 'mask.png'),
|
||||
overlay = path.join(__dirname, 'overlay.png');
|
||||
|
||||
function compositeIcon () {
|
||||
print('Images:composite', `Compositing favicon on ${ properties.width }x${ properties.height } canvas with offset ${ offset }`);
|
||||
canvas.composite(image, offset, offset);
|
||||
}
|
||||
|
||||
if (properties.mask) {
|
||||
print('Images:composite', 'Masking composite image on circle');
|
||||
async.parallel([
|
||||
(cb) => Jimp.read(circle, cb),
|
||||
(cb) => Jimp.read(overlay, cb)
|
||||
], (error, images) => {
|
||||
images[0].resize(maximum, Jimp.AUTO);
|
||||
images[1].resize(maximum, Jimp.AUTO);
|
||||
canvas.mask(images[0], 0, 0);
|
||||
canvas.composite(images[1], 0, 0);
|
||||
compositeIcon();
|
||||
return callback(error, canvas);
|
||||
});
|
||||
} else {
|
||||
compositeIcon();
|
||||
return callback(null, canvas);
|
||||
}
|
||||
},
|
||||
getBuffer: (canvas, callback) => {
|
||||
print('Images:getBuffer', 'Collecting image buffer from canvas');
|
||||
canvas.getBuffer(Jimp.MIME_PNG, callback);
|
||||
}
|
||||
},
|
||||
|
||||
RFG: {
|
||||
configure: (sourceset, request, callback) => {
|
||||
print('RFG:configure', 'Configuring RFG API request');
|
||||
const svgSource = _.find(sourceset, (source) => source.size.type === 'svg');
|
||||
|
||||
options.background = `#${ color(options.background).toHex() }`;
|
||||
request.master_picture.content = (svgSource || _.max(sourceset, ({ size: { width, height } }) => Math.max(width, height))).file.toString('base64');
|
||||
request.files_location.path = options.path;
|
||||
|
||||
if (options.icons.android) {
|
||||
const androidOptions = preparePlatformOptions('android', options.icons.android, options);
|
||||
|
||||
request.favicon_design.android_chrome.theme_color = options.background;
|
||||
request.favicon_design.android_chrome.manifest.name = options.appName;
|
||||
request.favicon_design.android_chrome.manifest.display = options.display;
|
||||
request.favicon_design.android_chrome.manifest.orientation = options.orientation;
|
||||
|
||||
if (androidOptions.shadow) {
|
||||
request.favicon_design.android_chrome.picture_aspect = 'shadow';
|
||||
} else if (androidOptions.offset > 0 && androidOptions.background) {
|
||||
request.favicon_design.android_chrome.picture_aspect = 'background_and_margin';
|
||||
request.favicon_design.android_chrome.background_color = androidOptions.background;
|
||||
request.favicon_design.android_chrome.margin = Math.round(ANDROID_BASE_SIZE / 100 * androidOptions.offset);
|
||||
}
|
||||
|
||||
} else {
|
||||
Reflect.deleteProperty(request.favicon_design, 'android_chrome');
|
||||
}
|
||||
|
||||
if (options.icons.appleIcon) {
|
||||
const appleIconOptions = preparePlatformOptions('appleIcon', options.icons.appleIcon, options);
|
||||
|
||||
request.favicon_design.ios.background_color = appleIconOptions.background;
|
||||
request.favicon_design.ios.margin = Math.round(IOS_BASE_SIZE / 100 * appleIconOptions.offset);
|
||||
} else {
|
||||
Reflect.deleteProperty(request.favicon_design, 'ios');
|
||||
}
|
||||
|
||||
if (options.icons.appleIcon && options.icons.appleStartup) {
|
||||
const appleStartupOptions = preparePlatformOptions('appleStartup', options.icons.appleStartup, options);
|
||||
|
||||
request.favicon_design.ios.startup_image.background_color = appleStartupOptions.background;
|
||||
request.favicon_design.ios.startup_image.margin = Math.round(IOS_STARTUP_BASE_SIZE / 100 * appleStartupOptions.offset);
|
||||
} else if (request.favicon_design.ios) {
|
||||
Reflect.deleteProperty(request.favicon_design.ios, 'startup_image');
|
||||
}
|
||||
|
||||
if (options.icons.coast) {
|
||||
const coastOptions = preparePlatformOptions('coast', options.icons.coast, options);
|
||||
|
||||
request.favicon_design.coast.background_color = coastOptions.background;
|
||||
request.favicon_design.coast.margin = Math.round(COAST_BASE_SIZE / 100 * coastOptions.offset);
|
||||
} else {
|
||||
Reflect.deleteProperty(request.favicon_design, 'coast');
|
||||
}
|
||||
|
||||
if (!options.icons.favicons) {
|
||||
Reflect.deleteProperty(request.favicon_design, 'desktop_browser');
|
||||
}
|
||||
|
||||
if (options.icons.firefox) {
|
||||
const firefoxOptions = preparePlatformOptions('firefox', options.icons.firefox, options);
|
||||
|
||||
request.favicon_design.firefox_app.background_color = firefoxOptions.background;
|
||||
request.favicon_design.firefox_app.margin = Math.round(FIREFOX_BASE_SIZE / 100 * firefoxOptions.offset);
|
||||
request.favicon_design.firefox_app.manifest.app_name = options.appName;
|
||||
request.favicon_design.firefox_app.manifest.app_description = options.appDescription;
|
||||
request.favicon_design.firefox_app.manifest.developer_name = options.developerName;
|
||||
request.favicon_design.firefox_app.manifest.developer_url = options.developerURL;
|
||||
} else {
|
||||
Reflect.deleteProperty(request.favicon_design, 'firefox_app');
|
||||
}
|
||||
|
||||
if (options.icons.windows) {
|
||||
const windowsOptions = preparePlatformOptions('windows', options.icons.windows, options);
|
||||
|
||||
request.favicon_design.windows.background_color = windowsOptions.background;
|
||||
} else {
|
||||
Reflect.deleteProperty(request.favicon_design, 'windows');
|
||||
}
|
||||
|
||||
if (options.icons.yandex) {
|
||||
const yandexOptions = preparePlatformOptions('yandex', options.icons.yandex, options);
|
||||
|
||||
request.favicon_design.yandex_browser.background_color = yandexOptions.background;
|
||||
request.favicon_design.yandex_browser.manifest.version = options.version;
|
||||
} else {
|
||||
Reflect.deleteProperty(request.favicon_design, 'yandex_browser');
|
||||
}
|
||||
|
||||
return callback(null, request);
|
||||
},
|
||||
request: (request, callback) => {
|
||||
print('RFG:request', 'Posting a request to the RFG API');
|
||||
client.post('http://realfavicongenerator.net/api/favicon', {
|
||||
data: { favicon_generation: request },
|
||||
headers: { 'Content-Type': 'application/json' }
|
||||
}, (data, response) => {
|
||||
const result = data.favicon_generation_result;
|
||||
|
||||
return result && response.statusCode === HTTP_SUCCESS ? callback(null, {
|
||||
files: result.favicon.files_urls,
|
||||
html: result.favicon.html_code
|
||||
}) : callback(result.result.error_message);
|
||||
});
|
||||
},
|
||||
fetch: (address, callback) => {
|
||||
const name = path.basename(address),
|
||||
image = contains(['.png', '.jpg', '.bmp', '.ico', '.svg'], path.extname(name));
|
||||
|
||||
print('RFG:fetch', `Fetching ${ image ? 'image' : 'file' } from RFG: ${ address }`);
|
||||
client.get(address, (buffer, response) => {
|
||||
const success = buffer && response.statusCode === HTTP_SUCCESS;
|
||||
|
||||
return success ? callback(null, {
|
||||
file: image ? null : { name, contents: buffer },
|
||||
image: image ? { name, contents: buffer } : null
|
||||
}) : callback(`Could not fetch URL: ${ address }`);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
module.exports = helpers;
|
||||
|
||||
})();
|
||||
Reference in New Issue
Block a user