Jumping through hoops to save a Metafile

3/29/2011 By Frank 0 comments

GDI+ includes a built-in decoder for EMF but it does not include a built-in encoder (see https://msdn.microsoft.com/en-us/library/bb882579(v=vs.110).aspx). Consequently, you can read an EMF, but not write it. Atleast, not through GDI+. If you use the Metafile.Save method (which is the natutal thing to do) then you will actually save a PNG and loose the vector graphics.

Save a Metafile

To save a Metafile object as an EMF file and preserve the vector graphics then you will need to jump through some hoops. Here is the boilerplate code:

1 [System.Runtime.InteropServices.DllImport("gdi32")] 2 public static extern int GetEnhMetaFileBits( 3 int hemf, int cbBuffer, byte[] lpbBuffer); 4 5 // caller remains owner of stream and metafile 6 static void saveMetafile(Stream stream, Metafile metafile) 7 { 8 // get the GDI handle 9 int enhMetafileHandle = metafile.GetHenhmetafile().ToInt32(); 10 11 // get required buffer size 12 int bufferSize = GetEnhMetaFileBits(enhMetafileHandle, 0, null); 13 14 // create buffer 15 byte[] buffer = new byte[bufferSize]; 16 17 // get metafile data 18 if (GetEnhMetaFileBits(enhMetafileHandle, bufferSize, buffer) <= 0) 19 { 20 throw new System.ApplicationException("GetEnhMetaFileBits failed"); 21 } 22 23 // write metafile data to stream 24 stream.Write(buffer, 0, bufferSize); 25 }

Render a PDF page to an EMF file

Our PDF rasterizer component lets you render to an EMF file. Here is the code that renders a PDF page to an EMF:

1 using (FileStream pdfFile = new FileStream( 2 "in.pdf", FileMode.Open, FileAccess.Read)) 3 { 4 // get the first PDF page 5 Document document = new Document(pdfFile); 6 Page page = document.Pages[0]; 7 8 Metafile metafile = createMetafile(page); 9 10 ... 11 }

This code uses the following helper function:

1 // caller becomes owner of returned Metafile 2 static Metafile createMetafile(Page page) 3 { 4 Metafile metafile = null; 5 6 using (Graphics graphics = Graphics.FromHwndInternal(IntPtr.Zero)) 7 { 8 System.IntPtr hdc = graphics.GetHdc(); 9 metafile = new Metafile( 10 hdc, 11 new System.Drawing.RectangleF( 12 0, 0, 13 (float)page.Width, (float)page.Height), 14 MetafileFrameUnit.Point); 15 graphics.ReleaseHdc(hdc); 16 } 17 18 using (Graphics metafileGraphics = Graphics.FromImage(metafile)) 19 { 20 // for some reason the metafile header and metafile graphics 21 // may have different resolutions 22 // adjust for this using a scale transform 23 MetafileHeader metafileHeader = metafile.GetMetafileHeader(); 24 metafileGraphics.ScaleTransform( 25 metafileHeader.DpiX / metafileGraphics.DpiX, 26 metafileHeader.DpiY / metafileGraphics.DpiY); 27 28 // PDF graphics are in points; this corresponds to 72 DPI 29 // adjust for the difference with the metaGraphics resolution 30 metafileGraphics.ScaleTransform( 31 metafileGraphics.DpiX / 72f, 32 metafileGraphics.DpiY / 72f); 33 34 page.Draw(metafileGraphics); 35 } 36 return metafile; 37 }

I borrowed the piece that adjust for the difference in resolution between the metafileGraphics and metafileHeader from Nicholas Piasecki's blog (unfortunately it isn't available online anymore).

Using the saveMetafile method above, you can now save this to an EMF file as follows:

1 using (FileStream emfFile = new FileStream( 2 "out.emf", FileMode.Create, FileAccess.Write)) 3 { 4 saveMetafile(emfFile, metafile); 5 } 6 7 if (null != metafile) metafile.Dispose();

If you open the EMF file in the Windows viewer, you can zoom in to the maximum level and see that the vector graphics are preserved:

PDF-to-EMF.png