Coverage Report

Created: 2026-09-28 10:59

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