Search code examples
c#svguwpwin2d

Export UWP canvas to SVG


I'm trying to create an SVG file from some shape objects(path geometries, ellipses etc.) drawn on XAML canvas controls (the canvases are rendered on top of each other inside grid controls). It looks like Win2D can provide the classes to generate the SVG file, but I'm struggling to figure out how to populate the CanvasSvgDocument class with the shapes.

This is the only partial example I've found, but the answer seems to include a conversion to XML strings to load into the CanvasSvgDocument which seems like doing the same task twice (as SVG files are XML). Is anybody able to provide an example of how I might do this?

My current best guess of what the resulting code might look like is:

using Microsoft.Graphics.Canvas;
using Microsoft.Graphics.Canvas.Svg;
using Microsoft.Graphics.Canvas.UI.Xaml;
using System;
using System.Threading.Tasks;
using Windows.Storage.Streams;
using Windows.UI.Xaml;
using Windows.UI.Xaml.Controls;
using Windows.UI.Xaml.Media;
using Windows.UI.Xaml.Shapes;

namespace MyApp
{
    public class ExportSVG
    {
        private CanvasSvgDocument SVG { get; } = new(new CanvasDevice());

        public async Task SaveASync(IRandomAccessStream stream) => await SVG.SaveAsync(stream);

        public void AddCanvases(UIElement element)
        {
            if (element is Grid grid)
            {
                foreach (UIElement child in grid.Children)
                {
                    AddCanvases(child);
                }
            }
            else if (element is Canvas canvas)
            {
                AddCanvas(canvas);
            }
        }

        public void AddCanvas(Canvas canvas)
        {
            foreach (UIElement element in canvas.Children)
            {
                if (element is Path path)
                {
                    if (path.Data is PathGeometry pathGeometry)
                    {
                        foreach (PathFigure pathFigure in pathGeometry.Figures)
                        {
                            // Add path to SVG
                        }
                    }
                    else if (path.Data is EllipseGeometry ellipseGeometry)
                    {
                        // Add ellipse to SVG
                    }
                }
                else if (element is TextBlock textBlock)
                {
                    // add text to SVG
                }
            }
        }
    }
}

Solution

  • In the end I was able to use the XmlWriter class to write my own canvas-to-svg converter. Using the example in the question:

    using Microsoft.Graphics.Canvas;
    using Microsoft.Graphics.Canvas.Svg;
    using Microsoft.Graphics.Canvas.UI.Xaml;
    using System;
    using System.Xml;
    using System.Threading.Tasks;
    using Windows.Storage.Streams;
    using Windows.UI.Xaml;
    using Windows.UI.Xaml.Controls;
    using Windows.UI.Xaml.Media;
    using Windows.UI.Xaml.Shapes;
    
    namespace MyApp
    {
        public class ExportSVG
        {
            private XmlWriter Writer { get; }
            public SVGWriter(System.IO.Stream stream)
            {
                Writer = XmlWriter.Create(stream, new XmlWriterSettings()
                {
                    Indent = true,
                });
                Writer.WriteStartElement("svg", "http://www.w3.org/2000/svg");
                Write("version", "1.1");
            }
    
            public void AddCanvases(UIElement element)
            {
                if (element is Grid grid)
                {
                    foreach (UIElement child in grid.Children)
                    {
                        AddCanvases(child);
                    }
                }
                else if (element is Canvas canvas)
                {
                    AddCanvas(canvas);
                }
            }
    
            public void AddCanvas(Canvas canvas)
            {
                foreach (UIElement element in canvas.Children)
                {
                    if (element is Path path)
                    {
                        else if (path.Data is EllipseGeometry ellipseGeometry)
                        {
                            Writer.WriteStartElement("ellipse");
                            Write("stroke", ellipseGeometry.Stroke);
                            Write("stroke-width", ellipseGeometry.StrokeThickness);
                            Write("cx", ellipseGeometry.Center.X);
                            Write("cy", ellipseGeometry.Center.Y);
                            Write("rx", ellipseGeometry.RadiusX);
                            Write("ry", ellipseGeometry.RadiusY);
                            Writer.WriteEndElement();
                        }
                    }
                    else if (element is TextBlock textBlock)
                    {
                        Writer.WriteStartElement("text");
                        Write("x", Canvas.GetLeft(textBlock));
                        Write("y", Canvas.GetTop(textBlock) + textBlock.ActualHeight);
                        Write("font-family", textBlock.FontFamily.Source);
                        Write("font-size", $"{textBlock.FontSize}px");
                        Writer.WriteString(textBlock.Text);
                        Writer.WriteEndElement();
                    }
                }
            }
    
            private void Write(string name, string value)
            {
                Writer.WriteAttributeString(name, value);
            }
    
            private void Write(string name, double value)
            {
                Write(name, ((float)value).ToString());
            }
    
            public void Dispose()
            {
                Writer.WriteEndElement();
                Writer.Close();
                Writer.Dispose();
            }
        }
    }