Table of Contents

Resource SVG Converter - ResourceSvgConverter

The ResourceSvgConverter converter class takes multiple files and directories as input and outputs a ResourceDictionary in XAML format. This is useful for vector icons, but required supports in the rendering process to track brushes and pens, and an extension to the XmlXamlWriter class to format a compact XAML output. It is the most recent SharpVector SVG converter and adds extension features that will be supported in the other converters.

Note

Currently, the input is restricted to local or network files and directories.

The output is string or local or network file or one of the following

  • Stream: A stream object created by the user.
  • TextWriter: A text writer object created by the user.
  • XmlWriter: An XML writer object created by the user

Resource Options

This converter offers extra settings or options in the form of WpfResourceSettings class.

resource_settings

The following are the properties exposed by the resource options to customize the output resource dictionary XAML:

  • ResourceMode: An enumeration specifying the type of the resource object; DrawingGroup or DrawingImage.
    <ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" 
      xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:po="http://schemas.microsoft.com/winfx/2006/xaml/presentation/options" 
      xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" mc:Ignorable="po">
      <DrawingGroup x:Key="shapes-rect-01-t" po:Freeze="True" ClipGeometry="F0M0,0L480,0L480,360L0,360z">
      </DrawingGroup>
    </ResourceDictionary>
    
  • ResourceFreeze: Makes the freezable resource object unmodifiable and sets its IsFrozen property to true. To freeze a resource object declared in markup, the PresentationOptions:Freeze attribute is used, as shown above as po.Freeze, and the xmlns:po="http://schemas.microsoft.com/winfx/2006/xaml/presentation/options" namespace is added.
    Note

    If this property is set to true, it is admisable to set the ResourceAccessType type below to Static.

  • IndentSpaces: This specifies the number of spaces used for the indentation of the XAML output. It is honored with the XmlReaderSettings. The default is 2 spaces.
  • NumericPrecision: This specifies numeric precision or number of decimal places for the floating number. The default is 4, in complaince to the SVG specifications. Setting this to -1 will disable this property.
  • ColorNameFormat: This determines the key name of color objects, if both BindToResources and BindToColors properties are enabled. The default is Color{0}.
  • BrushNameFormat: This determines the key name of brush objects, if the BindToResources property is enabled. The default is Brush{0}.
    Note

    Only solid color brushes are supported and extracted from the drawings.

  • PenNameFormat: This determines the key name of pen or stroke objects, if the BindToResources property is enabled. The default is Pen{0}
  • BindToResources: This determines whether the media basic objects; color, brush and stroke/pen are extracted and the drawing objects bind to them. The default is true.
    <ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" 
      xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
      <Color x:Key="Color1">#FF000000</Color>
      <Color x:Key="Color2">#FF008000</Color>
      <SolidColorBrush x:Key="Brush1" Color="{DynamicResource Color1}" />
      <SolidColorBrush x:Key="Brush2" Color="{DynamicResource Color2}" />
      <Pen x:Key="Pen1" Brush="{DynamicResource Brush2}" Thickness="1" StartLineCap="Flat" EndLineCap="Flat" LineJoin="Miter" />
      <DrawingImage x:Key="simple-path">
        <DrawingImage.Drawing>
          <DrawingGroup ClipGeometry="F0M0,0L110,0L110,110L0,110z">
            <GeometryDrawing Brush="{DynamicResource Brush1}" Pen="{DynamicResource Pen1}" 
            Geometry="F1M10,30A20,20,0,0,1,50,30A20,20,0,0,1,90,30Q90,60 50,90 10,60 10,30z" />
          </DrawingGroup>
        </DrawingImage.Drawing>
      </DrawingImage>
    </ResourceDictionary>
    
  • BindToColors: This determines if the color object of brushes is extracted. The default is true.
    <ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" 
      xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
      <Color x:Key="Color1">#FF000000</Color>
      <Color x:Key="Color2">#FF008000</Color>
      <SolidColorBrush x:Key="Brush1" Color="{DynamicResource Color1}" />
      <SolidColorBrush x:Key="Brush2" Color="{DynamicResource Color2}" />
      <Pen x:Key="Pen1" Brush="{DynamicResource Brush2}" Thickness="1" StartLineCap="Flat" EndLineCap="Flat" LineJoin="Miter" />
      <DrawingImage x:Key="simple-path">
        <DrawingImage.Drawing>
          <DrawingGroup ClipGeometry="F0M0,0L110,0L110,110L0,110z">
            <GeometryDrawing Brush="{DynamicResource Brush1}" Pen="{DynamicResource Pen1}" 
            Geometry="F1M10,30A20,20,0,0,1,50,30A20,20,0,0,1,90,30Q90,60 50,90 10,60 10,30z" />
          </DrawingGroup>
        </DrawingImage.Drawing>
      </DrawingImage>
    </ResourceDictionary>
    
  • BindPenToBrushes: This determines if solid color brushes of pen/stroke is extracted. The default is true.
    <ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" 
      xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
      <Color x:Key="Color1">#FF000000</Color>
      <Color x:Key="Color2">#FF008000</Color>
      <SolidColorBrush x:Key="Brush1" Color="{DynamicResource Color1}" />
      <SolidColorBrush x:Key="Brush2" Color="{DynamicResource Color2}" />
      <Pen x:Key="Pen1" Brush="{DynamicResource Brush2}" Thickness="1" StartLineCap="Flat" EndLineCap="Flat" LineJoin="Miter" />
      <DrawingImage x:Key="simple-path">
        <DrawingImage.Drawing>
          <DrawingGroup ClipGeometry="F0M0,0L110,0L110,110L0,110z">
            <GeometryDrawing Brush="{DynamicResource Brush1}" Pen="{DynamicResource Pen1}" 
            Geometry="F1M10,30A20,20,0,0,1,50,30A20,20,0,0,1,90,30Q90,60 50,90 10,60 10,30z" />
          </DrawingGroup>
        </DrawingImage.Drawing>
      </DrawingImage>
    </ResourceDictionary>
    
    Note

    If BindToResources property is set to false, then BindToColors and BindPenToBrushes values have no effect.

  • UseResourceIndex: This specifies whether a zero-based numbering is applied to the key names. The default is false.
    <ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" 
      xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
      <Color x:Key="Color0">#FF000000</Color>
      <Color x:Key="Color1">#FF008000</Color>
      <SolidColorBrush x:Key="Brush0" Color="{DynamicResource Color0}" />
      <SolidColorBrush x:Key="Brush1" Color="{DynamicResource Color1}" />
      <Pen x:Key="Pen0" Brush="{DynamicResource Brush1}" Thickness="1" StartLineCap="Flat" EndLineCap="Flat" LineJoin="Miter" />
      <DrawingImage x:Key="simple-path">
        <DrawingImage.Drawing>
          <DrawingGroup ClipGeometry="F0M0,0L110,0L110,110L0,110z">
            <GeometryDrawing Brush="{DynamicResource Brush0}" Pen="{DynamicResource Pen0}" 
            Geometry="F1M10,30A20,20,0,0,1,50,30A20,20,0,0,1,90,30Q90,60 50,90 10,60 10,30z" />
          </DrawingGroup>
        </DrawingImage.Drawing>
      </DrawingImage>
    </ResourceDictionary>
    
  • ColorPalette: This is a dictionary of color to resource key, specifying a predefined resource keys for the colors.
    // Create the resource settings or options
    var resourceSettings = new WpfResourceSettings() {
        ColorPalette = new Dictionary<Color, string>(WpfDrawingResources.ColorComparer) {
            { (Color)ColorConverter.ConvertFromString("#FF000000"), "IconFill" },
            { (Color)ColorConverter.ConvertFromString("#FF008000"), "IconBorder" },
        }
    };
    // Add a directory as SVG source
    resourceSettings.AddSource(svgDir);
    
    // Create the resource converter
    var converter = new ResourceSvgConverter(resourceSettings);
    
    // Perform the conversion to ResourceDictionary XAML
    var xamlText = converter.Convert();
    
  • Sources: Gets an enumeration of all the SVG sources to be converted to the resource dictionary.
  • SourceCount: Gets the count of the SVG sources to be converted to the resource dictionary.
  • ResourceAccess: This specifies the type of the resource access, DynamicResource or StaticResource, to be applied to the resource objects.
    <ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" 
      xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
      <Color x:Key="Color1">#FF000000</Color>
      <Color x:Key="Color2">#FF008000</Color>
      <SolidColorBrush x:Key="Brush1" Color="{DynamicResource Color1}" />
      <SolidColorBrush x:Key="Brush2" Color="{DynamicResource Color2}" />
      <Pen x:Key="Pen1" Brush="{DynamicResource Brush2}" Thickness="1" StartLineCap="Flat" EndLineCap="Flat" LineJoin="Miter" />
      <DrawingImage x:Key="simple-path">
        <DrawingImage.Drawing>
          <DrawingGroup ClipGeometry="F0M0,0L110,0L110,110L0,110z">
            <GeometryDrawing Brush="{DynamicResource Brush1}" Pen="{DynamicResource Pen1}" 
            Geometry="F1M10,30A20,20,0,0,1,50,30A20,20,0,0,1,90,30Q90,60 50,90 10,60 10,30z" />
          </DrawingGroup>
        </DrawingImage.Drawing>
      </DrawingImage>
    </ResourceDictionary>
    
  • ResourceResolverType: Get a value specifying the current type of the resource object key resolver, which is implemented through the IResourceKeyResolver interface. This applies to the main resource objects; DrawingGroup or DrawingImage (and not the color, brush or pen objects). It returns None, if no resource key resolver is attached, in which case the default resolver will be used.

