(It Just) Has To Be .Net
Two long-term IT techies, with a penchant for Microsoft's .NET Framework, air views on whatever topics take their fancy.

HowTo: Insert an image into a Word document and display it using OpenXML

One of the tasks I've had over the last few weeks has been to automatically generate documentation from an architectural model. It's been (and continues to be) a relatively painful, sluggish process, as I'm generating Microsoft Word 2007 documents using the Open XML SDK. Never more so than when I came to add support for including images.

I'm going to do a 10,000' pass through the way that I approached it, so it's not a beginners guide to OpenXML. You're going to need to be able to find your way around an OpenXML document using the Open XML SDK first.

The basic process I'm using is to generate from my architectural model an XML document that will ultimately become a Microsoft Word document, like this:

  1. <?xml version="1.0" encoding="utf-8" ?>
  2. <content xmlns:w="http://schemas.openxmlformats.org/wordprocessingml/2006/main">
  3.   <bookmark id="ContentToReplace">
  4.     <para style="Heading1">Major Heading</para>
  5.     <para style="Heading2">Secondary Heading</para>
  6.     <table>
  7.       <tr>
  8.         <td>
  9.           <para>Name</para>
  10.         </td>
  11.         <td>
  12.           <para>Steve Morgan</para>
  13.         </td>
  14.       </tr>
  15.       <tr>
  16.         <td>
  17.           <para>Address</para>
  18.         </td>
  19.         <td>
  20.           <para>www.hastobe.net</para>
  21.           <para>Somewhere in Hyperspace</para>
  22.         </td>
  23.       </tr>
  24.     </table>
  25.     <para style="Heading2">Another Secondary Heading</para>
  26.     <image src="..\..\testfiles\mypic.png"/>
  27.     <para>Good now, sit down, and tell me, he that knows,</para>
  28.     <para>Why this same strict and most observant watch</para>
  29.     <para>So nightly toils the subject of the land,</para>
  30.     <para>And why such daily cast of brazen cannon,</para>
  31.     <para>And foreign mart for implements of war;</para>
  32.     <para>Why such impress of shipwrights, whose sore task</para>
  33.     <para>Does not divide the Sunday from the week;</para>
  34.     <para>What might be toward, that this sweaty haste</para>
  35.     <para>Doth make the night joint-labourer with the day:</para>
  36.     <para>Who is't that can inform me?</para>
  37.   </bookmark>
  38. </content>

I have some code that rips through a Word template document, looking for bookmarks and replacing the content of those bookmarks with Open XML fragments. The fragments themselves are derived from the above XML structure using nothing more than good old-fashioned XSLT.

Generating paragraphs of text and tables is pretty simple, as other than transforming the XML and inserting the resulting Open XML into the document, there's not much to it.

Images, on the other hand, are a little more complicated. There are two stages to adding an image to a Word document (or any Open XML document, for that matter). The first is that you must add an "Image Part" that holds the binary representation of the image, itself. Finding out how to do that was pretty easy:

  1. WordProcessingDocument m_Document = ...the document you're building...
  2.  
  3. public void InsertImage(string imageFile, string contentType)
  4. {
  5.     ImagePart part = m_Document.MainDocumentPart.AddImagePart(contentType);
  6.  
  7.     using (FileStream stream = new FileStream(imageFile, FileMode.Open))
  8.     {
  9.         part.FeedData(stream);
  10.     }
  11. }

This function adds a new image part to the document and copies the content of the image file. However, getting the image displayed on the page is another matter entirely. It should be no surprise that we must add some markup to the main document to render the image. I created what I consider to be a minimal Open XML fragment that creates a single paragraph containing nothing but the requested image:

  1. <w:p xmlns:w="http://schemas.openxmlformats.org/wordprocessingml/2006/main"
  2.     xmlns:wp="http://schemas.openxmlformats.org/drawingml/2006/
                   wordprocessingDrawing"
  3.     xmlns:a="http://schemas.openxmlformats.org/drawingml/2006/main"
  4.     xmlns:pic="http://schemas.openxmlformats.org/drawingml/2006/picture"
  5.     xmlns:r="http://schemas.openxmlformats.org/officeDocument/2006/relationships">
  6.   <w:r>
  7.     <w:drawing>
  8.       <wp:inline>
  9.         <wp:extent cx="{WIDTH-IN-EMUS}" cy="{HEIGHT-IN-EMUS}" />
  10.         <wp:docPr id="{DRAWING-OBJECT-ID}" name="{FILENAME-OF-IMAGE}"
                      descr="An automatically inserted image" />
  11.         <wp:cNvGraphicFramePr>
  12.           <a:graphicFrameLocks noChangeAspect="1" />
  13.         </wp:cNvGraphicFramePr>
  14.         <a:graphic>
  15.           <a:graphicData uri="http://schemas.openxmlformats.org/drawingml/
                                    2006/picture"
    >
  16.             <pic:pic>
  17.               <pic:nvPicPr>
  18.                 <pic:cNvPr id="{PICTURE-ID}" name="{FILENAME-OF-IMAGE}" />
  19.                 <pic:cNvPicPr />
  20.               </pic:nvPicPr>
  21.               <pic:blipFill>
  22.                 <a:blip r:embed="{RELATIONSHIP-ID-OF-IMAGE-PART}" cstate="print"/>
  23.                 <a:stretch>
  24.                   <a:fillRect />
  25.                 </a:stretch>
  26.               </pic:blipFill>
  27.               <pic:spPr>
  28.                 <a:xfrm>
  29.                   <a:off x="0" y="0" />
  30.                   <a:ext cx="{WIDTH-IN-EMUS}" cy="{HEIGHT-IN-EMUS}" />
  31.                 </a:xfrm>
  32.                 <a:prstGeom prst="rect"/>
  33.               </pic:spPr>
  34.             </pic:pic>
  35.           </a:graphicData>
  36.         </a:graphic>
  37.       </wp:inline>
  38.     </w:drawing>
  39.   </w:r>
  40. </w:p>

