Coverage Report

Created: 2025-11-16 09:57

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