ResourceKeyResolverType - Default

For the illustration of the resource key resolvers, we will assume a directory containing the following three SVG files

about.svg area_chart.svg crystal_oscillator.svg

The following sample code uses the default resource key resolver, or ResourceKeyResolver, to resolve the keys. If not specified, the keys are simply the SVG file names without the file extension.

using System.Windows;
using System.Windows.Media;

using SharpVectors.Renderers;
using SharpVectors.Converters;
using SharpVectors.Renderers.Wpf;

namespace SharpVectors.Test.Sample
{
    public class ResourceSvgConverterSample
    {
        private WpfResourceSettings _resourceSettings;

        public ResourceSvgConverterSample()
        {
            // Create the resource settings or options
            _resourceSettings = new WpfResourceSettings();
            _resourceSettings.ResourceFreeze = false; // Do not freeze
        }

        public string Convert(string svgDir)
        {
            // Add a directory as SVG source
            _resourceSettings.AddSource(svgDir);

            // Create the resource converter
            var converter = new ResourceSvgConverter(_resourceSettings);

            // Perform the conversion to ResourceDictionary XAML
            return converter.Convert();
        }
    }
}

The default resource key resolver supports simple string substitution template to customize the resource keys with the following two tags

  • ${name}: Representing the SVG file name without the extension.
  • ${number}: Representing the resource number or index, depending on the value of the UseResourceIndex property.

