Supporting metafile formats: WMF/EMF/EMF+

LibreOffice supports many file formats, and among them are some raster and vector image formats from Microsoft. Metafile formats WMF, EMF and EMF+ are among the vector formats usable in Microsoft products, and also in LibreOffice. Here we discuss the implementation of the  support for these file formats in LibreOffice.

We call these file formats metafiles, as they are means of storing drawing commands that are calls to the Windows API that draws shapes and text on the screen. It is possible to replay these metafiles to have a graphical output in an appropriate context.

It is possible to create complex shapes using metafiles. For example, if you take a look at the odk/examples/basic/forms_and_controls folder in the LibreOffice source code, you will see some nice examples. Here is one of them: A delicious burger created using vector primitives.

berger.wmf

berger.wmf

The oldest metafile format is WMF, which goes back to Windows 3.1 during 1990s, but EMF and EMF+ are newer formats and support many new features.

LibreOffice can open these file formats. It is also possible to use them as the images inside another formats. For example, you can have WMF inside a DOC or DOCX file, or in native ODT format of LibreOffice.

Code Structure for Metafile Support

The emfio module of LibreOffice handles reading of the metafile records. It essentially translates these records into the mataactions. Then, vcl and drawinglayer display them. You can take a look at emfio/source/reader to see the main source files, wmfreader.cxx and emfreader.cxx in which rely on mtftools.cxx.

An important class which is used here is the GDIMetaFile class, which is described in the vcl/README.GDIMetaFile.md as:

The GDIMetaFile class reads, writes, manipulates and replays metafiles via the VCL module.

A typical use case is to initialize a new GDIMetaFile, open the actual stored metafile and read it in via GDIMetaFile::Read( aIStream ). This reads in the metafile into the GDIMetafile object – it can read in an old-style VCLMTF metafile (back in the days that Microsoft didn’t document the metafile format this was used), as well as EMF+ files – and adds them to a list (vector) of MetaActions. You can also populate your own GDIMetaFile via AddAction(), RemoveAction(), ReplaceAction(), etc.

Once the GDIMetafile object is read to be used, you can “play” the metafile, “pause” it, “wind forward” or “rewind” the metafile.

Other than reading, LibreOffice can write metafile formats as the output. For the output filter, vcl/source/filter/wmf is the place to look at.

Tools for Working with Metafile Formats

As the metafile formats are binary files, you will need tools to be able to work with these formats. We discuss three useful tools among others: mso-dumper, limerest and mtf-demo.

Mso-dumper

The mso-dumper is an in-house developed tool created and used by LibreOffice developers to dump information from the Microsoft binary formats. It reads the binary files WMF, EMF, EMF+ in addition to DOC, PPT, XLS and other Microsoft formats, and dumps it as xml.

To be able to dump a metafile, you should do this:

$ git clone https://git.libreoffice.org/mso-dumper
$ cd mso-dumper
$ ./wmf-dump.py burger.wmf
$ ./emf-dump.py computer_mail.emf

Please note that the burger.wmf and computer_mail.emf files are available inside the LibreOffice source. You should replace the burger.wmf and computer_mail.emf with the path of the file you want to dump its structure.

Limereset

Re-lab provides this tool which had the former name of OLEToy. It is a graphical tool that is suitable for understanding the binary file formats including the metafiles, and investigating the contents of the sample binary files. In addition to the visual display of records and their contents, it has a nice hex viewer that is very handy when debugging binary formats.

You can download this tool from here:

https://gitlab.com/re-lab-project/limerest

Mtf-demo

The mtf-demo is also a useful tool for displaying the metafiles WMF, EMF/EMF+, and also dumping the metaactions.

A demo renders of a metafile using vcl be seen by:

./bin/run mtfdemo odk/examples/basic/forms_and_controls/burger.wmf

This opens the burger.wmf as displays it in a window. You should have built the LibreOffice from sources to be able to run the above command from the LibreOffice source folder.

For debugging purposes, it is also possible to dump metaactions created as the intermediary format before rendering the metafile using -d option:

./bin/run mtfdemo -d odk/examples/basic/forms_and_controls/burger.wmf

If the command is successful, this message will be shown, and metadump.xml will be put in the current folder. The output will be:

Dumped metaactions as metadump.xml

DrawingLayer Primitives

The actual drawing is done using vcl and drawinglayer. One can dump the drawinglayer primitives for debugging purposes. We don’t have a dedicated tool to dump the primitives, but if you look at the tests inside emfio/qa/cppunit/emf/EmfImportTest.cxx, you can add this code snippet to do this:
Primitive2DSequence aSequence = parseEmf(u"emfio/qa/cppunit/wmf/data/stockobject.emf");
drawinglayer::Primitive2dXmlDump dumper;
Primitive2DContainer aContainer(aSequence);
dumper.dump(aContainer, "/tmp/drawinglayer.xml");

Then, after invoking make CppunitTest_emfio_emf, /tmp/drawinglayer.xml will be the dump of the drawinglayer primitives to the /tmp/drawinglayer.xml.

Status of Metafile Support in LibreOffice

Support of metafile formats in LibreOffice is not complete. Some unimplemented records, and some bugs remaining. If you want to help, you can refer to the emfio documentation to see the list of unimplemented records in WMF and EMF/EMF+. Please look at the “Limitations” section.

Recently, Bartosz Kosiorek implemented SETARCDIRECTION record of the EMF. He added the support for this specific record to the LibreOffice core with this commit:

His implementation consists of several changes, including a change in emfio/source/reader/emfreader.cxx to read the record from the file and create tools::Polygon aPoly with additional parameter IsArcDirectionClockWise(). Also, change in emfio/source/reader/mtftools.cxx to set the mbClockWiseArcDirection member variable of the MtfTools class, adding setter/getter for this variable, and also changes in tools/source/generic/poly.cxx to change Polygon::Polygon() and ImplPolygon::ImplPolygon() to add support for the extra parameter, a boolean variable bool bClockWiseArcDirection.

This commit also contains a sample document and a new unit test, TestSetArcDirection() to make sure that the support for this record does not break easily in the future.

Final Notes

For a detailed tutorial on how to fix regressions, you can refer to this blog post:

Regression Fix: Missing Lines in DOCX