Git Product home page Git Product logo

libemf2svg's Introduction

libemf2svg

Ubuntu-x86_64 Ubuntu-aarch64 Alpine-x86_64

MacOS-x86_64 MacOS-arm64 Windows-x86_64 MSys-x86_64

Coverage Status

MS EMF (Enhanced Metafile) to SVG conversion library.

Motivation

By themselves, EMF/EMF+ files are rare in the wild. However, they are frequently embedded inside other MS file formats.

This project was started to properly convert Visio stencils (.VSS) to svg and be able to reuse public stencils in other environments than MS Visio (see libvisio2svg).

However this project could be use beyond its original motivations to handle emf blobs in any MS formats.

Output example

Example

Dependencies

fmem and argp-standalone libraries are integrated as CMake external projects. No additional installation or handling is required.

Installing the dependencies on Debian:

# compiler
apt-get install gcc g++
# or
apt-get install clang

# build deps
apt-get install cmake pkg-config

# library deps with their headers
apt-get install libpng-dev libc6-dev libfontconfig1-dev libfreetype6-dev zlib1g-dev

Installing the dependencies on macOS:

$ brew install argp-standalone cmake libpng freetype fontconfig gcc

Installing the dependencies on RHEL/CentOS/Fedora:

yum install cmake libpng-devel freetype-devel fontconfig-devel gcc-c++ gcc

Installing the dependencies on Windows for MSVC native builds Dependencies are installed by vcpkg package manager. Installation is implemented as a step of CMake configuration procedure.

Also note that in some rare cases, to properly handle text fields (ETO_GLYPH_INDEX flag), the ttf font used by the documents must be present and indexed (fontconfig) on your system.

Building

Commands to build this project:

# options:
# * [-DUSE_CLANG=on]: use clang instead of gcc
# * [-DSTATIC=on]: build static library
# * [-DDEBUG=on]: compile with debugging symbols
# * [-DLONLY=on]: build the library only, no demo/test apps
#
# CMAKE_INSTALL_PREFIX is optional, default is /usr/local/

# Linux, MacOS
$ cmake . -DCMAKE_INSTALL_PREFIX=/usr/

# Windows native (MSVC) build
$ cmake . -DCMAKE_TOOLCHAIN_FILE=$(pwd)/vcpkg/scripts/buildsystems/vcpkg.cmake

# Cross-compilation
# This project employs vcpkg (https://github.com/microsoft/vcpkg) to setup cross-compilation environment
$ cmake . -DCMAKE_TOOLCHAIN_FILE=$(pwd)/vcpkg/scripts/buildsystems/vcpkg.cmake -DVCPKG_TARGET_TRIPLET=<triplet>
# The following triplets are tested in CI:
# * x64-linux   (both for Ubuntu and ALpine Linux)
# * arm64-linux (Ubuntu)
# * x64-osx
# * arm64-osx
# * x64-mingw-static

# compilation
$ make

# installation
$ make install

Please note that you cannot use relative pathes when CMAKE_TOOLCHAIN_FILE is specified at cmake command line. You may need to replace $(pwd) with a reference that is appropriate for your environment.

Command line tool

$ ./emf2svg-conv --help
Usage: emf2svg-conv [OPTION...] -i FILE -o FILE
emf2svg -- Enhanced Metafile to SVG converter

  -h, --height=HEIGHT        Max height in px
  -i, --input=FILE           Input EMF file
  -o, --output=FILE          Output SVG file
  -p, --emfplus              Handle EMF+ records
  -v, --verbose              Produce verbose output
  -w, --width=WIDTH          Max width in px
  -?, --help                 Give this help list
      --usage                Give a short usage message
      --version              Print program version
  -V, --version              Print emf2svg version

Mandatory or optional arguments to long options are also mandatory or optional
for any corresponding short options.

Report bugs to https://github.com/kakwa/libemf2svg/issues.

# usage example:
$ ./emf2svg-conv -i ./tests/resources/emf/test-037.emf -o example.svg -v