For instance, using a key format of icon_${name}, an SVG file with the name about.svg will be have a resource key: icon_about.

The following sample code illustrates how the customized resource key format is applied with the default resolver, and the resulting XAML output:

using System.Windows;
using System.Windows.Media;

using SharpVectors.Renderers;
using SharpVectors.Converters;
using SharpVectors.Renderers.Wpf;

namespace SharpVectors.Test.Sample
{
    public class ResourceSvgConverterSample
    {
        private WpfResourceSettings _resourceSettings;

        public ResourceSvgConverterSample()
        {
            // Create the resource settings or options
            _resourceSettings = new WpfResourceSettings();
            _resourceSettings.ResourceFreeze = false; // Do not freeze

            // Initialize the default key resolver and register it
            var resolver = new ResourceKeyResolver("icon_${name}");
            _resourceSettings.RegisterResolver(resolver);
        }

        public string Convert(string svgDir)
        {
            // Add a directory as SVG source
            _resourceSettings.AddSource(svgDir);

            // Create the resource converter
            var converter = new ResourceSvgConverter(_resourceSettings);

            // Perform the conversion to ResourceDictionary XAML
            return converter.Convert();
        }
    }
}

ResourceKeyResolverType - Dictionary

For the cases where the resource keys are already defined, you can use the dictionary resolver, or DictionaryKeyResolver, which maps the SVG file names without the extension to the predefined keys.

