UNPKG

@publidata/utils-svg

Version:

Collection of methods to handle svg files and src

276 lines (250 loc) 8.37 kB
import get from "axios"; export let FontAwesomeUtils; if (typeof window !== "undefined") { FontAwesomeUtils = require("@publidata/utils-fontawesome").FontAwesomeUtils; FontAwesomeUtils.setToken(process.env.FA_TOKEN); FontAwesomeUtils.setKit(process.env.FA_KIT); } /** * Check if the given string is a valid URL pointing to SVG file * @param {String} url URL to be checked * @return {Boolean} Return a boolean on whether the given URL is pointing to a SVG file */ export const isSvgUrl = url => { if (!url) return false; return url.includes("http") && url.includes(".svg"); } /** * Fetch the SVG file content from the given URL * @param {String} url Source URL to be fetched * @return {Promise} Return the data fetched from the given URL */ export const getSvgData = async url => new Promise((resolve, reject) => { if (url.includes("http")) { get(`${url}${url.includes("?") ? "&" : "?"}cacheblock=true`) .then(({ data }) => resolve(data)) .catch(err => reject(err)); } else resolve(url); }); /** * Fetch the SVG for the corresponding FA icon * @param {String} icon FA icon name * @return {Promise} Return the data fetched from the given URL */ export const getFASvgFile = icon => { if (!FontAwesomeUtils || !icon) return null; let parsedIcon = icon; if (icon.split(" ").length === 1) { // Means the icon is not prefixed parsedIcon = `fas ${icon}`; } return FontAwesomeUtils.toSvg(parsedIcon); }; /** * Fetch the SVG file for both, FA icon and SVG file * @param {String} src icon name or URL to be fetched * @param {Object} options Options to be passed to the fetch function (content, details) * @return {Promise} Return the data fetched from the given URL */ export const retrieveSvg = async (src, options = {}) => new Promise(async (resolve, reject) => { if (src.includes("http")) { getSvgData(src) .then(svg => { if (options.content === "rich") { resolve({ svg, isImage: true, ...options.details }); } resolve(svg); }) .catch(err => reject(err)); // Look for an FA icon } if (src.includes("fa-")) { getFASvgFile(src) .then(svg => { if (options.content === "rich") { resolve({ svg, isImage: false, ...options.details }); } resolve(svg); }) .catch(err => reject(err)); } }); /** * Will transform the SVG file to an encoded URL * @param {String} svg SVG file content * @return {String} Return the data fetched from the given URL */ export const svgToDataUrl = svg => `data:image/svg+xml,${encodeURIComponent(svg)}`; /** * Replace the colors in the SVG file with the given color * @param {String} svg SVG file content * @param {String} newColor New color to replace the old one * @param {Array} [baseColor] Optional. Colors to be replaced, can be an array of colors or a single color string * @return {String} Return the data fetched from the given URL */ export const replaceSvgColor = ( _svg, newColor, baseColors = ["#ffffff", "#fff", "white", "currentColor"] ) => { if (!_svg) return null; let svg = String(_svg); // Ajoute un fill par défaut si aucun présent dans le SVG if (!hasFillColor(svg)) { svg = addFillToSvgTag(svg, baseColors[0]); } if (Array.isArray(baseColors)) { baseColors.forEach(color => { svg = svg.replaceAll(color, newColor); svg = svg.replaceAll(color.toUpperCase(), newColor); }); } else { svg = svg.replaceAll(baseColors, newColor); svg = svg.replaceAll(baseColors.toUpperCase(), newColor); } return svg; }; /** * * @param {String} svg, SVG file content * @returns {Boolean} Return true if the SVG file has a fill attribute */ export const hasFillColor = svg => svg.includes("fill="); /** * * @param {String} svg, SVG file content * @returns {String} Return the SVG file without the style attribute */ export const removeStyleTag = svg => svg.replace(/<style[^>]*>[\s\S]*?<\/style>/g, ""); /** * * @param {String} svg, SVG file content * @param {String} color, color to be added * @returns {String} Return the SVG file colored */ export const addFillToSvgTag = (svg, color = "black") => { if (!svg) return null; let _svg = new String(svg); return _svg.replaceAll("<svg", `<svg fill="${color}"`); }; /** * Get the SVG viewbox has a string if exist * @param {String} svg SVG file content * @return {String} Return the viewbox string if exist */ export const retrieveSvgViewbox = svg => { const viewbox = svg.match(/viewBox="([^"]*)"/); return viewbox ? viewbox[1] : null; }; /** * Get the SVG id has a string if exist * @param {String} svg SVG file * @return {String} Return the id if exist */ export const retrieveSvgId = svg => { const id = svg.match(/id="([^"]*)"/); return id ? id[1] : null; }; /** * Add the width and height to the SVG file * @param {String} svg SVG file * @param {String} width Width to be added * @param {String} height Height to be added * @param {String} custom Cutom text to be added in the SVG tag * @return {String} Return the id if exist */ export const addWidthAndHeightToSvg = (svg, width, height, custom) => { const viewbox = retrieveSvgViewbox(svg); return svg.replace( /<svg([^>]*)>/, `<svg width="${width}" height="${height}" viewBox="${ viewbox || `0 0 ${width} ${height}` }" ${custom || ""}>` ); }; /** * Will get the wviewbox dimensions of the SVG file * @param {String} svg SVG file content * @return {Object} Return the viewbox dimensions in the form of {x, y, w, h} */ export const retrieveViewboxData = svg => { const viewbox = retrieveSvgViewbox(svg); const [x, y, w, h] = viewbox.split(" "); return { x, y, w, h }; }; /** * Remove the XML declaration from the SVG file * @param {String} svg SVG file content * @return {Object} Return the cleaned SVG file */ export const removeXmlTag = svg => svg.replace(/<\?xml[^>]*\?>/g, ""); /** * Remove the DOCTYPE declaration from the SVG file * @param {String} svg SVG file content * @return {Object} Return the cleaned SVG file */ export const removeDoctype = svg => svg.replace(/<!DOCTYPE[^>]*>/g, ""); export const isArtBoard = id => id?.toLowerCase().includes("artboard") || false; /** * Ensure that the SVG element has width and height attributes set. * If not, it will try to extract them from the viewBox attribute. * @param {SVGElement} svgElem - The SVG element to ensure width and height for. * @returns {SVGElement} - The modified SVG element with width and height attributes set. */ export const ensureSVGWidthHeight = svgElem => { if (!svgElem.getAttribute("width") || !svgElem.getAttribute("height")) { const viewBox = svgElem.getAttribute("viewBox"); if (viewBox) { const match = viewBox.match(/\w+ \w+ (\w+) (\w+)/); if (match) { const viewBoxWidth = match[1]; const viewBoxHeight = match[2]; svgElem.setAttribute("width", viewBoxWidth); svgElem.setAttribute("height", viewBoxHeight); } } } return svgElem; }; /** * Wait for an image to load * @param {element} img - Image element to wait for load * @returns {Promise} - Returns a promise that resolves when the image is loaded or rejects if there is an error */ export const waitForImageLoad = img => new Promise((resolve, reject) => { img.onload = () => resolve(); // eslint-disable-line img.onerror = reject; // eslint-disable-line }); /** * Change the color of an SVG file and return a data URL * @param {string} svgUrl - URL of the SVG file * @param {string} color - Color to replace in the SVG in hexadecimal format (e.g., "#ff0000") * @returns {Promise<string|null>} - Returns a promise that resolves to the data URL of the modified SVG or null if the URL is not valid */ export const changeSvgColorAndReturnUrl = async (svgUrl, color) => { try { if (!isSvgUrl(svgUrl)) { return svgUrl; } const svg = await getSvgData(svgUrl); const colored = replaceSvgColor(svg, color); const dataUrl = svgToDataUrl(colored); return dataUrl; } catch (error) { console.error("Error changing SVG color:", error); throw error; } };