Coverage Report

Created: 2026-02-14 09:37

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/libreoffice/svx/source/sdr/primitive2d/sdrdecompositiontools.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 <sdr/primitive2d/sdrdecompositiontools.hxx>
21
#include <sdr/primitive2d/sdrcellprimitive.hxx>
22
#include <drawinglayer/primitive2d/baseprimitive2d.hxx>
23
#include <drawinglayer/primitive2d/PolygonStrokeArrowPrimitive2D.hxx>
24
#include <drawinglayer/primitive2d/PolyPolygonGradientPrimitive2D.hxx>
25
#include <drawinglayer/primitive2d/PolyPolygonHatchPrimitive2D.hxx>
26
#include <drawinglayer/primitive2d/PolyPolygonGraphicPrimitive2D.hxx>
27
#include <drawinglayer/primitive2d/PolyPolygonColorPrimitive2D.hxx>
28
#include <drawinglayer/primitive2d/softedgeprimitive2d.hxx>
29
#include <drawinglayer/primitive2d/unifiedtransparenceprimitive2d.hxx>
30
#include <drawinglayer/primitive2d/transparenceprimitive2d.hxx>
31
#include <drawinglayer/primitive2d/PolyPolygonRGBAPrimitive2D.hxx>
32
#include <drawinglayer/primitive2d/PolyPolygonAlphaGradientPrimitive2D.hxx>
33
#include <basegfx/polygon/b2dpolypolygontools.hxx>
34
#include <drawinglayer/primitive2d/fillgradientprimitive2d.hxx>
35
#include <drawinglayer/attribute/strokeattribute.hxx>
36
#include <drawinglayer/attribute/linestartendattribute.hxx>
37
#include <drawinglayer/attribute/sdrfillgraphicattribute.hxx>
38
#include <basegfx/matrix/b2dhommatrix.hxx>
39
#include <drawinglayer/primitive2d/shadowprimitive2d.hxx>
40
#include <sdr/attribute/sdrtextattribute.hxx>
41
#include <drawinglayer/primitive2d/glowprimitive2d.hxx>
42
#include <sdr/primitive2d/sdrtextprimitive2d.hxx>
43
#include <svx/svdotext.hxx>
44
#include <basegfx/polygon/b2dpolygontools.hxx>
45
#include <drawinglayer/primitive2d/animatedprimitive2d.hxx>
46
#include <drawinglayer/animation/animationtiming.hxx>
47
#include <drawinglayer/primitive2d/maskprimitive2d.hxx>
48
#include <drawinglayer/geometry/viewinformation2d.hxx>
49
#include <drawinglayer/primitive2d/texthierarchyprimitive2d.hxx>
50
#include <drawinglayer/attribute/sdrfillattribute.hxx>
51
#include <drawinglayer/attribute/sdrlineattribute.hxx>
52
#include <drawinglayer/attribute/sdrlinestartendattribute.hxx>
53
#include <drawinglayer/attribute/sdrshadowattribute.hxx>
54
#include <drawinglayer/attribute/sdrglowattribute.hxx>
55
#include <drawinglayer/attribute/sdrglowtextattribute.hxx>
56
#include <docmodel/theme/FormatScheme.hxx>
57
#include <osl/diagnose.h>
58
59
// for SlideBackgroundFillPrimitive2D
60
#include <svx/sdr/primitive2d/svx_primitivetypes2d.hxx>
61
#include <svx/unoapi.hxx>
62
#include <svx/svdpage.hxx>
63
#include <sdr/primitive2d/sdrattributecreator.hxx>
64
#include <sdr/contact/viewcontactofmasterpagedescriptor.hxx>
65
66
using namespace com::sun::star;
67
68
69
namespace drawinglayer::primitive2d
70
{
71
namespace
72
{
73
/// @returns the offset to apply/unapply to scale according to correct origin for a given alignment.
74
basegfx::B2DTuple getShadowScaleOriginOffset(const basegfx::B2DTuple& aScale,
75
                                             model::RectangleAlignment eAlignment)
76
0
{
77
0
    switch (eAlignment)
78
0
    {
79
0
        case model::RectangleAlignment::TopLeft:
80
0
            return { 0, 0 };
81
0
        case model::RectangleAlignment::Top:
82
0
            return { aScale.getX() / 2, 0 };
83
0
        case model::RectangleAlignment::TopRight:
84
0
            return { aScale.getX(), 0 };
85
0
        case model::RectangleAlignment::Left:
86
0
            return { 0, aScale.getY() / 2 };
87
0
        case model::RectangleAlignment::Center:
88
0
            return { aScale.getX() / 2, aScale.getY() / 2 };
89
0
        case model::RectangleAlignment::Right:
90
0
            return { aScale.getX(), aScale.getY() / 2 };
91
0
        case model::RectangleAlignment::BottomLeft:
92
0
            return { 0, aScale.getY() };
93
0
        case model::RectangleAlignment::Bottom:
94
0
            return { aScale.getX() / 2, aScale.getY() };
95
0
        case model::RectangleAlignment::BottomRight:
96
0
            return { aScale.getX(), aScale.getY() };
97
0
        default:
98
0
            return { 0, 0 };
99
0
    }
100
0
};
101
102
// See also: SdrTextObj::AdjustRectToTextDistance
103
basegfx::B2DRange getTextAnchorRange(const attribute::SdrTextAttribute& rText,
104
                                     const basegfx::B2DRange& rSnapRange)
105
349
{
106
    // Take vertical text orientation into account when deciding
107
    // which dimension is its width, and which is its height
108
349
    const OutlinerParaObject& rOutlinerParaObj = rText.getOutlinerParaObject();
109
349
    const bool bVerticalWriting(rOutlinerParaObj.IsEffectivelyVertical());
110
349
    const double fWidthForText = bVerticalWriting ? rSnapRange.getHeight() : rSnapRange.getWidth();
111
    // create a range describing the wanted text position and size (aTextAnchorRange). This
112
    // means to use the text distance values here
113
    // If the margin is larger than the entire width of the text area, then limit the
114
    // margin.
115
349
    const double fTextLeftDistance
116
349
        = std::min(static_cast<double>(rText.getTextLeftDistance()), fWidthForText);
117
349
    const double nTextRightDistance
118
349
        = std::min(static_cast<double>(rText.getTextRightDistance()), fWidthForText);
119
349
    double fDistanceForTextL, fDistanceForTextT, fDistanceForTextR, fDistanceForTextB;
120
349
    if (!bVerticalWriting)
121
349
    {
122
349
        fDistanceForTextL = fTextLeftDistance;
123
349
        fDistanceForTextT = rText.getTextUpperDistance();
124
349
        fDistanceForTextR = nTextRightDistance;
125
349
        fDistanceForTextB = rText.getTextLowerDistance();
126
349
    }
127
0
    else if (rOutlinerParaObj.IsTopToBottom())
128
0
    {
129
0
        fDistanceForTextL = rText.getTextLowerDistance();
130
0
        fDistanceForTextT = fTextLeftDistance;
131
0
        fDistanceForTextR = rText.getTextUpperDistance();
132
0
        fDistanceForTextB = nTextRightDistance;
133
0
    }
134
0
    else
135
0
    {
136
0
        fDistanceForTextL = rText.getTextUpperDistance();
137
0
        fDistanceForTextT = nTextRightDistance;
138
0
        fDistanceForTextR = rText.getTextLowerDistance();
139
0
        fDistanceForTextB = fTextLeftDistance;
140
0
    }
141
349
    const basegfx::B2DPoint aTopLeft(rSnapRange.getMinX() + fDistanceForTextL,
142
349
                                     rSnapRange.getMinY() + fDistanceForTextT);
143
349
    const basegfx::B2DPoint aBottomRight(rSnapRange.getMaxX() - fDistanceForTextR,
144
349
                                         rSnapRange.getMaxY() - fDistanceForTextB);
145
349
    basegfx::B2DRange aAnchorRange;
146
147
    // tdf#165732 Margins are too large in some cases (left > right or top > bottom)
148
    // - in this case do not expand the range
149
349
    bool bInvalidMargins
150
349
        = (aTopLeft.getX() >= aBottomRight.getX() || aTopLeft.getY() >= aBottomRight.getY());
151
349
    if (!bInvalidMargins)
152
284
    {
153
284
        aAnchorRange.expand(aTopLeft);
154
284
        aAnchorRange.expand(aBottomRight);
155
284
    }
156
157
    // If the shape has no width, then don't attempt to break the text into multiple
158
    // lines, not a single character would satisfy a zero width requirement.
159
    // SdrTextObj::impDecomposeBlockTextPrimitive() uses the same constant to
160
    // effectively set no limits.
161
349
    if (!bVerticalWriting && aAnchorRange.getWidth() == 0)
162
65
    {
163
65
        aAnchorRange.expand(basegfx::B2DPoint(aTopLeft.getX() - 1000000, aTopLeft.getY()));
164
65
        aAnchorRange.expand(basegfx::B2DPoint(aBottomRight.getX() + 1000000, aBottomRight.getY()));
165
65
    }
166
284
    else if (bVerticalWriting && aAnchorRange.getHeight() == 0)
167
0
    {
168
0
        aAnchorRange.expand(basegfx::B2DPoint(aTopLeft.getX(), aTopLeft.getY() - 1000000));
169
0
        aAnchorRange.expand(basegfx::B2DPoint(aBottomRight.getX(), aBottomRight.getY() + 1000000));
170
0
    }
171
349
    return aAnchorRange;
172
349
}
173
174
drawinglayer::attribute::SdrFillAttribute getMasterPageFillAttribute(
175
    const geometry::ViewInformation2D& rViewInformation,
176
    basegfx::B2DVector& rPageSize)
177
0
{
178
0
    drawinglayer::attribute::SdrFillAttribute aRetval;
179
180
    // get SdrPage
181
0
    const SdrPage* pVisualizedPage(GetSdrPageFromXDrawPage(rViewInformation.getVisualizedPage()));
182
183
0
    if(nullptr != pVisualizedPage)
184
0
    {
185
        // copy needed values for further processing
186
0
        rPageSize.setX(pVisualizedPage->GetWidth());
187
0
        rPageSize.setY(pVisualizedPage->GetHeight());
188
189
0
        if(pVisualizedPage->IsMasterPage())
190
0
        {
191
            // the page is a MasterPage, so we are in MasterPage view
192
            // still need #i110846#, see ViewContactOfMasterPage
193
0
            if(pVisualizedPage->getSdrPageProperties().GetStyleSheet())
194
0
            {
195
                // create page fill attributes with correct properties
196
0
                aRetval = drawinglayer::primitive2d::createNewSdrFillAttribute(
197
0
                    pVisualizedPage->getSdrPageProperties().GetItemSet());
198
0
            }
199
0
        }
200
0
        else
201
0
        {
202
            // the page is *no* MasterPage, we are in normal Page view, get the MasterPage
203
0
            if(pVisualizedPage->TRG_HasMasterPage())
204
0
            {
205
0
                sdr::contact::ViewContact& rVC(pVisualizedPage->TRG_GetMasterPageDescriptorViewContact());
206
0
                sdr::contact::ViewContactOfMasterPageDescriptor* pVCOMPD(
207
0
                    dynamic_cast<sdr::contact::ViewContactOfMasterPageDescriptor*>(&rVC));
208
209
0
                if(nullptr != pVCOMPD)
210
0
                {
211
                    // in this case the still needed #i110846# is part of
212
                    //  getCorrectSdrPageProperties, that's the main reason to re-use
213
                    // that call/functionality here
214
0
                    const SdrPageProperties* pCorrectProperties(
215
0
                        pVCOMPD->GetMasterPageDescriptor().getCorrectSdrPageProperties());
216
217
0
                    if(pCorrectProperties)
218
0
                    {
219
                        // create page fill attributes when correct properties were identified
220
0
                        aRetval = drawinglayer::primitive2d::createNewSdrFillAttribute(
221
0
                            pCorrectProperties->GetItemSet());
222
0
                    }
223
0
                }
224
0
            }
225
0
        }
226
0
    }
227
228
0
    return aRetval;
229
0
}
230
231
// provide a Primitive2D for the SlideBackgroundFill-mode. It is capable
232
// of expressing that state of fill and it's decomposition implements all
233
// needed preparation of the geometry in an isolated and controllable
234
// space and way.
235
// It is currently simple buffered (due to being derived from
236
// BufferedDecompositionPrimitive2D) and detects if FillStyle changes
237
class SlideBackgroundFillPrimitive2D final : public BufferedDecompositionPrimitive2D
238
{
239
private:
240
    /// the basegfx::B2DPolyPolygon geometry
241
    basegfx::B2DPolyPolygon maPolyPolygon;
242
243
    /// the last SdrFillAttribute the geometry was created for
244
    drawinglayer::attribute::SdrFillAttribute maLastFill;
245
246
protected:
247
    // create decomposition data
248
    virtual Primitive2DReference create2DDecomposition(
249
        const geometry::ViewInformation2D& rViewInformation) const override;
250
251
public:
252
    /// constructor
253
    SlideBackgroundFillPrimitive2D(
254
        const basegfx::B2DPolyPolygon& rPolyPolygon);
255
256
    /// check existing decomposition data, call parent
257
    virtual void get2DDecomposition(
258
        Primitive2DDecompositionVisitor& rVisitor,
259
        const geometry::ViewInformation2D& rViewInformation) const override;
260
261
    /// data read access
262
0
    const basegfx::B2DPolyPolygon& getB2DPolyPolygon() const { return maPolyPolygon; }
263
264
    /// compare operator
265
    virtual bool operator==(const BasePrimitive2D& rPrimitive) const override;
266
267
    /// get range
268
    virtual basegfx::B2DRange getB2DRange(const geometry::ViewInformation2D& rViewInformation) const override;
269
270
    /// provide unique ID
271
    virtual sal_uInt32 getPrimitive2DID() const override;
272
};
273
274
SlideBackgroundFillPrimitive2D::SlideBackgroundFillPrimitive2D(
275
    const basegfx::B2DPolyPolygon& rPolyPolygon)
276
0
: BufferedDecompositionPrimitive2D()
277
0
, maPolyPolygon(rPolyPolygon)
278
0
, maLastFill()
279
0
{
280
0
}
281
282
Primitive2DReference SlideBackgroundFillPrimitive2D::create2DDecomposition(
283
    const geometry::ViewInformation2D& rViewInformation) const
284
0
{
285
0
    basegfx::B2DVector aPageSize;
286
287
    // get fill from target Page, this will check for all needed things
288
    // like MasterPage/relationships, etc. (see getMasterPageFillAttribute impl above)
289
0
    drawinglayer::attribute::SdrFillAttribute aFill(
290
0
        getMasterPageFillAttribute(rViewInformation, aPageSize));
291
292
    // if fill is on default (empty), nothing will be shown, we are done
293
0
    if(aFill.isDefault())
294
0
        return nullptr;
295
296
    // Get PolygonRange of own local geometry
297
0
    const basegfx::B2DRange aPolygonRange(getB2DPolyPolygon().getB2DRange());
298
299
    // if local geometry is empty, nothing will be shown, we are done
300
0
    if(aPolygonRange.isEmpty())
301
0
        return nullptr;
302
303
    // Get PageRange
304
0
    const basegfx::B2DRange aPageRange(0.0, 0.0, aPageSize.getX(), aPageSize.getY());
305
306
    // if local geometry does not overlap with PageRange, nothing will be shown, we are done
307
0
    if(!aPageRange.overlaps(aPolygonRange))
308
0
        return nullptr;
309
310
    // create FillPrimitive2D with the geometry (the PolyPolygon) and
311
    // the page's definitonRange to:
312
    // - on one hand limit to geometry
313
    // - on the other hand allow continuation of fill outside of
314
    //   MasterPage's range
315
0
    const attribute::FillGradientAttribute aEmptyFillTransparenceGradient;
316
0
    const Primitive2DReference aCreatedFill(
317
0
        createPolyPolygonFillPrimitive(
318
0
            getB2DPolyPolygon(), // geometry
319
0
            aPageRange, // definition range
320
0
            aFill,
321
0
            aEmptyFillTransparenceGradient));
322
323
0
    return aCreatedFill;
324
0
}
325
326
void SlideBackgroundFillPrimitive2D::get2DDecomposition(
327
    Primitive2DDecompositionVisitor& rVisitor,
328
    const geometry::ViewInformation2D& rViewInformation) const
329
0
{
330
0
    basegfx::B2DVector aPageSize;
331
0
    drawinglayer::attribute::SdrFillAttribute aFill;
332
333
0
    if(hasBuffered2DDecomposition())
334
0
    {
335
0
        aFill = getMasterPageFillAttribute(rViewInformation, aPageSize);
336
337
0
        if(!(aFill == maLastFill))
338
0
        {
339
            // conditions of last local decomposition have changed, delete
340
0
            const_cast< SlideBackgroundFillPrimitive2D* >(this)->setBuffered2DDecomposition(nullptr);
341
0
        }
342
0
    }
343
344
0
    if(!hasBuffered2DDecomposition())
345
0
    {
346
        // remember last Fill
347
0
        const_cast< SlideBackgroundFillPrimitive2D* >(this)->maLastFill = std::move(aFill);
348
0
    }
349
350
    // use parent implementation
351
0
    BufferedDecompositionPrimitive2D::get2DDecomposition(rVisitor, rViewInformation);
352
0
}
353
354
bool SlideBackgroundFillPrimitive2D::operator==(const BasePrimitive2D& rPrimitive) const
355
0
{
356
0
    if (BufferedDecompositionPrimitive2D::operator==(rPrimitive))
357
0
    {
358
0
        const SlideBackgroundFillPrimitive2D& rCompare
359
0
            = static_cast<const SlideBackgroundFillPrimitive2D&>(rPrimitive);
360
361
0
        return getB2DPolyPolygon() == rCompare.getB2DPolyPolygon();
362
0
    }
363
364
0
    return false;
365
0
}
366
367
basegfx::B2DRange SlideBackgroundFillPrimitive2D::getB2DRange(
368
    const geometry::ViewInformation2D& /*rViewInformation*/) const
369
0
{
370
    // return range
371
0
    return getB2DPolyPolygon().getB2DRange();
372
0
}
373
374
// provide unique ID
375
sal_uInt32 SlideBackgroundFillPrimitive2D::getPrimitive2DID() const
376
0
{
377
0
    return PRIMITIVE2D_ID_SLIDEBACKGROUNDFILLPRIMITIVE2D;
378
0
}
379
380
}; // end of anonymous namespace
381
382
        Primitive2DReference createPolyPolygonFillPrimitive(
383
            const basegfx::B2DPolyPolygon& rPolyPolygon,
384
            const attribute::SdrFillAttribute& rFill,
385
            const attribute::FillGradientAttribute& rAlphaGradient)
386
0
        {
387
            // when we have no given definition range, use the range of the given geometry
388
            // also for definition (simplest case)
389
0
            const basegfx::B2DRange aRange(rPolyPolygon.getB2DRange());
390
391
0
            return createPolyPolygonFillPrimitive(
392
0
                rPolyPolygon,
393
0
                aRange,
394
0
                rFill,
395
0
                rAlphaGradient);
396
0
        }
397
398
        Primitive2DReference createPolyPolygonFillPrimitive(
399
            const basegfx::B2DPolyPolygon& rPolyPolygon,
400
            const basegfx::B2DRange& rDefinitionRange,
401
            const attribute::SdrFillAttribute& rFill,
402
            const attribute::FillGradientAttribute& rAlphaGradient)
403
476
        {
404
476
            if(basegfx::fTools::moreOrEqual(rFill.getTransparence(), 1.0))
405
8
            {
406
8
                return Primitive2DReference();
407
8
            }
408
409
            // prepare access to FillGradientAttribute
410
468
            const attribute::FillGradientAttribute& rFillGradient(rFill.getGradient());
411
412
            // prepare fully scaled polygon
413
468
            rtl::Reference<BasePrimitive2D> pNewFillPrimitive;
414
415
468
            if(!rFillGradient.isDefault())
416
0
            {
417
0
                const bool bHasTransparency(!basegfx::fTools::equalZero(rFill.getTransparence()));
418
                // note: need to use !bHasTransparency to do the same as below
419
                // where embedding to transparency is done. There, simple transparency
420
                // gets priority over gradient transparency (and none). Thus here only one
421
                // option is used. Note that the implementation of FillGradientPrimitive2D
422
                // and PolyPolygonGradientPrimitive2D do support both alphas being used
423
0
                const bool bHasCompatibleAlphaGradient(!bHasTransparency
424
0
                    && !rAlphaGradient.isDefault()
425
0
                    && rFillGradient.sameDefinitionThanAlpha(rAlphaGradient));
426
427
0
                if(bHasTransparency || bHasCompatibleAlphaGradient)
428
0
                {
429
                    // SDPR: check early if we have a gradient and an alpha
430
                    // gradient that 'fits' in its geometric definition
431
                    // so that it can be rendered as RGBA directly. If yes,
432
                    // create it and return early
433
0
                    return new PolyPolygonGradientPrimitive2D(
434
0
                        rPolyPolygon,
435
0
                        rDefinitionRange,
436
0
                        rFillGradient,
437
0
                        bHasCompatibleAlphaGradient ? &rAlphaGradient : nullptr,
438
0
                        bHasTransparency ? rFill.getTransparence() : 0.0);
439
0
                }
440
441
0
                pNewFillPrimitive = new PolyPolygonGradientPrimitive2D(
442
0
                    rPolyPolygon,
443
0
                    rDefinitionRange,
444
0
                    rFillGradient);
445
0
            }
446
468
            else if(!rFill.getHatch().isDefault())
447
0
            {
448
0
                pNewFillPrimitive = new PolyPolygonHatchPrimitive2D(
449
0
                    rPolyPolygon,
450
0
                    rDefinitionRange,
451
0
                    rFill.getColor(),
452
0
                    rFill.getHatch());
453
0
            }
454
468
            else if(!rFill.getFillGraphic().isDefault())
455
0
            {
456
                // SDPR: check early if we have alpha and add directly
457
0
                if(0.0 != rFill.getTransparence())
458
0
                {
459
0
                    return new PolyPolygonGraphicPrimitive2D(
460
0
                        rPolyPolygon,
461
0
                        rDefinitionRange,
462
0
                        rFill.getFillGraphic().createFillGraphicAttribute(rDefinitionRange),
463
0
                        rFill.getTransparence());
464
0
                }
465
466
0
                pNewFillPrimitive = new PolyPolygonGraphicPrimitive2D(
467
0
                    rPolyPolygon,
468
0
                    rDefinitionRange,
469
0
                    rFill.getFillGraphic().createFillGraphicAttribute(rDefinitionRange));
470
0
            }
471
468
            else if(rFill.isSlideBackgroundFill())
472
0
            {
473
                // create needed Primitive2D representation for
474
                // SlideBackgroundFill-mode
475
0
                pNewFillPrimitive = new SlideBackgroundFillPrimitive2D(
476
0
                    rPolyPolygon);
477
0
            }
478
468
            else
479
468
            {
480
                // SDPR: check early if we have alpha and add directly
481
468
                if(0.0 != rFill.getTransparence())
482
0
                {
483
0
                    return new PolyPolygonRGBAPrimitive2D(
484
0
                        rPolyPolygon,
485
0
                        rFill.getColor(),
486
0
                        rFill.getTransparence());
487
0
                }
488
489
                // SDPR: check early if we have alpha gradient and add directly
490
                // This may be useful for some SDPRs like Cairo: It can render RGBA
491
                // gradients quick and direct, so it can use polygon color as RGB
492
                // (no real gradient steps) combined with the existing alpha steps
493
468
                if (!rAlphaGradient.isDefault())
494
0
                {
495
0
                    return new PolyPolygonAlphaGradientPrimitive2D(
496
0
                        rPolyPolygon,
497
0
                        rFill.getColor(),
498
0
                        rAlphaGradient);
499
0
                }
500
501
468
                return new PolyPolygonColorPrimitive2D(
502
468
                    rPolyPolygon,
503
468
                    rFill.getColor());
504
468
            }
505
506
0
            if(0.0 != rFill.getTransparence())
507
0
            {
508
                // create simpleTransparencePrimitive, add created fill primitive
509
0
                Primitive2DContainer aContent { pNewFillPrimitive };
510
0
                return new UnifiedTransparencePrimitive2D(std::move(aContent), rFill.getTransparence());
511
0
            }
512
513
0
            if(!rAlphaGradient.isDefault())
514
0
            {
515
                // create sequence with created fill primitive
516
0
                Primitive2DContainer aContent { pNewFillPrimitive };
517
518
                // create FillGradientPrimitive2D for transparence and add to new sequence
519
                // fillGradientPrimitive is enough here (compared to PolyPolygonGradientPrimitive2D) since float transparence will be masked anyways
520
0
                Primitive2DContainer aAlpha {
521
0
                    new FillGradientPrimitive2D(
522
0
                        rPolyPolygon.getB2DRange(),
523
0
                        rDefinitionRange,
524
0
                        rAlphaGradient)
525
0
                };
526
527
                // create TransparencePrimitive2D using alpha and content
528
0
                return new TransparencePrimitive2D(std::move(aContent), std::move(aAlpha));
529
0
            }
530
531
            // add to decomposition
532
0
            return pNewFillPrimitive;
533
0
        }
534
535
        Primitive2DReference createPolygonLinePrimitive(
536
            const basegfx::B2DPolygon& rPolygon,
537
            const attribute::SdrLineAttribute& rLine,
538
            const attribute::SdrLineStartEndAttribute& rStroke)
539
270
        {
540
            // create line and stroke attribute
541
270
            const attribute::LineAttribute aLineAttribute(rLine.getColor(), rLine.getWidth(), rLine.getJoin(), rLine.getCap());
542
270
            attribute::StrokeAttribute aStrokeAttribute(std::vector(rLine.getDotDashArray()), rLine.getFullDotDashLen());
543
270
            rtl::Reference<BasePrimitive2D> pNewLinePrimitive;
544
545
270
            if(!rPolygon.isClosed() && !rStroke.isDefault())
546
0
            {
547
0
                attribute::LineStartEndAttribute aStart(rStroke.getStartWidth(), rStroke.getStartPolyPolygon(), rStroke.isStartCentered());
548
0
                attribute::LineStartEndAttribute aEnd(rStroke.getEndWidth(), rStroke.getEndPolyPolygon(), rStroke.isEndCentered());
549
550
                // create data
551
0
                pNewLinePrimitive = new PolygonStrokeArrowPrimitive2D(rPolygon, aLineAttribute, aStrokeAttribute, aStart, aEnd);
552
0
            }
553
270
            else
554
270
            {
555
                // create data
556
270
                pNewLinePrimitive = new PolygonStrokePrimitive2D(rPolygon, aLineAttribute, std::move(aStrokeAttribute));
557
270
            }
558
559
270
            if(0.0 != rLine.getTransparence())
560
0
            {
561
                // create simpleTransparencePrimitive, add created fill primitive
562
0
                Primitive2DContainer aContent { pNewLinePrimitive };
563
0
                return new UnifiedTransparencePrimitive2D(std::move(aContent), rLine.getTransparence());
564
0
            }
565
270
            else
566
270
            {
567
                // add to decomposition
568
270
                return pNewLinePrimitive;
569
270
            }
570
270
        }
571
572
        Primitive2DReference createTextPrimitive(
573
            const basegfx::B2DPolyPolygon& rUnitPolyPolygon,
574
            const basegfx::B2DHomMatrix& rObjectTransform,
575
            const attribute::SdrTextAttribute& rText,
576
            const attribute::SdrLineAttribute& rStroke,
577
            bool bCellText,
578
            bool bWordWrap)
579
349
        {
580
349
            basegfx::B2DHomMatrix aAnchorTransform(rObjectTransform);
581
349
            rtl::Reference<SdrTextPrimitive2D> pNew;
582
583
349
            if(rText.isContour())
584
0
            {
585
                // contour text
586
0
                if(!rStroke.isDefault() && 0.0 != rStroke.getWidth())
587
0
                {
588
                    // take line width into account and shrink contour polygon accordingly
589
                    // decompose to get scale
590
0
                    basegfx::B2DVector aScale, aTranslate;
591
0
                    double fRotate, fShearX;
592
0
                    rObjectTransform.decompose(aScale, aTranslate, fRotate, fShearX);
593
594
                    // scale outline to object's size to allow growing with value relative to that size
595
                    // and also to keep aspect ratio
596
0
                    basegfx::B2DPolyPolygon aScaledUnitPolyPolygon(rUnitPolyPolygon);
597
0
                    aScaledUnitPolyPolygon.transform(basegfx::utils::createScaleB2DHomMatrix(
598
0
                        fabs(aScale.getX()), fabs(aScale.getY())));
599
600
                    // grow the polygon. To shrink, use negative value (half width)
601
0
                    aScaledUnitPolyPolygon = basegfx::utils::growInNormalDirection(aScaledUnitPolyPolygon, -(rStroke.getWidth() * 0.5));
602
603
                    // scale back to unit polygon
604
0
                    aScaledUnitPolyPolygon.transform(basegfx::utils::createScaleB2DHomMatrix(
605
0
                        0.0 != aScale.getX() ? 1.0 / aScale.getX() : 1.0,
606
0
                        0.0 != aScale.getY() ? 1.0 / aScale.getY() : 1.0));
607
608
                    // create with unit polygon
609
0
                    pNew = new SdrContourTextPrimitive2D(
610
0
                        &rText.getSdrText(),
611
0
                        rText.getOutlinerParaObject(),
612
0
                        std::move(aScaledUnitPolyPolygon),
613
0
                        rObjectTransform);
614
0
                }
615
0
                else
616
0
                {
617
                    // create with unit polygon
618
0
                    pNew = new SdrContourTextPrimitive2D(
619
0
                        &rText.getSdrText(),
620
0
                        rText.getOutlinerParaObject(),
621
0
                        rUnitPolyPolygon,
622
0
                        rObjectTransform);
623
0
                }
624
0
            }
625
349
            else if(!rText.getSdrFormTextAttribute().isDefault())
626
0
            {
627
                // text on path, use scaled polygon
628
0
                basegfx::B2DPolyPolygon aScaledPolyPolygon(rUnitPolyPolygon);
629
0
                aScaledPolyPolygon.transform(rObjectTransform);
630
0
                pNew = new SdrPathTextPrimitive2D(
631
0
                    &rText.getSdrText(),
632
0
                    rText.getOutlinerParaObject(),
633
0
                    std::move(aScaledPolyPolygon),
634
0
                    rText.getSdrFormTextAttribute());
635
0
            }
636
349
            else
637
349
            {
638
                // rObjectTransform is the whole SdrObject transformation from unit rectangle
639
                // to its size and position. Decompose to allow working with single values.
640
349
                basegfx::B2DVector aScale, aTranslate;
641
349
                double fRotate, fShearX;
642
349
                rObjectTransform.decompose(aScale, aTranslate, fRotate, fShearX);
643
644
                // extract mirroring
645
349
                const bool bMirrorX(aScale.getX() < 0.0);
646
349
                const bool bMirrorY(aScale.getY() < 0.0);
647
349
                aScale = basegfx::absolute(aScale);
648
649
                // Get the real size, since polygon outline and scale
650
                // from the object transformation may vary (e.g. ellipse segments)
651
349
                basegfx::B2DHomMatrix aJustScaleTransform;
652
349
                aJustScaleTransform.set(0, 0, aScale.getX());
653
349
                aJustScaleTransform.set(1, 1, aScale.getY());
654
349
                basegfx::B2DPolyPolygon aScaledUnitPolyPolygon(rUnitPolyPolygon);
655
349
                aScaledUnitPolyPolygon.transform(aJustScaleTransform);
656
349
                const basegfx::B2DRange aTextAnchorRange
657
349
                    = getTextAnchorRange(rText, aScaledUnitPolyPolygon.getB2DRange());
658
659
                // now create a transformation from this basic range (aTextAnchorRange)
660
                // #i121494# if we have no scale use at least 1.0 to have a carrier e.g. for
661
                // mirror values, else these will get lost
662
349
                aAnchorTransform = basegfx::utils::createScaleTranslateB2DHomMatrix(
663
349
                    basegfx::fTools::equalZero(aTextAnchorRange.getWidth()) ? 1.0 : aTextAnchorRange.getWidth(),
664
349
                    basegfx::fTools::equalZero(aTextAnchorRange.getHeight()) ? 1.0 : aTextAnchorRange.getHeight(),
665
349
                    aTextAnchorRange.getMinX(), aTextAnchorRange.getMinY());
666
667
                // apply mirroring
668
349
                aAnchorTransform.scale(bMirrorX ? -1.0 : 1.0, bMirrorY ? -1.0 : 1.0);
669
670
                // apply object's other transforms
671
349
                aAnchorTransform = basegfx::utils::createShearXRotateTranslateB2DHomMatrix(fShearX, fRotate, aTranslate)
672
349
                    * aAnchorTransform;
673
674
349
                if(rText.isFitToSize())
675
0
                {
676
                    // stretched text in range
677
0
                    pNew = new SdrStretchTextPrimitive2D(
678
0
                        &rText.getSdrText(),
679
0
                        rText.getOutlinerParaObject(),
680
0
                        aAnchorTransform,
681
0
                        rText.isFixedCellHeight());
682
0
                }
683
349
                else if(rText.isAutoFit())
684
147
                {
685
                    // isotropically scaled text in range
686
147
                    pNew = new SdrAutoFitTextPrimitive2D(
687
147
                                    &rText.getSdrText(),
688
147
                                    rText.getOutlinerParaObject(),
689
147
                                    aAnchorTransform,
690
147
                                    bWordWrap,
691
147
                                    rText.isFixedCellHeight());
692
147
                }
693
202
                else if( rText.isChainable() && !rText.isInEditMode() )
694
0
                {
695
0
                    pNew = new SdrChainedTextPrimitive2D(
696
0
                                    &rText.getSdrText(),
697
0
                                    rText.getOutlinerParaObject(),
698
0
                                    aAnchorTransform );
699
0
                }
700
202
                else // text in range
701
202
                {
702
                    // build new primitive
703
202
                    pNew = new SdrBlockTextPrimitive2D(
704
202
                        &rText.getSdrText(),
705
202
                        rText.getOutlinerParaObject(),
706
202
                        aAnchorTransform,
707
202
                        rText.getSdrTextHorzAdjust(),
708
202
                        rText.getSdrTextVertAdjust(),
709
202
                        rText.isFixedCellHeight(),
710
202
                        rText.isScroll(),
711
202
                        bCellText,
712
202
                        bWordWrap);
713
202
                }
714
349
            }
715
716
349
            OSL_ENSURE(pNew != nullptr, "createTextPrimitive: no text primitive created (!)");
717
718
349
            if(rText.isBlink())
719
0
            {
720
                // prepare animation and primitive list
721
0
                drawinglayer::animation::AnimationEntryList aAnimationList;
722
0
                rText.getBlinkTextTiming(aAnimationList);
723
724
0
                if(0.0 != aAnimationList.getDuration())
725
0
                {
726
                    // create content sequence
727
0
                    Primitive2DContainer aContent { pNew };
728
729
                    // create and add animated switch primitive
730
0
                    return new AnimatedBlinkPrimitive2D(aAnimationList, std::move(aContent));
731
0
                }
732
0
                else
733
0
                {
734
                    // add to decomposition
735
0
                    return pNew;
736
0
                }
737
0
            }
738
739
349
            if(rText.isScroll())
740
0
            {
741
                // suppress scroll when FontWork
742
0
                if(rText.getSdrFormTextAttribute().isDefault())
743
0
                {
744
                    // get scroll direction
745
0
                    const SdrTextAniDirection eDirection(rText.getSdrText().GetObject().GetTextAniDirection());
746
0
                    const bool bHorizontal(SdrTextAniDirection::Left == eDirection || SdrTextAniDirection::Right == eDirection);
747
748
                    // decompose to get separated values for the scroll box
749
0
                    basegfx::B2DVector aScale, aTranslate;
750
0
                    double fRotate, fShearX;
751
0
                    aAnchorTransform.decompose(aScale, aTranslate, fRotate, fShearX);
752
753
                    // build transform from scaled only to full AnchorTransform and inverse
754
0
                    const basegfx::B2DHomMatrix aSRT(basegfx::utils::createShearXRotateTranslateB2DHomMatrix(
755
0
                        fShearX, fRotate, aTranslate));
756
0
                    basegfx::B2DHomMatrix aISRT(aSRT);
757
0
                    aISRT.invert();
758
759
                    // bring the primitive back to scaled only and get scaled range, create new clone for this
760
0
                    rtl::Reference<SdrTextPrimitive2D> pNew2 = pNew->createTransformedClone(aISRT);
761
0
                    OSL_ENSURE(pNew2, "createTextPrimitive: Could not create transformed clone of text primitive (!)");
762
0
                    pNew = pNew2.get();
763
764
                    // create neutral geometry::ViewInformation2D for local range and decompose calls. This is okay
765
                    // since the decompose is view-independent
766
0
                    geometry::ViewInformation2D aViewInformation2D;
767
768
                    // get range
769
0
                    const basegfx::B2DRange aScaledRange(pNew->getB2DRange(aViewInformation2D));
770
771
                    // create left outside and right outside transformations. Also take care
772
                    // of the clip rectangle
773
0
                    basegfx::B2DHomMatrix aLeft, aRight;
774
0
                    basegfx::B2DPoint aClipTopLeft(0.0, 0.0);
775
0
                    basegfx::B2DPoint aClipBottomRight(aScale.getX(), aScale.getY());
776
777
0
                    if(bHorizontal)
778
0
                    {
779
0
                        aClipTopLeft.setY(aScaledRange.getMinY());
780
0
                        aClipBottomRight.setY(aScaledRange.getMaxY());
781
0
                        aLeft.translate(-aScaledRange.getMaxX(), 0.0);
782
0
                        aRight.translate(aScale.getX() - aScaledRange.getMinX(), 0.0);
783
0
                    }
784
0
                    else
785
0
                    {
786
0
                        aClipTopLeft.setX(aScaledRange.getMinX());
787
0
                        aClipBottomRight.setX(aScaledRange.getMaxX());
788
0
                        aLeft.translate(0.0, -aScaledRange.getMaxY());
789
0
                        aRight.translate(0.0, aScale.getY() - aScaledRange.getMinY());
790
0
                    }
791
792
0
                    aLeft *= aSRT;
793
0
                    aRight *= aSRT;
794
795
                    // prepare animation list
796
0
                    drawinglayer::animation::AnimationEntryList aAnimationList;
797
798
0
                    if(bHorizontal)
799
0
                    {
800
0
                        rText.getScrollTextTiming(aAnimationList, aScale.getX(), aScaledRange.getWidth());
801
0
                    }
802
0
                    else
803
0
                    {
804
0
                        rText.getScrollTextTiming(aAnimationList, aScale.getY(), aScaledRange.getHeight());
805
0
                    }
806
807
0
                    if(0.0 != aAnimationList.getDuration())
808
0
                    {
809
                        // create a new Primitive2DContainer containing the animated text in its scaled only state.
810
                        // use the decomposition to force to simple text primitives, those will no longer
811
                        // need the outliner for formatting (alternatively it is also possible to just add
812
                        // pNew to aNewPrimitiveSequence)
813
0
                        Primitive2DContainer aAnimSequence;
814
0
                        pNew->get2DDecomposition(aAnimSequence, aViewInformation2D);
815
0
                        pNew.clear();
816
817
                        // create a new animatedInterpolatePrimitive and add it
818
0
                        Primitive2DContainer aContent {
819
0
                            new AnimatedInterpolatePrimitive2D({ aLeft, aRight }, aAnimationList, std::move(aAnimSequence))
820
0
                        };
821
822
                        // scrolling needs an encapsulating clipping primitive
823
0
                        const basegfx::B2DRange aClipRange(aClipTopLeft, aClipBottomRight);
824
0
                        basegfx::B2DPolygon aClipPolygon(basegfx::utils::createPolygonFromRect(aClipRange));
825
0
                        aClipPolygon.transform(aSRT);
826
0
                        return new MaskPrimitive2D(basegfx::B2DPolyPolygon(aClipPolygon), std::move(aContent));
827
0
                    }
828
0
                    else
829
0
                    {
830
                        // add to decomposition
831
0
                        return pNew;
832
0
                    }
833
0
                }
834
0
            }
835
836
349
            if(rText.isInEditMode())
837
0
            {
838
                // #i97628#
839
                // encapsulate with TextHierarchyEditPrimitive2D to allow renderers
840
                // to suppress actively edited content if needed
841
0
                Primitive2DContainer aContent { pNew };
842
843
                // create and add TextHierarchyEditPrimitive2D primitive
844
0
                return new TextHierarchyEditPrimitive2D(std::move(aContent));
845
0
            }
846
349
            else
847
349
            {
848
                // add to decomposition
849
349
                return pNew;
850
349
            }
851
349
        }
852
853
        Primitive2DContainer createEmbeddedShadowPrimitive(
854
            Primitive2DContainer&& rContent,
855
            const attribute::SdrShadowAttribute& rShadow,
856
            const basegfx::B2DHomMatrix& rObjectMatrix,
857
            const Primitive2DContainer* pContentForShadow)
858
0
        {
859
0
            if(rContent.empty())
860
0
                return std::move(rContent);
861
862
0
            basegfx::B2DHomMatrix aShadowOffset;
863
864
0
            if(rShadow.getSize().getX() != 100000)
865
0
            {
866
0
                basegfx::B2DTuple aScale;
867
0
                basegfx::B2DTuple aTranslate;
868
0
                double fRotate = 0;
869
0
                double fShearX = 0;
870
0
                rObjectMatrix.decompose(aScale, aTranslate, fRotate, fShearX);
871
                // Scale the shadow
872
0
                aTranslate += getShadowScaleOriginOffset(aScale, rShadow.getAlignment());
873
0
                aShadowOffset.translate(-aTranslate);
874
0
                aShadowOffset.scale(rShadow.getSize().getX() * 0.00001, rShadow.getSize().getY() * 0.00001);
875
0
                aShadowOffset.translate(aTranslate);
876
0
            }
877
878
0
            aShadowOffset.translate(rShadow.getOffset().getX(), rShadow.getOffset().getY());
879
880
            // create shadow primitive and add content
881
0
            const Primitive2DContainer& rContentForShadow
882
0
                    = pContentForShadow ? *pContentForShadow : rContent;
883
0
            int nContentWithTransparence = std::count_if(
884
0
                rContentForShadow.begin(), rContentForShadow.end(),
885
0
                [](const Primitive2DReference& xChild) {
886
0
                    auto pChild = dynamic_cast<SdrCellPrimitive2D*>(xChild.get());
887
0
                    return pChild && pChild->getTransparenceForShadow() != 0;
888
0
                });
889
0
            if (nContentWithTransparence == 0)
890
0
            {
891
0
                Primitive2DContainer aRetval(2);
892
0
                aRetval[0] =
893
0
                    new ShadowPrimitive2D(
894
0
                        aShadowOffset,
895
0
                        rShadow.getColor(),
896
0
                        rShadow.getBlur(),
897
0
                        Primitive2DContainer(pContentForShadow ? *pContentForShadow : rContent));
898
899
0
                if (0.0 != rShadow.getTransparence())
900
0
                {
901
                    // create SimpleTransparencePrimitive2D
902
0
                    Primitive2DContainer aTempContent{ aRetval[0] };
903
904
0
                    aRetval[0] =
905
0
                        new UnifiedTransparencePrimitive2D(
906
0
                            std::move(aTempContent),
907
0
                            rShadow.getTransparence());
908
0
                }
909
910
0
                aRetval[1] = new GroupPrimitive2D(std::move(rContent));
911
0
                return aRetval;
912
0
            }
913
914
0
            Primitive2DContainer aRetval;
915
0
            for (const auto& xChild : rContentForShadow)
916
0
            {
917
0
                aRetval.emplace_back(
918
0
                    new ShadowPrimitive2D(aShadowOffset, rShadow.getColor(), rShadow.getBlur(),
919
0
                                            Primitive2DContainer({ xChild })));
920
0
                if (rShadow.getTransparence() != 0.0)
921
0
                {
922
0
                    Primitive2DContainer aTempContent{ aRetval.back() };
923
0
                    aRetval.back() = new UnifiedTransparencePrimitive2D(
924
0
                            std::move(aTempContent), rShadow.getTransparence());
925
0
                }
926
0
            }
927
928
0
            aRetval.push_back(new GroupPrimitive2D(std::move(rContent)));
929
0
            return aRetval;
930
0
        }
931
932
        Primitive2DContainer createEmbeddedGlowPrimitive(
933
            Primitive2DContainer&& rContent,
934
            const attribute::SdrGlowAttribute& rGlow)
935
0
        {
936
0
            if(rContent.empty())
937
0
                return std::move(rContent);
938
0
            Primitive2DContainer aRetval(2);
939
0
            aRetval[0] = new GlowPrimitive2D(rGlow.getColor(), rGlow.getRadius(), Primitive2DContainer(rContent));
940
0
            aRetval[1] = new GroupPrimitive2D(Primitive2DContainer(std::move(rContent)));
941
0
            return aRetval;
942
0
        }
943
944
        Primitive2DContainer createEmbeddedTextGlowPrimitive(
945
            Primitive2DContainer&& rContent,
946
            const attribute::SdrGlowTextAttribute& rGlow)
947
0
        {
948
0
            if (rContent.empty())
949
0
                return std::move(rContent);
950
951
0
            Primitive2DContainer aRetval(2);
952
0
            aRetval[0] = new GlowPrimitive2D(rGlow.getTextColor(), rGlow.getTextRadius(), Primitive2DContainer(rContent));
953
0
            aRetval[1] = new GroupPrimitive2D(Primitive2DContainer(std::move(rContent)));
954
955
0
            return aRetval;
956
0
        }
957
958
        Primitive2DContainer createEmbeddedSoftEdgePrimitive(Primitive2DContainer&& aContent,
959
                                                             sal_Int32 nRadius)
960
0
        {
961
0
            if (aContent.empty() || !nRadius)
962
0
                return std::move(aContent);
963
0
            Primitive2DContainer aRetval(1);
964
0
            aRetval[0] = new SoftEdgePrimitive2D(nRadius, std::move(aContent));
965
0
            return aRetval;
966
0
        }
967
968
} // end of namespace
969
970
/* vim:set shiftwidth=4 softtabstop=4 expandtab: */