OpenXML insert SVG image into Word document

By Mirek on (tags: OpenXML, SVG, Word, categories: tools, code)

Today I’ll show you a quick solution on how to insert an SVG vector graphic into Microsoft Office Word document programatically. Keep reading.

We are going to use the OpenXML SDK from Microsoft which is available as DocumentFormat.OpenXml nuget package. Assuming we have an empty Word document, we want to programatically insert an SVG image into that document using OpenXML SDK. Full, working, demonstration solution is attached to this post and you can download it using the link at the end of the post.

Before we start, a quick intro on how to work with OpenXML Word documents and a general structure of the document itself.

As name suggests, the OpenXML format is based on XML content. Every Word document (and all other OpenXML file formats) is basically a zipped folder that consists of many folders and files. Mostly xml files. An example of such unzipped folder is shown below

OPenXML_01

As you can see, for instance all images that are embedded into the Word document are being kept as separate files under media folder. The most important part for us is the document.xml file, which defines the structure of the body of document. Here we define elements like paragraphs, tables, lists and reference objects like embedded images or predefined formatting styles.

Working with a pure XML content would be tedious though. Moreover the structure of open xml format is very complex, as complex and feature rich are the Microsoft Office tools itself. The OpenXML SDK is helpfulf here in a way, that you don’t need to manipulate raw xml content files, but instead, you work on pure .NET objects. It also gives you a strongly typed document schema validation, so using SDK ensures there is a little chance to break the document structure. Hovewer, the complexity of the structure stays and, as long as the Open SDK is still “low level” way of manipulating the format, we need some guidance.
The very first resource I suggest to check out, is the Microsoft documentation about inserting an jpeg image to the word document. Its available here.
Then the Open XML SDK 2.5 Productivity Tool is a super helpful also. It’s capable to nicely visualize the document structure as XML. It has direct documentation integration, but what’s most valuable, this tool is capable of generating C# code based off of the parsed Word documemnt element! Yes, you can select a portion of document body and the tool shows you the code of the SDK that is required to recreate the selected element!

OPenXML_02

Wihout any further ado, let’s dive in to the code.

Assume we have an empty.docx word document file and the kiwi.svg image. We want to embed the image into the file and append it to the document content.
We need to do the following:

  1. Open the document for editing
  2. Create new ImagePart for our SVG image and upload the image content into that part
  3. Modify the body of document by adding proper structure and reference the image part
using (var wpd = WordprocessingDocument.Open(documentPath, true))
{
     var svgImgPartId = AddNewSVGImagePart(wpd, imagePath);
     AddSvgImageToBody(wpd, svgImgPartId, Path.GetFileName(imagePath));
     wpd.Save(); }

Then we have AddNewSVGImagePart method

static string AddNewSVGImagePart(WordprocessingDocument wpd, string filePath)
{
     var imgPart = wpd!.MainDocumentPart!.AddImagePart(ImagePartType.Svg);
     using FileStream stream = new FileStream(filePath, FileMode.Open);
     imgPart.FeedData(stream);
     return wpd!.MainDocumentPart!.GetIdOfPart(imgPart); }

Everythink is self explanatory so far I hope. Now the most tricky part.

To have the SVG support we need to add extra SVGBlip extension to main Picture Blip.

static void AddSvgImageToBody(WordprocessingDocument wordDoc, string svgImagePartId, string imageName)
{
     /* Define the reference of the image. */
     var element =
          new Drawing(
              new DW.Inline(
                  /* Define image display size (in English Metric Units) */
                  new DW.Extent() { Cx = 3971925, Cy = 3576728 },
                  new DW.EffectExtent()
                  {
                      LeftEdge = 0L,
                      TopEdge = 0L,
                      RightEdge = 0L,
                      BottomEdge = 0L
                  },
                  new DW.DocProperties()
                  {
                      Id = (UInt32Value)1U,
                      Name = Path.GetFileNameWithoutExtension(imageName),
                  },
                  new DW.NonVisualGraphicFrameDrawingProperties(
                      new A.GraphicFrameLocks() { NoChangeAspect = true }),
                  new A.Graphic(
                      new A.GraphicData(
                          new PIC.Picture(
                              new PIC.NonVisualPictureProperties(
                                  new PIC.NonVisualDrawingProperties()
                                  {
                                      Id = (UInt32Value)0U,
                                      Name = imageName
                                  },
                                  new PIC.NonVisualPictureDrawingProperties()),
                              new PIC.BlipFill(
                                  new A.Blip(
                                      new A.BlipExtensionList(
                                          new A.BlipExtension()
                                          {
                                              Uri = "{28A0092B-C50C-407E-A947-70E740481C1C}"
                                          },
                                          /* Extra Blip extension for SVG support */
                                          new A.BlipExtension(new SVGBlip{ Embed = svgImagePartId })
                                          {
                                              Uri = "{96DAC541-7B7A-43D3-8B79-37D633B846F1}"
                                          })
                                  )
                                  {
                                      /* This is ID of fallback ImagePart */
                                      Embed = svgImagePartId,
                                      CompressionState =
                                      A.BlipCompressionValues.Print
                                  },
                                  new A.Stretch(
                                      new A.FillRectangle())),
                              new PIC.ShapeProperties(
                                  new A.Transform2D(
                                      new A.Offset() { X = 0L, Y = 0L },
                                      new A.Extents() { Cx = 990000L, Cy = 792000L }),
                                  new A.PresetGeometry(
                                      new A.AdjustValueList()
                                  )
                                  { Preset = A.ShapeTypeValues.Rectangle }))
                      )
                      { Uri = "http://schemas.openxmlformats.org/drawingml/2006/picture" })
              )
              {
                  DistanceFromTop = (UInt32Value)0U,
                  DistanceFromBottom = (UInt32Value)0U,
                  DistanceFromLeft = (UInt32Value)0U,
                  DistanceFromRight = (UInt32Value)0U,
                  EditId = "50D07946"
              });
     /* Append the reference to body, the element should be in a Run. */
     wordDoc!.MainDocumentPart!.Document!.Body!.AppendChild(new Paragraph(new Run(element))); }

As you can see the structure is quite complex. The most important is the Blip (Blob Large Image or Picture) element though.

new A.Blip(
     new A.BlipExtensionList(
         new A.BlipExtension()
         {
             Uri = "{28A0092B-C50C-407E-A947-70E740481C1C}"
         },
         /* Extra SVG Blip extension for SVG support */
         new A.BlipExtension(new SVGBlip{ Embed = svgImagePartId })
         {
             Uri = "{96DAC541-7B7A-43D3-8B79-37D633B846F1}"
         }) ) {
     /* This is ID of fallback ImagePart */
     Embed = svgImagePartId,
     CompressionState = A.BlipCompressionValues.Print }

Normally, when you insert an jpg or png image, the Blip element’s Embed property is populated with image part reference id and all is fine. For the SVG support hovewer, the logic is different. The normal flow of Blip is used as a backward compatibility in case the document is opened in older version of MS Word. Then, there is an extra extension attached to the Blip (with important Uri = "{96DAC541-7B7A-43D3-8B79-37D633B846F1}" ) which holds the SVGBlip element. SVGBlip element then references the SVG image part in its Embed property.

So, as you can see in above example, we are missing the fallback image (left for brevity), as the svg image part is referenced in main Blip element and SVGBlip element. The proper way would be to extract the jpg image out from the SVG in some standarized resolution. Add that image as JPG image part to the document, then reference it in main Blip element as a fallback scenario. Then import SVG file as separate image part in the document and reference it in SVGBlip element.

Thanks.

Download attachement - 13 KB