The following is a discussion of the many ways applications read and write metafiles and how your application can handle them correctly. Metafile Overview Metafile Headers Placeable Metafiles Metafile Applications Creating Metafiles Displaying Metafiles Reading Metafiles Passing Metafiles via DDE Passing Metafiles to the Clipboard Metafile Overview A metafile is a way to store a series of GDI records, or objects, used for drawing so that they can be played back at a later time, or when the application gets a WM_PAINT message. Metafiles are used to store images on disk or to be transferred between applications via the clipboard as a "Picture" or via DDE with a CF_METAFILEPICT format. One of the many advantages of metafiles is that they can be resized inside your application or other applications without loosing a lot of the picture quality. Also, since the metafile consists of a bunch of records, the metafile can be considerably smaller than a bitmap, especially a large bitmap. A metafile consists of a collection of graphics device interface (GDI) functions that describe an image. Because metafiles take up less space and are more device independent than bitmaps, they provide convenient storage for images that appear repeatedly in an application or need to be moved from one application to another. To generate a metafile, a Windows application creates a special device context that sends GDI commands to a file or memory for storage. The application can later play back the metafile and display the image. A series of records follows the metafile header. GDI stores most of the GDI functions that an application can use to create metafiles in typical records. The remainder of the functions are stored in function-specific records. In processing Windows Metafiles, it is important to know that there are three kinds of headers that exist and two file formats - both of which use the same .WMF file extension. The following information will discuss to use the different headers for a memory and disk metafile, what they are for and when to use them. Metafile Headers In order to understand how to display a metafile, you need to know certain attributes that effect the displaying of them. METAFILEPICT The two attributes that effect the displaying of the metafile are the "mapping mode", and how big to display/scale the metafile in the X and Y "extents" or directions the way the author intended it. The structure that contains these two attributes is known as the METAFILEPICT structure and is used when passing information about the memory metafile to the clipboard or to another application via DDE. However, we will see in the next structure that this information is not preserved when storing a metafile to disk. typedef struct tagMETAFILEPICT { int mm; int xExt, yExt; HANDLE hMF; } METAFILEPICT; How you use each field's value is discussed in the Displaying Metafiles section. Normal disk metafile header The metafile that is stored in memory is the same as the "normal" metafile file stored on disk. This metafile consists of two parts: a header followed by a list of GDI records. This header contains a description of the size, in words, of the metafile and the number of drawing objects it uses. This information is useful in allocating memory for the size of the disk metafile. The metafile header has the following format: struct { WORD mtType; WORD mtHeaderSize; WORD mtVersion; DWORD mtSize; WORD mtNoObjects; DWORD mtMaxRecord; WORD mtNoParameters; } The value of each field and the specific information on how the GDI records are stored on disk can be found in the Microsoft Windows Programmer's Reference, Volume 2, File Formats. Placeable disk metafile header The other metafile file format is known as a "Placeable" metafile. The Placeable metafile has the same information as normal metafiles except that there is an additional 22 byte header at the beginning of the file and is defined as follows: struct { DWORD key; HANDLE hmf; RECT bbox; WORD inch; DWORD reserved; WORD checksum; } PLACEABLEHEADER; The purpose of this extra 22 byte header is to preserve the original aspect ratio and size of the metafile the author intended the metafile to be when first "placed" on the screen - since the user may have the picture they are viewing in different ZOOMED modes. However, this extra header makes it so the standard GetMetaFile function will not work correctly. The format for these "placeable metafiles" has been standardized for a long time and is maintained by Aldus Corporation. See the Metafile Applications for a list of the applications that support this format. The value of each field is discussed in the Placeable Metafiles section. Placeable Metafiles The Placeable header is 22 bytes in length and is defined as follows: typedef struct { DWORD key; HANDLE hmf; RECT bbox; WORD inch; DWORD reserved; WORD checksum; } METAFILEHEADER; These fields have the following meanings: Field Definition key Binary key that uniquely identifies this file type. This must be 0x9AC6CDD7L. hmf Unused; must be zero. bbox* The coordinates of a rectangle that tightly bounds the picture. These coordinates are in metafile units as defined below. inch The number of metafile units to the inch. To avoid numeric overflow, this value should be less than 1440. Most applications use 576 or 1000. reserved A reserved double word; must be zero. checksum** A checksum of the 10 words that precede it, calculated by XORing zero with these 10 words and putting the result in the checksum field (see example below). metafileData The actual content of the Windows metafile retrieved by copying the data returned by GetMetafileBits to the file. The number of bytes should be equal to the MS-DOS file length minus 22. * To determine this value, you need to provide the lowest point (leftmost and topmost) an object is displayed, and the largest point (rightmost and bottommost) an object is displayed. **Example of checksum calculation code: void ComputeMetafileHeaderChecksum(pMFHead) METAFILEHEADER *pMFHead; { WORD *p; for (p =(WORD *)pMFHead,pMFHead -> checksum = 0; p < (WORD *)&pMFHead ->checksum; ++p) pMFHead ->checksum ^= *p; } Metafile applications The following Microsoft applications REQUIRE "placeable" metafiles when reading from disk: Power Point* WinWord 2.0 MS Publisher MS Draw MS Graph MS WordArt Non- MS applications that support "placeable" metafiles: Aldus PageMaker Micrografx Designer Coral Draw There is a sample application in the Win 3.1 SDK called WMFDCODE that demonstrates how to read and display "placeable" metafiles. * PowerPoint will only read in metafiles that are in MM_ANISOTROPIC mode. Creating Metafiles There are certain rules that every metafile should follow so that they can be accepted and easily manipulated in other applications. Here are the rules. 1. Set a Mapping Mode as one of the first records. Use MM_ANISOTROPIC whenever possible. Be aware that some applications will only read in metafiles that are in MM_ANISOTROPIC mode. Object Linking and Embedding (OLE) requires that the presentation format is in MM_ANISOTROPIC mode. 2. Set the SetWindowOrg and SetWindowExt functions next. 3. Use the Escape Comment function for internal use. 4. Do not use SetViewportExt or SetViewportOrg IF you want the user to have the ability to resize or change the aspect ratio the object. 5. Do not use any of the functions that start with Get or functions that returns data. 6. Do not use any of the Region functions since they are not device independent. 7. Use only the functions listed in Windows Reference Manual in the File formats section. Microsoft Windows 3.0 Programmer's Reference, File Formats section. 8. Use StretchBlt or StretchDIB instead of BitBlt. Following these rules will allow you to make metafiles that all other applications can use easily. Displaying Metafiles To display a metafile in your application will vary depending on the source and kind of metafile. Here are the different kinds, and the order in which we will discuss them. 1] Internal - to be used only within the application that created it 2] Clipboard & DDE - from another application 3] Normal disk based metafile 4] Placeable disk based metafile Internal metafiles For internal based metafiles, you use the mapping mode you want and the size in the x and y direction you want. To create and playback your own internal metafile could be as simple as the following code sample. HDC hDC, hMetaDC; hMetaDC = CreateMetaFile(NULL); // NULL is for a memory metafile Rectangle(hMetaDC, 10, 10, 20, 20); CloseMetaFile(hMetaDC); hDC = GetDC(hWnd); PlayMetafile(hDC, hMetaDC); ReleaseDC(hWnd, hDC); DeleteMetaFile(hMetaDC); If you wanted to get fancy and have the metafile displayed only within a rectangle you set by click and dragging the mouse, you would use the SetViewPortOrg and SetViewPortExt functions. Here is what the code would look like. case WM_LBUTTONDOWN: xStart=LOWORD(lParam); yStart=HIWORD(lParam); break; case WM_LBUTTONUP: xEnd=LOWORD(lParam); yEnd=HIWORD(lParam); SetViewPortOrg (hDC, xStart, yStart); SetViewPortExt (hDC, xEnd - xStart, yEnd - yStart); PlayMetaFile(hDC, hMetaDC); break; If you wanted the metafile to always be displayed so that the x and y size was always equal, you would set the mapping mode to MM_ISOTROPIC and then play the metafile. Clipboard Metafiles When displaying a metafile obtained from the clipboard, you have to set the display for the metafile based on the values in the METAFILEPICT structure. typedef struct tagMETAFILEPICT { int mm; int xExt, yExt; HANDLE hMF; } METAFILEPICT; The METAFILEPICT structure has the following fields: mm Specifies the mapping mode in which the picture is drawn. xExt Specifies the size of the metafile picture for all modes except the MM_ISOTROPIC and MM_ANISOTROPIC modes. The x-extent specifies the width of the rectangle within which the picture is drawn. The coordinates are in units that correspond to the mapping mode. yExt Specifies the size of the metafile picture for all modes except the MM_ISOTROPIC and MM_ANISOTROPIC modes. The y-extent specifies the height of the rectangle within which the picture is drawn. The coordinates are in units that correspond to the mapping mode. hMF Identifies a memory metafile. For MM_ISOTROPIC and MM_ANISOTROPIC modes, which can be scaled, the xExt and yExt fields contain an optional suggested size in MM_HIMETRIC units. For MM_ANISOTROPIC pictures, xExt and yExt can be zero when no suggested size is supplied. For MM_ISOTROPIC pictures, an aspect ratio must be supplied even when no suggested size is given. (If a suggested size is given, the aspect ratio is implied by the size.) To give an aspect ratio without implying a suggested size, set xExt and yExt to negative values whose ratio is the appropriate aspect ratio. The magnitude of the negative xExt and yExt values will be ignored; only the ratio will be used. Here is a sample function that you would want to use to prepare the display area. void PrepareDisplay (hDC, lpmfp, xClient, yClient) HDC hDC ; LPMETAFILEPICT lpmfp ; short xClient, yClient ; { long xlScale, ylScale, lScale ; SetMapMode (hDC, lpmfp->mm) ; // The ViewPortOrg is defaulted to 0,0 if (lpmfp->mm == MM_ISOTROPIC || lpmfp->mm == MM_ANISOTROPIC) { if (lpmfp->xExt == 0) SetViewportExt (hDC, xClient, yClient) ; else if (lpmfp->xExt > 0) // use MM_HIMETRIC so divide by 100 SetViewportExt (hDC, (short) ((long) lpmfp->xExt * GetDeviceCaps (hDC, HORZRES) / GetDeviceCaps (hDC, HORZSIZE) / 100), (short) ((long) lpmfp->yExt * GetDeviceCaps (hDC, VERTRES) / GetDeviceCaps (hDC, VERTSIZE) / 100)) ; else if (lpmfp->xExt < 0) // preserve the ratio { xlScale = 100L * (long) xClient * GetDeviceCaps (hDC, HORZSIZE) / GetDeviceCaps (hDC, HORZRES) / -lpmfp->xExt ; ylScale = 100L * (long) yClient * GetDeviceCaps (hDC, VERTSIZE) / GetDeviceCaps (hDC, VERTRES) / -lpmfp->yExt ; lScale = min (xlScale, ylScale) ; SetViewportExt (hDC, (short) ((long) -lpmfp->xExt * lScale * GetDeviceCaps (hDC, HORZRES) / GetDeviceCaps (hDC, HORZSIZE) / 100), (short) ((long) -lpmfp->yExt * lScale * GetDeviceCaps (hDC, VERTRES) / GetDeviceCaps (hDC, VERTSIZE) / 100)) ; } // end if < 0 } // end if aniso or iso } Normal disk based metafiles When displaying a Normal metafile obtained with the GetMetaFile function, you have no idea what mapping mode to use, so you can use a default one, or MM_ANISOTROPIC, or provide a dialog box asking the person what mapping mode they want. After that, all you do is call the PlayMetaFile function. The least you should do is use the SetMapMode and SetViewportExt functions so in case the metafile doesn't have this information you will still see something. Placeable disk based metafiles After determining that the disk metafile is Placeable, you can set the display based on the values in the PLACEABLEMETAHEADER structure. Here is a sample function that you would use to prepare the display area. RECT bbox; WORD inch; Now the inch value will tell us how many units per inch there are, and the bbox value will tell us how big we can expect the tightest bounding rectangle will be. To get an idea of this, if the inch value is 500 and the value in bbox.right is 1500 and bbox.bottom is 1000, then we have an object that was designed to be 3 inches wide by 2 inches tall. This way, you can use the following table to determine how large you should make the object if you are currently in a different mapping mode. mapping mode divisor multiplier MM_LOMETRIC 10 MM_HIMETRIC 100 MM_TWIPS 14400 254 MM_LOENGLISH 10000 2540 MM_HIENGLISH 10000 254 MM_TEXT same value passed in If the value in hDC = GetDC (hWnd) ; GetClientRect (hWnd, &rect) ; SetMapMode (hDC, mfp.mm) ; SetViewportExt (hDC, rect.right, rect.bottom ) ; if (hMF) // make sure we have a hMF PlayMetaFile (hDC, hMF) ; ReleaseDC (hWnd, hDC) ; Reading Metafiles Since there are two different kinds of disk metafiles, both with the *.WMF file extension, you have to read the first few bytes and check the header to see if is a Placeable metafile or not. If is a Placeable metafile, below is a description and sample code on reading the file into memory. For Normal metafiles, you can use the GetMetaFile function. Using Windows Metafile Functions and Aldus Placeable Metafiles MS Knowledge Base ID: Q66949 Many Windows applications import or export Windows metafiles in a format known as the Aldus Placeable Metafile (APM) format. In this format, these metafiles cannot be used with the Windows metafile functions such as GetMetaFile(), CopyMetaFile(), PlayMetaFile(), etc. To use these metafiles, the APM header must be removed from the metafile and the remaining metafile bits must be written to a newly created metafile. More Information: The Placeable header is 22 bytes in length and is defined as follows: struct { DWORD key; HANDLE hmf; RECT bbox; WORD inch; DWORD reserved; WORD checksum; } APMFILEHEADER; The following code fragment demonstrates how to create a memory-based Windows metafile from an Aldus Placeable Metafile (APM) that will work with the metafile functions provided by Windows. For more information regarding the APM format, please contact the Aldus Developer's Desk at (206)622-5500 (ask for the Third-party Developer's Desk). BOOL RenderPlaceableMetafile (fh) int fh; // a file handle to the APM metafile is passed in { HANDLE hData; LPSTR lpData; DWORD OffsetToMeta; METAHEADER mfHeader; APMFILEHEADER APMHeader; OffsetToMeta = sizeof(APMHeader); // Seek to beginning of file and read APM header _llseek(fh, 0, 0); if (!_lread(fh, (LPSTR)&APMHeader, sizeof(APMFILEHEADER))) return(FALSE); // Error in reading the file // Return to read metafile header _llseek(fh, OffsetToMeta, 0); if (!_lread(fh, (LPSTR)&mfHeader, sizeof(METAHEADER))) return(FALSE); // Error in reading the file // Allocate memory for memory based metafile if (!(hData = GlobalAlloc(GHND, (mfHeader.mtSize * 2L)))) return(FALSE); // GlobalAlloc failed // Were we successful if (!(lpData = GlobalLock(hData))) { // Error in locking memory GlobalFree(hData); return(FALSE); } _llseek(fh, OffsetToMeta, 0); // Read metafile bits if (!_lread(fh, lpData, (mfHeader.mtSize * 2L))) { // Error in reading GlobalUnlock(hData); GlobalFree(hData); return(FALSE); } // Create the METAFILE with the bits we read in. if (!(hMF = SetMetaFileBits(hData))) return(FALSE); GlobalUnlock(hData); _lclose(fh); // Close the APM file return(TRUE); // Return success } Passing Metafiles via DDE In order to send a metafile via DDE messages, what you have to do is pass, at the end of the DDEDATA structure, a handle to a global shared data structure of type METAFILEPICT. The handle to the actual memory metafile is then placed into the hMF field of the METAFILEPICT structure. Passing METAFILEPICT Structures Through DDE Knowledge Base ID: Q69883 When an application sends a WM_DDE_REQUEST message and the server application replies with a WM_DDE_DATA message, the format of the data returned in the Value[] member of the DDEDATA structure is not always the same, but depends on the value of the cfFormat member of the structure. More Information: The server application defines the data returned in the Value[] member of the DDEDATA structure. This data must be in one of the formats used to pass data to the Clipboard. The standard clipboard data formats and a description of their data are defined in the documentation for the SetClipboardData() function on pages 4-370 and 4-371 in the "Microsoft Windows Software Development Kit Reference Volume 1". For example, for the CF_TEXT format, the actual data is returned from the DDE server, and for the CF_BITMAP format, a handle to the bitmap is sent. The table is not explicit as to what data is returned for the CF_METAFILEPICT data format. The Value[] parameter contains a handle to a global memory block containing a METAFILEPICT structure. To access this structure, call GlobalLock() to get a pointer to the memory and cast the pointer to LPMETAFILEPICT. For example: LPMETAFILEPICT lpMFP; lpMFP = (LPMETAFILEPICT)GlobalLock(lpDDEData->Value); At this stage, the metafile bits may be copied with the following statement: hMFBits = CopyMetaFile(lpMFP->hMF, NULL); You can then use PlayMetaFile function to display the metafile.