diff options
Diffstat (limited to '')
-rw-r--r-- | src/extension/internal/metafile-print.cpp | 478 |
1 files changed, 478 insertions, 0 deletions
diff --git a/src/extension/internal/metafile-print.cpp b/src/extension/internal/metafile-print.cpp new file mode 100644 index 0000000..7cb5800 --- /dev/null +++ b/src/extension/internal/metafile-print.cpp @@ -0,0 +1,478 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/** @file + * @brief Metafile printing - common routines + *//* + * Authors: + * Krzysztof KosiĆski <tweenk.pl@gmail.com> + * + * Copyright (C) 2013 Authors + * Released under GNU GPL v2+, read the file 'COPYING' for more information. + */ + +#include <cstring> +#include <fstream> +#include <glib.h> +#include <glibmm/miscutils.h> +#include <2geom/rect.h> +#include <2geom/curves.h> +#include <2geom/svg-path-parser.h> + +#include "extension/internal/metafile-print.h" +#include "extension/print.h" +#include "path-prefix.h" +#include "object/sp-gradient.h" +#include "object/sp-image.h" +#include "object/sp-linear-gradient.h" +#include "object/sp-pattern.h" +#include "object/sp-radial-gradient.h" +#include "style.h" + +namespace Inkscape { +namespace Extension { +namespace Internal { + +PrintMetafile::~PrintMetafile() +{ +#ifndef G_OS_WIN32 + // restore default signal handling for SIGPIPE + (void) signal(SIGPIPE, SIG_DFL); +#endif + return; +} + +static std::map<Glib::ustring, FontfixParams> const &get_ppt_fixable_fonts() +{ + static std::map<Glib::ustring, FontfixParams> _ppt_fixable_fonts; + + if (_ppt_fixable_fonts.empty()) { + _ppt_fixable_fonts = { + // clang-format off + {{"Arial"}, { 0.05, -0.055, -0.065}}, + {{"Times New Roman"}, { 0.05, -0.055, -0.065}}, + {{"Lucida Sans"}, {-0.025, -0.055, -0.065}}, + {{"Sans"}, { 0.05, -0.055, -0.065}}, + {{"Microsoft Sans Serif"}, {-0.05, -0.055, -0.065}}, + {{"Serif"}, { 0.05, -0.055, -0.065}}, + {{"Garamond"}, { 0.05, -0.055, -0.065}}, + {{"Century Schoolbook"}, { 0.25, 0.025, 0.025}}, + {{"Verdana"}, { 0.025, 0.0, 0.0}}, + {{"Tahoma"}, { 0.045, 0.025, 0.025}}, + {{"Symbol"}, { 0.025, 0.0, 0.0}}, + {{"Wingdings"}, { 0.05, 0.0, 0.0}}, + {{"Zapf Dingbats"}, { 0.025, 0.0, 0.0}}, + {{"Convert To Symbol"}, { 0.025, 0.0, 0.0}}, + {{"Convert To Wingdings"}, { 0.05, 0.0, 0.0}}, + {{"Convert To Zapf Dingbats"}, { 0.025, 0.0, 0.0}}, + {{"Sylfaen"}, { 0.1, 0.0, 0.0}}, + {{"Palatino Linotype"}, { 0.175, 0.125, 0.125}}, + {{"Segoe UI"}, { 0.1, 0.0, 0.0}}, + // clang-format on + }; + } + return _ppt_fixable_fonts; +} + + +bool PrintMetafile::textToPath(Inkscape::Extension::Print *ext) +{ + return ext->get_param_bool("textToPath"); +} + +unsigned int PrintMetafile::bind(Inkscape::Extension::Print * /*mod*/, Geom::Affine const &transform, float /*opacity*/) +{ + if (!m_tr_stack.empty()) { + Geom::Affine tr_top = m_tr_stack.top(); + m_tr_stack.push(transform * tr_top); + } else { + m_tr_stack.push(transform); + } + + return 1; +} + +unsigned int PrintMetafile::release(Inkscape::Extension::Print * /*mod*/) +{ + m_tr_stack.pop(); + return 1; +} + +// Finds font fix parameters for the given fontname. +void PrintMetafile::_lookup_ppt_fontfix(Glib::ustring const &fontname, FontfixParams ¶ms) +{ + auto const &fixable_fonts = get_ppt_fixable_fonts(); + auto it = fixable_fonts.find(fontname); + if (it != fixable_fonts.end()) { + params = it->second; + } +} + +U_COLORREF PrintMetafile::_gethexcolor(uint32_t color) +{ + U_COLORREF out; + out = U_RGB( + (color >> 16) & 0xFF, + (color >> 8) & 0xFF, + (color >> 0) & 0xFF + ); + return out; +} + +// Translate Inkscape weights to EMF weights. +uint32_t PrintMetafile::_translate_weight(unsigned inkweight) +{ + switch (inkweight) { + // 400 is tested first, as it is the most common case + case SP_CSS_FONT_WEIGHT_400: return U_FW_NORMAL; + case SP_CSS_FONT_WEIGHT_100: return U_FW_THIN; + case SP_CSS_FONT_WEIGHT_200: return U_FW_EXTRALIGHT; + case SP_CSS_FONT_WEIGHT_300: return U_FW_LIGHT; + case SP_CSS_FONT_WEIGHT_500: return U_FW_MEDIUM; + case SP_CSS_FONT_WEIGHT_600: return U_FW_SEMIBOLD; + case SP_CSS_FONT_WEIGHT_700: return U_FW_BOLD; + case SP_CSS_FONT_WEIGHT_800: return U_FW_EXTRABOLD; + case SP_CSS_FONT_WEIGHT_900: return U_FW_HEAVY; + default: return U_FW_NORMAL; + } +} + +/* opacity weighting of two colors as float. v1 is the color, op is its opacity, v2 is the background color */ +inline float opweight(float v1, float v2, float op) +{ + return v1 * op + v2 * (1.0 - op); +} + +U_COLORREF PrintMetafile::avg_stop_color(SPGradient *gr) +{ + U_COLORREF cr; + int last = gr->vector.stops.size() - 1; + if (last >= 1) { + float rgbs[3]; + float rgbe[3]; + float ops, ope; + + ops = gr->vector.stops[0 ].opacity; + ope = gr->vector.stops[last].opacity; + gr->vector.stops[0 ].color.get_rgb_floatv(rgbs); + gr->vector.stops[last].color.get_rgb_floatv(rgbe); + + /* Replace opacity at start & stop with that fraction background color, then average those two for final color. */ + cr = U_RGB( + 255 * ((opweight(rgbs[0], gv.rgb[0], ops) + opweight(rgbe[0], gv.rgb[0], ope)) / 2.0), + 255 * ((opweight(rgbs[1], gv.rgb[1], ops) + opweight(rgbe[1], gv.rgb[1], ope)) / 2.0), + 255 * ((opweight(rgbs[2], gv.rgb[2], ops) + opweight(rgbe[2], gv.rgb[2], ope)) / 2.0) + ); + } else { + cr = U_RGB(0, 0, 0); // The default fill + } + return cr; +} + +U_COLORREF PrintMetafile::weight_opacity(U_COLORREF c1) +{ + float opa = c1.Reserved / 255.0; + U_COLORREF result = U_RGB( + 255 * opweight((float)c1.Red / 255.0, gv.rgb[0], opa), + 255 * opweight((float)c1.Green / 255.0, gv.rgb[1], opa), + 255 * opweight((float)c1.Blue / 255.0, gv.rgb[2], opa) + ); + return result; +} + +/* t between 0 and 1, values outside that range use the nearest limit */ +U_COLORREF PrintMetafile::weight_colors(U_COLORREF c1, U_COLORREF c2, double t) +{ +#define clrweight(a,b,t) ((1-t)*((double) a) + (t)*((double) b)) + U_COLORREF result; + t = ( t > 1.0 ? 1.0 : ( t < 0.0 ? 0.0 : t)); + // clang-format off + result.Red = clrweight(c1.Red, c2.Red, t); + result.Green = clrweight(c1.Green, c2.Green, t); + result.Blue = clrweight(c1.Blue, c2.Blue, t); + result.Reserved = clrweight(c1.Reserved, c2.Reserved, t); + // clang-format on + + // now handle the opacity, mix the RGB with background at the weighted opacity + + if (result.Reserved != 255) { + result = weight_opacity(result); + } + + return result; +} + +// Extract hatchType, hatchColor from a name like +// EMFhatch<hatchType>_<hatchColor> +// Where the first one is a number and the second a color in hex. +// hatchType and hatchColor have been set with defaults before this is called. +// +void PrintMetafile::hatch_classify(char *name, int *hatchType, U_COLORREF *hatchColor, U_COLORREF *bkColor) +{ + int val; + uint32_t hcolor = 0; + uint32_t bcolor = 0; + + // name should be EMFhatch or WMFhatch but *MFhatch will be accepted + if (0 != strncmp(&name[1], "MFhatch", 7)) { + return; // not anything we can parse + } + name += 8; // EMFhatch already detected + val = 0; + while (*name && isdigit(*name)) { + val = 10 * val + *name - '0'; + name++; + } + *hatchType = val; + if (*name != '_' || val > U_HS_DITHEREDBKCLR) { // wrong syntax, cannot classify + *hatchType = -1; + } else { + name++; + if (2 != sscanf(name, "%X_%X", &hcolor, &bcolor)) { // not a pattern with background + if (1 != sscanf(name, "%X", &hcolor)) { + *hatchType = -1; // not a pattern, cannot classify + } + *hatchColor = _gethexcolor(hcolor); + } else { + *hatchColor = _gethexcolor(hcolor); + *bkColor = _gethexcolor(bcolor); + usebk = true; + } + } + /* Everything > U_HS_SOLIDCLR is solid, just specify the color in the brush rather than messing around with background or textcolor */ + if (*hatchType > U_HS_SOLIDCLR) { + *hatchType = U_HS_SOLIDCLR; + } +} + +// +// Recurse down from a brush pattern, try to figure out what it is. +// If an image is found set a pointer to the epixbuf, else set that to NULL +// If a pattern is found with a name like [EW]MFhatch3_3F7FFF return hatchType=3, hatchColor=3F7FFF (as a uint32_t), +// otherwise hatchType is set to -1 and hatchColor is not defined. +// + +void PrintMetafile::brush_classify(SPObject *parent, int depth, Inkscape::Pixbuf **epixbuf, int *hatchType, U_COLORREF *hatchColor, U_COLORREF *bkColor) +{ + if (depth == 0) { + *epixbuf = nullptr; + *hatchType = -1; + *hatchColor = U_RGB(0, 0, 0); + *bkColor = U_RGB(255, 255, 255); + } + depth++; + // first look along the pattern chain, if there is one + if (SP_IS_PATTERN(parent)) { + for (SPPattern *pat_i = SP_PATTERN(parent); pat_i != nullptr; pat_i = pat_i->ref ? pat_i->ref->getObject() : nullptr) { + if (SP_IS_IMAGE(pat_i)) { + *epixbuf = ((SPImage *)pat_i)->pixbuf; + return; + } + char temp[32]; // large enough + strncpy(temp, pat_i->getAttribute("id"), sizeof(temp)-1); // Some names may be longer than [EW]MFhatch#_###### + temp[sizeof(temp)-1] = '\0'; + hatch_classify(temp, hatchType, hatchColor, bkColor); + if (*hatchType != -1) { + return; + } + + // still looking? Look at this pattern's children, if there are any + for (auto& child: pat_i->children) { + if (*epixbuf || *hatchType != -1) { + break; + } + brush_classify(&child, depth, epixbuf, hatchType, hatchColor, bkColor); + } + } + } else if (SP_IS_IMAGE(parent)) { + *epixbuf = ((SPImage *)parent)->pixbuf; + return; + } else { // some inkscape rearrangements pass through nodes between pattern and image which are not classified as either. + for (auto& child: parent->children) { + if (*epixbuf || *hatchType != -1) { + break; + } + brush_classify(&child, depth, epixbuf, hatchType, hatchColor, bkColor); + } + } +} + +//swap R/B in 4 byte pixel +void PrintMetafile::swapRBinRGBA(char *px, int pixels) +{ + char tmp; + for (int i = 0; i < pixels * 4; px += 4, i += 4) { + tmp = px[2]; + px[2] = px[0]; + px[0] = tmp; + } +} + +int PrintMetafile::hold_gradient(void *gr, int mode) +{ + gv.mode = mode; + gv.grad = gr; + if (mode == DRAW_RADIAL_GRADIENT) { + SPRadialGradient *rg = (SPRadialGradient *) gr; + gv.r = rg->r.computed; // radius, but of what??? + gv.p1 = Geom::Point(rg->cx.computed, rg->cy.computed); // center + gv.p2 = Geom::Point(gv.r, 0) + gv.p1; // xhandle + gv.p3 = Geom::Point(0, -gv.r) + gv.p1; // yhandle + if (rg->gradientTransform_set) { + gv.p1 = gv.p1 * rg->gradientTransform; + gv.p2 = gv.p2 * rg->gradientTransform; + gv.p3 = gv.p3 * rg->gradientTransform; + } + } else if (mode == DRAW_LINEAR_GRADIENT) { + SPLinearGradient *lg = (SPLinearGradient *) gr; + gv.r = 0; // unused + gv.p1 = Geom::Point(lg->x1.computed, lg->y1.computed); // start + gv.p2 = Geom::Point(lg->x2.computed, lg->y2.computed); // end + gv.p3 = Geom::Point(0, 0); // unused + if (lg->gradientTransform_set) { + gv.p1 = gv.p1 * lg->gradientTransform; + gv.p2 = gv.p2 * lg->gradientTransform; + } + } else { + g_error("Fatal programming error, hold_gradient() in metafile-print.cpp called with invalid draw mode"); + } + return 1; +} + +/* convert from center ellipse to SVGEllipticalArc ellipse + + From: + http://www.w3.org/TR/SVG/implnote.html#ArcConversionEndpointToCenter + A point (x,y) on the arc can be found by: + + {x,y} = {cx,cy} + {cosF,-sinF,sinF,cosF} x {rxcosT,rysinT} + + where + {cx,cy} is the center of the ellipse + F is the rotation angle of the X axis of the ellipse from the true X axis + T is the rotation angle around the ellipse + {,,,} is the rotation matrix + rx,ry are the radii of the ellipse's axes + + For SVG parameterization need two points. + Arbitrarily we can use T=0 and T=pi + Since the sweep is 180 the flags are always 0: + + F is in RADIANS, but the SVGEllipticalArc needs degrees! + +*/ +Geom::PathVector PrintMetafile::center_ellipse_as_SVG_PathV(Geom::Point ctr, double rx, double ry, double F) +{ + using Geom::X; + using Geom::Y; + double x1, y1, x2, y2; + Geom::Path SVGep; + + x1 = ctr[X] + cos(F) * rx * cos(0) + sin(-F) * ry * sin(0); + y1 = ctr[Y] + sin(F) * rx * cos(0) + cos(F) * ry * sin(0); + x2 = ctr[X] + cos(F) * rx * cos(M_PI) + sin(-F) * ry * sin(M_PI); + y2 = ctr[Y] + sin(F) * rx * cos(M_PI) + cos(F) * ry * sin(M_PI); + + char text[256]; + snprintf(text, 256, " M %f,%f A %f %f %f 0 0 %f %f A %f %f %f 0 0 %f %f z", + x1, y1, rx, ry, F * 360. / (2.*M_PI), x2, y2, rx, ry, F * 360. / (2.*M_PI), x1, y1); + Geom::PathVector outres = Geom::parse_svg_path(text); + return outres; +} + + +/* rx2,ry2 must be larger than rx1,ry1! + angle is in RADIANS +*/ +Geom::PathVector PrintMetafile::center_elliptical_ring_as_SVG_PathV(Geom::Point ctr, double rx1, double ry1, double rx2, double ry2, double F) +{ + using Geom::X; + using Geom::Y; + double x11, y11, x12, y12; + double x21, y21, x22, y22; + double degrot = F * 360. / (2.*M_PI); + + x11 = ctr[X] + cos(F) * rx1 * cos(0) + sin(-F) * ry1 * sin(0); + y11 = ctr[Y] + sin(F) * rx1 * cos(0) + cos(F) * ry1 * sin(0); + x12 = ctr[X] + cos(F) * rx1 * cos(M_PI) + sin(-F) * ry1 * sin(M_PI); + y12 = ctr[Y] + sin(F) * rx1 * cos(M_PI) + cos(F) * ry1 * sin(M_PI); + + x21 = ctr[X] + cos(F) * rx2 * cos(0) + sin(-F) * ry2 * sin(0); + y21 = ctr[Y] + sin(F) * rx2 * cos(0) + cos(F) * ry2 * sin(0); + x22 = ctr[X] + cos(F) * rx2 * cos(M_PI) + sin(-F) * ry2 * sin(M_PI); + y22 = ctr[Y] + sin(F) * rx2 * cos(M_PI) + cos(F) * ry2 * sin(M_PI); + + char text[512]; + snprintf(text, 512, " M %f,%f A %f %f %f 0 1 %f %f A %f %f %f 0 1 %f %f z M %f,%f A %f %f %f 0 0 %f %f A %f %f %f 0 0 %f %f z", + x11, y11, rx1, ry1, degrot, x12, y12, rx1, ry1, degrot, x11, y11, + x21, y21, rx2, ry2, degrot, x22, y22, rx2, ry2, degrot, x21, y21); + Geom::PathVector outres = Geom::parse_svg_path(text); + + return outres; +} + +/* Elliptical hole in a large square extending from -50k to +50k */ +Geom::PathVector PrintMetafile::center_elliptical_hole_as_SVG_PathV(Geom::Point ctr, double rx, double ry, double F) +{ + using Geom::X; + using Geom::Y; + double x1, y1, x2, y2; + Geom::Path SVGep; + + x1 = ctr[X] + cos(F) * rx * cos(0) + sin(-F) * ry * sin(0); + y1 = ctr[Y] + sin(F) * rx * cos(0) + cos(F) * ry * sin(0); + x2 = ctr[X] + cos(F) * rx * cos(M_PI) + sin(-F) * ry * sin(M_PI); + y2 = ctr[Y] + sin(F) * rx * cos(M_PI) + cos(F) * ry * sin(M_PI); + + char text[256]; + snprintf(text, 256, " M %f,%f A %f %f %f 0 0 %f %f A %f %f %f 0 0 %f %f z M 50000,50000 50000,-50000 -50000,-50000 -50000,50000 z", + x1, y1, rx, ry, F * 360. / (2.*M_PI), x2, y2, rx, ry, F * 360. / (2.*M_PI), x1, y1); + Geom::PathVector outres = Geom::parse_svg_path(text); + return outres; +} + +/* rectangular cutter. +ctr "center" of rectangle (might not actually be in the center with respect to leading/trailing edges +pos vector from center to leading edge +neg vector from center to trailing edge +width vector to side edge +*/ +Geom::PathVector PrintMetafile::rect_cutter(Geom::Point ctr, Geom::Point pos, Geom::Point neg, Geom::Point width) +{ + Geom::PathVector outres; + Geom::Path cutter; + cutter.start(ctr + pos - width); + cutter.appendNew<Geom::LineSegment>(ctr + pos + width); + cutter.appendNew<Geom::LineSegment>(ctr + neg + width); + cutter.appendNew<Geom::LineSegment>(ctr + neg - width); + cutter.close(); + outres.push_back(cutter); + return outres; +} + +/* Convert from SPWindRule to livarot's FillRule + This is similar to what sp_selected_path_boolop() does +*/ +FillRule PrintMetafile::SPWR_to_LVFR(SPWindRule wr) +{ + FillRule fr; + if (wr == SP_WIND_RULE_EVENODD) { + fr = fill_oddEven; + } else { + fr = fill_nonZero; + } + return fr; +} + +} // namespace Internal +} // namespace Extension +} // namespace Inkscape + +/* + Local Variables: + mode:c++ + c-file-style:"stroustrup" + c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +)) + indent-tabs-mode:nil + fill-column:99 + End: +*/ +// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 : |