The following sample code uses the dictionary resource key resolver, or DictionaryKeyResolver, to resolve the keys of the previous SVG sample files.

using System;
using System.Collections.Generic;
using System.Windows;
using System.Windows.Media;

using SharpVectors.Renderers;
using SharpVectors.Converters;
using SharpVectors.Renderers.Wpf;

namespace SharpVectors.Test.Sample
{
    public class ResourceSvgConverterSample
    {
        private WpfResourceSettings _resourceSettings;

        public ResourceSvgConverterSample()
        {
            // Create the resource settings or options
            _resourceSettings = new WpfResourceSettings();
            _resourceSettings.ResourceFreeze = false; // Do not freeze

            // Create a dictionary of SVG file name to predefined names
            var dictionaryKeys = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase)
            {
                { "about",              "About" },
                { "area_chart",         "AreaChart" },
                { "crystal_oscillator", "CrystalOscillator" }
            };
            // Initialize the dictionary key resolver and register it
            var resolver = new DictionaryKeyResolver(dictionaryKeys);
            _resourceSettings.RegisterResolver(resolver);
        }

        public string Convert(string svgDir)
        {
            // Add a directory as SVG source
            _resourceSettings.AddSource(svgDir);

            // Create the resource converter
            var converter = new ResourceSvgConverter(_resourceSettings);

            // Perform the conversion to ResourceDictionary XAML
            return converter.Convert();
        }
    }
}

ResourceKeyResolverType - CodeSnippet

In applications you need to use a code snippet to implement the IResourceKeyResolver interface, we provider the code snippet resolver, or CodeSnippetKeyResolver, which uses CodeDomProvider to compile the implementation in memory and use it to resolve the keys. This is used to reduce dependencies but limits the lanagauge feature supports to .NET 4.0.

For the code snippet, the following conditions are required

  • The namespace must be SharpVectors.Renderers.
  • The class name must be SnippetKeyResolver.

The following sample code uses the snippet resource key resolver, or CodeSnippetKeyResolver, to resolve the keys. To reduce the code length for the screen, we will assume the snippet is stored in a file, and load it from the file.

using System;
using System.Xml;
using System.Windows;

namespace SharpVectors.Renderers {
    public sealed class SnippetKeyResolver : WpfSettings<SnippetKeyResolver>, IResourceKeyResolver {
        public SnippetKeyResolver() {
        }

        public ResourceKeyResolverType ResolverType {
            get {
                return ResourceKeyResolverType.Custom;
            }
        }

        public bool IsValid {
            get {
                return true;
            }
        }

        public void BeginResolve() {
        }

        public void EndResolve() {
        }

        public override SnippetKeyResolver Clone() {
            return new SnippetKeyResolver();
        }

        public override void ReadXml(XmlReader reader) {
        }

        public override void WriteXml(XmlWriter writer) {
        }

        public string Resolve(DependencyObject resource, int index, string fileName, string fileSource) {
            if (index < 0) {
                throw new ArgumentException("The specified index is invalid", "index");
            }
            NotNullNotEmpty(fileName, "fileName");

            var keyValue = ToLowerCamelCase(fileName.ToUpper());
            if (!string.IsNullOrWhiteSpace(keyValue) && keyValue.Length >= 3 && keyValue.Length < 255) {
                return keyValue;
            }
            return fileName;
        }

