emf-print.cpp revision 4e47dd454b9d10cbca9ad0390cfbd176858287b8
1N/A#ifdef HAVE_CONFIG_H
1N/A#include <sp-clippath.h>
1N/A#include "helper/geom-curves.h"
1N/A#include "inkscape-version.h"
1N/A#include "document.h"
1N/A#include "path-prefix.h"
1N/A#include "sp-pattern.h"
1N/A#include "sp-image.h"
1N/A#include "sp-gradient.h"
1N/A#include "sp-radial-gradient.h"
1N/A#include "sp-linear-gradient.h"
1N/A#include "display/cairo-utils.h"
1N/A#include "emf-print.h"
1N/Astatic bool FixPPTCharPos, FixPPTDashLine, FixPPTGrad2Polys, FixPPTLinGrad, FixPPTPatternAsHatch, FixImageRot;
1N/Avoid PrintEmf::smuggle_adxkyrtl_out(const char *string, uint32_t **adx, double *ky, int *rtl, int *ndx, float scale)
1N/A const char *cptr = &string[strlen(string) + 1]; // this works because of the first fake terminator
1N/A bool pageBoundingBox;
1N/A if (pageBoundingBox) {
1N/A (void) drawing_size((int) ceil(dwInchesX * 25.4), (int) ceil(dwInchesY * 25.4),1200.0/25.4, &rclBounds, &rclFrame);
1N/A // set up the reference device as 100 X A4 horizontal, (1200 dpi/25.4 -> dpmm). Extra digits maintain dpi better in EMF
1N/A snprintf(buff, sizeof(buff) - 1, "Inkscape %s (%s)\1%s\1", Inkscape::version_string, __DATE__, p);
1N/A 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"));
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) {