summaryrefslogtreecommitdiffstats
path: root/src/extension/internal/metafile-print.cpp
blob: 7cb5800af84dfeb813380825a4484173ed1c3afa (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
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 :