emf-print.cpp revision 27acb80f0e43fa9691dc5826e77c9c8eff8b7d47
0ec5755428223b8105e488e8fddc19de4db26521raf * @brief Enhanced Metafile printing
0ec5755428223b8105e488e8fddc19de4db26521raf * Authors:
0ec5755428223b8105e488e8fddc19de4db26521raf * Ulf Erikson <ulferikson@users.sf.net>
0ec5755428223b8105e488e8fddc19de4db26521raf * Jon A. Cruz <jon@joncruz.org>
0ec5755428223b8105e488e8fddc19de4db26521raf * Abhishek Sharma
0ec5755428223b8105e488e8fddc19de4db26521raf * David Mathog
0ec5755428223b8105e488e8fddc19de4db26521raf * Copyright (C) 2006-2009 Authors
0ec5755428223b8105e488e8fddc19de4db26521raf * Released under GNU GPL, read the file 'COPYING' for more information
0ec5755428223b8105e488e8fddc19de4db26521raf * References:
0ec5755428223b8105e488e8fddc19de4db26521raf * - How to Create & Play Enhanced Metafiles in Win32
0ec5755428223b8105e488e8fddc19de4db26521raf * - INFO: Windows Metafile Functions & Aldus Placeable Metafiles
0ec5755428223b8105e488e8fddc19de4db26521raf * - Metafile Functions
0ec5755428223b8105e488e8fddc19de4db26521raf * http://msdn.microsoft.com/library/en-us/gdi/metafile_0whf.asp
0ec5755428223b8105e488e8fddc19de4db26521raf * - Metafile Structures
0ec5755428223b8105e488e8fddc19de4db26521raf * http://msdn.microsoft.com/library/en-us/gdi/metafile_5hkj.asp
0ec5755428223b8105e488e8fddc19de4db26521raf#include "2geom/svg-path-parser.h" // to get from SVG text to Geom::Path
0ec5755428223b8105e488e8fddc19de4db26521raf/* globals */
0ec5755428223b8105e488e8fddc19de4db26521rafstatic double PX2WORLD;
0ec5755428223b8105e488e8fddc19de4db26521rafstatic bool FixPPTCharPos, FixPPTDashLine, FixPPTGrad2Polys, FixPPTLinGrad, FixPPTPatternAsHatch, FixImageRot;
0ec5755428223b8105e488e8fddc19de4db26521rafvoid PrintEmf::smuggle_adxkyrtl_out(const char *string, uint32_t **adx, double *ky, int *rtl, int *ndx, float scale)
0ec5755428223b8105e488e8fddc19de4db26521raf const char *cptr = &string[strlen(string) + 1]; // this works because of the first fake terminator
0ec5755428223b8105e488e8fddc19de4db26521raf return; // this could happen with an empty string
0ec5755428223b8105e488e8fddc19de4db26521raf // all of the class variables are initialized elsewhere, many in PrintEmf::Begin,
0ec5755428223b8105e488e8fddc19de4db26521rafunsigned int PrintEmf::setup(Inkscape::Extension::Print * /*mod*/)
0ec5755428223b8105e488e8fddc19de4db26521rafunsigned int PrintEmf::begin(Inkscape::Extension::Print *mod, SPDocument *doc)
0ec5755428223b8105e488e8fddc19de4db26521raf gchar const *utf8_fn = mod->get_param_string("destination");
0ec5755428223b8105e488e8fddc19de4db26521raf // Typically PX2WORLD is 1200/90, using inkscape's default dpi
0ec5755428223b8105e488e8fddc19de4db26521raf PX2WORLD = 1200.0 / Inkscape::Util::Quantity::convert(1.0, "in", "px");
0ec5755428223b8105e488e8fddc19de4db26521raf FixPPTGrad2Polys = mod->get_param_bool("FixPPTGrad2Polys");
0ec5755428223b8105e488e8fddc19de4db26521raf FixPPTPatternAsHatch = mod->get_param_bool("FixPPTPatternAsHatch");
0ec5755428223b8105e488e8fddc19de4db26521raf (void) emf_start(utf8_fn, 1000000, 250000, &et); // Initialize the et structure
0ec5755428223b8105e488e8fddc19de4db26521raf (void) htable_create(128, 128, &eht); // Initialize the eht structure
0ec5755428223b8105e488e8fddc19de4db26521raf // width and height in px
0ec5755428223b8105e488e8fddc19de4db26521raf // initialize a few global variables
0ec5755428223b8105e488e8fddc19de4db26521raf Inkscape::XML::Node *nv = sp_repr_lookup_name(doc->rroot, "sodipodi:namedview");
0ec5755428223b8105e488e8fddc19de4db26521raf uint32_t lc = strtoul(&p1[1], &p2, 16); // it looks like "#ABC123"
0ec5755428223b8105e488e8fddc19de4db26521raf pageBoundingBox = mod->get_param_bool("pageBoundingBox");
0ec5755428223b8105e488e8fddc19de4db26521raf d *= Geom::Scale(Inkscape::Util::Quantity::convert(1, "px", "in"));
0ec5755428223b8105e488e8fddc19de4db26521raf // dwInchesX x dwInchesY in micrometer units, 1200 dpi/25.4 -> dpmm
0ec5755428223b8105e488e8fddc19de4db26521raf (void) drawing_size((int) ceil(dwInchesX * 25.4), (int) ceil(dwInchesY * 25.4),1200.0/25.4, &rclBounds, &rclFrame);
0ec5755428223b8105e488e8fddc19de4db26521raf // set up the reference device as 100 X A4 horizontal, (1200 dpi/25.4 -> dpmm). Extra digits maintain dpi better in EMF
0ec5755428223b8105e488e8fddc19de4db26521raf (void) device_size(MMX, MMY, 1200.0 / 25.4, &szlDev, &szlMm);
0ec5755428223b8105e488e8fddc19de4db26521raf // set up the description: (version string)0(file)00
0ec5755428223b8105e488e8fddc19de4db26521raf snprintf(buff, sizeof(buff) - 1, "Inkscape %s (%s)\1%s\1", Inkscape::version_string, __DATE__, p);
0ec5755428223b8105e488e8fddc19de4db26521raf int cbDesc = 2 + wchar16len(Description); // also count the final terminator
0ec5755428223b8105e488e8fddc19de4db26521raf (void) U_Utf16leEdit(Description, '\1', '\0'); // swap the temporary \1 characters for nulls
0ec5755428223b8105e488e8fddc19de4db26521raf // construct the EMRHEADER record and append it to the EMF in memory
0ec5755428223b8105e488e8fddc19de4db26521raf rec = U_EMRHEADER_set(rclBounds, rclFrame, NULL, cbDesc, Description, szlDev, szlMm, 0);
0ec5755428223b8105e488e8fddc19de4db26521raf if (!rec || emf_append((PU_ENHMETARECORD)rec, et, U_REC_FREE)) {
0ec5755428223b8105e488e8fddc19de4db26521raf g_error("Fatal programming error in PrintEmf::begin at EMRHEADER");
0ec5755428223b8105e488e8fddc19de4db26521raf // Simplest mapping mode, supply all coordinates in pixels
0ec5755428223b8105e488e8fddc19de4db26521raf if (!rec || emf_append((PU_ENHMETARECORD)rec, et, U_REC_FREE)) {
0ec5755428223b8105e488e8fddc19de4db26521raf g_error("Fatal programming error in PrintEmf::begin at EMRSETMAPMODE");
0ec5755428223b8105e488e8fddc19de4db26521raf // In earlier versions this was used to scale from inkscape's dpi of 90 to
0ec5755428223b8105e488e8fddc19de4db26521raf // the files 1200 dpi, taking into account PX2WORLD which was 20. Now PX2WORLD
0ec5755428223b8105e488e8fddc19de4db26521raf // is set so that this matrix is unitary. The usual value of PX2WORLD is 1200/90,
0ec5755428223b8105e488e8fddc19de4db26521raf // but might be different if the internal dpi is changed.
0ec5755428223b8105e488e8fddc19de4db26521raf rec = U_EMRMODIFYWORLDTRANSFORM_set(worldTransform, U_MWT_LEFTMULTIPLY);
0ec5755428223b8105e488e8fddc19de4db26521raf if (!rec || emf_append((PU_ENHMETARECORD)rec, et, U_REC_FREE)) {
0ec5755428223b8105e488e8fddc19de4db26521raf g_error("Fatal programming error in PrintEmf::begin at EMRMODIFYWORLDTRANSFORM");
0ec5755428223b8105e488e8fddc19de4db26521raf snprintf(buff, sizeof(buff) - 1, "Screen=%dx%dpx, %dx%dmm", PixelsX, PixelsY, MMX, MMY);
0ec5755428223b8105e488e8fddc19de4db26521raf if (!rec || emf_append((PU_ENHMETARECORD)rec, et, U_REC_FREE)) {
0ec5755428223b8105e488e8fddc19de4db26521raf g_error("Fatal programming error in PrintEmf::begin at textcomment_set 1");
0ec5755428223b8105e488e8fddc19de4db26521raf snprintf(buff, sizeof(buff) - 1, "Drawing=%.1lfx%.1lfpx, %.1lfx%.1lfmm", _width, _height, Inkscape::Util::Quantity::convert(dwInchesX, "in", "mm"), Inkscape::Util::Quantity::convert(dwInchesY, "in", "mm"));
0ec5755428223b8105e488e8fddc19de4db26521raf if (!rec || emf_append((PU_ENHMETARECORD)rec, et, U_REC_FREE)) {
0ec5755428223b8105e488e8fddc19de4db26521raf g_error("Fatal programming error in PrintEmf::begin at textcomment_set 1");
0ec5755428223b8105e488e8fddc19de4db26521raf /* set some parameters, else the program that reads the EMF may default to other values */
0ec5755428223b8105e488e8fddc19de4db26521raf if (!rec || emf_append((PU_ENHMETARECORD)rec, et, U_REC_FREE)) {
0ec5755428223b8105e488e8fddc19de4db26521raf g_error("Fatal programming error in PrintEmf::begin at U_EMRSETBKMODE_set");
0ec5755428223b8105e488e8fddc19de4db26521raf if (!rec || emf_append((PU_ENHMETARECORD)rec, et, U_REC_FREE)) {
0ec5755428223b8105e488e8fddc19de4db26521raf g_error("Fatal programming error in PrintEmf::begin at U_EMRSETPOLYFILLMODE_set");
0ec5755428223b8105e488e8fddc19de4db26521raf // Text alignment: (only changed if RTL text is encountered )
0ec5755428223b8105e488e8fddc19de4db26521raf // - (x,y) coordinates received by this filter are those of the point where the text
0ec5755428223b8105e488e8fddc19de4db26521raf // actually starts, and already takes into account the text object's alignment;
0ec5755428223b8105e488e8fddc19de4db26521raf // - for this reason, the EMF text alignment must always be TA_BASELINE|TA_LEFT.
0ec5755428223b8105e488e8fddc19de4db26521raf if (!rec || emf_append((PU_ENHMETARECORD)rec, et, U_REC_FREE)) {
0ec5755428223b8105e488e8fddc19de4db26521raf g_error("Fatal programming error in PrintEmf::begin at U_EMRSETTEXTALIGN_set");
0ec5755428223b8105e488e8fddc19de4db26521raf htextcolor_rgb[0] = htextcolor_rgb[1] = htextcolor_rgb[2] = 0.0;
0ec5755428223b8105e488e8fddc19de4db26521raf if (!rec || emf_append((PU_ENHMETARECORD)rec, et, U_REC_FREE)) {
0ec5755428223b8105e488e8fddc19de4db26521raf g_error("Fatal programming error in PrintEmf::begin at U_EMRSETTEXTCOLOR_set");
0ec5755428223b8105e488e8fddc19de4db26521raf if (!rec || emf_append((PU_ENHMETARECORD)rec, et, U_REC_FREE)) {
0ec5755428223b8105e488e8fddc19de4db26521raf g_error("Fatal programming error in PrintEmf::begin at U_EMRSETROP2_set");
char *rec;
if (!et) {
if (!et) {
char *rec;
int hatchType;
if (!et) {
if (fcolor) {
fmode = style->fill_rule.computed == 0 ? U_WINDING : (style->fill_rule.computed == 2 ? U_ALTERNATE : U_ALTERNATE);
if (pixbuf) {
if (FixPPTPatternAsHatch) {
// currently we do not do anything with gradients, the code below just sets the color to the average of the stops
if (rg) {
if (FixPPTGrad2Polys) {
} else if (lg) {
switch (fill_mode) {
case DRAW_PAINT:
case DRAW_PATTERN:
// SVG text has no background attribute, so OPAQUE mode ALWAYS cancels after the next draw, otherwise it would mess up future text output.
if (usebk) {
case DRAW_IMAGE:
char *px;
char *rgba_px;
int numCt;
Bmih = bitmapinfoheader_set(width, height, 1, colortype, U_BI_RGB, 0, PXPERMETER, PXPERMETER, numCt, 0);
char *rec;
// never be used because we always select in a new one before drawing anythingrestore previous brush, necessary??? Would using a default stock object not work?
if (hbrush) {
hbrush = 0;
int linecap = 0;
int linejoin = 0;
int hatchType;
char *rgba_px;
int numCt = 0;
if (!et) {
if (style) {
if (pixbuf) {
Bmih = bitmapinfoheader_set(width, height, 1, colortype, U_BI_RGB, 0, PXPERMETER, PXPERMETER, numCt, 0);
if (usebk) { // OPAQUE mode ALWAYS cancels after the next draw, otherwise it would mess up future text output.
if (FixPPTPatternAsHatch) {
yhandle_point += c;
xhandle_point += c;
} else if (style->stroke.isColor()) { // test last, always seems to be set, even for other types above
using Geom::X;
using Geom::Y;
if (FixPPTDashLine) { // will break up line into many smaller lines. Override gradient if that was set, cannot do both.
for (i = 0; i < n_dash; i++) {
dash[i] = (uint32_t)(Inkscape::Util::Quantity::convert(1, "mm", "px") * style->stroke_dasharray.values[i]);
dash);
linejoin = 0;
U_RGB(0, 0, 0),
NULL);
if (Bmi) {
if (px) {
if (n_dash) {
delete[] dash;
// set the current pen to the stock object NULL_PEN and then delete the defined pen object, if there is one.
// never be used because we always select in a new one before drawing anythingrestore previous brush, necessary??? Would using a default stock object not work?
if (hpen) {
hpen = 0;
PathVector has more than one path, or that one path is open, or any of its segments are curved, then the
returned PathVector is . If the input path is already just straight lines and vertices the output will be the
++pit2;
*vertices = 0;
*vertices = 0;
return(bad);
if(!*vertices){
return(output);
int vertices;
*is_rect = false;
int vertex_count=0;
Find minimum rotation to align rectangle with X,Y axes. (Very degenerate if it is rotated 45 degrees.) */
vertex_count++;
*is_rect=true;
return(pR);
int stat = 0;
Geom::Point v2 = Geom::Point(1,0) * Geom::Rotate(-angle); // unit horizontal side (sign change because Y increases DOWN)
if(!stat){
return(stat);
switch(corner){
LR = 0;
UL = 0;
LR = 0;
UL = 0;
Geom::Point v1 = Geom::Point(1,0) * Geom::Rotate(-angle); // unit horizontal side (sign change because Y increases DOWN)
Geom::Point v2 = Geom::Point(0,1) * Geom::Rotate(-angle); // unit vertical side (sign change because Y increases DOWN)
return(P1);
using Geom::X;
using Geom::Y;
return(tv);
using Geom::X;
using Geom::Y;
use_fill = true;
use_stroke = false;
/* native linear gradients are only used if the object is a rectangle AND the gradient is parallel to the sides of the object */
bool is_Rect = false;
double angle;
int rectDir=0;
if(is_Rect){
The overlap is needed to avoid antialiasing artifacts when edges are not strictly aligned on pixel boundaries.
There is an inevitable loss of accuracy saving through an EMF file because of the integer coordinate system.
destroy_pen(); //this sets the NULL_PEN, otherwise gradient slices may display with boundaries, see longer explanation below
int nstops;
double start;
double stop;
/* radial gradient might stop part way through the shape, fill with outer color from there to "infinity".
pathvc = center_elliptical_hole_as_SVG_PathV(gv.p1, rx * (1.0 - overlap / range), ry * (1.0 - overlap / range), asin(xuv[Y]));
pathvc = center_elliptical_ring_as_SVG_PathV(gv.p1, rx * start / range, ry * start / range, rx * stop / range, ry * stop / range, asin(xuv[Y]));
istop++;
if(is_Rect){
char *rec;
int gMode;
/* The basic rectangle for all of these is placed with its UL corner at 0,0 with a size wRect,hRect.
Actual gradientfill records are either this entire rectangle or slices of it as defined by the stops.
This rectangle has already been transformed by tf (whatever rotation/scale) Inkscape had applied to it.
Geom::Affine tf2 = Geom::Rotate(-angle); // the rectangle may be drawn skewed to the coordinate system
double start;
double stop;
Geom::PathVector pathvr = sp_pathvector_boolop(pathvc, pathv, bool_op_inters, (FillRule) fill_nonZero, frb);
istop++;
PPT, and presumably others, pick whatever they want for the border if it is not specified, so no border can
To avoid this force the pen to NULL_PEN if we can determine that no pen will be needed after the fill.
bool all_closed = true;
all_closed = false;
use_fill = false;
use_stroke = true;
// convert the path, gets its complete length, and then make a new path with parameter length instead of t
Geom::Piecewise<Geom::D2<Geom::SBasis> > tmp_pathpw3; // new (discontinuous) path, composed of dots/dashes
if (slength) {
if (i >= n_dash) {
use_stroke = false;
use_fill = false;
usebk = false;
// These use whatever the current pen/brush are and need not be followed by a FILLPATH or STROKEPATH.
int nodes = 0;
int moves = 0;
int lines = 0;
int curves = 0;
moves++;
nodes++;
nodes++;
lines++;
} else if (&*cit) {
curves++;
if (!nodes) {
using Geom::X;
using Geom::Y;
bool done = false;
bool polygon = false;
bool rectangle = false;
bool ellipse = false;
polygon = true;
// lpPoints[2].x == lpPoints[10].x && lpPoints[3].x == lpPoints[9].x && lpPoints[4].x == lpPoints[8].x &&
// lpPoints[5].y == lpPoints[1].y && lpPoints[6].y == lpPoints[0].y && lpPoints[7].y == lpPoints[11].y)
if (polygon) {
if (rectangle) {
}, (U_POINTL) {
} else if (ellipse) {
}, (U_POINTL) {
done = true;
delete[] lpPoints;
return done;
/** Some parts based on win32.cpp by Lauris Kaplinski <lauris@kaplinski.com>. Was a part of Inkscape
Geom::Affine const &tf_rect, /** affine transform only used for defining location and size of rect, for all other tranforms, use the one from m_tr_stack */
char *px;
int numCt;
/* map the integer Dest coordinates back into pLL2, so that the rounded part does not destabilize the transform offset below */
if (!FixImageRot) { /* Rotate images - some programs cannot read them in correctly if they are rotated */
if (numCt) {
if (!FixImageRot) {
// may also be called with a simple_shape or an empty path, whereupon it just returns without doing anything
if (use_fill) {
if (use_stroke) {
destroy_pen();
return TRUE;
/* inkscape to EMF scaling is done below, but NOT the rotation/translation transform,
using Geom::X;
using Geom::Y;
if (use_fill) {
if (use_stroke) {
destroy_pen();
return TRUE;
unsigned int PrintEmf::text(Inkscape::Extension::Print * /*mod*/, char const *text, Geom::Point const &p,
if (!et) {
int fix90n = 0;
double rotb = -std::atan2(tf[1], tf[0]); // rotation for baseline offset for superscript/subscript, used below
double ky;
// the dx array is smuggled in like: text<nul>w1 w2 w3 ...wn<nul><nul>, where the widths are floats 7 characters wide, including the space
smuggle_adxkyrtl_out(text, &adx, &ky, &rtl, &ndx, PX2WORLD * std::min(tf.expansionX(), tf.expansionY())); // side effect: free() adx
if (rtl > 0) {
char *text2 = strdup(text); // because U_Utf8ToUtf16le calls iconv which does not like a const char *
//PPT gets funky with text within +-1 degree of a multiple of 90, but only for SOME fonts.Snap those to the central value
if (FixPPTCharPos) {
switch (newfont) {
case CVTSYM:
case CVTZDG:
case CVTWDG:
/* Note that text font sizes are stored into the EMF as fairly small integers and that limits their precision.
land right on an integer value in the EMF file, so those are exact. However, something like 18.1 pt will be
int textheight = round(-style->font_size.computed * PX2WORLD * std::min(tf.expansionX(), tf.expansionY()));
if (!hfont) {
if (!newfont) {
//Handle super/subscripts and vertical kerning
p2[Geom::X] -= style->baseline_shift.computed * std::sin( rotb );
p2[Geom::Y] -= style->baseline_shift.computed * std::cos( rotb );
if (FixPPTCharPos) {
// encoding characters. Unclear if emrtext wants the former or the latter but for now assume the former.
char *rec2;
if (rtl > 0) {
if (hfont) {