emf-inout.cpp revision 7221236468b0b60c925a058fb7a1919a141202d4
6408N/A#ifdef HAVE_CONFIG_H
6408N/A#define EMF_DRIVER
6408N/A#include "display/drawing-item.h"
6408N/A#include "unit-constants.h"
6408N/A#include "clear-n_.h"
6408N/A#include "document.h"
6408N/A#include "libunicode-convert/unicode-convert.h"
6408N/A#include <png.h> //This must precede text_reassemble.h or it blows up in pngconf.h when compiling
6408N/A#include "emf-print.h"
#include "emf-inout.h"
#include "uemf.h"
#include "text_reassemble.h"
#ifndef U_PS_JOIN_MASK
namespace Inkscape {
namespace Extension {
namespace Internal {
static bool clipset = false;
gcc -Wall -o testpng testpng.c -lpng
#include <png.h>
#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
} pixel_t;
} bitmap_t;
char *buffer;
if(p->buffer)
if(!p->buffer)
size_t x, y;
inverse of gethexcolor() in emf-print.cpp
return(out);
return FALSE;
return TRUE;
unsigned int ret;
if (ret) {
bool new_FixPPTPatternAsHatch = mod->get_param_bool("FixPPTPatternAsHatch"); // force all patterns as standard EMF hatch
ext->set_param_bool("FixPPTCharPos",new_FixPPTCharPos); // Remember to add any new ones to PrintEmf::init or a mysterious failure will result!
int type;
int level;
char *lpEMFR;
typedef struct emf_device_context {
char *font_name;
bool stroke_set;
bool fill_set;
typedef struct emf_callback_data {
int level;
double E2IdirY; // EMF Y direction relative to Inkscape Y direction. Will be negative for MM_LOMETRIC etc.
float MMX;
float MMY;
unsigned int id;
char *pDesc;
// both of these end up in <defs> under the names shown here. These structures allow duplicates to be avoided.
EMF_STRINGS hatches; // hold pattern names, all like EMFhatch#_$$$$$$ where # is the EMF hatch code and $$$$$$ is the color
int n_obj;
/* given the transformation matrix from worldTranform return the scale in the matrix part. Assumes that the
if(scale <= 0.0)scale=1.0; /* something is dreadfully wrong with the matrix, but do not crash over it */
return(scale);
/* given the transformation matrix from worldTranform and the current x,y position in inkscape coordinates,
generate an SVG transform that gives the same amount of rotation, no scaling, and maps x,y back onto x,y. This is used for
if(useoffset){
double newx = x * d->dc[d->level].worldTransform.eM11/scale + y * d->dc[d->level].worldTransform.eM21/scale;
double newy = x * d->dc[d->level].worldTransform.eM12/scale + y * d->dc[d->level].worldTransform.eM22/scale;
switch(hatchType){
case U_HS_SOLIDTEXTCLR:
case U_HS_DITHEREDTEXTCLR:
case U_HS_SOLIDBKCLR:
case U_HS_DITHEREDBKCLR:
// EMF can take solid colors from background or the default text color but on conversion to inkscape
// these need to go to a defined color. Consequently the hatchType also has to go to a solid color, otherwise
// on export the background/text might not match at the time this is written, and the colors will shift.
switch(hatchType){
case U_HS_HORIZONTAL:
case U_HS_VERTICAL:
case U_HS_FDIAGONAL:
*(d->defs) += " patternUnits=\"userSpaceOnUse\" width=\"6\" height=\"6\" x=\"0\" y=\"0\" viewBox=\"0 0 6 6\" preserveAspectRatio=\"none\" >\n";
case U_HS_BDIAGONAL:
*(d->defs) += " patternUnits=\"userSpaceOnUse\" width=\"6\" height=\"6\" x=\"0\" y=\"0\" viewBox=\"0 0 6 6\" preserveAspectRatio=\"none\" >\n";
case U_HS_CROSS:
case U_HS_DIAGCROSS:
*(d->defs) += " patternUnits=\"userSpaceOnUse\" width=\"6\" height=\"6\" x=\"0\" y=\"0\" viewBox=\"0 0 6 6\" preserveAspectRatio=\"none\" >\n";
sprintf(hrotname,"EMFhatch%d_%6.6X",U_HS_FDIAGONAL,sethexcolor(hatchColor)); // keep hatchname intact for later, hrotname will overwrite this
case U_HS_SOLIDCLR:
case U_HS_DITHEREDCLR:
case U_HS_SOLIDTEXTCLR:
case U_HS_DITHEREDTEXTCLR:
case U_HS_SOLIDBKCLR:
case U_HS_DITHEREDBKCLR:
if(current_rotation(d) >= 0.00001 || current_rotation(d) <= -0.00001){ /* some rotation, allow a little rounding error around 0 degrees */
if(!idx){
uint32_t add_image(PEMF_CALLBACK_DATA d, void *pEmr, uint32_t cbBits, uint32_t cbBmi, uint32_t iUsage, uint32_t offBits, uint32_t offBmi){
int dibparams;
if(!cbBits ||
!cbBmi ||
pEmr,
&px,
&ct,
&numCt,
&width,
&height,
if(!DIB_to_RGBA(
&mempng,
rgba_px);
base64String = strdup("iVBORw0KGgoAAAANSUhEUgAAAAQAAAADCAIAAAA7ljmRAAAAA3NCSVQICAjb4U/gAAAALElEQVQImQXBQQ2AMAAAsUJQMSWI2H8qME1yMshojwrvGB8XcHKvR1XtOTc/8HENumHCsOMAAAAASUVORK5CYII=");
been loaded? The image names contain no identifying information, they are just numbered sequentially.
XXXX is the rotation in radians x 1000000 and truncated. That is then stored in BASE64 as the "image".
if(current_rotation(d) >= 0.00001 || current_rotation(d) <= -0.00001){ /* some rotation, allow a little rounding error around 0 degrees */
if(!idx){
if(d->dwRop3){
switch(d->dwRop3){
case U_SRCINVERT:
case U_DSTINVERT:
case U_BLACKNESS:
case U_SRCERASE:
case U_NOTSRCCOPY:
case U_NOTSRCERASE:
case U_PATCOPY:
case U_WHITENESS:
case U_SRCAND:
case U_MERGECOPY:
case U_MERGEPAINT:
case U_PATPAINT:
switch(d->dwRop2){
case U_R2_BLACK:
case U_R2_NOTMERGEPEN:
case U_R2_MASKNOTPEN:
case U_R2_NOTCOPYPEN:
case U_R2_MASKPENNOT:
case U_R2_NOT:
case U_R2_XORPEN:
case U_R2_NOTMASKPEN:
case U_R2_NOTXORPEN:
case U_R2_NOP:
case U_R2_MERGENOTPEN:
case U_R2_COPYPEN:
case U_R2_MASKPEN:
case U_R2_MERGEPENNOT:
case U_R2_MERGEPEN:
case U_R2_WHITE:
// *(d->outsvg) += tmp_id.str().c_str();
case DRAW_PATTERN:
case DRAW_IMAGE:
case DRAW_PAINT:
if (d->dc[d->level].fill_set && d->dc[d->level].stroke_set && d->dc[d->level].style.stroke_width.value == 1 &&
case DRAW_PATTERN:
case DRAW_IMAGE:
case DRAW_PAINT:
if (clipset)
clipset = false;
double tmp;
tmp = ((((double) (px - d->dc[d->level].winorg.x))*scale) + d->dc[d->level].vieworg.x) * d->D2PscaleX;
tmp -= d->ulCornerOutX; //The EMF boundary rectangle can be anywhere, place its upper left corner in the Inkscape upper left corner
return(tmp);
double tmp;
tmp = ((((double) (py - d->dc[d->level].winorg.y))*scale) * d->E2IdirY + d->dc[d->level].vieworg.y) * d->D2PscaleY;
tmp -= d->ulCornerOutY; //The EMF boundary rectangle can be anywhere, place its upper left corner in the Inkscape upper left corner
return(tmp);
double wpx = px * d->dc[d->level].worldTransform.eM11 + py * d->dc[d->level].worldTransform.eM21 + d->dc[d->level].worldTransform.eDx;
double wpy = px * d->dc[d->level].worldTransform.eM12 + py * d->dc[d->level].worldTransform.eM22 + d->dc[d->level].worldTransform.eDy;
double ppx = fabs(px * (d->dc[d->level].ScaleInX ? d->dc[d->level].ScaleInX : 1.0) * d->D2PscaleX * current_scale(d));
return ppx;
if (!pEmr)
case U_PS_DASH:
case U_PS_DOT:
case U_PS_DASHDOT:
case U_PS_DASHDOTDOT:
if (d->dc[d->level].style.stroke_dash.dash && (d->level==0 || (d->level>0 && d->dc[d->level].style.stroke_dash.dash!=d->dc[d->level-1].style.stroke_dash.dash)))
case U_PS_SOLID:
case U_PS_ENDCAP_ROUND:
case U_PS_ENDCAP_SQUARE:
case U_PS_ENDCAP_FLAT:
case U_PS_JOIN_BEVEL:
case U_PS_JOIN_MITER:
case U_PS_JOIN_ROUND:
} else { // this stroke should always be rendered as 1 pixel wide, independent of zoom level (can that be done in SVG?)
//d->dc[d->level].style.stroke_width.value = 1.0;
if (!pEmr)
case U_PS_USERSTYLE:
if (d->dc[d->level].style.stroke_dash.dash && (d->level==0 || (d->level>0 && d->dc[d->level].style.stroke_dash.dash!=d->dc[d->level-1].style.stroke_dash.dash)))
case U_PS_DASH:
case U_PS_DOT:
case U_PS_DASHDOT:
case U_PS_DASHDOTDOT:
if (d->dc[d->level].style.stroke_dash.dash && (d->level==0 || (d->level>0 && d->dc[d->level].style.stroke_dash.dash!=d->dc[d->level-1].style.stroke_dash.dash)))
case U_PS_SOLID:
case U_PS_ENDCAP_ROUND:
case U_PS_ENDCAP_SQUARE:
case U_PS_ENDCAP_FLAT:
case U_PS_JOIN_BEVEL:
case U_PS_JOIN_MITER:
case U_PS_JOIN_ROUND:
if (pEmr->elp.elpPenStyle == U_PS_NULL) { // draw nothing, but fill out all the values with something
} else { // this stroke should always be rendered as 1 pixel wide, independent of zoom level (can that be done in SVG?)
//d->dc[d->level].style.stroke_width.value = 1.0;
else if(pEmr->elp.elpBrushStyle == U_BS_DIBPATTERN || pEmr->elp.elpBrushStyle == U_BS_DIBPATTERNPT){
d->dc[d->level].stroke_idx = add_image(d, pEmr, pEmr->cbBits, pEmr->cbBmi, *(uint32_t *) &(pEmr->elp.elpColor), pEmr->offBits, pEmr->offBmi);
tidx = add_image(d, (void *) pEmr, pEmr->cbBits, pEmr->cbBmi, pEmr->iUsage, pEmr->offBits, pEmr->offBmi);
if (!pEmr)return;
/* The logfont information always starts with a U_LOGFONT structure but the U_EMREXTCREATEFONTINDIRECTW
is defined as U_LOGFONT_PANOSE so it can handle one of those if that is actually present. Currently only logfont
d->dc[d->level].style.font_style.value = (pEmr->elfw.elfLogFont.lfItalic ? SP_CSS_FONT_STYLE_ITALIC : SP_CSS_FONT_STYLE_NORMAL);
if(ctmp){
if(*ctmp){
d->dc[d->level].font_name = strdup("Arial"); // Default font, EMF spec says device can pick whatever it wants
d->dc[d->level].style.baseline_shift.value = ((pEmr->elfw.elfLogFont.lfEscapement + 3600) % 3600) / 10; // use baseline_shift instead of text_transform to avoid overflow
// We are keeping a copy of the EMR rather than just a structure. Currently that is not necessary as the entire
// reord by record, and we might need to do that again at some point in the future if we start running into EMF
/* Identify probable Adobe Illustrator produced EMF files, which do strange things with the scaling.
int ret=0;
char *ptr;
if(pEmr->nDescription)string = U_Utf16leToUtf8((uint16_t *)((char *) pEmr + pEmr->offDescription), pEmr->nDescription, NULL);
if(string){
return(ret);
return res;
int dibparams;
if(!cbBits ||
!cbBmi ||
pEmr,
&px,
&ct,
&numCt,
&width,
&height,
if(!DIB_to_RGBA(
&mempng,
sub_px);
tmp_image << "iVBORw0KGgoAAAANSUhEUgAAAAQAAAADCAIAAAA7ljmRAAAAA3NCSVQICAjb4U/gAAAALElEQVQImQXBQQ2AMAAAsUJQMSWI2H8qME1yMshojwrvGB8XcHKvR1XtOTc/8HENumHCsOMAAAAASUVORK5CYII=";
tsp.boff = 0.0; /* offset to baseline from LL corner of bounding rectangle, changes with fs and taln*/
while(OK){
//std::cout << "record type: " << lpEMFR->iType << " length: " << lpEMFR->nSize << " offset: " << off <<std::endl;
//std::cout << "tri->dirty:"<< d->tri->dirty << " emr_mask: " << std::hex << emr_mask << std::dec << std::endl;
if ( (emr_mask != 0xFFFFFFFF) && (emr_mask & U_DRAW_TEXT) && d->tri->dirty){ // next record is valid type and forces pending text to be drawn immediately
//std::cout << "BEFORE DRAW logic d->mask: " << std::hex << d->mask << " emr_mask: " << emr_mask << std::dec << std::endl;
((d->mask & U_DRAW_ONLYTO) && !(emr_mask & U_DRAW_ONLYTO) ) // *TO records can only be followed by other *TO records
d->mask = 0;
d->drawtype = 0;
// std::cout << "AFTER DRAW logic d->mask: " << std::hex << d->mask << " emr_mask: " << emr_mask << std::dec << std::endl;
case U_EMR_HEADER:
if (d->pDesc) {
tmp_outdef << " xmlns:sodipodi=\"http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd\"\n"; // needed for sodipodi:role
if ((pEmr->szlMillimeters.cx + pEmr->szlMillimeters.cy) && ( pEmr->szlDevice.cx + pEmr->szlDevice.cy)){
tmp_outdef <<
for( int i=0; i < d->n_obj; ++i )
case U_EMR_POLYBEZIER:
uint32_t i,j;
tmp_str <<
tmp_str <<
case U_EMR_POLYGON:
uint32_t i;
tmp_str <<
tmp_str <<
case U_EMR_POLYLINE:
uint32_t i;
tmp_str <<
tmp_str <<
case U_EMR_POLYBEZIERTO:
uint32_t i,j;
tmp_path <<
case U_EMR_POLYLINETO:
uint32_t i;
tmp_path <<
case U_EMR_POLYPOLYLINE:
case U_EMR_POLYPOLYGON:
case U_EMR_SETWINDOWEXTEX:
d->dc[d->level].ScaleInX = (double) d->dc[d->level].sizeView.cx / (double) d->dc[d->level].sizeWnd.cx;
d->dc[d->level].ScaleInY = (double) d->dc[d->level].sizeView.cy / (double) d->dc[d->level].sizeWnd.cy;
case U_EMR_SETWINDOWORGEX:
case U_EMR_SETVIEWPORTEXTEX:
d->dc[d->level].ScaleInX = (double) d->dc[d->level].sizeView.cx / (double) d->dc[d->level].sizeWnd.cx;
d->dc[d->level].ScaleInY = (double) d->dc[d->level].sizeView.cy / (double) d->dc[d->level].sizeWnd.cy;
case U_EMR_SETVIEWPORTORGEX:
case U_EMR_EOF:
OK=0;
case U_EMR_SETMAPMODE:
case U_MM_TEXT:
/* For all of the following the indicated scale this will be encoded in WindowExtEx/ViewportExtex
case U_MM_ANISOTROPIC:
case U_EMR_SETPOLYFILLMODE:
case U_EMR_SETROP2:
case U_EMR_SETSTRETCHBLTMODE:
case U_EMR_SETTEXTALIGN:
case U_EMR_SETCOLORADJUSTMENT:
case U_EMR_SETTEXTCOLOR:
case U_EMR_SETBKCOLOR:
case U_EMR_MOVETOEX:
tmp_path <<
case U_EMR_INTERSECTCLIPRECT:
clipset = true;
if ((rc.left == rc_old.left) && (rc.top == rc_old.top) && (rc.right == rc_old.right) && (rc.bottom == rc_old.bottom))
case U_EMR_SAVEDC:
d->dc[d->level + 1].font_name = strdup(d->dc[d->level].font_name); // or memory access problems because font name pointer duplicated
case U_EMR_RESTOREDC:
if (d->dc[old_level].style.stroke_dash.dash && (old_level==0 || (old_level>0 && d->dc[old_level].style.stroke_dash.dash!=d->dc[old_level-1].style.stroke_dash.dash))){
old_level--;
case U_EMR_SETWORLDTRANSFORM:
case U_MWT_IDENTITY:
case U_MWT_LEFTMULTIPLY:
case U_MWT_RIGHTMULTIPLY:
case U_EMR_SELECTOBJECT:
switch (index) {
case U_NULL_BRUSH:
case U_BLACK_BRUSH:
case U_DKGRAY_BRUSH:
case U_GRAY_BRUSH:
case U_LTGRAY_BRUSH:
case U_WHITE_BRUSH:
float val = 0;
switch (index) {
case U_BLACK_BRUSH:
case U_DKGRAY_BRUSH:
case U_GRAY_BRUSH:
case U_LTGRAY_BRUSH:
case U_WHITE_BRUSH:
case U_NULL_PEN:
case U_BLACK_PEN:
case U_WHITE_PEN:
case U_EMR_CREATEPEN:
case U_EMR_CREATEMONOBRUSH:
case U_EMR_EXTCREATEPEN:
case U_EMR_CREATEPEN:
case U_EMR_DELETEOBJECT:
case U_EMR_ANGLEARC:
case U_EMR_ELLIPSE:
double cx = pix_to_x_point( d, (rclBox.left + rclBox.right)/2.0, (rclBox.bottom + rclBox.top)/2.0 );
double cy = pix_to_y_point( d, (rclBox.left + rclBox.right)/2.0, (rclBox.bottom + rclBox.top)/2.0 );
case U_EMR_RECTANGLE:
case U_EMR_ROUNDRECT:
case U_EMR_ARC:
int f1;
if(!stat){
case U_EMR_CHORD:
int f1;
case U_EMR_PIE:
int f1;
case U_EMR_LINETO:
tmp_path <<
case U_EMR_ARCTO:
int f1;
case U_EMR_SETARCDIRECTION:
case U_EMR_SETMITERLIMIT:
case U_EMR_BEGINPATH:
case U_EMR_ENDPATH:
d->mask &= (0xFFFFFFFF - U_DRAW_ONLYTO); // clear the OnlyTo bit (it might not have been set), prevents any further path extension
case U_EMR_CLOSEFIGURE:
case U_EMR_FILLPATH:
if(!(d->mask & U_DRAW_CLOSED)){ // Close a path not explicitly closed by an EMRCLOSEFIGURE, otherwise fill makes no sense
case U_EMR_STROKEANDFILLPATH:
if(!(d->mask & U_DRAW_CLOSED)){ // Close a path not explicitly closed by an EMRCLOSEFIGURE, otherwise fill makes no sense
case U_EMR_STROKEPATH:
case U_EMR_ABORTPATH:
d->drawtype = 0;
case U_EMR_COMMENT:
if ( *szTxt) {
szTxt++;
case U_EMR_EXTSELECTCLIPRGN:
clipset = false;
case U_EMR_BITBLT:
d->mask |= U_DRAW_CLOSED; // Bitblit is not really open or closed, but we need it to fill, and this is the flag for that
int sh = 0;
case U_EMR_STRETCHBLT:
case U_EMR_MASKBLT:
int sh = 0;
case U_EMR_STRETCHDIBITS:
case U_EMR_EXTTEXTOUTA:
case U_EMR_EXTTEXTOUTW:
case U_EMR_SMALLTEXTOUT:
int roff = sizeof(U_EMRSMALLTEXTOUT); //offset to the start of the variable fields, only used with U_EMR_SMALLTEXTOUT
int cChars;
cChars = 0;
if(!dup_wt)dup_wt = U_Latin1ToUtf32le((char *) pEmr + pEmr->emrtext.offString, pEmr->emrtext.nChars, NULL);
dup_wt = U_Utf16leToUtf32le((uint16_t *)((char *) pEmr + pEmr->emrtext.offString), pEmr->emrtext.nChars, NULL);
msdepua(dup_wt); //convert everything in Microsoft's private use area. For Symbol, Wingdings, Dingbats
char *ansi_text;
// Empty string or starts with an invalid escape/control sequence, which is bogus text. Throw it out before g_markup_escape_text can make things worse
if (ansi_text) {
memcpy(&tsp.color, &d->dc[d->level].textColor, sizeof(uint32_t)); //It is already an RGBA binary value, but compiler is picky about types
case SP_CSS_FONT_STYLE_ITALIC:
case SP_CSS_FONT_STYLE_NORMAL:
// EMF textalignment is a bit strange: 0x6 is center, 0x2 is right, 0x0 is left, the value 0x4 is also drawn left
ALIRIGHT);
ALITOP));
tsp.ori = d->dc[d->level].style.baseline_shift.value; // For now orientation is always the same as escapement
tsp.string = (uint8_t *) U_strdup(escaped_text); // this will be free'd much later at a trinfo_clear().
// when font name includes narrow it may not be set to "condensed". Narrow fonts do not work well anyway though
case U_EMR_POLYBEZIER16:
uint32_t i,j;
case U_EMR_POLYGON16:
unsigned int first = 0;
case U_EMR_POLYLINE16:
uint32_t i;
case U_EMR_POLYBEZIERTO16:
uint32_t i,j;
case U_EMR_POLYLINETO16:
uint32_t i;
case U_EMR_POLYPOLYLINE16:
case U_EMR_POLYPOLYGON16:
case U_EMR_CREATEMONOBRUSH:
case U_EMR_EXTCREATEPEN:
case U_EMR_SETICMMODE:
// When testing, uncomment the following to place a comment for each processed EMR record in the SVG
// *(d->outsvg) += dbg_str.str().c_str();
(void) emr_properties(U_EMR_INVALID); // force the release of the lookup table memory, returned value is irrelevant
typedef struct _SMALL_RECT {
memset(&d, 0, sizeof(d));
d.dc[0].font_name = strdup("Arial"); // Default font, EMF spec says device can pick whatever it wants
return NULL;
d.mask = 0;
d.drawtype = 0;
d.dwRop3 = 0;
char *contents;
if (d.pDesc)
SPDocument *doc = SPDocument::createNewDocFromMem(d.outsvg->c_str(), strlen(d.outsvg->c_str()), TRUE);
delete d.outsvg;
delete d.path;
delete d.outdef;
delete d.defs;
if (d.emf_obj) {
for (i=0; i<d.n_obj; i++)
delete_object(&d, i);
delete[] d.emf_obj;
for(int i=0; i<=d.level;i++){
return doc;
"<param name=\"textToPath\" gui-text=\"" N_("Convert texts to paths") "\" type=\"boolean\">true</param>\n"
"<param name=\"TnrToSymbol\" gui-text=\"" N_("Map Unicode to Symbol font") "\" type=\"boolean\">true</param>\n"
"<param name=\"TnrToWingdings\" gui-text=\"" N_("Map Unicode to Wingdings") "\" type=\"boolean\">true</param>\n"
"<param name=\"TnrToZapfDingbats\" gui-text=\"" N_("Map Unicode to Zapf Dingbats") "\" type=\"boolean\">true</param>\n"
"<param name=\"UsePUA\" gui-text=\"" N_("Use MS Unicode PUA (0xF020-0xF0FF) for converted characters") "\" type=\"boolean\">false</param>\n"
"<param name=\"FixPPTCharPos\" gui-text=\"" N_("Compensate for PPT font bug") "\" type=\"boolean\">false</param>\n"
"<param name=\"FixPPTDashLine\" gui-text=\"" N_("Convert dashed/dotted lines to single lines") "\" type=\"boolean\">false</param>\n"
"<param name=\"FixPPTGrad2Polys\" gui-text=\"" N_("Convert gradients to colored polygon series") "\" type=\"boolean\">false</param>\n"
"<param name=\"FixPPTPatternAsHatch\" gui-text=\"" N_("Map all fill patterns to standard EMF hatches") "\" type=\"boolean\">false</param>\n"
"<param name=\"FixImageRot\" gui-text=\"" N_("Ignore image rotations") "\" type=\"boolean\">false</param>\n"