Coverage Report

Created: 2025-12-31 10:39

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