One of my hobbies to create graphics using my favorite photo editing tool, Pixelmator Pro. It’s a fantastic tool; anyone on macOS should add it to their Applications folder. One of the benefits of using a photo-editing tool is the ability to create Scalable Vector Graphics (SVG). SVGs are an XML-based format that allows you to edit and manipulate the content of an image with a text editor if you choose, but I wouldn’t recommend it. Photo-editing tools are much better at creating graphics, but SVGs provide a way to extend and repurpose existing assets.

In this post, I’ll show you how you can take an existing SVG and create a programmable visual asset with some C# code. Let’s get started.

What’s an SVG?

Scalable Vector Graphics is an XML-based markup language, not too dissimilar to HTML, allowing users to create two-dimensional based vector graphics. A standard format used across multiple image editors to create infinitely scalable assets is widely supported by major browser vendors. First, let’s look at an example SVG, which we’ll program later with C#.

<?xml version="1.0" encoding="UTF-8"?>
<!-- Generated by Pixelmator Pro 3.2.3 -->
<svg width="1000" height="1000" viewBox="0 0 1000 1000" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
    <path id="Color-Fill" fill="#ff6900" stroke="none" d="M 0 0 L 1000 0 L 1000 1000 L 0 1000 Z"/>
    <g>
        <path id="Logo-Shape" fill="#020303" fill-rule="evenodd" stroke="none" d="M 832.5 500 C 832.5 316.365356 683.634644 167.5 500 167.5 C 316.365326 167.5 167.5 316.365356 167.5 500 C 167.5 683.634644 316.365326 832.5 500 832.5 C 683.634644 832.5 832.5 683.634644 832.5 500 Z"/>
        <text id="Beyond-The-Idea" xml:space="preserve" x="50%" y="50%" text-anchor="middle">
            <tspan id="Top" x="50%" y="493" font-family="JetBrains Mono" font-size="111.9625" fill="#ffffff" xml:space="preserve">BEYOND</tspan>
            <tspan id="Bottom" x="50%" y="604.2" font-family="JetBrains Mono" font-size="111.9625" fill="#ffffff" xml:space="preserve">THE IDEA</tspan>
        </text>
    </g>
</svg>

This SVG produces the following visual output.

SVG Template

An added benefit of being markup is you can typically target any element with styling or explicit color fills and strokes. The flexibility makes one asset infinitely configurable.

Neat, right?! So how do you take this XML and create a programmable image?

Hurndles to Programming SVGs

By the end of this section, we’ll have loaded an SVG into a .NET application’s memory, modified text elements of the SVG, and rendered the image to disk as a PNG. We’ll also create a wrapper class to make using your new SVG easier for other developers.

Before we start, let’s talk about caveats you’re likely to encounter. The code I’ll be showing can overcome these issues, while you’ll need to figure others out for your use case.

If you want to follow along, create a new console application and add the Svg NuGet package.

What about the fonts?

The first issue you’ll likely run into is fonts. SVGs are a markup language, and text elements reference fonts. Therefore, if you use a non-standard font (which is likely), you’re bound to run into rendering issues when you deploy your programmable SVG.

The Svg package has a static type called SvgFontManager which will let you load any font into the SVG rendering context.

In the case of this sample, let’s add a font, JetBrains Mono, to our assembly as an embedded resource, then load it into the font manager.

var assembly = Assembly.GetAssembly(typeof(PromotionalImage))!;
using var fontStream = assembly.GetManifestResourceStream("SvgFontEmbed.JetBrainsMono-Regular.ttf")!;
using var ms = new MemoryStream();
fontStream.CopyTo(ms);
// load font into font manager
SvgFontManager.PrivateFontDataList.Add(ms.ToArray());

The font name of JetBrains Mono is used in the SVG, as seen on the tspan elements.

<tspan id="Top" x="50%" y="493" font-family="JetBrains Mono" font-size="111.9625" fill="#ffffff" xml:space="preserve">BEYOND</tspan>

You can add as many or as few fonts as your SVG requires. But, of course, you can also forget to add any fonts you know will be on the destination host.

Text Centering

Another issue you may run into is the centering of text elements. While I mostly created the sample SVG in a photo editor, I manually modified the markup to adapt to variations in input. As a recommendation, you should change any numeric values for x or y to use percentage values, thus keeping the spirit of the original image intact.

