summaryrefslogtreecommitdiffstats
path: root/src/extension/internal/metafile-print.cpp
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--src/extension/internal/metafile-print.cpp478
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 &params)
+{
+ 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 :