diff --git a/config/defaults.json b/config/defaults.json index d76f224e..83cde0fc 100644 --- a/config/defaults.json +++ b/config/defaults.json @@ -12,6 +12,7 @@ "version": "1.0", "logging": false, "online": false, + "preferOnline": false, "pipeHTML": false, "icons": { "android": true, diff --git a/config/platform-options.json b/config/platform-options.json new file mode 100644 index 00000000..b867be49 --- /dev/null +++ b/config/platform-options.json @@ -0,0 +1,6 @@ +{ + "offset": { + "platforms": ["appleIcon", "firefox", "coast"], + "defaultTo": 0 + } +} \ No newline at end of file diff --git a/config/rfg.json b/config/rfg.json index 0e2e250b..eb033dce 100644 --- a/config/rfg.json +++ b/config/rfg.json @@ -12,7 +12,7 @@ "desktop_browser": {}, "ios": { "picture_aspect": "background_and_margin", - "margin": "4", + "margin": "0", "background_color": "#FFFFFF", "startup_image": { "background_color": "#FFFFFF" @@ -25,7 +25,8 @@ "firefox_app": { "picture_aspect": "circle", "keep_picture_in_circle": "true", - "circle_inner_margin": "5", + "circle_inner_margin": "0", + "margin": "0", "background_color": "#FFFFFF", "manifest": { "app_name": "Favicons", @@ -35,7 +36,7 @@ } }, "android_chrome": { - "picture_aspect": "shadow", + "picture_aspect": "no_change", "manifest": { "name": "Favicons", "display": "standalone", @@ -46,7 +47,7 @@ "coast": { "picture_aspect": "background_and_margin", "background_color": "#FFFFFF", - "margin": "12%" + "margin": "0" }, "yandex_browser": { "background_color": "#FFFFFF", diff --git a/es5.js b/es5.js index 349478c3..b1cedc11 100644 --- a/es5.js +++ b/es5.js @@ -25,7 +25,7 @@ var _ = require('underscore'), µ = helpers(options), background = µ.General.background(options.background); - function createFavicon(sourceset, properties, name, callback) { + function createFavicon(sourceset, properties, name, platformOptions, callback) { if (path.extname(name) === '.ico') { async.map(properties.sizes, function (sizeProperties, cb) { var newProperties = clone(properties); @@ -35,35 +35,37 @@ var _ = require('underscore'), var tempName = 'favicon-temp-' + newProperties.width + 'x' + newProperties.height + '.png'; - createFavicon(sourceset, newProperties, tempName, cb); + createFavicon(sourceset, newProperties, tempName, platformOptions, cb); }, function (error, results) { - var files = []; + if (error) { + return callback(error); + } - results.forEach(function (icoImage) { - files.push(icoImage.contents); + var files = results.map(function (icoImage) { + return icoImage.contents; }); toIco(files).then(function (buffer) { - callback(error, { name: name, contents: buffer }); - }); + return callback(null, { name: name, contents: buffer }); + }).catch(callback); }); } else { (function () { - var minimum = Math.min(properties.width, properties.height), - icon = _.min(sourceset, function (ico) { - return ico.size >= minimum; - }); + var maximum = Math.max(properties.width, properties.height), + offset = Math.round(maximum / 100 * platformOptions.offset) || 0; async.waterfall([function (cb) { - return µ.Images.read(icon.file, cb); + return µ.Images.nearest(sourceset, properties, offset, cb); + }, function (nearest, cb) { + return µ.Images.read(nearest.file, cb); }, function (buffer, cb) { - return µ.Images.resize(buffer, minimum, cb); + return µ.Images.resize(buffer, properties, offset, cb); }, function (resizedBuffer, cb) { return µ.Images.create(properties, background, function (error, canvas) { return cb(error, resizedBuffer, canvas); }); }, function (resizedBuffer, canvas, cb) { - return µ.Images.composite(canvas, resizedBuffer, properties, minimum, cb); + return µ.Images.composite(canvas, resizedBuffer, properties, offset, maximum, cb); }, function (composite, cb) { µ.Images.getBuffer(composite, cb); }], function (error, buffer) { @@ -97,11 +99,11 @@ var _ = require('underscore'), }); } - function createFavicons(sourceset, platform, callback) { + function createFavicons(sourceset, platform, platformOptions, callback) { var images = []; async.forEachOf(config.icons[platform], function (properties, name, cb) { - return createFavicon(sourceset, properties, name, function (error, image) { + return createFavicon(sourceset, properties, name, platformOptions, function (error, image) { return cb(images.push(image) && error); }); }, function (error) { @@ -109,9 +111,9 @@ var _ = require('underscore'), }); } - function createPlatform(sourceset, platform, callback) { + function createPlatform(sourceset, platform, platformOptions, callback) { async.parallel([function (cb) { - return createFavicons(sourceset, platform, cb); + return createFavicons(sourceset, platform, platformOptions, cb); }, function (cb) { return createFiles(platform, cb); }, function (cb) { @@ -125,8 +127,10 @@ var _ = require('underscore'), var response = { images: [], files: [], html: [] }; async.forEachOf(options.icons, function (enabled, platform, cb) { + var platformOptions = µ.General.preparePlatformOptions(platform, enabled); + if (enabled) { - createPlatform(sourceset, platform, function (error, images, files, html) { + createPlatform(sourceset, platform, platformOptions, function (error, images, files, html) { response.images = response.images.concat(images); response.files = response.files.concat(files); response.html = response.html.concat(html); @@ -141,7 +145,7 @@ var _ = require('underscore'), } function unpack(pack, callback) { - var response = { images: [], files: [], html: pack.html.split(',') }; + var response = { images: [], files: [], html: pack.html.split('\n') }; async.each(pack.files, function (url, cb) { return µ.RFG.fetch(url, function (error, box) { @@ -160,12 +164,16 @@ var _ = require('underscore'), }, function (pack, cb) { return unpack(pack, cb); }], function (error, results) { - return callback(error, results); + if (error && options.preferOnline) { + createOffline(sourceset, callback); + } else { + callback(error, results); + } }); } function create(sourceset, callback) { - options.online ? createOnline(sourceset, callback) : createOffline(sourceset, callback); + options.online || options.preferOnline ? createOnline(sourceset, callback) : createOffline(sourceset, callback); } async.waterfall([function (callback) { diff --git a/helpers-es5.js b/helpers-es5.js index c51c91da..10959bbc 100644 --- a/helpers-es5.js +++ b/helpers-es5.js @@ -15,9 +15,11 @@ var path = require('path'), 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; + NRC = require('node-rest-client').Client, + PLATFORM_OPTIONS = require('./config/platform-options.json'); (function () { @@ -77,9 +79,35 @@ var path = require('path'), }); } + function preparePlatformOptions(platform, options) { + if ((typeof options === 'undefined' ? 'undefined' : _typeof(options)) != 'object') { + options = {}; + } + + _.each(options, function (value, key) { + var platformOptionsRef = PLATFORM_OPTIONS[key]; + + if (typeof platformOptionsRef == 'undefined' || platformOptionsRef.platforms.indexOf(platform) == -1) { + return Reflect.deleteProperty(options, key); + } + }); + + _.each(PLATFORM_OPTIONS, function (_ref, key) { + var platforms = _ref.platforms; + var defaultTo = _ref.defaultTo; + + if (typeof options[key] == 'undefined' && platforms.indexOf(platform) != -1) { + options[key] = defaultTo; + } + }); + + return options; + } + return { General: { + preparePlatformOptions: preparePlatformOptions, background: function background(hex) { print('General:background', 'Parsing colour ' + hex); var rgba = color(hex).toRgb(); @@ -94,23 +122,31 @@ var path = require('path'), return callback('No source provided'); } else if (Buffer.isBuffer(_source)) { sourceset = [{ size: sizeOf(_source), file: _source }]; - return callback(sourceset.length ? null : 'Favicons source is invalid', sourceset); - } else if ((typeof _source === 'undefined' ? 'undefined' : _typeof(_source)) === 'object') { - async.each(_source, function (file, size, cb) { + callback(null, sourceset); + } else if (Array.isArray(_source)) { + async.each(_source, function (file, cb) { return readFile(file, function (error, buffer) { + if (error) { + return cb(error); + } + sourceset.push({ - size: { width: size, height: size, type: 'png' }, + size: sizeOf(buffer), file: buffer }); - return cb(error); + cb(null); }); }, function (error) { - return callback(error || sourceset.length ? null : 'Favicons source is invalid'); - }, sourceset); + return callback(error || sourceset.length ? null : 'Favicons source is invalid', sourceset); + }); } else if (typeof _source === 'string') { readFile(_source, function (error, buffer) { + if (error) { + return callback(error); + } + sourceset = [{ size: sizeOf(buffer), file: buffer }]; - return callback(error || (sourceset.length ? null : 'Favicons source is invalid'), sourceset); + callback(null, sourceset); }); } else { return callback('Invalid source type provided'); @@ -214,21 +250,54 @@ var path = require('path'), }, read: function read(file, callback) { print('Image:read', 'Reading file: ' + file.buffer); - Jimp.read(file, callback); + return Jimp.read(file, callback); }, - resize: function resize(image, properties, offset, callback) { - print('Images:resize', 'Resizing image to contain in ' + properties.width + 'x' + properties.height + ' with offset ' + offset); - image.contain(properties.width, properties.height, Jimp.HORIZONTAL_ALIGN_CENTER | Jimp.VERTICAL_ALIGN_MIDDLE); + nearest: function nearest(sourceset, properties, offset, callback) { + print('Image:nearest', 'Find nearest icon to ' + properties.width + 'x' + properties.height + ' with offset ' + offset); + + var offsetSize = offset * 2, + width = properties.width - offsetSize, + height = properties.height - offsetSize, + sideSize = Math.max(width, height), + svgSource = _.find(sourceset, function (source) { + return source.size.type == 'svg'; + }); + + var 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: height, width: width }).then(function (resizedBuffer) { + return callback(null, { + size: sizeOf(resizedBuffer), + file: resizedBuffer + }); + }).catch(callback); + } else { + _.each(sourceset, function (icon) { + var max = Math.max(icon.size.width, icon.size.height); + + if ((nearestSideSize > max || nearestSideSize < sideSize) && max >= sideSize) { + nearestIcon = icon; + nearestSideSize = max; + } + }); - if (offset) { - image.resize(properties.width - offset, properties.height - offset); + callback(null, nearestIcon); } - + }, + resize: function resize(image, properties, offset, callback) { + print('Images:resize', 'Resizing image to contain in ' + properties.width + 'x' + properties.height + ' with offset ' + offset); + var offsetSize = offset * 2; + image.contain(properties.width - offsetSize, properties.height - offsetSize, Jimp.HORIZONTAL_ALIGN_CENTER | Jimp.VERTICAL_ALIGN_MIDDLE); return callback(null, image); }, - composite: function composite(canvas, image, properties, maximum, callback) { - var offsetHeight = properties.height - maximum > 0 ? (properties.height - maximum) / 2 : 0, - offsetWidth = properties.width - maximum > 0 ? (properties.width - maximum) / 2 : 0, + composite: function composite(canvas, image, properties, offset, maximum, callback) { + var offsetSize = offset * 2, + maximumWithOffset = maximum - offsetSize, + offsetHeight = properties.height - maximumWithOffset > 0 ? (properties.height - maximumWithOffset) / 2 : 0, + offsetWidth = properties.width - maximumWithOffset > 0 ? (properties.width - maximumWithOffset) / 2 : 0, circle = path.join(__dirname, 'mask.png'), overlay = path.join(__dirname, 'overlay.png'); @@ -237,8 +306,10 @@ var path = require('path'), image.rotate(ROTATE_DEGREES); } - print('Images:composite', 'Compositing ' + maximum + 'x' + maximum + ' favicon on ' + properties.width + 'x' + properties.height + ' canvas'); - canvas.composite(image, offsetWidth, offsetHeight); + var compositeIcon = function compositeIcon() { + print('Images:composite', 'Compositing ' + maximum + 'x' + maximum + ' favicon on ' + properties.width + 'x' + properties.height + ' canvas with offset ' + offset); + canvas.composite(image, offsetWidth, offsetHeight); + }; if (properties.mask) { print('Images:composite', 'Masking composite image on circle'); @@ -251,9 +322,11 @@ var path = require('path'), 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); } }, @@ -266,22 +339,31 @@ var path = require('path'), RFG: { configure: function configure(sourceset, request, callback) { print('RFG:configure', 'Configuring RFG API request'); - request.master_picture.content = _.max(sourceset, function (image) { - return image.size.width; - }).file.toString('base64'); + var svgSource = _.find(sourceset, function (source) { + return source.size.type == 'svg'; + }); + options.background = '#' + color(options.background).toHex(); + request.master_picture.content = (svgSource || _.max(sourceset, function (_ref2) { + var _ref2$size = _ref2.size; + var width = _ref2$size.width; + var height = _ref2$size.height; + return Math.max(width, height); + })).file.toString('base64'); request.files_location.path = options.path; if (options.icons.android) { + 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; - request.favicon_design.android_chrome.theme_color = options.background; } else { Reflect.deleteProperty(request.favicon_design, 'android_chrome'); } if (options.icons.appleIcon) { + var appleIconOptions = preparePlatformOptions('appleIcon', options.icons.appleIcon); request.favicon_design.ios.background_color = options.background; + request.favicon_design.ios.margin = Math.round(57 / 100 * appleIconOptions.offset); } else { Reflect.deleteProperty(request.favicon_design, 'ios'); } @@ -293,7 +375,9 @@ var path = require('path'), } if (options.icons.coast) { + var coastOptions = preparePlatformOptions('coast', options.icons.coast); request.favicon_design.coast.background_color = options.background; + request.favicon_design.coast.margin = Math.round(228 / 100 * coastOptions.offset); } else { Reflect.deleteProperty(request.favicon_design, 'coast'); } @@ -303,7 +387,9 @@ var path = require('path'), } if (options.icons.firefox) { + var firefoxOptions = preparePlatformOptions('firefox', options.icons.firefox); request.favicon_design.firefox_app.background_color = options.background; + request.favicon_design.firefox_app.margin = Math.round(60 / 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; diff --git a/helpers.js b/helpers.js index 64b44ec8..b5f8618f 100644 --- a/helpers.js +++ b/helpers.js @@ -11,9 +11,11 @@ const path = require('path'), 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; + NRC = require('node-rest-client').Client, + PLATFORM_OPTIONS = require('./config/platform-options.json'); (() => { @@ -73,9 +75,32 @@ const path = require('path'), }); } + function preparePlatformOptions(platform, options) { + if (typeof options != 'object') { + options = {}; + } + + _.each(options, (value, key) => { + let 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; + } + }); + + return options; + } + return { General: { + preparePlatformOptions: preparePlatformOptions, background: (hex) => { print('General:background', `Parsing colour ${ hex }`); const rgba = color(hex).toRgb(); @@ -90,22 +115,31 @@ const path = require('path'), return callback('No source provided'); } else if (Buffer.isBuffer(source)) { sourceset = [{ size: sizeOf(source), file: source }]; - return callback(sourceset.length ? null : 'Favicons source is invalid', sourceset); - } else if (typeof source === 'object') { - async.each(source, (file, size, cb) => + 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: { width: size, height: size, type: 'png' }, + size: sizeOf(buffer), file: buffer }); - return cb(error); + cb(null); }), - (error) => - callback(error || sourceset.length ? null : 'Favicons source is invalid'), sourceset); + (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(error || (sourceset.length ? null : 'Favicons source is invalid'), sourceset); + callback(null, sourceset); }); } else { return callback('Invalid source type provided'); @@ -202,21 +236,52 @@ const path = require('path'), }, read: (file, callback) => { print('Image:read', `Reading file: ${ file.buffer }`); - Jimp.read(file, callback); + return Jimp.read(file, callback); }, - resize: (image, properties, offset, callback) => { - print('Images:resize', `Resizing image to contain in ${ properties.width }x${ properties.height } with offset ${ offset }`); - image.contain(properties.width, properties.height, Jimp.HORIZONTAL_ALIGN_CENTER | Jimp.VERTICAL_ALIGN_MIDDLE); + 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) => { + let max = Math.max(icon.size.width, icon.size.height); + + if ((nearestSideSize > max || nearestSideSize < sideSize) && max >= sideSize) { + nearestIcon = icon; + nearestSideSize = max; + } + }); - if (offset) { - image.resize(properties.width - offset, properties.height - offset); + callback(null, nearestIcon); } - + }, + resize: (image, properties, offset, callback) => { + print('Images:resize', `Resizing image to contain in ${ properties.width }x${ properties.height } with offset ${ offset }`); + let offsetSize = offset * 2; + image.contain(properties.width - offsetSize, properties.height - offsetSize, Jimp.HORIZONTAL_ALIGN_CENTER | Jimp.VERTICAL_ALIGN_MIDDLE); return callback(null, image); }, - composite: (canvas, image, properties, maximum, callback) => { - const offsetHeight = properties.height - maximum > 0 ? (properties.height - maximum) / 2 : 0, - offsetWidth = properties.width - maximum > 0 ? (properties.width - maximum) / 2 : 0, + composite: (canvas, image, properties, offset, maximum, callback) => { + const offsetSize = offset * 2, + maximumWithOffset = maximum - offsetSize, + offsetHeight = properties.height - maximumWithOffset > 0 ? (properties.height - maximumWithOffset) / 2 : 0, + offsetWidth = properties.width - maximumWithOffset > 0 ? (properties.width - maximumWithOffset) / 2 : 0, circle = path.join(__dirname, 'mask.png'), overlay = path.join(__dirname, 'overlay.png'); @@ -225,8 +290,10 @@ const path = require('path'), image.rotate(ROTATE_DEGREES); } - print('Images:composite', `Compositing ${ maximum }x${ maximum } favicon on ${ properties.width }x${ properties.height } canvas`); - canvas.composite(image, offsetWidth, offsetHeight); + const compositeIcon = () => { + print('Images:composite', `Compositing ${ maximum }x${ maximum } favicon on ${ properties.width }x${ properties.height } canvas with offset ${ offset }`); + canvas.composite(image, offsetWidth, offsetHeight); + }; if (properties.mask) { print('Images:composite', 'Masking composite image on circle'); @@ -238,9 +305,11 @@ const path = require('path'), 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); } }, @@ -253,20 +322,24 @@ const path = require('path'), RFG: { configure: (sourceset, request, callback) => { print('RFG:configure', 'Configuring RFG API request'); - request.master_picture.content = _.max(sourceset, (image) => image.size.width).file.toString('base64'); + 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) { + 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; - request.favicon_design.android_chrome.theme_color = options.background; } else { Reflect.deleteProperty(request.favicon_design, 'android_chrome'); } if (options.icons.appleIcon) { + let appleIconOptions = preparePlatformOptions('appleIcon', options.icons.appleIcon); request.favicon_design.ios.background_color = options.background; + request.favicon_design.ios.margin = Math.round(57 / 100 * appleIconOptions.offset); } else { Reflect.deleteProperty(request.favicon_design, 'ios'); } @@ -278,7 +351,9 @@ const path = require('path'), } if (options.icons.coast) { + let coastOptions = preparePlatformOptions('coast', options.icons.coast); request.favicon_design.coast.background_color = options.background; + request.favicon_design.coast.margin = Math.round(228 / 100 * coastOptions.offset); } else { Reflect.deleteProperty(request.favicon_design, 'coast'); } @@ -288,7 +363,9 @@ const path = require('path'), } if (options.icons.firefox) { + let firefoxOptions = preparePlatformOptions('firefox', options.icons.firefox); request.favicon_design.firefox_app.background_color = options.background; + request.favicon_design.firefox_app.margin = Math.round(60 / 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; diff --git a/index.js b/index.js index 2da780aa..b5abcab5 100644 --- a/index.js +++ b/index.js @@ -36,33 +36,33 @@ const _ = require('underscore'), createFavicon(sourceset, newProperties, tempName, platformOptions, cb); }, (error, results) => { - const files = []; + if (error) { + return callback(error); + } - results.forEach((icoImage) => { - files.push(icoImage.contents); - }); + const files = results.map((icoImage) => icoImage.contents); - toIco(files).then((buffer) => { - callback(error, { name, contents: buffer }); - }); + toIco(files) + .then((buffer) => callback(null, { name, contents: buffer })) + .catch(callback); } ); } else { - const minimum = Math.min(properties.width, properties.height), - maximum = Math.max(properties.width, properties.height), - icon = _.min(sourceset, (ico) => ico.size >= minimum), + const maximum = Math.max(properties.width, properties.height), offset = Math.round(maximum / 100 * platformOptions.offset) || 0; async.waterfall([ (cb) => - µ.Images.read(icon.file, cb), + µ.Images.nearest(sourceset, properties, offset, cb), + (nearest, cb) => + µ.Images.read(nearest.file, cb), (buffer, cb) => µ.Images.resize(buffer, properties, offset, cb), (resizedBuffer, cb) => µ.Images.create(properties, background, (error, canvas) => cb(error, resizedBuffer, canvas)), (resizedBuffer, canvas, cb) => - µ.Images.composite(canvas, resizedBuffer, properties, maximum - offset, cb), + µ.Images.composite(canvas, resizedBuffer, properties, offset, maximum, cb), (composite, cb) => { µ.Images.getBuffer(composite, cb); } @@ -118,7 +118,7 @@ const _ = require('underscore'), const response = { images: [], files: [], html: [] }; async.forEachOf(options.icons, (enabled, platform, cb) => { - const platformOptions = typeof enabled == "object" ? enabled : {}; + const platformOptions = µ.General.preparePlatformOptions(platform, enabled); if (enabled) { createPlatform(sourceset, platform, platformOptions, (error, images, files, html) => { @@ -135,7 +135,7 @@ const _ = require('underscore'), } function unpack (pack, callback) { - const response = { images: [], files: [], html: pack.html.split(',') }; + const response = { images: [], files: [], html: pack.html.split('\n') }; async.each(pack.files, (url, cb) => µ.RFG.fetch(url, (error, box) => @@ -152,12 +152,17 @@ const _ = require('underscore'), µ.RFG.request(request, cb), (pack, cb) => unpack(pack, cb) - ], (error, results) => - callback(error, results)); + ], (error, results) => { + if (error && options.preferOnline) { + createOffline(sourceset, callback) + } else { + callback(error, results); + } + }); } function create (sourceset, callback) { - options.online ? createOnline(sourceset, callback) : createOffline(sourceset, callback); + options.online || options.preferOnline ? createOnline(sourceset, callback) : createOffline(sourceset, callback); } async.waterfall([ diff --git a/package.json b/package.json index 426fcb2b..c7b88dd4 100644 --- a/package.json +++ b/package.json @@ -37,6 +37,7 @@ "mkdirp": "^0.5.1", "node-rest-client": "^1.5.1", "require-directory": "^2.1.1", + "svg2png": "^3.0.1", "through2": "^2.0.0", "tinycolor2": "^1.1.2", "to-ico": "^1.0.1", diff --git a/readme.md b/readme.md index 01a8ef3d..e3c8b6fa 100644 --- a/readme.md +++ b/readme.md @@ -20,7 +20,7 @@ Please note: Favicons is written in ES6, meaning you need Node 4.x or above. ```js var favicons = require('favicons'), - source = 'test/logo.png', // Source image(s). `string`, `buffer` or array of `{ size: filepath }` + source = 'test/logo.png', // Source image(s). `string`, `buffer` or array of `string` configuration = { appName: null, // Your application's name. `string` appDescription: null, // Your application's description. `string` @@ -34,15 +34,16 @@ var favicons = require('favicons'), version: "1.0", // Your application's version number. `number` logging: false, // Print logs to console? `boolean` online: false, // Use RealFaviconGenerator to create favicons? `boolean` + preferOnline: false, // Use offline generation, if online generation has failed. `boolean` icons: { - android: true, // Create Android homescreen icon. `boolean` or `{ offset: offsetInPercentage }` + android: true, // Create Android homescreen icon. `boolean` appleIcon: true, // Create Apple touch icons. `boolean` or `{ offset: offsetInPercentage }` - appleStartup: true, // Create Apple startup images. `boolean` or `{ offset: offsetInPercentage }` + appleStartup: true, // Create Apple startup images. `boolean` coast: { offset: 25 }, // Create Opera Coast icon with offset 25%. `boolean` or `{ offset: offsetInPercentage }` - favicons: true, // Create regular favicons. `boolean` or `{ offset: offsetInPercentage }` + favicons: true, // Create regular favicons. `boolean` firefox: true, // Create Firefox OS icons. `boolean` or `{ offset: offsetInPercentage }` - windows: true, // Create Windows 8 tile icons. `boolean` or `{ offset: offsetInPercentage }` - yandex: true // Create Yandex browser icon. `boolean` or `{ offset: offsetInPercentage }` + windows: true, // Create Windows 8 tile icons. `boolean` + yandex: true // Create Yandex browser icon. `boolean` } }, callback = function (error, response) {