Sitecore SVG Support

SVG support is a topic that has already been discussed multiple times in blog posts, stackoverflow answers etc.

The main and the most common solution is to register the SVG MIME Type under the /sitecore/mediaLibrary/mediaTypes node like the following line.




<mediaLibrary>
      <mediaTypes>
        <mediaType name="SVG" extensions="svg">
          <mimeType>image/svg+xml</mimeType>
          <forceDownload>false</forceDownload>
          <sharedTemplate>system/media/unversioned/image</sharedTemplate>
          <versionedTemplate>system/media/versioned/image</versionedTemplate>
          <mediaValidator type="Sitecore.Resources.Media.ImageValidator"/>
          <thumbnails>
            <generator type="Sitecore.Resources.Media.ImageThumbnailGenerator, Sitecore.Kernel">
              <extension>png</extension>
            </generator>
            <width>150</width>
            <height>150</height>
            <backgroundColor>#FFFFFF</backgroundColor>
          </thumbnails>
        </mediaType>
      </mediaTypes>
    </mediaLibrary>


And that is it ! Now your SVG images can be safely uploaded to the Media Library !

However now you will encounter some problems when the content editors start uploading and using SVG Images, so I decided to put a blog post about them.

Note: All source code shown here is using Sitecore 8.1 Initial Release. Also because the code for the corresponding pages is pretty big I will just post the relevant fragments here. The full code of the solution can be found on BitBucket.

Now we’ve got the SVG images into the media library. The next step is to use them around the site right? Which leads me to:

Problem 1: My Rich Text Editor is not accepting SVGs !

Imagine one of our content editors decided to place the SVG image in a Rich Text Field somewhere in the site, sounds simple right ? Well, it does sound simple, but as it seems – it is not, because after trying to add the image via the Insert Sitecore Media button the following message appears:

Media Library

As it seems Sitecore still doesn’t like SVGs and it doesn’t treat them as images that can be placed in the Rich Text fields. So after spending some time with the decompiled Sitecore.Client assembly, the exception can be traced to the following line located in the InsertImageForm class in the OnOK function:


else if (!(MediaManager.GetMedia(MediaUri.Parse((Item) mediaItem)) is ImageMedia))
{
    SheerResponse.Alert("The selected item is not an image. Select an image to continue.");
}

So Sitecore is not considering our SVG as ImageMedia.