Library

Shorten examples:

Conversion from EMF to SVG (complete example here):

#include <emf2svg.h>
//[...]
int main(int argc, char *argv[]){

    /* emf content size */
    size_t emf_size;
    /* emf content */
    char * emf_content;
    /* svg output string */
    char *svg_out = NULL;
    /* svg output length */
    size_t svg_out_len = 0;

    //[...]

    /*************************** options settings **************************/

    /* allocate the options structure) */
    generatorOptions *options = (generatorOptions *)calloc(1, \
            sizeof(generatorOptions));
    /* debugging flag (prints the emf record in stdout if true) */
    options->verbose = true;
    /* emf+ flag (handles emf+ records if true) */
    options->emfplus = true;
    /* if a custom xml/svg namespace is needed (keep empty in doubt) */
    options->nameSpace = (char *)"svg";
    /* includes the svg start and stop tags (set to false if the result
     * of this call is meant to be used inside another svg) */
    options->svgDelimiter = true;
    /* image width in px (set to 0 to use the original emf device width) */
    options->imgWidth = 0;
    /* image height in px (set to 0 to use the original emf device height) */
    options->imgHeight = 0;

    /***************************** conversion ******************************/

    int ret = emf2svg(emf_content, emf_size, &svg_out, &svg_out_len, options);

    /***********************************************************************/

    //[...]
}

Check document for EMF+ record presence (complete example here):

int main(int argc, char *argv[]){

    /* emf content size */
    size_t emf_size;
    /* emf content */
    char * emf_content;
    /* svg output string */
    char *svg_out = NULL;
    /* svg output length */
    size_t svg_out_len = 0;

    bool emfplus;
    int ret = emf2svg_is_emfplus(emf_content, emf_size, &emfplus);
    if(emfplus)
        fprintf(stdout,"%s contains EMF+ records\n", file_name);
}

See ./src/conv/emf2svg.cpp for a real life example.

EMF/EMF+ record type coverage

EMF RECORDS:

Status Count Percent
Supported 37 [ 35%]
Partial 33 [ 31%]
Unused 2 [ 1%]
Ignored 33 [ 31%]
Total 105

EMF+ RECORDS:

Status Count Percent
Supported 0 [ 0%]
Partial 0 [ 0%]
Unused 0 [ 0%]
Ignored 85 [ 100%]
Total 85

ChangeLogs

1.7.3:

  • Fixed incorrect handling of polygon fill modes

1.7.2:

  • vcpkg and GHA scripts update

1.7.1:

  • added width and heigt attributes for svg even when Y-coordinates are repaired

1.7.0:

  • refactor build scripts to facilitate better portability and ruby integration

1.6.0:

  • add arm64 MacOS support (cross-compilation only, no tests)

1.5.0:

  • add Alpine Linux support

1.4.0:

  • add arm64 Debian Linux support (cross-compilation only, no tests)

1.3.1:

  • add MSVC 17 (2022) support

1.3.0:

  • add MSVC Windows native build

1.X.X: (forked to metanorma)

  • add support for EMF images without an initial viewport setup
  • add handling of EMF images with wrong transformation applied (Wine-generated)

1.1.0:

  • add handling of font index encoding
  • add fontconfig dependency
  • add freetype dependency
  • add common variables LIB_INSTALL_DIR, BIN_INSTALL_DIR, INCLUDE_INSTALL_DIR to set install directories

1.0.3:

  • Fixing compilation on CentOS 7 (work around argp bug)

1.0.2:

  • broken release, please don't use

