Recently I stumbled on an article from SitePoint on how to customize your GitHub Profile page (Home to Create an Impressive GitHub Profile README). Among the several useful ideas it included, it showed several free sources of icons to use including SVG icons from Devicons GitHub Repository.

All worked great except when you view GitHub in Dark Mode (the page has a black background). Then many of the Devicons, SVG images with transparent backgrounds, the icon contents become either fully or partially not visible:

Devicon in light mode

Devicon

Devicon in dark mode

Devicon

Because GitHub’s Readme.md doesn’t allow inline CSS or provide any options to force the background of the page or any content block within, the options to use Devicons and support dark mode was limiting.

Because the icon files are SVG, one solution was to simply insert a simple path in the SVG graphic at the bottom most layer that provides a solid color to contrast the remaining paths:

...viewBox="0 0 128 128"><path fill="#ffffff" d="M0 0 H128 V128 H0z"/><path ...

The inserted path above simply creates a path starting at the top/left origin 0,0, draws to the top/right corner 128,0, draws down to the bottom/right corner 128,128, draws to the bottom/left corner 0,128, closes the path back to 0,0, and fills the area with white. The remaining paths will draw the logo as they normally would, only now there is a solid background. So same SVG modified with the path above:

Devicon

This could be done manually per icon, by copying the images and inserting a background path, or programmatically. Of course that later is more appropriate for GitHub users (“Do something manually?”).

It’s a simple task:

  1. Get the original SVG contents
  2. Find the viewBox within the SVG
  3. Using the origin, width and height insert a <path /> element before all other paths
  4. Finally output that as the SVG graphic.

Can be accomplished in just about any programming language. I used PHP to build a solution.

The built solution is located in one of my GitHub repositories: svg-add-background-for-github. Fork or download it there, and customize it for your use. I’ll example only the top level concepts of my implementation, the source file should be well documented to explain some of the details.

I decided on 2 query string parameters: icon and color. The first parameter ‘icon’ is required. After looking how Devicons organizes their content, to keep things simple I decided this parameter will be the icon file name (the ‘.svg’ extension optional) and parent folder in the format <icon-group>/<icon-name>:

Screen shot of icons folder on Devicons GitHub Repository page

So the ‘icon’ value for the Amazon Web Services Original Wordmark SVG image would be:

?icon=amazonwebservices/amazonwebservices-original-wordmark

The ‘color’ parameter represents the background color to add, and it defaults to white (technically ‘ffffff’). The color value can be any hex color value (example a bright blue):

?icon=amazonwebservices/amazonwebservices-original-wordmark&color=0000ff

Once I know what icon, plug that into the full URL and get the contents of the SVG file:

// Get icon to try to get - bail if not passed.
$icon = $_GET['icon'] ?? null;
if (!$icon) throw new \Exception('No icon passed');


// Should only consist of alphanumeric and slash, strip .svg if included.
$icon = preg_replace('/(.svg$|[^\w\d\/-])/i', '', $icon);


// Color. Must be 6 hex digits.
$color = $_GET['color'] ?? null;
if (!$color || !preg_match('/^[\da-f]{6}$/i', $color))
$color = 'ffffff';


// Build URL for original SVG
$url = sprintf('https://cdn.jsdelivr.net/gh/devicons/devicon/icons/%s.svg', $icon);
$svg = file_get_contents($url);

At this point if the URL was valid, my $svg will content the SVG content, something like:

<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 128 128"><path d="M64 1.023v.002-.002c-30 0-54.557 … "/></svg>

To insert a new path that goes corner to corner to corner and closes, and fills with the selected color, need the origin (X & Y – usually 0, 0), the width and the height of the SVG, and where to insert the background path. In an SVG file that information can be found by searching for the viewBox attribute, and everything after until the ‘>’ character:

<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 128 128"><path d="M64 1.023v.002-.002c-30 0-54.557 … "/></svg>

Using a regular expression and PHP’s preg_match() can find that pattern easy enough. The matched text and matching coordinates can then be deconstructed into $replace, $x, $y, $w, and $h:

if (preg_match('/viewBox="(\d+) (\d+) (\d+) (\d+)".*?>/i', $svg, $matches)) {
      list($replace, $x, $y, $w, $h) = $matches;

Then build the corner to corner path with the fill color using sprintf():

      $backgroundPath = sprintf(
        '<path fill="#%5$s" d="M%1$s %2$s H%3$s V%4$s H%1$sz"/>',
        $x, $y, $w, $h,
        $color
      );

The $backgroundPath will now contain the background path: <path fill="#ffffff" d="M0 0 H128 V128 H0z"/>.

Last is to insert the path as the first in the SVG. Using str_replace() that can be done by replacing what preg_match() found above ($replace variable contains the highlighted yellow code above) with itself and append the background to that:

      // Replace found viewBox to end of tag with same but append new path
      $svg = str_replace($replace, $replace . $backgroundPath, $svg);

So the full code that modifies the $svg contents looks like:

if (preg_match('/viewBox="(\d+) (\d+) (\d+) (\d+)".*?>/i', $svg, $matches)) {
      list($replace, $x, $y, $w, $h) = $matches;
      $backgroundPath = sprintf(
        '<path fill="#%5$s" d="M%1$s %2$s H%3$s V%4$s H%1$sz"/>',
        $x, $y, $w, $h,
        $color
      );
      // Replace found viewBox to end of tag with same but append new path
      $svg = str_replace($replace, $replace . $backgroundPath, $svg);
}

Then simply output the result:

if ($svg) {
    header('Content-type: image/svg+xml');
    echo $svg;
}

My code in GitHub repository referenced above follows the steps above, with a little more error handling and support for caching the changes.

Other things that can be done might include adding padding. The viewBox X and Y coordinates can be negative. So with the example above viewBox="0 0 128 128", that could be replaced with viewBox="-10 -10 148 148". Change the sprintf() values setting the $backgoundPath, and create a new replacement string with str_replace():

      $pad = 10;
      $backgroundPath = sprintf(
        '',
        $x2 = $x - $pad, $y2 = $y - $pad, $w2 = $w + 2 * $pad, $h2 = $h + 2 * $pad,
        $color
      );
      $newReplace = str_replace(
        "$x $y $w $h",
        sprintf('%s %s %s %s', $x2, $y2, $w2, $h2),
        $replace
      );
      $svg = str_replace($replace, $newReplace . $backgroundPath, $svg);

Keep in mind, that’s not the same as a border, it scales with the SVG. So if with 10 SVGs you adding the same padding, their faux border will all scale the same only if they all share the same defined veiwBox width and height.