Coverage Report

Created: 2025-07-07 10:01

/src/libreoffice/svx/source/sdr/primitive2d/sdrframeborderprimitive2d.cxx
Line
Count
Source (jump to first uncovered line)
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 <svx/sdr/primitive2d/sdrframeborderprimitive2d.hxx>
21
#include <drawinglayer/primitive2d/borderlineprimitive2d.hxx>
22
#include <drawinglayer/primitive2d/groupprimitive2d.hxx>
23
#include <drawinglayer/geometry/viewinformation2d.hxx>
24
#include <svx/sdr/primitive2d/svx_primitivetypes2d.hxx>
25
#include <basegfx/polygon/b2dpolygontools.hxx>
26
#include <svtools/borderhelper.hxx>
27
28
namespace
29
{
30
    double snapToDiscreteUnit(
31
        double fValue,
32
        double fMinimalDiscreteUnit)
33
29.9k
    {
34
29.9k
        if(0.0 != fValue)
35
10.0k
        {
36
10.0k
            fValue = std::max(fValue, fMinimalDiscreteUnit);
37
10.0k
        }
38
39
29.9k
        return fValue;
40
29.9k
    }
41
42
    class StyleVectorCombination
43
    {
44
    private:
45
        struct OffsetAndHalfWidthAndColor
46
        {
47
            double          mfOffset;
48
            double          mfHalfWidth;
49
            Color           maColor;
50
51
            OffsetAndHalfWidthAndColor(double offset, double halfWidth, Color color) :
52
10.0k
                mfOffset(offset),
53
10.0k
                mfHalfWidth(halfWidth),
54
10.0k
                maColor(color)
55
10.0k
            {}
56
        };
57
58
        double                                      mfRefModeOffset;
59
        basegfx::B2DVector                          maB2DVector;
60
        double                                      mfAngle;
61
        std::vector< OffsetAndHalfWidthAndColor >   maOffsets;
62
63
    public:
64
        StyleVectorCombination(
65
            const svx::frame::Style& rStyle,
66
            const basegfx::B2DVector& rB2DVector,
67
            double fAngle,
68
            bool bMirrored,
69
            const Color* pForceColor,
70
            double fMinimalDiscreteUnit)
71
9.98k
        :   mfRefModeOffset(0.0),
72
9.98k
            maB2DVector(rB2DVector),
73
9.98k
            mfAngle(fAngle)
74
9.98k
        {
75
9.98k
            if (!rStyle.IsUsed())
76
0
                return;
77
78
9.98k
            svx::frame::RefMode aRefMode(rStyle.GetRefMode());
79
9.98k
            Color aPrim(rStyle.GetColorPrim());
80
9.98k
            Color aSecn(rStyle.GetColorSecn());
81
9.98k
            const bool bSecnUsed(0.0 != rStyle.Secn());
82
83
            // Get the single segment line widths. This is the point where the
84
            // minimal discrete unit will be used if given (fMinimalDiscreteUnit). If
85
            // not given it's 0.0 and thus will have no influence.
86
9.98k
            double fPrim(snapToDiscreteUnit(rStyle.Prim(), fMinimalDiscreteUnit));
87
9.98k
            const double fDist(snapToDiscreteUnit(rStyle.Dist(), fMinimalDiscreteUnit));
88
9.98k
            double fSecn(snapToDiscreteUnit(rStyle.Secn(), fMinimalDiscreteUnit));
89
90
            // Of course also do not use svx::frame::Style::GetWidth() for obvious
91
            // reasons.
92
9.98k
            const double fStyleWidth(fPrim + fDist + fSecn);
93
94
9.98k
            if(bMirrored)
95
2.59k
            {
96
2.59k
                switch(aRefMode)
97
2.59k
                {
98
107
                    case svx::frame::RefMode::Begin: aRefMode = svx::frame::RefMode::End; break;
99
0
                    case svx::frame::RefMode::End: aRefMode = svx::frame::RefMode::Begin; break;
100
2.49k
                    default: break;
101
2.59k
                }
102
103
2.59k
                if(bSecnUsed)
104
2
                {
105
2
                    std::swap(aPrim, aSecn);
106
2
                    std::swap(fPrim, fSecn);
107
2
                }
108
2.59k
            }
109
110
9.98k
            if (svx::frame::RefMode::Centered != aRefMode)
111
216
            {
112
216
                const double fHalfWidth(fStyleWidth * 0.5);
113
114
216
                if (svx::frame::RefMode::Begin == aRefMode)
115
109
                {
116
                    // move aligned below vector
117
109
                    mfRefModeOffset = fHalfWidth;
118
109
                }
119
107
                else if (svx::frame::RefMode::End == aRefMode)
120
107
                {
121
                    // move aligned above vector
122
107
                    mfRefModeOffset = -fHalfWidth;
123
107
                }
124
216
            }
125
126
9.98k
            if (bSecnUsed)
127
13
            {
128
                // both or all three lines used
129
13
                const bool bPrimTransparent(rStyle.GetColorPrim().IsFullyTransparent());
130
13
                const bool bDistTransparent(!rStyle.UseGapColor() || rStyle.GetColorGap().IsFullyTransparent());
131
13
                const bool bSecnTransparent(aSecn.IsFullyTransparent());
132
133
13
                if(!bPrimTransparent || !bDistTransparent || !bSecnTransparent)
134
13
                {
135
13
                    const double a(mfRefModeOffset - (fStyleWidth * 0.5));
136
13
                    const double b(a + fPrim);
137
13
                    const double c(b + fDist);
138
13
                    const double d(c + fSecn);
139
140
13
                    maOffsets.push_back(
141
13
                        OffsetAndHalfWidthAndColor(
142
13
                            (a + b) * 0.5,
143
13
                            fPrim * 0.5,
144
13
                            nullptr != pForceColor ? *pForceColor : aPrim));
145
146
13
                    maOffsets.push_back(
147
13
                        OffsetAndHalfWidthAndColor(
148
13
                            (b + c) * 0.5,
149
13
                            fDist * 0.5,
150
13
                            rStyle.UseGapColor()
151
13
                                ? (nullptr != pForceColor ? *pForceColor : rStyle.GetColorGap())
152
13
                                : COL_TRANSPARENT));
153
154
13
                    maOffsets.push_back(
155
13
                        OffsetAndHalfWidthAndColor(
156
13
                            (c + d) * 0.5,
157
13
                            fSecn * 0.5,
158
13
                            nullptr != pForceColor ? *pForceColor : aSecn));
159
13
                }
160
13
            }
161
9.97k
            else
162
9.97k
            {
163
                // one line used, push two values, from outer to inner
164
9.97k
                if(!rStyle.GetColorPrim().IsFullyTransparent())
165
9.97k
                {
166
9.97k
                    maOffsets.push_back(
167
9.97k
                        OffsetAndHalfWidthAndColor(
168
9.97k
                            mfRefModeOffset,
169
9.97k
                            fPrim * 0.5,
170
9.97k
                            nullptr != pForceColor ? *pForceColor : aPrim));
171
9.97k
                }
172
9.97k
            }
173
9.98k
        }
174
175
2.48k
        double getRefModeOffset() const { return mfRefModeOffset; }
176
15.1k
        const basegfx::B2DVector& getB2DVector() const { return maB2DVector; }
177
164
        double getAngle() const { return mfAngle; }
178
7.43k
        bool empty() const { return maOffsets.empty(); }
179
17.4k
        size_t size() const { return maOffsets.size(); }
180
181
        void getColorAndOffsetAndHalfWidth(size_t nIndex, Color& rColor, double& rfOffset, double& rfHalfWidth) const
182
12.4k
        {
183
12.4k
            if(nIndex >= maOffsets.size())
184
0
                return;
185
12.4k
            const OffsetAndHalfWidthAndColor& rCandidate(maOffsets[nIndex]);
186
12.4k
            rfOffset = rCandidate.mfOffset;
187
12.4k
            rfHalfWidth = rCandidate.mfHalfWidth;
188
12.4k
            rColor = rCandidate.maColor;
189
12.4k
        }
190
    };
191
192
    class StyleVectorTable
193
    {
194
    private:
195
        std::vector< StyleVectorCombination >       maEntries;
196
197
    public:
198
        StyleVectorTable()
199
4.97k
        {
200
4.97k
        }
201
202
        void add(
203
            const svx::frame::Style& rStyle,
204
            const basegfx::B2DVector& rMyVector,
205
            const basegfx::B2DVector& rOtherVector,
206
            bool bMirrored,
207
            double fMinimalDiscreteUnit)
208
5.14k
        {
209
5.14k
            if(!rStyle.IsUsed() || basegfx::areParallel(rMyVector, rOtherVector))
210
112
                return;
211
212
            // create angle between both. angle() needs vectors pointing away from the same point,
213
            // so take the mirrored one. Add M_PI to get from -pi..+pi to [0..M_PI_2] for sorting
214
5.02k
            const double fAngle(basegfx::B2DVector(-rMyVector.getX(), -rMyVector.getY()).angle(rOtherVector) + M_PI);
215
5.02k
            maEntries.emplace_back(
216
5.02k
                rStyle,
217
5.02k
                rOtherVector,
218
5.02k
                fAngle,
219
5.02k
                bMirrored,
220
5.02k
                nullptr,
221
5.02k
                fMinimalDiscreteUnit);
222
5.02k
        }
223
224
        void sort()
225
4.97k
        {
226
            // sort inverse from highest to lowest
227
4.97k
            std::sort(
228
4.97k
                maEntries.begin(),
229
4.97k
                maEntries.end(),
230
4.97k
                [](const StyleVectorCombination& a, const StyleVectorCombination& b)
231
4.97k
                    { return a.getAngle() > b.getAngle(); });
232
4.97k
        }
233
234
4.97k
        bool empty() const { return maEntries.empty(); }
235
4.95k
        const std::vector< StyleVectorCombination >& getEntries() const{ return maEntries; }
236
    };
237
238
    struct CutSet
239
    {
240
        double          mfOLML;
241
        double          mfORML;
242
        double          mfOLMR;
243
        double          mfORMR;
244
245
5.03k
        CutSet() : mfOLML(0.0), mfORML(0.0), mfOLMR(0.0), mfORMR(0.0)
246
5.03k
        {
247
5.03k
        }
248
249
        bool operator<( const CutSet& rOther) const
250
0
        {
251
0
            const double fA(mfOLML + mfORML + mfOLMR + mfORMR);
252
0
            const double fB(rOther.mfOLML + rOther.mfORML + rOther.mfOLMR + rOther.mfORMR);
253
254
0
            return fA < fB;
255
0
        }
256
257
242
        double getSum() const { return mfOLML + mfORML + mfOLMR + mfORMR; }
258
    };
259
260
    void getCutSet(
261
        CutSet& rCutSet,
262
        const basegfx::B2DPoint& rLeft,
263
        const basegfx::B2DPoint& rRight,
264
        const basegfx::B2DVector& rX,
265
        const basegfx::B2DPoint& rOtherLeft,
266
        const basegfx::B2DPoint& rOtherRight,
267
        const basegfx::B2DVector& rOtherX)
268
5.03k
    {
269
5.03k
        basegfx::utils::findCut(
270
5.03k
            rLeft,
271
5.03k
            rX,
272
5.03k
            rOtherLeft,
273
5.03k
            rOtherX,
274
5.03k
            CutFlagValue::LINE,
275
5.03k
            &rCutSet.mfOLML);
276
277
5.03k
        basegfx::utils::findCut(
278
5.03k
            rRight,
279
5.03k
            rX,
280
5.03k
            rOtherLeft,
281
5.03k
            rOtherX,
282
5.03k
            CutFlagValue::LINE,
283
5.03k
            &rCutSet.mfOLMR);
284
285
5.03k
        basegfx::utils::findCut(
286
5.03k
            rLeft,
287
5.03k
            rX,
288
5.03k
            rOtherRight,
289
5.03k
            rOtherX,
290
5.03k
            CutFlagValue::LINE,
291
5.03k
            &rCutSet.mfORML);
292
293
5.03k
        basegfx::utils::findCut(
294
5.03k
            rRight,
295
5.03k
            rX,
296
5.03k
            rOtherRight,
297
5.03k
            rOtherX,
298
5.03k
            CutFlagValue::LINE,
299
5.03k
            &rCutSet.mfORMR);
300
5.03k
    }
301
302
    struct ExtendSet
303
    {
304
        double          mfExtLeft;
305
        double          mfExtRight;
306
307
5.00k
        ExtendSet() : mfExtLeft(0.0), mfExtRight(0.0) {}
308
    };
309
310
    void getExtends(
311
        std::vector<ExtendSet>& rExtendSet,                         // target Left/Right values to fill
312
        const basegfx::B2DPoint& rOrigin,                           // own vector start
313
        const StyleVectorCombination& rCombination,                 // own vector and offsets for lines
314
        const basegfx::B2DVector& rPerpendX,                        // normalized perpendicular to own vector
315
        const std::vector< StyleVectorCombination >& rStyleVector)  // other vectors emerging in this point
316
4.95k
    {
317
4.95k
        if(!(!rCombination.empty() && !rStyleVector.empty() && rCombination.size() == rExtendSet.size()))
318
0
            return;
319
320
4.95k
        const size_t nOffsetA(rCombination.size());
321
322
4.95k
        if(1 == nOffsetA)
323
4.94k
        {
324
4.94k
            Color aMyColor; double fMyOffset(0.0); double fMyHalfWidth(0.0);
325
4.94k
            rCombination.getColorAndOffsetAndHalfWidth(0, aMyColor, fMyOffset, fMyHalfWidth);
326
327
4.94k
            if(!aMyColor.IsFullyTransparent())
328
4.94k
            {
329
4.94k
                const basegfx::B2DPoint aLeft(rOrigin + (rPerpendX * (fMyOffset - fMyHalfWidth)));
330
4.94k
                const basegfx::B2DPoint aRight(rOrigin + (rPerpendX * (fMyOffset + fMyHalfWidth)));
331
4.94k
                std::vector< CutSet > aCutSets;
332
333
4.94k
                for(const auto& rStyleCandidate : rStyleVector)
334
5.02k
                {
335
5.02k
                    const basegfx::B2DVector aOtherPerpend(basegfx::getNormalizedPerpendicular(rStyleCandidate.getB2DVector()));
336
5.02k
                    const size_t nOffsetB(rStyleCandidate.size());
337
338
10.0k
                    for(size_t other(0); other < nOffsetB; other++)
339
5.03k
                    {
340
5.03k
                        Color aOtherColor; double fOtherOffset(0.0); double fOtherHalfWidth(0.0);
341
5.03k
                        rStyleCandidate.getColorAndOffsetAndHalfWidth(other, aOtherColor, fOtherOffset, fOtherHalfWidth);
342
343
5.03k
                        if(!aOtherColor.IsFullyTransparent())
344
5.02k
                        {
345
5.02k
                            const basegfx::B2DPoint aOtherLeft(rOrigin + (aOtherPerpend * (fOtherOffset - fOtherHalfWidth)));
346
5.02k
                            const basegfx::B2DPoint aOtherRight(rOrigin + (aOtherPerpend * (fOtherOffset + fOtherHalfWidth)));
347
348
5.02k
                            CutSet aNewCutSet;
349
5.02k
                            getCutSet(aNewCutSet, aLeft, aRight, rCombination.getB2DVector(), aOtherLeft, aOtherRight, rStyleCandidate.getB2DVector());
350
5.02k
                            aCutSets.push_back(aNewCutSet);
351
5.02k
                        }
352
5.03k
                    }
353
5.02k
                }
354
355
4.94k
                if(!aCutSets.empty())
356
4.94k
                {
357
4.94k
                    CutSet aCutSet(aCutSets[0]);
358
4.94k
                    const size_t nNumCutSets(aCutSets.size());
359
360
4.94k
                    if(1 != nNumCutSets)
361
82
                    {
362
82
                        double fCutSet(aCutSet.getSum());
363
364
164
                        for(size_t a(1); a < nNumCutSets; a++)
365
82
                        {
366
82
                            const CutSet& rCandidate(aCutSets[a]);
367
82
                            const double fCandidate(rCandidate.getSum());
368
369
82
                            if(basegfx::fTools::equalZero(fCandidate - fCutSet))
370
78
                            {
371
                                // both have equal center point, use medium cut
372
78
                                const double fNewOLML(std::max(std::min(rCandidate.mfOLML, rCandidate.mfORML), std::min(aCutSet.mfOLML, aCutSet.mfORML)));
373
78
                                const double fNewORML(std::min(std::max(rCandidate.mfOLML, rCandidate.mfORML), std::max(aCutSet.mfOLML, aCutSet.mfORML)));
374
78
                                const double fNewOLMR(std::max(std::min(rCandidate.mfOLMR, rCandidate.mfORMR), std::min(aCutSet.mfOLMR, aCutSet.mfORMR)));
375
78
                                const double fNewORMR(std::min(std::max(rCandidate.mfOLMR, rCandidate.mfORMR), std::max(aCutSet.mfOLMR, aCutSet.mfORMR)));
376
78
                                aCutSet.mfOLML = fNewOLML;
377
78
                                aCutSet.mfORML = fNewORML;
378
78
                                aCutSet.mfOLMR = fNewOLMR;
379
78
                                aCutSet.mfORMR = fNewORMR;
380
78
                                fCutSet = aCutSet.getSum();
381
78
                            }
382
4
                            else if(fCandidate < fCutSet)
383
2
                            {
384
                                // get minimum
385
2
                                fCutSet = fCandidate;
386
2
                                aCutSet = rCandidate;
387
2
                            }
388
82
                        }
389
82
                    }
390
391
4.94k
                    ExtendSet& rExt(rExtendSet[0]);
392
393
4.94k
                    rExt.mfExtLeft = std::min(aCutSet.mfOLML, aCutSet.mfORML);
394
4.94k
                    rExt.mfExtRight = std::min(aCutSet.mfOLMR, aCutSet.mfORMR);
395
4.94k
                }
396
4.94k
            }
397
4.94k
        }
398
4
        else
399
4
        {
400
4
            size_t nVisEdgeUp(0);
401
4
            size_t nVisEdgeDn(0);
402
403
16
            for(size_t my(0); my < nOffsetA; my++)
404
12
            {
405
12
                Color aMyColor; double fMyOffset(0.0); double fMyHalfWidth(0.0);
406
12
                rCombination.getColorAndOffsetAndHalfWidth(my, aMyColor, fMyOffset, fMyHalfWidth);
407
408
12
                if(!aMyColor.IsFullyTransparent())
409
8
                {
410
8
                    const basegfx::B2DPoint aLeft(rOrigin + (rPerpendX * (fMyOffset - fMyHalfWidth)));
411
8
                    const basegfx::B2DPoint aRight(rOrigin + (rPerpendX * (fMyOffset + fMyHalfWidth)));
412
8
                    const bool bUpper(my <= (nOffsetA >> 1));
413
8
                    const StyleVectorCombination& rStyleCandidate(bUpper ? rStyleVector.front() : rStyleVector.back());
414
8
                    const basegfx::B2DVector aOtherPerpend(basegfx::getNormalizedPerpendicular(rStyleCandidate.getB2DVector()));
415
8
                    const size_t nOffsetB(rStyleCandidate.size());
416
8
                    std::vector< CutSet > aCutSets;
417
418
16
                    for(size_t other(0); other < nOffsetB; other++)
419
8
                    {
420
8
                        Color aOtherColor; double fOtherOffset(0.0); double fOtherHalfWidth(0.0);
421
8
                        rStyleCandidate.getColorAndOffsetAndHalfWidth(other, aOtherColor, fOtherOffset, fOtherHalfWidth);
422
423
8
                        if(!aOtherColor.IsFullyTransparent())
424
8
                        {
425
8
                            const basegfx::B2DPoint aOtherLeft(rOrigin + (aOtherPerpend * (fOtherOffset - fOtherHalfWidth)));
426
8
                            const basegfx::B2DPoint aOtherRight(rOrigin + (aOtherPerpend * (fOtherOffset + fOtherHalfWidth)));
427
8
                            CutSet aCutSet;
428
8
                            getCutSet(aCutSet, aLeft, aRight, rCombination.getB2DVector(), aOtherLeft, aOtherRight, rStyleCandidate.getB2DVector());
429
8
                            aCutSets.push_back(aCutSet);
430
8
                        }
431
8
                    }
432
433
8
                    if(!aCutSets.empty())
434
8
                    {
435
                        // sort: min to start, max to end
436
8
                        std::sort(aCutSets.begin(), aCutSets.end());
437
8
                        const bool bOtherUpper(rStyleCandidate.getAngle() > M_PI);
438
439
                        // check if we need min or max
440
                        //  bUpper      bOtherUpper        MinMax
441
                        //    t             t               max
442
                        //    t             f               min
443
                        //    f             f               max
444
                        //    f             t               min
445
8
                        const bool bMax(bUpper == bOtherUpper);
446
8
                        size_t nBaseIndex(0);
447
8
                        const size_t nNumCutSets(aCutSets.size());
448
449
8
                        if(bMax)
450
4
                        {
451
                            // access at end
452
4
                            nBaseIndex = nNumCutSets - 1 - (bUpper ? nVisEdgeUp : nVisEdgeDn);
453
4
                        }
454
4
                        else
455
4
                        {
456
                            // access at start
457
4
                            nBaseIndex = bUpper ? nVisEdgeUp : nVisEdgeDn;
458
4
                        }
459
460
8
                        const size_t nSecuredIndex(std::clamp(nBaseIndex, size_t(0), size_t(nNumCutSets - 1)));
461
8
                        const CutSet& rCutSet(aCutSets[nSecuredIndex]);
462
8
                        ExtendSet& rExt(rExtendSet[my]);
463
464
8
                        rExt.mfExtLeft = std::min(rCutSet.mfOLML, rCutSet.mfORML);
465
8
                        rExt.mfExtRight = std::min(rCutSet.mfOLMR, rCutSet.mfORMR);
466
8
                    }
467
468
8
                    if(bUpper)
469
4
                    {
470
4
                        nVisEdgeUp++;
471
4
                    }
472
4
                    else
473
4
                    {
474
4
                        nVisEdgeDn++;
475
4
                    }
476
8
                }
477
12
            }
478
4
        }
479
4.95k
    }
480
481
    /**
482
     *  Helper method to create the correct drawinglayer::primitive2d::BorderLinePrimitive2D
483
     *  for the given data, especially the correct drawinglayer::primitive2d::BorderLine entries
484
     *  including the correctly solved/created LineStartEnd extends
485
     *
486
     *  rTarget : Here the evtl. created BorderLinePrimitive2D will be appended
487
     *  rOrigin : StartPoint of the Borderline
488
     *  rX      : Vector of the Borderline
489
     *  rBorder : svx::frame::Style of the of the Borderline
490
     *  rStartStyleVectorTable : All other Borderlines which have to be taken into account because
491
     *      they have the same StartPoint as the current Borderline. These will be used to calculate
492
     *      the correct LineStartEnd extends tor the BorderLinePrimitive2D. The definition should be
493
     *      built up using svx::frame::StyleVectorTable and StyleVectorTable::add and includes:
494
     *          rStyle      : the svx::frame::Style of one other BorderLine
495
     *          rMyVector   : the Vector of the *new* to-be-defined BorderLine, identical to rX
496
     *          rOtherVector: the Vector of one other BorderLine (may be, but does not need to be normalized),
497
     *                        always *pointing away* from the common StartPoint rOrigin
498
     *          bMirrored   : define if rStyle of one other BorderLine shall be mirrored (e.g. bottom-right edges)
499
     *      With multiple BorderLines the definitions have to be CounterClockWise. This will be
500
     *      ensured by StyleVectorTable sorting the entries, but knowing this may allow more efficient
501
     *      data creation.
502
     *  rEndStyleVectorTable: All other BorderLines that have the same EndPoint. There are differences to
503
     *      the Start definitions:
504
     *          - do not forget to consequently use -rX for rMyVector
505
     *          - definitions have to be ClockWise for the EndBorderLines, will be ensured by sorting
506
     *
507
     *  If you take all this into account, you will get correctly extended BorderLinePrimitive2D
508
     *  representations for the new to be defined BorderLine. That extensions will overlap nicely
509
     *  with the corresponding BorderLines and take all multiple line definitions in the ::Style into
510
     *  account.
511
     *  The internal solver is *not limited* to ::Style(s) with three parts (Left/Gap/Right), this is
512
     *  just due to svx::frame::Style's definitions. A new solver based on this one can be created
513
     *  anytime using more mulötiple borders based on the more flexible
514
     *  std::vector< drawinglayer::primitive2d::BorderLine > if needed.
515
     */
516
    void CreateBorderPrimitives(
517
        drawinglayer::primitive2d::Primitive2DContainer& rTarget,   /// target for created primitives
518
        const basegfx::B2DPoint& rOrigin,                           /// start point of borderline
519
        const basegfx::B2DVector& rX,                               /// X-Axis of borderline with length
520
        const svx::frame::Style& rBorder,                           /// Style of borderline
521
        const StyleVectorTable& rStartStyleVectorTable,             /// Styles and vectors (pointing away) at borderline start, ccw
522
        const StyleVectorTable& rEndStyleVectorTable,               /// Styles and vectors (pointing away) at borderline end, cw
523
        const Color* pForceColor,                                   /// If specified, overrides frame border color.
524
        double fMinimalDiscreteUnit)                                /// minimal discrete unit to use for svx::frame::Style width values
525
2.48k
    {
526
        // get offset color pairs for  style, one per visible line
527
2.48k
        const StyleVectorCombination aCombination(
528
2.48k
            rBorder,
529
2.48k
            rX,
530
2.48k
            0.0,
531
2.48k
            false,
532
2.48k
            pForceColor,
533
2.48k
            fMinimalDiscreteUnit);
534
535
2.48k
        if(aCombination.empty())
536
0
            return;
537
538
2.48k
        const basegfx::B2DVector aPerpendX(basegfx::getNormalizedPerpendicular(rX));
539
2.48k
        const bool bHasStartStyles(!rStartStyleVectorTable.empty());
540
2.48k
        const bool bHasEndStyles(!rEndStyleVectorTable.empty());
541
2.48k
        const size_t nOffsets(aCombination.size());
542
2.48k
        std::vector<ExtendSet> aExtendSetStart(nOffsets);
543
2.48k
        std::vector<ExtendSet> aExtendSetEnd(nOffsets);
544
545
2.48k
        if(bHasStartStyles)
546
2.47k
        {
547
            // create extends for line starts, use given point/vector and offsets
548
2.47k
            getExtends(aExtendSetStart, rOrigin, aCombination, aPerpendX, rStartStyleVectorTable.getEntries());
549
2.47k
        }
550
551
2.48k
        if(bHasEndStyles)
552
2.47k
        {
553
            // Create extends for line ends, create inverse point/vector and inverse offsets.
554
2.47k
            const StyleVectorCombination aMirroredCombination(
555
2.47k
                rBorder,
556
2.47k
                -rX,
557
2.47k
                0.0,
558
2.47k
                true,
559
2.47k
                pForceColor,
560
2.47k
                fMinimalDiscreteUnit);
561
562
2.47k
            getExtends(aExtendSetEnd, rOrigin + rX, aMirroredCombination, -aPerpendX, rEndStyleVectorTable.getEntries());
563
564
            // also need to inverse the result to apply to the correct lines
565
2.47k
            std::reverse(aExtendSetEnd.begin(), aExtendSetEnd.end());
566
2.47k
        }
567
568
2.48k
        std::vector< drawinglayer::primitive2d::BorderLine > aBorderlines;
569
2.48k
        const double fNegLength(-rX.getLength());
570
571
4.98k
        for(size_t a(0); a < nOffsets; a++)
572
2.50k
        {
573
2.50k
            Color aMyColor;
574
2.50k
            double fMyOffset(0.0);
575
2.50k
            double fMyHalfWidth(0.0);
576
2.50k
            aCombination.getColorAndOffsetAndHalfWidth(a, aMyColor, fMyOffset, fMyHalfWidth);
577
2.50k
            const ExtendSet& rExtStart(aExtendSetStart[a]);
578
2.50k
            const ExtendSet& rExtEnd(aExtendSetEnd[a]);
579
580
2.50k
            if(aMyColor.IsFullyTransparent())
581
7
            {
582
7
                aBorderlines.push_back(
583
7
                    drawinglayer::primitive2d::BorderLine(
584
7
                        fMyHalfWidth * 2.0));
585
7
            }
586
2.49k
            else
587
2.49k
            {
588
2.49k
                aBorderlines.push_back(
589
2.49k
                    drawinglayer::primitive2d::BorderLine(
590
2.49k
                        drawinglayer::attribute::LineAttribute(
591
2.49k
                            aMyColor.getBColor(),
592
2.49k
                            fMyHalfWidth * 2.0),
593
2.49k
                        fNegLength * rExtStart.mfExtLeft,
594
2.49k
                        fNegLength * rExtStart.mfExtRight,
595
2.49k
                        fNegLength * rExtEnd.mfExtRight,
596
2.49k
                        fNegLength * rExtEnd.mfExtLeft));
597
2.49k
            }
598
2.50k
        }
599
600
2.48k
        static const double fPatScFact(10.0); // 10.0 multiply, see old code
601
2.48k
        std::vector<double> aDashing(svtools::GetLineDashing(rBorder.Type(), rBorder.PatternScale() * fPatScFact));
602
2.48k
        drawinglayer::attribute::StrokeAttribute aStrokeAttribute(std::move(aDashing));
603
2.48k
        const basegfx::B2DPoint aStart(rOrigin + (aPerpendX * aCombination.getRefModeOffset()));
604
605
2.48k
        rTarget.append(
606
2.48k
                new drawinglayer::primitive2d::BorderLinePrimitive2D(
607
2.48k
                    aStart,
608
2.48k
                    aStart + rX,
609
2.48k
                    std::move(aBorderlines),
610
2.48k
                    std::move(aStrokeAttribute)));
611
2.48k
    }
612
613
    double getMinimalNonZeroValue(double fCurrent, double fNew)
614
25.3k
    {
615
25.3k
        if(0.0 != fNew)
616
10.1k
        {
617
10.1k
            if(0.0 != fCurrent)
618
7.03k
            {
619
7.03k
                fCurrent = std::min(fNew, fCurrent);
620
7.03k
            }
621
3.09k
            else
622
3.09k
            {
623
3.09k
                fCurrent = fNew;
624
3.09k
            }
625
10.1k
        }
626
627
25.3k
        return fCurrent;
628
25.3k
    }
629
630
    double getMinimalNonZeroBorderWidthFromStyle(double fCurrent, const svx::frame::Style& rStyle)
631
7.62k
    {
632
7.62k
        if(rStyle.IsUsed())
633
7.62k
        {
634
7.62k
            fCurrent = getMinimalNonZeroValue(fCurrent, rStyle.Prim());
635
7.62k
            fCurrent = getMinimalNonZeroValue(fCurrent, rStyle.Dist());
636
7.62k
            fCurrent = getMinimalNonZeroValue(fCurrent, rStyle.Secn());
637
7.62k
        }
638
639
7.62k
        return fCurrent;
640
7.62k
    }
641
}
642
643
namespace drawinglayer::primitive2d
644
{
645
        SdrFrameBorderData::SdrConnectStyleData::SdrConnectStyleData(
646
            const svx::frame::Style& rStyle,
647
            const basegfx::B2DVector& rNormalizedPerpendicular,
648
            bool bStyleMirrored)
649
5.14k
        :   maStyle(rStyle),
650
5.14k
            maNormalizedPerpendicular(rNormalizedPerpendicular),
651
5.14k
            mbStyleMirrored(bStyleMirrored)
652
5.14k
        {
653
5.14k
        }
654
655
        bool SdrFrameBorderData::SdrConnectStyleData::operator==(const SdrFrameBorderData::SdrConnectStyleData& rCompare) const
656
0
        {
657
0
            return mbStyleMirrored == rCompare.mbStyleMirrored
658
0
                && maStyle == rCompare.maStyle
659
0
                && maNormalizedPerpendicular == rCompare.maNormalizedPerpendicular;
660
0
        }
661
662
        SdrFrameBorderData::SdrFrameBorderData(
663
            const basegfx::B2DPoint& rOrigin,
664
            const basegfx::B2DVector& rX,
665
            const svx::frame::Style& rStyle,
666
            const Color* pForceColor)
667
2.48k
        :   maOrigin(rOrigin),
668
2.48k
            maX(rX),
669
2.48k
            maStyle(rStyle),
670
2.48k
            maColor(nullptr != pForceColor ? *pForceColor : Color()),
671
2.48k
            mbForceColor(nullptr != pForceColor)
672
2.48k
        {
673
2.48k
        }
674
675
        void SdrFrameBorderData::addSdrConnectStyleData(
676
            bool bStart,
677
            const svx::frame::Style& rStyle,
678
            const basegfx::B2DVector& rNormalizedPerpendicular,
679
            bool bStyleMirrored)
680
5.31k
        {
681
5.31k
            if(rStyle.IsUsed())
682
5.14k
            {
683
5.14k
                if(bStart)
684
2.57k
                {
685
2.57k
                    maStart.emplace_back(rStyle, rNormalizedPerpendicular, bStyleMirrored);
686
2.57k
                }
687
2.56k
                else
688
2.56k
                {
689
2.56k
                    maEnd.emplace_back(rStyle, rNormalizedPerpendicular, bStyleMirrored);
690
2.56k
                }
691
5.14k
            }
692
5.31k
        }
693
694
        void SdrFrameBorderData::create2DDecomposition(
695
            Primitive2DContainer& rContainer,
696
            double fMinimalDiscreteUnit) const
697
2.48k
        {
698
2.48k
            StyleVectorTable aStartVector;
699
2.48k
            StyleVectorTable aEndVector;
700
2.48k
            const basegfx::B2DVector aAxis(-maX);
701
702
2.48k
            for(const auto& rStart : maStart)
703
2.57k
            {
704
2.57k
                aStartVector.add(
705
2.57k
                    rStart.getStyle(),
706
2.57k
                    maX,
707
2.57k
                    rStart.getNormalizedPerpendicular(),
708
2.57k
                    rStart.getStyleMirrored(),
709
2.57k
                    fMinimalDiscreteUnit);
710
2.57k
            }
711
712
2.48k
            for(const auto& rEnd : maEnd)
713
2.56k
            {
714
2.56k
                aEndVector.add(
715
2.56k
                    rEnd.getStyle(),
716
2.56k
                    aAxis,
717
2.56k
                    rEnd.getNormalizedPerpendicular(),
718
2.56k
                    rEnd.getStyleMirrored(),
719
2.56k
                    fMinimalDiscreteUnit);
720
2.56k
            }
721
722
2.48k
            aStartVector.sort();
723
2.48k
            aEndVector.sort();
724
725
2.48k
            CreateBorderPrimitives(
726
2.48k
                rContainer,
727
2.48k
                maOrigin,
728
2.48k
                maX,
729
2.48k
                maStyle,
730
2.48k
                aStartVector,
731
2.48k
                aEndVector,
732
2.48k
                mbForceColor ? &maColor : nullptr,
733
2.48k
                fMinimalDiscreteUnit);
734
2.48k
        }
735
736
        double SdrFrameBorderData::getMinimalNonZeroBorderWidth() const
737
2.48k
        {
738
2.48k
            double fRetval(getMinimalNonZeroBorderWidthFromStyle(0.0, maStyle));
739
740
2.48k
            for(const auto& rStart : maStart)
741
2.57k
            {
742
2.57k
                fRetval = getMinimalNonZeroBorderWidthFromStyle(fRetval, rStart.getStyle());
743
2.57k
            }
744
745
2.48k
            for(const auto& rEnd : maEnd)
746
2.56k
            {
747
2.56k
                fRetval = getMinimalNonZeroBorderWidthFromStyle(fRetval, rEnd.getStyle());
748
2.56k
            }
749
750
2.48k
            return fRetval;
751
2.48k
        }
752
753
754
        bool SdrFrameBorderData::operator==(const SdrFrameBorderData& rCompare) const
755
0
        {
756
0
            return maOrigin == rCompare.maOrigin
757
0
                && maX == rCompare.maX
758
0
                && maStyle == rCompare.maStyle
759
0
                && maColor == rCompare.maColor
760
0
                && mbForceColor == rCompare.mbForceColor
761
0
                && maStart == rCompare.maStart
762
0
                && maEnd == rCompare.maEnd;
763
0
        }
764
765
766
        Primitive2DReference SdrFrameBorderPrimitive2D::create2DDecomposition(
767
            const geometry::ViewInformation2D& /*aViewInformation*/) const
768
611
        {
769
611
            if(getFrameBorders().empty())
770
0
            {
771
0
                return nullptr;
772
0
            }
773
774
611
            Primitive2DContainer aRetval;
775
776
            // Check and use the minimal non-zero BorderWidth for decompose
777
            // if that is set and wanted
778
611
            const double fMinimalDiscreteUnit(doForceToSingleDiscreteUnit()
779
611
                ? mfMinimalNonZeroBorderWidthUsedForDecompose
780
611
                : 0.0);
781
782
            // decompose all buffered SdrFrameBorderData entries and try to merge them
783
            // to reduce existing number of BorderLinePrimitive2D(s)
784
611
            for(const auto& rCandidate : getFrameBorders())
785
2.48k
            {
786
                // get decomposition on one SdrFrameBorderData entry
787
2.48k
                Primitive2DContainer aPartial;
788
2.48k
                rCandidate.create2DDecomposition(
789
2.48k
                    aPartial,
790
2.48k
                    fMinimalDiscreteUnit);
791
792
2.48k
                for(const auto& aCandidatePartial : aPartial)
793
2.48k
                {
794
2.48k
                    if(aRetval.empty())
795
611
                    {
796
                        // no local data yet, just add as 1st entry, done
797
611
                        aRetval.append(aCandidatePartial);
798
611
                    }
799
1.87k
                    else
800
1.87k
                    {
801
1.87k
                        bool bDidMerge(false);
802
803
1.87k
                        for(auto& aCandidateRetval : aRetval)
804
3.92k
                        {
805
                            // try to merge by appending new data to existing data
806
3.92k
                            const drawinglayer::primitive2d::Primitive2DReference aMergeRetvalPartial(
807
3.92k
                                drawinglayer::primitive2d::tryMergeBorderLinePrimitive2D(
808
3.92k
                                    static_cast<BorderLinePrimitive2D*>(aCandidateRetval.get()),
809
3.92k
                                    static_cast<BorderLinePrimitive2D*>(aCandidatePartial.get())));
810
811
3.92k
                            if(aMergeRetvalPartial.is())
812
56
                            {
813
                                // could append, replace existing data with merged data, done
814
56
                                aCandidateRetval = aMergeRetvalPartial;
815
56
                                bDidMerge = true;
816
56
                                break;
817
56
                            }
818
819
                            // try to merge by appending existing data to new data
820
3.87k
                            const drawinglayer::primitive2d::Primitive2DReference aMergePartialRetval(
821
3.87k
                                drawinglayer::primitive2d::tryMergeBorderLinePrimitive2D(
822
3.87k
                                    static_cast<BorderLinePrimitive2D*>(aCandidatePartial.get()),
823
3.87k
                                    static_cast<BorderLinePrimitive2D*>(aCandidateRetval.get())));
824
825
3.87k
                            if(aMergePartialRetval.is())
826
0
                            {
827
                                // could append, replace existing data with merged data, done
828
0
                                aCandidateRetval = aMergePartialRetval;
829
0
                                bDidMerge = true;
830
0
                                break;
831
0
                            }
832
3.87k
                        }
833
834
1.87k
                        if(!bDidMerge)
835
1.82k
                        {
836
                            // no merge after checking all existing data, append as new segment
837
1.82k
                            aRetval.append(aCandidatePartial);
838
1.82k
                        }
839
1.87k
                    }
840
2.48k
                }
841
2.48k
            }
842
843
611
            return new GroupPrimitive2D(std::move(aRetval));
844
611
        }
845
846
        SdrFrameBorderPrimitive2D::SdrFrameBorderPrimitive2D(
847
            SdrFrameBorderDataVector&& rFrameBorders,
848
            bool bForceToSingleDiscreteUnit)
849
611
        :   maFrameBorders(std::move(rFrameBorders)),
850
611
            mfMinimalNonZeroBorderWidth(0.0),
851
611
            mfMinimalNonZeroBorderWidthUsedForDecompose(0.0),
852
611
            mbForceToSingleDiscreteUnit(bForceToSingleDiscreteUnit)
853
611
        {
854
611
            if(!getFrameBorders().empty() && doForceToSingleDiscreteUnit())
855
611
            {
856
                // detect used minimal non-zero partial border width
857
611
                for(const auto& rCandidate : getFrameBorders())
858
2.48k
                {
859
2.48k
                    mfMinimalNonZeroBorderWidth = getMinimalNonZeroValue(
860
2.48k
                        mfMinimalNonZeroBorderWidth,
861
2.48k
                        rCandidate.getMinimalNonZeroBorderWidth());
862
2.48k
                }
863
611
            }
864
611
        }
865
866
        bool SdrFrameBorderPrimitive2D::operator==(const BasePrimitive2D& rPrimitive) const
867
0
        {
868
0
            if(BufferedDecompositionPrimitive2D::operator==(rPrimitive))
869
0
            {
870
0
                const SdrFrameBorderPrimitive2D& rCompare = static_cast<const SdrFrameBorderPrimitive2D&>(rPrimitive);
871
872
0
                return getFrameBorders() == rCompare.getFrameBorders()
873
0
                    && doForceToSingleDiscreteUnit() == rCompare.doForceToSingleDiscreteUnit();
874
0
            }
875
876
0
            return false;
877
0
        }
878
879
        void SdrFrameBorderPrimitive2D::get2DDecomposition(
880
            Primitive2DDecompositionVisitor& rVisitor,
881
            const geometry::ViewInformation2D& rViewInformation) const
882
611
        {
883
611
            if(doForceToSingleDiscreteUnit())
884
611
            {
885
                // Get the current DiscreteUnit, look at X and Y and use the maximum
886
611
                const basegfx::B2DVector aDiscreteVector(rViewInformation.getInverseObjectToViewTransformation() * basegfx::B2DVector(1.0, 1.0));
887
611
                double fDiscreteUnit(std::min(fabs(aDiscreteVector.getX()), fabs(aDiscreteVector.getY())));
888
889
611
                if(fDiscreteUnit <= mfMinimalNonZeroBorderWidth)
890
18
                {
891
                    // no need to use it, reset
892
18
                    fDiscreteUnit = 0.0;
893
18
                }
894
895
611
                if(fDiscreteUnit != mfMinimalNonZeroBorderWidthUsedForDecompose)
896
593
                {
897
                    // conditions of last local decomposition have changed, delete
898
                    // possible content
899
593
                    if(hasBuffered2DDecomposition())
900
0
                    {
901
0
                        const_cast< SdrFrameBorderPrimitive2D* >(this)->setBuffered2DDecomposition(nullptr);
902
0
                    }
903
904
                    // remember new conditions
905
593
                    const_cast< SdrFrameBorderPrimitive2D* >(this)->mfMinimalNonZeroBorderWidthUsedForDecompose = fDiscreteUnit;
906
593
                }
907
611
            }
908
909
            // call parent. This will call back ::create2DDecomposition above
910
            // where mfMinimalNonZeroBorderWidthUsedForDecompose will be used
911
            // when doForceToSingleDiscreteUnit() is true
912
611
            BufferedDecompositionPrimitive2D::get2DDecomposition(rVisitor, rViewInformation);
913
611
        }
914
915
        // provide unique ID
916
        sal_uInt32 SdrFrameBorderPrimitive2D::getPrimitive2DID() const
917
611
        {
918
611
            return PRIMITIVE2D_ID_SDRFRAMEBORDERTPRIMITIVE2D;
919
611
        }
920
921
} // end of namespace
922
923
/* vim:set shiftwidth=4 softtabstop=4 expandtab: */