Wednesday, January 27, 2016

Copy an SVG image to the clipboard from C#

Despite the title, this is really an article about putting arbitrary binary data onto the clipboard from .net.

Suppose you have the XML text corresponding to an SVG image stored in a string, and you'd like to put in on the clipboard as an image (not as text). The MIME type is "image/svg+xml", so it seems like the solution is straightforward:

string svg = GetSvg();
Clipboard.SetData("image/svg+xml", svg); // won't work

However, if you do this and then inspect the contents of the clipboard, you'll see that .net added a number of bytes in front of your xml text:


That's no good, and it means any SVG editor is going to reject an attempt to paste the image. Maybe we should try straight binary data instead of a string?

string svg = GetSvg();
byte[] bytes = Encoding.UTF8.GetBytes(svg);
Clipboard.SetData("image/svg+xml", bytes); // still won't work

This gives the same result, but with a slightly different set of extra bytes prepended. The fact that the prefix changed slightly when the datatype changed suggests we might be looking at a serialization header. And indeed, if you dig through the MSDN documentation, you might eventually find this note with the Clipboard class (emphasis mine):
An object must be serializable for it to be put on the Clipboard. If you pass a non-serializable object to a Clipboard method, the method will fail without throwing an exception. See System.Runtime.Serialization for more information on serialization. If your target application requires a very specific data format, the headers added to the data in the serialization process may prevent the application from recognizing your data. To preserve your data format, add your data as a Byte array to a MemoryStream and pass the MemoryStream to the SetData method.
Aha! Take 3:

string svg = GetSvg();
byte[] bytes = Encoding.UTF8.GetBytes(svg);
MemorySteam stream = new MemoryStream(bytes);
Clipboard.SetData("image/svg+xml", stream);

This, finally, will give the result you want. The same technique will work with DataObject, in case you want to copy a bitmap version of the image alongside the SVG.

Might have been nice to have that little note with Clipboard.SetData() too, but such is the life of a software developer. :)