Coverage Report

Created: 2026-08-14 10:22

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