Coverage Report

Created: 2026-10-14 09:33

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/libreoffice/svx/source/sdr/primitive2d/sdrframeborderprimitive2d.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 <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
122k
    {
34
122k
        if(0.0 != fValue)
35
40.9k
        {
36
40.9k
            fValue = std::max(fValue, fMinimalDiscreteUnit);
37
40.9k
        }
38
39
122k
        return fValue;
40
122k
    }
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
40.9k
                mfOffset(offset),
53
40.9k
                mfHalfWidth(halfWidth),
54
40.9k
                maColor(color)
55
40.9k
            {}
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
40.7k
        :   mfRefModeOffset(0.0),
72
40.7k
            maB2DVector(rB2DVector),
73
40.7k
            mfAngle(fAngle)
74
40.7k
        {
75
40.7k
            if (!rStyle.IsUsed())
76
0
                return;
77
78
40.7k
            svx::frame::RefMode aRefMode(rStyle.GetRefMode());
79
40.7k
            Color aPrim(rStyle.GetColorPrim());
80
40.7k
            Color aSecn(rStyle.GetColorSecn());
81
40.7k
            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
40.7k
            double fPrim(snapToDiscreteUnit(rStyle.Prim(), fMinimalDiscreteUnit));
87
40.7k
            const double fDist(snapToDiscreteUnit(rStyle.Dist(), fMinimalDiscreteUnit));
88
40.7k
            double fSecn(snapToDiscreteUnit(rStyle.Secn(), fMinimalDiscreteUnit));
89
90
            // Of course also do not use svx::frame::Style::GetWidth() for obvious
91
            // reasons.
92
40.7k
            const double fStyleWidth(fPrim + fDist + fSecn);
93
94
40.7k
            if(bMirrored)
95
10.9k
            {
96
10.9k
                switch(aRefMode)
97
10.9k
                {
98
2.31k
                    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
8.59k
                    default: break;
101
10.9k
                }
102
103
10.9k
                if(bSecnUsed)
104
29
                {
105
29
                    std::swap(aPrim, aSecn);
106
29
                    std::swap(fPrim, fSecn);
107
29
                }
108
10.9k
            }
109
110
40.7k
            if (svx::frame::RefMode::Centered != aRefMode)
111
4.98k
            {
112
4.98k
                const double fHalfWidth(fStyleWidth * 0.5);
113
114
4.98k
                if (svx::frame::RefMode::Begin == aRefMode)
115
2.66k
                {
116
                    // move aligned below vector
117
2.66k
                    mfRefModeOffset = fHalfWidth;
118
2.66k
                }
119
2.31k
                else if (svx::frame::RefMode::End == aRefMode)
120
2.31k
                {
121
                    // move aligned above vector
122
2.31k
                    mfRefModeOffset = -fHalfWidth;
123
2.31k
                }
124
4.98k
            }
125
126
40.7k
            if (bSecnUsed)
127
123
            {
128
                // both or all three lines used
129
123
                const bool bPrimTransparent(rStyle.GetColorPrim().IsFullyTransparent());
130
123
                const bool bDistTransparent(!rStyle.UseGapColor() || rStyle.GetColorGap().IsFullyTransparent());
131
123
                const bool bSecnTransparent(aSecn.IsFullyTransparent());
132
133
123
                if(!bPrimTransparent || !bDistTransparent || !bSecnTransparent)
134
123
                {
135
123
                    const double a(mfRefModeOffset - (fStyleWidth * 0.5));
136
123
                    const double b(a + fPrim);
137
123
                    const double c(b + fDist);
138
123
                    const double d(c + fSecn);
139
140
123
                    maOffsets.push_back(
141
123
                        OffsetAndHalfWidthAndColor(
142
123
                            (a + b) * 0.5,
143
123
                            fPrim * 0.5,
144
123
                            nullptr != pForceColor ? *pForceColor : aPrim));
145
146
123
                    maOffsets.push_back(
147
123
                        OffsetAndHalfWidthAndColor(
148
123
                            (b + c) * 0.5,
149
123
                            fDist * 0.5,
150
123
                            rStyle.UseGapColor()
151
123
                                ? (nullptr != pForceColor ? *pForceColor : rStyle.GetColorGap())
152
123
                                : COL_TRANSPARENT));
153
154
123
                    maOffsets.push_back(
155
123
                        OffsetAndHalfWidthAndColor(
156
123
                            (c + d) * 0.5,
157
123
                            fSecn * 0.5,
158
123
                            nullptr != pForceColor ? *pForceColor : aSecn));
159
123
                }
160
123
            }
161
40.6k
            else
162
40.6k
            {
163
                // one line used, push two values, from outer to inner
164
40.6k
                if(!rStyle.GetColorPrim().IsFullyTransparent())
165
40.5k
                {
166
40.5k
                    maOffsets.push_back(
167
40.5k
                        OffsetAndHalfWidthAndColor(
168
40.5k
                            mfRefModeOffset,
169
40.5k
                            fPrim * 0.5,
170
40.5k
                            nullptr != pForceColor ? *pForceColor : aPrim));
171
40.5k
                }
172
40.6k
            }
173
40.7k
        }
