Coverage Report

Created: 2026-05-16 09:25

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/libreoffice/drawinglayer/source/processor2d/vclprocessor2d.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 "vclprocessor2d.hxx"
21
22
#include "getdigitlanguage.hxx"
23
#include "vclhelperbufferdevice.hxx"
24
#include <cmath>
25
#include <comphelper/lok.hxx>
26
#include <tools/debug.hxx>
27
#include <tools/fract.hxx>
28
#include <utility>
29
#include <vcl/canvastools.hxx>
30
#include <vcl/fntstyle.hxx>
31
#include <vcl/glyphitemcache.hxx>
32
#include <vcl/graph.hxx>
33
#include <vcl/kernarray.hxx>
34
#include <vcl/outdev.hxx>
35
#include <vcl/rendercontext/DrawModeFlags.hxx>
36
#include <sal/log.hxx>
37
#include <basegfx/polygon/b2dpolygontools.hxx>
38
#include <basegfx/polygon/b2dpolypolygontools.hxx>
39
#include <basegfx/polygon/b2dpolygonclipper.hxx>
40
#include <basegfx/color/bcolor.hxx>
41
#include <basegfx/matrix/b2dhommatrixtools.hxx>
42
#include <drawinglayer/primitive2d/drawinglayer_primitivetypes2d.hxx>
43
#include <drawinglayer/primitive2d/textprimitive2d.hxx>
44
#include <drawinglayer/primitive2d/textdecoratedprimitive2d.hxx>
45
#include <drawinglayer/primitive2d/bitmapprimitive2d.hxx>
46
#include <drawinglayer/primitive2d/fillgraphicprimitive2d.hxx>
47
#include <drawinglayer/primitive2d/PolygonHairlinePrimitive2D.hxx>
48
#include <drawinglayer/primitive2d/PolygonStrokePrimitive2D.hxx>
49
#include <drawinglayer/primitive2d/PolyPolygonGraphicPrimitive2D.hxx>
50
#include <drawinglayer/primitive2d/maskprimitive2d.hxx>
51
#include <drawinglayer/primitive2d/modifiedcolorprimitive2d.hxx>
52
#include <drawinglayer/primitive2d/unifiedtransparenceprimitive2d.hxx>
53
#include <drawinglayer/primitive2d/transparenceprimitive2d.hxx>
54
#include <drawinglayer/primitive2d/transformprimitive2d.hxx>
55
#include <drawinglayer/primitive2d/markerarrayprimitive2d.hxx>
56
#include <drawinglayer/primitive2d/pagepreviewprimitive2d.hxx>
57
#include <drawinglayer/primitive2d/textenumsprimitive2d.hxx>
58
#include <drawinglayer/primitive2d/svggradientprimitive2d.hxx>
59
// control support
60
#include <drawinglayer/primitive2d/textlayoutdevice.hxx>
61
62
#include <drawinglayer/primitive2d/pointarrayprimitive2d.hxx>
63
#include <drawinglayer/primitive2d/epsprimitive2d.hxx>
64
65
using namespace com::sun::star;
66
67
namespace
68
{
69
sal_uInt32 calculateStepsForSvgGradient(const basegfx::BColor& rColorA,
70
                                        const basegfx::BColor& rColorB, double fDelta,
71
                                        double fDiscreteUnit)
72
0
{
73
    // use color distance, assume to do every color step
74
0
    sal_uInt32 nSteps(basegfx::fround(rColorA.getDistance(rColorB) * 255.0));
75
76
0
    if (nSteps)
77
0
    {
78
        // calc discrete length to change color each discrete unit (pixel)
79
0
        const sal_uInt32 nDistSteps(basegfx::fround(fDelta / fDiscreteUnit));
80
81
0
        nSteps = std::min(nSteps, nDistSteps);
82
0
    }
83
84
    // reduce quality to 3 discrete units or every 3rd color step for rendering
85
0
    nSteps /= 2;
86
87
    // roughly cut when too big or too small (not full quality, reduce complexity)
88
0
    nSteps = std::min(nSteps, sal_uInt32(255));
89
0
    nSteps = std::max(nSteps, sal_uInt32(1));
90
91
0
    return nSteps;
92
0
}
93
}
94
95
namespace
96
{
97
/** helper to convert a MapMode to a transformation */
98
basegfx::B2DHomMatrix getTransformFromMapMode(const MapMode& rMapMode)
99
1.08k
{
100
1.08k
    basegfx::B2DHomMatrix aMapping;
101
1.08k
    const double fNoScale = 1.0;
102
1.08k
    const Point& rOrigin(rMapMode.GetOrigin());
103
104
1.08k
    if (0 != rOrigin.X() || 0 != rOrigin.Y())
105
1.08k
    {
106
1.08k
        aMapping.translate(rOrigin.X(), rOrigin.Y());
107
1.08k
    }
108
109
1.08k
    if (rMapMode.GetScaleX() != fNoScale || rMapMode.GetScaleY() != fNoScale)
110
0
    {
111
0
        aMapping.scale(rMapMode.GetScaleX(), rMapMode.GetScaleY());
112
0
    }
113
114
1.08k
    return aMapping;
115
1.08k
}
116
}
117
118
namespace drawinglayer::processor2d
119
{
120
// rendering support
121
122
// directdraw of text simple portion or decorated portion primitive. When decorated, all the extra
123
// information is translated to VCL parameters and set at the font.
124
// Acceptance is restricted to no shearing and positive scaling in X and Y (no font mirroring
125
// for VCL)
126
void VclProcessor2D::RenderTextSimpleOrDecoratedPortionPrimitive2D(
127
    const primitive2d::TextSimplePortionPrimitive2D& rTextCandidate)
128
1.08k
{
129
    // decompose matrix to have position and size of text
130
1.08k
    basegfx::B2DHomMatrix aLocalTransform(maCurrentTransformation
131
1.08k
                                          * rTextCandidate.getTextTransform());
132
1.08k
    basegfx::B2DVector aFontScaling, aTranslate;
133
1.08k
    double fRotate, fShearX;
134
1.08k
    aLocalTransform.decompose(aFontScaling, aTranslate, fRotate, fShearX);
135
136
1.08k
    bool bPrimitiveAccepted(false);
137
138
    // tdf#95581: Assume tiny shears are rounding artefacts or whatever and can be ignored,
139
    // especially if the effect is less than a pixel.
140
1.08k
    if (std::abs(aFontScaling.getY() * fShearX) < 1)
141
1.08k
    {
142
1.08k
        if (aFontScaling.getX() < 0.0 && aFontScaling.getY() < 0.0)
143
0
        {
144
            // handle special case: If scale is negative in (x,y) (3rd quadrant), it can
145
            // be expressed as rotation by PI. Use this since the Font rendering will not
146
            // apply the negative scales in any form
147
0
            aFontScaling = basegfx::absolute(aFontScaling);
148
0
            fRotate += M_PI;
149
0
        }
150
151
1.08k
        if (aFontScaling.getX() > 0.0 && aFontScaling.getY() > 0.0)
152
1.08k
        {
153
1.08k
            double fIgnoreRotate, fIgnoreShearX;
154
155
1.08k
            basegfx::B2DVector aFontSize, aTextTranslate;
156
1.08k
            rTextCandidate.getTextTransform().decompose(aFontSize, aTextTranslate, fIgnoreRotate,
157
1.08k
                                                        fIgnoreShearX);
158
159
            // tdf#153092 Ideally we don't have to scale the font and dxarray, but we might have
160
            // to nevertheless if dealing with non integer sizes
161
1.08k
            const bool bScaleFont(aFontSize.getY() != std::round(aFontSize.getY())
162
1.08k
                                  || comphelper::LibreOfficeKit::isActive());
163
1.08k
            vcl::Font aFont;
164
165
            // Get the VCL font
166
1.08k
            if (!bScaleFont)
167
1.08k
            {
168
1.08k
                aFont = primitive2d::getVclFontFromFontAttribute(
169
1.08k
                    rTextCandidate.getFontAttribute(), aFontSize.getX(), aFontSize.getY(), fRotate,
170
1.08k
                    rTextCandidate.getLocale());
171
1.08k
            }
172
1
            else
173
1
            {
174
1
                aFont = primitive2d::getVclFontFromFontAttribute(
175
1
                    rTextCandidate.getFontAttribute(), aFontScaling.getX(), aFontScaling.getY(),
176
1
                    fRotate, rTextCandidate.getLocale());
177
1
            }
178
179
            // Don't draw fonts without height
180
1.08k
            Size aResultFontSize = aFont.GetFontSize();
181
1.08k
            if (aResultFontSize.Height() <= 0)
182
0
                return;
183
184
            // set FillColor Attribute
185
1.08k
            const Color aFillColor(rTextCandidate.getTextFillColor());
186
1.08k
            aFont.SetTransparent(aFillColor.IsTransparent());
187
1.08k
            aFont.SetFillColor(aFillColor);
188
189
1.08k
            const sal_uInt8 nProportionalFontSize(rTextCandidate.getProportionalFontSize());
190
1.08k
            assert(nProportionalFontSize > 0);
191
1.08k
            const bool bManualBackground(!aFillColor.IsTransparent()
192
0
                                         && nProportionalFontSize != 100);
193
1.08k
            if (bManualBackground)
194
0
            {
195
0
                aFont.SetTransparent(true);
196
0
                aFont.SetFillColor(COL_TRANSPARENT);
197
0
            }
198
199
            // handle additional font attributes
200
1.08k
            const primitive2d::TextDecoratedPortionPrimitive2D* pTCPP = nullptr;
201
1.08k
            if (rTextCandidate.getPrimitive2DID() == PRIMITIVE2D_ID_TEXTDECORATEDPORTIONPRIMITIVE2D)
202
0
                pTCPP = static_cast<const primitive2d::TextDecoratedPortionPrimitive2D*>(
203
0
                    &rTextCandidate);
204
205
1.08k
            if (pTCPP != nullptr)
206
0
            {
207
                // set the color of text decorations
208
0
                const basegfx::BColor aTextlineColor
209
0
                    = maBColorModifierStack.getModifiedColor(pTCPP->getTextlineColor());
210
0
                mpOutputDevice->SetTextLineColor(Color(aTextlineColor));
211
212
                // set Overline attribute
213
0
                const FontLineStyle eFontOverline(
214
0
                    primitive2d::mapTextLineToFontLineStyle(pTCPP->getFontOverline()));
215
0
                if (eFontOverline != LINESTYLE_NONE)
216
0
                {
217
0
                    aFont.SetOverline(eFontOverline);
218
0
                    const basegfx::BColor aOverlineColor
219
0
                        = maBColorModifierStack.getModifiedColor(pTCPP->getOverlineColor());
220
0
                    mpOutputDevice->SetOverlineColor(Color(aOverlineColor));
221
0
                    if (pTCPP->getWordLineMode())
222
0
                        aFont.SetWordLineMode(true);
223
0
                }
224
225
                // set Underline attribute
226
0
                const FontLineStyle eFontLineStyle(
227
0
                    primitive2d::mapTextLineToFontLineStyle(pTCPP->getFontUnderline()));
228
0
                if (eFontLineStyle != LINESTYLE_NONE)
229
0
                {
230
0
                    aFont.SetUnderline(eFontLineStyle);
231
0
                    if (pTCPP->getWordLineMode())
232
0
                        aFont.SetWordLineMode(true);
233
0
                }
234
235
                // set Strikeout attribute
236
0
                const FontStrikeout eFontStrikeout(
237
0
                    primitive2d::mapTextStrikeoutToFontStrikeout(pTCPP->getTextStrikeout()));
238
239
0
                if (eFontStrikeout != STRIKEOUT_NONE)
240
0
                    aFont.SetStrikeout(eFontStrikeout);
241
242
                // set EmphasisMark attribute
243
0
                FontEmphasisMark eFontEmphasisMark = FontEmphasisMark::NONE;
244
0
                switch (pTCPP->getTextEmphasisMark())
245
0
                {
246
0
                    default:
247
0
                        SAL_WARN("drawinglayer",
248
0
                                 "Unknown EmphasisMark style " << pTCPP->getTextEmphasisMark());
249
0
                        [[fallthrough]];
250
0
                    case primitive2d::TEXT_FONT_EMPHASIS_MARK_NONE:
251
0
                        eFontEmphasisMark = FontEmphasisMark::NONE;
252
0
                        break;
253
0
                    case primitive2d::TEXT_FONT_EMPHASIS_MARK_DOT:
254
0
                        eFontEmphasisMark = FontEmphasisMark::Dot;
255
0
                        break;
256
0
                    case primitive2d::TEXT_FONT_EMPHASIS_MARK_CIRCLE:
257
0
                        eFontEmphasisMark = FontEmphasisMark::Circle;
258
0
                        break;
259
0
                    case primitive2d::TEXT_FONT_EMPHASIS_MARK_DISC:
260
0
                        eFontEmphasisMark = FontEmphasisMark::Disc;
261
0
                        break;
262
0
                    case primitive2d::TEXT_FONT_EMPHASIS_MARK_ACCENT:
263
0
                        eFontEmphasisMark = FontEmphasisMark::Accent;
264
0
                        break;
265
0
                }
266
267
0
                if (eFontEmphasisMark != FontEmphasisMark::NONE)
268
0
                {
269
0
                    DBG_ASSERT((pTCPP->getEmphasisMarkAbove() != pTCPP->getEmphasisMarkBelow()),
270
0
                               "DrawingLayer: Bad EmphasisMark position!");
271
0
                    if (pTCPP->getEmphasisMarkAbove())
272
0
                        eFontEmphasisMark |= FontEmphasisMark::PosAbove;
273
0
                    else
274
0
                        eFontEmphasisMark |= FontEmphasisMark::PosBelow;
275
0
                    aFont.SetEmphasisMark(eFontEmphasisMark);
276
0
                }
277
278
                // set Relief attribute
279
0
                FontRelief eFontRelief = FontRelief::NONE;
280
0
                switch (pTCPP->getTextRelief())
281
0
                {
282
0
                    default:
283
0
                        SAL_WARN("drawinglayer", "Unknown Relief style " << pTCPP->getTextRelief());
284
0
                        [[fallthrough]];
285
0
                    case primitive2d::TEXT_RELIEF_NONE:
286
0
                        eFontRelief = FontRelief::NONE;
287
0
                        break;
288
0
                    case primitive2d::TEXT_RELIEF_EMBOSSED:
289
0
                        eFontRelief = FontRelief::Embossed;
290
0
                        break;
291
0
                    case primitive2d::TEXT_RELIEF_ENGRAVED:
292
0
                        eFontRelief = FontRelief::Engraved;
293
0
                        break;
294
0
                }
295
296
0
                if (eFontRelief != FontRelief::NONE)
297
0
                    aFont.SetRelief(eFontRelief);
298
299
                // set Shadow attribute
300
0
                if (pTCPP->getShadow())
301
0
                    aFont.SetShadow(true);
302
0
            }
303
304
            // create integer DXArray
305
1.08k
            KernArray aDXArray;
306
307
1.08k
            if (!rTextCandidate.getDXArray().empty())
308
1.08k
            {
309
1.08k
                double fPixelVectorFactor(1.0);
310
1.08k
                if (bScaleFont)
311
1
                {
312
1
                    const basegfx::B2DVector aPixelVector(maCurrentTransformation
313
1
                                                          * basegfx::B2DVector(1.0, 0.0));
314
1
                    fPixelVectorFactor = aPixelVector.getLength();
315
1
                }
316
317
1.08k
                aDXArray.reserve(rTextCandidate.getDXArray().size());
318
1.08k
                for (auto const& elem : rTextCandidate.getDXArray())
319
3.24k
                    aDXArray.push_back(elem * fPixelVectorFactor);
320
1.08k
            }
321
322
            // set parameters and paint text snippet
323
1.08k
            const basegfx::BColor aRGBFontColor(
324
1.08k
                maBColorModifierStack.getModifiedColor(rTextCandidate.getFontColor()));
325
326
            // Store previous complex text layout state, to be restored after drawing
327
1.08k
            const vcl::text::ComplexTextLayoutFlags nOldLayoutMode(mpOutputDevice->GetLayoutMode());
328
329
1.08k
            if (rTextCandidate.getFontAttribute().getRTL())
330
0
            {
331
0
                vcl::text::ComplexTextLayoutFlags nRTLLayoutMode(
332
0
                    nOldLayoutMode & ~vcl::text::ComplexTextLayoutFlags::BiDiStrong);
333
0
                nRTLLayoutMode |= vcl::text::ComplexTextLayoutFlags::BiDiRtl
334
0
                                  | vcl::text::ComplexTextLayoutFlags::TextOriginLeft;
335
0
                mpOutputDevice->SetLayoutMode(nRTLLayoutMode);
336
0
            }
337
1.08k
            else
338
1.08k
            {
339
                // tdf#101686: This is LTR text, but the output device may have RTL state.
340
1.08k
                vcl::text::ComplexTextLayoutFlags nLTRLayoutMode(nOldLayoutMode);
341
1.08k
                nLTRLayoutMode = nLTRLayoutMode & ~vcl::text::ComplexTextLayoutFlags::BiDiRtl;
342
1.08k
                nLTRLayoutMode = nLTRLayoutMode & ~vcl::text::ComplexTextLayoutFlags::BiDiStrong;
343
1.08k
                mpOutputDevice->SetLayoutMode(nLTRLayoutMode);
344
1.08k
            }
345
346
1.08k
            Point aStartPoint;
347
1.08k
            bool bChangeMapMode(false);
348
1.08k
            if (!bScaleFont)
349
1.08k
            {
350
1.08k
                basegfx::B2DHomMatrix aCombinedTransform(
351
1.08k
                    getTransformFromMapMode(mpOutputDevice->GetMapMode())
352
1.08k
                    * maCurrentTransformation);
353
354
1.08k
                basegfx::B2DVector aCurrentScaling, aCurrentTranslate;
355
1.08k
                double fCurrentRotate;
356
1.08k
                aCombinedTransform.decompose(aCurrentScaling, aCurrentTranslate, fCurrentRotate,
357
1.08k
                                             fIgnoreShearX);
358
359
1.08k
                const Point aOrigin(
360
1.08k
                    basegfx::fround<tools::Long>(aCurrentTranslate.getX() / aCurrentScaling.getX()),
361
1.08k
                    basegfx::fround<tools::Long>(aCurrentTranslate.getY()
362
1.08k
                                                 / aCurrentScaling.getY()));
363
364
1.08k
                double fScaleX(aCurrentScaling.getX());
365
1.08k
                double fScaleY(aCurrentScaling.getY());
366
367
1.08k
                MapMode aMapMode(mpOutputDevice->GetMapMode().GetMapUnit(), aOrigin, fScaleX,
368
1.08k
                                 fScaleY);
369
1.08k
                bChangeMapMode = aMapMode != mpOutputDevice->GetMapMode();
370
1.08k
                if (bChangeMapMode)
371
14
                {
372
14
                    mpOutputDevice->Push(vcl::PushFlags::MAPMODE);
373
14
                    mpOutputDevice->SetRelativeMapMode(aMapMode);
374
14
                }
375
376
1.08k
                basegfx::B2DHomMatrix aFinalTransform(aCombinedTransform
377
1.08k
                                                      * rTextCandidate.getTextTransform());
378
1.08k
                const basegfx::B2DPoint aPoint(aFinalTransform * basegfx::B2DPoint(0.0, 0.0));
379
380
1.08k
                Point aFinalPoint(
381
1.08k
                    basegfx::fround<tools::Long>(aPoint.getX() / aCurrentScaling.getX()),
382
1.08k
                    basegfx::fround<tools::Long>(aPoint.getY() / aCurrentScaling.getY()));
383
384
1.08k
                aStartPoint = Point(aFinalPoint.X() - aOrigin.X(), aFinalPoint.Y() - aOrigin.Y());
385
1.08k
            }
386
1
            else
387
1
            {
388
1
                const basegfx::B2DPoint aPoint(aLocalTransform * basegfx::B2DPoint(0.0, 0.0));
389
1
                double aPointX = aPoint.getX(), aPointY = aPoint.getY();
390
391
1
                if (!comphelper::LibreOfficeKit::isActive())
392
1
                {
393
                    // aFont has an integer size; we must scale a bit for precision
394
1
                    double nFontScalingFixY = aFontScaling.getY() / aResultFontSize.Height();
395
1
                    double nFontScalingFixX
396
1
                        = aFontScaling.getX()
397
1
                          / (aResultFontSize.Width() ? aResultFontSize.Width()
398
1
                                                     : aResultFontSize.Height());
399
400
#ifdef _WIN32
401
                    if (aResultFontSize.Width()
402
                        && aResultFontSize.Width() != aResultFontSize.Height())
403
                    {
404
                        // See getVclFontFromFontAttribute in drawinglayer/source/primitive2d/textlayoutdevice.cxx
405
                        vcl::Font aUnscaledTest(aFont);
406
                        aUnscaledTest.SetFontSize({ 0, aResultFontSize.Height() });
407
                        const FontMetric aUnscaledFontMetric(
408
                            Application::GetDefaultDevice()->GetFontMetric(aUnscaledTest));
409
                        if (aUnscaledFontMetric.GetAverageFontWidth() > 0)
410
                        {
411
                            double nExistingXScale = static_cast<double>(aResultFontSize.Width())
412
                                                     / aUnscaledFontMetric.GetAverageFontWidth();
413
                            nFontScalingFixX
414
                                = aFontScaling.getX() / aFontScaling.getY() / nExistingXScale;
415
                        }
416
                    }
417
#endif
418
419
1
                    if (!rtl_math_approxEqual(nFontScalingFixY, 1.0)
420
1
                        || !rtl_math_approxEqual(nFontScalingFixX, 1.0))
421
0
                    {
422
0
                        MapMode aMapMode = mpOutputDevice->GetMapMode();
423
0
                        aMapMode.SetScaleX(aMapMode.GetScaleX() * nFontScalingFixX);
424
0
                        aMapMode.SetScaleY(aMapMode.GetScaleY() * nFontScalingFixY);
425
426
0
                        assert(nFontScalingFixX != 0 && nFontScalingFixY != 0
427
0
                               && "or bValidScaling would be false");
428
429
0
                        Point origin = aMapMode.GetOrigin();
430
431
0
                        mpOutputDevice->Push(vcl::PushFlags::MAPMODE);
432
0
                        mpOutputDevice->SetRelativeMapMode(aMapMode);
433
0
                        bChangeMapMode = true;
434
435
0
                        aPointX = (aPointX + origin.X()) / nFontScalingFixX - origin.X();
436
0
                        aPointY = (aPointY + origin.Y()) / nFontScalingFixY - origin.Y();
437
0
                    }
438
1
                }
439
440
1
                aStartPoint = Point(basegfx::fround<tools::Long>(aPointX),
441
1
                                    basegfx::fround<tools::Long>(aPointY));
442
1
            }
443
444
            // tdf#168371 set letter spacing so that VCL knows it has to disable ligatures
445
1.08k
            aFont.SetFixKerning(rTextCandidate.getLetterSpacing());
446
447
1.08k
            aFont.SetOpticalSizing(rTextCandidate.getOpticalSizing());
448
1.08k
            aFont.SetVariations(rTextCandidate.getFontVariations());
449
450
            // tdf#152990 set the font after the MapMode is (potentially) set so canvas uses the desired
451
            // font size
452
1.08k
            mpOutputDevice->SetFont(aFont);
453
1.08k
            mpOutputDevice->SetTextColor(Color(aRGBFontColor));
454
455
1.08k
            if (bManualBackground)
456
0
            {
457
0
                FontMetric aFM = mpOutputDevice->GetFontMetric();
458
0
                tools::Long nPropAsc = aFM.GetAscent();
459
0
                tools::Long nPropDesc = aFM.GetDescent();
460
0
                double fScale = 100.0 / nProportionalFontSize;
461
0
                double fEscOff
462
0
                    = rTextCandidate.getEscapement() / -100.0 * aResultFontSize.Height() * fScale;
463
0
                tools::Long nAdjAsc = basegfx::fround<tools::Long>(fEscOff + nPropAsc * fScale);
464
0
                tools::Long nAdjDesc = basegfx::fround<tools::Long>(nPropDesc * fScale - fEscOff);
465
0
                tools::Long nTextWidth
466
0
                    = !aDXArray.empty()
467
0
                          ? basegfx::fround<tools::Long>(aDXArray.back())
468
0
                          : mpOutputDevice->GetTextWidth(rTextCandidate.getText(),
469
0
                                                         rTextCandidate.getTextPosition(),
470
0
                                                         rTextCandidate.getTextLength());
471
472
                // trim trailing whitespace from background width to not have background over
473
                // trailing whitespace, since that looks like an error for the user.
474
0
                if (!rTextCandidate.getDXArray().empty())
475
0
                {
476
0
                    sal_Int32 nLast(rTextCandidate.getTextPosition()
477
0
                                    + rTextCandidate.getTextLength() - 1);
478
0
                    sal_Int32 nFirst(rTextCandidate.getTextPosition());
479
0
                    while (nLast >= nFirst && rTextCandidate.getText()[nLast] == ' ')
480
0
                        nLast--;
481
0
                    sal_Int32 nTrimmedLen(nLast - nFirst + 1);
482
0
                    if (nTrimmedLen > 0 && nTrimmedLen < rTextCandidate.getTextLength())
483
0
                    {
484
0
                        double fPixelVectorFactor(1.0);
485
0
                        if (!aDXArray.empty() && !rTextCandidate.getDXArray().empty())
486
0
                            fPixelVectorFactor = static_cast<double>(aDXArray.back())
487
0
                                                 / rTextCandidate.getDXArray().back();
488
0
                        nTextWidth = basegfx::fround<tools::Long>(
489
0
                            rTextCandidate.getDXArray()[nTrimmedLen - 1] * fPixelVectorFactor);
490
0
                    }
491
0
                    else if (nTrimmedLen <= 0)
492
0
                        nTextWidth = 0;
493
0
                }
494
495
0
                auto aScopedPush = mpOutputDevice->ScopedPush(vcl::PushFlags::FILLCOLOR
496
0
                                                              | vcl::PushFlags::LINECOLOR);
497
0
                mpOutputDevice->SetFillColor(aFillColor);
498
0
                mpOutputDevice->SetLineColor();
499
0
                mpOutputDevice->DrawRect(
500
0
                    tools::Rectangle(aStartPoint.X(), aStartPoint.Y() - nAdjAsc,
501
0
                                     aStartPoint.X() + nTextWidth, aStartPoint.Y() + nAdjDesc));
502
0
            }
503
504
1.08k
            if (!aDXArray.empty())
505
1.08k
            {
506
1.08k
                const SalLayoutGlyphs* pGlyphs = SalLayoutGlyphsCache::self()->GetLayoutGlyphs(
507
1.08k
                    mpOutputDevice, rTextCandidate.getText(), rTextCandidate.getTextPosition(),
508
1.08k
                    rTextCandidate.getTextLength());
509
1.08k
                mpOutputDevice->DrawTextArray(
510
1.08k
                    aStartPoint, rTextCandidate.getText(), aDXArray,
511
1.08k
                    rTextCandidate.getKashidaArray(), rTextCandidate.getTextPosition(),
512
1.08k
                    rTextCandidate.getTextLength(), SalLayoutFlags::NONE, pGlyphs);
513
1.08k
            }
514
0
            else
515
0
            {
516
0
                mpOutputDevice->DrawText(aStartPoint, rTextCandidate.getText(),
517
0
                                         rTextCandidate.getTextPosition(),
518
0
                                         rTextCandidate.getTextLength());
519
0
            }
520
521
            // Restore previous layout mode
522
1.08k
            mpOutputDevice->SetLayoutMode(nOldLayoutMode);
523
524
1.08k
            if (bChangeMapMode)
525
14
                mpOutputDevice->Pop();
526
527
1.08k
            bPrimitiveAccepted = true;
528
1.08k
        }
529
1.08k
    }
530
531
1.08k
    if (!bPrimitiveAccepted)
532
0
    {
533
        // let break down
534
0
        process(rTextCandidate);
535
0
    }
536
1.08k
}
537
538
// direct draw of hairline
539
void VclProcessor2D::RenderPolygonHairlinePrimitive2D(
540
    const primitive2d::PolygonHairlinePrimitive2D& rPolygonCandidate, bool bPixelBased)
541
302
{
542
302
    const basegfx::BColor aHairlineColor(
543
302
        maBColorModifierStack.getModifiedColor(rPolygonCandidate.getBColor()));
544
302
    mpOutputDevice->SetLineColor(Color(aHairlineColor));
545
302
    mpOutputDevice->SetFillColor();
546
547
302
    basegfx::B2DPolygon aLocalPolygon(rPolygonCandidate.getB2DPolygon());
548
302
    aLocalPolygon.transform(maCurrentTransformation);
549
550
302
    if (bPixelBased && getViewInformation2D().getPixelSnapHairline())
551
0
    {
552
        // #i98289#
553
        // when a Hairline is painted and AntiAliasing is on the option SnapHorVerLinesToDiscrete
554
        // allows to suppress AntiAliasing for pure horizontal or vertical lines. This is done since
555
        // not-AntiAliased such lines look more pleasing to the eye (e.g. 2D chart content). This
556
        // NEEDS to be done in discrete coordinates, so only useful for pixel based rendering.
557
0
        aLocalPolygon = basegfx::utils::snapPointsOfHorizontalOrVerticalEdges(aLocalPolygon);
558
0
    }
559
560
302
    mpOutputDevice->DrawPolyLine(aLocalPolygon, 0.0);
561
302
}
562
563
// direct draw of transformed Bitmap primitive
564
void VclProcessor2D::RenderBitmapPrimitive2D(const primitive2d::BitmapPrimitive2D& rBitmapCandidate)
565
0
{
566
0
    Bitmap aBitmap(rBitmapCandidate.getBitmap());
567
0
    const basegfx::B2DHomMatrix aLocalTransform(maCurrentTransformation
568
0
                                                * rBitmapCandidate.getTransform());
569
570
0
    if (maBColorModifierStack.count())
571
0
    {
572
0
        aBitmap = aBitmap.Modify(maBColorModifierStack);
573
574
0
        if (aBitmap.IsEmpty())
575
0
        {
576
            // color gets completely replaced, get it
577
0
            const basegfx::BColor aModifiedColor(
578
0
                maBColorModifierStack.getModifiedColor(basegfx::BColor()));
579
0
            basegfx::B2DPolygon aPolygon(basegfx::utils::createUnitPolygon());
580
0
            aPolygon.transform(aLocalTransform);
581
582
0
            mpOutputDevice->SetFillColor(Color(aModifiedColor));
583
0
            mpOutputDevice->SetLineColor();
584
0
            mpOutputDevice->DrawPolygon(aPolygon);
585
586
0
            return;
587
0
        }
588
0
    }
589
590
    // #122923# do no longer add Alpha channel here; the right place to do this is when really
591
    // the own transformer is used (see OutputDevice::DrawTransformedBitmapEx).
592
593
    // draw using OutputDevice'sDrawTransformedBitmapEx
594
0
    mpOutputDevice->DrawTransformedBitmapEx(aLocalTransform, aBitmap);
595
0
}
596
597
void VclProcessor2D::RenderFillGraphicPrimitive2D(
598
    const primitive2d::FillGraphicPrimitive2D& rFillBitmapCandidate)
599
0
{
600
0
    if (rFillBitmapCandidate.getTransparency() < 0.0
601
0
        || rFillBitmapCandidate.getTransparency() > 1.0)
602
0
    {
603
        // invalid transparence, done
604
0
        return;
605
0
    }
606
607
0
    bool bPrimitiveAccepted = RenderFillGraphicPrimitive2DImpl(rFillBitmapCandidate);
608
609
0
    if (!bPrimitiveAccepted)
610
0
    {
611
        // do not accept, use decomposition
612
0
        process(rFillBitmapCandidate);
613
0
    }
614
0
}
615
616
bool VclProcessor2D::RenderFillGraphicPrimitive2DImpl(
617
    const primitive2d::FillGraphicPrimitive2D& rFillBitmapCandidate)
618
0
{
619
0
    const attribute::FillGraphicAttribute& rFillGraphicAttribute(
620
0
        rFillBitmapCandidate.getFillGraphic());
621
622
    // #121194# when tiling is used and content is bitmap-based, do direct tiling in the
623
    // renderer on pixel base to ensure tight fitting. Do not do this when
624
    // the fill is rotated or sheared.
625
0
    if (!rFillGraphicAttribute.getTiling())
626
0
        return false;
627
628
    // content is bitmap(ex)
629
    //
630
    // for Vector Graphic Data (SVG, EMF+) support, force decomposition when present. This will lead to use
631
    // the primitive representation of the vector data directly.
632
    //
633
    // when graphic is animated, force decomposition to use the correct graphic, else
634
    // fill style will not be animated
635
0
    if (GraphicType::Bitmap != rFillGraphicAttribute.getGraphic().GetType()
636
0
        || rFillGraphicAttribute.getGraphic().getVectorGraphicData()
637
0
        || rFillGraphicAttribute.getGraphic().IsAnimated())
638
0
        return false;
639
640
    // decompose matrix to check for shear, rotate and mirroring
641
0
    basegfx::B2DHomMatrix aLocalTransform(maCurrentTransformation
642
0
                                          * rFillBitmapCandidate.getTransformation());
643
0
    basegfx::B2DVector aScale, aTranslate;
644
0
    double fRotate, fShearX;
645
0
    aLocalTransform.decompose(aScale, aTranslate, fRotate, fShearX);
646
647
    // when nopt rotated/sheared
648
0
    if (!basegfx::fTools::equalZero(fRotate) || !basegfx::fTools::equalZero(fShearX))
649
0
        return false;
650
651
    // no shear or rotate, draw direct in pixel coordinates
652
653
    // transform object range to device coordinates (pixels). Use
654
    // the device transformation for better accuracy
655
0
    basegfx::B2DRange aObjectRange(aTranslate, aTranslate + aScale);
656
0
    aObjectRange.transform(mpOutputDevice->GetViewTransformation());
657
658
    // extract discrete size of object
659
0
    const sal_Int32 nOWidth(basegfx::fround(aObjectRange.getWidth()));
660
0
    const sal_Int32 nOHeight(basegfx::fround(aObjectRange.getHeight()));
661
662
    // only do something when object has a size in discrete units
663
0
    if (nOWidth <= 0 || nOHeight <= 0)
664
0
        return true;
665
666
    // transform graphic range to device coordinates (pixels). Use
667
    // the device transformation for better accuracy
668
0
    basegfx::B2DRange aGraphicRange(rFillGraphicAttribute.getGraphicRange());
669
0
    aGraphicRange.transform(mpOutputDevice->GetViewTransformation() * aLocalTransform);
670
671
    // extract discrete size of graphic
672
    // caution: when getting to zero, nothing would be painted; thus, do not allow this
673
0
    const sal_Int32 nBWidth(std::max(sal_Int32(1), basegfx::fround(aGraphicRange.getWidth())));
674
0
    const sal_Int32 nBHeight(std::max(sal_Int32(1), basegfx::fround(aGraphicRange.getHeight())));
675
676
    // nBWidth, nBHeight is the pixel size of the needed bitmap. To not need to scale it
677
    // in vcl many times, create a size-optimized version
678
0
    const Size aNeededBitmapSizePixel(nBWidth, nBHeight);
679
0
    Bitmap aBitmap(rFillGraphicAttribute.getGraphic().GetBitmap());
680
0
    const bool bPreScaled(nBWidth * nBHeight < (250 * 250));
681
682
    // ... but only up to a maximum size, else it gets too expensive
683
0
    if (bPreScaled)
684
0
    {
685
        // if color depth is below 24bit, expand before scaling for better quality.
686
        // This is even needed for low colors, else the scale will produce
687
        // a bitmap in gray or Black/White (!)
688
0
        if (isPalettePixelFormat(aBitmap.getPixelFormat()))
689
0
        {
690
0
            aBitmap.Convert(BmpConversion::N24Bit);
691
0
        }
692
693
0
        aBitmap.Scale(aNeededBitmapSizePixel, BmpScaleFlag::Interpolate);
694
0
    }
695
696
0
    if (rFillBitmapCandidate.hasTransparency())
697
0
        aBitmap.BlendAlpha(
698
0
            static_cast<sal_uInt8>(255 - (rFillBitmapCandidate.getTransparency() * 255)));
699
700
0
    if (maBColorModifierStack.count())
701
0
    {
702
        // when color modifier, apply to bitmap
703
0
        aBitmap = aBitmap.Modify(maBColorModifierStack);
704
705
        // ModifyBitmapEx uses empty bitmap as sign to return that
706
        // the content will be completely replaced to mono color, use shortcut
707
0
        if (aBitmap.IsEmpty())
708
0
        {
709
            // color gets completely replaced, get it
710
0
            const basegfx::BColor aModifiedColor(
711
0
                maBColorModifierStack.getModifiedColor(basegfx::BColor()));
712
0
            basegfx::B2DPolygon aPolygon(basegfx::utils::createUnitPolygon());
713
0
            aPolygon.transform(aLocalTransform);
714
715
0
            mpOutputDevice->SetFillColor(Color(aModifiedColor));
716
0
            mpOutputDevice->SetLineColor();
717
0
            mpOutputDevice->DrawPolygon(aPolygon);
718
719
0
            return true;
720
0
        }
721
0
    }
722
723
0
    sal_Int32 nBLeft(basegfx::fround(aGraphicRange.getMinX()));
724
0
    sal_Int32 nBTop(basegfx::fround(aGraphicRange.getMinY()));
725
0
    const sal_Int32 nOLeft(basegfx::fround(aObjectRange.getMinX()));
726
0
    const sal_Int32 nOTop(basegfx::fround(aObjectRange.getMinY()));
727
0
    sal_Int32 nPosX(0);
728
0
    sal_Int32 nPosY(0);
729
730
0
    if (nBLeft > nOLeft)
731
0
    {
732
0
        const sal_Int32 nDiff((nBLeft / nBWidth) + 1);
733
734
0
        nPosX -= nDiff;
735
0
        nBLeft -= nDiff * nBWidth;
736
0
    }
737
738
0
    if (nBLeft + nBWidth <= nOLeft)
739
0
    {
740
0
        const sal_Int32 nDiff(-nBLeft / nBWidth);
741
742
0
        nPosX += nDiff;
743
0
        nBLeft += nDiff * nBWidth;
744
0
    }
745
746
0
    if (nBTop > nOTop)
747
0
    {
748
0
        const sal_Int32 nDiff((nBTop / nBHeight) + 1);
749
750
0
        nPosY -= nDiff;
751
0
        nBTop -= nDiff * nBHeight;
752
0
    }
753
754
0
    if (nBTop + nBHeight <= nOTop)
755
0
    {
756
0
        const sal_Int32 nDiff(-nBTop / nBHeight);
757
758
0
        nPosY += nDiff;
759
0
        nBTop += nDiff * nBHeight;
760
0
    }
761
762
    // prepare OutDev
763
0
    const Point aEmptyPoint(0, 0);
764
    // the visible rect, in pixels
765
0
    const ::tools::Rectangle aVisiblePixel(aEmptyPoint, mpOutputDevice->GetOutputSizePixel());
766
0
    const bool bWasEnabled(mpOutputDevice->IsMapModeEnabled());
767
0
    mpOutputDevice->EnableMapMode(false);
768
769
    // check if offset is used
770
0
    const sal_Int32 nOffsetX(basegfx::fround(rFillGraphicAttribute.getOffsetX() * nBWidth));
771
0
    const sal_Int32 nOffsetY(basegfx::fround(rFillGraphicAttribute.getOffsetY() * nBHeight));
772
773
    // if the tile is a single pixel big, just flood fill with that pixel color
774
0
    if (nOffsetX == 0 && nOffsetY == 0 && aNeededBitmapSizePixel.getWidth() == 1
775
0
        && aNeededBitmapSizePixel.getHeight() == 1)
776
0
    {
777
0
        Color col = aBitmap.GetPixelColor(0, 0);
778
0
        mpOutputDevice->SetLineColor(col);
779
0
        mpOutputDevice->SetFillColor(col);
780
0
        mpOutputDevice->DrawRect(aVisiblePixel);
781
0
    }
782
0
    else if (nOffsetX)
783
0
    {
784
        // offset in X, so iterate over Y first and draw lines
785
0
        for (sal_Int32 nYPos(nBTop); nYPos < nOTop + nOHeight; nYPos += nBHeight, nPosY++)
786
0
        {
787
0
            for (sal_Int32 nXPos((nPosY % 2) ? nBLeft - nBWidth + nOffsetX : nBLeft);
788
0
                 nXPos < nOLeft + nOWidth; nXPos += nBWidth)
789
0
            {
790
0
                const ::tools::Rectangle aOutRectPixel(Point(nXPos, nYPos), aNeededBitmapSizePixel);
791
792
0
                if (aOutRectPixel.Overlaps(aVisiblePixel))
793
0
                {
794
0
                    if (bPreScaled)
795
0
                    {
796
0
                        mpOutputDevice->DrawBitmap(aOutRectPixel.TopLeft(), aBitmap);
797
0
                    }
798
0
                    else
799
0
                    {
800
0
                        mpOutputDevice->DrawBitmap(aOutRectPixel.TopLeft(), aNeededBitmapSizePixel,
801
0
                                                   aBitmap);
802
0
                    }
803
0
                }
804
0
            }
805
0
        }
806
0
    }
807
0
    else // nOffsetY is used
808
0
    {
809
        // possible offset in Y, so iterate over X first and draw columns
810
0
        for (sal_Int32 nXPos(nBLeft); nXPos < nOLeft + nOWidth; nXPos += nBWidth, nPosX++)
811
0
        {
812
0
            for (sal_Int32 nYPos((nPosX % 2) ? nBTop - nBHeight + nOffsetY : nBTop);
813
0
                 nYPos < nOTop + nOHeight; nYPos += nBHeight)
814
0
            {
815
0
                const ::tools::Rectangle aOutRectPixel(Point(nXPos, nYPos), aNeededBitmapSizePixel);
816
817
0
                if (aOutRectPixel.Overlaps(aVisiblePixel))
818
0
                {
819
0
                    if (bPreScaled)
820
0
                    {
821
0
                        mpOutputDevice->DrawBitmap(aOutRectPixel.TopLeft(), aBitmap);
822
0
                    }
823
0
                    else
824
0
                    {
825
0
                        mpOutputDevice->DrawBitmap(aOutRectPixel.TopLeft(), aNeededBitmapSizePixel,
826
0
                                                   aBitmap);
827
0
                    }
828
0
                }
829
0
            }
830
0
        }
831
0
    }
832
833
    // restore OutDev
834
0
    mpOutputDevice->EnableMapMode(bWasEnabled);
835
0
    return true;
836
0
}
837
838
// direct draw of Graphic
839
void VclProcessor2D::RenderPolyPolygonGraphicPrimitive2D(
840
    const primitive2d::PolyPolygonGraphicPrimitive2D& rPolygonCandidate)
841
0
{
842
0
    bool bDone(false);
843
0
    const basegfx::B2DPolyPolygon& rPolyPolygon = rPolygonCandidate.getB2DPolyPolygon();
844
845
    // #121194# Todo: check if this works
846
0
    if (!rPolyPolygon.count())
847
0
    {
848
        // empty polyPolygon, done
849
0
        bDone = true;
850
0
    }
851
0
    else
852
0
    {
853
0
        const attribute::FillGraphicAttribute& rFillGraphicAttribute
854
0
            = rPolygonCandidate.getFillGraphic();
855
856
        // try to catch cases where the graphic will be color-modified to a single
857
        // color (e.g. shadow)
858
0
        switch (rFillGraphicAttribute.getGraphic().GetType())
859
0
        {
860
0
            case GraphicType::GdiMetafile:
861
0
            {
862
                // metafiles are potentially transparent, cannot optimize, not done
863
0
                break;
864
0
            }
865
0
            case GraphicType::Bitmap:
866
0
            {
867
0
                if (!rFillGraphicAttribute.getGraphic().IsTransparent()
868
0
                    && !rFillGraphicAttribute.getGraphic().IsAlpha()
869
0
                    && !rPolygonCandidate.hasTransparency())
870
0
                {
871
                    // bitmap is not transparent and has no alpha
872
0
                    const sal_uInt32 nBColorModifierStackCount(maBColorModifierStack.count());
873
874
0
                    if (nBColorModifierStackCount)
875
0
                    {
876
0
                        const basegfx::BColorModifierSharedPtr& rTopmostModifier
877
0
                            = maBColorModifierStack.getBColorModifier(nBColorModifierStackCount
878
0
                                                                      - 1);
879
0
                        const basegfx::BColorModifier_replace* pReplacer
880
0
                            = dynamic_cast<const basegfx::BColorModifier_replace*>(
881
0
                                rTopmostModifier.get());
882
883
0
                        if (pReplacer)
884
0
                        {
885
                            // the bitmap fill is in unified color, so we can replace it with
886
                            // a single polygon fill. The form of the fill depends on tiling
887
0
                            if (rFillGraphicAttribute.getTiling())
888
0
                            {
889
                                // with tiling, fill the whole tools::PolyPolygon with the modifier color
890
0
                                basegfx::B2DPolyPolygon aLocalPolyPolygon(rPolyPolygon);
891
892
0
                                aLocalPolyPolygon.transform(maCurrentTransformation);
893
0
                                mpOutputDevice->SetLineColor();
894
0
                                mpOutputDevice->SetFillColor(Color(pReplacer->getBColor()));
895
0
                                mpOutputDevice->DrawPolyPolygon(aLocalPolyPolygon);
896
0
                            }
897
0
                            else
898
0
                            {
899
                                // without tiling, only the area common to the bitmap tile and the
900
                                // tools::PolyPolygon is filled. Create the bitmap tile area in object
901
                                // coordinates. For this, the object transformation needs to be created
902
                                // from the already scaled PolyPolygon. The tile area in object
903
                                // coordinates will always be non-rotated, so it's not necessary to
904
                                // work with a polygon here
905
0
                                basegfx::B2DRange aTileRange(
906
0
                                    rFillGraphicAttribute.getGraphicRange());
907
0
                                const basegfx::B2DRange aPolyPolygonRange(
908
0
                                    rPolyPolygon.getB2DRange());
909
0
                                const basegfx::B2DHomMatrix aNewObjectTransform(
910
0
                                    basegfx::utils::createScaleTranslateB2DHomMatrix(
911
0
                                        aPolyPolygonRange.getRange(),
912
0
                                        aPolyPolygonRange.getMinimum()));
913
914
0
                                aTileRange.transform(aNewObjectTransform);
915
916
                                // now clip the object polyPolygon against the tile range
917
                                // to get the common area
918
0
                                basegfx::B2DPolyPolygon aTarget
919
0
                                    = basegfx::utils::clipPolyPolygonOnRange(
920
0
                                        rPolyPolygon, aTileRange, true, false);
921
922
0
                                if (aTarget.count())
923
0
                                {
924
0
                                    aTarget.transform(maCurrentTransformation);
925
0
                                    mpOutputDevice->SetLineColor();
926
0
                                    mpOutputDevice->SetFillColor(Color(pReplacer->getBColor()));
927
0
                                    mpOutputDevice->DrawPolyPolygon(aTarget);
928
0
                                }
929
0
                            }
930
931
                            // simplified output executed, we are done
932
0
                            bDone = true;
933
0
                        }
934
0
                    }
935
0
                }
936
0
                break;
937
0
            }
938
0
            default: //GraphicType::NONE, GraphicType::Default
939
0
            {
940
                // empty graphic, we are done
941
0
                bDone = true;
942
0
                break;
943
0
            }
944
0
        }
945
0
    }
946
947
0
    if (!bDone)
948
0
    {
949
        // use default decomposition
950
0
        process(rPolygonCandidate);
951
0
    }
952
0
}
953
954
// mask group
955
void VclProcessor2D::RenderMaskPrimitive2DPixel(const primitive2d::MaskPrimitive2D& rMaskCandidate)
956
0
{
957
0
    if (rMaskCandidate.getChildren().empty())
958
0
        return;
959
960
0
    basegfx::B2DPolyPolygon aMask(rMaskCandidate.getMask());
961
962
0
    if (!aMask.count())
963
0
        return;
964
965
0
    aMask.transform(maCurrentTransformation);
966
967
    // Unless smooth edges are needed, simply use clipping.
968
0
    if (basegfx::utils::isRectangle(aMask) || !getViewInformation2D().getUseAntiAliasing())
969
0
    {
970
0
        auto popIt = mpOutputDevice->ScopedPush(vcl::PushFlags::CLIPREGION);
971
0
        mpOutputDevice->IntersectClipRegion(vcl::Region(aMask));
972
0
        process(rMaskCandidate.getChildren());
973
0
        return;
974
0
    }
975
976
0
    const basegfx::B2DRange aRange(aMask.getB2DRange());
977
0
    tools::Rectangle aMaskRect = vcl::unotools::rectangleFromB2DRectangle(aRange);
978
0
    impBufferDevice aBufferDevice(*mpOutputDevice, aMaskRect);
979
980
0
    if (!aBufferDevice.isVisible())
981
0
        return;
982
983
    // remember last OutDev and set to content
984
0
    OutputDevice* pLastOutputDevice = mpOutputDevice;
985
0
    mpOutputDevice = &aBufferDevice.getContent();
986
987
    // paint to it
988
0
    process(rMaskCandidate.getChildren());
989
990
    // back to old OutDev
991
0
    mpOutputDevice = pLastOutputDevice;
992
993
    // draw mask
994
0
    VirtualDevice& rMask = aBufferDevice.getTransparence();
995
0
    rMask.SetLineColor();
996
0
    rMask.SetFillColor(COL_BLACK);
997
0
    rMask.DrawPolyPolygon(aMask);
998
999
    // dump buffer to outdev
1000
0
    aBufferDevice.paint();
1001
0
}
1002
1003
// modified color group. Force output to unified color.
1004
void VclProcessor2D::RenderModifiedColorPrimitive2D(
1005
    const primitive2d::ModifiedColorPrimitive2D& rModifiedCandidate)
1006
0
{
1007
0
    if (!rModifiedCandidate.getChildren().empty())
1008
0
    {
1009
0
        maBColorModifierStack.push(rModifiedCandidate.getColorModifier());
1010
0
        process(rModifiedCandidate.getChildren());
1011
0
        maBColorModifierStack.pop();
1012
0
    }
1013
0
}
1014
1015
// unified sub-transparence. Draw to VDev first.
1016
void VclProcessor2D::RenderUnifiedTransparencePrimitive2D(
1017
    const primitive2d::UnifiedTransparencePrimitive2D& rTransCandidate)
1018
0
{
1019
0
    if (rTransCandidate.getChildren().empty())
1020
0
        return;
1021
1022
0
    if (0.0 == rTransCandidate.getTransparence())
1023
0
    {
1024
        // no transparence used, so just use the content
1025
0
        process(rTransCandidate.getChildren());
1026
0
    }
1027
0
    else if (rTransCandidate.getTransparence() > 0.0 && rTransCandidate.getTransparence() < 1.0)
1028
0
    {
1029
        // transparence is in visible range
1030
0
        basegfx::B2DRange aRange(rTransCandidate.getChildren().getB2DRange(getViewInformation2D()));
1031
0
        aRange.transform(maCurrentTransformation);
1032
0
        tools::Rectangle aRangeRect = vcl::unotools::rectangleFromB2DRectangle(aRange);
1033
0
        impBufferDevice aBufferDevice(*mpOutputDevice, aRangeRect);
1034
1035
0
        if (aBufferDevice.isVisible())
1036
0
        {
1037
            // remember last OutDev and set to content
1038
0
            OutputDevice* pLastOutputDevice = mpOutputDevice;
1039
0
            mpOutputDevice = &aBufferDevice.getContent();
1040
1041
            // paint content to it
1042
0
            process(rTransCandidate.getChildren());
1043
1044
            // back to old OutDev
1045
0
            mpOutputDevice = pLastOutputDevice;
1046
1047
            // dump buffer to outdev using given transparence
1048
0
            aBufferDevice.paint(rTransCandidate.getTransparence());
1049
0
        }
1050
0
    }
1051
0
}
1052
1053
// sub-transparence group. Draw to VDev first.
1054
void VclProcessor2D::RenderTransparencePrimitive2D(
1055
    const primitive2d::TransparencePrimitive2D& rTransCandidate)
1056
0
{
1057
0
    if (rTransCandidate.getChildren().empty())
1058
0
        return;
1059
1060
0
    basegfx::B2DRange aRange(rTransCandidate.getChildren().getB2DRange(getViewInformation2D()));
1061
0
    aRange.transform(maCurrentTransformation);
1062
0
    tools::Rectangle aRangeRect = vcl::unotools::rectangleFromB2DRectangle(aRange);
1063
0
    impBufferDevice aBufferDevice(*mpOutputDevice, aRangeRect);
1064
1065
0
    if (!aBufferDevice.isVisible())
1066
0
        return;
1067
1068
    // remember last OutDev and set to content
1069
0
    OutputDevice* pLastOutputDevice = mpOutputDevice;
1070
0
    mpOutputDevice = &aBufferDevice.getContent();
1071
1072
    // paint content to it
1073
0
    process(rTransCandidate.getChildren());
1074
1075
    // set to mask
1076
0
    mpOutputDevice = &aBufferDevice.getTransparence();
1077
1078
    // when painting transparence masks, reset the color stack
1079
0
    basegfx::BColorModifierStack aLastBColorModifierStack(maBColorModifierStack);
1080
0
    maBColorModifierStack = basegfx::BColorModifierStack();
1081
1082
    // paint mask to it (always with transparence intensities, evtl. with AA)
1083
0
    process(rTransCandidate.getTransparence());
1084
1085
    // back to old color stack
1086
0
    maBColorModifierStack = std::move(aLastBColorModifierStack);
1087
1088
    // back to old OutDev
1089
0
    mpOutputDevice = pLastOutputDevice;
1090
1091
    // dump buffer to outdev
1092
0
    aBufferDevice.paint();
1093
0
}
1094
1095
// transform group.
1096
void VclProcessor2D::RenderTransformPrimitive2D(
1097
    const primitive2d::TransformPrimitive2D& rTransformCandidate)
1098
222
{
1099
    // remember current transformation and ViewInformation
1100
222
    const basegfx::B2DHomMatrix aLastCurrentTransformation(maCurrentTransformation);
1101
222
    const geometry::ViewInformation2D aLastViewInformation2D(getViewInformation2D());
1102
1103
    // create new transformations for CurrentTransformation
1104
    // and for local ViewInformation2D
1105
222
    maCurrentTransformation = maCurrentTransformation * rTransformCandidate.getTransformation();
1106
222
    geometry::ViewInformation2D aViewInformation2D(getViewInformation2D());
1107
222
    aViewInformation2D.setObjectTransformation(getViewInformation2D().getObjectTransformation()
1108
222
                                               * rTransformCandidate.getTransformation());
1109
222
    setViewInformation2D(aViewInformation2D);
1110
1111
    // process content
1112
222
    process(rTransformCandidate.getChildren());
1113
1114
    // restore transformations
1115
222
    maCurrentTransformation = aLastCurrentTransformation;
1116
222
    setViewInformation2D(aLastViewInformation2D);
1117
222
}
1118
1119
// new XDrawPage for ViewInformation2D
1120
void VclProcessor2D::RenderPagePreviewPrimitive2D(
1121
    const primitive2d::PagePreviewPrimitive2D& rPagePreviewCandidate)
1122
0
{
1123
    // remember current transformation and ViewInformation
1124
0
    const geometry::ViewInformation2D aLastViewInformation2D(getViewInformation2D());
1125
1126
    // create new local ViewInformation2D
1127
0
    geometry::ViewInformation2D aViewInformation2D(getViewInformation2D());
1128
0
    aViewInformation2D.setVisualizedPage(rPagePreviewCandidate.getXDrawPage());
1129
0
    setViewInformation2D(aViewInformation2D);
1130
1131
    // process decomposed content
1132
0
    process(rPagePreviewCandidate);
1133
1134
    // restore transformations
1135
0
    setViewInformation2D(aLastViewInformation2D);
1136
0
}
1137
1138
// marker
1139
void VclProcessor2D::RenderMarkerArrayPrimitive2D(
1140
    const primitive2d::MarkerArrayPrimitive2D& rMarkArrayCandidate)
1141
0
{
1142
    // get data
1143
0
    const std::vector<basegfx::B2DPoint>& rPositions = rMarkArrayCandidate.getPositions();
1144
0
    const sal_uInt32 nCount(rPositions.size());
1145
1146
0
    if (!nCount || rMarkArrayCandidate.getMarker().IsEmpty())
1147
0
        return;
1148
1149
    // get pixel size
1150
0
    const Bitmap& rMarker(rMarkArrayCandidate.getMarker());
1151
0
    const Size aBitmapSize(rMarker.GetSizePixel());
1152
1153
0
    if (!(aBitmapSize.Width() && aBitmapSize.Height()))
1154
0
        return;
1155
1156
    // get discrete half size
1157
0
    const basegfx::B2DVector aDiscreteHalfSize((aBitmapSize.getWidth() - 1.0) * 0.5,
1158
0
                                               (aBitmapSize.getHeight() - 1.0) * 0.5);
1159
0
    const bool bWasEnabled(mpOutputDevice->IsMapModeEnabled());
1160
1161
    // do not forget evtl. moved origin in target device MapMode when
1162
    // switching it off; it would be missing and lead to wrong positions.
1163
    // All his could be done using logic sizes and coordinates, too, but
1164
    // we want a 1:1 bitmap rendering here, so it's more safe and faster
1165
    // to work with switching off MapMode usage completely.
1166
0
    const Point aOrigin(mpOutputDevice->GetMapMode().GetOrigin());
1167
1168
0
    mpOutputDevice->EnableMapMode(false);
1169
1170
0
    for (auto const& pos : rPositions)
1171
0
    {
1172
0
        const basegfx::B2DPoint aDiscreteTopLeft((maCurrentTransformation * pos)
1173
0
                                                 - aDiscreteHalfSize);
1174
0
        const Point aDiscretePoint(basegfx::fround<tools::Long>(aDiscreteTopLeft.getX()),
1175
0
                                   basegfx::fround<tools::Long>(aDiscreteTopLeft.getY()));
1176
1177
0
        mpOutputDevice->DrawBitmap(aDiscretePoint + aOrigin, rMarker);
1178
0
    }
1179
1180
0
    mpOutputDevice->EnableMapMode(bWasEnabled);
1181
0
}
1182
1183
// point
1184
void VclProcessor2D::RenderPointArrayPrimitive2D(
1185
    const primitive2d::PointArrayPrimitive2D& rPointArrayCandidate)
1186
0
{
1187
0
    const std::vector<basegfx::B2DPoint>& rPositions = rPointArrayCandidate.getPositions();
1188
0
    const basegfx::BColor aRGBColor(
1189
0
        maBColorModifierStack.getModifiedColor(rPointArrayCandidate.getRGBColor()));
1190
0
    const Color aVCLColor(aRGBColor);
1191
1192
0
    for (auto const& pos : rPositions)
1193
0
    {
1194
0
        const basegfx::B2DPoint aViewPosition(maCurrentTransformation * pos);
1195
0
        const Point aPos(basegfx::fround<tools::Long>(aViewPosition.getX()),
1196
0
                         basegfx::fround<tools::Long>(aViewPosition.getY()));
1197
1198
0
        mpOutputDevice->DrawPixel(aPos, aVCLColor);
1199
0
    }
1200
0
}
1201
1202
void VclProcessor2D::RenderPolygonStrokePrimitive2D(
1203
    const primitive2d::PolygonStrokePrimitive2D& rPolygonStrokeCandidate)
1204
0
{
1205
    // #i101491# method restructured to clearly use the DrawPolyLine
1206
    // calls starting from a defined line width
1207
0
    const attribute::LineAttribute& rLineAttribute = rPolygonStrokeCandidate.getLineAttribute();
1208
0
    const double fLineWidth(rLineAttribute.getWidth());
1209
0
    bool bDone(false);
1210
1211
0
    if (fLineWidth > 0.0)
1212
0
    {
1213
0
        const basegfx::B2DVector aDiscreteUnit(maCurrentTransformation
1214
0
                                               * basegfx::B2DVector(fLineWidth, 0.0));
1215
0
        const double fDiscreteLineWidth(aDiscreteUnit.getLength());
1216
0
        const attribute::StrokeAttribute& rStrokeAttribute
1217
0
            = rPolygonStrokeCandidate.getStrokeAttribute();
1218
0
        const basegfx::BColor aHairlineColor(
1219
0
            maBColorModifierStack.getModifiedColor(rLineAttribute.getColor()));
1220
0
        basegfx::B2DPolyPolygon aHairlinePolyPolygon;
1221
1222
0
        mpOutputDevice->SetLineColor(Color(aHairlineColor));
1223
0
        mpOutputDevice->SetFillColor();
1224
1225
0
        if (0.0 == rStrokeAttribute.getFullDotDashLen())
1226
0
        {
1227
            // no line dashing, just copy
1228
0
            aHairlinePolyPolygon.append(rPolygonStrokeCandidate.getB2DPolygon());
1229
0
        }
1230
0
        else
1231
0
        {
1232
            // else apply LineStyle
1233
0
            basegfx::utils::applyLineDashing(
1234
0
                rPolygonStrokeCandidate.getB2DPolygon(), rStrokeAttribute.getDotDashArray(),
1235
0
                &aHairlinePolyPolygon, nullptr, rStrokeAttribute.getFullDotDashLen());
1236
0
        }
1237
1238
0
        const sal_uInt32 nCount(aHairlinePolyPolygon.count());
1239
1240
0
        if (nCount)
1241
0
        {
1242
0
            const bool bAntiAliased(getViewInformation2D().getUseAntiAliasing());
1243
0
            aHairlinePolyPolygon.transform(maCurrentTransformation);
1244
1245
0
            if (bAntiAliased)
1246
0
            {
1247
0
                if (basegfx::fTools::lessOrEqual(fDiscreteLineWidth, 1.0))
1248
0
                {
1249
                    // line in range ]0.0 .. 1.0[
1250
                    // paint as simple hairline
1251
0
                    for (sal_uInt32 a(0); a < nCount; a++)
1252
0
                    {
1253
0
                        mpOutputDevice->DrawPolyLine(aHairlinePolyPolygon.getB2DPolygon(a), 0.0);
1254
0
                    }
1255
1256
0
                    bDone = true;
1257
0
                }
1258
0
                else if (basegfx::fTools::lessOrEqual(fDiscreteLineWidth, 2.0))
1259
0
                {
1260
                    // line in range [1.0 .. 2.0[
1261
                    // paint as 2x2 with dynamic line distance
1262
0
                    basegfx::B2DHomMatrix aMat;
1263
0
                    const double fDistance(fDiscreteLineWidth - 1.0);
1264
0
                    const double fHalfDistance(fDistance * 0.5);
1265
1266
0
                    for (sal_uInt32 a(0); a < nCount; a++)
1267
0
                    {
1268
0
                        basegfx::B2DPolygon aCandidate(aHairlinePolyPolygon.getB2DPolygon(a));
1269
1270
0
                        aMat.set(0, 2, -fHalfDistance);
1271
0
                        aMat.set(1, 2, -fHalfDistance);
1272
0
                        aCandidate.transform(aMat);
1273
0
                        mpOutputDevice->DrawPolyLine(aCandidate, 0.0);
1274
1275
0
                        aMat.set(0, 2, fDistance);
1276
0
                        aMat.set(1, 2, 0.0);
1277
0
                        aCandidate.transform(aMat);
1278
0
                        mpOutputDevice->DrawPolyLine(aCandidate, 0.0);
1279
1280
0
                        aMat.set(0, 2, 0.0);
1281
0
                        aMat.set(1, 2, fDistance);
1282
0
                        aCandidate.transform(aMat);
1283
0
                        mpOutputDevice->DrawPolyLine(aCandidate, 0.0);
1284
1285
0
                        aMat.set(0, 2, -fDistance);
1286
0
                        aMat.set(1, 2, 0.0);
1287
0
                        aCandidate.transform(aMat);
1288
0
                        mpOutputDevice->DrawPolyLine(aCandidate, 0.0);
1289
0
                    }
1290
1291
0
                    bDone = true;
1292
0
                }
1293
0
                else if (basegfx::fTools::lessOrEqual(fDiscreteLineWidth, 3.0))
1294
0
                {
1295
                    // line in range [2.0 .. 3.0]
1296
                    // paint as cross in a 3x3  with dynamic line distance
1297
0
                    basegfx::B2DHomMatrix aMat;
1298
0
                    const double fDistance((fDiscreteLineWidth - 1.0) * 0.5);
1299
1300
0
                    for (sal_uInt32 a(0); a < nCount; a++)
1301
0
                    {
1302
0
                        basegfx::B2DPolygon aCandidate(aHairlinePolyPolygon.getB2DPolygon(a));
1303
1304
0
                        mpOutputDevice->DrawPolyLine(aCandidate, 0.0);
1305
1306
0
                        aMat.set(0, 2, -fDistance);
1307
0
                        aMat.set(1, 2, 0.0);
1308
0
                        aCandidate.transform(aMat);
1309
0
                        mpOutputDevice->DrawPolyLine(aCandidate, 0.0);
1310
1311
0
                        aMat.set(0, 2, fDistance);
1312
0
                        aMat.set(1, 2, -fDistance);
1313
0
                        aCandidate.transform(aMat);
1314
0
                        mpOutputDevice->DrawPolyLine(aCandidate, 0.0);
1315
1316
0
                        aMat.set(0, 2, fDistance);
1317
0
                        aMat.set(1, 2, fDistance);
1318
0
                        aCandidate.transform(aMat);
1319
0
                        mpOutputDevice->DrawPolyLine(aCandidate, 0.0);
1320
1321
0
                        aMat.set(0, 2, -fDistance);
1322
0
                        aMat.set(1, 2, fDistance);
1323
0
                        aCandidate.transform(aMat);
1324
0
                        mpOutputDevice->DrawPolyLine(aCandidate, 0.0);
1325
0
                    }
1326
1327
0
                    bDone = true;
1328
0
                }
1329
0
                else
1330
0
                {
1331
                    // #i101491# line width above 3.0
1332
0
                }
1333
0
            }
1334
0
            else
1335
0
            {
1336
0
                if (basegfx::fTools::lessOrEqual(fDiscreteLineWidth, 1.5))
1337
0
                {
1338
                    // line width below 1.5, draw the basic hairline polygon
1339
0
                    for (sal_uInt32 a(0); a < nCount; a++)
1340
0
                    {
1341
0
                        mpOutputDevice->DrawPolyLine(aHairlinePolyPolygon.getB2DPolygon(a), 0.0);
1342
0
                    }
1343
1344
0
                    bDone = true;
1345
0
                }
1346
0
                else if (basegfx::fTools::lessOrEqual(fDiscreteLineWidth, 2.5))
1347
0
                {
1348
                    // line width is in range ]1.5 .. 2.5], use four hairlines
1349
                    // drawn in a square
1350
0
                    for (sal_uInt32 a(0); a < nCount; a++)
1351
0
                    {
1352
0
                        basegfx::B2DPolygon aCandidate(aHairlinePolyPolygon.getB2DPolygon(a));
1353
0
                        basegfx::B2DHomMatrix aMat;
1354
1355
0
                        mpOutputDevice->DrawPolyLine(aCandidate, 0.0);
1356
1357
0
                        aMat.set(0, 2, 1.0);
1358
0
                        aMat.set(1, 2, 0.0);
1359
0
                        aCandidate.transform(aMat);
1360
1361
0
                        mpOutputDevice->DrawPolyLine(aCandidate, 0.0);
1362
1363
0
                        aMat.set(0, 2, 0.0);
1364
0
                        aMat.set(1, 2, 1.0);
1365
0
                        aCandidate.transform(aMat);
1366
1367
0
                        mpOutputDevice->DrawPolyLine(aCandidate, 0.0);
1368
1369
0
                        aMat.set(0, 2, -1.0);
1370
0
                        aMat.set(1, 2, 0.0);
1371
0
                        aCandidate.transform(aMat);
1372
1373
0
                        mpOutputDevice->DrawPolyLine(aCandidate, 0.0);
1374
0
                    }
1375
1376
0
                    bDone = true;
1377
0
                }
1378
0
                else
1379
0
                {
1380
                    // #i101491# line width is above 2.5
1381
0
                }
1382
0
            }
1383
1384
0
            if (!bDone && rPolygonStrokeCandidate.getB2DPolygon().count() > 1000)
1385
0
            {
1386
                // #i101491# If the polygon complexity uses more than a given amount, do
1387
                // use OutputDevice::DrawPolyLine directly; this will avoid buffering all
1388
                // decompositions in primitives (memory) and fallback to old line painting
1389
                // for very complex polygons, too
1390
0
                for (sal_uInt32 a(0); a < nCount; a++)
1391
0
                {
1392
0
                    mpOutputDevice->DrawPolyLine(aHairlinePolyPolygon.getB2DPolygon(a),
1393
0
                                                 fDiscreteLineWidth, rLineAttribute.getLineJoin(),
1394
0
                                                 rLineAttribute.getLineCap(),
1395
0
                                                 rLineAttribute.getMiterMinimumAngle());
1396
0
                }
1397
1398
0
                bDone = true;
1399
0
            }
1400
0
        }
1401
0
    }
1402
1403
0
    if (!bDone)
1404
0
    {
1405
        // remember that we enter a PolygonStrokePrimitive2D decomposition,
1406
        // used for AA thick line drawing
1407
0
        mnPolygonStrokePrimitive2D++;
1408
1409
        // line width is big enough for standard filled polygon visualisation or zero
1410
0
        process(rPolygonStrokeCandidate);
1411
1412
        // leave PolygonStrokePrimitive2D
1413
0
        mnPolygonStrokePrimitive2D--;
1414
0
    }
1415
0
}
1416
1417
void VclProcessor2D::RenderEpsPrimitive2D(const primitive2d::EpsPrimitive2D& rEpsPrimitive2D)
1418
0
{
1419
    // The new decomposition of Metafiles made it necessary to add an Eps
1420
    // primitive to handle embedded Eps data. On some devices, this can be
1421
    // painted directly (mac, printer).
1422
    // To be able to handle the replacement correctly, i need to handle it myself
1423
    // since DrawEPS will not be able e.g. to rotate the replacement. To be able
1424
    // to do that, i added a boolean return to OutputDevice::DrawEPS(..)
1425
    // to know when EPS was handled directly already.
1426
0
    basegfx::B2DRange aRange(0.0, 0.0, 1.0, 1.0);
1427
0
    aRange.transform(maCurrentTransformation * rEpsPrimitive2D.getEpsTransform());
1428
1429
0
    if (aRange.isEmpty())
1430
0
        return;
1431
1432
0
    const ::tools::Rectangle aRectangle(static_cast<sal_Int32>(floor(aRange.getMinX())),
1433
0
                                        static_cast<sal_Int32>(floor(aRange.getMinY())),
1434
0
                                        static_cast<sal_Int32>(ceil(aRange.getMaxX())),
1435
0
                                        static_cast<sal_Int32>(ceil(aRange.getMaxY())));
1436
1437
0
    if (aRectangle.IsEmpty())
1438
0
        return;
1439
1440
0
    bool bWillReallyRender = mpOutputDevice->IsDeviceOutputNecessary();
1441
    // try to paint EPS directly without fallback visualisation
1442
0
    const bool bEPSPaintedDirectly
1443
0
        = bWillReallyRender
1444
0
          && mpOutputDevice->DrawEPS(aRectangle.TopLeft(), aRectangle.GetSize(),
1445
0
                                     rEpsPrimitive2D.getGfxLink());
1446
1447
0
    if (!bEPSPaintedDirectly)
1448
0
    {
1449
        // use the decomposition which will correctly handle the
1450
        // fallback visualisation using full transformation (e.g. rotation)
1451
0
        process(rEpsPrimitive2D);
1452
0
    }
1453
0
}
1454
1455
void VclProcessor2D::RenderSvgLinearAtomPrimitive2D(
1456
    const primitive2d::SvgLinearAtomPrimitive2D& rCandidate)
1457
0
{
1458
0
    const double fDelta(rCandidate.getOffsetB() - rCandidate.getOffsetA());
1459
1460
0
    if (fDelta <= 0.0)
1461
0
        return;
1462
1463
0
    const basegfx::BColor aColorA(maBColorModifierStack.getModifiedColor(rCandidate.getColorA()));
1464
0
    const basegfx::BColor aColorB(maBColorModifierStack.getModifiedColor(rCandidate.getColorB()));
1465
1466
    // calculate discrete unit in WorldCoordinates; use diagonal (1.0, 1.0) and divide by sqrt(2)
1467
0
    const basegfx::B2DVector aDiscreteVector(
1468
0
        getViewInformation2D().getInverseObjectToViewTransformation()
1469
0
        * basegfx::B2DVector(1.0, 1.0));
1470
0
    const double fDiscreteUnit(aDiscreteVector.getLength() * (1.0 / M_SQRT2));
1471
1472
    // use color distance and discrete lengths to calculate step count
1473
0
    const sal_uInt32 nSteps(calculateStepsForSvgGradient(aColorA, aColorB, fDelta, fDiscreteUnit));
1474
1475
    // switch off line painting
1476
0
    mpOutputDevice->SetLineColor();
1477
1478
    // prepare polygon in needed width at start position (with discrete overlap)
1479
0
    const basegfx::B2DPolygon aPolygon(basegfx::utils::createPolygonFromRect(
1480
0
        basegfx::B2DRange(rCandidate.getOffsetA() - fDiscreteUnit, 0.0,
1481
0
                          rCandidate.getOffsetA() + (fDelta / nSteps) + fDiscreteUnit, 1.0)));
1482
1483
    // prepare loop ([0.0 .. 1.0[)
1484
0
    double fUnitScale(0.0);
1485
0
    const double fUnitStep(1.0 / nSteps);
1486
1487
    // loop and paint
1488
0
    for (sal_uInt32 a(0); a < nSteps; a++, fUnitScale += fUnitStep)
1489
0
    {
1490
0
        basegfx::B2DPolygon aNew(aPolygon);
1491
1492
0
        aNew.transform(maCurrentTransformation
1493
0
                       * basegfx::utils::createTranslateB2DHomMatrix(fDelta * fUnitScale, 0.0));
1494
0
        mpOutputDevice->SetFillColor(Color(basegfx::interpolate(aColorA, aColorB, fUnitScale)));
1495
0
        mpOutputDevice->DrawPolyPolygon(basegfx::B2DPolyPolygon(aNew));
1496
0
    }
1497
0
}
1498
1499
void VclProcessor2D::RenderSvgRadialAtomPrimitive2D(
1500
    const primitive2d::SvgRadialAtomPrimitive2D& rCandidate)
1501
0
{
1502
0
    const double fDeltaScale(rCandidate.getScaleB() - rCandidate.getScaleA());
1503
1504
0
    if (fDeltaScale <= 0.0)
1505
0
        return;
1506
1507
0
    const basegfx::BColor aColorA(maBColorModifierStack.getModifiedColor(rCandidate.getColorA()));
1508
0
    const basegfx::BColor aColorB(maBColorModifierStack.getModifiedColor(rCandidate.getColorB()));
1509
1510
    // calculate discrete unit in WorldCoordinates; use diagonal (1.0, 1.0) and divide by sqrt(2)
1511
0
    const basegfx::B2DVector aDiscreteVector(
1512
0
        getViewInformation2D().getInverseObjectToViewTransformation()
1513
0
        * basegfx::B2DVector(1.0, 1.0));
1514
0
    const double fDiscreteUnit(aDiscreteVector.getLength() * (1.0 / M_SQRT2));
1515
1516
    // use color distance and discrete lengths to calculate step count
1517
0
    const sal_uInt32 nSteps(
1518
0
        calculateStepsForSvgGradient(aColorA, aColorB, fDeltaScale, fDiscreteUnit));
1519
1520
    // switch off line painting
1521
0
    mpOutputDevice->SetLineColor();
1522
1523
    // prepare loop ([0.0 .. 1.0[, full polygons, no polypolygons with holes)
1524
0
    double fUnitScale(0.0);
1525
0
    const double fUnitStep(1.0 / nSteps);
1526
1527
0
    for (sal_uInt32 a(0); a < nSteps; a++, fUnitScale += fUnitStep)
1528
0
    {
1529
0
        basegfx::B2DHomMatrix aTransform;
1530
0
        const double fEndScale(rCandidate.getScaleB() - (fDeltaScale * fUnitScale));
1531
1532
0
        if (rCandidate.isTranslateSet())
1533
0
        {
1534
0
            const basegfx::B2DVector aTranslate(basegfx::interpolate(
1535
0
                rCandidate.getTranslateB(), rCandidate.getTranslateA(), fUnitScale));
1536
1537
0
            aTransform = basegfx::utils::createScaleTranslateB2DHomMatrix(
1538
0
                fEndScale, fEndScale, aTranslate.getX(), aTranslate.getY());
1539
0
        }
1540
0
        else
1541
0
        {
1542
0
            aTransform = basegfx::utils::createScaleB2DHomMatrix(fEndScale, fEndScale);
1543
0
        }
1544
1545
0
        basegfx::B2DPolygon aNew(basegfx::utils::createPolygonFromUnitCircle());
1546
1547
0
        aNew.transform(maCurrentTransformation * aTransform);
1548
0
        mpOutputDevice->SetFillColor(Color(basegfx::interpolate(aColorB, aColorA, fUnitScale)));
1549
0
        mpOutputDevice->DrawPolyPolygon(basegfx::B2DPolyPolygon(aNew));
1550
0
    }
1551
0
}
1552
1553
void VclProcessor2D::adaptLineToFillDrawMode() const
1554
0
{
1555
0
    const DrawModeFlags nOriginalDrawMode(mpOutputDevice->GetDrawMode());
1556
1557
0
    if (!(nOriginalDrawMode
1558
0
          & (DrawModeFlags::BlackLine | DrawModeFlags::GrayLine | DrawModeFlags::WhiteLine
1559
0
             | DrawModeFlags::SettingsLine)))
1560
0
        return;
1561
1562
0
    DrawModeFlags nAdaptedDrawMode(nOriginalDrawMode);
1563
1564
0
    if (nOriginalDrawMode & DrawModeFlags::BlackLine)
1565
0
    {
1566
0
        nAdaptedDrawMode |= DrawModeFlags::BlackFill;
1567
0
    }
1568
0
    else
1569
0
    {
1570
0
        nAdaptedDrawMode &= ~DrawModeFlags::BlackFill;
1571
0
    }
1572
1573
0
    if (nOriginalDrawMode & DrawModeFlags::GrayLine)
1574
0
    {
1575
0
        nAdaptedDrawMode |= DrawModeFlags::GrayFill;
1576
0
    }
1577
0
    else
1578
0
    {
1579
0
        nAdaptedDrawMode &= ~DrawModeFlags::GrayFill;
1580
0
    }
1581
1582
0
    if (nOriginalDrawMode & DrawModeFlags::WhiteLine)
1583
0
    {
1584
0
        nAdaptedDrawMode |= DrawModeFlags::WhiteFill;
1585
0
    }
1586
0
    else
1587
0
    {
1588
0
        nAdaptedDrawMode &= ~DrawModeFlags::WhiteFill;
1589
0
    }
1590
1591
0
    if (nOriginalDrawMode & DrawModeFlags::SettingsLine)
1592
0
    {
1593
0
        nAdaptedDrawMode |= DrawModeFlags::SettingsFill;
1594
0
    }
1595
0
    else
1596
0
    {
1597
0
        nAdaptedDrawMode &= ~DrawModeFlags::SettingsFill;
1598
0
    }
1599
1600
0
    mpOutputDevice->SetDrawMode(nAdaptedDrawMode);
1601
0
}
1602
1603
void VclProcessor2D::adaptTextToFillDrawMode() const
1604
1.08k
{
1605
1.08k
    const DrawModeFlags nOriginalDrawMode(mpOutputDevice->GetDrawMode());
1606
1.08k
    if (!(nOriginalDrawMode
1607
1.08k
          & (DrawModeFlags::BlackText | DrawModeFlags::GrayText | DrawModeFlags::SettingsText)))
1608
1.08k
        return;
1609
1610
0
    DrawModeFlags nAdaptedDrawMode(nOriginalDrawMode);
1611
1612
0
    if (nOriginalDrawMode & DrawModeFlags::BlackText)
1613
0
    {
1614
0
        nAdaptedDrawMode |= DrawModeFlags::BlackFill;
1615
0
    }
1616
0
    else
1617
0
    {
1618
0
        nAdaptedDrawMode &= ~DrawModeFlags::BlackFill;
1619
0
    }
1620
1621
0
    if (nOriginalDrawMode & DrawModeFlags::GrayText)
1622
0
    {
1623
0
        nAdaptedDrawMode |= DrawModeFlags::GrayFill;
1624
0
    }
1625
0
    else
1626
0
    {
1627
0
        nAdaptedDrawMode &= ~DrawModeFlags::GrayFill;
1628
0
    }
1629
1630
0
    if (nOriginalDrawMode & DrawModeFlags::SettingsText)
1631
0
    {
1632
0
        nAdaptedDrawMode |= DrawModeFlags::SettingsFill;
1633
0
    }
1634
0
    else
1635
0
    {
1636
0
        nAdaptedDrawMode &= ~DrawModeFlags::SettingsFill;
1637
0
    }
1638
1639
0
    mpOutputDevice->SetDrawMode(nAdaptedDrawMode);
1640
0
}
1641
1642
void VclProcessor2D::onViewInformation2DChanged()
1643
444
{
1644
    // apply AntiAlias information to target device
1645
444
    if (getViewInformation2D().getUseAntiAliasing())
1646
444
        mpOutputDevice->SetAntialiasing(mpOutputDevice->GetAntialiasing()
1647
444
                                        | AntialiasingFlags::Enable);
1648
0
    else
1649
0
        mpOutputDevice->SetAntialiasing(mpOutputDevice->GetAntialiasing()
1650
0
                                        & ~AntialiasingFlags::Enable);
1651
1652
    // apply DrawModeFlags to target device
1653
444
    if (getViewInformation2D().getDrawModeFlags() != mpOutputDevice->GetDrawMode())
1654
0
        mpOutputDevice->SetDrawMode(getViewInformation2D().getDrawModeFlags());
1655
444
}
1656
1657
// process support
1658
VclProcessor2D::VclProcessor2D(const geometry::ViewInformation2D& rViewInformation,
1659
                               OutputDevice& rOutDev)
1660
56.4k
    : BaseProcessor2D(rViewInformation)
1661
56.4k
    , mpOutputDevice(&rOutDev)
1662
56.4k
    , maBColorModifierStack()
1663
56.4k
    , mnPolygonStrokePrimitive2D(0)
1664
56.4k
    , mnOriginalAA(rOutDev.GetAntialiasing())
1665
56.4k
{
1666
    // set digit language, derived from SvtCTLOptions to have the correct
1667
    // number display for arabic/hindi numerals
1668
56.4k
    rOutDev.SetDigitLanguage(drawinglayer::detail::getDigitLanguage());
1669
1670
    // NOTE: to save/restore original AntiAliasing mode we need
1671
    // to use mnOriginalAA here - OutputDevice::Push/Pop does not
1672
    // offer that
1673
56.4k
}
1674
1675
56.4k
VclProcessor2D::~VclProcessor2D() { mpOutputDevice->SetAntialiasing(mnOriginalAA); }
1676
}
1677
1678
/* vim:set shiftwidth=4 softtabstop=4 expandtab: */