Parsing colors in SVG

This function is part of icon manipulation functions in Iconify Tools.

Function parseColors() parses colors in SVG.

It can:

  • Find all colors.
  • Replace colors.
  • Add missing default color.

Function parses colors in:

  • Stylesheets.
  • Inline styles.
  • Shapes, including gradients and filters.
  • SVG animations that animate one of color attributes.

Function does not parse colors in:

  • Masks.

Usage

Function has the following parameters:

  • svg, SVG. Icon instance.
  • options, object. Options.

Function returns array of colors.

Function is asynchronous. That means you need to handle it as Promise instance, usually by adding await before function call.

Colors

Colors used in callback and returned by function can be two types:

  • Color object, same as in Iconify Utils.
  • string. Value is a string if color cannot be parsed.

Options

Options object has the following properties:

  • defaultColor, Color | string. Default color to add to shapes that use system default color. See below.
  • callback, function. Callback to call for each color. See below.

Default color

Some icons use system color. Example:

<svg viewBox="0 0 1200 400" xmlns="http://www.w3.org/2000/svg" width="1200" height="400">
   <path d="M300 200H150A150 150 0 10300 50z"/>
</svg>

In that icon path uses default fill. In Iconify all icons should use colors set by color attribute, which means shapes should use "currentColor" for fill.

If you set defaultColor option, parser will automatically set color for shapes that use default color.

Example:

default-color.ts
import { SVG, parseColors } from '@iconify/tools';

(async () => {
   const svg = new SVG(
       '<svg viewBox="0 0 1200 400" xmlns="http://www.w3.org/2000/svg" width="1200" height="400"><path d="M300 200H150A150 150 0 10300 50z"/></svg>'
   );

   // Add 'currentColor' to shapes that use default color
   await parseColors(svg, {
       defaultColor: 'currentColor',
   });

   console.log(svg.toMinifiedString());
})();
Result:
<svg viewBox="0 0 1200 400" xmlns="http://www.w3.org/2000/svg" width="1200" height="400"><path d="M300 200H150A150 150 0 10300 50z" fill="currentColor"/></svg>

Replacing colors

With callback option you can replace colors.

Callback has the following parameters:

  • attr, string. Attribute where color is used, such as "fill" or "stroke".
  • colorStr, string. Color value as string.
  • color, Color | null. Parsed color value. If color can be parsed, callback will have Color value. If color cannot be parsed, callback will have null value.
  • tagName, string. Optional parameter. Name of tag where color is found. If color is found in stylesheet, parameter will be undefined.

Callback should return:

  • Color or string to set new color. If you do not want to change color, return color passed to callback (either colorStr or non-null color object).
  • "remove" to remove element. This is used to remove stuff like white background rectangle left by some editors.
  • "unset" to remove color.

Callback can be asynchronous.

Example

example.ts
import { compareColors, stringToColor } from '@iconify/utils/lib/colors';
import { IconSet, parseColors, isEmptyColor } from '@iconify/tools';

const iconSet = new IconSet({
   prefix: 'codicon',
   icons: {
       'add': {
           body: '<path d="M14 7v1H8v6H7V8H1V7h6V1h1v6h6z"/>',
       },
       'debug-pause': {
           body: '<path d="M4.5 3H6v10H4.5V3zm7 0v10H10V3h1.5z" fill="#000"/>',
           hidden: true,
       },
       'triangle-left': {
           body: '<path d="M10.44 2l.56.413v11.194l-.54.393L5 8.373v-.827L10.44 2z" fill="#000"/>',
       },
   },
   aliases: {
       'plus': {
           parent: 'add',
       },
       'triangle-right': {
           parent: 'triangle-left',
           hFlip: true,
       },
   },
});

(async () => {
   await iconSet.forEach(async (name, type) => {
       if (type !== 'icon') {
           // Ignore aliases and variations: they inherit content from parent icon, so there is nothing to change
           return;
       }

       const svg = iconSet.toSVG(name);
       if (svg) {
           await parseColors(svg, {
               // Change default color to 'currentColor'
               defaultColor: 'currentColor',

               // Callback to parse each color
               callback: (attr, colorStr, color) => {
                   if (!color) {
                       // color === null, so color cannot be parsed
                       // Return colorStr to keep old value
                       return colorStr;
                   }

                   if (isEmptyColor(color)) {
                       // Color is empty: 'none' or 'transparent'
                       // Return color object to keep old value
                       return color;
                   }

                   // Black color: change to 'currentColor'
                   if (compareColors(color, stringToColor('black'))) {
                       return 'currentColor';
                   }

                   // White color: belongs to white background rectangle: remove rectangle
                   if (compareColors(color, stringToColor('white'))) {
                       return 'remove';
                   }

                   // Unexpected color. Add code to check for it
                   throw new Error(
                       `Unexpected color "${colorStr}" in attribute ${attr}`
                   );
               },
           });

           // Update icon in icon set
           iconSet.fromSVG(name, svg);
       }
   });

   // Export icon set
   console.log(iconSet.export());
})();
Result:
{
   "prefix": "codicon",
   "icons": {
       "add": {
           "body": "<path d=\"M14 7v1H8v6H7V8H1V7h6V1h1v6h6z\" fill=\"currentColor\"/>"
       },
       "debug-pause": {
           "body": "<path d=\"M4.5 3H6v10H4.5V3zm7 0v10H10V3h1.5z\" fill=\"currentColor\"/>",
           "hidden": true
       },
       "triangle-left": {
           "body": "<path d=\"M10.44 2l.56.413v11.194l-.54.393L5 8.373v-.827L10.44 2z\" fill=\"currentColor\"/>"
       }
   },
   "aliases": {
       "plus": {
           "parent": "add"
       },
       "triangle-right": {
           "parent": "triangle-left",
           "hFlip": true
       }
   }
}