My notes on how to use SVG graphics in a WPF application
The Goal
The goal is to be able do do things like this:
... where mySvgImage somehow stands for a vector image that has somehow been obtained from an SVG file.
The solution must not involve any proprietary, closed-source libraries.
Naturally, we want one of the following:
- either directly include SVG files into our application as resources, or, if that is not possible, then
- have an "asset pipeline" approach where our SVG files are automatically converted during build into some format which is suitable for inclusion as a resource.
The Problem
WPF likes to do everything in its own idiomatic way, so vector graphics in WPF are specified using the following construct:
<Path Fill="#FFE68619">
<Path.Data>
<PathGeometry Figures="M8.564 1.289.2 16.256A.5.5 0 0 0 .636 17H17.364a.5.5 0 0 0 .5-.5.494.494 0 0 0-.064-.244L9.436 1.289a.5.5 0 0 0-.872 0ZM10 14.75a.25.25 0 0 1-.25.25H8.25A.25.25 0 0 1 8 14.75v-1.5A.25.25 0 0 1 8.25 13h1.5a.25.25 0 0 1 .25.25Zm0-3a.25.25 0 0 1-.25.25H8.25A.25.25 0 0 1 8 11.75v-6a.25.25 0 0 1 .25-.25h1.5a.25.25 0 0 1 .25.25Z" FillRule="NonZero"/>
</Path.Data>
</Path>
</Canvas>
Even though this notation is very similar to SVG path notation, WPF has no built-in support for SVG. They do not list SVG in their list of supported image file formats. (⬀) This is preposterous, but that's what it is.
Microsoft has added SVG support to UWP via the SvgImageSource class, (⬀) but they have not bothered doing the same for WPF, probably as part of their greater strategy to let WPF go gently into that good night, since UWP is their new big thing that everyone must now start using whether they like it or not.
Allegedly, there exists something called "XAML Islands" which offers means of including UWP controls within WPF applications, (⬀) (⬀) but it looks very convoluted, and the result will probably be held together by shoestrings, so I am not going to research that approach.
The Inkscape approach
Inkscape offers the ability to export SVG to something they call "xaml format", and it can even do that from the command line (⬀), making it suitable for using in an asset pipeline, but this feature is extremely limited. As far as I can tell, Inkscape offers absolutely no options for controlling how the exporting will be done; it just creates files that look like this:
<!--This file is NOT compatible with Silverlight-->
<Viewbox xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" Stretch="Uniform">
<Canvas Name="svg10" Width="18" Height="18">
<Canvas.RenderTransform>
<TranslateTransform X="0" Y="0"/>
</Canvas.RenderTransform>
<Canvas.Resources/>
<!--Unknown tag: metadata-->
<!--Unknown tag: sodipodi:namedview-->
<Canvas Name="AlertMedium">
<Path xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" Name="Shape" Fill="#FFE68619">
<Path.Data>
<PathGeometry Figures="M8.564 1.289.2 16.256A.5.5 0 0 0 .636 17H17.364a.5.5 0 0 0 .5-.5.494.494 0 0 0-.064-.244L9.436 1.289a.5.5 0 0 0-.872 0ZM10 14.75a.25.25 0 0 1-.25.25H8.25A.25.25 0 0 1 8 14.75v-1.5A.25.25 0 0 1 8.25 13h1.5a.25.25 0 0 1 .25.25Zm0-3a.25.25 0 0 1-.25.25H8.25A.25.25 0 0 1 8 11.75v-6a.25.25 0 0 1 .25-.25h1.5a.25.25 0 0 1 .25.25Z" FillRule="NonZero"/>
</Path.Data>
</Path>
</Canvas>
</Canvas>
</Viewbox>
Besides the obvious problem of useless elements that should not be there, the main problem with these files is that they are just plain unusable, because XAML offers no means of including one XAML file from within another XAML file. The only file inclusion mechanism supported by XAML is resource dictionaries, but this file is not describing a resource dictionary, so it cannot be included.
If you go looking for answers on Stackoverflow you will find that people copy-paste the PathGeometry elements from Inkscape-generated XAML files into their own XAML files, and they find nothing retarded in what they are doing. Others suggest to take the xaml files generated by Inkscape and copy them to Blend, and then convert them to Path, because the more clicks the better, I suppose.
It may be possible to programmatically load Inkscape-generated XAML files using the following construct:
new Uri("pack://application:,,,/Resources/MySvgExportedAsXaml.xaml")).Stream)
{
object o = XamlReader.Load(stream);
}
... but then it is unclear how to make objects created this way usable from within XAML.
punker76 (the main person behind MahApps) offers a way of programmatically creating an icon pack in his blog post titled "How to create a new IconPack with custom SVG Paths". (⬀) Such an icon pack can then be conveniently used from within XAML. However, his solution is tied to MahApps, while I would prefer an independent solution, and it is still not clear how to make that work with an object obtained by invoking the XamlReader.Load method.
In any case, the process already looks quite a bit convoluted, so that's where I stop researching the Inkscape approach.
References:
- Stackoverflow: "Import separate XAML as ResourceDictionary and assign x:Key" (⬀)
- Stackoverflow: "Inkscape (vector graphic)" (⬀)
Libraries
If you go searching for library suggestions on Stackoverflow you will find the following:- DevExpress (⬀)
- It looks very promising, (if we are to judge by how cool their web site looks,) but it is proprietary, so it is off-topic in this discussion.
- Svg2Xaml (⬀)
- This project is hosted in codeplex, which is closing down in July of 2021, and the author does not seem to have moved his project elsewhere. Some guy called Stefan Mischke has forked it on github (⬀) and has made a nuget package out of it, (⬀) but the lack of support and documentation makes this very unpromising.
- BerndK/SvgToXaml (⬀)
- This just converts svg to xaml. It might be possible to use it at runtime to load svg resources and convert them to xaml paths, and then use some other mechanism to make these paths usable from within xaml, but the process seems convoluted.
- SVG rendering engine (⬀)
- This is described as a WinForms solution, so whatever it does, it will not be directly usable from within XAML. I am listing it here because it might turn out to be useful, but I will not research it any further.
- ReaderSVG from AB4D (⬀ - link is defunct)
- This may have been an open-source project at some point a long time ago, but it looks like it has now become a commercial product (⬀), and archive.org does not index the old home of the project anymore.
- mntone/SvgForXaml (⬀)
- The README.md file shows a XAML snippet that looks promising, but it also says "Requirement: Win2D" and contains a warning about Windows 10 Aniversary Update, and I have no clue what are the implications of these statements. Overall, the project looks perhaps a bit too simplistic. Only try if other avenues fail.
- CodeProject: "SVGImage Control" (⬀)
- The author says "The SVG parser and render is still not complete, but it is in a state where it can read most 'simple' SVG files I have found online so far." So, no time should be invested on this. See next entry, "dotnetprojects/SVGImage".
- dotnetprojects/SVGImage (⬀)
- Initially forked from CodeProject: "SVGImage Control"; appears to be in active development, (last update 5 months ago.) Its README.md looks tacky, but it may work, and it even has a nuget package (⬀).
- SharpVectorGraphics (aka SVG#) (⬀)
- A very old, abandoned project. Has no documentation. Uses org.w3c.dom.svg to convert to GDI GraphicsPath. Contains some incomplete code. Not worth looking at. This is what SharpVectors (see below) started out from.
- SharpVectors (⬀)
References:
- Stackoverflow: "Library for using SVG in Windows Forms / WPF?" (⬀)
What if I already have some XAML graphics?
If you are already using some graphics that exist in XAML-only form, (for example, the MahApps icon packs,) and you would prefer to have all vector graphics expressed in a single format throughout your application, then you might want to perform a one-time conversion of your existing XAML graphics to SVG. This will also allow you to get rid of the MahApps icon packs, which are many megabytes large, while your application is presumably using only a tiny fraction of them.
For simple graphics, I have found that XAML notation is identical to SVG notation, so simply pasting the XAML string (<PathGeometry Figures="...") into the corresponding SVG string (<path d="...") yields a valid SVG file that you can work with. Since this is a one-time-only operation, it is not too bad if it has to be done manually.
If there is a need for more advanced conversions, it appears that there are no tools out there for converting XAML to SVG, but this might be useful: Stackoverflow: "Wpf InkCanvas save stokes as svg" (⬀)
SVG Validation
Of course, if you start generating SVG from other sources, you are bound to end up with invalid SVG files. Unfortunately, as far as I can tell, Inkscape does not give the slightest indication that there is something wrong with an SVG file. If you give Inkscape an invalid file to load, it will silently fail and render nothing.
To troubleshoot SVG issues, a somewhat decent SVG validator is validator.nu. (At least it helped me find problems in one SVG file, and after I fixed it, Inkscape successfully loaded it.)
Another validator, which though is a bit too verbose and a bit too strict, is the W3C validator.
There also exists something called "SVG Sanitizer" at svg.enshrined.co.uk, written by some guy called Daryll Doyle, it is completely useless, do not waste any time with it. (You can take hint from the fact that it is written in PHP.)
Спасибо!
ReplyDelete