Coverage Report

Created: 2026-05-16 09:25

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/libreoffice/drawinglayer/source/processor2d/cairopixelprocessor2d.cxx
Line
Count
Source
1
/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4; fill-column: 100 -*- */
2
/*
3
 * This file is part of the LibreOffice project.
4
 *
5
 * This Source Code Form is subject to the terms of the Mozilla Public
6
 * License, v. 2.0. If a copy of the MPL was not distributed with this
7
 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
8
 */
9
10
#include <sal/config.h>
11
12
#include <drawinglayer/processor2d/cairopixelprocessor2d.hxx>
13
#include <drawinglayer/processor2d/SDPRProcessor2dTools.hxx>
14
#include <sal/log.hxx>
15
#include <vcl/BitmapTools.hxx>
16
#include <vcl/BitmapWriteAccess.hxx>
17
#include <vcl/alpha.hxx>
18
#include <vcl/cairo.hxx>
19
#include <vcl/CairoFormats.hxx>
20
#include <vcl/canvastools.hxx>
21
#include <vcl/outdev.hxx>
22
#include <vcl/rendercontext/DrawModeFlags.hxx>
23
#include <vcl/sysdata.hxx>
24
#include <vcl/svapp.hxx>
25
#include <comphelper/lok.hxx>
26
#include <basegfx/polygon/b2dpolygontools.hxx>
27
#include <basegfx/polygon/b2dpolypolygontools.hxx>
28
#include <drawinglayer/primitive2d/drawinglayer_primitivetypes2d.hxx>
29
#include <drawinglayer/primitive2d/PolyPolygonColorPrimitive2D.hxx>
30
#include <drawinglayer/primitive2d/PolygonHairlinePrimitive2D.hxx>
31
#include <drawinglayer/primitive2d/bitmapprimitive2d.hxx>
32
#include <drawinglayer/primitive2d/unifiedtransparenceprimitive2d.hxx>
33
#include <drawinglayer/primitive2d/backgroundcolorprimitive2d.hxx>
34
#include <drawinglayer/primitive2d/baseprimitive2d.hxx>
35
#include <drawinglayer/primitive2d/markerarrayprimitive2d.hxx>
36
#include <drawinglayer/primitive2d/maskprimitive2d.hxx>
37
#include <drawinglayer/primitive2d/modifiedcolorprimitive2d.hxx>
38
#include <drawinglayer/primitive2d/pointarrayprimitive2d.hxx>
39
#include <drawinglayer/primitive2d/PolygonStrokePrimitive2D.hxx>
40
#include <drawinglayer/primitive2d/Tools.hxx>
41
#include <drawinglayer/primitive2d/transformprimitive2d.hxx>
42
#include <drawinglayer/primitive2d/transparenceprimitive2d.hxx>
43
#include <drawinglayer/primitive2d/fillgraphicprimitive2d.hxx>
44
#include <drawinglayer/primitive2d/fillgradientprimitive2d.hxx>
45
#include <drawinglayer/primitive2d/invertprimitive2d.hxx>
46
#include <drawinglayer/primitive2d/PolyPolygonRGBAPrimitive2D.hxx>
47
#include <drawinglayer/primitive2d/PolyPolygonAlphaGradientPrimitive2D.hxx>
48
#include <drawinglayer/primitive2d/BitmapAlphaPrimitive2D.hxx>
49
#include <drawinglayer/primitive2d/textprimitive2d.hxx>
50
#include <drawinglayer/primitive2d/textdecoratedprimitive2d.hxx>
51
#include <drawinglayer/primitive2d/svggradientprimitive2d.hxx>
52
#include <drawinglayer/primitive2d/controlprimitive2d.hxx>
53
#include <drawinglayer/primitive2d/textlayoutdevice.hxx>
54
#include <drawinglayer/primitive2d/patternfillprimitive2d.hxx>
55
#include <basegfx/matrix/b2dhommatrixtools.hxx>
56
#include <basegfx/utils/systemdependentdata.hxx>
57
#include <basegfx/utils/bgradient.hxx>
58
#include <vcl/BitmapReadAccess.hxx>
59
#include <vcl/vcllayout.hxx>
60
#include <officecfg/Office/Common.hxx>
61
#include <com/sun/star/awt/XView.hpp>
62
#include <com/sun/star/awt/XControl.hpp>
63
#include <unordered_map>
64
#ifndef _WIN32
65
#include <dlfcn.h>
66
#endif
67
#include "vclhelperbufferdevice.hxx"
68
#include <iostream>
69
70
using namespace com::sun::star;
71
72
namespace
73
{
74
void impl_cairo_set_hairline(cairo_t* pRT,
75
                             const drawinglayer::geometry::ViewInformation2D& rViewInformation,
76
                             bool bCairoCoordinateLimitWorkaroundActive)
77
122
{
78
122
#ifndef _WIN32
79
122
#if CAIRO_VERSION >= CAIRO_VERSION_ENCODE(1, 18, 0)
80
122
    void* addr(dlsym(nullptr, "cairo_set_hairline"));
81
122
    if (nullptr != addr)
82
0
    {
83
0
        cairo_set_hairline(pRT, true);
84
0
        return;
85
0
    }
86
122
#endif
87
122
    if (bCairoCoordinateLimitWorkaroundActive)
88
0
    {
89
        // we have to render in view coordinates, set line width to 1.0
90
0
        cairo_set_line_width(pRT, 1.0);
91
0
    }
92
122
    else
93
122
    {
94
        // avoid cairo_device_to_user_distance, see note on that below
95
122
        const double fPx(
96
122
            (rViewInformation.getInverseObjectToViewTransformation() * basegfx::B2DVector(1.0, 0.0))
97
122
                .getLength());
98
122
        cairo_set_line_width(pRT, fPx);
99
122
    }
100
#else
101
    // No system cairo on Windows, so cairo is by necessity one built by us, and we know that it is
102
    // the right version with cairo_set_hairline().
103
    (void)rViewInformation;
104
    (void)bCairoCoordinateLimitWorkaroundActive;
105
    cairo_set_hairline(pRT, true);
106
#endif
107
122
}
108
109
void addB2DPolygonToPathGeometry(cairo_t* pRT, const basegfx::B2DPolygon& rPolygon)
110
542
{
111
542
    const sal_uInt32 nPointCount(rPolygon.count());
112
113
542
    if (0 == nPointCount)
114
        // no points, done
115
0
        return;
116
117
    // get basic infos
118
542
    const bool bClosed(rPolygon.isClosed());
119
542
    const sal_uInt32 nEdgeCount(bClosed ? nPointCount : nPointCount - 1);
120
121
    // get 1st point and move to it
122
542
    basegfx::B2DPoint aCurrent(rPolygon.getB2DPoint(0));
123
542
    cairo_move_to(pRT, aCurrent.getX(), aCurrent.getY());
124
125
3.04k
    for (sal_uInt32 nIndex(0); nIndex < nEdgeCount; nIndex++)
126
2.50k
    {
127
        // get index for and next point
128
2.50k
        const sal_uInt32 nNextIndex((nIndex + 1) % nPointCount);
129
2.50k
        const basegfx::B2DPoint aNext(rPolygon.getB2DPoint(nNextIndex));
130
131
        // get and check curve stuff
132
2.50k
        basegfx::B2DPoint aCP1(rPolygon.getNextControlPoint(nIndex));
133
2.50k
        basegfx::B2DPoint aCP2(rPolygon.getPrevControlPoint(nNextIndex));
134
2.50k
        const bool bCP1Equal(aCP1.equal(aCurrent));
135
2.50k
        const bool bCP2Equal(aCP2.equal(aNext));
136
137
2.50k
        if (!bCP1Equal || !bCP2Equal)
138
342
        {
139
            // tdf#99165, see other similar changes for more info
140
342
            if (bCP1Equal)
141
0
                aCP1 = aCurrent + ((aCP2 - aCurrent) * 0.0005);
142
143
342
            if (bCP2Equal)
144
0
                aCP2 = aNext + ((aCP1 - aNext) * 0.0005);
145
146
342
            cairo_curve_to(pRT, aCP1.getX(), aCP1.getY(), aCP2.getX(), aCP2.getY(), aNext.getX(),
147
342
                           aNext.getY());
148
342
        }
149
2.15k
        else
150
2.15k
        {
151
2.15k
            cairo_line_to(pRT, aNext.getX(), aNext.getY());
152
2.15k
        }
153
154
        // prepare next step
155
2.50k
        aCurrent = aNext;
156
2.50k
    }
157
158
542
    if (bClosed)
159
530
        cairo_close_path(pRT);
160
542
}
161
162
// needed as helper, see below. It guarantees clean
163
// construction/cleanup using destructor
164
// NOTE: maybe mpSurface can be constructed even simpler,
165
// not sure about that. It is only used to construct
166
// and hold path data
167
struct CairoContextHolder
168
{
169
    cairo_surface_t* mpSurface;
170
    cairo_t* mpRenderContext;
171
172
    CairoContextHolder()
173
60
        : mpSurface(cairo_image_surface_create(CAIRO_FORMAT_A1, 1, 1))
174
60
        , mpRenderContext(cairo_create(mpSurface))
175
60
    {
176
60
    }
177
178
    ~CairoContextHolder()
179
0
    {
180
0
        cairo_destroy(mpRenderContext);
181
0
        cairo_surface_destroy(mpSurface);
182
0
    }
183
184
1.60k
    cairo_t* getContext() const { return mpRenderContext; }
185
};
186
187
// global static helper instance
188
CairoContextHolder globalStaticCairoContext;
189
190
// it shows that re-using and buffering path geometry data using
191
// cairo is more complicated than initially thought: when adding
192
// a path to a cairo_t render context it already *uses* the set
193
// transformation, also usually consumes the path when painting.
194
// The (only available) method cairo_copy_path to preserve that
195
// data *also* transforms the path - if not already created in
196
// transformed form - using the current transformation set at the
197
// cairo context.
198
// This is not what we want to have a re-usable path that is
199
// buffered at the Poly(poly)gon: we explicitly want *exactly*
200
// the coordinates in the polygon preserved *at* the polygon to
201
// be able to re-use that data independent from any set
202
// transformation at any cairo context.
203
// Thus, create paths using a helper (CairoPathHelper) using a
204
// helper cairo context (CairoContextHolder) that never gets
205
// transformed. This removes the need to feed it the cairo context,
206
// but also does not immediately add the path data to the target
207
// context, that needs to be done using cairo_append_path at the
208
// target cairo context. That works since all geometry is designed
209
// to use exactly that coordinate system the polygon is already
210
// designed for anyways, and it transforms as needed inside the
211
// target cairo context as needed (if transform is set)
212
class CairoPathHelper
213
{
214
    // the created CairoPath
215
    cairo_path_t* mpCairoPath;
216
217
public:
218
    CairoPathHelper(const basegfx::B2DPolygon& rPolygon)
219
122
        : mpCairoPath(nullptr)
220
122
    {
221
122
        cairo_new_path(globalStaticCairoContext.getContext());
222
122
        addB2DPolygonToPathGeometry(globalStaticCairoContext.getContext(), rPolygon);
223
122
        mpCairoPath = cairo_copy_path(globalStaticCairoContext.getContext());
224
122
    }
225
226
    CairoPathHelper(const basegfx::B2DPolyPolygon& rPolyPolygon)
227
408
        : mpCairoPath(nullptr)
228
408
    {
229
408
        cairo_new_path(globalStaticCairoContext.getContext());
230
408
        for (const auto& rPolygon : rPolyPolygon)
231
420
            addB2DPolygonToPathGeometry(globalStaticCairoContext.getContext(), rPolygon);
232
408
        mpCairoPath = cairo_copy_path(globalStaticCairoContext.getContext());
233
408
    }
234
235
    ~CairoPathHelper()
236
530
    {
237
        // need to cleanup instance
238
530
        cairo_path_destroy(mpCairoPath);
239
530
    }
240
241
    // read access
242
530
    cairo_path_t* getCairoPath() const { return mpCairoPath; }
243
244
    sal_Int64 getEstimatedSize() const
245
0
    {
246
0
        if (nullptr == mpCairoPath)
247
0
            return 0;
248
249
        // per node:
250
        // - num_data incarnations of
251
        // - sizeof(cairo_path_data_t) which is a union of defines and point data
252
        //   thus may 2 x sizeof(double)
253
0
        return mpCairoPath->num_data * sizeof(cairo_path_data_t);
254
0
    }
255
};
256
257
class SystemDependentData_CairoPathGeometry : public basegfx::SystemDependentData
258
{
259
    // the CairoPath holder
260
    std::shared_ptr<CairoPathHelper> mpCairoPathHelper;
261
262
public:
263
    SystemDependentData_CairoPathGeometry(const std::shared_ptr<CairoPathHelper>& pCairoPathHelper)
264
24
        : basegfx::SystemDependentData(Application::GetSystemDependentDataManager(),
265
24
                                       basegfx::SDD_Type::SDDType_CairoPathGeometry)
266
24
        , mpCairoPathHelper(pCairoPathHelper)
267
24
    {
268
24
    }
269
270
    // read access
271
0
    const std::shared_ptr<CairoPathHelper>& getCairoPathHelper() const { return mpCairoPathHelper; }
272
273
    virtual sal_Int64 estimateUsageInBytes() const override
274
0
    {
275
0
        return (nullptr != mpCairoPathHelper) ? mpCairoPathHelper->getEstimatedSize() : 0;
276
0
    }
277
};
278
279
constexpr unsigned long nMinimalPointsPath(4);
280
constexpr unsigned long nMinimalPointsFill(12);
281
282
void checkAndDoPixelSnap(cairo_t* pRT,
283
                         const drawinglayer::geometry::ViewInformation2D& rViewInformation)
284
122
{
285
122
    const bool bPixelSnap(rViewInformation.getPixelSnapHairline()
286
0
                          && rViewInformation.getUseAntiAliasing());
287
288
122
    if (!bPixelSnap)
289
        // no pixel snap, done
290
122
        return;
291
292
    // with the comments above at CairoPathHelper we cannot do PixelSnap
293
    // at path construction time, so it needs to be done *after* the path
294
    // data is added to the cairo context. Advantage is that all general
295
    // path data can be buffered, though, but needs view-dependent manipulation
296
    // here after being added.
297
    // For now, just snap all points - no real need to identify hor/ver lines
298
    // when you think about it
299
300
    // get helper path
301
0
    cairo_path_t* path(cairo_copy_path(pRT));
302
303
0
    if (0 == path->num_data)
304
0
    {
305
        // path is empty, done
306
0
        cairo_path_destroy(path);
307
0
        return;
308
0
    }
309
310
0
    auto doPixelSnap([&pRT](double& rX, double& rY) {
311
        // transform to discrete pixels
312
0
        cairo_user_to_device(pRT, &rX, &rY);
313
314
        // round them, also add 0.5 which will be as transform in
315
        // the paint method to move to 'inside' pixels when AA used.
316
        // remember: this is only done when AA is active (see bPixelSnap
317
        // above) and moves the hairline to full-pixel position
318
0
        rX = trunc(rX) + 0.5;
319
0
        rY = trunc(rY) + 0.5;
320
321
        // transform back to former transformed state
322
0
        cairo_device_to_user(pRT, &rX, &rY);
323
0
    });
324
325
0
    for (int a(0); a < path->num_data; a += path->data[a].header.length)
326
0
    {
327
0
        cairo_path_data_t* data(&path->data[a]);
328
329
0
        switch (data->header.type)
330
0
        {
331
0
            case CAIRO_PATH_CURVE_TO:
332
0
            {
333
                // curve: snap all three point positions,
334
                // thus use fallthrough below
335
0
                doPixelSnap(data[2].point.x, data[2].point.y);
336
0
                doPixelSnap(data[3].point.x, data[3].point.y);
337
0
                [[fallthrough]]; // break;
338
0
            }
339
0
            case CAIRO_PATH_MOVE_TO:
340
0
            case CAIRO_PATH_LINE_TO:
341
0
            {
342
                // path/move: snap first point position
343
0
                doPixelSnap(data[1].point.x, data[1].point.y);
344
0
                break;
345
0
            }
346
0
            case CAIRO_PATH_CLOSE_PATH:
347
0
            {
348
0
                break;
349
0
            }
350
0
        }
351
0
    }
352
353
    // set changed path back at cairo context
354
0
    cairo_new_path(pRT);
355
0
    cairo_append_path(pRT, path);
356
357
    // destroy helper path
358
0
    cairo_path_destroy(path);
359
0
}
360
361
void getOrCreatePathGeometry(cairo_t* pRT, const basegfx::B2DPolygon& rPolygon,
362
                             const drawinglayer::geometry::ViewInformation2D& rViewInformation,
363
                             bool bPixelSnap)
364
122
{
365
    // try to access buffered data
366
122
    std::shared_ptr<SystemDependentData_CairoPathGeometry> pSystemDependentData_CairoPathGeometry(
367
122
        rPolygon.getSystemDependentData<SystemDependentData_CairoPathGeometry>(
368
122
            basegfx::SDD_Type::SDDType_CairoPathGeometry));
369
370
122
    if (pSystemDependentData_CairoPathGeometry)
371
0
    {
372
        // re-use data and do evtl. needed pixel snap after adding on cairo path data
373
0
        cairo_append_path(
374
0
            pRT, pSystemDependentData_CairoPathGeometry->getCairoPathHelper()->getCairoPath());
375
0
        if (bPixelSnap)
376
0
            checkAndDoPixelSnap(pRT, rViewInformation);
377
0
        return;
378
0
    }
379
380
    // create new data and add path data to pRT and do evtl. needed pixel snap after adding on cairo path data
381
122
    std::shared_ptr<CairoPathHelper> pCairoPathHelper(std::make_shared<CairoPathHelper>(rPolygon));
382
122
    cairo_append_path(pRT, pCairoPathHelper->getCairoPath());
383
122
    if (bPixelSnap)
384
122
        checkAndDoPixelSnap(pRT, rViewInformation);
385
386
    // add to buffering mechanism if not trivial
387
122
    if (rPolygon.count() > nMinimalPointsPath)
388
0
        rPolygon.addOrReplaceSystemDependentData<SystemDependentData_CairoPathGeometry>(
389
0
            pCairoPathHelper);
390
122
}
391
392
void getOrCreateFillGeometry(cairo_t* pRT, const basegfx::B2DPolyPolygon& rPolyPolygon)
393
405
{
394
    // try to access buffered data
395
405
    std::shared_ptr<SystemDependentData_CairoPathGeometry> pSystemDependentData_CairoPathGeometry(
396
405
        rPolyPolygon.getSystemDependentData<SystemDependentData_CairoPathGeometry>(
397
405
            basegfx::SDD_Type::SDDType_CairoPathGeometry));
398
399
405
    if (pSystemDependentData_CairoPathGeometry)
400
0
    {
401
        // re-use data
402
0
        cairo_append_path(
403
0
            pRT, pSystemDependentData_CairoPathGeometry->getCairoPathHelper()->getCairoPath());
404
0
        return;
405
0
    }
406
407
    // create new data and add path data to pRT
408
405
    std::shared_ptr<CairoPathHelper> pCairoPathHelper(
409
405
        std::make_shared<CairoPathHelper>(rPolyPolygon));
410
405
    cairo_append_path(pRT, pCairoPathHelper->getCairoPath());
411
412
    // get all PointCount to detect non-trivial
413
405
    sal_uInt32 nAllPointCount(0);
414
405
    for (const auto& rPolygon : rPolyPolygon)
415
417
        nAllPointCount += rPolygon.count();
416
417
    // add to buffering mechanism when no PixelSnapHairline (see above) and not trivial
418
405
    if (nAllPointCount > nMinimalPointsFill)
419
24
        rPolyPolygon.addOrReplaceSystemDependentData<SystemDependentData_CairoPathGeometry>(
420
24
            pCairoPathHelper);
421
405
}
422
423
// check for env var that decides for using downscale pattern
424
const char* pDisableDownScale(getenv("SAL_DISABLE_CAIRO_DOWNSCALE"));
425
const bool bDisableDownScale(nullptr != pDisableDownScale);
426
constexpr unsigned long nMinimalDiscreteSize(15);
427
constexpr unsigned long nHalfMDSize((nMinimalDiscreteSize + 1) / 2);
428
constexpr unsigned long
429
nMinimalDiscreteSquareSizeToBuffer(nMinimalDiscreteSize* nMinimalDiscreteSize);
430
431
class CairoSurfaceHelper
432
{
433
    // the buffered CairoSurface (bitmap data)
434
    cairo_surface_t* mpCairoSurface;
435
436
    // evtl. MipMapped data (pre-scale to reduce data processing load)
437
    mutable std::unordered_map<sal_uInt64, cairo_surface_t*> maDownscaled;
438
439
    // create 32bit RGBA data for given Bitmap
440
    void createRGBA(const Bitmap& rBitmap)
441
0
    {
442
0
        BitmapScopedReadAccess pReadAccess(rBitmap);
443
0
        const tools::Long nHeight(pReadAccess->Height());
444
0
        const tools::Long nWidth(pReadAccess->Width());
445
0
        mpCairoSurface = cairo_image_surface_create(CAIRO_FORMAT_ARGB32, nWidth, nHeight);
446
0
        if (cairo_surface_status(mpCairoSurface) != CAIRO_STATUS_SUCCESS)
447
0
        {
448
0
            SAL_WARN("drawinglayer",
449
0
                     "cairo_image_surface_create failed for: " << nWidth << " x " << nHeight);
450
0
            return;
451
0
        }
452
0
        const sal_uInt32 nStride(cairo_format_stride_for_width(CAIRO_FORMAT_ARGB32, nWidth));
453
0
        unsigned char* surfaceData(cairo_image_surface_get_data(mpCairoSurface));
454
455
0
        for (tools::Long y(0); y < nHeight; ++y)
456
0
        {
457
0
            unsigned char* pPixelData(surfaceData + (nStride * y));
458
459
0
            for (tools::Long x(0); x < nWidth; ++x)
460
0
            {
461
0
                const BitmapColor aColor(pReadAccess->GetColor(y, x));
462
0
                const sal_uInt16 nAlpha(aColor.GetAlpha());
463
464
0
                pPixelData[SVP_CAIRO_RED] = vcl::bitmap::premultiply(aColor.GetRed(), nAlpha);
465
0
                pPixelData[SVP_CAIRO_GREEN] = vcl::bitmap::premultiply(aColor.GetGreen(), nAlpha);
466
0
                pPixelData[SVP_CAIRO_BLUE] = vcl::bitmap::premultiply(aColor.GetBlue(), nAlpha);
467
0
                pPixelData[SVP_CAIRO_ALPHA] = nAlpha;
468
0
                pPixelData += 4;
469
0
            }
470
0
        }
471
472
0
        cairo_surface_mark_dirty(mpCairoSurface);
473
0
    }
474
475
    // create 32bit RGB data for given Bitmap
476
    void createRGB(const Bitmap& rBitmap)
477
0
    {
478
0
        BitmapScopedReadAccess pReadAccess(rBitmap);
479
0
        const tools::Long nHeight(pReadAccess->Height());
480
0
        const tools::Long nWidth(pReadAccess->Width());
481
0
        mpCairoSurface = cairo_image_surface_create(CAIRO_FORMAT_RGB24, nWidth, nHeight);
482
0
        if (cairo_surface_status(mpCairoSurface) != CAIRO_STATUS_SUCCESS)
483
0
        {
484
0
            SAL_WARN("drawinglayer",
485
0
                     "cairo_image_surface_create failed for: " << nWidth << " x " << nHeight);
486
0
            return;
487
0
        }
488
0
        sal_uInt32 nStride(cairo_format_stride_for_width(CAIRO_FORMAT_RGB24, nWidth));
489
0
        unsigned char* surfaceData(cairo_image_surface_get_data(mpCairoSurface));
490
491
0
        for (tools::Long y(0); y < nHeight; ++y)
492
0
        {
493
0
            unsigned char* pPixelData(surfaceData + (nStride * y));
494
495
0
            for (tools::Long x(0); x < nWidth; ++x)
496
0
            {
497
0
                const BitmapColor aColor(pReadAccess->GetColor(y, x));
498
499
0
                pPixelData[SVP_CAIRO_RED] = aColor.GetRed();
500
0
                pPixelData[SVP_CAIRO_GREEN] = aColor.GetGreen();
501
0
                pPixelData[SVP_CAIRO_BLUE] = aColor.GetBlue();
502
0
                pPixelData[SVP_CAIRO_ALPHA] = 255; // not really needed
503
0
                pPixelData += 4;
504
0
            }
505
0
        }
506
507
0
        cairo_surface_mark_dirty(mpCairoSurface);
508
0
    }
509
510
// #define TEST_RGB16
511
#ifdef TEST_RGB16
512
    // experimental: create 16bit RGB data for given Bitmap
513
    void createRGB16(const Bitmap& rBitmap)
514
    {
515
        BitmapScopedReadAccess pReadAccess(rBitmap);
516
        const tools::Long nHeight(pReadAccess->Height());
517
        const tools::Long nWidth(pReadAccess->Width());
518
        mpCairoSurface = cairo_image_surface_create(CAIRO_FORMAT_RGB16_565, nWidth, nHeight);
519
        if (cairo_surface_status(mpCairoSurface) != CAIRO_STATUS_SUCCESS)
520
        {
521
            SAL_WARN("drawinglayer",
522
                     "cairo_image_surface_create failed for: " << nWidth << " x " << nHeight);
523
            return;
524
        }
525
        sal_uInt32 nStride(cairo_format_stride_for_width(CAIRO_FORMAT_RGB16_565, nWidth));
526
        unsigned char* surfaceData(cairo_image_surface_get_data(mpCairoSurface));
527
528
        for (tools::Long y(0); y < nHeight; ++y)
529
        {
530
            unsigned char* pPixelData(surfaceData + (nStride * y));
531
532
            for (tools::Long x(0); x < nWidth; ++x)
533
            {
534
                const BitmapColor aColor(pReadAccess->GetColor(y, x));
535
                const sal_uInt8 aLeft((aColor.GetBlue() >> 3) | ((aColor.GetGreen() << 3) & 0xe0));
536
                const sal_uInt8 aRight((aColor.GetRed() & 0xf8) | (aColor.GetGreen() >> 5));
537
#ifdef OSL_BIGENDIAN
538
                pPixelData[1] = aRight;
539
                pPixelData[0] = aLeft;
540
#else
541
                pPixelData[0] = aLeft;
542
                pPixelData[1] = aRight;
543
#endif
544
                pPixelData += 2;
545
            }
546
        }
547
548
        cairo_surface_mark_dirty(mpCairoSurface);
549
    }
550
#endif
551
552
public:
553
    CairoSurfaceHelper(const Bitmap& rBitmap)
554
0
        : mpCairoSurface(nullptr)
555
0
        , maDownscaled()
556
0
    {
557
0
        if (rBitmap.HasAlpha())
558
0
            createRGBA(rBitmap);
559
0
        else
560
#ifdef TEST_RGB16
561
            createRGB16(rBitmap);
562
#else
563
0
            createRGB(rBitmap);
564
0
#endif
565
0
    }
566
567
    /* constructor for when we are using the cairo data directly from the underlying SvpSalBitmap */
568
    CairoSurfaceHelper(cairo_surface_t* pCairoSurface)
569
0
        : mpCairoSurface(pCairoSurface)
570
0
        , maDownscaled()
571
0
    {
572
0
    }
573
574
    ~CairoSurfaceHelper()
575
0
    {
576
        // cleanup surface
577
0
        cairo_surface_destroy(mpCairoSurface);
578
579
        // cleanup MipMap surfaces
580
0
        for (auto& candidate : maDownscaled)
581
0
            cairo_surface_destroy(candidate.second);
582
0
    }
583
584
    cairo_surface_t* getCairoSurface(sal_uInt32 nTargetWidth = 0,
585
                                     sal_uInt32 nTargetHeight = 0) const
586
0
    {
587
        // in simple cases just return the single created surface
588
0
        if (bDisableDownScale || nullptr == mpCairoSurface || 0 == nTargetWidth
589
0
            || 0 == nTargetHeight)
590
0
            return mpCairoSurface;
591
592
        // get width/height of original surface
593
0
        const sal_uInt32 nSourceWidth(cairo_image_surface_get_width(mpCairoSurface));
594
0
        const sal_uInt32 nSourceHeight(cairo_image_surface_get_height(mpCairoSurface));
595
596
        // zoomed in, need to stretch at paint, no pre-scale useful
597
0
        if (nTargetWidth >= nSourceWidth || nTargetHeight >= nSourceHeight)
598
0
            return mpCairoSurface;
599
600
        // calculate downscale factor. Only use ONE factor to get the diagonal
601
        // MipMap, NOT the full MipMap field in X/Y for uneven factors in both dimensions
602
0
        sal_uInt32 nFactor(1);
603
0
        sal_uInt32 nW((nSourceWidth + 1) / 2);
604
0
        sal_uInt32 nH((nSourceHeight + 1) / 2);
605
606
0
        while (nW > nTargetWidth && nW > nHalfMDSize && nH > nTargetHeight && nH > nHalfMDSize)
607
0
        {
608
0
            nW = (nW + 1) / 2;
609
0
            nH = (nH + 1) / 2;
610
0
            nFactor *= 2;
611
0
        }
612
613
0
        if (1 == nFactor)
614
0
        {
615
            // original size *is* best binary size, use it
616
0
            return mpCairoSurface;
617
0
        }
618
619
        // go up one scale again
620
0
        nW *= 2;
621
0
        nH *= 2;
622
623
        // bail out if the multiplication for the key would overflow
624
0
        if (nW >= SAL_MAX_UINT32 || nH >= SAL_MAX_UINT32)
625
0
            return mpCairoSurface;
626
627
        // check if we have a downscaled version of required size
628
0
        const sal_uInt64 key((nW * static_cast<sal_uInt64>(SAL_MAX_UINT32)) + nH);
629
0
        auto isHit(maDownscaled.find(key));
630
631
        // found -> return it
632
0
        if (isHit != maDownscaled.end())
633
0
            return isHit->second;
634
635
        // create new surface in the targeted size
636
0
        cairo_surface_t* pSurfaceTarget(cairo_surface_create_similar(
637
0
            mpCairoSurface, cairo_surface_get_content(mpCairoSurface), nW, nH));
638
639
        // made a version to scale self first with direct memory access.
640
        // That worked well, but would've been hard to support
641
        // CAIRO_FORMAT_A1 and similar (including bit shifting), so
642
        // I decided to go with cairo itself - use CAIRO_FILTER_FAST or
643
        // CAIRO_FILTER_GOOD though. Please modify as needed for
644
        // performance/quality
645
0
        cairo_t* cr = cairo_create(pSurfaceTarget);
646
0
        const double fScaleX(static_cast<double>(nW) / static_cast<double>(nSourceWidth));
647
0
        const double fScaleY(static_cast<double>(nH) / static_cast<double>(nSourceHeight));
648
649
0
        cairo_scale(cr, fScaleX, fScaleY);
650
0
        cairo_set_source_surface(cr, mpCairoSurface, 0.0, 0.0);
651
0
        cairo_pattern_set_filter(cairo_get_source(cr), CAIRO_FILTER_GOOD);
652
0
        cairo_paint(cr);
653
0
        cairo_destroy(cr);
654
655
        // NOTE: Took out, until now not really needed
656
        // need to set device_scale for downscale surfaces to get
657
        // them handled correctly
658
        // cairo_surface_set_device_scale(pSurfaceTarget, fScaleX, fScaleY);
659
660
        // add entry to cached entries
661
0
        maDownscaled[key] = pSurfaceTarget;
662
663
0
        return pSurfaceTarget;
664
0
    }
665
666
    bool isTrivial() const
667
0
    {
668
0
        if (nullptr == mpCairoSurface)
669
0
            return true;
670
671
0
        const sal_uInt32 nSourceWidth(cairo_image_surface_get_width(mpCairoSurface));
672
0
        const sal_uInt32 nSourceHeight(cairo_image_surface_get_height(mpCairoSurface));
673
674
0
        return nSourceWidth * nSourceHeight < nMinimalDiscreteSquareSizeToBuffer;
675
0
    }
676
};
677
678
class SystemDependentData_CairoSurface : public basegfx::SystemDependentData
679
{
680
    // the CairoSurface holder
681
    std::shared_ptr<CairoSurfaceHelper> mpCairoSurfaceHelper;
682
683
public:
684
    SystemDependentData_CairoSurface(const Bitmap& rBitmap)
685
0
        : basegfx::SystemDependentData(Application::GetSystemDependentDataManager(),
686
0
                                       basegfx::SDD_Type::SDDType_CairoSurface)
687
0
        , mpCairoSurfaceHelper(std::make_shared<CairoSurfaceHelper>(rBitmap))
688
0
    {
689
0
    }
690
691
    // read access
692
    const std::shared_ptr<CairoSurfaceHelper>& getCairoSurfaceHelper() const
693
0
    {
694
0
        return mpCairoSurfaceHelper;
695
0
    }
696
697
    virtual sal_Int64 estimateUsageInBytes() const override;
698
};
699
700
sal_Int64 SystemDependentData_CairoSurface::estimateUsageInBytes() const
701
0
{
702
0
    sal_Int64 nRetval(0);
703
704
0
    if (mpCairoSurfaceHelper)
705
0
    {
706
0
        cairo_surface_t* pSurface(mpCairoSurfaceHelper->getCairoSurface());
707
0
        const tools::Long nStride(cairo_image_surface_get_stride(pSurface));
708
0
        const tools::Long nHeight(cairo_image_surface_get_height(pSurface));
709
710
0
        nRetval = nStride * nHeight;
711
712
        // if we do downscale, size will grow by 1/4 + 1/16 + 1/32 + ...,
713
        // rough estimation just multiplies by 1.25 .. 1.33, should be good enough
714
        // for estimation of buffer survival time
715
0
        if (!bDisableDownScale)
716
0
        {
717
0
            nRetval = (nRetval * 5) / 4;
718
0
        }
719
0
    }
720
721
0
    return nRetval;
722
0
}
723
724
std::shared_ptr<CairoSurfaceHelper> getOrCreateCairoSurfaceHelper(const Bitmap& rBitmap)
725
0
{
726
    // When using a cairo-backed Bitmap (i.e. SvpSalBitmap), we can avoid a lot of copying,
727
    // which is beneficial for documents with lots of large images.
728
0
    cairo_surface_t* pSurface(static_cast<cairo_surface_t*>(rBitmap.tryToGetCairoSurface()));
729
0
    if (nullptr != pSurface)
730
0
    {
731
0
        return std::make_shared<CairoSurfaceHelper>(pSurface);
732
0
    }
733
734
0
    const basegfx::SystemDependentDataHolder* pHolder(rBitmap.accessSystemDependentDataHolder());
735
0
    std::shared_ptr<SystemDependentData_CairoSurface> pSystemDependentData_CairoSurface;
736
737
0
    if (nullptr != pHolder)
738
0
    {
739
        // try to access SystemDependentDataHolder and buffered data
740
0
        pSystemDependentData_CairoSurface
741
0
            = std::static_pointer_cast<SystemDependentData_CairoSurface>(
742
0
                pHolder->getSystemDependentData(basegfx::SDD_Type::SDDType_CairoSurface));
743
0
    }
744
745
0
    if (!pSystemDependentData_CairoSurface)
746
0
    {
747
        // create new SystemDependentData_CairoSurface
748
0
        pSystemDependentData_CairoSurface
749
0
            = std::make_shared<SystemDependentData_CairoSurface>(rBitmap);
750
751
        // only add if feasible
752
0
        if (nullptr != pHolder
753
0
            && !pSystemDependentData_CairoSurface->getCairoSurfaceHelper()->isTrivial()
754
0
            && pSystemDependentData_CairoSurface->calculateCombinedHoldCyclesInSeconds() > 0)
755
0
        {
756
0
            basegfx::SystemDependentData_SharedPtr r2(pSystemDependentData_CairoSurface);
757
0
            const_cast<basegfx::SystemDependentDataHolder*>(pHolder)
758
0
                ->addOrReplaceSystemDependentData(r2);
759
0
        }
760
0
    }
761
762
0
    return pSystemDependentData_CairoSurface->getCairoSurfaceHelper();
763
0
}
764
765
// This bit-tweaking looping is unpleasant and unfortunate
766
void LuminanceToAlpha(cairo_surface_t* pMask)
767
0
{
768
0
    cairo_surface_flush(pMask);
769
770
0
    const sal_uInt32 nWidth(cairo_image_surface_get_width(pMask));
771
0
    const sal_uInt32 nHeight(cairo_image_surface_get_height(pMask));
772
0
    const sal_uInt32 nStride(cairo_image_surface_get_stride(pMask));
773
774
0
    if (0 == nWidth || 0 == nHeight)
775
0
        return;
776
777
0
    unsigned char* mask_surface_data(cairo_image_surface_get_data(pMask));
778
779
    // change to unsigned 16bit and shifting. This is not much
780
    // faster on modern processors due to nowadays good double/
781
    // float HW, but may also be used on smaller HW (ARM, ...).
782
    // Since source is sal_uInt8 integer using double (see version
783
    // before) is not required numerically either.
784
    // scaling values are now put to a 256 entry lookup for R, G and B
785
    // thus 768 bytes, so no multiplications have to happen. The values
786
    // used to create these are (54+183+18 == 255):
787
    //    sal_uInt16 nR(0.2125 * 256.0); // -> 54.4
788
    //    sal_uInt16 nG(0.7154 * 256.0); // -> 183.1424
789
    //    sal_uInt16 nB(0.0721 * 256.0); // -> 18.4576
790
    // and the short loop (for nR, nG and nB resp.) like:
791
    //    for(unsigned short a(0); a < 256; a++)
792
    //        std::cout << ((a * nR) / 255) << ", ";
793
0
    static constexpr std::array<sal_uInt8, 256> nRArray
794
0
        = { 0,  0,  0,  0,  0,  1,  1,  1,  1,  1,  2,  2,  2,  2,  2,  3,  3,  3,  3,  4,  4,  4,
795
0
            4,  4,  5,  5,  5,  5,  5,  6,  6,  6,  6,  6,  7,  7,  7,  7,  8,  8,  8,  8,  8,  9,
796
0
            9,  9,  9,  9,  10, 10, 10, 10, 11, 11, 11, 11, 11, 12, 12, 12, 12, 12, 13, 13, 13, 13,
797
0
            13, 14, 14, 14, 14, 15, 15, 15, 15, 15, 16, 16, 16, 16, 16, 17, 17, 17, 17, 18, 18, 18,
798
0
            18, 18, 19, 19, 19, 19, 19, 20, 20, 20, 20, 20, 21, 21, 21, 21, 22, 22, 22, 22, 22, 23,
799
0
            23, 23, 23, 23, 24, 24, 24, 24, 24, 25, 25, 25, 25, 26, 26, 26, 26, 26, 27, 27, 27, 27,
800
0
            27, 28, 28, 28, 28, 29, 29, 29, 29, 29, 30, 30, 30, 30, 30, 31, 31, 31, 31, 31, 32, 32,
801
0
            32, 32, 33, 33, 33, 33, 33, 34, 34, 34, 34, 34, 35, 35, 35, 35, 36, 36, 36, 36, 36, 37,
802
0
            37, 37, 37, 37, 38, 38, 38, 38, 38, 39, 39, 39, 39, 40, 40, 40, 40, 40, 41, 41, 41, 41,
803
0
            41, 42, 42, 42, 42, 42, 43, 43, 43, 43, 44, 44, 44, 44, 44, 45, 45, 45, 45, 45, 46, 46,
804
0
            46, 46, 47, 47, 47, 47, 47, 48, 48, 48, 48, 48, 49, 49, 49, 49, 49, 50, 50, 50, 50, 51,
805
0
            51, 51, 51, 51, 52, 52, 52, 52, 52, 53, 53, 53, 53, 54 };
806
0
    static constexpr std::array<sal_uInt8, 256> nGArray
807
0
        = { 0,   0,   1,   2,   2,   3,   4,   5,   5,   6,   7,   7,   8,   9,   10,  10,
808
0
            11,  12,  12,  13,  14,  15,  15,  16,  17,  17,  18,  19,  20,  20,  21,  22,
809
0
            22,  23,  24,  25,  25,  26,  27,  27,  28,  29,  30,  30,  31,  32,  33,  33,
810
0
            34,  35,  35,  36,  37,  38,  38,  39,  40,  40,  41,  42,  43,  43,  44,  45,
811
0
            45,  46,  47,  48,  48,  49,  50,  50,  51,  52,  53,  53,  54,  55,  55,  56,
812
0
            57,  58,  58,  59,  60,  61,  61,  62,  63,  63,  64,  65,  66,  66,  67,  68,
813
0
            68,  69,  70,  71,  71,  72,  73,  73,  74,  75,  76,  76,  77,  78,  78,  79,
814
0
            80,  81,  81,  82,  83,  83,  84,  85,  86,  86,  87,  88,  88,  89,  90,  91,
815
0
            91,  92,  93,  94,  94,  95,  96,  96,  97,  98,  99,  99,  100, 101, 101, 102,
816
0
            103, 104, 104, 105, 106, 106, 107, 108, 109, 109, 110, 111, 111, 112, 113, 114,
817
0
            114, 115, 116, 116, 117, 118, 119, 119, 120, 121, 122, 122, 123, 124, 124, 125,
818
0
            126, 127, 127, 128, 129, 129, 130, 131, 132, 132, 133, 134, 134, 135, 136, 137,
819
0
            137, 138, 139, 139, 140, 141, 142, 142, 143, 144, 144, 145, 146, 147, 147, 148,
820
0
            149, 149, 150, 151, 152, 152, 153, 154, 155, 155, 156, 157, 157, 158, 159, 160,
821
0
            160, 161, 162, 162, 163, 164, 165, 165, 166, 167, 167, 168, 169, 170, 170, 171,
822
0
            172, 172, 173, 174, 175, 175, 176, 177, 177, 178, 179, 180, 180, 181, 182, 183 };
823
0
    static constexpr std::array<sal_uInt8, 256> nBArray
824
0
        = { 0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  1,  1,  1,  1,  1,  1,  1,
825
0
            1,  1,  1,  1,  1,  1,  1,  2,  2,  2,  2,  2,  2,  2,  2,  2,  2,  2,  2,  2,  2,  3,
826
0
            3,  3,  3,  3,  3,  3,  3,  3,  3,  3,  3,  3,  3,  4,  4,  4,  4,  4,  4,  4,  4,  4,
827
0
            4,  4,  4,  4,  4,  5,  5,  5,  5,  5,  5,  5,  5,  5,  5,  5,  5,  5,  5,  6,  6,  6,
828
0
            6,  6,  6,  6,  6,  6,  6,  6,  6,  6,  6,  6,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,
829
0
            7,  7,  7,  7,  8,  8,  8,  8,  8,  8,  8,  8,  8,  8,  8,  8,  8,  8,  9,  9,  9,  9,
830
0
            9,  9,  9,  9,  9,  9,  9,  9,  9,  9,  10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10,
831
0
            10, 10, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 12, 12, 12, 12, 12, 12,
832
0
            12, 12, 12, 12, 12, 12, 12, 12, 12, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13,
833
0
            13, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 15, 15, 15, 15, 15, 15, 15,
834
0
            15, 15, 15, 15, 15, 15, 15, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 17,
835
0
            17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 18 };
836
837
0
    for (sal_uInt32 y(0); y < nHeight; ++y)
838
0
    {
839
0
        unsigned char* pMaskPixelData = mask_surface_data + (nStride * y);
840
841
0
        for (sal_uInt32 x(0); x < nWidth; ++x)
842
0
        {
843
            // do not forget that we have pre-multiplied alpha
844
0
            sal_uInt8 nAlpha(pMaskPixelData[SVP_CAIRO_ALPHA]);
845
846
0
            if (0 != nAlpha)
847
0
            {
848
                // get Luminance in range [0..255]
849
0
                const sal_uInt8 nLum(nRArray[pMaskPixelData[SVP_CAIRO_RED]]
850
0
                                     + nGArray[pMaskPixelData[SVP_CAIRO_GREEN]]
851
0
                                     + nBArray[pMaskPixelData[SVP_CAIRO_BLUE]]);
852
853
0
                if (255 != nAlpha)
854
                    // remove pre-multiplied alpha (use existing VCL tooling)
855
0
                    nAlpha = vcl::bitmap::unpremultiply(nLum, nAlpha);
856
0
                else
857
                    // already what we need
858
0
                    nAlpha = nLum;
859
860
0
                pMaskPixelData[SVP_CAIRO_ALPHA] = 255 - nAlpha;
861
0
            }
862
863
0
            pMaskPixelData += 4;
864
0
        }
865
0
    }
866
867
0
    cairo_surface_mark_dirty(pMask);
868
0
}
869
870
basegfx::B2DRange getDiscreteViewRange(cairo_t* pRT)
871
120
{
872
120
    double clip_x1, clip_x2, clip_y1, clip_y2;
873
120
    cairo_save(pRT);
874
120
    cairo_identity_matrix(pRT);
875
120
    cairo_clip_extents(pRT, &clip_x1, &clip_y1, &clip_x2, &clip_y2);
876
120
    cairo_restore(pRT);
877
878
120
    return basegfx::B2DRange(basegfx::B2DPoint(clip_x1, clip_y1),
879
120
                             basegfx::B2DPoint(clip_x2, clip_y2));
880
120
}
881
882
bool checkCoordinateLimitWorkaroundNeededForUsedCairo()
883
3
{
884
    // setup surface and render context
885
3
    cairo_surface_t* pSurface(cairo_image_surface_create(CAIRO_FORMAT_RGB24, 8, 8));
886
3
    if (!pSurface)
887
0
    {
888
0
        SAL_INFO(
889
0
            "drawinglayer",
890
0
            "checkCoordinateLimitWorkaroundNeededForUsedCairo: got no surface -> be pessimistic");
891
0
        return true;
892
0
    }
893
894
3
    cairo_t* pRender(cairo_create(pSurface));
895
3
    if (!pRender)
896
0
    {
897
0
        SAL_INFO(
898
0
            "drawinglayer",
899
0
            "checkCoordinateLimitWorkaroundNeededForUsedCairo: got no render -> be pessimistic");
900
0
        cairo_surface_destroy(pSurface);
901
0
        return true;
902
0
    }
903
904
    // set basic values
905
3
    cairo_set_antialias(pRender, CAIRO_ANTIALIAS_NONE);
906
3
    cairo_set_fill_rule(pRender, CAIRO_FILL_RULE_EVEN_ODD);
907
3
    cairo_set_operator(pRender, CAIRO_OPERATOR_OVER);
908
3
    cairo_set_source_rgb(pRender, 1.0, 0.0, 0.0);
909
910
    // create a to-be rendered area centered at the fNumCairoMax
911
    // spot and 8x8 discrete units in size
912
3
    constexpr double fNumCairoMax(1 << 23);
913
3
    const basegfx::B2DPoint aCenter(fNumCairoMax, fNumCairoMax);
914
3
    const basegfx::B2DPoint aOffset(4, 4);
915
3
    const basegfx::B2DRange aObject(aCenter - aOffset, aCenter + aOffset);
916
917
    // create transformation to render that to an area with
918
    // range(0, 0, 8, 8) and set as transformation
919
3
    const basegfx::B2DHomMatrix aObjectToView(basegfx::utils::createSourceRangeTargetRangeTransform(
920
3
        aObject, basegfx::B2DRange(0, 0, 8, 8)));
921
3
    cairo_matrix_t aMatrix;
922
3
    cairo_matrix_init(&aMatrix, aObjectToView.a(), aObjectToView.b(), aObjectToView.c(),
923
3
                      aObjectToView.d(), aObjectToView.e(), aObjectToView.f());
924
3
    cairo_set_matrix(pRender, &aMatrix);
925
926
    // get/create the path for an object exactly filling that area
927
3
    cairo_new_path(pRender);
928
3
    basegfx::B2DPolyPolygon aObjectPolygon(basegfx::utils::createPolygonFromRect(aObject));
929
3
    CairoPathHelper aPathHelper(aObjectPolygon);
930
3
    cairo_append_path(pRender, aPathHelper.getCairoPath());
931
932
    // render it and flush since we want to immediately inspect result
933
3
    cairo_fill(pRender);
934
3
    cairo_surface_flush(pSurface);
935
936
    // get access to pixel data
937
3
    const sal_uInt32 nStride(cairo_image_surface_get_stride(pSurface));
938
3
    sal_uInt8* pStartPixelData(cairo_image_surface_get_data(pSurface));
939
940
    // extract red value for pixels at (1,1) and (7,7)
941
3
    sal_uInt8 aRedAt_1_1((pStartPixelData + (nStride * 1) + 1)[SVP_CAIRO_RED]);
942
3
    sal_uInt8 aRedAt_6_6((pStartPixelData + (nStride * 6) + 6)[SVP_CAIRO_RED]);
943
944
    // cleanup
945
3
    cairo_destroy(pRender);
946
3
    cairo_surface_destroy(pSurface);
947
948
    // if cairo works or has no 24.8 internal format all pixels
949
    // have to be red (255), thus workaround is needed if !=
950
3
    auto const needed = aRedAt_1_1 != aRedAt_6_6;
951
3
    SAL_INFO("drawinglayer", "checkCoordinateLimitWorkaroundNeededForUsedCairo: " << needed);
952
3
    return needed;
953
3
}
954
}
955
956
namespace drawinglayer::processor2d
957
{
958
void CairoPixelProcessor2D::onViewInformation2DChanged()
959
454
{
960
    // apply AntiAlias information to target device
961
454
    cairo_set_antialias(mpRT, getViewInformation2D().getUseAntiAliasing() ? CAIRO_ANTIALIAS_DEFAULT
962
454
                                                                          : CAIRO_ANTIALIAS_NONE);
963
454
}
964
965
CairoPixelProcessor2D::CairoPixelProcessor2D(
966
    const basegfx::BColorModifierStack& rBColorModifierStack,
967
    const geometry::ViewInformation2D& rViewInformation, cairo_surface_t* pTarget)
968
0
    : BaseProcessor2D(rViewInformation)
969
0
    , mpTargetOutputDevice(nullptr)
970
0
    , maBColorModifierStack(rBColorModifierStack)
971
0
    , mpOwnedSurface(nullptr)
972
0
    , mpRT(nullptr)
973
    , mbRenderSimpleTextDirect(
974
0
          officecfg::Office::Common::Drawinglayer::RenderSimpleTextDirect::get())
975
    , mbRenderDecoratedTextDirect(
976
0
          officecfg::Office::Common::Drawinglayer::RenderDecoratedTextDirect::get())
977
0
    , mnClipRecursionCount(0)
978
0
    , mbCairoCoordinateLimitWorkaroundActive(false)
979
0
{
980
    // no target, nothing to initialize
981
0
    if (nullptr == pTarget)
982
0
        return;
983
984
    // create RenderTarget for full target
985
0
    mpRT = cairo_create(pTarget);
986
987
0
    if (nullptr == mpRT)
988
        // error, invalid
989
0
        return;
990
991
    // initialize some basic used values/settings
992
0
    cairo_set_antialias(mpRT, rViewInformation.getUseAntiAliasing() ? CAIRO_ANTIALIAS_DEFAULT
993
0
                                                                    : CAIRO_ANTIALIAS_NONE);
994
0
    cairo_set_fill_rule(mpRT, CAIRO_FILL_RULE_EVEN_ODD);
995
0
    cairo_set_operator(mpRT, CAIRO_OPERATOR_OVER);
996
997
    // evaluate if CairoCoordinateLimitWorkaround is needed
998
0
    evaluateCairoCoordinateLimitWorkaround();
999
0
}
1000
1001
CairoPixelProcessor2D::CairoPixelProcessor2D(const geometry::ViewInformation2D& rViewInformation,
1002
                                             tools::Long nWidthPixel, tools::Long nHeightPixel,
1003
                                             bool bUseRGBA)
1004
114
    : BaseProcessor2D(rViewInformation)
1005
114
    , mpTargetOutputDevice(nullptr)
1006
114
    , maBColorModifierStack()
1007
114
    , mpOwnedSurface(nullptr)
1008
114
    , mpRT(nullptr)
1009
    , mbRenderSimpleTextDirect(
1010
114
          officecfg::Office::Common::Drawinglayer::RenderSimpleTextDirect::get())
1011
    , mbRenderDecoratedTextDirect(
1012
114
          officecfg::Office::Common::Drawinglayer::RenderDecoratedTextDirect::get())
1013
114
    , mnClipRecursionCount(0)
1014
114
    , mbCairoCoordinateLimitWorkaroundActive(false)
1015
114
{
1016
114
    if (nWidthPixel <= 0 || nHeightPixel <= 0)
1017
        // no size, invalid
1018
0
        return;
1019
1020
114
    mpOwnedSurface = cairo_image_surface_create(bUseRGBA ? CAIRO_FORMAT_ARGB32 : CAIRO_FORMAT_RGB24,
1021
114
                                                nWidthPixel, nHeightPixel);
1022
1023
114
    if (nullptr == mpOwnedSurface)
1024
        // error, invalid
1025
0
        return;
1026
1027
    // create RenderTarget for full target
1028
114
    mpRT = cairo_create(mpOwnedSurface);
1029
1030
114
    if (nullptr == mpRT)
1031
        // error, invalid
1032
0
        return;
1033
1034
    // initialize some basic used values/settings
1035
114
    cairo_set_antialias(mpRT, rViewInformation.getUseAntiAliasing() ? CAIRO_ANTIALIAS_DEFAULT
1036
114
                                                                    : CAIRO_ANTIALIAS_NONE);
1037
114
    cairo_set_fill_rule(mpRT, CAIRO_FILL_RULE_EVEN_ODD);
1038
114
    cairo_set_operator(mpRT, CAIRO_OPERATOR_OVER);
1039
1040
    // evaluate if CairoCoordinateLimitWorkaround is needed
1041
114
    evaluateCairoCoordinateLimitWorkaround();
1042
114
}
1043
1044
CairoPixelProcessor2D::CairoPixelProcessor2D(OutputDevice& rOutputDevice,
1045
                                             const geometry::ViewInformation2D& rViewInformation)
1046
6
    : BaseProcessor2D(rViewInformation)
1047
6
    , mpTargetOutputDevice(&rOutputDevice)
1048
6
    , maBColorModifierStack()
1049
6
    , mpOwnedSurface(nullptr)
1050
6
    , mpRT(nullptr)
1051
    , mbRenderSimpleTextDirect(
1052
6
          officecfg::Office::Common::Drawinglayer::RenderSimpleTextDirect::get())
1053
    , mbRenderDecoratedTextDirect(
1054
6
          officecfg::Office::Common::Drawinglayer::RenderDecoratedTextDirect::get())
1055
6
    , mnClipRecursionCount(0)
1056
6
    , mbCairoCoordinateLimitWorkaroundActive(false)
1057
6
{
1058
6
    SystemGraphicsData aData(mpTargetOutputDevice->GetSystemGfxData());
1059
6
    cairo_surface_t* pTarget(static_cast<cairo_surface_t*>(aData.pSurface));
1060
1061
    // no target, nothing to initialize
1062
6
    if (nullptr == pTarget)
1063
0
    {
1064
0
        mpTargetOutputDevice = nullptr;
1065
0
        return;
1066
0
    }
1067
1068
    // get evtl. offsets if OutputDevice is e.g. a OUTDEV_WINDOW
1069
    // to evaluate if initial clip is needed
1070
6
    const tools::Long nOffsetPixelX(mpTargetOutputDevice->GetOutOffXPixel());
1071
6
    const tools::Long nOffsetPixelY(mpTargetOutputDevice->GetOutOffYPixel());
1072
6
    const tools::Long nWidthPixel(mpTargetOutputDevice->GetOutputWidthPixel());
1073
6
    const tools::Long nHeightPixel(mpTargetOutputDevice->GetOutputHeightPixel());
1074
6
    bool bClipNeeded(false);
1075
1076
6
    if (0 != nOffsetPixelX || 0 != nOffsetPixelY || 0 != nWidthPixel || 0 != nHeightPixel)
1077
6
    {
1078
6
        if (0 != nOffsetPixelX || 0 != nOffsetPixelY)
1079
6
        {
1080
            // if offset is used we need initial clip
1081
6
            bClipNeeded = true;
1082
6
        }
1083
0
        else
1084
0
        {
1085
            // no offset used, compare to real pixel size
1086
0
            const tools::Long nRealPixelWidth(cairo_image_surface_get_width(pTarget));
1087
0
            const tools::Long nRealPixelHeight(cairo_image_surface_get_height(pTarget));
1088
1089
0
            if (nRealPixelWidth != nWidthPixel || nRealPixelHeight != nHeightPixel)
1090
0
            {
1091
                // if size differs we need initial clip
1092
0
                bClipNeeded = true;
1093
0
            }
1094
0
        }
1095
6
    }
1096
1097
6
    if (bClipNeeded)
1098
6
    {
1099
        // Make use of the possibility to add an initial clip relative
1100
        // to the 'real' pixel dimensions of the target surface. This is e.g.
1101
        // needed here due to the existence of 'virtual' target surfaces that
1102
        // internally use an offset and limited pixel size, mainly used for
1103
        // UI elements.
1104
        // let the CairoPixelProcessor2D do this, it has internal,
1105
        // system-specific possibilities to do that in an elegant and
1106
        // efficient way (using cairo_surface_create_for_rectangle).
1107
6
        mpOwnedSurface = cairo_surface_create_for_rectangle(pTarget, nOffsetPixelX, nOffsetPixelY,
1108
6
                                                            nWidthPixel, nHeightPixel);
1109
1110
6
        if (nullptr == mpOwnedSurface)
1111
0
        {
1112
            // error, invalid
1113
0
            mpTargetOutputDevice = nullptr;
1114
0
            return;
1115
0
        }
1116
1117
6
        mpRT = cairo_create(mpOwnedSurface);
1118
6
    }
1119
0
    else
1120
0
    {
1121
        // create RenderTarget for full target
1122
0
        mpRT = cairo_create(pTarget);
1123
0
    }
1124
1125
6
    if (nullptr == mpRT)
1126
0
    {
1127
        // error, invalid
1128
0
        mpTargetOutputDevice = nullptr;
1129
0
        return;
1130
0
    }
1131
1132
    // initialize some basic used values/settings
1133
6
    cairo_set_antialias(mpRT, rViewInformation.getUseAntiAliasing() ? CAIRO_ANTIALIAS_DEFAULT
1134
6
                                                                    : CAIRO_ANTIALIAS_NONE);
1135
6
    cairo_set_fill_rule(mpRT, CAIRO_FILL_RULE_EVEN_ODD);
1136
6
    cairo_set_operator(mpRT, CAIRO_OPERATOR_OVER);
1137
1138
    // prepare output directly to pixels
1139
6
    mpTargetOutputDevice->Push(vcl::PushFlags::MAPMODE);
1140
6
    mpTargetOutputDevice->SetMapMode();
1141
1142
    // evaluate if CairoCoordinateLimitWorkaround is needed
1143
6
    evaluateCairoCoordinateLimitWorkaround();
1144
6
}
1145
1146
CairoPixelProcessor2D::~CairoPixelProcessor2D()
1147
120
{
1148
120
    if (nullptr != mpTargetOutputDevice) // restore MapMode
1149
6
        mpTargetOutputDevice->Pop();
1150
120
    if (nullptr != mpRT)
1151
120
        cairo_destroy(mpRT);
1152
120
    if (nullptr != mpOwnedSurface)
1153
120
        cairo_surface_destroy(mpOwnedSurface);
1154
120
}
1155
1156
Bitmap CairoPixelProcessor2D::extractBitmap() const
1157
114
{
1158
    // default is empty Bitmap
1159
114
    Bitmap aRetval;
1160
1161
114
    if (nullptr == mpRT)
1162
        // no RenderContext, not valid
1163
0
        return aRetval;
1164
1165
114
    cairo_surface_t* pSource(cairo_get_target(mpRT));
1166
114
    if (nullptr == pSource)
1167
        // no surface, not valid
1168
0
        return aRetval;
1169
1170
    // check pixel sizes
1171
114
    const sal_uInt32 nWidth(cairo_image_surface_get_width(pSource));
1172
114
    const sal_uInt32 nHeight(cairo_image_surface_get_height(pSource));
1173
114
    if (0 == nWidth || 0 == nHeight)
1174
        // no content, not valid
1175
0
        return aRetval;
1176
1177
    // check format
1178
114
    const cairo_format_t aFormat(cairo_image_surface_get_format(pSource));
1179
114
    if (CAIRO_FORMAT_ARGB32 != aFormat && CAIRO_FORMAT_RGB24 != aFormat)
1180
        // we for now only support ARGB32 and RGB24, format not supported, not valid
1181
0
        return aRetval;
1182
1183
    // ensure surface read access, wer need CAIRO_SURFACE_TYPE_IMAGE
1184
114
    cairo_surface_t* pReadSource(pSource);
1185
1186
114
    if (CAIRO_SURFACE_TYPE_IMAGE != cairo_surface_get_type(pReadSource))
1187
0
    {
1188
        // create mapping for read access to source
1189
0
        pReadSource = cairo_surface_map_to_image(pReadSource, nullptr);
1190
0
    }
1191
1192
    // prepare VCL/Bitmap stuff
1193
114
    const Size aBitmapSize(nWidth, nHeight);
1194
114
    const bool bHasAlpha(CAIRO_FORMAT_ARGB32 == aFormat);
1195
114
    Bitmap aBitmap(aBitmapSize, bHasAlpha ? vcl::PixelFormat::N32_BPP : vcl::PixelFormat::N24_BPP);
1196
114
    BitmapWriteAccess aAccess(aBitmap);
1197
114
    if (!aAccess)
1198
0
    {
1199
0
        SAL_WARN("drawinglayer", "Could not create image, likely too large, size= " << aBitmapSize);
1200
0
        return aRetval;
1201
0
    }
1202
1203
    // prepare cairo stuff
1204
114
    const sal_uInt32 nStride(cairo_image_surface_get_stride(pReadSource));
1205
114
    unsigned char* pStartPixelData(cairo_image_surface_get_data(pReadSource));
1206
1207
    // separate loops for bHasAlpha so that we have *no* branch in the
1208
    // loops itself
1209
114
    if (bHasAlpha)
1210
114
    {
1211
8.89k
        for (sal_uInt32 y(0); y < nHeight; ++y)
1212
8.78k
        {
1213
            // prepare scanline
1214
8.78k
            unsigned char* pPixelData(pStartPixelData + (nStride * y));
1215
8.78k
            Scanline pWriteRGBA = aAccess.GetScanline(y);
1216
1217
180k
            for (sal_uInt32 x(0); x < nWidth; ++x)
1218
171k
            {
1219
                // RGBA: Do not forget: it's pre-multiplied
1220
171k
                sal_uInt8 nAlpha(pPixelData[SVP_CAIRO_ALPHA]);
1221
171k
                aAccess.SetPixelOnData(
1222
171k
                    pWriteRGBA, x,
1223
171k
                    BitmapColor(
1224
171k
                        ColorAlpha, vcl::bitmap::unpremultiply(pPixelData[SVP_CAIRO_RED], nAlpha),
1225
171k
                        vcl::bitmap::unpremultiply(pPixelData[SVP_CAIRO_GREEN], nAlpha),
1226
171k
                        vcl::bitmap::unpremultiply(pPixelData[SVP_CAIRO_BLUE], nAlpha), nAlpha));
1227
171k
                pPixelData += 4;
1228
171k
            }
1229
8.78k
        }
1230
114
    }
1231
0
    else
1232
0
    {
1233
0
        for (sal_uInt32 y(0); y < nHeight; ++y)
1234
0
        {
1235
            // prepare scanline
1236
0
            unsigned char* pPixelData(pStartPixelData + (nStride * y));
1237
0
            Scanline pWriteRGB = aAccess.GetScanline(y);
1238
1239
0
            for (sal_uInt32 x(0); x < nWidth; ++x)
1240
0
            {
1241
0
                aAccess.SetPixelOnData(pWriteRGB, x,
1242
0
                                       BitmapColor(pPixelData[SVP_CAIRO_RED],
1243
0
                                                   pPixelData[SVP_CAIRO_GREEN],
1244
0
                                                   pPixelData[SVP_CAIRO_BLUE]));
1245
0
                pPixelData += 4;
1246
0
            }
1247
0
        }
1248
0
    }
1249
1250
    // construct and return Bitmap
1251
114
    aRetval = std::move(aBitmap);
1252
1253
114
    if (pReadSource != pSource)
1254
0
    {
1255
        // cleanup mapping for read/write access to source
1256
0
        cairo_surface_unmap_image(pSource, pReadSource);
1257
0
    }
1258
1259
114
    return aRetval;
1260
114
}
1261
1262
void CairoPixelProcessor2D::processBitmapPrimitive2D(
1263
    const primitive2d::BitmapPrimitive2D& rBitmapCandidate)
1264
0
{
1265
0
    constexpr DrawModeFlags BITMAP(DrawModeFlags::BlackBitmap | DrawModeFlags::WhiteBitmap
1266
0
                                   | DrawModeFlags::GrayBitmap);
1267
0
    const DrawModeFlags aDrawModeFlags(getViewInformation2D().getDrawModeFlags());
1268
0
    const bool bDrawModeFlagsUsed(aDrawModeFlags & BITMAP);
1269
1270
0
    if (bDrawModeFlagsUsed)
1271
0
    {
1272
        // if DrawModeFlags for Bitmap are used, encapsulate with
1273
        // corresponding BColorModifier
1274
0
        if (aDrawModeFlags & DrawModeFlags::BlackBitmap)
1275
0
        {
1276
0
            const basegfx::BColorModifierSharedPtr aBColorModifier(
1277
0
                std::make_shared<basegfx::BColorModifier_replace>(basegfx::BColor(0, 0, 0)));
1278
0
            maBColorModifierStack.push(aBColorModifier);
1279
0
        }
1280
0
        else if (aDrawModeFlags & DrawModeFlags::WhiteBitmap)
1281
0
        {
1282
0
            const basegfx::BColorModifierSharedPtr aBColorModifier(
1283
0
                std::make_shared<basegfx::BColorModifier_replace>(basegfx::BColor(1, 1, 1)));
1284
0
            maBColorModifierStack.push(aBColorModifier);
1285
0
        }
1286
0
        else // DrawModeFlags::GrayBitmap
1287
0
        {
1288
0
            const basegfx::BColorModifierSharedPtr aBColorModifier(
1289
0
                std::make_shared<basegfx::BColorModifier_gray>());
1290
0
            maBColorModifierStack.push(aBColorModifier);
1291
0
        }
1292
0
    }
1293
1294
0
    paintBitmapAlpha(rBitmapCandidate.getBitmap(), rBitmapCandidate.getTransform());
1295
1296
0
    if (bDrawModeFlagsUsed)
1297
0
        maBColorModifierStack.pop();
1298
0
}
1299
1300
void CairoPixelProcessor2D::paintBitmapAlpha(const Bitmap& rBitmap,
1301
                                             const basegfx::B2DHomMatrix& rTransform,
1302
                                             double fTransparency)
1303
0
{
1304
    // transparency invalid or completely transparent, done
1305
0
    if (fTransparency < 0.0 || fTransparency >= 1.0)
1306
0
    {
1307
0
        return;
1308
0
    }
1309
1310
    // check if graphic content is inside discrete local ViewPort
1311
0
    const basegfx::B2DRange& rDiscreteViewPort(getViewInformation2D().getDiscreteViewport());
1312
0
    const basegfx::B2DHomMatrix aLocalTransform(
1313
0
        getViewInformation2D().getObjectToViewTransformation() * rTransform);
1314
1315
0
    if (!rDiscreteViewPort.isEmpty())
1316
0
    {
1317
0
        basegfx::B2DRange aUnitRange(0.0, 0.0, 1.0, 1.0);
1318
1319
0
        aUnitRange.transform(aLocalTransform);
1320
1321
0
        if (!aUnitRange.overlaps(rDiscreteViewPort))
1322
0
        {
1323
            // content is outside discrete local ViewPort
1324
0
            return;
1325
0
        }
1326
0
    }
1327
1328
0
    Bitmap aBitmap(rBitmap);
1329
1330
0
    if (aBitmap.IsEmpty() || aBitmap.GetSizePixel().IsEmpty())
1331
0
    {
1332
        // no pixel data, done
1333
0
        return;
1334
0
    }
1335
1336
    // work with dimensions in discrete target pixels to use evtl. MipMap pre-scale
1337
0
    const tools::Long nDestWidth((aLocalTransform * basegfx::B2DVector(1.0, 0.0)).getLength());
1338
0
    const tools::Long nDestHeight((aLocalTransform * basegfx::B2DVector(0.0, 1.0)).getLength());
1339
1340
    // tdf#167831 check for output size, may have zero discrete dimension in X and/or Y
1341
0
    if (0 == nDestWidth || 0 == nDestHeight)
1342
0
    {
1343
        // it has and is thus invisible
1344
0
        return;
1345
0
    }
1346
1347
0
    if (maBColorModifierStack.count())
1348
0
    {
1349
        // need to apply ColorModifier to Bitmap data
1350
0
        aBitmap = aBitmap.Modify(maBColorModifierStack);
1351
1352
0
        if (aBitmap.IsEmpty())
1353
0
        {
1354
            // color gets completely replaced, get it
1355
0
            const basegfx::BColor aModifiedColor(
1356
0
                maBColorModifierStack.getModifiedColor(basegfx::BColor()));
1357
1358
            // use unit geometry as fallback object geometry. Do *not*
1359
            // transform, the below used method will use the already
1360
            // correctly initialized local ViewInformation
1361
0
            const basegfx::B2DPolygon& aPolygon(basegfx::utils::createUnitPolygon());
1362
1363
            // draw directly, done
1364
0
            paintPolyPolygonRGBA(basegfx::B2DPolyPolygon(aPolygon), aModifiedColor, fTransparency);
1365
1366
0
            return;
1367
0
        }
1368
0
    }
1369
1370
    // access or create cairo bitmap data
1371
0
    std::shared_ptr<CairoSurfaceHelper> aCairoSurfaceHelper(getOrCreateCairoSurfaceHelper(aBitmap));
1372
0
    if (!aCairoSurfaceHelper)
1373
0
    {
1374
0
        SAL_WARN("drawinglayer", "SDPRCairo: No SurfaceHelper from Bitmap (!)");
1375
0
        return;
1376
0
    }
1377
1378
0
    cairo_surface_t* pTarget(aCairoSurfaceHelper->getCairoSurface(nDestWidth, nDestHeight));
1379
0
    if (nullptr == pTarget)
1380
0
    {
1381
0
        SAL_WARN("drawinglayer", "SDPRCairo: No CairoSurface from Bitmap SurfaceHelper (!)");
1382
0
        return;
1383
0
    }
1384
1385
0
    cairo_save(mpRT);
1386
1387
    // set linear transformation - no fAAOffset for bitmap data
1388
0
    cairo_matrix_t aMatrix;
1389
0
    cairo_matrix_init(&aMatrix, aLocalTransform.a(), aLocalTransform.b(), aLocalTransform.c(),
1390
0
                      aLocalTransform.d(), aLocalTransform.e(), aLocalTransform.f());
1391
0
    cairo_set_matrix(mpRT, &aMatrix);
1392
1393
0
    static bool bRenderTransformationBounds(false);
1394
0
    if (bRenderTransformationBounds)
1395
0
    {
1396
0
        cairo_set_source_rgba(mpRT, 1, 0, 0, 0.8);
1397
0
        impl_cairo_set_hairline(mpRT, getViewInformation2D(),
1398
0
                                isCairoCoordinateLimitWorkaroundActive());
1399
0
        cairo_rectangle(mpRT, 0, 0, 1, 1);
1400
0
        cairo_stroke(mpRT);
1401
0
    }
1402
1403
0
    cairo_set_source_surface(mpRT, pTarget, 0, 0);
1404
1405
    // get the pattern created by cairo_set_source_surface and
1406
    // it's transformation
1407
0
    cairo_pattern_t* sourcepattern = cairo_get_source(mpRT);
1408
0
    cairo_pattern_get_matrix(sourcepattern, &aMatrix);
1409
1410
    // RGBA sources overlap the unit geometry range, slightly,
1411
    // to see that activate bRenderTransformationBounds and
1412
    // insert a ARGB image, zoom to the borders. Seems to be half
1413
    // a pixel. Very good to demonstrate: 8x1 pixel, some
1414
    // transparent.
1415
    // Also errors with images 1 pixel wide/high, e.g. insert
1416
    // RGBA 8x1, 1x8 to see (and deactivate fix below). It also
1417
    // depends on the used filter, see comment below at
1418
    // cairo_pattern_set_filter. Found also errors with more
1419
    // than one pixel, so cannot use as criteria.
1420
    // This effect is also visible in the left/right/bottom/top
1421
    // page shadows, these DO use 8x1/1x8 images which led me to
1422
    // that problem. I double-checked that these *are* correctly
1423
    // defined, that is not the problem.
1424
    // Decided now to use clipping always. That again is
1425
    // simple (we are in unit coordinates)
1426
0
    cairo_rectangle(mpRT, 0, 0, 1, 1);
1427
0
    cairo_clip(mpRT);
1428
0
    cairo_matrix_scale(&aMatrix, cairo_image_surface_get_width(pTarget),
1429
0
                       cairo_image_surface_get_height(pTarget));
1430
1431
    // The alternative wpuld be: resize/scale it SLIGHTLY to force
1432
    // that half pixel overlap to be inside the unit range.
1433
    // That makes the error disappear, so no clip needed, but
1434
    // SLIGHTLY smaller. Keeping this code if someone might have
1435
    // to finetune this later for reference.
1436
    //
1437
    // cairo_matrix_init_scale(&aMatrix, nWidth + 1, nHeight + 1);
1438
    // cairo_matrix_translate(&aMatrix, -0.5 / (nWidth + 1), -0.5 / (nHeight + 1));
1439
1440
    // The error/effect described above also is connected to the
1441
    // filter used, so I checked the filter modes available
1442
    // in Cairo:
1443
    //
1444
    // CAIRO_FILTER_FAST: okay, small errors, sometimes stretching some pixels
1445
    // CAIRO_FILTER_GOOD: stretching error
1446
    // CAIRO_FILTER_BEST: okay, small errors
1447
    // CAIRO_FILTER_NEAREST: similar to CAIRO_FILTER_FAST
1448
    // CAIRO_FILTER_BILINEAR: similar to CAIRO_FILTER_GOOD
1449
    // CAIRO_FILTER_GAUSSIAN: same as CAIRO_FILTER_GOOD/CAIRO_FILTER_BILINEAR, should
1450
    //   not be used anyways (see docs)
1451
    //
1452
    // CAIRO_FILTER_GOOD seems to be the default anyways, but set it
1453
    // to be on the safe side
1454
0
    cairo_pattern_set_filter(sourcepattern, CAIRO_FILTER_GOOD);
1455
1456
    // also set extend to CAIRO_EXTEND_PAD, else the outside of the
1457
    // bitmap is guessed as COL_BLACK and the filtering would blend
1458
    // against COL_BLACK what might give strange gray lines at borders
1459
    // of white-on-white bitmaps (used e.g. when painting controls).
1460
    // NOTE: CAIRO_EXTEND_REPEAT also works with clipping and might be
1461
    // broader supported by Cairo implementations
1462
0
    cairo_pattern_set_extend(sourcepattern, CAIRO_EXTEND_PAD);
1463
1464
0
    cairo_pattern_set_matrix(sourcepattern, &aMatrix);
1465
1466
    // paint bitmap data, evtl. with additional alpha channel
1467
0
    if (!basegfx::fTools::equalZero(fTransparency))
1468
0
        cairo_paint_with_alpha(mpRT, 1.0 - fTransparency);
1469
0
    else
1470
0
        cairo_paint(mpRT);
1471
1472
0
    cairo_restore(mpRT);
1473
0
}
1474
1475
void CairoPixelProcessor2D::processPointArrayPrimitive2D(
1476
    const primitive2d::PointArrayPrimitive2D& rPointArrayCandidate)
1477
0
{
1478
0
    const std::vector<basegfx::B2DPoint>& rPositions(rPointArrayCandidate.getPositions());
1479
1480
0
    if (rPositions.empty())
1481
0
    {
1482
        // no geometry, done
1483
0
        return;
1484
0
    }
1485
1486
0
    cairo_save(mpRT);
1487
1488
    // determine & set color
1489
0
    basegfx::BColor aPointColor(getLineColor(rPointArrayCandidate.getRGBColor()));
1490
0
    aPointColor = maBColorModifierStack.getModifiedColor(aPointColor);
1491
0
    cairo_set_source_rgb(mpRT, aPointColor.getRed(), aPointColor.getGreen(), aPointColor.getBlue());
1492
1493
    // To really paint a single pixel I found nothing better than
1494
    // switch off AA and draw a pixel-aligned rectangle
1495
0
    const cairo_antialias_t eOldAAMode(cairo_get_antialias(mpRT));
1496
0
    cairo_set_antialias(mpRT, CAIRO_ANTIALIAS_NONE);
1497
1498
0
    for (auto const& pos : rPositions)
1499
0
    {
1500
0
        const basegfx::B2DPoint aDiscretePos(getViewInformation2D().getObjectToViewTransformation()
1501
0
                                             * pos);
1502
0
        const double fX(ceil(aDiscretePos.getX()));
1503
0
        const double fY(ceil(aDiscretePos.getY()));
1504
1505
0
        cairo_rectangle(mpRT, fX, fY, 1, 1);
1506
0
        cairo_fill(mpRT);
1507
0
    }
1508
1509
0
    cairo_set_antialias(mpRT, eOldAAMode);
1510
0
    cairo_restore(mpRT);
1511
0
}
1512
1513
void CairoPixelProcessor2D::processPolygonHairlinePrimitive2D(
1514
    const primitive2d::PolygonHairlinePrimitive2D& rPolygonHairlinePrimitive2D)
1515
122
{
1516
122
    const basegfx::B2DPolygon& rPolygon(rPolygonHairlinePrimitive2D.getB2DPolygon());
1517
1518
122
    if (!rPolygon.count())
1519
0
    {
1520
        // no geometry, done
1521
0
        return;
1522
0
    }
1523
1524
122
    cairo_save(mpRT);
1525
1526
    // determine & set color
1527
122
    basegfx::BColor aHairlineColor(getLineColor(rPolygonHairlinePrimitive2D.getBColor()));
1528
122
    aHairlineColor = maBColorModifierStack.getModifiedColor(aHairlineColor);
1529
122
    cairo_set_source_rgb(mpRT, aHairlineColor.getRed(), aHairlineColor.getGreen(),
1530
122
                         aHairlineColor.getBlue());
1531
1532
    // set LineWidth, use Cairo's special cairo_set_hairline
1533
122
    impl_cairo_set_hairline(mpRT, getViewInformation2D(), isCairoCoordinateLimitWorkaroundActive());
1534
1535
122
    if (isCairoCoordinateLimitWorkaroundActive())
1536
0
    {
1537
        // need to fallback to paint in view coordinates, unfortunately
1538
        // need to transform self (cairo will do it wrong in this coordinate
1539
        // space), so no need to try to buffer
1540
0
        cairo_new_path(mpRT);
1541
0
        basegfx::B2DPolygon aAdaptedPolygon(rPolygon);
1542
0
        const double fAAOffset(getViewInformation2D().getUseAntiAliasing() ? 0.5 : 0.0);
1543
0
        aAdaptedPolygon.transform(basegfx::utils::createTranslateB2DHomMatrix(fAAOffset, fAAOffset)
1544
0
                                  * getViewInformation2D().getObjectToViewTransformation());
1545
0
        cairo_identity_matrix(mpRT);
1546
0
        addB2DPolygonToPathGeometry(mpRT, aAdaptedPolygon);
1547
0
        cairo_stroke(mpRT);
1548
0
    }
1549
122
    else
1550
122
    {
1551
        // set linear transformation. use own, prepared, re-usable
1552
        // ObjectToViewTransformation and PolyPolygon data and let
1553
        // cairo do the transformations
1554
122
        cairo_matrix_t aMatrix;
1555
122
        const basegfx::B2DHomMatrix& rObjectToView(
1556
122
            getViewInformation2D().getObjectToViewTransformation());
1557
122
        const double fAAOffset(getViewInformation2D().getUseAntiAliasing() ? 0.5 : 0.0);
1558
122
        cairo_matrix_init(&aMatrix, rObjectToView.a(), rObjectToView.b(), rObjectToView.c(),
1559
122
                          rObjectToView.d(), rObjectToView.e() + fAAOffset,
1560
122
                          rObjectToView.f() + fAAOffset);
1561
122
        cairo_set_matrix(mpRT, &aMatrix);
1562
1563
        // get PathGeometry & paint it
1564
122
        cairo_new_path(mpRT);
1565
122
        getOrCreatePathGeometry(mpRT, rPolygon, getViewInformation2D(),
1566
122
                                getViewInformation2D().getUseAntiAliasing());
1567
122
        cairo_stroke(mpRT);
1568
122
    }
1569
1570
122
    cairo_restore(mpRT);
1571
122
}
1572
1573
void CairoPixelProcessor2D::processPolyPolygonColorPrimitive2D(
1574
    const primitive2d::PolyPolygonColorPrimitive2D& rPolyPolygonColorPrimitive2D)
1575
405
{
1576
405
    if (getViewInformation2D().getDrawModeFlags() & DrawModeFlags::NoFill)
1577
        // NoFill wanted, done
1578
0
        return;
1579
1580
405
    const basegfx::BColor aFillColor(getFillColor(rPolyPolygonColorPrimitive2D.getBColor()));
1581
405
    paintPolyPolygonRGBA(rPolyPolygonColorPrimitive2D.getB2DPolyPolygon(), aFillColor);
1582
405
}
1583
1584
void CairoPixelProcessor2D::paintPolyPolygonRGBA(const basegfx::B2DPolyPolygon& rPolyPolygon,
1585
                                                 const basegfx::BColor& rColor,
1586
                                                 double fTransparency)
1587
405
{
1588
    // transparency invalid or completely transparent, done
1589
405
    if (fTransparency < 0.0 || fTransparency >= 1.0)
1590
0
    {
1591
0
        return;
1592
0
    }
1593
1594
405
    const sal_uInt32 nCount(rPolyPolygon.count());
1595
1596
405
    if (!nCount)
1597
0
    {
1598
        // no geometry, done
1599
0
        return;
1600
0
    }
1601
1602
405
    cairo_save(mpRT);
1603
1604
    // determine & set color
1605
405
    const basegfx::BColor aFillColor(maBColorModifierStack.getModifiedColor(rColor));
1606
1607
405
    if (!basegfx::fTools::equalZero(fTransparency))
1608
0
        cairo_set_source_rgba(mpRT, aFillColor.getRed(), aFillColor.getGreen(),
1609
0
                              aFillColor.getBlue(), 1.0 - fTransparency);
1610
405
    else
1611
405
        cairo_set_source_rgb(mpRT, aFillColor.getRed(), aFillColor.getGreen(),
1612
405
                             aFillColor.getBlue());
1613
1614
405
    if (isCairoCoordinateLimitWorkaroundActive())
1615
0
    {
1616
        // need to fallback to paint in view coordinates, unfortunately
1617
        // need to transform self (cairo will do it wrong in this coordinate
1618
        // space), so no need to try to buffer
1619
0
        cairo_new_path(mpRT);
1620
0
        basegfx::B2DPolyPolygon aAdaptedPolyPolygon(rPolyPolygon);
1621
0
        aAdaptedPolyPolygon.transform(getViewInformation2D().getObjectToViewTransformation());
1622
0
        cairo_identity_matrix(mpRT);
1623
0
        for (const auto& rPolygon : aAdaptedPolyPolygon)
1624
0
            addB2DPolygonToPathGeometry(mpRT, rPolygon);
1625
0
        cairo_fill(mpRT);
1626
0
    }
1627
405
    else
1628
405
    {
1629
        // set linear transformation. use own, prepared, re-usable
1630
        // ObjectToViewTransformation and PolyPolygon data and let
1631
        // cairo do the transformations
1632
405
        cairo_matrix_t aMatrix;
1633
405
        const basegfx::B2DHomMatrix& rObjectToView(
1634
405
            getViewInformation2D().getObjectToViewTransformation());
1635
405
        cairo_matrix_init(&aMatrix, rObjectToView.a(), rObjectToView.b(), rObjectToView.c(),
1636
405
                          rObjectToView.d(), rObjectToView.e(), rObjectToView.f());
1637
405
        cairo_set_matrix(mpRT, &aMatrix);
1638
1639
        // get PathGeometry & paint it
1640
405
        cairo_new_path(mpRT);
1641
405
        getOrCreateFillGeometry(mpRT, rPolyPolygon);
1642
405
        cairo_fill(mpRT);
1643
405
    }
1644
1645
405
    cairo_restore(mpRT);
1646
405
}
1647
1648
void CairoPixelProcessor2D::processTransparencePrimitive2D(
1649
    const primitive2d::TransparencePrimitive2D& rTransCandidate)
1650
0
{
1651
0
    if (rTransCandidate.getChildren().empty())
1652
0
    {
1653
        // no content, done
1654
0
        return;
1655
0
    }
1656
1657
0
    if (rTransCandidate.getTransparence().empty())
1658
0
    {
1659
        // no mask (so nothing visible), done
1660
0
        return;
1661
0
    }
1662
1663
    // calculate visible range, create only for that range
1664
0
    basegfx::B2DRange aDiscreteRange(
1665
0
        rTransCandidate.getChildren().getB2DRange(getViewInformation2D()));
1666
0
    aDiscreteRange.transform(getViewInformation2D().getObjectToViewTransformation());
1667
0
    basegfx::B2DRange aVisibleRange(aDiscreteRange);
1668
0
    aVisibleRange.intersect(getDiscreteViewRange(mpRT));
1669
1670
0
    if (aVisibleRange.isEmpty())
1671
0
    {
1672
        // not visible, done
1673
0
        return;
1674
0
    }
1675
1676
0
    cairo_save(mpRT);
1677
1678
    // tdf#166734 need to expand to full pixels due to pre-rendering
1679
    // will use discrete pixels/top-left position
1680
0
    aVisibleRange.expand(
1681
0
        basegfx::B2DPoint(floor(aVisibleRange.getMinX()), floor(aVisibleRange.getMinY())));
1682
0
    aVisibleRange.expand(
1683
0
        basegfx::B2DPoint(ceil(aVisibleRange.getMaxX()), ceil(aVisibleRange.getMaxY())));
1684
1685
    // create embedding transformation for sub-surface
1686
0
    const basegfx::B2DHomMatrix aEmbedTransform(basegfx::utils::createTranslateB2DHomMatrix(
1687
0
        -aVisibleRange.getMinX(), -aVisibleRange.getMinY()));
1688
0
    geometry::ViewInformation2D aViewInformation2D(getViewInformation2D());
1689
0
    aViewInformation2D.setViewTransformation(aEmbedTransform
1690
0
                                             * getViewInformation2D().getViewTransformation());
1691
1692
    // draw mask to temporary surface
1693
0
    cairo_surface_t* pTarget(cairo_get_target(mpRT));
1694
0
    const double fContainedWidth(aVisibleRange.getWidth());
1695
0
    const double fContainedHeight(aVisibleRange.getHeight());
1696
0
    cairo_surface_t* pMask(cairo_surface_create_similar_image(pTarget, CAIRO_FORMAT_ARGB32,
1697
0
                                                              fContainedWidth, fContainedHeight));
1698
0
    CairoPixelProcessor2D aMaskRenderer(getBColorModifierStack(), aViewInformation2D, pMask);
1699
0
    aMaskRenderer.process(rTransCandidate.getTransparence());
1700
1701
    // convert mask to something cairo can use
1702
0
    LuminanceToAlpha(pMask);
1703
1704
    // draw content to temporary surface
1705
0
    cairo_surface_t* pContent(cairo_surface_create_similar(
1706
0
        pTarget, cairo_surface_get_content(pTarget), fContainedWidth, fContainedHeight));
1707
0
    CairoPixelProcessor2D aContent(getBColorModifierStack(), aViewInformation2D, pContent);
1708
0
    aContent.process(rTransCandidate.getChildren());
1709
1710
    // munge the temporary surfaces to our target surface
1711
0
    cairo_set_source_surface(mpRT, pContent, aVisibleRange.getMinX(), aVisibleRange.getMinY());
1712
0
    cairo_mask_surface(mpRT, pMask, aVisibleRange.getMinX(), aVisibleRange.getMinY());
1713
1714
    // cleanup temporary surfaces
1715
0
    cairo_surface_destroy(pContent);
1716
0
    cairo_surface_destroy(pMask);
1717
1718
0
    cairo_restore(mpRT);
1719
0
}
1720
1721
void CairoPixelProcessor2D::processInvertPrimitive2D(
1722
    const primitive2d::InvertPrimitive2D& rInvertCandidate)
1723
0
{
1724
0
    if (rInvertCandidate.getChildren().empty())
1725
0
    {
1726
        // no content, done
1727
0
        return;
1728
0
    }
1729
1730
    // calculate visible range, create only for that range
1731
0
    basegfx::B2DRange aDiscreteRange(
1732
0
        rInvertCandidate.getChildren().getB2DRange(getViewInformation2D()));
1733
0
    aDiscreteRange.transform(getViewInformation2D().getObjectToViewTransformation());
1734
0
    basegfx::B2DRange aVisibleRange(aDiscreteRange);
1735
0
    aVisibleRange.intersect(getDiscreteViewRange(mpRT));
1736
1737
0
    if (aVisibleRange.isEmpty())
1738
0
    {
1739
        // not visible, done
1740
0
        return;
1741
0
    }
1742
1743
0
    cairo_save(mpRT);
1744
1745
    // tdf#166734 need to expand to full pixels due to pre-rendering
1746
    // will use discrete pixels/top-left position
1747
0
    aVisibleRange.expand(
1748
0
        basegfx::B2DPoint(floor(aVisibleRange.getMinX()), floor(aVisibleRange.getMinY())));
1749
0
    aVisibleRange.expand(
1750
0
        basegfx::B2DPoint(ceil(aVisibleRange.getMaxX()), ceil(aVisibleRange.getMaxY())));
1751
1752
    // create embedding transformation for sub-surface
1753
0
    const basegfx::B2DHomMatrix aEmbedTransform(basegfx::utils::createTranslateB2DHomMatrix(
1754
0
        -aVisibleRange.getMinX(), -aVisibleRange.getMinY()));
1755
0
    geometry::ViewInformation2D aViewInformation2D(getViewInformation2D());
1756
0
    aViewInformation2D.setViewTransformation(aEmbedTransform
1757
0
                                             * getViewInformation2D().getViewTransformation());
1758
1759
    // draw sub-content to temporary surface
1760
0
    cairo_surface_t* pTarget(cairo_get_target(mpRT));
1761
0
    const double fContainedWidth(aVisibleRange.getWidth());
1762
0
    const double fContainedHeight(aVisibleRange.getHeight());
1763
0
    cairo_surface_t* pContent(cairo_surface_create_similar_image(
1764
0
        pTarget, CAIRO_FORMAT_ARGB32, fContainedWidth, fContainedHeight));
1765
0
    CairoPixelProcessor2D aContent(getBColorModifierStack(), aViewInformation2D, pContent);
1766
0
    aContent.process(rInvertCandidate.getChildren());
1767
0
    cairo_surface_flush(pContent);
1768
1769
    // decide if to use builtin or create XOR yourself
1770
    // NOTE: not using and doing self is closer to what the
1771
    //       current default does, so keep it
1772
0
    static bool bUseBuiltinXOR(false);
1773
1774
0
    if (bUseBuiltinXOR)
1775
0
    {
1776
        // draw XOR to target using Cairo Operator CAIRO_OPERATOR_XOR
1777
0
        cairo_set_source_surface(mpRT, pContent, aVisibleRange.getMinX(), aVisibleRange.getMinY());
1778
0
        cairo_rectangle(mpRT, aVisibleRange.getMinX(), aVisibleRange.getMinY(),
1779
0
                        aVisibleRange.getWidth(), aVisibleRange.getHeight());
1780
0
        cairo_set_operator(mpRT, CAIRO_OPERATOR_XOR);
1781
0
        cairo_fill(mpRT);
1782
0
    }
1783
0
    else
1784
0
    {
1785
        // get read/write access to target - XOR unfortunately needs that
1786
0
        cairo_surface_t* pRenderTarget(pTarget);
1787
1788
0
        if (CAIRO_SURFACE_TYPE_IMAGE != cairo_surface_get_type(pRenderTarget))
1789
0
        {
1790
            // create mapping for read/write access to pRenderTarget
1791
0
            pRenderTarget = cairo_surface_map_to_image(pRenderTarget, nullptr);
1792
0
        }
1793
1794
        // iterate over pre-rendered pContent (call it Front)
1795
0
        const sal_uInt32 nFrontWidth(cairo_image_surface_get_width(pContent));
1796
0
        const sal_uInt32 nFrontHeight(cairo_image_surface_get_height(pContent));
1797
0
        const sal_uInt32 nFrontStride(cairo_image_surface_get_stride(pContent));
1798
0
        unsigned char* pFrontDataRoot(cairo_image_surface_get_data(pContent));
1799
1800
        // in parallel, iterate over original data (call it Back)
1801
0
        const sal_uInt32 nBackOffX(aVisibleRange.getMinX());
1802
0
        const sal_uInt32 nBackOffY(aVisibleRange.getMinY());
1803
0
        const sal_uInt32 nBackStride(cairo_image_surface_get_stride(pRenderTarget));
1804
0
        unsigned char* pBackDataRoot(cairo_image_surface_get_data(pRenderTarget));
1805
0
        const bool bBackPreMultiply(CAIRO_FORMAT_ARGB32
1806
0
                                    == cairo_image_surface_get_format(pRenderTarget));
1807
1808
0
        if (nullptr != pFrontDataRoot && nullptr != pBackDataRoot)
1809
0
        {
1810
0
            for (sal_uInt32 y(0); y < nFrontHeight; ++y)
1811
0
            {
1812
                // get mem locations
1813
0
                unsigned char* pFrontData(pFrontDataRoot + (nFrontStride * y));
1814
0
                unsigned char* pBackData(pBackDataRoot + (nBackStride * (y + nBackOffY))
1815
0
                                         + (nBackOffX * 4));
1816
1817
                // added advance mem to for-expression to be able to continue calls inside
1818
0
                for (sal_uInt32 x(0); x < nFrontWidth; ++x, pBackData += 4, pFrontData += 4)
1819
0
                {
1820
                    // do not forget pre-multiply. Use 255 for non-premultiplied to
1821
                    // not have to do if not needed
1822
0
                    const sal_uInt8 nBackAlpha(bBackPreMultiply ? pBackData[SVP_CAIRO_ALPHA] : 255);
1823
1824
                    // change will only be visible in back/target when not fully transparent
1825
0
                    if (0 == nBackAlpha)
1826
0
                        continue;
1827
1828
                    // do not forget pre-multiply -> need to get both alphas. Use 255
1829
                    // for non-premultiplied to not have to do if not needed
1830
0
                    const sal_uInt8 nFrontAlpha(pFrontData[SVP_CAIRO_ALPHA]);
1831
1832
                    // only something to do if source is not fully transparent
1833
0
                    if (0 == nFrontAlpha)
1834
0
                        continue;
1835
1836
0
                    sal_uInt8 nFrontB(pFrontData[SVP_CAIRO_BLUE]);
1837
0
                    sal_uInt8 nFrontG(pFrontData[SVP_CAIRO_GREEN]);
1838
0
                    sal_uInt8 nFrontR(pFrontData[SVP_CAIRO_RED]);
1839
1840
0
                    if (255 != nFrontAlpha)
1841
0
                    {
1842
                        // get front color (Front is always CAIRO_FORMAT_ARGB32 and
1843
                        // thus pre-multiplied)
1844
0
                        nFrontB = vcl::bitmap::unpremultiply(nFrontB, nFrontAlpha);
1845
0
                        nFrontG = vcl::bitmap::unpremultiply(nFrontG, nFrontAlpha);
1846
0
                        nFrontR = vcl::bitmap::unpremultiply(nFrontR, nFrontAlpha);
1847
0
                    }
1848
1849
0
                    sal_uInt8 nBackB(pBackData[SVP_CAIRO_BLUE]);
1850
0
                    sal_uInt8 nBackG(pBackData[SVP_CAIRO_GREEN]);
1851
0
                    sal_uInt8 nBackR(pBackData[SVP_CAIRO_RED]);
1852
1853
0
                    if (255 != nBackAlpha)
1854
0
                    {
1855
                        // get back color if bBackPreMultiply (aka 255)
1856
0
                        nBackB = vcl::bitmap::unpremultiply(nBackB, nBackAlpha);
1857
0
                        nBackG = vcl::bitmap::unpremultiply(nBackG, nBackAlpha);
1858
0
                        nBackR = vcl::bitmap::unpremultiply(nBackR, nBackAlpha);
1859
0
                    }
1860
1861
                    // create XOR r,g,b
1862
0
                    const sal_uInt8 b(nFrontB ^ nBackB);
1863
0
                    const sal_uInt8 g(nFrontG ^ nBackG);
1864
0
                    const sal_uInt8 r(nFrontR ^ nBackR);
1865
1866
                    // write back directly to pBackData/target
1867
0
                    if (255 == nBackAlpha)
1868
0
                    {
1869
0
                        pBackData[SVP_CAIRO_BLUE] = b;
1870
0
                        pBackData[SVP_CAIRO_GREEN] = g;
1871
0
                        pBackData[SVP_CAIRO_RED] = r;
1872
0
                    }
1873
0
                    else
1874
0
                    {
1875
                        // additionally premultiply if bBackPreMultiply (aka 255)
1876
0
                        pBackData[SVP_CAIRO_BLUE] = vcl::bitmap::premultiply(b, nBackAlpha);
1877
0
                        pBackData[SVP_CAIRO_GREEN] = vcl::bitmap::premultiply(g, nBackAlpha);
1878
0
                        pBackData[SVP_CAIRO_RED] = vcl::bitmap::premultiply(r, nBackAlpha);
1879
0
                    }
1880
0
                }
1881
0
            }
1882
1883
0
            cairo_surface_mark_dirty(pRenderTarget);
1884
0
        }
1885
1886
0
        if (pRenderTarget != pTarget)
1887
0
        {
1888
            // cleanup mapping for read/write access to target
1889
0
            cairo_surface_unmap_image(pTarget, pRenderTarget);
1890
0
        }
1891
0
    }
1892
1893
    // cleanup temporary surface
1894
0
    cairo_surface_destroy(pContent);
1895
1896
0
    cairo_restore(mpRT);
1897
0
}
1898
1899
void CairoPixelProcessor2D::processMaskPrimitive2D(
1900
    const primitive2d::MaskPrimitive2D& rMaskCandidate)
1901
0
{
1902
0
    if (rMaskCandidate.getChildren().empty())
1903
0
    {
1904
        // no content, done
1905
0
        return;
1906
0
    }
1907
1908
0
    const basegfx::B2DPolyPolygon& rMask(rMaskCandidate.getMask());
1909
1910
0
    if (!rMask.count())
1911
0
    {
1912
        // no mask (so nothing inside), done
1913
0
        return;
1914
0
    }
1915
1916
    // calculate visible range
1917
0
    basegfx::B2DRange aMaskRange(rMask.getB2DRange());
1918
0
    aMaskRange.transform(getViewInformation2D().getObjectToViewTransformation());
1919
0
    if (!getDiscreteViewRange(mpRT).overlaps(aMaskRange))
1920
0
    {
1921
        // not visible, done
1922
0
        return;
1923
0
    }
1924
1925
0
    cairo_save(mpRT);
1926
1927
0
    if (isCairoCoordinateLimitWorkaroundActive())
1928
0
    {
1929
        // need to fallback to paint in view coordinates, unfortunately
1930
        // need to transform self (cairo will do it wrong in this coordinate
1931
        // space), so no need to try to buffer
1932
0
        cairo_new_path(mpRT);
1933
0
        basegfx::B2DPolyPolygon aAdaptedPolyPolygon(rMask);
1934
0
        aAdaptedPolyPolygon.transform(getViewInformation2D().getObjectToViewTransformation());
1935
0
        for (const auto& rPolygon : aAdaptedPolyPolygon)
1936
0
            addB2DPolygonToPathGeometry(mpRT, rPolygon);
1937
1938
        // clip to this mask
1939
0
        cairo_clip(mpRT);
1940
0
    }
1941
0
    else
1942
0
    {
1943
        // set linear transformation for applying mask. use no fAAOffset for mask
1944
0
        cairo_matrix_t aMatrix;
1945
0
        const basegfx::B2DHomMatrix& rObjectToView(
1946
0
            getViewInformation2D().getObjectToViewTransformation());
1947
0
        cairo_matrix_init(&aMatrix, rObjectToView.a(), rObjectToView.b(), rObjectToView.c(),
1948
0
                          rObjectToView.d(), rObjectToView.e(), rObjectToView.f());
1949
0
        cairo_set_matrix(mpRT, &aMatrix);
1950
1951
        // create path geometry and put mask as path
1952
0
        cairo_new_path(mpRT);
1953
0
        getOrCreateFillGeometry(mpRT, rMask);
1954
1955
        // clip to this mask
1956
0
        cairo_clip(mpRT);
1957
1958
        // reset transformation to not have it set when processing
1959
        // child content below (was only used to set clip path)
1960
0
        cairo_identity_matrix(mpRT);
1961
0
    }
1962
1963
    // process sub-content (that shall be masked)
1964
0
    mnClipRecursionCount++;
1965
0
    process(rMaskCandidate.getChildren());
1966
0
    mnClipRecursionCount--;
1967
1968
0
    cairo_restore(mpRT);
1969
1970
0
    if (0 == mnClipRecursionCount)
1971
0
    {
1972
        // for *some* reason Cairo seems to have problems using cairo_clip
1973
        // recursively, in combination with cairo_save/cairo_restore. I think
1974
        // it *should* work as used here, see
1975
        // https://www.cairographics.org/manual/cairo-cairo-t.html#cairo-clip
1976
        // where this combination is explicitly mentioned/explained. It may
1977
        // just be a error in cairo, too (?).
1978
        // The error is that without that for some reason the last clip is not
1979
        // restored but *stays*, so e.g. when having a shape filled with
1980
        // 'tux.svg' and an ellipse overlapping in front, suddenly (but not
1981
        // always?) the ellipse gets 'clipped' against the shape filled with
1982
        // the tux graphic.
1983
        // What helps is to count the clip recursion for each incarnation of
1984
        // CairoPixelProcessor2D/cairo_t used and call/use cairo_reset_clip
1985
        // when last clip is left.
1986
0
        cairo_reset_clip(mpRT);
1987
0
    }
1988
0
}
1989
1990
void CairoPixelProcessor2D::processModifiedColorPrimitive2D(
1991
    const primitive2d::ModifiedColorPrimitive2D& rModifiedCandidate)
1992
0
{
1993
    // standard implementation
1994
0
    if (!rModifiedCandidate.getChildren().empty())
1995
0
    {
1996
0
        maBColorModifierStack.push(rModifiedCandidate.getColorModifier());
1997
0
        process(rModifiedCandidate.getChildren());
1998
0
        maBColorModifierStack.pop();
1999
0
    }
2000
0
}
2001
2002
void CairoPixelProcessor2D::processTransformPrimitive2D(
2003
    const primitive2d::TransformPrimitive2D& rTransformCandidate)
2004
227
{
2005
    // standard implementation
2006
    // remember current transformation and ViewInformation
2007
227
    const geometry::ViewInformation2D aLastViewInformation2D(getViewInformation2D());
2008
2009
    // create new transformations for local ViewInformation2D
2010
227
    geometry::ViewInformation2D aViewInformation2D(getViewInformation2D());
2011
227
    aViewInformation2D.setObjectTransformation(getViewInformation2D().getObjectTransformation()
2012
227
                                               * rTransformCandidate.getTransformation());
2013
227
    setViewInformation2D(aViewInformation2D);
2014
2015
    // process content
2016
227
    process(rTransformCandidate.getChildren());
2017
2018
    // restore transformations
2019
227
    setViewInformation2D(aLastViewInformation2D);
2020
227
}
2021
2022
void CairoPixelProcessor2D::processUnifiedTransparencePrimitive2D(
2023
    const primitive2d::UnifiedTransparencePrimitive2D& rTransCandidate)
2024
0
{
2025
0
    if (rTransCandidate.getChildren().empty())
2026
0
    {
2027
        // no content, done
2028
0
        return;
2029
0
    }
2030
2031
0
    if (0.0 == rTransCandidate.getTransparence())
2032
0
    {
2033
        // not transparent at all, use content
2034
0
        process(rTransCandidate.getChildren());
2035
0
        return;
2036
0
    }
2037
2038
0
    if (rTransCandidate.getTransparence() < 0.0 || rTransCandidate.getTransparence() > 1.0)
2039
0
    {
2040
        // invalid transparence, done
2041
0
        return;
2042
0
    }
2043
2044
    // calculate visible range, create only for that range
2045
0
    basegfx::B2DRange aDiscreteRange(
2046
0
        rTransCandidate.getChildren().getB2DRange(getViewInformation2D()));
2047
0
    aDiscreteRange.transform(getViewInformation2D().getObjectToViewTransformation());
2048
0
    basegfx::B2DRange aVisibleRange(aDiscreteRange);
2049
0
    aVisibleRange.intersect(getDiscreteViewRange(mpRT));
2050
2051
0
    if (aVisibleRange.isEmpty())
2052
0
    {
2053
        // not visible, done
2054
0
        return;
2055
0
    }
2056
2057
0
    cairo_save(mpRT);
2058
2059
    // tdf#166734 need to expand to full pixels due to pre-rendering
2060
    // will use discrete pixels/top-left position
2061
0
    aVisibleRange.expand(
2062
0
        basegfx::B2DPoint(floor(aVisibleRange.getMinX()), floor(aVisibleRange.getMinY())));
2063
0
    aVisibleRange.expand(
2064
0
        basegfx::B2DPoint(ceil(aVisibleRange.getMaxX()), ceil(aVisibleRange.getMaxY())));
2065
2066
    // create embedding transformation for sub-surface
2067
0
    const basegfx::B2DHomMatrix aEmbedTransform(basegfx::utils::createTranslateB2DHomMatrix(
2068
0
        -aVisibleRange.getMinX(), -aVisibleRange.getMinY()));
2069
0
    geometry::ViewInformation2D aViewInformation2D(getViewInformation2D());
2070
0
    aViewInformation2D.setViewTransformation(aEmbedTransform
2071
0
                                             * getViewInformation2D().getViewTransformation());
2072
2073
    // draw content to temporary surface
2074
0
    cairo_surface_t* pTarget(cairo_get_target(mpRT));
2075
0
    const double fContainedWidth(aVisibleRange.getWidth());
2076
0
    const double fContainedHeight(aVisibleRange.getHeight());
2077
0
    cairo_surface_t* pContent(cairo_surface_create_similar(
2078
0
        pTarget, cairo_surface_get_content(pTarget), fContainedWidth, fContainedHeight));
2079
0
    CairoPixelProcessor2D aContent(getBColorModifierStack(), aViewInformation2D, pContent);
2080
0
    aContent.process(rTransCandidate.getChildren());
2081
2082
    // paint temporary surface to target with fixed transparence
2083
0
    cairo_set_source_surface(mpRT, pContent, aVisibleRange.getMinX(), aVisibleRange.getMinY());
2084
0
    cairo_paint_with_alpha(mpRT, 1.0 - rTransCandidate.getTransparence());
2085
2086
    // cleanup temporary surface
2087
0
    cairo_surface_destroy(pContent);
2088
2089
0
    cairo_restore(mpRT);
2090
0
}
2091
2092
void CairoPixelProcessor2D::processMarkerArrayPrimitive2D(
2093
    const primitive2d::MarkerArrayPrimitive2D& rMarkerArrayCandidate)
2094
0
{
2095
0
    const std::vector<basegfx::B2DPoint>& rPositions(rMarkerArrayCandidate.getPositions());
2096
2097
0
    if (rPositions.empty())
2098
0
    {
2099
        // no geometry, done
2100
0
        return;
2101
0
    }
2102
2103
0
    const Bitmap& rMarker(rMarkerArrayCandidate.getMarker());
2104
2105
0
    if (rMarker.IsEmpty())
2106
0
    {
2107
        // no marker defined, done
2108
0
        return;
2109
0
    }
2110
2111
    // prepare Marker's Bitmap
2112
0
    Bitmap aBitmap(rMarkerArrayCandidate.getMarker());
2113
2114
0
    constexpr DrawModeFlags BITMAP(DrawModeFlags::BlackBitmap | DrawModeFlags::WhiteBitmap
2115
0
                                   | DrawModeFlags::GrayBitmap);
2116
0
    const DrawModeFlags aDrawModeFlags(getViewInformation2D().getDrawModeFlags());
2117
0
    if (aDrawModeFlags & BITMAP)
2118
0
    {
2119
0
        if (aDrawModeFlags & DrawModeFlags::BlackBitmap)
2120
0
        {
2121
0
            const basegfx::BColorModifierSharedPtr aBColorModifier(
2122
0
                std::make_shared<basegfx::BColorModifier_replace>(basegfx::BColor(0, 0, 0)));
2123
0
            maBColorModifierStack.push(aBColorModifier);
2124
0
        }
2125
0
        else if (aDrawModeFlags & DrawModeFlags::WhiteBitmap)
2126
0
        {
2127
0
            const basegfx::BColorModifierSharedPtr aBColorModifier(
2128
0
                std::make_shared<basegfx::BColorModifier_replace>(basegfx::BColor(1, 1, 1)));
2129
0
            maBColorModifierStack.push(aBColorModifier);
2130
0
        }
2131
0
        else // DrawModeFlags::GrayBitmap
2132
0
        {
2133
0
            const basegfx::BColorModifierSharedPtr aBColorModifier(
2134
0
                std::make_shared<basegfx::BColorModifier_gray>());
2135
0
            maBColorModifierStack.push(aBColorModifier);
2136
0
        }
2137
2138
        // need to apply ColorModifier to Bitmap data
2139
0
        aBitmap = aBitmap.Modify(maBColorModifierStack);
2140
2141
0
        if (aBitmap.IsEmpty())
2142
0
        {
2143
            // color gets completely replaced, get it
2144
0
            const basegfx::BColor aReplacementColor(
2145
0
                maBColorModifierStack.getModifiedColor(basegfx::BColor()));
2146
0
            Bitmap aBitmap2(rMarker.GetSizePixel(), vcl::PixelFormat::N24_BPP);
2147
0
            aBitmap2.Erase(Color(aReplacementColor));
2148
2149
0
            if (rMarker.HasAlpha())
2150
0
                aBitmap = Bitmap(aBitmap2, rMarker.CreateAlphaMask());
2151
0
            else
2152
0
                aBitmap = std::move(aBitmap2);
2153
0
        }
2154
2155
0
        maBColorModifierStack.pop();
2156
0
    }
2157
2158
    // access or create cairo bitmap data
2159
0
    std::shared_ptr<CairoSurfaceHelper> aCairoSurfaceHelper(getOrCreateCairoSurfaceHelper(aBitmap));
2160
0
    if (!aCairoSurfaceHelper)
2161
0
    {
2162
0
        SAL_WARN("drawinglayer", "SDPRCairo: No SurfaceHelper from Bitmap (!)");
2163
0
        return;
2164
0
    }
2165
2166
    // do not use dimensions, these are usually small instances
2167
0
    cairo_surface_t* pTarget(aCairoSurfaceHelper->getCairoSurface());
2168
0
    if (nullptr == pTarget)
2169
0
    {
2170
0
        SAL_WARN("drawinglayer", "SDPRCairo: No CairoSurface from Bitmap SurfaceHelper (!)");
2171
0
        return;
2172
0
    }
2173
2174
0
    const sal_uInt32 nWidth(cairo_image_surface_get_width(pTarget));
2175
0
    const sal_uInt32 nHeight(cairo_image_surface_get_height(pTarget));
2176
0
    const tools::Long nMiX((nWidth / 2) + 1);
2177
0
    const tools::Long nMiY((nHeight / 2) + 1);
2178
2179
0
    cairo_save(mpRT);
2180
0
    cairo_identity_matrix(mpRT);
2181
0
    const cairo_antialias_t eOldAAMode(cairo_get_antialias(mpRT));
2182
0
    cairo_set_antialias(mpRT, CAIRO_ANTIALIAS_NONE);
2183
2184
0
    for (auto const& pos : rPositions)
2185
0
    {
2186
0
        const basegfx::B2DPoint aDiscretePos(getViewInformation2D().getObjectToViewTransformation()
2187
0
                                             * pos);
2188
0
        const double fX(ceil(aDiscretePos.getX()));
2189
0
        const double fY(ceil(aDiscretePos.getY()));
2190
2191
0
        cairo_set_source_surface(mpRT, pTarget, fX - nMiX, fY - nMiY);
2192
0
        cairo_paint(mpRT);
2193
0
    }
2194
2195
0
    cairo_set_antialias(mpRT, eOldAAMode);
2196
0
    cairo_restore(mpRT);
2197
0
}
2198
2199
void CairoPixelProcessor2D::processBackgroundColorPrimitive2D(
2200
    const primitive2d::BackgroundColorPrimitive2D& rBackgroundColorCandidate)
2201
0
{
2202
    // check for allowed range [0.0 .. 1.0[
2203
0
    if (rBackgroundColorCandidate.getTransparency() < 0.0
2204
0
        || rBackgroundColorCandidate.getTransparency() >= 1.0)
2205
0
        return;
2206
2207
0
    if (getViewInformation2D().getDrawModeFlags() & DrawModeFlags::NoFill)
2208
        // NoFill wanted, done
2209
0
        return;
2210
2211
0
    if (!getViewInformation2D().getViewport().isEmpty())
2212
0
    {
2213
        // we have a Viewport set with limitations, render as needed/defined
2214
        // by BackgroundColorPrimitive2D::create2DDecomposition. Alternatively,
2215
        // just use recursion/decompose in this case
2216
0
        process(rBackgroundColorCandidate);
2217
0
        return;
2218
0
    }
2219
2220
    // no Viewport set, render surface completely
2221
0
    cairo_save(mpRT);
2222
0
    basegfx::BColor aFillColor(getFillColor(rBackgroundColorCandidate.getBColor()));
2223
0
    aFillColor = maBColorModifierStack.getModifiedColor(aFillColor);
2224
0
    cairo_set_source_rgba(mpRT, aFillColor.getRed(), aFillColor.getGreen(), aFillColor.getBlue(),
2225
0
                          1.0 - rBackgroundColorCandidate.getTransparency());
2226
    // to also copy alpha part of color, see cairo docu. Will be reset by restore below
2227
0
    cairo_set_operator(mpRT, CAIRO_OPERATOR_SOURCE);
2228
0
    cairo_paint(mpRT);
2229
0
    cairo_restore(mpRT);
2230
0
}
2231
2232
void CairoPixelProcessor2D::processPolygonStrokePrimitive2D(
2233
    const primitive2d::PolygonStrokePrimitive2D& rPolygonStrokeCandidate)
2234
0
{
2235
0
    const basegfx::B2DPolygon& rPolygon(rPolygonStrokeCandidate.getB2DPolygon());
2236
0
    const attribute::LineAttribute& rLineAttribute(rPolygonStrokeCandidate.getLineAttribute());
2237
2238
0
    if (!rPolygon.count() || rLineAttribute.getWidth() < 0.0)
2239
0
    {
2240
        // no geometry, done
2241
0
        return;
2242
0
    }
2243
2244
    // get some values early that might be used for decisions
2245
0
    const bool bHairline(0.0 == rLineAttribute.getWidth());
2246
0
    const basegfx::B2DHomMatrix& rObjectToView(
2247
0
        getViewInformation2D().getObjectToViewTransformation());
2248
0
    const double fDiscreteLineWidth(
2249
0
        bHairline
2250
0
            ? 1.0
2251
0
            : (rObjectToView * basegfx::B2DVector(rLineAttribute.getWidth(), 0.0)).getLength());
2252
2253
    // Here for every combination which the system-specific implementation is not
2254
    // capable of visualizing, use the (for decomposable Primitives always possible)
2255
    // fallback to the decomposition.
2256
0
    if (basegfx::B2DLineJoin::NONE == rLineAttribute.getLineJoin() && fDiscreteLineWidth > 1.5)
2257
0
    {
2258
        // basegfx::B2DLineJoin::NONE is special for our office, no other GraphicSystem
2259
        // knows that (so far), so fallback to decomposition. This is only needed if
2260
        // LineJoin will be used, so also check for discrete LineWidth before falling back
2261
0
        process(rPolygonStrokeCandidate);
2262
0
        return;
2263
0
    }
2264
2265
    // This is a method every system-specific implementation of a decomposable Primitive
2266
    // can use to allow simple optical control of paint implementation:
2267
    // Create a copy, e.g. change color to 'red' as here and paint before the system
2268
    // paints it using the decomposition. That way you can - if active - directly
2269
    // optically compare if the system-specific solution is geometrically identical to
2270
    // the decomposition (which defines our interpretation that we need to visualize).
2271
    // Look below in the impl for bRenderDecomposeForCompareInRed to see that in that case
2272
    // we create a half-transparent paint to better support visual control
2273
0
    static bool bRenderDecomposeForCompareInRed(false);
2274
2275
0
    if (bRenderDecomposeForCompareInRed)
2276
0
    {
2277
0
        const attribute::LineAttribute aRed(
2278
0
            basegfx::BColor(1.0, 0.0, 0.0), rLineAttribute.getWidth(), rLineAttribute.getLineJoin(),
2279
0
            rLineAttribute.getLineCap(), rLineAttribute.getMiterMinimumAngle());
2280
0
        rtl::Reference<primitive2d::PolygonStrokePrimitive2D> xCopy(
2281
0
            new primitive2d::PolygonStrokePrimitive2D(
2282
0
                rPolygonStrokeCandidate.getB2DPolygon(), aRed,
2283
0
                rPolygonStrokeCandidate.getStrokeAttribute()));
2284
0
        process(*xCopy);
2285
0
    }
2286
2287
0
    cairo_save(mpRT);
2288
2289
    // setup line attributes
2290
0
    cairo_line_join_t eCairoLineJoin = CAIRO_LINE_JOIN_MITER;
2291
0
    switch (rLineAttribute.getLineJoin())
2292
0
    {
2293
0
        case basegfx::B2DLineJoin::Bevel:
2294
0
            eCairoLineJoin = CAIRO_LINE_JOIN_BEVEL;
2295
0
            break;
2296
0
        case basegfx::B2DLineJoin::Round:
2297
0
            eCairoLineJoin = CAIRO_LINE_JOIN_ROUND;
2298
0
            break;
2299
0
        case basegfx::B2DLineJoin::NONE:
2300
0
        case basegfx::B2DLineJoin::Miter:
2301
0
            eCairoLineJoin = CAIRO_LINE_JOIN_MITER;
2302
0
            break;
2303
0
    }
2304
0
    cairo_set_line_join(mpRT, eCairoLineJoin);
2305
2306
    // convert miter minimum angle to miter limit
2307
0
    double fMiterLimit
2308
0
        = 1.0 / sin(std::max(rLineAttribute.getMiterMinimumAngle(), 0.01 * M_PI) / 2.0);
2309
0
    cairo_set_miter_limit(mpRT, fMiterLimit);
2310
2311
    // setup cap attribute
2312
0
    cairo_line_cap_t eCairoLineCap(CAIRO_LINE_CAP_BUTT);
2313
0
    switch (rLineAttribute.getLineCap())
2314
0
    {
2315
0
        default: // css::drawing::LineCap_BUTT:
2316
0
        {
2317
0
            eCairoLineCap = CAIRO_LINE_CAP_BUTT;
2318
0
            break;
2319
0
        }
2320
0
        case css::drawing::LineCap_ROUND:
2321
0
        {
2322
0
            eCairoLineCap = CAIRO_LINE_CAP_ROUND;
2323
0
            break;
2324
0
        }
2325
0
        case css::drawing::LineCap_SQUARE:
2326
0
        {
2327
0
            eCairoLineCap = CAIRO_LINE_CAP_SQUARE;
2328
0
            break;
2329
0
        }
2330
0
    }
2331
0
    cairo_set_line_cap(mpRT, eCairoLineCap);
2332
2333
    // determine & set color
2334
0
    basegfx::BColor aLineColor(getLineColor(rLineAttribute.getColor()));
2335
0
    aLineColor = maBColorModifierStack.getModifiedColor(aLineColor);
2336
0
    if (bRenderDecomposeForCompareInRed)
2337
0
        aLineColor.setRed(0.5);
2338
0
    cairo_set_source_rgb(mpRT, aLineColor.getRed(), aLineColor.getGreen(), aLineColor.getBlue());
2339
2340
    // check stroke
2341
0
    const attribute::StrokeAttribute& rStrokeAttribute(
2342
0
        rPolygonStrokeCandidate.getStrokeAttribute());
2343
0
    const bool bDashUsed(!rStrokeAttribute.isDefault()
2344
0
                         && !rStrokeAttribute.getDotDashArray().empty()
2345
0
                         && 0.0 < rStrokeAttribute.getFullDotDashLen());
2346
0
    if (isCairoCoordinateLimitWorkaroundActive())
2347
0
    {
2348
        // need to fallback to paint in view coordinates, unfortunately
2349
        // need to transform self (cairo will do it wrong in this coordinate
2350
        // space), so no need to try to buffer
2351
0
        cairo_new_path(mpRT);
2352
0
        basegfx::B2DPolygon aAdaptedPolygon(rPolygon);
2353
0
        const double fAAOffset(getViewInformation2D().getUseAntiAliasing() ? 0.5 : 0.0);
2354
0
        aAdaptedPolygon.transform(basegfx::utils::createTranslateB2DHomMatrix(fAAOffset, fAAOffset)
2355
0
                                  * getViewInformation2D().getObjectToViewTransformation());
2356
0
        cairo_identity_matrix(mpRT);
2357
0
        addB2DPolygonToPathGeometry(mpRT, aAdaptedPolygon);
2358
2359
        // process/set LineWidth
2360
0
        const double fObjectLineWidth(bHairline
2361
0
                                          ? 1.0
2362
0
                                          : (getViewInformation2D().getObjectToViewTransformation()
2363
0
                                             * basegfx::B2DVector(rLineAttribute.getWidth(), 0.0))
2364
0
                                                .getLength());
2365
0
        cairo_set_line_width(mpRT, fObjectLineWidth);
2366
2367
0
        if (bDashUsed)
2368
0
        {
2369
0
            std::vector<double> aStroke(rStrokeAttribute.getDotDashArray());
2370
0
            for (auto& rCandidate : aStroke)
2371
0
                rCandidate = (getViewInformation2D().getObjectToViewTransformation()
2372
0
                              * basegfx::B2DVector(rCandidate, 0.0))
2373
0
                                 .getLength();
2374
0
            cairo_set_dash(mpRT, aStroke.data(), aStroke.size(), 0.0);
2375
0
        }
2376
2377
0
        cairo_stroke(mpRT);
2378
0
    }
2379
0
    else
2380
0
    {
2381
        // set linear transformation
2382
0
        cairo_matrix_t aMatrix;
2383
0
        const double fAAOffset(getViewInformation2D().getUseAntiAliasing() ? 0.5 : 0.0);
2384
0
        cairo_matrix_init(&aMatrix, rObjectToView.a(), rObjectToView.b(), rObjectToView.c(),
2385
0
                          rObjectToView.d(), rObjectToView.e() + fAAOffset,
2386
0
                          rObjectToView.f() + fAAOffset);
2387
0
        cairo_set_matrix(mpRT, &aMatrix);
2388
2389
        // create path geometry and put mask as path
2390
0
        cairo_new_path(mpRT);
2391
0
        getOrCreatePathGeometry(mpRT, rPolygon, getViewInformation2D(),
2392
0
                                bHairline && getViewInformation2D().getUseAntiAliasing());
2393
2394
        // process/set LineWidth
2395
0
        const double fObjectLineWidth(
2396
0
            bHairline ? (getViewInformation2D().getInverseObjectToViewTransformation()
2397
0
                         * basegfx::B2DVector(1.0, 0.0))
2398
0
                            .getLength()
2399
0
                      : rLineAttribute.getWidth());
2400
0
        cairo_set_line_width(mpRT, fObjectLineWidth);
2401
2402
0
        if (bDashUsed)
2403
0
        {
2404
0
            const std::vector<double>& rStroke = rStrokeAttribute.getDotDashArray();
2405
0
            cairo_set_dash(mpRT, rStroke.data(), rStroke.size(), 0.0);
2406
0
        }
2407
2408
        // render
2409
0
        cairo_stroke(mpRT);
2410
0
    }
2411
2412
0
    cairo_restore(mpRT);
2413
0
}
2414
2415
void CairoPixelProcessor2D::processLineRectanglePrimitive2D(
2416
    const primitive2d::LineRectanglePrimitive2D& rLineRectanglePrimitive2D)
2417
0
{
2418
0
    if (rLineRectanglePrimitive2D.getB2DRange().isEmpty())
2419
0
    {
2420
        // no geometry, done
2421
0
        return;
2422
0
    }
2423
2424
0
    cairo_save(mpRT);
2425
2426
    // work in view coordinates
2427
0
    const double fAAOffset(getViewInformation2D().getUseAntiAliasing() ? 0.5 : 0.0);
2428
0
    basegfx::B2DRange aRange(rLineRectanglePrimitive2D.getB2DRange());
2429
0
    aRange.transform(getViewInformation2D().getObjectToViewTransformation());
2430
0
    cairo_identity_matrix(mpRT);
2431
2432
0
    basegfx::BColor aHairlineColor(getLineColor(rLineRectanglePrimitive2D.getBColor()));
2433
0
    aHairlineColor = maBColorModifierStack.getModifiedColor(aHairlineColor);
2434
0
    cairo_set_source_rgb(mpRT, aHairlineColor.getRed(), aHairlineColor.getGreen(),
2435
0
                         aHairlineColor.getBlue());
2436
2437
0
    const double fDiscreteLineWidth((getViewInformation2D().getInverseObjectToViewTransformation()
2438
0
                                     * basegfx::B2DVector(1.0, 0.0))
2439
0
                                        .getLength());
2440
0
    cairo_set_line_width(mpRT, fDiscreteLineWidth);
2441
2442
0
    cairo_rectangle(mpRT, aRange.getMinX() + fAAOffset, aRange.getMinY() + fAAOffset,
2443
0
                    aRange.getWidth(), aRange.getHeight());
2444
0
    cairo_stroke(mpRT);
2445
2446
0
    cairo_restore(mpRT);
2447
0
}
2448
2449
void CairoPixelProcessor2D::processFilledRectanglePrimitive2D(
2450
    const primitive2d::FilledRectanglePrimitive2D& rFilledRectanglePrimitive2D)
2451
0
{
2452
0
    if (rFilledRectanglePrimitive2D.getB2DRange().isEmpty())
2453
0
    {
2454
        // no geometry, done
2455
0
        return;
2456
0
    }
2457
2458
0
    if (getViewInformation2D().getDrawModeFlags() & DrawModeFlags::NoFill)
2459
        // NoFill wanted, done
2460
0
        return;
2461
2462
0
    cairo_save(mpRT);
2463
2464
    // work in view coordinates
2465
0
    basegfx::B2DRange aRange(rFilledRectanglePrimitive2D.getB2DRange());
2466
0
    aRange.transform(getViewInformation2D().getObjectToViewTransformation());
2467
0
    cairo_identity_matrix(mpRT);
2468
2469
0
    basegfx::BColor aFillColor(getFillColor(rFilledRectanglePrimitive2D.getBColor()));
2470
0
    aFillColor = maBColorModifierStack.getModifiedColor(aFillColor);
2471
0
    cairo_set_source_rgb(mpRT, aFillColor.getRed(), aFillColor.getGreen(), aFillColor.getBlue());
2472
2473
0
    cairo_rectangle(mpRT, aRange.getMinX(), aRange.getMinY(), aRange.getWidth(),
2474
0
                    aRange.getHeight());
2475
0
    cairo_fill(mpRT);
2476
2477
0
    cairo_restore(mpRT);
2478
0
}
2479
2480
void CairoPixelProcessor2D::processSingleLinePrimitive2D(
2481
    const primitive2d::SingleLinePrimitive2D& rSingleLinePrimitive2D)
2482
0
{
2483
0
    cairo_save(mpRT);
2484
2485
0
    basegfx::BColor aLineColor(getLineColor(rSingleLinePrimitive2D.getBColor()));
2486
0
    aLineColor = maBColorModifierStack.getModifiedColor(aLineColor);
2487
0
    cairo_set_source_rgb(mpRT, aLineColor.getRed(), aLineColor.getGreen(), aLineColor.getBlue());
2488
2489
0
    const double fAAOffset(getViewInformation2D().getUseAntiAliasing() ? 0.5 : 0.0);
2490
0
    const basegfx::B2DHomMatrix& rObjectToView(
2491
0
        getViewInformation2D().getObjectToViewTransformation());
2492
0
    const basegfx::B2DPoint aStart(rObjectToView * rSingleLinePrimitive2D.getStart());
2493
0
    const basegfx::B2DPoint aEnd(rObjectToView * rSingleLinePrimitive2D.getEnd());
2494
0
    cairo_identity_matrix(mpRT);
2495
2496
0
    cairo_set_line_width(mpRT, 1.0f);
2497
2498
0
    cairo_move_to(mpRT, aStart.getX() + fAAOffset, aStart.getY() + fAAOffset);
2499
0
    cairo_line_to(mpRT, aEnd.getX() + fAAOffset, aEnd.getY() + fAAOffset);
2500
0
    cairo_stroke(mpRT);
2501
2502
0
    cairo_restore(mpRT);
2503
0
}
2504
2505
void CairoPixelProcessor2D::processFillGraphicPrimitive2D(
2506
    const primitive2d::FillGraphicPrimitive2D& rFillGraphicPrimitive2D)
2507
0
{
2508
0
    if (rFillGraphicPrimitive2D.getTransparency() < 0.0
2509
0
        || rFillGraphicPrimitive2D.getTransparency() > 1.0)
2510
0
    {
2511
        // invalid transparence, done
2512
0
        return;
2513
0
    }
2514
2515
0
    Bitmap aPreparedBitmap;
2516
0
    basegfx::B2DRange aFillUnitRange(rFillGraphicPrimitive2D.getFillGraphic().getGraphicRange());
2517
0
    constexpr double fBigDiscreteArea(300.0 * 300.0);
2518
2519
    // use tooling to do various checks and prepare tiled rendering, see
2520
    // description of method, parameters and return value there
2521
0
    if (!prepareBitmapForDirectRender(rFillGraphicPrimitive2D, getViewInformation2D(),
2522
0
                                      aPreparedBitmap, aFillUnitRange, fBigDiscreteArea))
2523
0
    {
2524
        // no output needed, done
2525
0
        return;
2526
0
    }
2527
2528
0
    if (aPreparedBitmap.IsEmpty())
2529
0
    {
2530
        // output needed and Bitmap data empty, so no bitmap data based
2531
        // tiled rendering is suggested. Use fallback for paint
2532
        // and decomposition
2533
0
        process(rFillGraphicPrimitive2D);
2534
0
        return;
2535
0
    }
2536
2537
    // work with dimensions in discrete target pixels to use evtl. MipMap pre-scale
2538
0
    const basegfx::B2DHomMatrix aLocalTransform(
2539
0
        getViewInformation2D().getObjectToViewTransformation()
2540
0
        * rFillGraphicPrimitive2D.getTransformation());
2541
0
    const tools::Long nDestWidth(
2542
0
        (aLocalTransform * basegfx::B2DVector(aFillUnitRange.getWidth(), 0.0)).getLength());
2543
0
    const tools::Long nDestHeight(
2544
0
        (aLocalTransform * basegfx::B2DVector(0.0, aFillUnitRange.getHeight())).getLength());
2545
2546
    // tdf#167831 check for output size, may have zero discrete dimension in X and/or Y
2547
0
    if (0 == nDestWidth || 0 == nDestHeight)
2548
0
    {
2549
        // In which case, maybe we are zoomed out far enough to make the fill bitmap less than one pixel by one pixel,
2550
        // and so we need to fill with a color that is an average of the bitmap's color.
2551
0
        basegfx::BColor aFillColor = aPreparedBitmap.GetAverageColor().getBColor();
2552
0
        bool bTemporaryGrayColorModifier(false);
2553
0
        const DrawModeFlags aDrawModeFlags(getViewInformation2D().getDrawModeFlags());
2554
0
        if (aDrawModeFlags & DrawModeFlags::GrayBitmap)
2555
0
        {
2556
0
            bTemporaryGrayColorModifier = true;
2557
0
            const basegfx::BColorModifierSharedPtr aBColorModifier(
2558
0
                std::make_shared<basegfx::BColorModifier_gray>());
2559
0
            maBColorModifierStack.push(aBColorModifier);
2560
0
        }
2561
2562
0
        if (maBColorModifierStack.count())
2563
0
        {
2564
            // apply ColorModifier to Bitmap data
2565
0
            aFillColor = maBColorModifierStack.getModifiedColor(aFillColor);
2566
2567
0
            if (bTemporaryGrayColorModifier)
2568
                // cleanup temporary BColorModifier
2569
0
                maBColorModifierStack.pop();
2570
0
        }
2571
2572
        // draw geometry in single color using prepared ReplacementColor
2573
2574
        // use unit geometry as fallback object geometry. Do *not*
2575
        // transform, the below used method will use the already
2576
        // correctly initialized local ViewInformation
2577
0
        basegfx::B2DPolygon aPolygon(basegfx::utils::createUnitPolygon());
2578
2579
        // what we still need to apply is the object transform from the
2580
        // local primitive, that is not part of DisplayInfo yet
2581
0
        aPolygon.transform(rFillGraphicPrimitive2D.getTransformation());
2582
2583
        // draw directly, done
2584
0
        paintPolyPolygonRGBA(basegfx::B2DPolyPolygon(aPolygon), aFillColor,
2585
0
                             rFillGraphicPrimitive2D.getTransparency());
2586
0
        return;
2587
0
    }
2588
2589
0
    constexpr DrawModeFlags BITMAP(DrawModeFlags::BlackBitmap | DrawModeFlags::WhiteBitmap
2590
0
                                   | DrawModeFlags::GrayBitmap);
2591
0
    basegfx::BColor aReplacementColor(0, 0, 0);
2592
0
    bool bTemporaryGrayColorModifier(false);
2593
0
    const DrawModeFlags aDrawModeFlags(getViewInformation2D().getDrawModeFlags());
2594
0
    if (aDrawModeFlags & BITMAP)
2595
0
    {
2596
0
        if (aDrawModeFlags & DrawModeFlags::BlackBitmap)
2597
0
        {
2598
            // aReplacementColor already set
2599
0
            aPreparedBitmap.SetEmpty();
2600
0
        }
2601
0
        else if (aDrawModeFlags & DrawModeFlags::WhiteBitmap)
2602
0
        {
2603
0
            aReplacementColor = basegfx::BColor(1, 1, 1);
2604
0
            aPreparedBitmap.SetEmpty();
2605
0
        }
2606
0
        else // DrawModeFlags::GrayBitmap
2607
0
        {
2608
0
            bTemporaryGrayColorModifier = true;
2609
0
            const basegfx::BColorModifierSharedPtr aBColorModifier(
2610
0
                std::make_shared<basegfx::BColorModifier_gray>());
2611
0
            maBColorModifierStack.push(aBColorModifier);
2612
0
        }
2613
0
    }
2614
2615
0
    if (!aPreparedBitmap.IsEmpty() && maBColorModifierStack.count())
2616
0
    {
2617
        // apply ColorModifier to Bitmap data
2618
0
        aPreparedBitmap = aPreparedBitmap.Modify(maBColorModifierStack);
2619
2620
0
        if (aPreparedBitmap.IsEmpty())
2621
0
        {
2622
            // color gets completely replaced, get it
2623
0
            aReplacementColor = maBColorModifierStack.getModifiedColor(basegfx::BColor());
2624
0
        }
2625
2626
0
        if (bTemporaryGrayColorModifier)
2627
            // cleanup temporary BColorModifier
2628
0
            maBColorModifierStack.pop();
2629
0
    }
2630
2631
    // if PreparedBitmap is empty, draw geometry in single color using
2632
    // prepared ReplacementColor
2633
0
    if (aPreparedBitmap.IsEmpty())
2634
0
    {
2635
        // use unit geometry as fallback object geometry. Do *not*
2636
        // transform, the below used method will use the already
2637
        // correctly initialized local ViewInformation
2638
0
        basegfx::B2DPolygon aPolygon(basegfx::utils::createUnitPolygon());
2639
2640
        // what we still need to apply is the object transform from the
2641
        // local primitive, that is not part of DisplayInfo yet
2642
0
        aPolygon.transform(rFillGraphicPrimitive2D.getTransformation());
2643
2644
        // draw directly, done
2645
0
        paintPolyPolygonRGBA(basegfx::B2DPolyPolygon(aPolygon), aReplacementColor,
2646
0
                             rFillGraphicPrimitive2D.getTransparency());
2647
0
        return;
2648
0
    }
2649
2650
    // access or create cairo bitmap data
2651
0
    std::shared_ptr<CairoSurfaceHelper> aCairoSurfaceHelper(
2652
0
        getOrCreateCairoSurfaceHelper(aPreparedBitmap));
2653
0
    if (!aCairoSurfaceHelper)
2654
0
    {
2655
0
        SAL_WARN("drawinglayer", "SDPRCairo: No SurfaceHelper from Bitmap (!)");
2656
0
        return;
2657
0
    }
2658
2659
0
    cairo_surface_t* pTarget(aCairoSurfaceHelper->getCairoSurface(nDestWidth, nDestHeight));
2660
0
    if (nullptr == pTarget)
2661
0
    {
2662
0
        SAL_WARN("drawinglayer", "SDPRCairo: No CairoSurface from Bitmap SurfaceHelper (!)");
2663
0
        return;
2664
0
    }
2665
2666
0
    cairo_save(mpRT);
2667
2668
    // set linear transformation - no fAAOffset for bitmap data
2669
0
    cairo_matrix_t aMatrix;
2670
0
    cairo_matrix_init(&aMatrix, aLocalTransform.a(), aLocalTransform.b(), aLocalTransform.c(),
2671
0
                      aLocalTransform.d(), aLocalTransform.e(), aLocalTransform.f());
2672
0
    cairo_set_matrix(mpRT, &aMatrix);
2673
2674
0
    const sal_uInt32 nWidth(cairo_image_surface_get_width(pTarget));
2675
0
    const sal_uInt32 nHeight(cairo_image_surface_get_height(pTarget));
2676
2677
0
    cairo_set_source_surface(mpRT, pTarget, 0, 0);
2678
2679
    // get the pattern created by cairo_set_source_surface and
2680
    // it's transformation
2681
0
    cairo_pattern_t* sourcepattern = cairo_get_source(mpRT);
2682
0
    cairo_pattern_get_matrix(sourcepattern, &aMatrix);
2683
2684
    // clip for RGBA (see other places)
2685
0
    if (CAIRO_FORMAT_ARGB32 == cairo_image_surface_get_format(pTarget))
2686
0
    {
2687
0
        cairo_rectangle(mpRT, 0, 0, 1, 1);
2688
0
        cairo_clip(mpRT);
2689
0
    }
2690
2691
    // create transformation for source pattern (inverse, see
2692
    // cairo docu: uses user space to pattern space transformation)
2693
0
    cairo_matrix_init_scale(&aMatrix, nWidth / aFillUnitRange.getWidth(),
2694
0
                            nHeight / aFillUnitRange.getHeight());
2695
0
    cairo_matrix_translate(&aMatrix, -aFillUnitRange.getMinX(), -aFillUnitRange.getMinY());
2696
2697
    // set source pattern transform & activate pattern repeat
2698
0
    cairo_pattern_set_matrix(sourcepattern, &aMatrix);
2699
0
    cairo_pattern_set_extend(sourcepattern, CAIRO_EXTEND_REPEAT);
2700
2701
    // CAIRO_FILTER_GOOD seems to be the default anyways, but set it
2702
    // to be on the safe side
2703
0
    cairo_pattern_set_filter(sourcepattern, CAIRO_FILTER_GOOD);
2704
2705
    // paint
2706
0
    if (rFillGraphicPrimitive2D.hasTransparency())
2707
0
        cairo_paint_with_alpha(mpRT, 1.0 - rFillGraphicPrimitive2D.getTransparency());
2708
0
    else
2709
0
        cairo_paint(mpRT);
2710
2711
0
    static bool bRenderTransformationBounds(false);
2712
0
    if (bRenderTransformationBounds)
2713
0
    {
2714
0
        cairo_set_source_rgba(mpRT, 0, 1, 0, 0.8);
2715
0
        impl_cairo_set_hairline(mpRT, getViewInformation2D(),
2716
0
                                isCairoCoordinateLimitWorkaroundActive());
2717
        // full object
2718
0
        cairo_rectangle(mpRT, 0, 0, 1, 1);
2719
        // outline of pattern root image
2720
0
        cairo_rectangle(mpRT, aFillUnitRange.getMinX(), aFillUnitRange.getMinY(),
2721
0
                        aFillUnitRange.getWidth(), aFillUnitRange.getHeight());
2722
0
        cairo_stroke(mpRT);
2723
0
    }
2724
2725
0
    cairo_restore(mpRT);
2726
0
}
2727
2728
void CairoPixelProcessor2D::processFillGradientPrimitive2D_drawOutputRange(
2729
    const primitive2d::FillGradientPrimitive2D& rFillGradientPrimitive2D)
2730
0
{
2731
    // prepare outer color
2732
0
    basegfx::BColor aOuterColor(getGradientColor(rFillGradientPrimitive2D.getOuterColor()));
2733
0
    aOuterColor = maBColorModifierStack.getModifiedColor(aOuterColor);
2734
2735
0
    cairo_save(mpRT);
2736
2737
    // fill simple rect with outer color
2738
0
    if (rFillGradientPrimitive2D.hasAlphaGradient())
2739
0
    {
2740
0
        const attribute::FillGradientAttribute& rAlphaGradient(
2741
0
            rFillGradientPrimitive2D.getAlphaGradient());
2742
0
        double fLuminance(0.0);
2743
2744
0
        if (!rAlphaGradient.getColorStops().empty())
2745
0
        {
2746
0
            if (css::awt::GradientStyle_AXIAL == rAlphaGradient.getStyle())
2747
0
                fLuminance = rAlphaGradient.getColorStops().back().getStopColor().luminance();
2748
0
            else
2749
0
                fLuminance = rAlphaGradient.getColorStops().front().getStopColor().luminance();
2750
0
        }
2751
2752
0
        cairo_set_source_rgba(mpRT, aOuterColor.getRed(), aOuterColor.getGreen(),
2753
0
                              aOuterColor.getBlue(), 1.0 - fLuminance);
2754
0
    }
2755
0
    else
2756
0
    {
2757
0
        cairo_set_source_rgb(mpRT, aOuterColor.getRed(), aOuterColor.getGreen(),
2758
0
                             aOuterColor.getBlue());
2759
0
    }
2760
2761
0
    const basegfx::B2DHomMatrix aTrans(getViewInformation2D().getObjectToViewTransformation());
2762
0
    cairo_matrix_t aMatrix;
2763
0
    cairo_matrix_init(&aMatrix, aTrans.a(), aTrans.b(), aTrans.c(), aTrans.d(), aTrans.e(),
2764
0
                      aTrans.f());
2765
0
    cairo_set_matrix(mpRT, &aMatrix);
2766
2767
0
    const basegfx::B2DRange& rRange(rFillGradientPrimitive2D.getOutputRange());
2768
0
    cairo_rectangle(mpRT, rRange.getMinX(), rRange.getMinY(), rRange.getWidth(),
2769
0
                    rRange.getHeight());
2770
0
    cairo_fill(mpRT);
2771
2772
0
    cairo_restore(mpRT);
2773
0
}
2774
2775
bool CairoPixelProcessor2D::processFillGradientPrimitive2D_isCompletelyBordered(
2776
    const primitive2d::FillGradientPrimitive2D& rFillGradientPrimitive2D)
2777
0
{
2778
0
    const attribute::FillGradientAttribute& rFillGradient(
2779
0
        rFillGradientPrimitive2D.getFillGradient());
2780
0
    const double fBorder(rFillGradient.getBorder());
2781
2782
    // check if completely 'bordered out'. This can be the case for all
2783
    // types of gradients
2784
0
    if (basegfx::fTools::less(fBorder, 1.0) && fBorder >= 0.0)
2785
0
    {
2786
        // no, we have visible content besides border
2787
0
        return false;
2788
0
    }
2789
2790
    // draw all-covering polygon using getOuterColor and getOutputRange
2791
0
    processFillGradientPrimitive2D_drawOutputRange(rFillGradientPrimitive2D);
2792
0
    return true;
2793
0
}
2794
2795
void CairoPixelProcessor2D::processFillGradientPrimitive2D_linear_axial(
2796
    const primitive2d::FillGradientPrimitive2D& rFillGradientPrimitive2D)
2797
0
{
2798
0
    const attribute::FillGradientAttribute& rFillGradient(
2799
0
        rFillGradientPrimitive2D.getFillGradient());
2800
0
    assert(!rFillGradientPrimitive2D.hasAlphaGradient()
2801
0
           || rFillGradient.sameDefinitionThanAlpha(rFillGradientPrimitive2D.getAlphaGradient()));
2802
0
    assert(
2803
0
        (css::awt::GradientStyle_LINEAR == rFillGradientPrimitive2D.getFillGradient().getStyle()
2804
0
         || css::awt::GradientStyle_AXIAL == rFillGradientPrimitive2D.getFillGradient().getStyle())
2805
0
        && "SDPRCairo: Helper allows only SPECIFIED types (!)");
2806
0
    cairo_save(mpRT);
2807
2808
    // need to do 'antique' stuff adaptions for rotate/transitionStart in object coordinates
2809
    // (DefinitionRange) to have the right 'bending' on rotation
2810
0
    basegfx::B2DRange aAdaptedRange(rFillGradientPrimitive2D.getDefinitionRange());
2811
0
    const double fAngle(basegfx::normalizeToRange((2 * M_PI) - rFillGradient.getAngle(), 2 * M_PI));
2812
0
    const bool bAngle(!basegfx::fTools::equalZero(fAngle));
2813
0
    const basegfx::B2DPoint aCenter(aAdaptedRange.getCenter());
2814
2815
    // pack rotation and offset into a transformation covering that part
2816
0
    basegfx::B2DHomMatrix aRotation(basegfx::utils::createRotateAroundPoint(aCenter, fAngle));
2817
2818
    // create local transform to work in object coordinates based on OutputRange,
2819
    // combine with rotation - that way we can then just draw into AdaptedRange
2820
0
    basegfx::B2DHomMatrix aLocalTransform(getViewInformation2D().getObjectToViewTransformation()
2821
0
                                          * aRotation);
2822
0
    cairo_matrix_t aMatrix;
2823
0
    cairo_matrix_init(&aMatrix, aLocalTransform.a(), aLocalTransform.b(), aLocalTransform.c(),
2824
0
                      aLocalTransform.d(), aLocalTransform.e(), aLocalTransform.f());
2825
0
    cairo_set_matrix(mpRT, &aMatrix);
2826
2827
0
    if (bAngle)
2828
0
    {
2829
        // expand Range by rotating
2830
0
        aAdaptedRange.transform(aRotation);
2831
0
    }
2832
2833
    // create linear pattern in unit coordinates in y-direction
2834
0
    cairo_pattern_t* pPattern(
2835
0
        cairo_pattern_create_linear(aAdaptedRange.getCenterX(), aAdaptedRange.getMinY(),
2836
0
                                    aAdaptedRange.getCenterX(), aAdaptedRange.getMaxY()));
2837
2838
    // get color stops (make copy, might have to be changed)
2839
0
    basegfx::BColorStops aBColorStops(rFillGradient.getColorStops());
2840
0
    basegfx::BColorStops aBColorStopsAlpha;
2841
0
    const bool bHasAlpha(rFillGradientPrimitive2D.hasAlphaGradient());
2842
0
    if (bHasAlpha)
2843
0
        aBColorStopsAlpha = rFillGradientPrimitive2D.getAlphaGradient().getColorStops();
2844
0
    const bool bAxial(css::awt::GradientStyle_AXIAL == rFillGradient.getStyle());
2845
2846
    // get and apply border - create soace at start in gradient
2847
0
    const double fBorder(std::max(std::min(rFillGradient.getBorder(), 1.0), 0.0));
2848
0
    if (!basegfx::fTools::equalZero(fBorder))
2849
0
    {
2850
0
        if (bAxial)
2851
0
        {
2852
0
            aBColorStops.reverseColorStops();
2853
0
            if (bHasAlpha)
2854
0
                aBColorStopsAlpha.reverseColorStops();
2855
0
        }
2856
2857
0
        aBColorStops.createSpaceAtStart(fBorder);
2858
0
        if (bHasAlpha)
2859
0
            aBColorStopsAlpha.createSpaceAtStart(fBorder);
2860
2861
0
        if (bAxial)
2862
0
        {
2863
0
            aBColorStops.reverseColorStops();
2864
0
            if (bHasAlpha)
2865
0
                aBColorStopsAlpha.reverseColorStops();
2866
0
        }
2867
0
    }
2868
2869
0
    if (bAxial)
2870
0
    {
2871
        // expand with mirrored ColorStops to create axial
2872
0
        aBColorStops.doApplyAxial();
2873
0
        if (bHasAlpha)
2874
0
            aBColorStopsAlpha.doApplyAxial();
2875
0
    }
2876
2877
    // Apply steps if used to 'emulate' LO's 'discrete step' feature
2878
0
    if (rFillGradient.getSteps())
2879
0
    {
2880
0
        aBColorStops.doApplySteps(rFillGradient.getSteps());
2881
0
        if (bHasAlpha)
2882
0
            aBColorStopsAlpha.doApplySteps(rFillGradient.getSteps());
2883
0
    }
2884
2885
    // add color stops
2886
0
    for (size_t a(0); a < aBColorStops.size(); a++)
2887
0
    {
2888
0
        const basegfx::BColorStop& rStop = aBColorStops.getStop(a);
2889
0
        const basegfx::BColor aColor(maBColorModifierStack.getModifiedColor(rStop.getStopColor()));
2890
2891
0
        if (bHasAlpha)
2892
0
        {
2893
0
            const basegfx::BColor aAlpha(aBColorStopsAlpha.getStopColor(a));
2894
0
            cairo_pattern_add_color_stop_rgba(pPattern, rStop.getStopOffset(), aColor.getRed(),
2895
0
                                              aColor.getGreen(), aColor.getBlue(),
2896
0
                                              1.0 - aAlpha.luminance());
2897
0
        }
2898
0
        else
2899
0
        {
2900
0
            if (rFillGradientPrimitive2D.hasTransparency())
2901
0
            {
2902
0
                cairo_pattern_add_color_stop_rgba(pPattern, rStop.getStopOffset(), aColor.getRed(),
2903
0
                                                  aColor.getGreen(), aColor.getBlue(),
2904
0
                                                  1.0 - rFillGradientPrimitive2D.getTransparency());
2905
0
            }
2906
0
            else
2907
0
            {
2908
0
                cairo_pattern_add_color_stop_rgb(pPattern, rStop.getStopOffset(), aColor.getRed(),
2909
0
                                                 aColor.getGreen(), aColor.getBlue());
2910
0
            }
2911
0
        }
2912
0
    }
2913
2914
    // draw OutRange
2915
0
    basegfx::B2DRange aOutRange(rFillGradientPrimitive2D.getOutputRange());
2916
0
    if (bAngle)
2917
0
    {
2918
        // expand backwards to cover all area needed for OutputRange
2919
0
        aRotation.invert();
2920
0
        aOutRange.transform(aRotation);
2921
0
    }
2922
0
    cairo_rectangle(mpRT, aOutRange.getMinX(), aOutRange.getMinY(), aOutRange.getWidth(),
2923
0
                    aOutRange.getHeight());
2924
0
    cairo_set_source(mpRT, pPattern);
2925
0
    cairo_fill(mpRT);
2926
2927
    // cleanup
2928
0
    cairo_pattern_destroy(pPattern);
2929
0
    cairo_restore(mpRT);
2930
0
}
2931
2932
void CairoPixelProcessor2D::processFillGradientPrimitive2D_square_rect(
2933
    const primitive2d::FillGradientPrimitive2D& rFillGradientPrimitive2D)
2934
0
{
2935
0
    if (rFillGradientPrimitive2D.hasAlphaGradient() || rFillGradientPrimitive2D.hasTransparency())
2936
0
    {
2937
        // Do not use direct alpha for this: It paints using four trapez that
2938
        // do not add up at edges due to being painted AntiAliased; that means
2939
        // common pixels do not add up, but blend by transparency, so leaving
2940
        // visual traces -> process recursively
2941
0
        process(rFillGradientPrimitive2D);
2942
0
        return;
2943
0
    }
2944
2945
0
    assert(
2946
0
        (css::awt::GradientStyle_SQUARE == rFillGradientPrimitive2D.getFillGradient().getStyle()
2947
0
         || css::awt::GradientStyle_RECT == rFillGradientPrimitive2D.getFillGradient().getStyle())
2948
0
        && "SDPRCairo: Helper allows only SPECIFIED types (!)");
2949
0
    cairo_save(mpRT);
2950
2951
    // draw all-covering polygon using getOuterColor and getOutputRange,
2952
    // the partial paints below will not fill areas outside automatically
2953
    // as happens in the other gradient paints
2954
0
    processFillGradientPrimitive2D_drawOutputRange(rFillGradientPrimitive2D);
2955
2956
    // get DefinitionRange and adapt if needed
2957
0
    basegfx::B2DRange aAdaptedRange(rFillGradientPrimitive2D.getDefinitionRange());
2958
0
    const bool bSquare(css::awt::GradientStyle_SQUARE
2959
0
                       == rFillGradientPrimitive2D.getFillGradient().getStyle());
2960
0
    const basegfx::B2DPoint aCenter(aAdaptedRange.getCenter());
2961
0
    bool bLandscape(false);
2962
0
    double fSmallRadius(1.0);
2963
2964
    // get rotation and offset values
2965
0
    const attribute::FillGradientAttribute& rFillGradient(
2966
0
        rFillGradientPrimitive2D.getFillGradient());
2967
0
    const double fAngle(basegfx::normalizeToRange((2 * M_PI) - rFillGradient.getAngle(), 2 * M_PI));
2968
0
    const bool bAngle(!basegfx::fTools::equalZero(fAngle));
2969
0
    const double fOffxsetX(std::max(std::min(rFillGradient.getOffsetX(), 1.0), 0.0));
2970
0
    const double fOffxsetY(std::max(std::min(rFillGradient.getOffsetY(), 1.0), 0.0));
2971
2972
0
    if (bSquare)
2973
0
    {
2974
        // expand to make width == height
2975
0
        const basegfx::B2DRange& rDefRange(rFillGradientPrimitive2D.getDefinitionRange());
2976
2977
0
        if (rDefRange.getWidth() > rDefRange.getHeight())
2978
0
        {
2979
            // landscape -> square
2980
0
            const double fRadius(0.5 * rDefRange.getWidth());
2981
0
            aAdaptedRange.expand(basegfx::B2DPoint(rDefRange.getMinX(), aCenter.getY() - fRadius));
2982
0
            aAdaptedRange.expand(basegfx::B2DPoint(rDefRange.getMaxX(), aCenter.getY() + fRadius));
2983
0
        }
2984
0
        else
2985
0
        {
2986
            // portrait -> square
2987
0
            const double fRadius(0.5 * rDefRange.getHeight());
2988
0
            aAdaptedRange.expand(basegfx::B2DPoint(aCenter.getX() - fRadius, rDefRange.getMinY()));
2989
0
            aAdaptedRange.expand(basegfx::B2DPoint(aCenter.getX() + fRadius, rDefRange.getMaxY()));
2990
0
        }
2991
2992
0
        bLandscape = true;
2993
0
        fSmallRadius = 0.5 * aAdaptedRange.getWidth();
2994
0
    }
2995
0
    else
2996
0
    {
2997
0
        if (bAngle)
2998
0
        {
2999
            // expand range using applied rotation
3000
0
            aAdaptedRange.transform(basegfx::utils::createRotateAroundPoint(aCenter, fAngle));
3001
0
        }
3002
3003
        // set local params as needed for non-square
3004
0
        bLandscape = aAdaptedRange.getWidth() > aAdaptedRange.getHeight();
3005
0
        fSmallRadius = 0.5 * (bLandscape ? aAdaptedRange.getHeight() : aAdaptedRange.getWidth());
3006
0
    }
3007
3008
    // pack rotation and offset into a combined transformation that covers that parts
3009
0
    basegfx::B2DHomMatrix aRotAndTranslate;
3010
0
    aRotAndTranslate.translate(-aCenter.getX(), -aCenter.getY());
3011
0
    if (bAngle)
3012
0
        aRotAndTranslate.rotate(fAngle);
3013
0
    aRotAndTranslate.translate(aAdaptedRange.getMinX() + (fOffxsetX * aAdaptedRange.getWidth()),
3014
0
                               aAdaptedRange.getMinY() + (fOffxsetY * aAdaptedRange.getHeight()));
3015
3016
    // create local transform to work in object coordinates based on OutputRange,
3017
    // combine with rotation and offset - that way we can then just draw into
3018
    // AdaptedRange
3019
0
    basegfx::B2DHomMatrix aLocalTransform(getViewInformation2D().getObjectToViewTransformation()
3020
0
                                          * aRotAndTranslate);
3021
0
    cairo_matrix_t aMatrix;
3022
0
    cairo_matrix_init(&aMatrix, aLocalTransform.a(), aLocalTransform.b(), aLocalTransform.c(),
3023
0
                      aLocalTransform.d(), aLocalTransform.e(), aLocalTransform.f());
3024
0
    cairo_set_matrix(mpRT, &aMatrix);
3025
3026
    // get color stops (make copy, might have to be changed)
3027
0
    basegfx::BColorStops aBColorStops(rFillGradient.getColorStops());
3028
3029
    // apply BColorModifierStack early - the BColorStops are used multiple
3030
    // times below, so do this only once
3031
0
    if (0 != maBColorModifierStack.count())
3032
0
    {
3033
0
        aBColorStops.tryToApplyBColorModifierStack(maBColorModifierStack);
3034
0
    }
3035
3036
    // get and apply border - create soace at start in gradient
3037
0
    const double fBorder(std::max(std::min(rFillGradient.getBorder(), 1.0), 0.0));
3038
0
    if (!basegfx::fTools::equalZero(fBorder))
3039
0
    {
3040
0
        aBColorStops.createSpaceAtStart(fBorder);
3041
0
    }
3042
3043
    // Apply steps if used to 'emulate' LO's 'discrete step' feature
3044
0
    if (rFillGradient.getSteps())
3045
0
    {
3046
0
        aBColorStops.doApplySteps(rFillGradient.getSteps());
3047
0
    }
3048
3049
    // get half single pixel size to fill touching 'gaps'
3050
    // NOTE: I formally used cairo_device_to_user_distance, but that
3051
    // can indeed create negative sizes if the transformation e.g.
3052
    // contains rotation(s). could use fabs(), but just rely on
3053
    // linear algebra and use the (always positive) length of a vector
3054
0
    const double fHalfPx((getViewInformation2D().getInverseObjectToViewTransformation()
3055
0
                          * basegfx::B2DVector(1.0, 0.0))
3056
0
                             .getLength());
3057
3058
    // draw top part trapez/triangle
3059
0
    {
3060
0
        cairo_move_to(mpRT, aAdaptedRange.getMinX(), aAdaptedRange.getMinY());
3061
0
        cairo_line_to(mpRT, aAdaptedRange.getMaxX(), aAdaptedRange.getMinY());
3062
0
        cairo_line_to(mpRT, aAdaptedRange.getMaxX(), aAdaptedRange.getMinY() + fHalfPx);
3063
0
        if (!bSquare && bLandscape)
3064
0
        {
3065
0
            cairo_line_to(mpRT, aAdaptedRange.getMaxX() - fSmallRadius, aCenter.getY() + fHalfPx);
3066
0
            cairo_line_to(mpRT, aAdaptedRange.getMinX() + fSmallRadius, aCenter.getY() + fHalfPx);
3067
0
        }
3068
0
        else
3069
0
        {
3070
0
            cairo_line_to(mpRT, aCenter.getX(), aAdaptedRange.getMinY() + fSmallRadius + fHalfPx);
3071
0
        }
3072
0
        cairo_line_to(mpRT, aAdaptedRange.getMinX(), aAdaptedRange.getMinY() + fHalfPx);
3073
0
        cairo_close_path(mpRT);
3074
3075
        // create linear pattern in needed coordinates directly
3076
        // NOTE: I *tried* to create in unit coordinates and adapt modifying and re-using
3077
        // cairo_pattern_set_matrix - that *seems* to work but sometimes runs into
3078
        // numerical problems -> probably cairo implementation. So stay safe and do
3079
        // it the easy way, for the cost of re-creating gradient definitions (still cheap)
3080
0
        cairo_pattern_t* pPattern(cairo_pattern_create_linear(
3081
0
            aCenter.getX(), aAdaptedRange.getMinY(), aCenter.getX(),
3082
0
            aAdaptedRange.getMinY()
3083
0
                + (bLandscape ? aAdaptedRange.getHeight() * 0.5 : fSmallRadius)));
3084
0
        for (const auto& aStop : aBColorStops)
3085
0
        {
3086
0
            const basegfx::BColor& rColor(aStop.getStopColor());
3087
0
            cairo_pattern_add_color_stop_rgb(pPattern, aStop.getStopOffset(), rColor.getRed(),
3088
0
                                             rColor.getGreen(), rColor.getBlue());
3089
0
        }
3090
3091
0
        cairo_set_source(mpRT, pPattern);
3092
0
        cairo_fill(mpRT);
3093
0
        cairo_pattern_destroy(pPattern);
3094
0
    }
3095
3096
0
    {
3097
        // draw right part trapez/triangle
3098
0
        cairo_move_to(mpRT, aAdaptedRange.getMaxX(), aAdaptedRange.getMinY());
3099
0
        cairo_line_to(mpRT, aAdaptedRange.getMaxX(), aAdaptedRange.getMaxY());
3100
0
        if (bSquare || bLandscape)
3101
0
        {
3102
0
            cairo_line_to(mpRT, aAdaptedRange.getMaxX() - fSmallRadius - fHalfPx, aCenter.getY());
3103
0
        }
3104
0
        else
3105
0
        {
3106
0
            cairo_line_to(mpRT, aCenter.getX() - fHalfPx, aAdaptedRange.getMaxY() - fSmallRadius);
3107
0
            cairo_line_to(mpRT, aCenter.getX() - fHalfPx, aAdaptedRange.getMinY() + fSmallRadius);
3108
0
        }
3109
0
        cairo_close_path(mpRT);
3110
3111
        // create linear pattern in needed coordinates directly
3112
0
        cairo_pattern_t* pPattern(cairo_pattern_create_linear(
3113
0
            aAdaptedRange.getMaxX(), aCenter.getY(),
3114
0
            aAdaptedRange.getMaxX() - (bLandscape ? fSmallRadius : aAdaptedRange.getWidth() * 0.5),
3115
0
            aCenter.getY()));
3116
0
        for (const auto& aStop : aBColorStops)
3117
0
        {
3118
0
            const basegfx::BColor& rColor(aStop.getStopColor());
3119
0
            cairo_pattern_add_color_stop_rgb(pPattern, aStop.getStopOffset(), rColor.getRed(),
3120
0
                                             rColor.getGreen(), rColor.getBlue());
3121
0
        }
3122
3123
0
        cairo_set_source(mpRT, pPattern);
3124
0
        cairo_fill(mpRT);
3125
0
        cairo_pattern_destroy(pPattern);
3126
0
    }
3127
3128
0
    {
3129
        // draw bottom part trapez/triangle
3130
0
        cairo_move_to(mpRT, aAdaptedRange.getMaxX(), aAdaptedRange.getMaxY());
3131
0
        cairo_line_to(mpRT, aAdaptedRange.getMinX(), aAdaptedRange.getMaxY());
3132
0
        cairo_line_to(mpRT, aAdaptedRange.getMinX(), aAdaptedRange.getMaxY() - fHalfPx);
3133
0
        if (!bSquare && bLandscape)
3134
0
        {
3135
0
            cairo_line_to(mpRT, aAdaptedRange.getMinX() + fSmallRadius, aCenter.getY() - fHalfPx);
3136
0
            cairo_line_to(mpRT, aAdaptedRange.getMaxX() - fSmallRadius, aCenter.getY() - fHalfPx);
3137
0
        }
3138
0
        else
3139
0
        {
3140
0
            cairo_line_to(mpRT, aCenter.getX(), aAdaptedRange.getMaxY() - fSmallRadius - fHalfPx);
3141
0
        }
3142
0
        cairo_line_to(mpRT, aAdaptedRange.getMaxX(), aAdaptedRange.getMaxY() - fHalfPx);
3143
0
        cairo_close_path(mpRT);
3144
3145
        // create linear pattern in needed coordinates directly
3146
0
        cairo_pattern_t* pPattern(cairo_pattern_create_linear(
3147
0
            aCenter.getX(), aAdaptedRange.getMaxY(), aCenter.getX(),
3148
0
            aAdaptedRange.getMaxY()
3149
0
                - (bLandscape ? aAdaptedRange.getHeight() * 0.5 : fSmallRadius)));
3150
0
        for (const auto& aStop : aBColorStops)
3151
0
        {
3152
0
            const basegfx::BColor& rColor(aStop.getStopColor());
3153
0
            cairo_pattern_add_color_stop_rgb(pPattern, aStop.getStopOffset(), rColor.getRed(),
3154
0
                                             rColor.getGreen(), rColor.getBlue());
3155
0
        }
3156
3157
0
        cairo_set_source(mpRT, pPattern);
3158
0
        cairo_fill(mpRT);
3159
0
        cairo_pattern_destroy(pPattern);
3160
0
    }
3161
3162
0
    {
3163
        // draw left part trapez/triangle
3164
0
        cairo_move_to(mpRT, aAdaptedRange.getMinX(), aAdaptedRange.getMaxY());
3165
0
        cairo_line_to(mpRT, aAdaptedRange.getMinX(), aAdaptedRange.getMinY());
3166
0
        if (bSquare || bLandscape)
3167
0
        {
3168
0
            cairo_line_to(mpRT, aAdaptedRange.getMinX() + fSmallRadius + fHalfPx, aCenter.getY());
3169
0
        }
3170
0
        else
3171
0
        {
3172
0
            cairo_line_to(mpRT, aCenter.getX() + fHalfPx, aAdaptedRange.getMinY() + fSmallRadius);
3173
0
            cairo_line_to(mpRT, aCenter.getX() + fHalfPx, aAdaptedRange.getMaxY() - fSmallRadius);
3174
0
        }
3175
0
        cairo_close_path(mpRT);
3176
3177
        // create linear pattern in needed coordinates directly
3178
0
        cairo_pattern_t* pPattern(cairo_pattern_create_linear(
3179
0
            aAdaptedRange.getMinX(), aCenter.getY(),
3180
0
            aAdaptedRange.getMinX() + (bLandscape ? fSmallRadius : aAdaptedRange.getWidth() * 0.5),
3181
0
            aCenter.getY()));
3182
0
        for (const auto& aStop : aBColorStops)
3183
0
        {
3184
0
            const basegfx::BColor& rColor(aStop.getStopColor());
3185
0
            cairo_pattern_add_color_stop_rgb(pPattern, aStop.getStopOffset(), rColor.getRed(),
3186
0
                                             rColor.getGreen(), rColor.getBlue());
3187
0
        }
3188
3189
0
        cairo_set_source(mpRT, pPattern);
3190
0
        cairo_fill(mpRT);
3191
0
        cairo_pattern_destroy(pPattern);
3192
0
    }
3193
3194
    // cleanup
3195
0
    cairo_restore(mpRT);
3196
0
}
3197
3198
void CairoPixelProcessor2D::processFillGradientPrimitive2D_radial_elliptical(
3199
    const primitive2d::FillGradientPrimitive2D& rFillGradientPrimitive2D)
3200
0
{
3201
0
    const attribute::FillGradientAttribute& rFillGradient(
3202
0
        rFillGradientPrimitive2D.getFillGradient());
3203
0
    assert(!rFillGradientPrimitive2D.hasAlphaGradient()
3204
0
           || rFillGradient.sameDefinitionThanAlpha(rFillGradientPrimitive2D.getAlphaGradient()));
3205
0
    assert((css::awt::GradientStyle_RADIAL == rFillGradientPrimitive2D.getFillGradient().getStyle()
3206
0
            || css::awt::GradientStyle_ELLIPTICAL
3207
0
                   == rFillGradientPrimitive2D.getFillGradient().getStyle())
3208
0
           && "SDPRCairo: Helper allows only SPECIFIED types (!)");
3209
0
    cairo_save(mpRT);
3210
3211
    // need to do 'antique' stuff adaptions for rotate/transitionStart in object coordinates
3212
    // (DefinitionRange) to have the right 'bending' on rotation
3213
0
    const basegfx::B2DRange rDefRange(rFillGradientPrimitive2D.getDefinitionRange());
3214
0
    const basegfx::B2DPoint aCenter(rDefRange.getCenter());
3215
0
    double fRadius(1.0);
3216
0
    double fRatioElliptical(1.0);
3217
0
    const bool bRadial(css::awt::GradientStyle_RADIAL == rFillGradient.getStyle());
3218
3219
    // use what is done in initEllipticalGradientInfo method to get as close as
3220
    // possible to former stuff, expand AdaptedRange as needed
3221
0
    if (bRadial)
3222
0
    {
3223
0
        const double fHalfOriginalDiag(std::hypot(rDefRange.getWidth(), rDefRange.getHeight())
3224
0
                                       * 0.5);
3225
0
        fRadius = fHalfOriginalDiag;
3226
0
    }
3227
0
    else
3228
0
    {
3229
0
        double fTargetSizeX(M_SQRT2 * rDefRange.getWidth());
3230
0
        double fTargetSizeY(M_SQRT2 * rDefRange.getHeight());
3231
0
        fRatioElliptical = fTargetSizeX / fTargetSizeY;
3232
0
        fRadius = std::max(fTargetSizeX, fTargetSizeY) * 0.5;
3233
0
    }
3234
3235
    // get rotation and offset values
3236
0
    const double fAngle(basegfx::normalizeToRange((2 * M_PI) - rFillGradient.getAngle(), 2 * M_PI));
3237
0
    const bool bAngle(!basegfx::fTools::equalZero(fAngle));
3238
0
    const double fOffxsetX(std::max(std::min(rFillGradient.getOffsetX(), 1.0), 0.0));
3239
0
    const double fOffxsetY(std::max(std::min(rFillGradient.getOffsetY(), 1.0), 0.0));
3240
3241
    // pack rotation and offset into a combined transformation covering that parts
3242
0
    basegfx::B2DHomMatrix aRotAndTranslate;
3243
0
    aRotAndTranslate.translate(-aCenter.getX(), -aCenter.getY());
3244
0
    if (bAngle)
3245
0
        aRotAndTranslate.rotate(fAngle);
3246
0
    aRotAndTranslate.translate(rDefRange.getMinX() + (fOffxsetX * rDefRange.getWidth()),
3247
0
                               rDefRange.getMinY() + (fOffxsetY * rDefRange.getHeight()));
3248
3249
    // create local transform to work in object coordinates based on OutputRange,
3250
    // combine with rotation and offset - that way we can then just draw into
3251
    // AdaptedRange
3252
0
    basegfx::B2DHomMatrix aLocalTransform(getViewInformation2D().getObjectToViewTransformation()
3253
0
                                          * aRotAndTranslate);
3254
0
    cairo_matrix_t aMatrix;
3255
0
    cairo_matrix_init(&aMatrix, aLocalTransform.a(), aLocalTransform.b(), aLocalTransform.c(),
3256
0
                      aLocalTransform.d(), aLocalTransform.e(), aLocalTransform.f());
3257
0
    cairo_set_matrix(mpRT, &aMatrix);
3258
3259
    // create linear pattern in unit coordinates in y-direction
3260
0
    cairo_pattern_t* pPattern(cairo_pattern_create_radial(aCenter.getX(), aCenter.getY(), fRadius,
3261
0
                                                          aCenter.getX(), aCenter.getY(), 0.0));
3262
3263
    // get color stops (make copy, might have to be changed)
3264
0
    basegfx::BColorStops aBColorStops(rFillGradient.getColorStops());
3265
0
    basegfx::BColorStops aBColorStopsAlpha;
3266
0
    const bool bHasAlpha(rFillGradientPrimitive2D.hasAlphaGradient());
3267
0
    if (bHasAlpha)
3268
0
        aBColorStopsAlpha = rFillGradientPrimitive2D.getAlphaGradient().getColorStops();
3269
3270
    // get and apply border - create soace at start in gradient
3271
0
    const double fBorder(std::max(std::min(rFillGradient.getBorder(), 1.0), 0.0));
3272
0
    if (!basegfx::fTools::equalZero(fBorder))
3273
0
    {
3274
0
        aBColorStops.createSpaceAtStart(fBorder);
3275
0
        if (bHasAlpha)
3276
0
            aBColorStopsAlpha.createSpaceAtStart(fBorder);
3277
0
    }
3278
3279
    // Apply steps if used to 'emulate' LO's 'discrete step' feature
3280
0
    if (rFillGradient.getSteps())
3281
0
    {
3282
0
        aBColorStops.doApplySteps(rFillGradient.getSteps());
3283
0
        if (bHasAlpha)
3284
0
            aBColorStopsAlpha.doApplySteps(rFillGradient.getSteps());
3285
0
    }
3286
3287
    // add color stops
3288
0
    for (size_t a(0); a < aBColorStops.size(); a++)
3289
0
    {
3290
0
        const basegfx::BColorStop& rStop = aBColorStops.getStop(a);
3291
0
        const basegfx::BColor aColor(maBColorModifierStack.getModifiedColor(rStop.getStopColor()));
3292
3293
0
        if (bHasAlpha)
3294
0
        {
3295
0
            const basegfx::BColor aAlpha(aBColorStopsAlpha.getStopColor(a));
3296
0
            cairo_pattern_add_color_stop_rgba(pPattern, rStop.getStopOffset(), aColor.getRed(),
3297
0
                                              aColor.getGreen(), aColor.getBlue(),
3298
0
                                              1.0 - aAlpha.luminance());
3299
0
        }
3300
0
        else
3301
0
        {
3302
0
            if (rFillGradientPrimitive2D.hasTransparency())
3303
0
            {
3304
0
                cairo_pattern_add_color_stop_rgba(pPattern, rStop.getStopOffset(), aColor.getRed(),
3305
0
                                                  aColor.getGreen(), aColor.getBlue(),
3306
0
                                                  1.0 - rFillGradientPrimitive2D.getTransparency());
3307
0
            }
3308
0
            else
3309
0
            {
3310
0
                cairo_pattern_add_color_stop_rgb(pPattern, rStop.getStopOffset(), aColor.getRed(),
3311
0
                                                 aColor.getGreen(), aColor.getBlue());
3312
0
            }
3313
0
        }
3314
0
    }
3315
3316
0
    cairo_set_source(mpRT, pPattern);
3317
3318
0
    if (!bRadial) // css::awt::GradientStyle_ELLIPTICAL
3319
0
    {
3320
        // set cairo matrix at cairo_pattern_t to get needed ratio scale done.
3321
        // this is necessary since cairo_pattern_create_radial does *not*
3322
        // support ellipse resp. radial gradient with non-equidistant
3323
        // ratio directly
3324
        // this uses the transformation 'from user space to pattern space' as
3325
        // cairo docu states. That is the inverse of the intuitive thought
3326
        // model: describe from coordinates in texture, so use B2DHomMatrix
3327
        // and invert at the end to have better control about what has to happen
3328
0
        basegfx::B2DHomMatrix aTrans;
3329
3330
        // move center to origin to prepare scale/rotate
3331
0
        aTrans.translate(-aCenter.getX(), -aCenter.getY());
3332
3333
        // get scale factor and apply as needed
3334
0
        if (fRatioElliptical > 1.0)
3335
0
            aTrans.scale(1.0, 1.0 / fRatioElliptical);
3336
0
        else
3337
0
            aTrans.scale(fRatioElliptical, 1.0);
3338
3339
        // move transformed stuff back to center
3340
0
        aTrans.translate(aCenter.getX(), aCenter.getY());
3341
3342
        // invert and set at cairo_pattern_t
3343
0
        aTrans.invert();
3344
0
        cairo_matrix_init(&aMatrix, aTrans.a(), aTrans.b(), aTrans.c(), aTrans.d(), aTrans.e(),
3345
0
                          aTrans.f());
3346
0
        cairo_pattern_set_matrix(pPattern, &aMatrix);
3347
0
    }
3348
3349
    // draw OutRange. Due to rot and translate being part of the
3350
    // set transform in cairo we need to back-transform (and expand
3351
    // as needed) the OutputRange to paint at the right place and
3352
    // get all OutputRange covered
3353
0
    basegfx::B2DRange aOutRange(rFillGradientPrimitive2D.getOutputRange());
3354
0
    aRotAndTranslate.invert();
3355
0
    aOutRange.transform(aRotAndTranslate);
3356
0
    cairo_rectangle(mpRT, aOutRange.getMinX(), aOutRange.getMinY(), aOutRange.getWidth(),
3357
0
                    aOutRange.getHeight());
3358
0
    cairo_fill(mpRT);
3359
3360
    // cleanup
3361
0
    cairo_pattern_destroy(pPattern);
3362
0
    cairo_restore(mpRT);
3363
0
}
3364
3365
void CairoPixelProcessor2D::processFillGradientPrimitive2D_fallback_decompose(
3366
    const primitive2d::FillGradientPrimitive2D& rFillGradientPrimitive2D)
3367
0
{
3368
0
    if (rFillGradientPrimitive2D.hasAlphaGradient())
3369
0
    {
3370
        // process recursively to eliminate alpha, cannot be used in decompose fallback
3371
0
        process(rFillGradientPrimitive2D);
3372
0
        return;
3373
0
    }
3374
3375
    // this helper draws the given gradient using the decompose fallback,
3376
    // maybe needed in some cases an can/will be handy
3377
0
    cairo_save(mpRT);
3378
3379
    // draw all-covering initial BG polygon 1st using getOuterColor and getOutputRange
3380
0
    processFillGradientPrimitive2D_drawOutputRange(rFillGradientPrimitive2D);
3381
3382
    // bet basic form in unit coordinates
3383
0
    CairoPathHelper aForm(rFillGradientPrimitive2D.getUnitPolygon());
3384
3385
    // paint solid fill steps by providing callback as lambda
3386
0
    auto aCallback([this, &aForm](const basegfx::B2DHomMatrix& rMatrix,
3387
0
                                  const basegfx::BColor& rColor) {
3388
0
        const basegfx::B2DHomMatrix aTrans(getViewInformation2D().getObjectToViewTransformation()
3389
0
                                           * rMatrix);
3390
0
        cairo_matrix_t aMatrix;
3391
0
        cairo_matrix_init(&aMatrix, aTrans.a(), aTrans.b(), aTrans.c(), aTrans.d(), aTrans.e(),
3392
0
                          aTrans.f());
3393
0
        cairo_set_matrix(mpRT, &aMatrix);
3394
3395
0
        const basegfx::BColor aColor(maBColorModifierStack.getModifiedColor(rColor));
3396
0
        cairo_set_source_rgb(mpRT, aColor.getRed(), aColor.getGreen(), aColor.getBlue());
3397
3398
0
        cairo_append_path(mpRT, aForm.getCairoPath());
3399
3400
0
        cairo_fill(mpRT);
3401
0
    });
3402
3403
    // call value generator to trigger callbacks
3404
0
    rFillGradientPrimitive2D.generateMatricesAndColors(aCallback);
3405
3406
0
    cairo_restore(mpRT);
3407
0
}
3408
3409
void CairoPixelProcessor2D::processFillGradientPrimitive2D(
3410
    const primitive2d::FillGradientPrimitive2D& rFillGradientPrimitive2D)
3411
0
{
3412
0
    if (rFillGradientPrimitive2D.getDefinitionRange().isEmpty())
3413
0
    {
3414
        // no definition area, done
3415
0
        return;
3416
0
    }
3417
3418
0
    if (rFillGradientPrimitive2D.getOutputRange().isEmpty())
3419
0
    {
3420
        // no output area, done
3421
0
        return;
3422
0
    }
3423
3424
0
    const attribute::FillGradientAttribute& rFillGradient(
3425
0
        rFillGradientPrimitive2D.getFillGradient());
3426
3427
0
    if (rFillGradient.isDefault())
3428
0
    {
3429
        // no gradient definition, done
3430
0
        return;
3431
0
    }
3432
3433
    // check if completely 'bordered out'
3434
0
    if (processFillGradientPrimitive2D_isCompletelyBordered(rFillGradientPrimitive2D))
3435
0
    {
3436
        // yes, done, was processed as single filled rectangle (using getOuterColor())
3437
0
        return;
3438
0
    }
3439
3440
0
    constexpr DrawModeFlags SIMPLE_GRADIENT(DrawModeFlags::WhiteGradient
3441
0
                                            | DrawModeFlags::SettingsGradient);
3442
0
    const DrawModeFlags aDrawModeFlags(getViewInformation2D().getDrawModeFlags());
3443
0
    if (aDrawModeFlags & SIMPLE_GRADIENT)
3444
0
    {
3445
        // use simple, single-color OutputRange draw
3446
0
        processFillGradientPrimitive2D_drawOutputRange(rFillGradientPrimitive2D);
3447
0
        return;
3448
0
    }
3449
3450
0
    const bool bTemporaryGrayColorModifier(aDrawModeFlags & DrawModeFlags::GrayGradient);
3451
0
    if (bTemporaryGrayColorModifier)
3452
0
    {
3453
0
        const basegfx::BColorModifierSharedPtr aBColorModifier(
3454
0
            std::make_shared<basegfx::BColorModifier_gray>());
3455
0
        maBColorModifierStack.push(aBColorModifier);
3456
0
    }
3457
3458
    // evtl. prefer fallback: cairo does *not* render hard color transitions
3459
    // in gradients anti-aliased which is most visible in 'step'ed gradients,
3460
    // but may also happen in normal ones -> may need to be checked in
3461
    // basegfx::BColorStops (as tooling, like isSymmetrical() or similar).
3462
    // due to the nature of 'step'ing this also means a low number of
3463
    // filled polygons to be drawn (no 'smooth' parts to be replicated),
3464
    // so this is no runtime burner by definition.
3465
    // Making this configurable using static bool, may be moved to settings
3466
    // somewhere later. Do not forget to deactivate when working on 'step'ping
3467
    // stuff in the other helpers (!)
3468
0
    static bool bPreferAntiAliasedHardColorTransitions(true);
3469
3470
0
    if (bPreferAntiAliasedHardColorTransitions && rFillGradient.getSteps())
3471
0
    {
3472
0
        processFillGradientPrimitive2D_fallback_decompose(rFillGradientPrimitive2D);
3473
0
    }
3474
0
    else
3475
0
    {
3476
0
        switch (rFillGradient.getStyle())
3477
0
        {
3478
0
            case css::awt::GradientStyle_LINEAR:
3479
0
            case css::awt::GradientStyle_AXIAL:
3480
0
            {
3481
                // use specialized renderer for this cases - linear, axial
3482
0
                processFillGradientPrimitive2D_linear_axial(rFillGradientPrimitive2D);
3483
0
                break;
3484
0
            }
3485
0
            case css::awt::GradientStyle_RADIAL:
3486
0
            case css::awt::GradientStyle_ELLIPTICAL:
3487
0
            {
3488
                // use specialized renderer for this cases - radial, elliptical
3489
3490
                //  NOTE for css::awt::GradientStyle_ELLIPTICAL:
3491
                // The first time ever I will accept slight deviations for the
3492
                // elliptical case here due to it's old chaotic move-two-pixels inside
3493
                // rendering method that cannot be patched into a lineartransformation
3494
                // and is hard/difficult to support in more modern systems. Differences
3495
                // are small and mostly would be visible *if* in steps-mode what is
3496
                // also rare. IF that should make problems reactivation of that case
3497
                // for the default case below is possible. main reason is that speed
3498
                // for direct rendering in cairo is much better.
3499
0
                processFillGradientPrimitive2D_radial_elliptical(rFillGradientPrimitive2D);
3500
0
                break;
3501
0
            }
3502
0
            case css::awt::GradientStyle_SQUARE:
3503
0
            case css::awt::GradientStyle_RECT:
3504
0
            {
3505
                // use specialized renderer for this cases - square, rect
3506
                // NOTE: *NO* support for FillGradientAlpha here. it is anyways
3507
                // hard to map these to direct rendering, but to do so the four
3508
                // trapezoids/sides are 'stitched' together, so painting RGBA
3509
                // directly will make the overlaps look bad and like errors.
3510
                // Anyways, these gradient types are only our internal heritage
3511
                // and rendering them directly is already much faster, will be okay.
3512
0
                processFillGradientPrimitive2D_square_rect(rFillGradientPrimitive2D);
3513
0
                break;
3514
0
            }
3515
0
            default:
3516
0
            {
3517
                // NOTE: All cases are covered above, but keep this as fallback,
3518
                // so it is possible anytime to exclude one of the cases above again
3519
                // and go back to decomposed version - just in case...
3520
0
                processFillGradientPrimitive2D_fallback_decompose(rFillGradientPrimitive2D);
3521
0
                break;
3522
0
            }
3523
0
        }
3524
0
    }
3525
3526
0
    if (bTemporaryGrayColorModifier)
3527
        // cleanup temporary BColorModifier
3528
0
        maBColorModifierStack.pop();
3529
0
}
3530
3531
void CairoPixelProcessor2D::processPatternFillPrimitive2D(
3532
    const primitive2d::PatternFillPrimitive2D& rPrimitive)
3533
0
{
3534
0
    if (!mpTargetOutputDevice)
3535
0
        return;
3536
0
    const basegfx::B2DRange& rReferenceRange = rPrimitive.getReferenceRange();
3537
0
    if (rReferenceRange.isEmpty() || rReferenceRange.getWidth() <= 0.0
3538
0
        || rReferenceRange.getHeight() <= 0.0)
3539
0
        return;
3540
0
    basegfx::B2DPolyPolygon aMask = rPrimitive.getMask();
3541
0
    aMask.transform(getViewInformation2D().getObjectToViewTransformation());
3542
0
    const basegfx::B2DRange aMaskRange(aMask.getB2DRange());
3543
0
    if (aMaskRange.isEmpty() || aMaskRange.getWidth() <= 0.0 || aMaskRange.getHeight() <= 0.0)
3544
0
        return;
3545
0
    sal_uInt32 nTileWidth, nTileHeight;
3546
0
    rPrimitive.getTileSize(nTileWidth, nTileHeight, getViewInformation2D());
3547
0
    if (nTileWidth == 0 || nTileHeight == 0)
3548
0
        return;
3549
0
    Bitmap aTileImage = rPrimitive.createTileImage(nTileWidth, nTileHeight);
3550
0
    tools::Rectangle aMaskRect = vcl::unotools::rectangleFromB2DRectangle(aMaskRange);
3551
    // Unless smooth edges are needed, simply use clipping.
3552
0
    if (basegfx::utils::isRectangle(aMask) || !getViewInformation2D().getUseAntiAliasing())
3553
0
    {
3554
0
        mpTargetOutputDevice->Push(vcl::PushFlags::CLIPREGION);
3555
0
        mpTargetOutputDevice->IntersectClipRegion(vcl::Region(aMask));
3556
0
        mpTargetOutputDevice->DrawWallpaper(aMaskRect, Wallpaper(aTileImage));
3557
0
        mpTargetOutputDevice->Pop();
3558
0
        return;
3559
0
    }
3560
3561
    // if the tile is a single pixel big, just flood fill with that pixel color
3562
0
    if (nTileWidth == 1 && nTileHeight == 1)
3563
0
    {
3564
0
        Color col = aTileImage.GetPixelColor(0, 0);
3565
0
        mpTargetOutputDevice->SetLineColor(col);
3566
0
        mpTargetOutputDevice->SetFillColor(col);
3567
0
        mpTargetOutputDevice->DrawPolyPolygon(aMask);
3568
0
        return;
3569
0
    }
3570
3571
0
    impBufferDevice aBufferDevice(*mpTargetOutputDevice, aMaskRect);
3572
0
    if (!aBufferDevice.isVisible())
3573
0
        return;
3574
    // remember last OutDev and set to content
3575
0
    OutputDevice* pLastOutputDevice = mpTargetOutputDevice;
3576
0
    mpTargetOutputDevice = &aBufferDevice.getContent();
3577
0
    mpTargetOutputDevice->DrawWallpaper(aMaskRect, Wallpaper(aTileImage));
3578
    // back to old OutDev
3579
0
    mpTargetOutputDevice = pLastOutputDevice;
3580
    // draw mask
3581
0
    VirtualDevice& rMask = aBufferDevice.getTransparence();
3582
0
    rMask.SetLineColor();
3583
0
    rMask.SetFillColor(COL_BLACK);
3584
0
    rMask.DrawPolyPolygon(aMask);
3585
    // dump buffer to outdev
3586
0
    aBufferDevice.paint();
3587
0
}
3588
3589
void CairoPixelProcessor2D::processPolyPolygonRGBAPrimitive2D(
3590
    const primitive2d::PolyPolygonRGBAPrimitive2D& rPolyPolygonRGBAPrimitive2D)
3591
0
{
3592
0
    if (getViewInformation2D().getDrawModeFlags() & DrawModeFlags::NoFill)
3593
        // NoFill wanted, done
3594
0
        return;
3595
3596
0
    const basegfx::BColor aFillColor(getFillColor(rPolyPolygonRGBAPrimitive2D.getBColor()));
3597
3598
0
    if (!rPolyPolygonRGBAPrimitive2D.hasTransparency())
3599
0
    {
3600
        // do what CairoPixelProcessor2D::processPolyPolygonColorPrimitive2D does
3601
0
        paintPolyPolygonRGBA(rPolyPolygonRGBAPrimitive2D.getB2DPolyPolygon(), aFillColor);
3602
0
        return;
3603
0
    }
3604
3605
    // draw with alpha directly
3606
0
    paintPolyPolygonRGBA(rPolyPolygonRGBAPrimitive2D.getB2DPolyPolygon(), aFillColor,
3607
0
                         rPolyPolygonRGBAPrimitive2D.getTransparency());
3608
0
}
3609
3610
void CairoPixelProcessor2D::processPolyPolygonAlphaGradientPrimitive2D(
3611
    const primitive2d::PolyPolygonAlphaGradientPrimitive2D& rPolyPolygonAlphaGradientPrimitive2D)
3612
0
{
3613
0
    if (getViewInformation2D().getDrawModeFlags() & DrawModeFlags::NoFill)
3614
        // NoFill wanted, done
3615
0
        return;
3616
3617
0
    const basegfx::B2DPolyPolygon& rPolyPolygon(
3618
0
        rPolyPolygonAlphaGradientPrimitive2D.getB2DPolyPolygon());
3619
0
    if (0 == rPolyPolygon.count())
3620
0
    {
3621
        // no geometry, done
3622
0
        return;
3623
0
    }
3624
3625
0
    basegfx::BColor aFillColor(getFillColor(rPolyPolygonAlphaGradientPrimitive2D.getBColor()));
3626
0
    aFillColor = maBColorModifierStack.getModifiedColor(aFillColor);
3627
3628
0
    const attribute::FillGradientAttribute& rAlphaGradient(
3629
0
        rPolyPolygonAlphaGradientPrimitive2D.getAlphaGradient());
3630
0
    if (rAlphaGradient.isDefault())
3631
0
    {
3632
        // default is a single ColorStop at 0.0 with black (0, 0, 0). The
3633
        // luminance is then 0.0, too -> not transparent at all
3634
0
        paintPolyPolygonRGBA(rPolyPolygon, aFillColor);
3635
0
        return;
3636
0
    }
3637
3638
0
    basegfx::BColor aSingleColor;
3639
0
    const basegfx::BColorStops& rAlphaStops(rAlphaGradient.getColorStops());
3640
0
    if (rAlphaStops.isSingleColor(aSingleColor))
3641
0
    {
3642
        // draw with alpha directly
3643
0
        paintPolyPolygonRGBA(rPolyPolygon, aFillColor, aSingleColor.luminance());
3644
0
        return;
3645
0
    }
3646
3647
0
    const css::awt::GradientStyle aStyle(rAlphaGradient.getStyle());
3648
0
    if (css::awt::GradientStyle_SQUARE == aStyle || css::awt::GradientStyle_RECT == aStyle)
3649
0
    {
3650
        // direct paint cannot be used for these styles since they get 'stitched'
3651
        // by multiple parts, so *need* single alpha for multiple pieces, go
3652
        // with decompose/recursion
3653
0
        process(rPolyPolygonAlphaGradientPrimitive2D);
3654
0
        return;
3655
0
    }
3656
3657
    // render as FillGradientPrimitive2D. The idea is to create BColorStops
3658
    // with the same number of entries, but all the same color, using the
3659
    // polygon's target fill color, so we can directly paint gradients as
3660
    // RGBA in Cairo
3661
0
    basegfx::BColorStops aColorStops;
3662
3663
    // create ColorStops at same stops but single color
3664
0
    aColorStops.reserve(rAlphaStops.size());
3665
0
    for (const auto& entry : rAlphaStops)
3666
0
        aColorStops.addStop(entry.getStopOffset(), aFillColor);
3667
3668
    // create FillGradient using that single-color ColorStops
3669
0
    const attribute::FillGradientAttribute aFillGradient(
3670
0
        rAlphaGradient.getStyle(), rAlphaGradient.getBorder(), rAlphaGradient.getOffsetX(),
3671
0
        rAlphaGradient.getOffsetY(), rAlphaGradient.getAngle(), aColorStops,
3672
0
        rAlphaGradient.getSteps());
3673
3674
    // create temporary FillGradientPrimitive2D, but do not forget
3675
    // to embed to MaskPrimitive2D to get the PolyPolygon form
3676
0
    const basegfx::B2DRange aRange(rPolyPolygon.getB2DRange());
3677
0
    const primitive2d::Primitive2DContainer aContainerMaskedFillGradient{
3678
0
        rtl::Reference<primitive2d::MaskPrimitive2D>(new primitive2d::MaskPrimitive2D(
3679
0
            rPolyPolygon,
3680
0
            primitive2d::Primitive2DContainer{ rtl::Reference<primitive2d::FillGradientPrimitive2D>(
3681
0
                new primitive2d::FillGradientPrimitive2D(aRange, // OutputRange
3682
0
                                                         aRange, // DefinitionRange
3683
0
                                                         aFillGradient, &rAlphaGradient)) }))
3684
0
    };
3685
3686
    // render this. Use container to not trigger decompose for temporary content
3687
0
    process(aContainerMaskedFillGradient);
3688
0
}
3689
3690
void CairoPixelProcessor2D::processBitmapAlphaPrimitive2D(
3691
    const primitive2d::BitmapAlphaPrimitive2D& rBitmapAlphaPrimitive2D)
3692
0
{
3693
0
    constexpr DrawModeFlags BITMAP(DrawModeFlags::BlackBitmap | DrawModeFlags::WhiteBitmap
3694
0
                                   | DrawModeFlags::GrayBitmap);
3695
0
    const DrawModeFlags aDrawModeFlags(getViewInformation2D().getDrawModeFlags());
3696
0
    const bool bDrawModeFlagsUsed(aDrawModeFlags & BITMAP);
3697
3698
0
    if (bDrawModeFlagsUsed)
3699
0
    {
3700
0
        if (aDrawModeFlags & DrawModeFlags::BlackBitmap)
3701
0
        {
3702
0
            const basegfx::BColorModifierSharedPtr aBColorModifier(
3703
0
                std::make_shared<basegfx::BColorModifier_replace>(basegfx::BColor(0, 0, 0)));
3704
0
            maBColorModifierStack.push(aBColorModifier);
3705
0
        }
3706
0
        else if (aDrawModeFlags & DrawModeFlags::WhiteBitmap)
3707
0
        {
3708
0
            const basegfx::BColorModifierSharedPtr aBColorModifier(
3709
0
                std::make_shared<basegfx::BColorModifier_replace>(basegfx::BColor(1, 1, 1)));
3710
0
            maBColorModifierStack.push(aBColorModifier);
3711
0
        }
3712
0
        else // DrawModeFlags::GrayBitmap
3713
0
        {
3714
0
            const basegfx::BColorModifierSharedPtr aBColorModifier(
3715
0
                std::make_shared<basegfx::BColorModifier_gray>());
3716
0
            maBColorModifierStack.push(aBColorModifier);
3717
0
        }
3718
0
    }
3719
3720
0
    if (!rBitmapAlphaPrimitive2D.hasTransparency())
3721
0
    {
3722
        // do what CairoPixelProcessor2D::processPolyPolygonColorPrimitive2D does
3723
0
        paintBitmapAlpha(rBitmapAlphaPrimitive2D.getBitmap(),
3724
0
                         rBitmapAlphaPrimitive2D.getTransform());
3725
0
    }
3726
0
    else
3727
0
    {
3728
        // draw with alpha directly
3729
0
        paintBitmapAlpha(rBitmapAlphaPrimitive2D.getBitmap(),
3730
0
                         rBitmapAlphaPrimitive2D.getTransform(),
3731
0
                         rBitmapAlphaPrimitive2D.getTransparency());
3732
0
    }
3733
3734
0
    if (bDrawModeFlagsUsed)
3735
0
        maBColorModifierStack.pop();
3736
0
}
3737
3738
void CairoPixelProcessor2D::processTextSimplePortionPrimitive2D(
3739
    const primitive2d::TextSimplePortionPrimitive2D& rCandidate)
3740
458
{
3741
458
    if (SAL_LIKELY(mbRenderSimpleTextDirect))
3742
0
    {
3743
0
        renderTextSimpleOrDecoratedPortionPrimitive2D(rCandidate, nullptr);
3744
0
    }
3745
458
    else
3746
458
    {
3747
458
        process(rCandidate);
3748
458
    }
3749
458
}
3750
3751
void CairoPixelProcessor2D::processTextDecoratedPortionPrimitive2D(
3752
    const primitive2d::TextDecoratedPortionPrimitive2D& rCandidate)
3753
0
{
3754
0
    if (SAL_LIKELY(mbRenderDecoratedTextDirect))
3755
0
    {
3756
0
        if (!rCandidate.getOrCreateBrokenUpText().empty())
3757
0
        {
3758
            // if BrokenUpText/WordLineMode is used, go into recursion
3759
            // with single snippets
3760
0
            process(rCandidate.getOrCreateBrokenUpText());
3761
0
            return;
3762
0
        }
3763
3764
0
        renderTextSimpleOrDecoratedPortionPrimitive2D(rCandidate, &rCandidate);
3765
0
    }
3766
0
    else
3767
0
    {
3768
0
        process(rCandidate);
3769
0
    }
3770
0
}
3771
3772
void CairoPixelProcessor2D::renderTextBackground(
3773
    const primitive2d::TextSimplePortionPrimitive2D& rTextCandidate, double fAscent,
3774
    double fDescent, const basegfx::B2DHomMatrix& rTransform, double fTextWidth)
3775
0
{
3776
0
    cairo_save(mpRT);
3777
0
    cairo_matrix_t aMatrix;
3778
0
    cairo_matrix_init(&aMatrix, rTransform.a(), rTransform.b(), rTransform.c(), rTransform.d(),
3779
0
                      rTransform.e(), rTransform.f());
3780
0
    cairo_set_matrix(mpRT, &aMatrix);
3781
0
    basegfx::BColor aFillColor(getFillColor(rTextCandidate.getTextFillColor().getBColor()));
3782
0
    aFillColor = maBColorModifierStack.getModifiedColor(aFillColor);
3783
0
    cairo_set_source_rgb(mpRT, aFillColor.getRed(), aFillColor.getGreen(), aFillColor.getBlue());
3784
    // Disable anti-aliasing so adjacent background rectangles share exact pixel
3785
    // edges with no visible seam between them.
3786
0
    cairo_antialias_t eOldAA = cairo_get_antialias(mpRT);
3787
0
    cairo_set_antialias(mpRT, CAIRO_ANTIALIAS_NONE);
3788
0
    cairo_rectangle(mpRT, 0.0, -fAscent, fTextWidth, fAscent + fDescent);
3789
0
    cairo_fill(mpRT);
3790
0
    cairo_set_antialias(mpRT, eOldAA);
3791
0
    cairo_restore(mpRT);
3792
0
}
3793
3794
void CairoPixelProcessor2D::renderSalLayout(const std::unique_ptr<SalLayout>& rSalLayout,
3795
                                            const basegfx::BColor& rTextColor,
3796
                                            const basegfx::B2DHomMatrix& rTransform,
3797
                                            bool bAntiAliase) const
3798
0
{
3799
0
    cairo_save(mpRT);
3800
0
    cairo_matrix_t aMatrix;
3801
0
    cairo_matrix_init(&aMatrix, rTransform.a(), rTransform.b(), rTransform.c(), rTransform.d(),
3802
0
                      rTransform.e(), rTransform.f());
3803
0
    cairo_set_matrix(mpRT, &aMatrix);
3804
0
    rSalLayout->drawSalLayout(mpRT, rTextColor, bAntiAliase);
3805
0
    cairo_restore(mpRT);
3806
0
}
3807
3808
void CairoPixelProcessor2D::renderTextDecorationWithOptionalTransformAndColor(
3809
    const primitive2d::TextDecoratedPortionPrimitive2D& rDecoratedCandidate,
3810
    const basegfx::utils::B2DHomMatrixBufferedOnDemandDecompose& rDecTrans,
3811
    const basegfx::B2DHomMatrix* pOptionalObjectTransform, const basegfx::BColor* pReplacementColor)
3812
0
{
3813
    // get decorations from Primitive (using original TextTransform),
3814
    // guaranteed the same visualization as a decomposition would create
3815
0
    const primitive2d::Primitive2DContainer& rDecorationGeometryContent(
3816
0
        rDecoratedCandidate.getOrCreateDecorationGeometryContent(
3817
0
            rDecTrans, rDecoratedCandidate.getText(), rDecoratedCandidate.getTextPosition(),
3818
0
            rDecoratedCandidate.getTextLength(), rDecoratedCandidate.getDXArray()));
3819
3820
0
    if (rDecorationGeometryContent.empty())
3821
0
    {
3822
        // no decoration, done
3823
0
        return;
3824
0
    }
3825
3826
    // modify ColorStack as needed - if needed
3827
0
    if (nullptr != pReplacementColor)
3828
0
        maBColorModifierStack.push(
3829
0
            std::make_shared<basegfx::BColorModifier_replace>(*pReplacementColor));
3830
3831
    // modify transformation as needed - if needed
3832
0
    const geometry::ViewInformation2D aLastViewInformation2D(getViewInformation2D());
3833
0
    if (nullptr != pOptionalObjectTransform)
3834
0
    {
3835
0
        geometry::ViewInformation2D aViewInformation2D(getViewInformation2D());
3836
0
        aViewInformation2D.setObjectTransformation(*pOptionalObjectTransform);
3837
0
        setViewInformation2D(aViewInformation2D);
3838
0
    }
3839
3840
    // render primitives
3841
0
    process(rDecorationGeometryContent);
3842
3843
    // restore mods
3844
0
    if (nullptr != pOptionalObjectTransform)
3845
0
        setViewInformation2D(aLastViewInformation2D);
3846
0
    if (nullptr != pReplacementColor)
3847
0
        maBColorModifierStack.pop();
3848
0
}
3849
3850
void CairoPixelProcessor2D::renderTextSimpleOrDecoratedPortionPrimitive2D(
3851
    const primitive2d::TextSimplePortionPrimitive2D& rTextCandidate,
3852
    const primitive2d::TextDecoratedPortionPrimitive2D* pDecoratedCandidate)
3853
0
{
3854
0
    primitive2d::TextLayouterDevice aTextLayouter;
3855
0
    rTextCandidate.createTextLayouter(aTextLayouter);
3856
0
    std::unique_ptr<SalLayout> pSalLayout(rTextCandidate.createSalLayout(aTextLayouter));
3857
3858
0
    if (!pSalLayout)
3859
0
    {
3860
        // got no layout, error. use decompose as fallback
3861
0
        process(rTextCandidate);
3862
0
        return;
3863
0
    }
3864
3865
    // prepare local transformations
3866
0
    basegfx::utils::B2DHomMatrixBufferedOnDemandDecompose aDecTrans(
3867
0
        rTextCandidate.getTextTransform());
3868
0
    const basegfx::B2DHomMatrix aObjTransformWithoutScale(
3869
0
        basegfx::utils::createShearXRotateTranslateB2DHomMatrix(
3870
0
            aDecTrans.getShearX(), aDecTrans.getRotate(), aDecTrans.getTranslate()));
3871
0
    const basegfx::B2DHomMatrix aFullTextTransform(
3872
0
        getViewInformation2D().getObjectToViewTransformation() * aObjTransformWithoutScale);
3873
3874
0
    if (!rTextCandidate.getTextFillColor().IsTransparent())
3875
0
    {
3876
        // render TextBackground first -> casts no shadow itself, so do independent of
3877
        // text shadow being activated
3878
0
        double fAscent(aTextLayouter.getFontAscent());
3879
0
        double fDescent(aTextLayouter.getFontDescent());
3880
3881
0
        if (nullptr != pDecoratedCandidate
3882
0
            && primitive2d::TEXT_FONT_EMPHASIS_MARK_NONE
3883
0
                   != pDecoratedCandidate->getTextEmphasisMark())
3884
0
        {
3885
0
            if (pDecoratedCandidate->getEmphasisMarkAbove())
3886
0
                fAscent += aTextLayouter.getTextHeight() * (250.0 / 1000.0);
3887
0
            if (pDecoratedCandidate->getEmphasisMarkBelow())
3888
0
                fDescent += aTextLayouter.getTextHeight() * (250.0 / 1000.0);
3889
0
        }
3890
3891
0
        const sal_uInt8 nProportionalFontSize(rTextCandidate.getProportionalFontSize());
3892
0
        assert(nProportionalFontSize > 0);
3893
0
        double fBgWidth(pSalLayout->GetTextWidth());
3894
0
        if (nProportionalFontSize != 100)
3895
0
        {
3896
0
            const double fScale(100.0 / nProportionalFontSize);
3897
0
            const double fEscOffset(rTextCandidate.getEscapement() / -100.0
3898
0
                                    * aDecTrans.getScale().getY() * fScale);
3899
0
            fAscent = fEscOffset + fAscent * fScale;
3900
0
            fDescent = fDescent * fScale - fEscOffset;
3901
3902
            // trim trailing whitespace from background width to not have background over
3903
            // trailing whitespace, since that looks like an error for the user.
3904
0
            const auto& rDXArray(rTextCandidate.getDXArray());
3905
0
            if (!rDXArray.empty())
3906
0
            {
3907
0
                const OUString& rText(rTextCandidate.getText());
3908
0
                sal_Int32 nLast(rTextCandidate.getTextPosition() + rTextCandidate.getTextLength()
3909
0
                                - 1);
3910
0
                sal_Int32 nFirst(rTextCandidate.getTextPosition());
3911
0
                while (nLast >= nFirst && rText[nLast] == ' ')
3912
0
                    nLast--;
3913
0
                sal_Int32 nTrimmedLen(nLast - nFirst + 1);
3914
0
                if (nTrimmedLen > 0 && nTrimmedLen < rTextCandidate.getTextLength())
3915
0
                    fBgWidth = rDXArray[nTrimmedLen - 1];
3916
0
                else if (nTrimmedLen <= 0)
3917
0
                    fBgWidth = 0;
3918
0
            }
3919
0
        }
3920
3921
0
        renderTextBackground(rTextCandidate, fAscent, fDescent, aFullTextTransform, fBgWidth);
3922
0
    }
3923
3924
    // get TextColor early, may have to be modified
3925
0
    basegfx::BColor aTextColor(getTextColor(rTextCandidate.getFontColor()));
3926
3927
0
    if (rTextCandidate.hasShadow())
3928
0
    {
3929
        // Text shadow is constant, relative to font size, *not* rotated with
3930
        // text (always from top-left!)
3931
0
        static const double fFactor(1.0 / 24.0);
3932
0
        const double fTextShadowOffset(aDecTrans.getScale().getY() * fFactor);
3933
3934
        // see ::ImplDrawSpecialText -> no longer simple fixed color
3935
0
        const basegfx::BColor aBlack(0.0, 0.0, 0.0);
3936
0
        basegfx::BColor aShadowColor(aBlack);
3937
0
        if (aBlack == aTextColor || aTextColor.luminance() < (8.0 / 255.0))
3938
0
            aShadowColor = COL_LIGHTGRAY.getBColor();
3939
0
        aShadowColor = maBColorModifierStack.getModifiedColor(aShadowColor);
3940
3941
        // create shadow offset
3942
0
        const basegfx::B2DHomMatrix aShadowTransform(
3943
0
            basegfx::utils::createTranslateB2DHomMatrix(fTextShadowOffset, fTextShadowOffset));
3944
0
        const basegfx::B2DHomMatrix aShadowFullTextTransform(
3945
            // right to left: 1st the ObjTrans, then the shadow offset, last ObjToView. That way
3946
            // the shadow is always from top-left, independent of text rotation. Independent from
3947
            // thinking about if that is wanted (shadow direction *could* rotate with the text)
3948
            // this is what the office currently does -> do *not* change visualization (!)
3949
0
            getViewInformation2D().getObjectToViewTransformation() * aShadowTransform
3950
0
            * aObjTransformWithoutScale);
3951
3952
        // render text as shadow
3953
0
        renderSalLayout(pSalLayout, aShadowColor, aShadowFullTextTransform,
3954
0
                        getViewInformation2D().getUseAntiAliasing());
3955
3956
0
        if (rTextCandidate.hasTextDecoration())
3957
0
        {
3958
0
            const basegfx::B2DHomMatrix aTransform(getViewInformation2D().getObjectTransformation()
3959
0
                                                   * aShadowTransform);
3960
0
            renderTextDecorationWithOptionalTransformAndColor(*pDecoratedCandidate, aDecTrans,
3961
0
                                                              &aTransform, &aShadowColor);
3962
0
        }
3963
0
    }
3964
0
    if (rTextCandidate.hasOutline())
3965
0
    {
3966
        // render as outline
3967
0
        aTextColor = maBColorModifierStack.getModifiedColor(aTextColor);
3968
0
        basegfx::B2DHomMatrix aInvViewTransform;
3969
3970
        // discrete offsets defined here to easily allow to change them,
3971
        // e.g. if more 'fat' outline is wanted, it may be increased to 1.5
3972
0
        constexpr double fZero(0.0);
3973
0
        constexpr double fPlus(1.0);
3974
0
        constexpr double fMinus(-1.0);
3975
3976
0
        static constexpr std::array<std::pair<double, double>, 8> offsets{
3977
0
            std::pair<double, double>{ fMinus, fMinus }, std::pair<double, double>{ fZero, fMinus },
3978
0
            std::pair<double, double>{ fPlus, fMinus },  std::pair<double, double>{ fMinus, fZero },
3979
0
            std::pair<double, double>{ fPlus, fZero },   std::pair<double, double>{ fMinus, fPlus },
3980
0
            std::pair<double, double>{ fZero, fPlus },   std::pair<double, double>{ fPlus, fPlus }
3981
0
        };
3982
3983
0
        if (rTextCandidate.hasTextDecoration())
3984
0
        {
3985
            // to use discrete offset (pixels) we will need the back-transform from
3986
            // discrete view coordinates to 'world' coordinates (logic view coordinates),
3987
            // this is the inverse ViewTransformation.
3988
            // NOTE: Alternatively we could calculate the lengths for fPlus/fMinus in
3989
            // logic view coordinates, but would need to create another B2DHomMatrix and
3990
            // to do it correct would need to handle two vectors holding the directions,
3991
            // else - if ever someone will rotate/shear that transformation - it would
3992
            // break
3993
0
            aInvViewTransform = getViewInformation2D().getViewTransformation();
3994
0
            aInvViewTransform.invert();
3995
0
        }
3996
3997
0
        for (const auto& offset : offsets)
3998
0
        {
3999
0
            const basegfx::B2DHomMatrix aDiscreteOffset(
4000
0
                basegfx::utils::createTranslateB2DHomMatrix(offset.first, offset.second));
4001
0
            renderSalLayout(pSalLayout, aTextColor, aDiscreteOffset * aFullTextTransform,
4002
0
                            getViewInformation2D().getUseAntiAliasing());
4003
0
            if (rTextCandidate.hasTextDecoration())
4004
0
            {
4005
0
                basegfx::B2DHomMatrix aTransform(
4006
0
                    aInvViewTransform * aDiscreteOffset
4007
0
                    * getViewInformation2D().getObjectToViewTransformation());
4008
0
                renderTextDecorationWithOptionalTransformAndColor(*pDecoratedCandidate, aDecTrans,
4009
0
                                                                  &aTransform);
4010
0
            }
4011
0
        }
4012
4013
        // at (center, center) paint in COL_WHITE
4014
0
        aTextColor = maBColorModifierStack.getModifiedColor(COL_WHITE.getBColor());
4015
0
        renderSalLayout(pSalLayout, aTextColor, aFullTextTransform,
4016
0
                        getViewInformation2D().getUseAntiAliasing());
4017
0
        if (rTextCandidate.hasTextDecoration())
4018
0
        {
4019
0
            renderTextDecorationWithOptionalTransformAndColor(*pDecoratedCandidate, aDecTrans,
4020
0
                                                              nullptr, &aTextColor);
4021
0
        }
4022
4023
        // paint is complete, Outline and TextRelief cannot be combined, return
4024
0
        return;
4025
0
    }
4026
4027
0
    if (rTextCandidate.hasTextRelief())
4028
0
    {
4029
        // manipulate TextColor for final text paint below (see ::ImplDrawSpecialText)
4030
0
        if (aTextColor == COL_BLACK.getBColor())
4031
0
            aTextColor = COL_WHITE.getBColor();
4032
4033
        // relief offset defined here to easily allow to change them
4034
        // see ::ImplDrawSpecialText and the comment @ 'nOff += mnDPIX/300'
4035
0
        const bool bEmboss(primitive2d::TEXT_RELIEF_EMBOSSED
4036
0
                           == pDecoratedCandidate->getTextRelief());
4037
0
        constexpr double fReliefOffset(1.1);
4038
0
        const double fOffset(bEmboss ? fReliefOffset : -fReliefOffset);
4039
0
        const basegfx::B2DHomMatrix aDiscreteOffset(
4040
0
            basegfx::utils::createTranslateB2DHomMatrix(fOffset, fOffset));
4041
4042
        // see aReliefColor in ::ImplDrawSpecialText
4043
0
        basegfx::BColor aReliefColor(COL_LIGHTGRAY.getBColor());
4044
0
        if (COL_WHITE.getBColor() == aTextColor)
4045
0
            aReliefColor = COL_BLACK.getBColor();
4046
0
        aReliefColor = maBColorModifierStack.getModifiedColor(aReliefColor);
4047
4048
        // render relief text with offset
4049
0
        renderSalLayout(pSalLayout, aReliefColor, aDiscreteOffset * aFullTextTransform,
4050
0
                        getViewInformation2D().getUseAntiAliasing());
4051
4052
0
        if (rTextCandidate.hasTextDecoration())
4053
0
        {
4054
0
            basegfx::B2DHomMatrix aInvViewTransform(getViewInformation2D().getViewTransformation());
4055
0
            aInvViewTransform.invert();
4056
0
            const basegfx::B2DHomMatrix aTransform(
4057
0
                aInvViewTransform * aDiscreteOffset
4058
0
                * getViewInformation2D().getObjectToViewTransformation());
4059
0
            renderTextDecorationWithOptionalTransformAndColor(*pDecoratedCandidate, aDecTrans,
4060
0
                                                              &aTransform, &aReliefColor);
4061
0
        }
4062
0
    }
4063
4064
    // render text
4065
0
    aTextColor = maBColorModifierStack.getModifiedColor(aTextColor);
4066
0
    renderSalLayout(pSalLayout, aTextColor, aFullTextTransform,
4067
0
                    getViewInformation2D().getUseAntiAliasing());
4068
4069
0
    if (rTextCandidate.hasTextDecoration())
4070
0
    {
4071
        // render using same geometry/primitives that a decompose would
4072
        // create -> safe to get the same visualization for both
4073
0
        renderTextDecorationWithOptionalTransformAndColor(*pDecoratedCandidate, aDecTrans);
4074
0
    }
4075
0
}
4076
4077
bool CairoPixelProcessor2D::handleSvgGradientHelper(
4078
    const primitive2d::SvgGradientHelper& rCandidate)
4079
0
{
4080
    // check PolyPolygon to be filled
4081
0
    const basegfx::B2DPolyPolygon& rPolyPolygon(rCandidate.getPolyPolygon());
4082
4083
0
    if (!rPolyPolygon.count())
4084
0
    {
4085
        // no PolyPolygon, done
4086
0
        return true;
4087
0
    }
4088
4089
    // calculate visible range
4090
0
    basegfx::B2DRange aPolyPolygonRange(rPolyPolygon.getB2DRange());
4091
0
    aPolyPolygonRange.transform(getViewInformation2D().getObjectToViewTransformation());
4092
0
    if (!getDiscreteViewRange(mpRT).overlaps(aPolyPolygonRange))
4093
0
    {
4094
        // not visible, done
4095
0
        return true;
4096
0
    }
4097
4098
0
    if (!rCandidate.getCreatesContent())
4099
0
    {
4100
        // creates no content, done
4101
0
        return true;
4102
0
    }
4103
4104
0
    basegfx::BColor aSimpleColor;
4105
0
    bool bDrawSimple(false);
4106
0
    primitive2d::SvgGradientEntryVector::const_reference aEntry(
4107
0
        rCandidate.getGradientEntries().back());
4108
4109
0
    constexpr DrawModeFlags SIMPLE_GRADIENT(DrawModeFlags::WhiteGradient
4110
0
                                            | DrawModeFlags::SettingsGradient);
4111
0
    if (getViewInformation2D().getDrawModeFlags() & SIMPLE_GRADIENT)
4112
0
    {
4113
0
        aSimpleColor = getGradientColor(aSimpleColor);
4114
0
        bDrawSimple = true;
4115
0
    }
4116
4117
0
    if (!bDrawSimple && rCandidate.getSingleEntry())
4118
0
    {
4119
        // only one color entry, fill with last existing color, done
4120
0
        aSimpleColor = aEntry.getColor();
4121
0
        bDrawSimple = true;
4122
0
    }
4123
4124
0
    if (bDrawSimple)
4125
0
    {
4126
0
        paintPolyPolygonRGBA(rCandidate.getPolyPolygon(), aSimpleColor, 1.0 - aEntry.getOpacity());
4127
0
        return true;
4128
0
    }
4129
4130
0
    return false;
4131
0
}
4132
4133
void CairoPixelProcessor2D::processSvgLinearGradientPrimitive2D(
4134
    const primitive2d::SvgLinearGradientPrimitive2D& rCandidate)
4135
0
{
4136
    // check for simple cases, returns if all necessary is already done
4137
0
    if (handleSvgGradientHelper(rCandidate))
4138
0
    {
4139
        // simple case, handled, done
4140
0
        return;
4141
0
    }
4142
4143
0
    cairo_save(mpRT);
4144
4145
0
    const bool bTemporaryGrayColorModifier(getViewInformation2D().getDrawModeFlags()
4146
0
                                           & DrawModeFlags::GrayGradient);
4147
0
    if (bTemporaryGrayColorModifier)
4148
0
    {
4149
0
        const basegfx::BColorModifierSharedPtr aBColorModifier(
4150
0
            std::make_shared<basegfx::BColorModifier_gray>());
4151
0
        maBColorModifierStack.push(aBColorModifier);
4152
0
    }
4153
4154
    // set ObjectToView as regular transformation at CairoContext
4155
0
    const basegfx::B2DHomMatrix aTrans(getViewInformation2D().getObjectToViewTransformation());
4156
0
    cairo_matrix_t aMatrix;
4157
0
    cairo_matrix_init(&aMatrix, aTrans.a(), aTrans.b(), aTrans.c(), aTrans.d(), aTrans.e(),
4158
0
                      aTrans.f());
4159
0
    cairo_set_matrix(mpRT, &aMatrix);
4160
4161
    // create pattern using unit coordinates. Unit coordinates here means that
4162
    // the transformation provided by the primitive maps the linear gradient
4163
    // to (0,0) -> (1,0) at the unified object coordinates, along the unified
4164
    // X-Axis
4165
0
    cairo_pattern_t* pPattern(cairo_pattern_create_linear(0, 0, 1, 0));
4166
4167
    // get pre-defined UnitGradientToObject transformation from primitive
4168
    // and invert to get ObjectToUnitGradient transform
4169
0
    basegfx::B2DHomMatrix aObjectToUnitGradient(
4170
0
        rCandidate.createUnitGradientToObjectTransformation());
4171
0
    aObjectToUnitGradient.invert();
4172
4173
    // set ObjectToUnitGradient as transformation at gradient - patterns
4174
    // need the inverted transformation, see cairo documentation
4175
0
    cairo_matrix_init(&aMatrix, aObjectToUnitGradient.a(), aObjectToUnitGradient.b(),
4176
0
                      aObjectToUnitGradient.c(), aObjectToUnitGradient.d(),
4177
0
                      aObjectToUnitGradient.e(), aObjectToUnitGradient.f());
4178
0
    cairo_pattern_set_matrix(pPattern, &aMatrix);
4179
4180
    // add color stops
4181
0
    const primitive2d::SvgGradientEntryVector& rGradientEntries(rCandidate.getGradientEntries());
4182
4183
0
    for (const auto& entry : rGradientEntries)
4184
0
    {
4185
0
        const basegfx::BColor aColor(maBColorModifierStack.getModifiedColor(entry.getColor()));
4186
0
        cairo_pattern_add_color_stop_rgba(pPattern, entry.getOffset(), aColor.getRed(),
4187
0
                                          aColor.getGreen(), aColor.getBlue(), entry.getOpacity());
4188
0
    }
4189
4190
    // set SpreadMethod. Note that we have no SpreadMethod::None because the
4191
    // source is SVG and SVG does also not have that (checked that)
4192
0
    switch (rCandidate.getSpreadMethod())
4193
0
    {
4194
0
        case primitive2d::SpreadMethod::Pad:
4195
0
            cairo_pattern_set_extend(pPattern, CAIRO_EXTEND_PAD);
4196
0
            break;
4197
0
        case primitive2d::SpreadMethod::Reflect:
4198
0
            cairo_pattern_set_extend(pPattern, CAIRO_EXTEND_REFLECT);
4199
0
            break;
4200
0
        case primitive2d::SpreadMethod::Repeat:
4201
0
            cairo_pattern_set_extend(pPattern, CAIRO_EXTEND_REPEAT);
4202
0
            break;
4203
0
    }
4204
4205
    // get PathGeometry & paint it filed with gradient
4206
0
    cairo_new_path(mpRT);
4207
0
    getOrCreateFillGeometry(mpRT, rCandidate.getPolyPolygon());
4208
0
    cairo_set_source(mpRT, pPattern);
4209
0
    cairo_fill(mpRT);
4210
4211
    // cleanup
4212
0
    cairo_pattern_destroy(pPattern);
4213
0
    cairo_restore(mpRT);
4214
4215
0
    if (bTemporaryGrayColorModifier)
4216
        // cleanup temporary BColorModifier
4217
0
        maBColorModifierStack.pop();
4218
0
}
4219
4220
void CairoPixelProcessor2D::processSvgRadialGradientPrimitive2D(
4221
    const primitive2d::SvgRadialGradientPrimitive2D& rCandidate)
4222
0
{
4223
    // check for simple cases, returns if all necessary is already done
4224
0
    if (handleSvgGradientHelper(rCandidate))
4225
0
    {
4226
        // simple case, handled, done
4227
0
        return;
4228
0
    }
4229
4230
0
    cairo_save(mpRT);
4231
4232
0
    bool bTemporaryGrayColorModifier(false);
4233
0
    if (getViewInformation2D().getDrawModeFlags() & DrawModeFlags::GrayGradient)
4234
0
    {
4235
0
        bTemporaryGrayColorModifier = true;
4236
0
        const basegfx::BColorModifierSharedPtr aBColorModifier(
4237
0
            std::make_shared<basegfx::BColorModifier_gray>());
4238
0
        maBColorModifierStack.push(aBColorModifier);
4239
0
    }
4240
4241
    // set ObjectToView as regular transformation at CairoContext
4242
0
    const basegfx::B2DHomMatrix aTrans(getViewInformation2D().getObjectToViewTransformation());
4243
0
    cairo_matrix_t aMatrix;
4244
0
    cairo_matrix_init(&aMatrix, aTrans.a(), aTrans.b(), aTrans.c(), aTrans.d(), aTrans.e(),
4245
0
                      aTrans.f());
4246
0
    cairo_set_matrix(mpRT, &aMatrix);
4247
4248
    // get pre-defined UnitGradientToObject transformation from primitive
4249
    // and invert to get ObjectToUnitGradient transform
4250
0
    basegfx::B2DHomMatrix aObjectToUnitGradient(
4251
0
        rCandidate.createUnitGradientToObjectTransformation());
4252
0
    aObjectToUnitGradient.invert();
4253
4254
    // prepare empty FocalVector
4255
0
    basegfx::B2DVector aFocalVector(0.0, 0.0);
4256
4257
0
    if (rCandidate.isFocalSet())
4258
0
    {
4259
        // FocalPoint is used, create ObjectTransform based on polygon range
4260
0
        const basegfx::B2DRange aPolyRange(rCandidate.getPolyPolygon().getB2DRange());
4261
0
        const double fPolyWidth(aPolyRange.getWidth());
4262
0
        const double fPolyHeight(aPolyRange.getHeight());
4263
0
        const basegfx::B2DHomMatrix aObjectTransform(
4264
0
            basegfx::utils::createScaleTranslateB2DHomMatrix(
4265
0
                fPolyWidth, fPolyHeight, aPolyRange.getMinX(), aPolyRange.getMinY()));
4266
4267
        // get vector, then transform to object coordinates, then to
4268
        // UnitGradient coordinates to be in the needed coordinate system
4269
0
        aFocalVector = basegfx::B2DVector(rCandidate.getStart() - rCandidate.getFocal());
4270
0
        aFocalVector *= aObjectTransform;
4271
0
        aFocalVector *= aObjectToUnitGradient;
4272
0
    }
4273
4274
    // create pattern using unit coordinates. Unit coordinates here means that
4275
    // the transformation provided by the primitive maps the radial gradient
4276
    // to (0,0) as center, 1.0 as radius - which is the unit circle. The
4277
    // FocalPoint (if used) has to be relative to that, so - since unified
4278
    // center is at (0, 0), handling as vector is sufficient
4279
0
    cairo_pattern_t* pPattern(
4280
0
        cairo_pattern_create_radial(0, 0, 0, aFocalVector.getX(), aFocalVector.getY(), 1));
4281
4282
    // set ObjectToUnitGradient as transformation at gradient - patterns
4283
    // need the inverted transformation, see cairo documentation
4284
0
    cairo_matrix_init(&aMatrix, aObjectToUnitGradient.a(), aObjectToUnitGradient.b(),
4285
0
                      aObjectToUnitGradient.c(), aObjectToUnitGradient.d(),
4286
0
                      aObjectToUnitGradient.e(), aObjectToUnitGradient.f());
4287
0
    cairo_pattern_set_matrix(pPattern, &aMatrix);
4288
4289
    // add color stops
4290
0
    const primitive2d::SvgGradientEntryVector& rGradientEntries(rCandidate.getGradientEntries());
4291
4292
0
    for (const auto& entry : rGradientEntries)
4293
0
    {
4294
0
        const basegfx::BColor aColor(maBColorModifierStack.getModifiedColor(entry.getColor()));
4295
0
        cairo_pattern_add_color_stop_rgba(pPattern, entry.getOffset(), aColor.getRed(),
4296
0
                                          aColor.getGreen(), aColor.getBlue(), entry.getOpacity());
4297
0
    }
4298
4299
    // set SpreadMethod
4300
0
    switch (rCandidate.getSpreadMethod())
4301
0
    {
4302
0
        case primitive2d::SpreadMethod::Pad:
4303
0
            cairo_pattern_set_extend(pPattern, CAIRO_EXTEND_PAD);
4304
0
            break;
4305
0
        case primitive2d::SpreadMethod::Reflect:
4306
0
            cairo_pattern_set_extend(pPattern, CAIRO_EXTEND_REFLECT);
4307
0
            break;
4308
0
        case primitive2d::SpreadMethod::Repeat:
4309
0
            cairo_pattern_set_extend(pPattern, CAIRO_EXTEND_REPEAT);
4310
0
            break;
4311
0
    }
4312
4313
    // get PathGeometry & paint it filed with gradient
4314
0
    cairo_new_path(mpRT);
4315
0
    getOrCreateFillGeometry(mpRT, rCandidate.getPolyPolygon());
4316
0
    cairo_set_source(mpRT, pPattern);
4317
0
    cairo_fill(mpRT);
4318
4319
    // cleanup
4320
0
    cairo_pattern_destroy(pPattern);
4321
0
    cairo_restore(mpRT);
4322
4323
0
    if (bTemporaryGrayColorModifier)
4324
        // cleanup temporary BColorModifier
4325
0
        maBColorModifierStack.pop();
4326
0
}
4327
4328
void CairoPixelProcessor2D::processControlPrimitive2D(
4329
    const primitive2d::ControlPrimitive2D& rControlPrimitive)
4330
0
{
4331
    // find out if the control is already visualized as a VCL-ChildWindow
4332
0
    bool bControlIsVisibleAsChildWindow(rControlPrimitive.isVisibleAsChildWindow());
4333
4334
    // tdf#131281 FormControl rendering for Tiled Rendering
4335
0
    if (bControlIsVisibleAsChildWindow && comphelper::LibreOfficeKit::isActive())
4336
0
    {
4337
        // Do force paint when we are in Tiled Renderer and FormControl is 'visible'
4338
0
        bControlIsVisibleAsChildWindow = false;
4339
0
    }
4340
4341
0
    if (bControlIsVisibleAsChildWindow)
4342
0
    {
4343
        // f the control is already visualized as a VCL-ChildWindow it
4344
        // does not need to be painted at all
4345
0
        return;
4346
0
    }
4347
4348
0
    bool bDone(false);
4349
4350
0
    try
4351
0
    {
4352
0
        if (nullptr != mpTargetOutputDevice)
4353
0
        {
4354
0
            const uno::Reference<awt::XGraphics> xTargetGraphics(
4355
0
                mpTargetOutputDevice->CreateUnoGraphics());
4356
4357
0
            if (xTargetGraphics.is())
4358
0
            {
4359
                // Needs to be drawn. Link new graphics and view
4360
0
                const uno::Reference<awt::XControl>& rXControl(rControlPrimitive.getXControl());
4361
0
                uno::Reference<awt::XView> xControlView(rXControl, uno::UNO_QUERY_THROW);
4362
0
                const uno::Reference<awt::XGraphics> xOriginalGraphics(xControlView->getGraphics());
4363
0
                xControlView->setGraphics(xTargetGraphics);
4364
4365
                // get position
4366
0
                const basegfx::B2DHomMatrix aObjectToPixel(
4367
0
                    getViewInformation2D().getObjectToViewTransformation()
4368
0
                    * rControlPrimitive.getTransform());
4369
0
                const basegfx::B2DPoint aTopLeftPixel(aObjectToPixel * basegfx::B2DPoint(0.0, 0.0));
4370
4371
0
                xControlView->draw(basegfx::fround(aTopLeftPixel.getX()),
4372
0
                                   basegfx::fround(aTopLeftPixel.getY()));
4373
4374
                // restore original graphics
4375
0
                xControlView->setGraphics(xOriginalGraphics);
4376
0
                bDone = true;
4377
0
            }
4378
0
        }
4379
0
    }
4380
0
    catch (const uno::Exception&)
4381
0
    {
4382
        // #i116763# removing since there is a good alternative when the xControlView
4383
        // is not found and it is allowed to happen
4384
        // DBG_UNHANDLED_EXCEPTION();
4385
0
    }
4386
4387
0
    if (!bDone)
4388
0
    {
4389
        // process recursively and use the decomposition as Bitmap
4390
0
        process(rControlPrimitive);
4391
0
    }
4392
0
}
4393
4394
void CairoPixelProcessor2D::evaluateCairoCoordinateLimitWorkaround()
4395
120
{
4396
120
    static bool bAlreadyCheckedIfNeeded(false);
4397
120
    static bool bIsNeeded(false);
4398
4399
120
    if (!bAlreadyCheckedIfNeeded)
4400
3
    {
4401
        // check once for office runtime: is workaround needed?
4402
3
        bAlreadyCheckedIfNeeded = true;
4403
3
        bIsNeeded = checkCoordinateLimitWorkaroundNeededForUsedCairo();
4404
3
    }
4405
4406
120
    if (!bIsNeeded)
4407
0
    {
4408
        // we have a working cairo, so workaround is not needed
4409
        // and mbCairoCoordinateLimitWorkaroundActive can stay false
4410
0
        return;
4411
0
    }
4412
4413
    // get discrete size (pixels)
4414
120
    basegfx::B2DRange aLogicViewRange(getDiscreteViewRange(mpRT));
4415
4416
    // transform to world coordinates -> logic view range
4417
120
    basegfx::B2DHomMatrix aInvViewTrans(getViewInformation2D().getViewTransformation());
4418
120
    aInvViewTrans.invert();
4419
120
    aLogicViewRange.transform(aInvViewTrans);
4420
4421
    // create 1<<23 CairoCoordinate limit from 24.8 internal format
4422
    // and a range fitting to it (just once, this is static)
4423
120
    constexpr double fNumCairoMax(1 << 23);
4424
120
    static const basegfx::B2DRange aNumericalCairoLimit(-fNumCairoMax, -fNumCairoMax,
4425
120
                                                        fNumCairoMax - 1.0, fNumCairoMax - 1.0);
4426
4427
120
    if (!aLogicViewRange.isEmpty() && !aNumericalCairoLimit.isInside(aLogicViewRange))
4428
0
    {
4429
        // aLogicViewRange is not completely inside region covered by
4430
        // 24.8 cairo format, thus workaround is needed, set flag
4431
0
        mbCairoCoordinateLimitWorkaroundActive = true;
4432
0
    }
4433
120
}
4434
4435
basegfx::BColor CairoPixelProcessor2D::getLineColor(const basegfx::BColor& rColor) const
4436
122
{
4437
122
    constexpr DrawModeFlags LINE(DrawModeFlags::BlackLine | DrawModeFlags::WhiteLine
4438
122
                                 | DrawModeFlags::GrayLine | DrawModeFlags::SettingsLine);
4439
122
    const DrawModeFlags aDrawModeFlags(getViewInformation2D().getDrawModeFlags());
4440
4441
122
    if (!(aDrawModeFlags & LINE))
4442
122
        return rColor;
4443
4444
0
    if (aDrawModeFlags & DrawModeFlags::BlackLine)
4445
0
        return basegfx::BColor(0, 0, 0);
4446
4447
0
    if (aDrawModeFlags & DrawModeFlags::WhiteLine)
4448
0
        return basegfx::BColor(1, 1, 1);
4449
4450
0
    if (aDrawModeFlags & DrawModeFlags::GrayLine)
4451
0
    {
4452
0
        const double fLuminance(rColor.luminance());
4453
0
        return basegfx::BColor(fLuminance, fLuminance, fLuminance);
4454
0
    }
4455
4456
    // DrawModeFlags::SettingsLine
4457
0
    if (aDrawModeFlags & DrawModeFlags::SettingsForSelection)
4458
0
        return Application::GetSettings().GetStyleSettings().GetHighlightColor().getBColor();
4459
4460
0
    return Application::GetSettings().GetStyleSettings().GetWindowTextColor().getBColor();
4461
0
}
4462
4463
basegfx::BColor CairoPixelProcessor2D::getFillColor(const basegfx::BColor& rColor) const
4464
405
{
4465
405
    constexpr DrawModeFlags FILL(DrawModeFlags::BlackFill | DrawModeFlags::WhiteFill
4466
405
                                 | DrawModeFlags::GrayFill | DrawModeFlags::SettingsFill);
4467
405
    const DrawModeFlags aDrawModeFlags(getViewInformation2D().getDrawModeFlags());
4468
4469
405
    if (!(aDrawModeFlags & FILL))
4470
405
        return rColor;
4471
4472
0
    if (aDrawModeFlags & DrawModeFlags::BlackFill)
4473
0
        return basegfx::BColor(0, 0, 0);
4474
4475
0
    if (aDrawModeFlags & DrawModeFlags::WhiteFill)
4476
0
        return basegfx::BColor(1, 1, 1);
4477
4478
0
    if (aDrawModeFlags & DrawModeFlags::GrayFill)
4479
0
    {
4480
0
        const double fLuminance(rColor.luminance());
4481
0
        return basegfx::BColor(fLuminance, fLuminance, fLuminance);
4482
0
    }
4483
4484
    // DrawModeFlags::SettingsFill
4485
0
    if (aDrawModeFlags & DrawModeFlags::SettingsForSelection)
4486
0
        return Application::GetSettings().GetStyleSettings().GetHighlightColor().getBColor();
4487
4488
0
    return Application::GetSettings().GetStyleSettings().GetWindowColor().getBColor();
4489
0
}
4490
4491
basegfx::BColor CairoPixelProcessor2D::getTextColor(const basegfx::BColor& rColor) const
4492
0
{
4493
0
    constexpr DrawModeFlags TEXT
4494
0
        = DrawModeFlags::BlackText | DrawModeFlags::GrayText | DrawModeFlags::SettingsText;
4495
0
    const DrawModeFlags aDrawModeFlags(getViewInformation2D().getDrawModeFlags());
4496
4497
0
    if (!(aDrawModeFlags & TEXT))
4498
0
        return rColor;
4499
4500
0
    if (aDrawModeFlags & DrawModeFlags::BlackText)
4501
0
        return basegfx::BColor(0, 0, 0);
4502
4503
0
    if (aDrawModeFlags & DrawModeFlags::GrayText)
4504
0
    {
4505
0
        const double fLuminance(rColor.luminance());
4506
0
        return basegfx::BColor(fLuminance, fLuminance, fLuminance);
4507
0
    }
4508
4509
    // DrawModeFlags::SettingsText
4510
0
    if (aDrawModeFlags & DrawModeFlags::SettingsForSelection)
4511
0
        return Application::GetSettings().GetStyleSettings().GetHighlightTextColor().getBColor();
4512
4513
0
    return Application::GetSettings().GetStyleSettings().GetWindowTextColor().getBColor();
4514
0
}
4515
4516
basegfx::BColor CairoPixelProcessor2D::getGradientColor(const basegfx::BColor& rColor) const
4517
0
{
4518
0
    constexpr DrawModeFlags GRADIENT(DrawModeFlags::GrayGradient | DrawModeFlags::WhiteGradient
4519
0
                                     | DrawModeFlags::SettingsGradient);
4520
0
    const DrawModeFlags aDrawModeFlags(getViewInformation2D().getDrawModeFlags());
4521
4522
0
    if (!(aDrawModeFlags & GRADIENT))
4523
0
        return rColor;
4524
4525
0
    if (aDrawModeFlags & DrawModeFlags::WhiteGradient)
4526
0
        return basegfx::BColor(1, 1, 1);
4527
4528
0
    if (aDrawModeFlags & DrawModeFlags::GrayGradient)
4529
0
    {
4530
0
        const double fLuminance(rColor.luminance());
4531
0
        return basegfx::BColor(fLuminance, fLuminance, fLuminance);
4532
0
    }
4533
4534
    // DrawModeFlags::SettingsGradient
4535
0
    if (aDrawModeFlags & DrawModeFlags::SettingsForSelection)
4536
0
        return Application::GetSettings().GetStyleSettings().GetHighlightColor().getBColor();
4537
4538
0
    return Application::GetSettings().GetStyleSettings().GetWindowColor().getBColor();
4539
0
}
4540
4541
void CairoPixelProcessor2D::processBasePrimitive2D(const primitive2d::BasePrimitive2D& rCandidate)
4542
2.65k
{
4543
2.65k
    const cairo_status_t aStart(cairo_status(mpRT));
4544
4545
2.65k
    switch (rCandidate.getPrimitive2DID())
4546
2.65k
    {
4547
        // geometry that *has* to be processed
4548
0
        case PRIMITIVE2D_ID_BITMAPPRIMITIVE2D:
4549
0
        {
4550
0
            processBitmapPrimitive2D(
4551
0
                static_cast<const primitive2d::BitmapPrimitive2D&>(rCandidate));
4552
0
            break;
4553
0
        }
4554
0
        case PRIMITIVE2D_ID_POINTARRAYPRIMITIVE2D:
4555
0
        {
4556
0
            processPointArrayPrimitive2D(
4557
0
                static_cast<const primitive2d::PointArrayPrimitive2D&>(rCandidate));
4558
0
            break;
4559
0
        }
4560
122
        case PRIMITIVE2D_ID_POLYGONHAIRLINEPRIMITIVE2D:
4561
122
        {
4562
122
            processPolygonHairlinePrimitive2D(
4563
122
                static_cast<const primitive2d::PolygonHairlinePrimitive2D&>(rCandidate));
4564
122
            break;
4565
0
        }
4566
405
        case PRIMITIVE2D_ID_POLYPOLYGONCOLORPRIMITIVE2D:
4567
405
        {
4568
405
            processPolyPolygonColorPrimitive2D(
4569
405
                static_cast<const primitive2d::PolyPolygonColorPrimitive2D&>(rCandidate));
4570
405
            break;
4571
0
        }
4572
        // embedding/groups that *have* to be processed
4573
0
        case PRIMITIVE2D_ID_TRANSPARENCEPRIMITIVE2D:
4574
0
        {
4575
0
            processTransparencePrimitive2D(
4576
0
                static_cast<const primitive2d::TransparencePrimitive2D&>(rCandidate));
4577
0
            break;
4578
0
        }
4579
0
        case PRIMITIVE2D_ID_INVERTPRIMITIVE2D:
4580
0
        {
4581
0
            processInvertPrimitive2D(
4582
0
                static_cast<const primitive2d::InvertPrimitive2D&>(rCandidate));
4583
0
            break;
4584
0
        }
4585
0
        case PRIMITIVE2D_ID_MASKPRIMITIVE2D:
4586
0
        {
4587
0
            processMaskPrimitive2D(static_cast<const primitive2d::MaskPrimitive2D&>(rCandidate));
4588
0
            break;
4589
0
        }
4590
0
        case PRIMITIVE2D_ID_MODIFIEDCOLORPRIMITIVE2D:
4591
0
        {
4592
0
            processModifiedColorPrimitive2D(
4593
0
                static_cast<const primitive2d::ModifiedColorPrimitive2D&>(rCandidate));
4594
0
            break;
4595
0
        }
4596
227
        case PRIMITIVE2D_ID_TRANSFORMPRIMITIVE2D:
4597
227
        {
4598
227
            processTransformPrimitive2D(
4599
227
                static_cast<const primitive2d::TransformPrimitive2D&>(rCandidate));
4600
227
            break;
4601
0
        }
4602
4603
        // geometry that *may* be processed due to being able to do it better
4604
        // then using the decomposition
4605
0
        case PRIMITIVE2D_ID_UNIFIEDTRANSPARENCEPRIMITIVE2D:
4606
0
        {
4607
0
            processUnifiedTransparencePrimitive2D(
4608
0
                static_cast<const primitive2d::UnifiedTransparencePrimitive2D&>(rCandidate));
4609
0
            break;
4610
0
        }
4611
0
        case PRIMITIVE2D_ID_MARKERARRAYPRIMITIVE2D:
4612
0
        {
4613
0
            processMarkerArrayPrimitive2D(
4614
0
                static_cast<const primitive2d::MarkerArrayPrimitive2D&>(rCandidate));
4615
0
            break;
4616
0
        }
4617
0
        case PRIMITIVE2D_ID_BACKGROUNDCOLORPRIMITIVE2D:
4618
0
        {
4619
0
            processBackgroundColorPrimitive2D(
4620
0
                static_cast<const primitive2d::BackgroundColorPrimitive2D&>(rCandidate));
4621
0
            break;
4622
0
        }
4623
0
        case PRIMITIVE2D_ID_POLYGONSTROKEPRIMITIVE2D:
4624
0
        {
4625
0
            processPolygonStrokePrimitive2D(
4626
0
                static_cast<const primitive2d::PolygonStrokePrimitive2D&>(rCandidate));
4627
0
            break;
4628
0
        }
4629
0
        case PRIMITIVE2D_ID_LINERECTANGLEPRIMITIVE2D:
4630
0
        {
4631
0
            processLineRectanglePrimitive2D(
4632
0
                static_cast<const primitive2d::LineRectanglePrimitive2D&>(rCandidate));
4633
0
            break;
4634
0
        }
4635
0
        case PRIMITIVE2D_ID_FILLEDRECTANGLEPRIMITIVE2D:
4636
0
        {
4637
0
            processFilledRectanglePrimitive2D(
4638
0
                static_cast<const primitive2d::FilledRectanglePrimitive2D&>(rCandidate));
4639
0
            break;
4640
0
        }
4641
0
        case PRIMITIVE2D_ID_SINGLELINEPRIMITIVE2D:
4642
0
        {
4643
0
            processSingleLinePrimitive2D(
4644
0
                static_cast<const primitive2d::SingleLinePrimitive2D&>(rCandidate));
4645
0
            break;
4646
0
        }
4647
0
        case PRIMITIVE2D_ID_FILLGRAPHICPRIMITIVE2D:
4648
0
        {
4649
0
            processFillGraphicPrimitive2D(
4650
0
                static_cast<const primitive2d::FillGraphicPrimitive2D&>(rCandidate));
4651
0
            break;
4652
0
        }
4653
0
        case PRIMITIVE2D_ID_FILLGRADIENTPRIMITIVE2D:
4654
0
        {
4655
0
            processFillGradientPrimitive2D(
4656
0
                static_cast<const primitive2d::FillGradientPrimitive2D&>(rCandidate));
4657
0
            break;
4658
0
        }
4659
0
        case PRIMITIVE2D_ID_PATTERNFILLPRIMITIVE2D:
4660
0
        {
4661
0
            processPatternFillPrimitive2D(
4662
0
                static_cast<const drawinglayer::primitive2d::PatternFillPrimitive2D&>(rCandidate));
4663
0
            break;
4664
0
        }
4665
0
        case PRIMITIVE2D_ID_POLYPOLYGONRGBAPRIMITIVE2D:
4666
0
        {
4667
0
            processPolyPolygonRGBAPrimitive2D(
4668
0
                static_cast<const primitive2d::PolyPolygonRGBAPrimitive2D&>(rCandidate));
4669
0
            break;
4670
0
        }
4671
0
        case PRIMITIVE2D_ID_BITMAPALPHAPRIMITIVE2D:
4672
0
        {
4673
0
            processBitmapAlphaPrimitive2D(
4674
0
                static_cast<const primitive2d::BitmapAlphaPrimitive2D&>(rCandidate));
4675
0
            break;
4676
0
        }
4677
0
        case PRIMITIVE2D_ID_POLYPOLYGONALPHAGRADIENTPRIMITIVE2D:
4678
0
        {
4679
0
            processPolyPolygonAlphaGradientPrimitive2D(
4680
0
                static_cast<const primitive2d::PolyPolygonAlphaGradientPrimitive2D&>(rCandidate));
4681
0
            break;
4682
0
        }
4683
458
        case PRIMITIVE2D_ID_TEXTSIMPLEPORTIONPRIMITIVE2D:
4684
458
        {
4685
458
            processTextSimplePortionPrimitive2D(
4686
458
                static_cast<const primitive2d::TextSimplePortionPrimitive2D&>(rCandidate));
4687
458
            break;
4688
0
        }
4689
0
        case PRIMITIVE2D_ID_TEXTDECORATEDPORTIONPRIMITIVE2D:
4690
0
        {
4691
0
            processTextDecoratedPortionPrimitive2D(
4692
0
                static_cast<const primitive2d::TextDecoratedPortionPrimitive2D&>(rCandidate));
4693
0
            break;
4694
0
        }
4695
0
        case PRIMITIVE2D_ID_SVGLINEARGRADIENTPRIMITIVE2D:
4696
0
        {
4697
0
            processSvgLinearGradientPrimitive2D(
4698
0
                static_cast<const primitive2d::SvgLinearGradientPrimitive2D&>(rCandidate));
4699
0
            break;
4700
0
        }
4701
0
        case PRIMITIVE2D_ID_SVGRADIALGRADIENTPRIMITIVE2D:
4702
0
        {
4703
0
            processSvgRadialGradientPrimitive2D(
4704
0
                static_cast<const primitive2d::SvgRadialGradientPrimitive2D&>(rCandidate));
4705
0
            break;
4706
0
        }
4707
0
        case PRIMITIVE2D_ID_CONTROLPRIMITIVE2D:
4708
0
        {
4709
0
            processControlPrimitive2D(
4710
0
                static_cast<const primitive2d::ControlPrimitive2D&>(rCandidate));
4711
0
            break;
4712
0
        }
4713
4714
        // continue with decompose
4715
1.44k
        default:
4716
1.44k
        {
4717
1.44k
            SAL_INFO("drawinglayer", "default case for " << drawinglayer::primitive2d::idToString(
4718
1.44k
                                         rCandidate.getPrimitive2DID()));
4719
            // process recursively
4720
1.44k
            process(rCandidate);
4721
1.44k
            break;
4722
1.44k
        }
4723
2.65k
    }
4724
4725
2.65k
    const cairo_status_t aEnd(cairo_status(mpRT));
4726
4727
2.65k
    if (aStart != aEnd)
4728
0
    {
4729
0
        SAL_WARN("drawinglayer", "CairoSDPR: Cairo status problem (!)");
4730
0
    }
4731
2.65k
}
4732
4733
} // end of namespace
4734
4735
/* vim:set shiftwidth=4 softtabstop=4 expandtab cinoptions=b1,g0,N-s cinkeys+=0=break: */