Coverage Report

Created: 2026-07-10 11:04

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/libreoffice/drawinglayer/source/processor2d/vclpixelprocessor2d.cxx
Line
Count
Source
1
/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
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
 * This file incorporates work covered by the following license notice:
10
 *
11
 *   Licensed to the Apache Software Foundation (ASF) under one or more
12
 *   contributor license agreements. See the NOTICE file distributed
13
 *   with this work for additional information regarding copyright
14
 *   ownership. The ASF licenses this file to you under the Apache
15
 *   License, Version 2.0 (the "License"); you may not use this file
16
 *   except in compliance with the License. You may obtain a copy of
17
 *   the License at http://www.apache.org/licenses/LICENSE-2.0 .
18
 */
19
20
#include "vclpixelprocessor2d.hxx"
21
#include "vclhelperbufferdevice.hxx"
22
#include <comphelper/lok.hxx>
23
24
#include <sal/log.hxx>
25
#include <vcl/outdev.hxx>
26
#include <vcl/hatch.hxx>
27
#include <vcl/canvastools.hxx>
28
#include <vcl/vclenum.hxx>
29
#include <basegfx/polygon/b2dpolygontools.hxx>
30
#include <basegfx/polygon/b2dpolypolygontools.hxx>
31
#include <basegfx/utils/bgradient.hxx>
32
33
#include <drawinglayer/primitive2d/drawinglayer_primitivetypes2d.hxx>
34
#include <drawinglayer/primitive2d/Tools.hxx>
35
#include <drawinglayer/primitive2d/textprimitive2d.hxx>
36
#include <drawinglayer/primitive2d/PolygonHairlinePrimitive2D.hxx>
37
#include <drawinglayer/primitive2d/PolygonStrokePrimitive2D.hxx>
38
#include <drawinglayer/primitive2d/PolyPolygonColorPrimitive2D.hxx>
39
#include <drawinglayer/primitive2d/PolyPolygonGradientPrimitive2D.hxx>
40
#include <drawinglayer/primitive2d/PolyPolygonGraphicPrimitive2D.hxx>
41
#include <drawinglayer/primitive2d/bitmapprimitive2d.hxx>
42
#include <drawinglayer/primitive2d/fillgraphicprimitive2d.hxx>
43
#include <drawinglayer/primitive2d/maskprimitive2d.hxx>
44
#include <drawinglayer/primitive2d/modifiedcolorprimitive2d.hxx>
45
#include <drawinglayer/primitive2d/transparenceprimitive2d.hxx>
46
#include <drawinglayer/primitive2d/transformprimitive2d.hxx>
47
#include <drawinglayer/primitive2d/markerarrayprimitive2d.hxx>
48
#include <drawinglayer/primitive2d/controlprimitive2d.hxx>
49
#include <drawinglayer/primitive2d/borderlineprimitive2d.hxx>
50
#include <drawinglayer/primitive2d/fillgradientprimitive2d.hxx>
51
#include <drawinglayer/primitive2d/unifiedtransparenceprimitive2d.hxx>
52
#include <drawinglayer/primitive2d/pagepreviewprimitive2d.hxx>
53
#include <drawinglayer/primitive2d/backgroundcolorprimitive2d.hxx>
54
#include <drawinglayer/primitive2d/svggradientprimitive2d.hxx>
55
#include <drawinglayer/primitive2d/pointarrayprimitive2d.hxx>
56
#include <drawinglayer/primitive2d/fillhatchprimitive2d.hxx>
57
#include <drawinglayer/primitive2d/epsprimitive2d.hxx>
58
#include <drawinglayer/primitive2d/patternfillprimitive2d.hxx>
59
60
#include <com/sun/star/awt/XControl.hpp>
61
62
#include <officecfg/Office/Common.hxx>
63
#include <vcl/gradient.hxx>
64
65
using namespace com::sun::star;
66
67
namespace drawinglayer::processor2d
68
{
69
VclPixelProcessor2D::VclPixelProcessor2D(const geometry::ViewInformation2D& rViewInformation,
70
                                         OutputDevice& rOutDev)
71
0
    : VclProcessor2D(rViewInformation, rOutDev)
72
0
    , m_nOrigAntiAliasing(rOutDev.GetAntialiasing())
73
    , m_bRenderSimpleTextDirect(
74
0
          officecfg::Office::Common::Drawinglayer::RenderSimpleTextDirect::get())
75
    , m_bRenderDecoratedTextDirect(
76
0
          officecfg::Office::Common::Drawinglayer::RenderDecoratedTextDirect::get())
77
0
{
78
    // prepare maCurrentTransformation matrix with viewTransformation to target directly to pixels
79
0
    maCurrentTransformation = rViewInformation.getObjectToViewTransformation();
80
81
    // prepare output directly to pixels
82
0
    mpOutputDevice->Push(vcl::PushFlags::MAPMODE);
83
0
    mpOutputDevice->SetMapMode();
84
85
    // react on AntiAliasing settings
86
0
    if (rViewInformation.getUseAntiAliasing())
87
0
    {
88
0
        mpOutputDevice->SetAntialiasing(m_nOrigAntiAliasing | AntialiasingFlags::Enable);
89
0
    }
90
0
    else
91
0
    {
92
0
        mpOutputDevice->SetAntialiasing(m_nOrigAntiAliasing & ~AntialiasingFlags::Enable);
93
0
    }
94
0
}
95
96
VclPixelProcessor2D::~VclPixelProcessor2D()
97
0
{
98
    // restore MapMode
99
0
    mpOutputDevice->Pop();
100
101
    // restore AntiAliasing
102
0
    mpOutputDevice->SetAntialiasing(m_nOrigAntiAliasing);
103
0
}
104
105
void VclPixelProcessor2D::tryDrawPolyPolygonColorPrimitive2DDirect(
106
    const drawinglayer::primitive2d::PolyPolygonColorPrimitive2D& rSource, double fTransparency)
107
0
{
108
0
    if (!rSource.getB2DPolyPolygon().count() || fTransparency < 0.0 || fTransparency >= 1.0)
109
0
    {
110
        // no geometry, done
111
0
        return;
112
0
    }
113
114
0
    const basegfx::BColor aPolygonColor(
115
0
        maBColorModifierStack.getModifiedColor(rSource.getBColor()));
116
117
0
    if (comphelper::LibreOfficeKit::isActive() && aPolygonColor.isAutomatic())
118
0
        mpOutputDevice->SetFillColor(getViewInformation2D().getAutoColor());
119
0
    else
120
0
        mpOutputDevice->SetFillColor(Color(aPolygonColor));
121
122
0
    mpOutputDevice->SetLineColor();
123
0
    mpOutputDevice->DrawTransparent(maCurrentTransformation, rSource.getB2DPolyPolygon(),
124
0
                                    fTransparency);
125
0
}
126
127
bool VclPixelProcessor2D::tryDrawPolygonHairlinePrimitive2DDirect(
128
    const drawinglayer::primitive2d::PolygonHairlinePrimitive2D& rSource, double fTransparency)
129
0
{
130
0
    const basegfx::B2DPolygon& rLocalPolygon(rSource.getB2DPolygon());
131
132
0
    if (!rLocalPolygon.count() || fTransparency < 0.0 || fTransparency >= 1.0)
133
0
    {
134
        // no geometry, done
135
0
        return true;
136
0
    }
137
138
0
    const basegfx::BColor aLineColor(maBColorModifierStack.getModifiedColor(rSource.getBColor()));
139
140
0
    mpOutputDevice->SetFillColor();
141
0
    mpOutputDevice->SetLineColor(Color(aLineColor));
142
    //aLocalPolygon.transform(maCurrentTransformation);
143
144
    // try drawing; if it did not work, use standard fallback
145
0
    return mpOutputDevice->DrawPolyLineDirect(maCurrentTransformation, rLocalPolygon, 0.0,
146
0
                                              fTransparency);
147
0
}
148
149
bool VclPixelProcessor2D::tryDrawPolygonStrokePrimitive2DDirect(
150
    const drawinglayer::primitive2d::PolygonStrokePrimitive2D& rSource, double fTransparency)
151
0
{
152
0
    const basegfx::B2DPolygon& rLocalPolygon(rSource.getB2DPolygon());
153
154
0
    if (!rLocalPolygon.count() || fTransparency < 0.0 || fTransparency >= 1.0)
155
0
    {
156
        // no geometry, done
157
0
        return true;
158
0
    }
159
160
0
    if (basegfx::B2DLineJoin::NONE == rSource.getLineAttribute().getLineJoin()
161
0
        && css::drawing::LineCap_BUTT != rSource.getLineAttribute().getLineCap())
162
0
    {
163
        // better use decompose to get that combination done for now, see discussion
164
        // at https://bugs.documentfoundation.org/show_bug.cgi?id=130478#c17 and ff
165
0
        return false;
166
0
    }
167
168
    // MM01: Radically change here - no dismantle/applyLineDashing,
169
    // let that happen low-level at DrawPolyLineDirect implementations
170
    // to open up for buffering and evtl. direct draw with sys-dep
171
    // graphic systems. Check for stroke is in use
172
0
    const bool bStrokeAttributeNotUsed(rSource.getStrokeAttribute().isDefault()
173
0
                                       || 0.0 == rSource.getStrokeAttribute().getFullDotDashLen());
174
175
0
    const basegfx::BColor aLineColor(
176
0
        maBColorModifierStack.getModifiedColor(rSource.getLineAttribute().getColor()));
177
178
0
    mpOutputDevice->SetFillColor();
179
0
    mpOutputDevice->SetLineColor(Color(aLineColor));
180
181
    // MM01 draw direct, hand over dash data if available
182
0
    return mpOutputDevice->DrawPolyLineDirect(
183
0
        maCurrentTransformation, rLocalPolygon,
184
        // tdf#124848 use LineWidth direct, do not try to solve for zero-case (aka hairline)
185
0
        rSource.getLineAttribute().getWidth(), fTransparency,
186
0
        bStrokeAttributeNotUsed ? nullptr : &rSource.getStrokeAttribute().getDotDashArray(),
187
0
        rSource.getLineAttribute().getLineJoin(), rSource.getLineAttribute().getLineCap(),
188
0
        rSource.getLineAttribute().getMiterMinimumAngle());
189
0
}
190
191
void VclPixelProcessor2D::processBasePrimitive2D(const primitive2d::BasePrimitive2D& rCandidate)
192
0
{
193
0
    switch (rCandidate.getPrimitive2DID())
194
0
    {
195
0
        case PRIMITIVE2D_ID_TEXTSIMPLEPORTIONPRIMITIVE2D:
196
0
        {
197
0
            processTextSimplePortionPrimitive2D(
198
0
                static_cast<const primitive2d::TextSimplePortionPrimitive2D&>(rCandidate));
199
0
            break;
200
0
        }
201
0
        case PRIMITIVE2D_ID_TEXTDECORATEDPORTIONPRIMITIVE2D:
202
0
        {
203
0
            processTextDecoratedPortionPrimitive2D(
204
0
                static_cast<const primitive2d::TextSimplePortionPrimitive2D&>(rCandidate));
205
0
            break;
206
0
        }
207
0
        case PRIMITIVE2D_ID_POLYGONHAIRLINEPRIMITIVE2D:
208
0
        {
209
0
            processPolygonHairlinePrimitive2D(
210
0
                static_cast<const primitive2d::PolygonHairlinePrimitive2D&>(rCandidate));
211
0
            break;
212
0
        }
213
0
        case PRIMITIVE2D_ID_BITMAPPRIMITIVE2D:
214
0
        {
215
            // direct draw of transformed Bitmap primitive
216
0
            processBitmapPrimitive2D(
217
0
                static_cast<const primitive2d::BitmapPrimitive2D&>(rCandidate));
218
0
            break;
219
0
        }
220
0
        case PRIMITIVE2D_ID_FILLGRAPHICPRIMITIVE2D:
221
0
        {
222
            // direct draw of fillBitmapPrimitive
223
0
            RenderFillGraphicPrimitive2D(
224
0
                static_cast<const primitive2d::FillGraphicPrimitive2D&>(rCandidate));
225
0
            break;
226
0
        }
227
0
        case PRIMITIVE2D_ID_POLYPOLYGONGRADIENTPRIMITIVE2D:
228
0
        {
229
0
            processPolyPolygonGradientPrimitive2D(
230
0
                static_cast<const primitive2d::PolyPolygonGradientPrimitive2D&>(rCandidate));
231
0
            break;
232
0
        }
233
0
        case PRIMITIVE2D_ID_POLYPOLYGONGRAPHICPRIMITIVE2D:
234
0
        {
235
            // direct draw of bitmap
236
0
            RenderPolyPolygonGraphicPrimitive2D(
237
0
                static_cast<const primitive2d::PolyPolygonGraphicPrimitive2D&>(rCandidate));
238
0
            break;
239
0
        }
240
0
        case PRIMITIVE2D_ID_POLYPOLYGONCOLORPRIMITIVE2D:
241
0
        {
242
0
            processPolyPolygonColorPrimitive2D(
243
0
                static_cast<const primitive2d::PolyPolygonColorPrimitive2D&>(rCandidate));
244
0
            break;
245
0
        }
246
0
        case PRIMITIVE2D_ID_METAFILEPRIMITIVE2D:
247
0
        {
248
0
            processMetaFilePrimitive2D(rCandidate);
249
0
            break;
250
0
        }
251
0
        case PRIMITIVE2D_ID_MASKPRIMITIVE2D:
252
0
        {
253
            // mask group.
254
0
            RenderMaskPrimitive2DPixel(
255
0
                static_cast<const primitive2d::MaskPrimitive2D&>(rCandidate));
256
0
            break;
257
0
        }
258
0
        case PRIMITIVE2D_ID_MODIFIEDCOLORPRIMITIVE2D:
259
0
        {
260
            // modified color group. Force output to unified color.
261
0
            RenderModifiedColorPrimitive2D(
262
0
                static_cast<const primitive2d::ModifiedColorPrimitive2D&>(rCandidate));
263
0
            break;
264
0
        }
265
0
        case PRIMITIVE2D_ID_UNIFIEDTRANSPARENCEPRIMITIVE2D:
266
0
        {
267
0
            processUnifiedTransparencePrimitive2D(
268
0
                static_cast<const primitive2d::UnifiedTransparencePrimitive2D&>(rCandidate));
269
0
            break;
270
0
        }
271
0
        case PRIMITIVE2D_ID_TRANSPARENCEPRIMITIVE2D:
272
0
        {
273
            // sub-transparence group. Draw to VDev first.
274
0
            RenderTransparencePrimitive2D(
275
0
                static_cast<const primitive2d::TransparencePrimitive2D&>(rCandidate));
276
0
            break;
277
0
        }
278
0
        case PRIMITIVE2D_ID_TRANSFORMPRIMITIVE2D:
279
0
        {
280
            // transform group.
281
0
            RenderTransformPrimitive2D(
282
0
                static_cast<const primitive2d::TransformPrimitive2D&>(rCandidate));
283
0
            break;
284
0
        }
285
0
        case PRIMITIVE2D_ID_PAGEPREVIEWPRIMITIVE2D:
286
0
        {
287
            // new XDrawPage for ViewInformation2D
288
0
            RenderPagePreviewPrimitive2D(
289
0
                static_cast<const primitive2d::PagePreviewPrimitive2D&>(rCandidate));
290
0
            break;
291
0
        }
292
0
        case PRIMITIVE2D_ID_MARKERARRAYPRIMITIVE2D:
293
0
        {
294
            // marker array
295
0
            RenderMarkerArrayPrimitive2D(
296
0
                static_cast<const primitive2d::MarkerArrayPrimitive2D&>(rCandidate));
297
0
            break;
298
0
        }
299
0
        case PRIMITIVE2D_ID_POINTARRAYPRIMITIVE2D:
300
0
        {
301
            // point array
302
0
            RenderPointArrayPrimitive2D(
303
0
                static_cast<const primitive2d::PointArrayPrimitive2D&>(rCandidate));
304
0
            break;
305
0
        }
306
0
        case PRIMITIVE2D_ID_CONTROLPRIMITIVE2D:
307
0
        {
308
0
            processControlPrimitive2D(
309
0
                static_cast<const primitive2d::ControlPrimitive2D&>(rCandidate));
310
0
            break;
311
0
        }
312
0
        case PRIMITIVE2D_ID_POLYGONSTROKEPRIMITIVE2D:
313
0
        {
314
0
            processPolygonStrokePrimitive2D(
315
0
                static_cast<const primitive2d::PolygonStrokePrimitive2D&>(rCandidate));
316
0
            break;
317
0
        }
318
0
        case PRIMITIVE2D_ID_FILLHATCHPRIMITIVE2D:
319
0
        {
320
0
            processFillHatchPrimitive2D(
321
0
                static_cast<const primitive2d::FillHatchPrimitive2D&>(rCandidate));
322
0
            break;
323
0
        }
324
0
        case PRIMITIVE2D_ID_BACKGROUNDCOLORPRIMITIVE2D:
325
0
        {
326
0
            processBackgroundColorPrimitive2D(
327
0
                static_cast<const primitive2d::BackgroundColorPrimitive2D&>(rCandidate));
328
0
            break;
329
0
        }
330
0
        case PRIMITIVE2D_ID_INVERTPRIMITIVE2D:
331
0
        {
332
0
            processInvertPrimitive2D(rCandidate);
333
0
            break;
334
0
        }
335
0
        case PRIMITIVE2D_ID_EPSPRIMITIVE2D:
336
0
        {
337
0
            RenderEpsPrimitive2D(static_cast<const primitive2d::EpsPrimitive2D&>(rCandidate));
338
0
            break;
339
0
        }
340
0
        case PRIMITIVE2D_ID_SVGLINEARATOMPRIMITIVE2D:
341
0
        {
342
0
            RenderSvgLinearAtomPrimitive2D(
343
0
                static_cast<const primitive2d::SvgLinearAtomPrimitive2D&>(rCandidate));
344
0
            break;
345
0
        }
346
0
        case PRIMITIVE2D_ID_SVGRADIALATOMPRIMITIVE2D:
347
0
        {
348
0
            RenderSvgRadialAtomPrimitive2D(
349
0
                static_cast<const primitive2d::SvgRadialAtomPrimitive2D&>(rCandidate));
350
0
            break;
351
0
        }
352
0
        case PRIMITIVE2D_ID_BORDERLINEPRIMITIVE2D:
353
0
        {
354
0
            processBorderLinePrimitive2D(
355
0
                static_cast<const drawinglayer::primitive2d::BorderLinePrimitive2D&>(rCandidate));
356
0
            break;
357
0
        }
358
0
        case PRIMITIVE2D_ID_FILLGRADIENTPRIMITIVE2D:
359
0
        {
360
0
            processFillGradientPrimitive2D(
361
0
                static_cast<const drawinglayer::primitive2d::FillGradientPrimitive2D&>(rCandidate));
362
0
            break;
363
0
        }
364
0
        case PRIMITIVE2D_ID_PATTERNFILLPRIMITIVE2D:
365
0
        {
366
0
            processPatternFillPrimitive2D(
367
0
                static_cast<const drawinglayer::primitive2d::PatternFillPrimitive2D&>(rCandidate));
368
0
            break;
369
0
        }
370
0
        default:
371
0
        {
372
0
            SAL_INFO("drawinglayer", "default case for " << drawinglayer::primitive2d::idToString(
373
0
                                         rCandidate.getPrimitive2DID()));
374
            // process recursively
375
0
            process(rCandidate);
376
0
            break;
377
0
        }
378
0
    }
379
0
}
380
381
void VclPixelProcessor2D::processTextSimplePortionPrimitive2D(
382
    const primitive2d::TextSimplePortionPrimitive2D& rCandidate)
383
0
{
384
    // Adapt evtl. used special DrawMode
385
0
    const DrawModeFlags nOriginalDrawMode(mpOutputDevice->GetDrawMode());
386
0
    adaptTextToFillDrawMode();
387
388
0
    if (SAL_LIKELY(m_bRenderSimpleTextDirect))
389
0
    {
390
0
        RenderTextSimpleOrDecoratedPortionPrimitive2D(rCandidate);
391
0
    }
392
0
    else
393
0
    {
394
0
        process(rCandidate);
395
0
    }
396
397
    // restore DrawMode
398
0
    mpOutputDevice->SetDrawMode(nOriginalDrawMode);
399
0
}
400
401
void VclPixelProcessor2D::processTextDecoratedPortionPrimitive2D(
402
    const primitive2d::TextSimplePortionPrimitive2D& rCandidate)
403
0
{
404
    // Adapt evtl. used special DrawMode
405
0
    const DrawModeFlags nOriginalDrawMode(mpOutputDevice->GetDrawMode());
406
0
    adaptTextToFillDrawMode();
407
408
0
    if (SAL_LIKELY(m_bRenderDecoratedTextDirect))
409
0
    {
410
0
        RenderTextSimpleOrDecoratedPortionPrimitive2D(rCandidate);
411
0
    }
412
0
    else
413
0
    {
414
0
        process(rCandidate);
415
0
    }
416
417
    // restore DrawMode
418
0
    mpOutputDevice->SetDrawMode(nOriginalDrawMode);
419
0
}
420
421
void VclPixelProcessor2D::processPolygonHairlinePrimitive2D(
422
    const primitive2d::PolygonHairlinePrimitive2D& rPolygonHairlinePrimitive2D)
423
0
{
424
0
    if (tryDrawPolygonHairlinePrimitive2DDirect(rPolygonHairlinePrimitive2D, 0.0))
425
0
    {
426
0
        return;
427
0
    }
428
429
    // direct draw of hairline
430
0
    RenderPolygonHairlinePrimitive2D(rPolygonHairlinePrimitive2D, true);
431
0
}
432
433
void VclPixelProcessor2D::processBitmapPrimitive2D(
434
    const primitive2d::BitmapPrimitive2D& rBitmapCandidate)
435
0
{
436
    // check if graphic content is inside discrete local ViewPort
437
0
    const basegfx::B2DRange& rDiscreteViewPort(getViewInformation2D().getDiscreteViewport());
438
0
    const basegfx::B2DHomMatrix aLocalTransform(maCurrentTransformation
439
0
                                                * rBitmapCandidate.getTransform());
440
441
0
    if (!rDiscreteViewPort.isEmpty())
442
0
    {
443
0
        basegfx::B2DRange aUnitRange(0.0, 0.0, 1.0, 1.0);
444
445
0
        aUnitRange.transform(aLocalTransform);
446
447
0
        if (!aUnitRange.overlaps(rDiscreteViewPort))
448
0
        {
449
            // content is outside discrete local ViewPort
450
0
            return;
451
0
        }
452
0
    }
453
454
0
    RenderBitmapPrimitive2D(rBitmapCandidate);
455
0
}
456
457
void VclPixelProcessor2D::processPolyPolygonGradientPrimitive2D(
458
    const primitive2d::PolyPolygonGradientPrimitive2D& rPolygonCandidate)
459
0
{
460
0
    basegfx::B2DPolyPolygon aLocalPolyPolygon(rPolygonCandidate.getB2DPolyPolygon());
461
462
    // no geometry, no need to render, done
463
0
    if (!aLocalPolyPolygon.count())
464
0
        return;
465
466
    // *try* direct draw (AKA using old VCL stuff) to render gradient
467
0
    const attribute::FillGradientAttribute& rGradient(rPolygonCandidate.getFillGradient());
468
469
    // MCGR: *many* - and not only GradientStops - cases cannot be handled by VCL
470
    // so use decomposition
471
    // NOTE: There may be even more reasons to detect, e.g. a ViewTransformation
472
    // that uses shear/rotate/mirror (what VCL cannot handle at all), see
473
    // other checks already in processFillGradientPrimitive2D
474
0
    if (rGradient.cannotBeHandledByVCL())
475
0
    {
476
0
        process(rPolygonCandidate);
477
0
        return;
478
0
    }
479
480
0
    basegfx::BColor aStartColor(
481
0
        maBColorModifierStack.getModifiedColor(rGradient.getColorStops().front().getStopColor()));
482
0
    basegfx::BColor aEndColor(
483
0
        maBColorModifierStack.getModifiedColor(rGradient.getColorStops().back().getStopColor()));
484
485
0
    if (aStartColor == aEndColor)
486
0
    {
487
        // no gradient at all, draw as polygon in AA and non-AA case
488
0
        aLocalPolyPolygon.transform(maCurrentTransformation);
489
0
        mpOutputDevice->SetLineColor();
490
0
        mpOutputDevice->SetFillColor(Color(aStartColor));
491
0
        mpOutputDevice->DrawPolyPolygon(aLocalPolyPolygon);
492
0
        return;
493
0
    }
494
495
    // use the primitive decomposition
496
0
    process(rPolygonCandidate);
497
0
}
498
499
void VclPixelProcessor2D::processPolyPolygonColorPrimitive2D(
500
    const primitive2d::PolyPolygonColorPrimitive2D& rPolyPolygonColorPrimitive2D)
501
0
{
502
    // try to use directly
503
0
    basegfx::B2DPolyPolygon aLocalPolyPolygon;
504
505
0
    tryDrawPolyPolygonColorPrimitive2DDirect(rPolyPolygonColorPrimitive2D, 0.0);
506
    // okay, done. In this case no gaps should have to be repaired, too
507
508
    // when AA is on and this filled polygons are the result of stroked line geometry,
509
    // draw the geometry once extra as lines to avoid AA 'gaps' between partial polygons
510
    // Caution: This is needed in both cases (!)
511
0
    if (!(mnPolygonStrokePrimitive2D && getViewInformation2D().getUseAntiAliasing()
512
0
          && (mpOutputDevice->GetAntialiasing() & AntialiasingFlags::Enable)))
513
0
        return;
514
515
0
    const basegfx::BColor aPolygonColor(
516
0
        maBColorModifierStack.getModifiedColor(rPolyPolygonColorPrimitive2D.getBColor()));
517
0
    sal_uInt32 nCount(aLocalPolyPolygon.count());
518
519
0
    if (!nCount)
520
0
    {
521
0
        aLocalPolyPolygon = rPolyPolygonColorPrimitive2D.getB2DPolyPolygon();
522
0
        aLocalPolyPolygon.transform(maCurrentTransformation);
523
0
        nCount = aLocalPolyPolygon.count();
524
0
    }
525
526
0
    mpOutputDevice->SetFillColor();
527
0
    mpOutputDevice->SetLineColor(Color(aPolygonColor));
528
529
0
    for (sal_uInt32 a(0); a < nCount; a++)
530
0
    {
531
0
        mpOutputDevice->DrawPolyLine(aLocalPolyPolygon.getB2DPolygon(a), 0.0);
532
0
    }
533
0
}
534
535
void VclPixelProcessor2D::processUnifiedTransparencePrimitive2D(
536
    const primitive2d::UnifiedTransparencePrimitive2D& rUniTransparenceCandidate)
537
0
{
538
    // Detect if a single PolyPolygonColorPrimitive2D is contained; in that case,
539
    // use the faster OutputDevice::DrawTransparent method
540
0
    const primitive2d::Primitive2DContainer& rContent = rUniTransparenceCandidate.getChildren();
541
542
0
    if (rContent.empty())
543
0
        return;
544
545
0
    if (0.0 == rUniTransparenceCandidate.getTransparence())
546
0
    {
547
        // not transparent at all, use content
548
0
        process(rUniTransparenceCandidate.getChildren());
549
0
    }
550
0
    else if (rUniTransparenceCandidate.getTransparence() > 0.0
551
0
             && rUniTransparenceCandidate.getTransparence() < 1.0)
552
0
    {
553
0
        bool bDrawTransparentUsed(false);
554
555
0
        if (1 == rContent.size())
556
0
        {
557
0
            const primitive2d::BasePrimitive2D* pBasePrimitive = rContent.front().get();
558
559
0
            switch (pBasePrimitive->getPrimitive2DID())
560
0
            {
561
0
                case PRIMITIVE2D_ID_POLYPOLYGONCOLORPRIMITIVE2D:
562
0
                {
563
                    // single transparent tools::PolyPolygon identified, use directly
564
0
                    const primitive2d::PolyPolygonColorPrimitive2D* pPoPoColor
565
0
                        = static_cast<const primitive2d::PolyPolygonColorPrimitive2D*>(
566
0
                            pBasePrimitive);
567
0
                    assert(pPoPoColor && "OOps, PrimitiveID and PrimitiveType do not match (!)");
568
0
                    bDrawTransparentUsed = true;
569
0
                    tryDrawPolyPolygonColorPrimitive2DDirect(
570
0
                        *pPoPoColor, rUniTransparenceCandidate.getTransparence());
571
0
                    break;
572
0
                }
573
0
                case PRIMITIVE2D_ID_POLYGONHAIRLINEPRIMITIVE2D:
574
0
                {
575
                    // single transparent PolygonHairlinePrimitive2D identified, use directly
576
0
                    const primitive2d::PolygonHairlinePrimitive2D* pPoHair
577
0
                        = static_cast<const primitive2d::PolygonHairlinePrimitive2D*>(
578
0
                            pBasePrimitive);
579
0
                    SAL_WARN_IF(!pPoHair, "drawinglayer",
580
0
                                "OOps, PrimitiveID and PrimitiveType do not match (!)");
581
582
                    // do no tallow by default - problem is that self-overlapping parts of this geometry will
583
                    // not be in an all-same transparency but will already alpha-cover themselves with blending.
584
                    // This is not what the UnifiedTransparencePrimitive2D defines: It requires all its
585
                    // content to be uniformly transparent.
586
                    // For hairline the effect is pretty minimal, but still not correct.
587
0
                    bDrawTransparentUsed = false;
588
0
                    break;
589
0
                }
590
0
                case PRIMITIVE2D_ID_POLYGONSTROKEPRIMITIVE2D:
591
0
                {
592
                    // single transparent PolygonStrokePrimitive2D identified, use directly
593
0
                    const primitive2d::PolygonStrokePrimitive2D* pPoStroke
594
0
                        = static_cast<const primitive2d::PolygonStrokePrimitive2D*>(pBasePrimitive);
595
0
                    SAL_WARN_IF(!pPoStroke, "drawinglayer",
596
0
                                "OOps, PrimitiveID and PrimitiveType do not match (!)");
597
598
                    // do no tallow by default - problem is that self-overlapping parts of this geometry will
599
                    // not be in an all-same transparency but will already alpha-cover themselves with blending.
600
                    // This is not what the UnifiedTransparencePrimitive2D defines: It requires all its
601
                    // content to be uniformly transparent.
602
                    // To check, activate and draw a wide transparent self-crossing line/curve
603
0
                    bDrawTransparentUsed = false;
604
0
                    break;
605
0
                }
606
0
                default:
607
0
                    SAL_INFO("drawinglayer",
608
0
                             "default case for " << drawinglayer::primitive2d::idToString(
609
0
                                 rUniTransparenceCandidate.getPrimitive2DID()));
610
0
                    break;
611
0
            }
612
0
        }
613
614
0
        if (!bDrawTransparentUsed)
615
0
        {
616
            // unified sub-transparence. Draw to VDev first.
617
0
            RenderUnifiedTransparencePrimitive2D(rUniTransparenceCandidate);
618
0
        }
619
0
    }
620
0
}
621
622
void VclPixelProcessor2D::processControlPrimitive2D(
623
    const primitive2d::ControlPrimitive2D& rControlPrimitive)
624
0
{
625
    // find out if the control is already visualized as a VCL-ChildWindow
626
0
    bool bControlIsVisibleAsChildWindow(rControlPrimitive.isVisibleAsChildWindow());
627
628
    // tdf#131281 The FormControls are not painted when using the Tiled Rendering for a simple
629
    // reason: when e.g. bControlIsVisibleAsChildWindow is true. This is the case because the
630
    // office is in non-layout mode (default for controls at startup). For the common office
631
    // this means that there exists a real VCL-System-Window for the control, so it is *not*
632
    // painted here due to being exactly obscured by that real Window (and creates danger of
633
    // flickering, too).
634
    // Tiled Rendering clients usually do *not* have real VCL-Windows for the controls, but
635
    // exactly that would be needed on each client displaying the tiles (what would be hard
636
    // to do but also would have advantages - the clients would have real controls in the
637
    //  shape of their target system which could be interacted with...). It is also what the
638
    // office does.
639
    // For now, fallback to just render these controls when Tiled Rendering is active to just
640
    // have them displayed on all clients.
641
0
    if (bControlIsVisibleAsChildWindow && comphelper::LibreOfficeKit::isActive())
642
0
    {
643
        // Do force paint when we are in Tiled Renderer and FormControl is 'visible'
644
0
        bControlIsVisibleAsChildWindow = false;
645
0
    }
646
647
0
    if (bControlIsVisibleAsChildWindow)
648
0
    {
649
        // f the control is already visualized as a VCL-ChildWindow it
650
        // does not need to be painted at all
651
0
        return;
652
0
    }
653
654
0
    bool bDone(false);
655
656
0
    try
657
0
    {
658
0
        const uno::Reference<awt::XGraphics> xTargetGraphics(mpOutputDevice->CreateUnoGraphics());
659
660
0
        if (xTargetGraphics.is())
661
0
        {
662
            // Needs to be drawn. Link new graphics and view
663
0
            const uno::Reference<awt::XControl>& rXControl(rControlPrimitive.getXControl());
664
0
            uno::Reference<awt::XView> xControlView(rXControl, uno::UNO_QUERY_THROW);
665
0
            const uno::Reference<awt::XGraphics> xOriginalGraphics(xControlView->getGraphics());
666
0
            xControlView->setGraphics(xTargetGraphics);
667
668
            // get position
669
0
            const basegfx::B2DHomMatrix aObjectToPixel(maCurrentTransformation
670
0
                                                       * rControlPrimitive.getTransform());
671
0
            const basegfx::B2DPoint aTopLeftPixel(aObjectToPixel * basegfx::B2DPoint(0.0, 0.0));
672
673
            // Do not forget to use the evtl. offsetted origin of the target device,
674
            // e.g. when used with mask/transparence buffer device
675
0
            const Point aOrigin(mpOutputDevice->GetMapMode().GetOrigin());
676
0
            xControlView->draw(aOrigin.X() + basegfx::fround(aTopLeftPixel.getX()),
677
0
                               aOrigin.Y() + basegfx::fround(aTopLeftPixel.getY()));
678
679
            // restore original graphics
680
0
            xControlView->setGraphics(xOriginalGraphics);
681
0
            bDone = true;
682
0
        }
683
0
    }
684
0
    catch (const uno::Exception&)
685
0
    {
686
        // #i116763# removing since there is a good alternative when the xControlView
687
        // is not found and it is allowed to happen
688
        // DBG_UNHANDLED_EXCEPTION();
689
0
    }
690
691
0
    if (!bDone)
692
0
    {
693
        // process recursively and use the decomposition as Bitmap
694
0
        process(rControlPrimitive);
695
0
    }
696
0
}
697
698
void VclPixelProcessor2D::processPolygonStrokePrimitive2D(
699
    const primitive2d::PolygonStrokePrimitive2D& rPolygonStrokePrimitive2D)
700
0
{
701
    // try to use directly
702
0
    if (tryDrawPolygonStrokePrimitive2DDirect(rPolygonStrokePrimitive2D, 0.0))
703
0
    {
704
0
        return;
705
0
    }
706
707
    // the stroke primitive may be decomposed to filled polygons. To keep
708
    // evtl. set DrawModes aka DrawModeFlags::BlackLine, DrawModeFlags::GrayLine,
709
    // DrawModeFlags::GhostedLine, DrawModeFlags::WhiteLine or DrawModeFlags::SettingsLine
710
    // working, these need to be copied to the corresponding fill modes
711
0
    const DrawModeFlags nOriginalDrawMode(mpOutputDevice->GetDrawMode());
712
0
    adaptLineToFillDrawMode();
713
714
    // polygon stroke primitive
715
716
    // Lines with 1 and 2 pixel width without AA need special treatment since their visualization
717
    // as filled polygons is geometrically correct but looks wrong since polygon filling avoids
718
    // the right and bottom pixels. The used method evaluates that and takes the correct action,
719
    // including calling recursively with decomposition if line is wide enough
720
0
    RenderPolygonStrokePrimitive2D(rPolygonStrokePrimitive2D);
721
722
    // restore DrawMode
723
0
    mpOutputDevice->SetDrawMode(nOriginalDrawMode);
724
0
}
725
726
void VclPixelProcessor2D::processFillHatchPrimitive2D(
727
    const primitive2d::FillHatchPrimitive2D& rFillHatchPrimitive)
728
0
{
729
0
    if (getViewInformation2D().getUseAntiAliasing())
730
0
    {
731
        // if AA is used (or ignore smoothing is on), there is no need to smooth
732
        // hatch painting, use decomposition
733
0
        process(rFillHatchPrimitive);
734
0
    }
735
0
    else
736
0
    {
737
        // without AA, use VCL to draw the hatch. It snaps hatch distances to the next pixel
738
        // and forces hatch distance to be >= 3 pixels to make the hatch display look smoother.
739
        // This is wrong in principle, but looks nicer. This could also be done here directly
740
        // without VCL usage if needed
741
0
        const attribute::FillHatchAttribute& rFillHatchAttributes
742
0
            = rFillHatchPrimitive.getFillHatch();
743
744
        // create hatch polygon in range size and discrete coordinates
745
0
        basegfx::B2DRange aHatchRange(rFillHatchPrimitive.getOutputRange());
746
0
        aHatchRange.transform(maCurrentTransformation);
747
0
        const basegfx::B2DPolygon aHatchPolygon(basegfx::utils::createPolygonFromRect(aHatchRange));
748
749
0
        if (rFillHatchAttributes.isFillBackground())
750
0
        {
751
            // #i111846# background fill is active; draw fill polygon
752
0
            const basegfx::BColor aPolygonColor(
753
0
                maBColorModifierStack.getModifiedColor(rFillHatchPrimitive.getBColor()));
754
755
0
            mpOutputDevice->SetFillColor(Color(aPolygonColor));
756
0
            mpOutputDevice->SetLineColor();
757
0
            mpOutputDevice->DrawPolygon(aHatchPolygon);
758
0
        }
759
760
        // set hatch line color
761
0
        const basegfx::BColor aHatchColor(
762
0
            maBColorModifierStack.getModifiedColor(rFillHatchPrimitive.getBColor()));
763
0
        mpOutputDevice->SetFillColor();
764
0
        mpOutputDevice->SetLineColor(Color(aHatchColor));
765
766
        // get hatch style
767
0
        HatchStyle eHatchStyle(HatchStyle::Single);
768
769
0
        switch (rFillHatchAttributes.getStyle())
770
0
        {
771
0
            default: // HatchStyle::Single
772
0
            {
773
0
                break;
774
0
            }
775
0
            case attribute::HatchStyle::Double:
776
0
            {
777
0
                eHatchStyle = HatchStyle::Double;
778
0
                break;
779
0
            }
780
0
            case attribute::HatchStyle::Triple:
781
0
            {
782
0
                eHatchStyle = HatchStyle::Triple;
783
0
                break;
784
0
            }
785
0
        }
786
787
        // create hatch
788
0
        const basegfx::B2DVector aDiscreteDistance(
789
0
            maCurrentTransformation * basegfx::B2DVector(rFillHatchAttributes.getDistance(), 0.0));
790
0
        const sal_uInt32 nDistance(basegfx::fround(aDiscreteDistance.getLength()));
791
0
        const sal_uInt32 nAngle10(
792
0
            basegfx::fround(basegfx::rad2deg<10>(rFillHatchAttributes.getAngle())));
793
0
        ::Hatch aVCLHatch(eHatchStyle, Color(rFillHatchAttributes.getColor()), nDistance,
794
0
                          Degree10(nAngle10));
795
796
        // draw hatch using VCL
797
0
        mpOutputDevice->DrawHatch(::tools::PolyPolygon(::tools::Polygon(aHatchPolygon)), aVCLHatch);
798
0
    }
799
0
}
800
801
void VclPixelProcessor2D::processBackgroundColorPrimitive2D(
802
    const primitive2d::BackgroundColorPrimitive2D& rPrimitive)
803
0
{
804
    // #i98404# Handle directly, especially when AA is active
805
0
    const AntialiasingFlags nOriginalAA(mpOutputDevice->GetAntialiasing());
806
807
    // switch AA off in all cases
808
0
    mpOutputDevice->SetAntialiasing(mpOutputDevice->GetAntialiasing() & ~AntialiasingFlags::Enable);
809
810
    // create color for fill
811
0
    const basegfx::BColor aPolygonColor(
812
0
        maBColorModifierStack.getModifiedColor(rPrimitive.getBColor()));
813
0
    Color aFillColor(aPolygonColor);
814
0
    aFillColor.SetAlpha(255 - sal_uInt8((rPrimitive.getTransparency() * 255.0) + 0.5));
815
0
    mpOutputDevice->SetFillColor(aFillColor);
816
0
    mpOutputDevice->SetLineColor();
817
818
    // create rectangle for fill
819
0
    const basegfx::B2DRange& aViewport(getViewInformation2D().getDiscreteViewport());
820
0
    const ::tools::Rectangle aRectangle(static_cast<sal_Int32>(floor(aViewport.getMinX())),
821
0
                                        static_cast<sal_Int32>(floor(aViewport.getMinY())),
822
0
                                        static_cast<sal_Int32>(ceil(aViewport.getMaxX())),
823
0
                                        static_cast<sal_Int32>(ceil(aViewport.getMaxY())));
824
0
    mpOutputDevice->DrawRect(aRectangle);
825
826
    // restore AA setting
827
0
    mpOutputDevice->SetAntialiasing(nOriginalAA);
828
0
}
829
830
void VclPixelProcessor2D::processBorderLinePrimitive2D(
831
    const drawinglayer::primitive2d::BorderLinePrimitive2D& rBorder)
832
0
{
833
    // Process recursively, but switch off AntiAliasing for
834
    // horizontal/vertical lines (*not* diagonal lines).
835
    // Checked using AntialiasingFlags::PixelSnapHairline instead,
836
    // but with AntiAliasing on the display really is too 'ghosty' when
837
    // using fine stroking. Correct, but 'ghosty'.
838
839
    // It has shown that there are quite some problems here:
840
    // - vcl OutDev renderer methods still use fallbacks to paint
841
    //   multiple single lines between discrete sizes of < 3.5 what
842
    //   looks bad and does not match
843
    // - mix of filled Polygons and Lines is bad when AA switched off
844
    // - Alignment of AA with non-AA may be bad in diverse different
845
    //   renderers
846
    //
847
    // Due to these reasons I change the strategy: Always draw AAed, but
848
    // allow fallback to test/check and if needed. The normal case
849
    // where BorderLines will be system-dependently snapped to have at
850
    // least a single discrete width per partial line (there may be up to
851
    // three) works well nowadays due to most renderers moving the AA stuff
852
    // by 0.5 pixels (discrete units) to match well with the non-AAed parts.
853
    //
854
    // Env-Switch for steering this, default is off.
855
    // Enable by setting at all (and to something)
856
0
    static const char* pSwitchOffAntiAliasingForHorVerBorderlines(
857
0
        getenv("SAL_SWITCH_OFF_ANTIALIASING_FOR_HOR_VER_BORTDERLINES"));
858
0
    static bool bSwitchOffAntiAliasingForHorVerBorderlines(
859
0
        nullptr != pSwitchOffAntiAliasingForHorVerBorderlines);
860
861
0
    if (bSwitchOffAntiAliasingForHorVerBorderlines
862
0
        && rBorder.isHorizontalOrVertical(getViewInformation2D()))
863
0
    {
864
0
        AntialiasingFlags nAntiAliasing = mpOutputDevice->GetAntialiasing();
865
0
        mpOutputDevice->SetAntialiasing(nAntiAliasing & ~AntialiasingFlags::Enable);
866
0
        process(rBorder);
867
0
        mpOutputDevice->SetAntialiasing(nAntiAliasing);
868
0
    }
869
0
    else
870
0
    {
871
0
        process(rBorder);
872
0
    }
873
0
}
874
875
void VclPixelProcessor2D::processInvertPrimitive2D(const primitive2d::BasePrimitive2D& rCandidate)
876
0
{
877
    // invert primitive (currently only used for HighContrast fallback for selection in SW and SC).
878
    // (Not true, also used at least for the drawing of dragged column and row boundaries in SC.)
879
    // Set OutDev to XOR and switch AA off (XOR does not work with AA)
880
0
    auto popIt = mpOutputDevice->ScopedPush();
881
0
    mpOutputDevice->SetRasterOp(RasterOp::Xor);
882
0
    const AntialiasingFlags nAntiAliasing(mpOutputDevice->GetAntialiasing());
883
0
    mpOutputDevice->SetAntialiasing(nAntiAliasing & ~AntialiasingFlags::Enable);
884
885
    // process content recursively
886
0
    process(rCandidate);
887
888
    // restore OutDev
889
0
    mpOutputDevice->SetAntialiasing(nAntiAliasing);
890
0
}
891
892
void VclPixelProcessor2D::processMetaFilePrimitive2D(const primitive2d::BasePrimitive2D& rCandidate)
893
0
{
894
    // #i98289#
895
0
    const bool bForceLineSnap(getViewInformation2D().getPixelSnapHairline());
896
0
    const AntialiasingFlags nOldAntiAliase(mpOutputDevice->GetAntialiasing());
897
898
0
    if (bForceLineSnap)
899
0
    {
900
0
        mpOutputDevice->SetAntialiasing(nOldAntiAliase | AntialiasingFlags::PixelSnapHairline);
901
0
    }
902
903
0
    process(rCandidate);
904
905
0
    if (bForceLineSnap)
906
0
    {
907
0
        mpOutputDevice->SetAntialiasing(nOldAntiAliase);
908
0
    }
909
0
}
910
911
void VclPixelProcessor2D::processFillGradientPrimitive2D(
912
    const primitive2d::FillGradientPrimitive2D& rPrimitive)
913
0
{
914
0
    if (rPrimitive.hasAlphaGradient() || rPrimitive.hasTransparency())
915
0
    {
916
        // SDPR: As long as direct alpha is not supported by this
917
        // renderer we need to work on the decomposition, so call it
918
0
        process(rPrimitive);
919
0
        return;
920
0
    }
921
922
0
    const attribute::FillGradientAttribute& rFillGradient = rPrimitive.getFillGradient();
923
0
    bool useDecompose(false);
924
925
    // MCGR: *many* - and not only GradientStops - cases cannot be handled by VCL
926
    // so use decomposition
927
0
    if (rFillGradient.cannotBeHandledByVCL())
928
0
    {
929
0
        useDecompose = true;
930
0
    }
931
932
    // tdf#149754 VCL gradient draw is not capable to handle all primitive gradient definitions,
933
    // what should be clear due to being developed to extend/replace them in
934
    // capabilities & precision.
935
    // It is e.g. not capable to correctly paint if the OutputRange is not completely
936
    // inside the DefinitionRange, thus forcing to paint gradient parts *outside* the
937
    // DefinitionRange.
938
    // This happens for Writer with Frames anchored in Frames (and was broken due to
939
    // falling back to VCL Gradient paint here), and for the new SlideBackgroundFill
940
    // Fill mode for objects using it that reach outside the page (which is the
941
    // DefinitionRange in that case).
942
    // I see no real reason to fallback here to OutputDevice::DrawGradient and VCL
943
    // gradient paint at all (system-dependent renderers wouldn't in the future), but
944
    // will for convenience only add that needed additional correcting case
945
0
    if (!useDecompose && !rPrimitive.getDefinitionRange().isInside(rPrimitive.getOutputRange()))
946
0
    {
947
0
        useDecompose = true;
948
0
    }
949
950
    // tdf#151081 need to use regular primitive decomposition when the gradient
951
    // is transformed in any other way then just translate & scale
952
0
    if (!useDecompose)
953
0
    {
954
0
        basegfx::B2DVector aScale, aTranslate;
955
0
        double fRotate, fShearX;
956
957
0
        maCurrentTransformation.decompose(aScale, aTranslate, fRotate, fShearX);
958
959
        // detect if transformation is rotated, sheared or mirrored in X and/or Y
960
0
        if (!basegfx::fTools::equalZero(fRotate) || !basegfx::fTools::equalZero(fShearX)
961
0
            || aScale.getX() < 0.0 || aScale.getY() < 0.0)
962
0
        {
963
0
            useDecompose = true;
964
0
        }
965
0
    }
966
967
0
    if (useDecompose)
968
0
    {
969
        // default is to use the direct render below. For security,
970
        // keep the (simple) fallback to decompose in place here
971
0
        static bool bTryDirectRender(true);
972
973
0
        if (bTryDirectRender)
974
0
        {
975
            // MCGR: Avoid one level of primitive creation, use FillGradientPrimitive2D
976
            // tooling to directly create needed geometry & color for getting better
977
            // performance (partially compensate for potentially more expensive multi
978
            // color gradients).
979
            // To handle a primitive that needs paint, either use decompose, or - when you
980
            // do not want that for any reason, e.g. extra primitives created - implement
981
            // a direct handling in your primitive renderer. This is always possible
982
            // since primitives by definition are self-contained what means they have all
983
            // needed data locally available to do so.
984
            // The question is the complexity to invest - the implemented decompose
985
            // is always a good hint of what is needed to do this. In this case I decided
986
            // to add some tooling methods to the primitive itself to support this. These
987
            // are used in decompose and can be used - as here now - for direct handling,
988
            // too. This is always a possibility in primitive handling - you can, but do not
989
            // have to.
990
0
            mpOutputDevice->SetFillColor(
991
0
                Color(maBColorModifierStack.getModifiedColor(rPrimitive.getOuterColor())));
992
0
            mpOutputDevice->SetLineColor();
993
0
            mpOutputDevice->DrawTransparent(
994
0
                maCurrentTransformation,
995
0
                basegfx::B2DPolyPolygon(
996
0
                    basegfx::utils::createPolygonFromRect(rPrimitive.getOutputRange())),
997
0
                0.0);
998
999
            // paint solid fill steps by providing callback as lambda
1000
0
            auto aCallback([&rPrimitive, this](const basegfx::B2DHomMatrix& rMatrix,
1001
0
                                               const basegfx::BColor& rColor) {
1002
                // create part polygon
1003
0
                basegfx::B2DPolygon aNewPoly(rPrimitive.getUnitPolygon());
1004
0
                aNewPoly.transform(rMatrix);
1005
1006
                // create solid fill
1007
0
                mpOutputDevice->SetFillColor(Color(maBColorModifierStack.getModifiedColor(rColor)));
1008
0
                mpOutputDevice->DrawTransparent(maCurrentTransformation,
1009
0
                                                basegfx::B2DPolyPolygon(aNewPoly), 0.0);
1010
0
            });
1011
1012
            // call value generator to trigger callbacks
1013
0
            rPrimitive.generateMatricesAndColors(aCallback);
1014
0
        }
1015
0
        else
1016
0
        {
1017
            // use the decompose
1018
0
            process(rPrimitive);
1019
0
        }
1020
1021
0
        return;
1022
0
    }
1023
1024
    // try to use vcl - since vcl uses the old gradient paint mechanisms this may
1025
    // create wrong geometries. If so, add another case above for useDecompose
1026
0
    Gradient aGradient(rFillGradient.getStyle(),
1027
0
                       Color(rFillGradient.getColorStops().front().getStopColor()),
1028
0
                       Color(rFillGradient.getColorStops().back().getStopColor()));
1029
1030
0
    aGradient.SetAngle(Degree10(static_cast<int>(basegfx::rad2deg<10>(rFillGradient.getAngle()))));
1031
0
    aGradient.SetBorder(rFillGradient.getBorder() * 100);
1032
0
    aGradient.SetOfsX(rFillGradient.getOffsetX() * 100.0);
1033
0
    aGradient.SetOfsY(rFillGradient.getOffsetY() * 100.0);
1034
0
    aGradient.SetSteps(rFillGradient.getSteps());
1035
1036
0
    basegfx::B2DRange aOutputRange(rPrimitive.getOutputRange());
1037
0
    aOutputRange.transform(maCurrentTransformation);
1038
0
    basegfx::B2DRange aFullRange(rPrimitive.getDefinitionRange());
1039
0
    aFullRange.transform(maCurrentTransformation);
1040
1041
0
    const tools::Rectangle aOutputRectangle(
1042
0
        std::floor(aOutputRange.getMinX()), std::floor(aOutputRange.getMinY()),
1043
0
        std::ceil(aOutputRange.getMaxX()), std::ceil(aOutputRange.getMaxY()));
1044
0
    const tools::Rectangle aFullRectangle(
1045
0
        std::floor(aFullRange.getMinX()), std::floor(aFullRange.getMinY()),
1046
0
        std::ceil(aFullRange.getMaxX()), std::ceil(aFullRange.getMaxY()));
1047
1048
0
    auto popIt = mpOutputDevice->ScopedPush(vcl::PushFlags::CLIPREGION);
1049
0
    mpOutputDevice->IntersectClipRegion(aOutputRectangle);
1050
0
    mpOutputDevice->DrawGradient(aFullRectangle, aGradient);
1051
0
}
1052
1053
void VclPixelProcessor2D::processPatternFillPrimitive2D(
1054
    const primitive2d::PatternFillPrimitive2D& rPrimitive)
1055
0
{
1056
0
    const basegfx::B2DRange& rReferenceRange = rPrimitive.getReferenceRange();
1057
0
    if (rReferenceRange.isEmpty() || rReferenceRange.getWidth() <= 0.0
1058
0
        || rReferenceRange.getHeight() <= 0.0)
1059
0
        return;
1060
1061
0
    basegfx::B2DPolyPolygon aMask = rPrimitive.getMask();
1062
0
    aMask.transform(maCurrentTransformation);
1063
0
    const basegfx::B2DRange aMaskRange(aMask.getB2DRange());
1064
1065
0
    if (aMaskRange.isEmpty() || aMaskRange.getWidth() <= 0.0 || aMaskRange.getHeight() <= 0.0)
1066
0
        return;
1067
1068
0
    sal_uInt32 nTileWidth, nTileHeight;
1069
0
    rPrimitive.getTileSize(nTileWidth, nTileHeight, getViewInformation2D());
1070
0
    if (nTileWidth == 0 || nTileHeight == 0)
1071
0
        return;
1072
0
    Bitmap aTileImage = rPrimitive.createTileImage(nTileWidth, nTileHeight);
1073
0
    tools::Rectangle aMaskRect = vcl::unotools::rectangleFromB2DRectangle(aMaskRange);
1074
1075
    // Unless smooth edges are needed, simply use clipping.
1076
0
    if (basegfx::utils::isRectangle(aMask) || !getViewInformation2D().getUseAntiAliasing())
1077
0
    {
1078
0
        auto popIt = mpOutputDevice->ScopedPush(vcl::PushFlags::CLIPREGION);
1079
0
        mpOutputDevice->IntersectClipRegion(vcl::Region(aMask));
1080
0
        Wallpaper aWallpaper(aTileImage);
1081
0
        aWallpaper.SetColor(COL_TRANSPARENT);
1082
0
        Point aPaperPt(aMaskRect.getX() % nTileWidth, aMaskRect.getY() % nTileHeight);
1083
0
        tools::Rectangle aPaperRect(aPaperPt, aTileImage.GetSizePixel());
1084
0
        aWallpaper.SetRect(aPaperRect);
1085
0
        mpOutputDevice->DrawWallpaper(aMaskRect, aWallpaper);
1086
0
        return;
1087
0
    }
1088
1089
    // if the tile is a single pixel big, just flood fill with that pixel color
1090
0
    if (nTileWidth == 1 && nTileHeight == 1)
1091
0
    {
1092
0
        Color col = aTileImage.GetPixelColor(0, 0);
1093
0
        mpOutputDevice->SetLineColor(col);
1094
0
        mpOutputDevice->SetFillColor(col);
1095
0
        mpOutputDevice->DrawPolyPolygon(aMask);
1096
0
        return;
1097
0
    }
1098
1099
0
    impBufferDevice aBufferDevice(*mpOutputDevice, aMaskRect);
1100
1101
0
    if (!aBufferDevice.isVisible())
1102
0
        return;
1103
1104
    // remember last OutDev and set to content
1105
0
    OutputDevice* pLastOutputDevice = mpOutputDevice;
1106
0
    mpOutputDevice = &aBufferDevice.getContent();
1107
1108
0
    Wallpaper aWallpaper(aTileImage);
1109
0
    aWallpaper.SetColor(COL_TRANSPARENT);
1110
0
    Point aPaperPt(aMaskRect.getX() % nTileWidth, aMaskRect.getY() % nTileHeight);
1111
0
    tools::Rectangle aPaperRect(aPaperPt, aTileImage.GetSizePixel());
1112
0
    aWallpaper.SetRect(aPaperRect);
1113
0
    mpOutputDevice->DrawWallpaper(aMaskRect, aWallpaper);
1114
1115
    // back to old OutDev
1116
0
    mpOutputDevice = pLastOutputDevice;
1117
1118
    // draw mask
1119
0
    VirtualDevice& rMask = aBufferDevice.getTransparence();
1120
0
    rMask.SetLineColor();
1121
0
    rMask.SetFillColor(COL_BLACK);
1122
0
    rMask.DrawPolyPolygon(aMask);
1123
1124
    // dump buffer to outdev
1125
0
    aBufferDevice.paint();
1126
0
}
1127
1128
} // end of namespace
1129
1130
/* vim:set shiftwidth=4 softtabstop=4 expandtab: */