Note: Here is the time to say that I haven`t found an approach to register the SVG as ImageMedia. If someone has an idea I will be very grateful if he can post it in the comments section.

Well the fix is pretty straightforward – nothing fancy here. We need to hijack the dialog and add some custom logic to recognize our SVG as a real image.

The first thing we need to do is to create our custom class that will be an exact replica of the decompiled code of the InsertImageForm class, except the modified else/if statement which should be modified to as follows:

else if (!(MediaManager.GetMedia(MediaUri.Parse(mediaItem)) is ImageMedia || mediaItem.MimeType == "image/svg+xml"]))
{
    SheerResponse.Alert("The selected item is not an image. Select an image to continue.");
}

The next step is to make the dialog use our own class. The XML Layout file for this dialog is sitecore\shell\Controls\Rich Text Editor\InsertImage\InsertImage.xml. There we need to replace the following line:


<CodeBeside Type="Sitecore.Shell.Controls.RichTextEditor.InsertImage.InsertImageForm,Sitecore.Client">

with



<CodeBeside Type="[YOUR_CLASS_NAME],[YOUR_ASSEMBLY]"/>


Note: I usually prefer to copy and paste the XML Layout into /sitecore/shell/override folder instead of modifying the existing one.

And now we can safely add our SVG Image !

Media Library Uploaded

But there is still a problem, which leads me to:

Problem 2: My Rich Text Editor is not handling the SVG correctly !

So now we need to make the image appear ! The problem shows itself when we check the html of the Rich Text Editor.

SVG Image

The SVG image is handled as a normal Sitecore Media File, meaning that it goes through the image handler and is displayed as a normal image in <img> tag. The problem is that the SVG is actually a XML File which needs to be rendered as it is for consistency. If your FrontEnd developers don’t want to manipulate the actual SVG when it gets rendered – you can stop reading here :). If you are one of the few unlucky ones – no worries, there is hope ! The salvation is again in the OnOK method of the InsertImageForm class, just a line bellow our modifications. If the image is considered a valid image file the following code gets executed:


MediaUrlOptions shellOptions = MediaUrlOptions.GetShellOptions();
shellOptions.Language = this.ContentLanguage;
string text = !string.IsNullOrEmpty(HttpContext.Current.Request.Form["AlternateText"]) ? HttpContext.Current.Request.Form["AlternateText"] : mediaItem.Alt;
Tag image = new Tag("img");
this.SetDimensions(mediaItem, shellOptions, image);
image.Add("Src", MediaManager.GetMediaUrl(mediaItem, shellOptions));
image.Add("Alt", StringUtil.EscapeQuote(text));
image.Add("_languageInserted", "true");
if (this.Mode == "webedit")
{
    SheerResponse.SetDialogValue(StringUtil.EscapeJavascriptString(image.ToString()));
    base.OnOK(sender, args);
}
else
    SheerResponse.Eval("scClose(" + StringUtil.EscapeJavascriptString(image.ToString()) + ")");

So no matter the image type – it always gets appended to an <img> tag with the corresponding URL. In order to make our SVG fully functional instead of rendering the image itself we need to render the XML contents of the actual media file. It can be done easily by reading the stream of the actual media image. Also we need to make sure that we set the width and height in which we want to display our SVG. The modified code looks like this:


if (mediaItem.MimeType == "image/svg+xml")
{
   string result;

   using (StreamReader reader = new StreamReader(mediaItem.GetMediaStream(), Encoding.UTF8))
   {
      result = reader.ReadToEnd();
   }

   XDocument svg = XDocument.Parse(result);
   NameValueCollection form = HttpContext.Current.Request.Form;

   if (svg.Document?.Root != null)
   {
      int width;

      if (int.TryParse(form["Width"], out width))
      {
         svg.Document.Root.SetAttributeValue("width", width);
      }

      int height;

      if (int.TryParse(form["Height"], out height))
      {
          svg.Document.Root.SetAttributeValue("height", height);
      }

      result = svg.ToString();
   }

   if (Mode == "webedit")
   {
      SheerResponse.SetDialogValue(StringUtil.EscapeJavascriptString(result));
      base.OnOK(sender, args);
   }
   else
   {
      SheerResponse.Eval("scClose(" + StringUtil.EscapeJavascriptString(result) + ")");
   }

}
else
{
   Tag image = new Tag("img");
   SetDimensions(mediaItem, shellOptions, image);
   image.Add("Src", MediaManager.GetMediaUrl(mediaItem, shellOptions));
   image.Add("Alt", StringUtil.EscapeQuote(text));
   image.Add("_languageInserted", "true");

   if (Mode == "webedit")
   {
      SheerResponse.SetDialogValue(StringUtil.EscapeJavascriptString(image.ToString()));
      base.OnOK(sender, args);
   }
   else
   {
        SheerResponse.Eval("scClose(" + StringUtil.EscapeJavascriptString(image.ToString()) + ")");
   }
}

By doing this modification – now the SVG is rendered as the xml it should be rendered as ! 🙂

SVG Rendered

So up to now we can properly render SVG Images in our Rich Text Editor ! But

Problem 3: But what about rendering it on a Sublayout/Rendering ?

Well, the next struggle is to render the SVG correctly in our components. And here is one of the things I love most about Sitecore – all field renders (if used correctly :)) goes through the RenderField pipeline ! Meaning – we just need to modify the GetImageFieldValue processor with our own and we should be set !

The GetImageFieldValue processor renders the image thanks to ImageRenderer helper class. So we need to create our own ImageRenderer which is going to support SVGs as well.

We are going to create a new class named ImageRendererEx which will contain all the code from the original ImageRenderer, but have modifications for rendering our SVG images. The only change here should be in the Render() method. The modified Render function looks like this:


public virtual RenderFieldResult Render()
{
   var obj = Item;
   if (obj == null)
   {
      return RenderFieldResult.Empty;
   }

   var keyValuePairs = Parameters;

   if (keyValuePairs == null)
   {
      return RenderFieldResult.Empty;
   }

   ParseNode(keyValuePairs);

   var innerField = obj.Fields[FieldName];

   if (innerField != null)
   {
      imageField = new ImageField(innerField, FieldValue);

      ParseField(imageField);
      AdjustImageSize(imageField, scale, maxWidth, maxHeight, ref width,
      ref height);

      if (imageField.MediaItem != null)
      {
         MediaItem imageMediaItem = new MediaItem(imageField.MediaItem);

         if (imageMediaItem.MimeType == "image/svg+xml")
         {
            string result;

            using (StreamReader reader = new StreamReader(imageMediaItem.GetMediaStream(), Encoding.UTF8))
            {
               result = reader.ReadToEnd();
            }

            XDocument svg = XDocument.Parse(result);

            if (svg.Document?.Root != null)
            {
               if (width > 0)
               {
                  svg.Document.Root.SetAttributeValue("width", width);
               }

               if (height > 0)
               {
                  svg.Document.Root.SetAttributeValue("height", height);
               }

               result = svg.ToString();
            }

            return new RenderFieldResult(result);
         }
      }
   }

   var site = Context.Site;

   if ((string.IsNullOrEmpty(source) || IsBroken(imageField)) && site != null &&
site.DisplayMode == DisplayMode.Edit)
   {
      source = GetDefaultImage();
      className += " scEmptyImage";
      className = className.TrimStart(' ');
   }

   if (string.IsNullOrEmpty(source))
   {
      return RenderFieldResult.Empty;
   }

   var imageSource = GetSource();
   var stringBuilder = new StringBuilder("<img "); AddAttribute(stringBuilder, "src", imageSource); AddAttribute(stringBuilder, "border", border); AddAttribute(stringBuilder, "hspace", hspace); AddAttribute(stringBuilder, "vspace", vspace); AddAttribute(stringBuilder, "class", className); AddAttribute(stringBuilder, "alt", HttpUtility.HtmlAttributeEncode(alt), xhtml); if (width > 0)
   {
   AddAttribute(stringBuilder, "width", width.ToString());
   }

   if (height > 0)
   {
   AddAttribute(stringBuilder, "height", height.ToString());
   }

   CopyAttributes(stringBuilder, keyValuePairs);
   stringBuilder.Append(" />");
   return new RenderFieldResult(stringBuilder.ToString());
}

And we are done ! Now no matter if we are rendering the image in a MVC Rendering or WebForms Sublayout, if we are using the Sitecore Helper or the Web Control (Worth to mention that it will also work if you are using ORM as long as it is using the RenderField pipeline to fill the values – like Glass Mapper is :)) we will get a nicely formatted SVG image!

Field Rendered Image

 

Full code can be found on BitBucket.

Happy SVGing ! 🙂

8 thoughts on “Sitecore SVG Support”

  1. Thank you for the blog, it was very helpful.
    I did run into an issue when I was trying to use SVGs as image fields rather than in RTF. When I tried to change the properties through the content editor, I the image would disappear. I opened the Sitecore DLLs in IL Spy to see what was going on, and it looks like if the Image does not have dimensions that the property screen will not properly edit the image.

    I ended up overriding the ImagePropertiesPage class, and before the code:

    string text = item[“Dimensions”];
    if (string.IsNullOrEmpty(text))
    {
    return;
    }
    int num = text.IndexOf(‘x’);
    if (num < 0)
    {
    return;
    }
    this.ImageWidth = MainUtil.GetInt(StringUtil.Left(text, num).Trim(), 0);
    this.ImageHeight = MainUtil.GetInt(StringUtil.Mid(text, num + 1).Trim(), 0);

    I added some validation to see if the item[“Extension”] was an SVG file.

    I don't know if I missed a step, in the above code that would have solved my issue.

  2. The root cause, as you point out, is that the svg type isn’t created by MediaManager as ImageMedia. A little decompiling and looking at other configs, and I discovered that you can just add this prototype node to your mediatype config and no code is required at all.

    The Item will be returned from MediaManager as ImageMedia now.:

  3. Just to validate the above comments, I fixed this simply by adding the following inside the mediaType element in web.config

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s