Now, do you see those upper-case names in parenthesis (such as {WIDTH-IN-EMUS})? They're placeholders for values that must be dynamically inserted and are derived as follows:

{WIDTH-IN-EMUS}

That's the width of the image. Not measured in terms of flightless birds, but English Metric Units. There are 914400 EMUs to the inch, so it's calculated from:

widthInEmus = widthInPixels / HorizontalResolutionInDPI * 914400;
{HEIGHT-IN-EMUS}

Similarly, this is the height of the image in English Metric Units. Calculated from:

heightInEmus = heightInPixels / VerticalResolutionInDPI * 914400;
{DRAWING-OBJECT-ID}

This is a unique identifier for the Drawing Object. I calculate the initial value by looping through all of the existing <wp:docPr> elements in the document to find the largest Id, then increment it for each new image.

{FILENAME-OF-IMAGE}

Just for the sake of convention, I put the terminal name of the image file in here.

{PICTURE-ID}

This is a unique identifier for the Picture (as opposed to the Drawing Object - hmm?) and is derived in the same way as {DRAWING-OBJECT-ID} but from <pic:cNvPr> elements, not <wp:docPr>.

{RELATIONSHIP-ID-OF-IMAGE-PART}

This is what binds the image placeholder in the document to the image resource that we've embedded in the new Image Part. To get it, we need to change our InsertImage function slightly:

  1. public string InsertImage(string imageFile, string contentType)
  2. {
  3.     ImagePart part = m_Document.MainDocumentPart.AddImagePart(contentType);
  4.  
  5.     using (FileStream stream = new FileStream(imageFile, FileMode.Open))
  6.     {
  7.         part.FeedData(stream);
  8.     }
  9.    
  10.     return m_Document.MainDocumentPart.GetIdOfPart(part);
  11. }

The value returned by InsertImage is the Relationship Id that we insert into this attribute in the XML.

So there you have it (for now). Equipped with your new Image Part and appropriate Open XML fragment, you should now to able to insert images, ad-nauseum, into your Open XML documents. I apologise for having skimmed over the subject at such a high level. Hopefully, I'll get round to posting some more articles on Open XML that will fill in a few of the blanks. In the meantime, if you need some specific help, let me know.


Posted Sep 15 2008, 12:01 PM by Steve Morgan

Comments

Baffled wrote re: HowTo: Insert an image into a Word document and display it using OpenXML
on Mon, Jan 18 2010 10:05

Hi

I'm needing to do something similar, and so I'm wondering why you are using a Drawing (w:drawing) for your images rather than the Picture ( w:pict ) tag? Would it not be simpler to use the w:pict -> v:imagedata approach?

Steve Morgan wrote re: HowTo: Insert an image into a Word document and display it using OpenXML
on Mon, Jan 18 2010 11:21

That's a very good question and one that I don't have a decent answer for at the moment!

All of the examples that I found while researching this topic used Drawing, but using Picture certainly looks far more straightforward.

I'll have to do some more investigation, now.

Thanks for contributing!

egs wrote re: HowTo: Insert an image into a Word document and display it using OpenXML
on Thu, Nov 18 2010 0:14

is there an example of {FILENAME-OF-IMAGE} because i have problems with the image

Buck wrote re: HowTo: Insert an image into a Word document and display it using OpenXML
on Fri, Jun 24 2011 21:11

Great article.  

Is there any example of making this working using a MemoryStream instead of a FileStream?  Every time I use a MemoryStreamm, I get a Red X on my document.

how to insert bookmark in word using c#/VB.NET - Programmers Goodies wrote how to insert bookmark in word using c#/VB.NET - Programmers Goodies
on Sun, Jul 24 2011 16:55

Pingback from  how to insert bookmark in word using c#/VB.NET - Programmers Goodies

Steve Morgan 2008. All rights reserved.
Powered by Community Server (Non-Commercial Edition), by Telligent Systems