1.0.1:

  • cleaner handling of memstream on OSX (don't install libmemstream, just embed it)

1.0.0:

  • better cmake regarding finding dependency libraries (libpng)
  • /!\ API break, must pass an additionnal argument to emf2svg function:
--- a/goodies/old.c
+++ b/goodies/new.c
@@ -22,6 +22,8 @@ int main(int argc, char *argv[]){
     char * emf_content = mmap(0, emf_size, PROT_READ, MAP_PRIVATE, fd, 0);
     /* svg output string */
     char *svg_out = NULL;
+    /* svg output length */
+    size_t svg_out_len;

     /*************************** options settings **************************/

@@ -44,7 +46,7 @@ int main(int argc, char *argv[]){

     /***************************** conversion ******************************/

-    int ret = emf2svg(emf_content, emf_size, &svg_out, options);
+    int ret = emf2svg(emf_content, emf_size, &svg_out, &svg_out_len, options);

     /***********************************************************************/
  • general cleanup of the project (remove external files not needed)

0.5.1:

  • fix build on OS X

0.5.0:

  • add alpha layer handling in bitmap blobs conversion
  • add brush patterns

0.4.0:

  • fix text orientation
  • fix origin handling in special case

0.3.0:

  • completly rework how the origin is calculated, it now takes correctly into account both viewport and window orgs

0.2.0:

  • code reorganization
  • add support for ANGLEARC, EMRSTRETCHBLT, EMRBITBLT and more
  • add handling of bitmap, RLE4 and RLE8 image blobs
  • add some rough handling of clipping forms
  • fix text rendering to not collapse spaces

0.1.0:

  • first version

Development

General source code organisation:

Useful links:

  • MS-EMF: EMF specifications.
  • MS-EMF+: EMF+ specifications.
  • MS-WMF: WMF specifications.
  • GDI: GDI specification (clearer than EMF in explaining how it works).
  • SVG: SVG specifications.

Testing

  • Stats on the number of emf records covered:
$ ./tests/resources/coverage.sh
  • Fuzzing on the library:

Using American Fuzzy Lop:

# remove big files from test pool
$ mkdir ./tmp
$ find tests/resources/emf -size +1M -name "*.emf" -exec mv {} ./tmp \;

# compile with afl compiler
$ cmake -DCMAKE_CXX_COMPILER=afl-clang++ -DCMAKE_C_COMPILER=afl-clang .
$ make

# run afl (see man for more advanced usage)
$ afl-fuzz -i tests/resources/emf -o out/ -t 10000 -- ./emf2svg-conv -i '@@' -o out/

# restore the files
mv ./tmp/* tests/resources/emf
  • Check correctness and memory leaks (xmllint and valgrind needed):
# options: -n to disable valgrind tests, -v for verbose output
# see -h for complete list of options
$ ./tests/resources/check_correctness.sh #[-n] [-v]

# generated svg:
$ ls tests/out/test-*
tests/out/test-000.emf.svg  tests/out/test-051.emf.svg
[...]

The emf files used for these checks are located in ./tests/resources/emf/.

Useful Commands

To build, run on emf test files and visualize (with geeqie):

$ cmake .&& \
    make &&\
    "./tests/resources/check_correctness.sh" -n &&\
    geeqie "tests/out"

To check against corrupted emf:

$ cmake -DDEBUG=ON . &&\
    make &&\
    "./tests/resources/check_correctness.sh" -sxN \
    -e "./tests/resources/emf-corrupted/"

To print records index in svg as comments:

$ cmake -DINDEX=ON . && make

To reformat/reindent the code (clang-format):

$ ./goodies/format

Y-coordinates repair in EMF files

In EMF coordinates are specified using an origin ([0,0] point) located at the upper-left corner: x-coordinates increase to the right; y-coordinates increase from top to bottom.

The SVG coordinate system, on the other hand, uses the same origin ([0,0] point) at the bottom-left corner: x-coordinates increase to the right; but y-coordinates increase from top to bottom.

Typically, a simple shift of the y-axis through a single SVG/CSS transformation is used to transform from EMF coordinates to SVG coordinates.

However, under certain circumstances some tools (for instance, SparxSystem Enterprise Architect in Wine) will generate EMF files with malformed coordinates. These images have an origin at the top-left corner with y-coordinates increasing from top to bottom, yet these y-coordinates are inverted (multiplied by -1) to simulate a normal EMF look.

Furthermore, this inversion phenomenon cannot be solved with plain mirroring as it occurs to all (complex) objects of the hierarchy. For example, text boxes have only their y-coordinate anchor point mirrored, but the text direction is set properly.

This specific layout issue cannot be fixed by a single SVG/CSS transformation, and therefore the processing code is required to detect and invert only the affected y-coordinates, while keeping other attributes intact.

Contributing

Contribution are welcomed. Nothing special here, it's the usual "fork; commit(s); pull request". Only one thing however, run ./goodies/format (clang-format) before the pull request.

libemf2svg's People

Contributors

camobap avatar cian-chambliss avatar gitter-badger avatar kakwa avatar maxirmx avatar ronaldtse avatar spike77453 avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar

libemf2svg's Issues

Rename the "EA compatibility mode" into generic "compatible/repair" mode

So far the fixes have been in two categories:

  1. libemv2svg bug for an Enterprise Architect image from Windows (#6)

These are valid EMF files but could not load properly without the bug fix.

  1. compatibility/repair with EMF images generated on the Wine platform (the broken files now parsable when using the EA compatibility mode)

These are "invalid" EMF files generated by Enterprise Architect on Wine (works on Windows, but broken via Wine). These files technically needed "repair" to be loadable, so this is a "compatibility mode for Wine generated EMF files".

Unable to convert Enterprise Architect generated EMF files

SparxSystem's Enterprise Architect software generates EMF files that can be read on Windows (Microsoft Word), but not by libemf2svg. Somehow, libemf2svg crashes/exits on the input of these files.

We have to fix libemf2svg so that it reads these files and successfully converts these EMF files into SVG.

valgrind errors /MacOs

==17830== Warning: set address range perms: large range [0x7fff20066000, 0x80001fe66000) (defined)
==17830== Warning: set address range perms: large range [0x7fff20066000, 0x7fff7fcea000) (defined)
==17830== Warning: set address range perms: large range [0x7fff8e2ca000, 0x7fffc0066000) (noaccess)
==17830== Warning: set address range perms: large range [0x7fffc0066000, 0x7fffe2f3a000) (defined)
==17830== Warning: set address range perms: large range [0x7fffe2f3a000, 0x7fffffe00000) (noaccess)
libpng warning: Application built with libpng-1.4.12 but running with 1.6.37

Declare libxml2 dependency

First of all, thanks for taking over the maintenance of this package, this is very useful.

My build failed due to missing libXml2. This isn't listed as a dependency in the README so maybe it should be added. The package containing this library on Ubuntu is libxml2-dev.

Macintosh arm64 architecture not recognised

When I try to install this gem under arm64, via gem install emf2svg, I get:

ld: warning: ignoring file /private/var/folders/st/02jjnpwd1bz15gp_7m7fm1lr0000gs/T/d20211205-18736-2tn5lo/libemf2svg/deps/lib/libfmem.a, building for macOS-arm64 but attempting to link with file built for macOS-x86_64

And indeed, libemf2svg/deps/lib/libfmem.a is compiled as x86_64, not the desired xarm64 architecture

This occurs because deps/src/fm-build/CMakeFiles/fmem.dir/flags.make ends up containing -arch x86_64

And that occurs, because deps/src/fm-build/CMakeFiles/3.22.0/CMakeSystem.cmake contains

set(CMAKE_HOST_SYSTEM_PROCESSOR "x86_64")
set(CMAKE_HOST_SYSTEM_PROCESSOR "x86_64")

instead of the correct xarm64

I can't work out what is going on when I try to build it; I just know that this gem is doing a lot of patching of vcpkg, and vcpkg seems to be the only place where the cmake architecture is manipulated.

Another EMF that goes crazy

This EMF works perfectly in Word:

Screenshot 2021-06-28 at 7 13 50 PM

But with boxes flying out in InkScape (flipped Y coordinates for the boxes?):
Screenshot 2021-06-28 at 7 15 17 PM

And it goes crazy in EmfViewer (completely flipped around):
Screenshot 2021-06-28 at 7 14 19 PM

Here's the file:
image1.emf.zip

Ensure libemf2svg works on Windows

In the Metanorma project we need to ensure libemf2svg also compiles on Windows.

  • Add Windows GHA build
  • Update CMake and code if necessary

Missing gradients in EMF files

In the generated SVGs we don't see any gradients.

Screenshot 2021-07-12 at 7 43 02 PM

However, in the original Enterprise Architect PNGs, the gradients exist.

Screenshot 2021-07-12 at 7 43 11 PM

We need to find out whether these gradients are actually missing from the EMFs or they got lost in the EMF=>SVG process.

Images:
Primitives.zip

Add release automation workflow

libemf2svg depends on vcpkg submodule so it is necesary to create and upload release archive that includes vcpkg manually
We can automate this step in a separate release workflow

Switch on wrong fill_mode

I've noticed at least 2 places where switch over states->currentDeviceContext.fill_mode is compared to wrong enum.

First:

switch (states->currentDeviceContext.fill_mode) {
case (U_ALTERNATE):
verbose_printf(" Fill Rule: U_ALTERNATE\n");
break;
case (U_WINDING):
verbose_printf(" Fill Rule: U_WINDING\n");
break;
case (0):
verbose_printf(" Fill Rule: UNSET\n");
break;
default:
verbose_printf(" Fill Rule: UNKNOWN\n");
break;
}

Second:

switch (states->currentDeviceContext.fill_mode) {
case (U_ALTERNATE):
sprintf(fill_rule, "fill-rule:\"evenodd\" ");
break;
case (U_WINDING):
sprintf(fill_rule, "fill-rule:\"nonzero\" ");
break;
default:
sprintf(fill_rule, " ");
break;
}

Here values are compared to PolygonFillMode, but states->currentDeviceContext.fill_mode is a brush style. Probably those switches should be done over states->currentDeviceContext.fill_polymode

valgrind warning (memory leak) / Alpine Linux

==1101== 72,704 bytes in 1 blocks are still reachable in loss record 1 of 1
==1101==    at 0x48A26D9: malloc (vg_replace_malloc.c:380)
==1101==    by 0x4BBA086: ??? (in /usr/lib/libstdc++.so.6.0.28)
==1101==    by 0x4059D1B: ??? (in /lib/ld-musl-x86_64.so.1)
==1101==    by 0xB907FED: ???
==1101==    by 0x59D92: ???
==1101==    by 0xCA07: ???
==1101==    by 0x197CB7: ???

Wrong command line example in the README

Same situation when done in the command line like mentioned in the README (cmake . -DCMAKE_TOOLCHAIN_FILE=vcpkg/scripts/buildsystems/vcpkg.cmake), although to make it work I have to prepend the path with ./ to become:
cmake . -DCMAKE_TOOLCHAIN_FILE=./vcpkg/scripts/buildsystems/vcpkg.cmake

Originally posted by @ant1j in #39 (comment)

Windows build failure vagrant/CMake-3.21

vagrant@WIN-UUF9OP3870I C:\ribose\libemf2svg>cmake -B C:\ribose\libemf2svg -DCMAKE_BUILD_TYPE=Release -DCMAKE_TOOLCHAIN_FILE=C:\ribose\libemf2svg\vcpkg\scri
pts\buildsystems\vcpkg.cmake
-- Building for: Visual Studio 16 2019
-- Bootstrapping vcpkg before install
-- Bootstrapping vcpkg before install - done
-- Running vcpkg install
-- Running vcpkg install - failed
CMake Error at vcpkg/scripts/buildsystems/vcpkg.cmake:835 (message):
vcpkg install failed. See logs for more information:
C:\ribose\libemf2svg\vcpkg-manifest-install.log
Call Stack (most recent call first):
C:/Program Files/CMake/share/cmake-3.21/Modules/CMakeDetermineSystem.cmake:124 (include)
CMakeLists.txt:17 (project)

-- Configuring incomplete, errors occurred!

cc @alexeymorozov

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.