%PDF- %PDF-
Direktori : /proc/309157/root/www/specpages-backup/node_modules/favicons/dist/ |
Current File : //proc/309157/root/www/specpages-backup/node_modules/favicons/dist/helpers.js |
"use strict"; require("core-js/modules/es.symbol.description"); require("core-js/modules/es.array.iterator"); require("core-js/modules/es.promise"); require("core-js/modules/es.string.replace"); const path = require("path"); const url = require("url"); const fs = require("fs"); const { promisify } = require("util"); const color = require("tinycolor2"); const colors = require("colors"); const jsonxml = require("jsontoxml"); const sizeOf = require("image-size"); const Jimp = require("jimp"); const sharp = require("sharp"); const xml2js = require("xml2js"); const PLATFORM_OPTIONS = require("./config/platform-options.json"); module.exports = function (options) { function directory(path) { return path.substr(-1) === "/" ? path : `${path}/`; } function relative(path, relativeToPath = false) { return url.resolve(!relativeToPath && options.path && directory(options.path) || "", path); } function log(context, message) { if (options.logging) { const { magenta, green, yellow } = colors; message = message.replace(/ \d+(x\d+)?/g, item => magenta(item)); message = message.replace(/#([0-9a-f]{3}){1,2}/g, item => magenta(item)); console.log(`${green("[Favicons]")} ${yellow(context)}: ${message}...`); } } function parseColor(hex) { const { r, g, b, a } = color(hex).toRgb(); return Jimp.rgbaToInt(r, g, b, a * 255); } // sharp renders the SVG in its source width and height with 72 DPI which can // cause a blurry result in case the source SVG is defined in lower size than // the target size. To avoid this, resize the source SVG to the needed size // before passing it to sharp by increasing its width and/or height // attributes. // // Currently it seems this won't be fixed in sharp, so we need a workaround: // https://github.com/lovell/sharp/issues/729#issuecomment-284708688 // // They suggest setting the image density to a "resized" density based on the // target render size but this does not seem to work with favicons and may // cause other errors with "unnecessarily high" image density values. // // For further information, see: // https://github.com/itgalaxy/favicons/issues/264 const svgtool = { ensureSize(svgSource, width, height) { let svgWidth = svgSource.size.width; let svgHeight = svgSource.size.height; if (svgWidth >= width && svgHeight >= height) { // If the base SVG is large enough, it does not need to be modified. return Promise.resolve(svgSource.file); } else if (width > height) { svgHeight = Math.round(svgHeight * (width / svgWidth)); svgWidth = width; } else { // width <= height svgWidth = Math.round(svgWidth * (height / svgHeight)); svgHeight = height; } // Modify the source SVG's width and height attributes for sharp to render // it correctly. log("svgtool:ensureSize", `Resizing SVG to ${svgWidth}x${svgHeight}`); return this.resize(svgSource.file, svgWidth, svgHeight); }, resize(svgFile, width, height) { return new Promise((resolve, reject) => { xml2js.parseString(svgFile, (err, xmlDoc) => { if (err) { return reject(err); } xmlDoc.svg.$.width = width; xmlDoc.svg.$.height = height; const builder = new xml2js.Builder(); const modifiedSvg = builder.buildObject(xmlDoc); resolve(Buffer.from(modifiedSvg)); }); }); } }; return { General: { source(src) { log("General:source", `Source type is ${typeof src}`); if (Buffer.isBuffer(src)) { try { return Promise.resolve([{ size: sizeOf(src), file: src }]); } catch (error) { return Promise.reject(new Error("Invalid image buffer")); } } else if (typeof src === "string") { return promisify(fs.readFile)(src).then(this.source.bind(this)); } else if (Array.isArray(src) && !src.some(Array.isArray)) { if (!src.length) { return Promise.reject(new Error("No source provided")); } return Promise.all(src.map(this.source.bind(this))).then(results => [].concat(...results)); } else { return Promise.reject(new Error("Invalid source type provided")); } }, preparePlatformOptions(platform) { const parameters = typeof options.icons[platform] === "object" ? options.icons[platform] : {}; for (const key of Object.keys(parameters)) { if (!(key in PLATFORM_OPTIONS) || !PLATFORM_OPTIONS[key].platforms.includes(platform)) { throw new Error(`Unsupported option '${key}' on platform '${platform}'`); } } for (const key of Object.keys(PLATFORM_OPTIONS)) { const platformOption = PLATFORM_OPTIONS[key]; const { platforms, defaultTo } = platformOption; if (!(key in parameters) && platforms.includes(platform)) { parameters[key] = platform in platformOption ? platformOption[platform] : defaultTo; } } if (parameters.background === true) { parameters.background = options.background; } return parameters; } }, HTML: { render(htmlTemplate) { return htmlTemplate(Object.assign({}, options, { relative })); } }, Files: { create(properties, name, isHtml) { return new Promise((resolve, reject) => { log("Files:create", `Creating file: ${name}`); if (name === "manifest.json") { properties.name = options.appName; properties.short_name = options.appShortName || options.appName; properties.description = options.appDescription; properties.dir = options.dir; properties.lang = options.lang; properties.display = options.display; properties.orientation = options.orientation; properties.scope = options.scope; properties.start_url = options.start_url; properties.background_color = options.background; properties.theme_color = options.theme_color; properties.icons.map(icon => icon.src = relative(icon.src, options.manifestRelativePaths)); 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 = Object.keys(properties.icons).reduce((obj, key) => Object.assign(obj, { [key]: relative(properties.icons[key], options.manifestRelativePaths) }), {}); properties = JSON.stringify(properties, null, 2); } else if (name === "browserconfig.xml") { properties[0].children[0].children[0].children.map(property => { if (property.name === "TileColor") { property.text = options.background; } else { property.attrs.src = relative(property.attrs.src, options.manifestRelativePaths); } }); properties = jsonxml(properties, { prettyPrint: true, xmlHeader: true, indent: " " }); } else if (name === "yandex-browser-manifest.json") { properties.version = options.version; properties.api_version = 1; properties.layout.logo = relative(properties.layout.logo, options.manifestRelativePaths); properties.layout.color = options.background; properties = JSON.stringify(properties, null, 2); } else if (isHtml) { properties = properties.join("\n"); } else { reject(`Unknown format of file ${name}.`); } resolve({ name, contents: properties }); }); } }, Images: { create(properties) { log("Image:create", `Creating empty ${properties.width}x${properties.height} canvas with ${properties.transparent ? "transparent" : properties.background} background`); return Jimp.create(properties.width, properties.height, properties.transparent ? 0 : parseColor(properties.background)); }, render(sourceset, properties, offset) { log("Image:render", `Find nearest icon to ${properties.width}x${properties.height} with offset ${offset}`); const width = properties.width - offset * 2; const height = properties.height - offset * 2; const svgSource = sourceset.find(source => source.size.type === "svg"); let promise = null; if (svgSource) { const background = { r: 0, g: 0, b: 0, alpha: 0 }; log("Image:render", `Rendering SVG to ${width}x${height}`); promise = svgtool.ensureSize(svgSource, width, height).then(svgBuffer => sharp(svgBuffer).resize({ background, width, height, fit: sharp.fit.contain }).toBuffer()).then(Jimp.read); } else { const sideSize = Math.max(width, height); let nearestIcon = sourceset[0]; let nearestSideSize = Math.max(nearestIcon.size.width, nearestIcon.size.height); for (const icon of sourceset) { const max = Math.max(icon.size.width, icon.size.height); if ((nearestSideSize > max || nearestSideSize < sideSize) && max >= sideSize) { nearestIcon = icon; nearestSideSize = max; } } log("Images:render", `Resizing PNG to ${width}x${height}`); promise = Jimp.read(nearestIcon.file).then(image => image.contain(width, height, Jimp.HORIZONTAL_ALIGN_CENTER | Jimp.VERTICAL_ALIGN_MIDDLE, options.pixel_art && width >= image.bitmap.width && height >= image.bitmap.height ? Jimp.RESIZE_NEAREST_NEIGHBOR : null)); } return promise.then(image => image); }, mask: Jimp.read(path.join(__dirname, "mask.png")), overlayGlow: Jimp.read(path.join(__dirname, "overlay-glow.png")), // Gimp drop shadow filter: input: mask.png, config: X: 2, Y: 5, Offset: 5, Color: black, Opacity: 20 overlayShadow: Jimp.read(path.join(__dirname, "overlay-shadow.png")), composite(canvas, image, properties, offset, max) { if (properties.mask) { log("Images:composite", "Masking composite image on circle"); return Promise.all([this.mask, this.overlayGlow, this.overlayShadow]).then(([mask, glow, shadow]) => { canvas.mask(mask.clone().resize(max, Jimp.AUTO), 0, 0); if (properties.overlayGlow) { canvas.composite(glow.clone().resize(max, Jimp.AUTO), 0, 0); } if (properties.overlayShadow) { canvas.composite(shadow.clone().resize(max, Jimp.AUTO), 0, 0); } properties = Object.assign({}, properties, { mask: false }); return this.composite(canvas, image, properties, offset, max); }); } log("Images:composite", `Compositing favicon on ${properties.width}x${properties.height} canvas with offset ${offset}`); canvas.composite(image, offset, offset); if (properties.rotate) { const degrees = 90; log("Images:render", `Rotating image by ${degrees}`); canvas.rotate(degrees, false); } return canvas.getBufferAsync(Jimp.MIME_PNG); } } }; };