        private static string ToLowerCamelCase(string fileName) {
            if (string.IsNullOrWhiteSpace(fileName)) {
                return string.Empty;
            }

            string camelCaseStr = char.ToLower(fileName[0]).ToString();

            if (fileName.Length > 1) {
                bool isStartOfWord = false;
                for (int i = 1; i < fileName.Length; i++) {
                    char currChar = fileName[i];
                    if (currChar == '_' || currChar == '-') {
                        isStartOfWord = true;
                    } else if (char.IsUpper(currChar)) {
                        if (isStartOfWord) {
                            camelCaseStr += currChar;
                        } else {
                            camelCaseStr += char.ToLower(currChar);
                        }
                        isStartOfWord = false;
                    } else {
                        camelCaseStr += currChar;
                        isStartOfWord = false;
                    }
                }
            }
            return camelCaseStr;
        }
    }
}

The test code snippet (C# code) will convert the specified SVG file name to the lower camel case naming format as the resource key.

using System.Windows;
using System.Windows.Media;

using SharpVectors.Renderers;
using SharpVectors.Converters;
using SharpVectors.Renderers.Wpf;

namespace SharpVectors.Test.Sample
{
    public class ResourceSvgConverterSample
    {
        private WpfResourceSettings _resourceSettings;

        public ResourceSvgConverterSample(string snippetFile)
        {
            // Create the resource settings or options
            _resourceSettings = new WpfResourceSettings();
            _resourceSettings.ResourceFreeze = false; // Do not freeze

            // Get the code snippet, here assumed from a file
            string codeSnippet = string.Empty;
            using (var reader = new System.IO.StreamReader(snippetFile))
            {
                codeSnippet = reader.ReadToEnd();
            }

            // Initialize the code snippet key resolver and register it
            var resolver = new CodeSnippetKeyResolver(codeSnippet, "cs");
            _resourceSettings.RegisterResolver(resolver);
        }

        public string Convert(string svgDir)
        {
            // Add a directory as SVG source
            _resourceSettings.AddSource(svgDir);

            // Create the resource converter
            var converter = new ResourceSvgConverter(_resourceSettings);

            // Perform the conversion to ResourceDictionary XAML
            return converter.Convert();
        }
    }
}

ResourceKeyResolverType - Custom

You can create a custom resource key resolver by implementing the IResourceKeyResolver interface.

The following sample code implements a resource key resolver that will convert the specified SVG file name to the upper camel case naming format as the resource key.

using System;
using System.Xml;

using System.Windows;

using SharpVectors.Renderers;

namespace SharpVectors.Test.Sample {
    internal sealed class CustomResourceKeyResolver : WpfSettings<CustomResourceKeyResolver>, IResourceKeyResolver {
        public CustomResourceKeyResolver() {
        }

        public override CustomResourceKeyResolver Clone() {
            return new CustomResourceKeyResolver();
        }

        public override void ReadXml(XmlReader reader) {
            NotNull(reader, nameof(reader));
        }

        public override void WriteXml(XmlWriter writer) {
            NotNull(writer, nameof(writer));
        }

        public ResourceKeyResolverType ResolverType {
            get {
                return ResourceKeyResolverType.Custom;
            }
        }

        public bool IsValid {
            get {
                return true;
            }
        }

        public void BeginResolve() {
        }

        public void EndResolve() {
        }

        public string Resolve(DependencyObject resource, int index, string fileName, string fileSource) {
            if (index < 0) {
                throw new ArgumentException("The specified index is invalid", "index");
            }
            NotNullNotEmpty(fileName, "fileName");

            var keyValue = ToUpperCamelCase(fileName.ToUpper());
            if (!string.IsNullOrWhiteSpace(keyValue) && keyValue.Length >= 3 && keyValue.Length < 255) {
                return keyValue;
            }
            return fileName;
        }

        internal static string ToUpperCamelCase(string fileName) {
            if (string.IsNullOrWhiteSpace(fileName)) {
                return string.Empty;
            }

            string camelCaseStr = fileName[0].ToString();

            if (fileName.Length > 1) {
                bool isStartOfWord = false;
                for (int i = 1; i < fileName.Length; i++) {
                    char currChar = fileName[i];
                    if (currChar == '_' || currChar == '-') {
                        isStartOfWord = true;
                    } else if (char.IsUpper(currChar)) {
                        if (isStartOfWord) {
                            camelCaseStr += currChar;
                        } else {
                            camelCaseStr += char.ToLower(currChar);
                        }
                        isStartOfWord = false;
                    } else {
                        camelCaseStr += currChar;
                        isStartOfWord = false;
                    }
                }
            }
            return camelCaseStr;
        }
    }
}

The following illustrate how to use the custom resource key resolver.

using System.Windows;
using System.Windows.Media;

using SharpVectors.Renderers;
using SharpVectors.Converters;
using SharpVectors.Renderers.Wpf;

namespace SharpVectors.Test.Sample
{
    public class ResourceSvgConverterSample
    {
        private WpfResourceSettings _resourceSettings;

        public ResourceSvgConverterSample()
        {
            // Create the resource settings or options
            _resourceSettings = new WpfResourceSettings();
            _resourceSettings.ResourceFreeze = false; // Do not freeze

            // Initialize the custom key resolver and register it
            var resolver = new CustomResourceKeyResolver();
            _resourceSettings.RegisterResolver(resolver);
        }

        public string Convert(string svgDir)
        {
            // Add a directory as SVG source
            _resourceSettings.AddSource(svgDir);

            // Create the resource converter
            var converter = new ResourceSvgConverter(_resourceSettings);

            // Perform the conversion to ResourceDictionary XAML
            return converter.Convert();
        }
    }
}

Options XML Serialization

The resource settings, WpfResourceSettings object can be saved to file in an XML format, or initialized from an XML format file.

You can, therefore, create a resource dictionary converter options for a project and share with other developers or projects. The following code defines a sample resource dictionary converter options and serializes WpfResourceSettings object to XML.

using System.Collections.Generic;
using System.Windows.Media;

using SharpVectors.Renderers;
using SharpVectors.Renderers.Wpf;

namespace SharpVectors.Test.Sample
{
    public class ResourceSvgConverterSample
    {
        private WpfResourceSettings _resourceSettings;

        public ResourceSvgConverterSample()
        {
            // Create the resource settings or options
            _resourceSettings = new WpfResourceSettings();
            _resourceSettings.ResourceFreeze = false; // Do not freeze

            // Initialize the default key resolver and register it
            var resolver = new ResourceKeyResolver("icon_${name}");
            _resourceSettings.RegisterResolver(resolver);

            // Add predefined color palette
            _resourceSettings.ColorPalette = new Dictionary<Color, string>(WpfDrawingResources.ColorComparer)
            {
                {(Color)ColorConverter.ConvertFromString("#FF008000"), "SvgColor01"},
                {(Color)ColorConverter.ConvertFromString("#FF000000"), "SvgColor02"},
                {(Color)ColorConverter.ConvertFromString("#FFFFFF00"), "SvgColor03"},
                {(Color)ColorConverter.ConvertFromString("#FF0000FF"), "SvgColor04"},
                {(Color)ColorConverter.ConvertFromString("#FF00FF00"), "SvgColor05"},
                {(Color)ColorConverter.ConvertFromString("#FF339966"), "SvgColor06"},
                {(Color)ColorConverter.ConvertFromString("#FFFF00FF"), "SvgColor07"},
                {(Color)ColorConverter.ConvertFromString("#FFFFA500"), "SvgColor08"},
                {(Color)ColorConverter.ConvertFromString("#FF007700"), "SvgColor09"},
                {(Color)ColorConverter.ConvertFromString("#FF33CC66"), "SvgColor10"}
            };

            // Add directories as SVG source
            _resourceSettings.AddSource(@"C:\Abc-Project\Icons1");
            _resourceSettings.AddSource(@"C:\Abc-Project\Icons2");
        }

        public string Save()
        {
            // Serialize the resource settings to XML
            return _resourceSettings.Save();
        }
    }
}