174
175
13.7k
        double getRefModeOffset() const { return mfRefModeOffset; }
176
55.5k
        const basegfx::B2DVector& getB2DVector() const { return maB2DVector; }
177
2.20k
        double getAngle() const { return mfAngle; }
178
31.1k
        bool empty() const { return maOffsets.empty(); }
179
66.9k
        size_t size() const { return maOffsets.size(); }
180
181
        void getColorAndOffsetAndHalfWidth(size_t nIndex, Color& rColor, double& rfOffset, double& rfHalfWidth) const
182
49.9k
        {
183
49.9k
            if(nIndex >= maOffsets.size())
184
0
                return;
185
49.9k
            const OffsetAndHalfWidthAndColor& rCandidate(maOffsets[nIndex]);
186
49.9k
            rfOffset = rCandidate.mfOffset;
187
49.9k
            rfHalfWidth = rCandidate.mfHalfWidth;
188
49.9k
            rColor = rCandidate.maColor;
189
49.9k
        }
190
    };
191
192
    class StyleVectorTable
193
    {
194
    private:
195
        std::vector< StyleVectorCombination >       maEntries;
196
197
    public:
198
        StyleVectorTable()
199
27.5k
        {
200
27.5k
        }
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
20.6k
        {
209
20.6k
            if(!rStyle.IsUsed() || basegfx::areParallel(rMyVector, rOtherVector))
210
2.21k
                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
18.4k
            const double fAngle(basegfx::B2DVector(-rMyVector.getX(), -rMyVector.getY()).angle(rOtherVector) + M_PI);
215
18.4k
            maEntries.emplace_back(
216
18.4k
                rStyle,
217
18.4k
                rOtherVector,
218
18.4k
                fAngle,
219
18.4k
                bMirrored,
220
18.4k
                nullptr,
221
18.4k
                fMinimalDiscreteUnit);
222
18.4k
        }
223
224
        void sort()
225
27.5k
        {
226
            // sort inverse from highest to lowest
227
27.5k
            std::sort(
228
27.5k
                maEntries.begin(),
229
27.5k
                maEntries.end(),
230
27.5k
                [](const StyleVectorCombination& a, const StyleVectorCombination& b)
231
27.5k
                    { return a.getAngle() > b.getAngle(); });
232
27.5k
        }
233
234
27.5k
        bool empty() const { return maEntries.empty(); }
235
17.3k
        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
18.5k
        CutSet() : mfOLML(0.0), mfORML(0.0), mfOLMR(0.0), mfORMR(0.0)
246
18.5k
        {
247
18.5k
        }
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
3.19k
        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
18.5k
    {
269
18.5k
        basegfx::utils::findCut(
270
18.5k
            rLeft,
271
18.5k
            rX,
272
18.5k
            rOtherLeft,
273
18.5k
            rOtherX,
274
18.5k
            CutFlagValue::LINE,
275
18.5k
            &rCutSet.mfOLML);
276
277
18.5k
        basegfx::utils::findCut(
278
18.5k
            rRight,
279
18.5k
            rX,
280
18.5k
            rOtherLeft,
281
18.5k
            rOtherX,
282
18.5k
            CutFlagValue::LINE,
283
18.5k
            &rCutSet.mfOLMR);
284
285
18.5k
        basegfx::utils::findCut(
286
18.5k
            rLeft,
287
18.5k
            rX,
288
18.5k
            rOtherRight,
289
18.5k
            rOtherX,
290
18.5k
            CutFlagValue::LINE,
291
18.5k
            &rCutSet.mfORML);
292
293
18.5k
        basegfx::utils::findCut(
294
18.5k
            rRight,
295
18.5k
            rX,
296
18.5k
            rOtherRight,
297
18.5k
            rOtherX,
298
18.5k
            CutFlagValue::LINE,
299
18.5k
            &rCutSet.mfORMR);
300
18.5k
    }
301
302
    struct ExtendSet
303
    {
304
        double          mfExtLeft;
305
        double          mfExtRight;
306
307
27.6k
        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
17.3k
    {
317
17.3k
        if(!(!rCombination.empty() && !rStyleVector.empty() && rCombination.size() == rExtendSet.size()))
318
0
            return;
319
320
17.3k
        const size_t nOffsetA(rCombination.size());
321
322
17.3k
        if(1 == nOffsetA)
323
17.3k
        {
324
17.3k
            Color aMyColor; double fMyOffset(0.0); double fMyHalfWidth(0.0);
325
17.3k
            rCombination.getColorAndOffsetAndHalfWidth(0, aMyColor, fMyOffset, fMyHalfWidth);
326
327
17.3k
            if(!aMyColor.IsFullyTransparent())
328
17.3k
            {
329
17.3k
                const basegfx::B2DPoint aLeft(rOrigin + (rPerpendX * (fMyOffset - fMyHalfWidth)));
330
17.3k
                const basegfx::B2DPoint aRight(rOrigin + (rPerpendX * (fMyOffset + fMyHalfWidth)));
331
17.3k
                std::vector< CutSet > aCutSets;
332
333
17.3k
                for(const auto& rStyleCandidate : rStyleVector)
334
18.3k
                {
335
18.3k
                    const basegfx::B2DVector aOtherPerpend(basegfx::getNormalizedPerpendicular(rStyleCandidate.getB2DVector()));
336
18.3k
                    const size_t nOffsetB(rStyleCandidate.size());
337
338
36.8k
                    for(size_t other(0); other < nOffsetB; other++)
339
18.4k
                    {
340
18.4k
                        Color aOtherColor; double fOtherOffset(0.0); double fOtherHalfWidth(0.0);
341
18.4k
                        rStyleCandidate.getColorAndOffsetAndHalfWidth(other, aOtherColor, fOtherOffset, fOtherHalfWidth);
342
343
18.4k
                        if(!aOtherColor.IsFullyTransparent())
344
18.4k
                        {
345
18.4k
                            const basegfx::B2DPoint aOtherLeft(rOrigin + (aOtherPerpend * (fOtherOffset - fOtherHalfWidth)));
346
18.4k
                            const basegfx::B2DPoint aOtherRight(rOrigin + (aOtherPerpend * (fOtherOffset + fOtherHalfWidth)));
347
348
18.4k
                            CutSet aNewCutSet;
349
18.4k
                            getCutSet(aNewCutSet, aLeft, aRight, rCombination.getB2DVector(), aOtherLeft, aOtherRight, rStyleCandidate.getB2DVector());
350
18.4k
                            aCutSets.push_back(aNewCutSet);
351
18.4k
                        }
352
18.4k
                    }
353
18.3k
                }
354
355
17.3k
                if(!aCutSets.empty())
356
17.2k
                {
357
17.2k
                    CutSet aCutSet(aCutSets[0]);
358
17.2k
                    const size_t nNumCutSets(aCutSets.size());
359
360
17.2k
                    if(1 != nNumCutSets)
361
1.10k
                    {
362
1.10k
                        double fCutSet(aCutSet.getSum());
363
364
2.20k
                        for(size_t a(1); a < nNumCutSets; a++)
365
1.10k
                        {
366
1.10k
                            const CutSet& rCandidate(aCutSets[a]);
367
1.10k
                            const double fCandidate(rCandidate.getSum());
368
369
1.10k
                            if(basegfx::fTools::equalZero(fCandidate - fCutSet))
370
988
                            {
371
                                // both have equal center point, use medium cut
372
988
                                const double fNewOLML(std::max(std::min(rCandidate.mfOLML, rCandidate.mfORML), std::min(aCutSet.mfOLML, aCutSet.mfORML)));
373
988
                                const double fNewORML(std::min(std::max(rCandidate.mfOLML, rCandidate.mfORML), std::max(aCutSet.mfOLML, aCutSet.mfORML)));
374
988
                                const double fNewOLMR(std::max(std::min(rCandidate.mfOLMR, rCandidate.mfORMR), std::min(aCutSet.mfOLMR, aCutSet.mfORMR)));
375
988
                                const double fNewORMR(std::min(std::max(rCandidate.mfOLMR, rCandidate.mfORMR), std::max(aCutSet.mfOLMR, aCutSet.mfORMR)));
376
988
                                aCutSet.mfOLML = fNewOLML;
377
988
                                aCutSet.mfORML = fNewORML;
378
988
                                aCutSet.mfOLMR = fNewOLMR;
379
988
                                aCutSet.mfORMR = fNewORMR;
380
988
                                fCutSet = aCutSet.getSum();
381
988
                            }
382
114
                            else if(fCandidate < fCutSet)
383
69
                            {
384
                                // get minimum
385
69
                                fCutSet = fCandidate;
386
69
                                aCutSet = rCandidate;
387
69
                            }
388
1.10k
                        }
389
1.10k
                    }
390
391
17.2k
                    ExtendSet& rExt(rExtendSet[0]);
392
393
17.2k
                    rExt.mfExtLeft = std::min(aCutSet.mfOLML, aCutSet.mfORML);
394
17.2k
                    rExt.mfExtRight = std::min(aCutSet.mfOLMR, aCutSet.mfORMR);
395
17.2k
                }
396
17.3k
            }
397
17.3k
        }
398
59
        else
399
59
        {
400
59
            size_t nVisEdgeUp(0);
401
59
            size_t nVisEdgeDn(0);
402
403
236
            for(size_t my(0); my < nOffsetA; my++)
404
177
            {
405
177
                Color aMyColor; double fMyOffset(0.0); double fMyHalfWidth(0.0);
406
177
                rCombination.getColorAndOffsetAndHalfWidth(my, aMyColor, fMyOffset, fMyHalfWidth);
407
408
177
                if(!aMyColor.IsFullyTransparent())
409
118
                {
410
118
                    const basegfx::B2DPoint aLeft(rOrigin + (rPerpendX * (fMyOffset - fMyHalfWidth)));
411
118
                    const basegfx::B2DPoint aRight(rOrigin + (rPerpendX * (fMyOffset + fMyHalfWidth)));
412
118
                    const bool bUpper(my <= (nOffsetA >> 1));
413
118
                    const StyleVectorCombination& rStyleCandidate(bUpper ? rStyleVector.front() : rStyleVector.back());
414
118
                    const basegfx::B2DVector aOtherPerpend(basegfx::getNormalizedPerpendicular(rStyleCandidate.getB2DVector()));
415
118
                    const size_t nOffsetB(rStyleCandidate.size());
416
118
                    std::vector< CutSet > aCutSets;
417
418
236
                    for(size_t other(0); other < nOffsetB; other++)
419
118
                    {
420
118
                        Color aOtherColor; double fOtherOffset(0.0); double fOtherHalfWidth(0.0);
421
118
                        rStyleCandidate.getColorAndOffsetAndHalfWidth(other, aOtherColor, fOtherOffset, fOtherHalfWidth);
422
423
118
                        if(!aOtherColor.IsFullyTransparent())
424
118
                        {
425
118
                            const basegfx::B2DPoint aOtherLeft(rOrigin + (aOtherPerpend * (fOtherOffset - fOtherHalfWidth)));
426
118
                            const basegfx::B2DPoint aOtherRight(rOrigin + (aOtherPerpend * (fOtherOffset + fOtherHalfWidth)));
427
118
                            CutSet aCutSet;
428
118
                            getCutSet(aCutSet, aLeft, aRight, rCombination.getB2DVector(), aOtherLeft, aOtherRight, rStyleCandidate.getB2DVector());
429
118
                            aCutSets.push_back(aCutSet);
430
118
                        }
431
118
                    }
432
433
118
                    if(!aCutSets.empty())
434
118
                    {
435
                        // sort: min to start, max to end
436
118
                        std::sort(aCutSets.begin(), aCutSets.end());
437
118
                        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
118
                        const bool bMax(bUpper == bOtherUpper);
446
118
                        size_t nBaseIndex(0);
447
118
                        const size_t nNumCutSets(aCutSets.size());
448
449
118
                        if(bMax)
450
59
                        {
451
                            // access at end
452
59
                            nBaseIndex = nNumCutSets - 1 - (bUpper ? nVisEdgeUp : nVisEdgeDn);
453
59
                        }
454
59
                        else
455
59
                        {
456
                            // access at start
457
59
                            nBaseIndex = bUpper ? nVisEdgeUp : nVisEdgeDn;
458
59
                        }
459
460
118
                        const size_t nSecuredIndex(std::clamp(nBaseIndex, size_t(0), size_t(nNumCutSets - 1)));
461
118
                        const CutSet& rCutSet(aCutSets[nSecuredIndex]);
462
118
                        ExtendSet& rExt(rExtendSet[my]);
463
464
118
                        rExt.mfExtLeft = std::min(rCutSet.mfOLML, rCutSet.mfORML);
465
118
                        rExt.mfExtRight = std::min(rCutSet.mfOLMR, rCutSet.mfORMR);
466
118
                    }
467
468
118
                    if(bUpper)
469
59
                    {
470
59
                        nVisEdgeUp++;
471
59
                    }
472
59
                    else
473
59
                    {
474
59
                        nVisEdgeDn++;
475
59
                    }
476
118
                }
477
177
            }
478
59
        }
479
17.3k
    }
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
13.7k
    {
526
        // get offset color pairs for  style, one per visible line
527
13.7k
        const StyleVectorCombination aCombination(
528
13.7k
            rBorder,
529
13.7k
            rX,
530
13.7k
            0.0,
531
13.7k
            false,
532
13.7k
            pForceColor,
533
13.7k
            fMinimalDiscreteUnit);
534
535
13.7k
        if(aCombination.empty())
536
9
            return;
537
538
13.7k
        const basegfx::B2DVector aPerpendX(basegfx::getNormalizedPerpendicular(rX));
539
13.7k
        const bool bHasStartStyles(!rStartStyleVectorTable.empty());
540
13.7k
        const bool bHasEndStyles(!rEndStyleVectorTable.empty());
541
13.7k
        const size_t nOffsets(aCombination.size());
542
13.7k
        std::vector<ExtendSet> aExtendSetStart(nOffsets);
543
13.7k
        std::vector<ExtendSet> aExtendSetEnd(nOffsets);
544
545
13.7k
        if(bHasStartStyles)
546
8.83k
        {
547
            // create extends for line starts, use given point/vector and offsets
548
8.83k
            getExtends(aExtendSetStart, rOrigin, aCombination, aPerpendX, rStartStyleVectorTable.getEntries());
549
8.83k
        }
550
551
13.7k
        if(bHasEndStyles)
552
8.53k
        {
553
            // Create extends for line ends, create inverse point/vector and inverse offsets.
554
8.53k
            const StyleVectorCombination aMirroredCombination(
555
8.53k
                rBorder,
556
8.53k
                -rX,
557
8.53k
                0.0,
558
8.53k
                true,
559
8.53k
                pForceColor,
560
8.53k
                fMinimalDiscreteUnit);
561
562
8.53k
            getExtends(aExtendSetEnd, rOrigin + rX, aMirroredCombination, -aPerpendX, rEndStyleVectorTable.getEntries());
563
564
            // also need to inverse the result to apply to the correct lines
565
8.53k
            std::reverse(aExtendSetEnd.begin(), aExtendSetEnd.end());
566
8.53k
        }
567
568
13.7k
        std::vector< drawinglayer::primitive2d::BorderLine > aBorderlines;
569
13.7k
        const double fNegLength(-rX.getLength());
570
571
27.6k
        for(size_t a(0); a < nOffsets; a++)
572
13.8k
        {
573
13.8k
            Color aMyColor;
574
13.8k
            double fMyOffset(0.0);
575
13.8k
            double fMyHalfWidth(0.0);
576
13.8k
            aCombination.getColorAndOffsetAndHalfWidth(a, aMyColor, fMyOffset, fMyHalfWidth);
577
13.8k
            const ExtendSet& rExtStart(aExtendSetStart[a]);
578
13.8k
            const ExtendSet& rExtEnd(aExtendSetEnd[a]);
579
580
13.8k
            if(aMyColor.IsFullyTransparent())
581
35
            {
582
35
                aBorderlines.push_back(
583
35
                    drawinglayer::primitive2d::BorderLine(
584
35
                        fMyHalfWidth * 2.0));
585
35
            }
586
13.8k
            else
587
13.8k
            {
588
13.8k
                aBorderlines.push_back(
589
13.8k
                    drawinglayer::primitive2d::BorderLine(
590
13.8k
                        drawinglayer::attribute::LineAttribute(
591
13.8k
                            aMyColor.getBColor(),
592
13.8k
                            fMyHalfWidth * 2.0),
593
13.8k
                        fNegLength * rExtStart.mfExtLeft,
594
13.8k
                        fNegLength * rExtStart.mfExtRight,
595
13.8k
                        fNegLength * rExtEnd.mfExtRight,
596
13.8k
                        fNegLength * rExtEnd.mfExtLeft));
597
13.8k
            }
598
13.8k
        }
599
600
13.7k
        static const double fPatScFact(10.0); // 10.0 multiply, see old code
601
13.7k
        std::vector<double> aDashing(svtools::GetLineDashing(rBorder.Type(), rBorder.PatternScale() * fPatScFact));
602
13.7k
        drawinglayer::attribute::StrokeAttribute aStrokeAttribute(std::move(aDashing));
603
13.7k
        const basegfx::B2DPoint aStart(rOrigin + (aPerpendX * aCombination.getRefModeOffset()));
604
605
13.7k
        rTarget.append(
606
13.7k
                new drawinglayer::primitive2d::BorderLinePrimitive2D(
607
13.7k
                    aStart,
608
13.7k
                    aStart + rX,
609
13.7k
                    std::move(aBorderlines),
610
13.7k
                    std::move(aStrokeAttribute)));
611
13.7k
    }
612
613
    double getMinimalNonZeroValue(double fCurrent, double fNew)
614
117k
    {
615
117k
        if(0.0 != fNew)
616
48.3k
        {
617
48.3k
            if(0.0 != fCurrent)
618
29.1k
            {
619
29.1k
                fCurrent = std::min(fNew, fCurrent);
620
29.1k
            }
621
19.2k
            else
622
19.2k
            {
623
19.2k
                fCurrent = fNew;
624
19.2k
            }
625
48.3k
        }
626
627
117k
        return fCurrent;
628
117k
    }
629
630
    double getMinimalNonZeroBorderWidthFromStyle(double fCurrent, const svx::frame::Style& rStyle)
631
34.4k
    {
632
34.4k
        if(rStyle.IsUsed())
633
34.4k
        {
634
34.4k
            fCurrent = getMinimalNonZeroValue(fCurrent, rStyle.Prim());
635
34.4k
            fCurrent = getMinimalNonZeroValue(fCurrent, rStyle.Dist());
636
34.4k
            fCurrent = getMinimalNonZeroValue(fCurrent, rStyle.Secn());
637
34.4k
        }
638
639
34.4k
        return fCurrent;
640
34.4k
    }
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
20.6k
        :   maStyle(rStyle),
650
20.6k
            maNormalizedPerpendicular(rNormalizedPerpendicular),
651
20.6k
            mbStyleMirrored(bStyleMirrored)
652
20.6k
        {
653
20.6k
        }
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
13.7k
        :   maOrigin(rOrigin),
668
13.7k
            maX(rX),
669
13.7k
            maStyle(rStyle),
670
13.7k
            maColor(nullptr != pForceColor ? *pForceColor : Color()),
671
13.7k
            mbForceColor(nullptr != pForceColor)
672
13.7k
        {
673
13.7k
        }
674
675
        void SdrFrameBorderData::addSdrConnectStyleData(
676
            bool bStart,
677
            const svx::frame::Style& rStyle,
678
            const basegfx::B2DVector& rNormalizedPerpendicular,
679
            bool bStyleMirrored)
680
28.6k
        {
681
28.6k
            if(rStyle.IsUsed())
682
20.6k
            {
683
20.6k
                if(bStart)
684
10.4k
                {
685
10.4k
                    maStart.emplace_back(rStyle, rNormalizedPerpendicular, bStyleMirrored);
686
10.4k
                }
687
10.1k
                else
688
10.1k
                {
689
10.1k
                    maEnd.emplace_back(rStyle, rNormalizedPerpendicular, bStyleMirrored);
690
10.1k
                }
691
20.6k
            }
692
28.6k
        }
693
694
        void SdrFrameBorderData::create2DDecomposition(
695
            Primitive2DContainer& rContainer,
696
            double fMinimalDiscreteUnit) const
697
13.7k
        {
698
13.7k
            StyleVectorTable aStartVector;
699
13.7k
            StyleVectorTable aEndVector;
700
13.7k
            const basegfx::B2DVector aAxis(-maX);
701
702
13.7k
            for(const auto& rStart : maStart)
703
10.4k
            {
704
10.4k
                aStartVector.add(
705
10.4k
                    rStart.getStyle(),
706
10.4k
                    maX,
707
10.4k
                    rStart.getNormalizedPerpendicular(),
708
10.4k
                    rStart.getStyleMirrored(),
709
10.4k
                    fMinimalDiscreteUnit);
710
10.4k
            }
711
712
13.7k
            for(const auto& rEnd : maEnd)
713
10.1k
            {
714
10.1k
                aEndVector.add(
715
10.1k
                    rEnd.getStyle(),
716
10.1k
                    aAxis,
717
10.1k
                    rEnd.getNormalizedPerpendicular(),
718
10.1k
                    rEnd.getStyleMirrored(),
719
10.1k
                    fMinimalDiscreteUnit);
720
10.1k
            }
721
722
13.7k
            aStartVector.sort();
723
13.7k
            aEndVector.sort();
724
725
13.7k
            CreateBorderPrimitives(
726
13.7k
                rContainer,
727
13.7k
                maOrigin,
728
13.7k
                maX,
729
13.7k
                maStyle,
730
13.7k
                aStartVector,
731
13.7k
                aEndVector,
732
13.7k
                mbForceColor ? &maColor : nullptr,
733
13.7k
                fMinimalDiscreteUnit);
734
13.7k
        }
735
736
        double SdrFrameBorderData::getMinimalNonZeroBorderWidth() const
737
13.7k
        {
738
13.7k
            double fRetval(getMinimalNonZeroBorderWidthFromStyle(0.0, maStyle));
739
740
13.7k
            for(const auto& rStart : maStart)
741
10.4k
            {
742
10.4k
                fRetval = getMinimalNonZeroBorderWidthFromStyle(fRetval, rStart.getStyle());
743
10.4k
            }
744
745
13.7k
            for(const auto& rEnd : maEnd)
746
10.1k
            {
747
10.1k
                fRetval = getMinimalNonZeroBorderWidthFromStyle(fRetval, rEnd.getStyle());
748
10.1k
            }
749
750
13.7k
            return fRetval;
751
13.7k
        }
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
5.42k
        {
769
5.42k
            if(getFrameBorders().empty())
770
0
            {
771
0
                return nullptr;
772
0
            }
773
774
5.42k
            Primitive2DContainer aRetval;
775
776
            // Check and use the minimal non-zero BorderWidth for decompose
777
            // if that is set and wanted
778
5.42k
            const double fMinimalDiscreteUnit(doForceToSingleDiscreteUnit()
779
5.42k
                ? mfMinimalNonZeroBorderWidthUsedForDecompose
780
5.42k
                : 0.0);
781
782
            // decompose all buffered SdrFrameBorderData entries and try to merge them
783
            // to reduce existing number of BorderLinePrimitive2D(s)
784
5.42k
            for(const auto& rCandidate : getFrameBorders())
785
13.7k
            {
786
                // get decomposition on one SdrFrameBorderData entry
787
13.7k
                Primitive2DContainer aPartial;
788
13.7k
                rCandidate.create2DDecomposition(
789
13.7k
                    aPartial,
790
13.7k
                    fMinimalDiscreteUnit);
791
792
13.7k
                for(const auto& aCandidatePartial : aPartial)
793
13.7k
                {
794
13.7k
                    if(aRetval.empty())
795
5.42k
                    {
796
                        // no local data yet, just add as 1st entry, done
797
5.42k
                        aRetval.append(aCandidatePartial);
798
5.42k
                    }
799
8.34k
                    else
800
8.34k
                    {
801
8.34k
                        bool bDidMerge(false);
802
803
8.34k
                        for(auto& aCandidateRetval : aRetval)
804
16.5k
                        {
805
                            // try to merge by appending new data to existing data
806
16.5k
                            const drawinglayer::primitive2d::Primitive2DReference aMergeRetvalPartial(
807
16.5k
                                drawinglayer::primitive2d::tryMergeBorderLinePrimitive2D(
808
16.5k
                                    static_cast<BorderLinePrimitive2D*>(aCandidateRetval.get()),
809
16.5k
                                    static_cast<BorderLinePrimitive2D*>(aCandidatePartial.get())));
810
811
16.5k
                            if(aMergeRetvalPartial.is())
812
1.02k
                            {
813
                                // could append, replace existing data with merged data, done
814
1.02k
                                aCandidateRetval = aMergeRetvalPartial;
815
1.02k
                                bDidMerge = true;
816
1.02k
                                break;
817
1.02k
                            }
818
819
                            // try to merge by appending existing data to new data
820
15.5k
                            const drawinglayer::primitive2d::Primitive2DReference aMergePartialRetval(
821
15.5k
                                drawinglayer::primitive2d::tryMergeBorderLinePrimitive2D(
822
15.5k
                                    static_cast<BorderLinePrimitive2D*>(aCandidatePartial.get()),
823
15.5k
                                    static_cast<BorderLinePrimitive2D*>(aCandidateRetval.get())));
824
825
15.5k
                            if(aMergePartialRetval.is())
826
1
                            {
827
                                // could append, replace existing data with merged data, done
828
1
                                aCandidateRetval = aMergePartialRetval;
829
1
                                bDidMerge = true;
830
1
                                break;
831
1
                            }
832
15.5k
                        }
833
834
8.34k
                        if(!bDidMerge)
835
7.31k
                        {
836
                            // no merge after checking all existing data, append as new segment
837
7.31k
                            aRetval.append(aCandidatePartial);
838
7.31k
                        }
839
8.34k
                    }
840
13.7k
                }
841
13.7k
            }
842
843
5.42k
            return new GroupPrimitive2D(std::move(aRetval));
844
5.42k
        }
845
846
        SdrFrameBorderPrimitive2D::SdrFrameBorderPrimitive2D(
847
            SdrFrameBorderDataVector&& rFrameBorders,
848
            bool bForceToSingleDiscreteUnit)
849
5.42k
        :   maFrameBorders(std::move(rFrameBorders)),
850
5.42k
            mfMinimalNonZeroBorderWidth(0.0),
851
5.42k
            mfMinimalNonZeroBorderWidthUsedForDecompose(0.0),
852
5.42k
            mbForceToSingleDiscreteUnit(bForceToSingleDiscreteUnit)
853
5.42k
        {
854
5.42k
            if(!getFrameBorders().empty() && doForceToSingleDiscreteUnit())
855
5.42k
            {
856
                // detect used minimal non-zero partial border width
857
5.42k
                for(const auto& rCandidate : getFrameBorders())
858
13.7k
                {
859
13.7k
                    mfMinimalNonZeroBorderWidth = getMinimalNonZeroValue(
860
13.7k
                        mfMinimalNonZeroBorderWidth,
861
13.7k
                        rCandidate.getMinimalNonZeroBorderWidth());
862
13.7k
                }
863
5.42k
            }
864
5.42k
        }
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
5.42k
        {
883
5.42k
            if(doForceToSingleDiscreteUnit())
884
5.42k
            {
885
                // Get the current DiscreteUnit, look at X and Y and use the maximum
886
5.42k
                const basegfx::B2DVector aDiscreteVector(rViewInformation.getInverseObjectToViewTransformation() * basegfx::B2DVector(1.0, 1.0));
887
5.42k
                double fDiscreteUnit(std::min(fabs(aDiscreteVector.getX()), fabs(aDiscreteVector.getY())));
888
889
5.42k
                if(fDiscreteUnit <= mfMinimalNonZeroBorderWidth)
890
1.39k
                {
891
                    // no need to use it, reset
892
1.39k
                    fDiscreteUnit = 0.0;
893
1.39k
                }
894
895
5.42k
                if(fDiscreteUnit != mfMinimalNonZeroBorderWidthUsedForDecompose)
896
4.03k
                {
897
                    // conditions of last local decomposition have changed, delete
898
                    // possible content
899
4.03k
                    if(hasBuffered2DDecomposition())
900
0
                    {
901
0
                        const_cast< SdrFrameBorderPrimitive2D* >(this)->setBuffered2DDecomposition(nullptr);
902
0
                    }
903
904
                    // remember new conditions
905
4.03k
                    const_cast< SdrFrameBorderPrimitive2D* >(this)->mfMinimalNonZeroBorderWidthUsedForDecompose = fDiscreteUnit;
906
4.03k
                }
907
5.42k
            }
908
909
            // call parent. This will call back ::create2DDecomposition above
910
            // where mfMinimalNonZeroBorderWidthUsedForDecompose will be used
911
            // when doForceToSingleDiscreteUnit() is true
912
5.42k
            BufferedDecompositionPrimitive2D::get2DDecomposition(rVisitor, rViewInformation);
913
5.42k
        }
914
915
        // provide unique ID
916
        sal_uInt32 SdrFrameBorderPrimitive2D::getPrimitive2DID() const
917
5.42k
        {
918
5.42k
            return PRIMITIVE2D_ID_SDRFRAMEBORDERTPRIMITIVE2D;
919
5.42k
        }
920
921
} // end of namespace
922
923
/* vim:set shiftwidth=4 softtabstop=4 expandtab: */