@publidata/utils-svg
Version:
Collection of methods to handle svg files and src
276 lines (250 loc) • 8.37 kB
JavaScript
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;
}
};