Using HTML colors instead of RGB

If you want to use HTML-based colors, you can use the ColorTranslator class in System.Drawing namespace.

ColorTranslator.FromHtml("#840087")

The Hexadecimal color representation is more common among image-editing apps, developers, and designers. Go with the flow.

Back To Programming an SVG

I thought I’d start with some of the issues I ran into before jumping into a programmable SVG because you’ll likely have to tinker with your SVG template to get the results you’re looking to produce.

You’ll first want to add your SVG template and fonts into your console application or class library as embedded resources. Doing so will make sharing these images easier in the long run, as all assets will be part of your compiled artifact.

<ItemGroup>
  <None Remove="JetBrainsMono-Regular.ttf" />
  <EmbeddedResource Include="JetBrainsMono-Regular.ttf" />
  <None Remove="template.svg" />
  <EmbeddedResource Include="template.svg" />
</ItemGroup>

You’ll be able to access these resources using the following code.

static PromotionalImage()
{
    var assembly = Assembly.GetAssembly(typeof(PromotionalImage))!;
    using var fontStream = assembly.GetManifestResourceStream("SvgFontEmbed.JetBrainsMono-Regular.ttf")!;
    using var ms = new MemoryStream();
    fontStream.CopyTo(ms);
    // load font into font manager
    SvgFontManager.PrivateFontDataList.Add(ms.ToArray());

    using var svgStream = assembly.GetManifestResourceStream("SvgFontEmbed.template.svg")!;
    using var textReader = new StreamReader(svgStream);

    SvgXml = textReader.ReadToEnd();
}

Your next step is to look at your SVG and determine all the identifiers you want your users to modify, along with the types of each element. In the case of the example above, we’ll be converting the background color and the two text spans of Top and Bottom.

using System.Drawing;
using System.Reflection;
using Svg;

public static class PromotionalImage
{
    private static string SvgXml;
    
    static PromotionalImage()
    {
        var assembly = Assembly.GetAssembly(typeof(PromotionalImage))!;
        using var fontStream = assembly.GetManifestResourceStream("SvgFontEmbed.JetBrainsMono-Regular.ttf")!;
        using var ms = new MemoryStream();
        fontStream.CopyTo(ms);
        // load font into font manager
        SvgFontManager.PrivateFontDataList.Add(ms.ToArray());

        using var svgStream = assembly.GetManifestResourceStream("SvgFontEmbed.template.svg")!;
        using var textReader = new StreamReader(svgStream);

        SvgXml = textReader.ReadToEnd();
    }

    public static SvgDocument Create(string topText, string bottomText, string? bgColor = null)
    {
        var document = SvgDocument.FromSvg<SvgDocument>(SvgXml);

        var texts = document.Children.FindSvgElementsOf<SvgTextSpan>().ToList();
        texts.First(t => t.ID == "Top").Text = topText;
        texts.First(t => t.ID == "Bottom").Text = bottomText;
        
        if (bgColor is { })
        {
            var bg = document.Children.Where(x => x.ID == "Color-Fill")
                .Where(e => e is SvgPath)
                .Cast<SvgPath>()
                .First();

            bg.Fill = new SvgColourServer(ColorTranslator.FromHtml(bgColor));
        }

        return document;
    }
}

Let’s look at the steps in the Create method.

  1. First, we create a new SVG document in memory. We want to do that to work with a fresh instance every time.
  2. Second, we find the text spans in our SVG by their identifiers and set the new values set by the user.
  3. Finally, we find the background of Color-Fill and set the color to the hexadecimal value passed in.

Let’s see how we can use this new class in code.

using System.Drawing.Imaging;

var image = PromotionalImage.Create("DROP", "THE DOT", "#840087");
image.Draw().Save("result.png", ImageFormat.Png);

The resulting image is a mutation of our original template.

drop the dot from svg template

Yes! You did it. You just programmed an SVG using C#.

Conclusion

With SVGs and .NET, you can procedurally generate an infinite amount of assets with minimal effort. The Svg library on NuGet supports many markup elements required to change any starting template dramatically. You could go so far as to add new paths, change colors, modify the text, switch fonts, or do whatever you wish. Embedding the resources into a class library would allow you to ship a generator via a NuGet package to use in various situations. The possibilities are limitless.

I hope you enjoyed this post, and as always, thanks for reading.