Coverage Report

Created: 2026-03-31 11:00

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