Coverage Report

Created: 2025-11-16 09:57

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