Coverage Report

Created: 2026-03-31 11:00

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/libreoffice/drawinglayer/source/primitive2d/borderlineprimitive2d.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 <drawinglayer/geometry/viewinformation2d.hxx>
21
#include <drawinglayer/primitive2d/borderlineprimitive2d.hxx>
22
#include <drawinglayer/primitive2d/drawinglayer_primitivetypes2d.hxx>
23
#include <basegfx/polygon/b2dpolygon.hxx>
24
#include <drawinglayer/primitive2d/PolyPolygonColorPrimitive2D.hxx>
25
#include <drawinglayer/primitive2d/PolygonStrokePrimitive2D.hxx>
26
#include <drawinglayer/primitive2d/groupprimitive2d.hxx>
27
#include <rtl/math.hxx>
28
29
#include <algorithm>
30
#include <utility>
31
32
33
namespace drawinglayer::primitive2d
34
{
35
        BorderLine::BorderLine(
36
            const drawinglayer::attribute::LineAttribute& rLineAttribute,
37
            double fStartLeft,
38
            double fStartRight,
39
            double fEndLeft,
40
            double fEndRight)
41
11.2k
        :   maLineAttribute(rLineAttribute),
42
11.2k
            mfStartLeft(fStartLeft),
43
11.2k
            mfStartRight(fStartRight),
44
11.2k
            mfEndLeft(fEndLeft),
45
11.2k
            mfEndRight(fEndRight),
46
11.2k
            mbIsGap(false)
47
11.2k
        {
48
11.2k
        }
49
50
        BorderLine::BorderLine(
51
            double fWidth)
52
3
        :   maLineAttribute(basegfx::BColor(), fWidth),
53
3
            mfStartLeft(0.0),
54
3
            mfStartRight(0.0),
55
3
            mfEndLeft(0.0),
56
3
            mfEndRight(0.0),
57
3
            mbIsGap(true)
58
3
        {
59
3
        }
60
61
        BorderLine::~BorderLine()
62
22.5k
        {
63
22.5k
        }
64
65
        bool BorderLine::operator==(const BorderLine& rBorderLine) const
66
0
        {
67
0
            return getLineAttribute() == rBorderLine.getLineAttribute()
68
0
                && getStartLeft() == rBorderLine.getStartLeft()
69
0
                && getStartRight() == rBorderLine.getStartRight()
70
0
                && getEndLeft() == rBorderLine.getEndLeft()
71
0
                && getEndRight() == rBorderLine.getEndRight()
72
0
                && isGap() == rBorderLine.isGap();
73
0
        }
74
75
        // helper to add a centered, maybe stroked line primitive to rContainer
76
        static void addPolygonStrokePrimitive2D(
77
            Primitive2DContainer& rContainer,
78
            const basegfx::B2DPoint& rStart,
79
            const basegfx::B2DPoint& rEnd,
80
            const attribute::LineAttribute& rLineAttribute,
81
            const attribute::StrokeAttribute& rStrokeAttribute)
82
10.9k
        {
83
10.9k
            basegfx::B2DPolygon aPolygon;
84
85
10.9k
            aPolygon.append(rStart);
86
10.9k
            aPolygon.append(rEnd);
87
88
10.9k
            if (rStrokeAttribute.isDefault())
89
0
            {
90
0
                rContainer.push_back(
91
0
                    new PolygonStrokePrimitive2D(
92
0
                        std::move(aPolygon),
93
0
                        rLineAttribute));
94
0
            }
95
10.9k
            else
96
10.9k
            {
97
10.9k
                rContainer.push_back(
98
10.9k
                    new PolygonStrokePrimitive2D(
99
10.9k
                        std::move(aPolygon),
100
10.9k
                        rLineAttribute,
101
10.9k
                        rStrokeAttribute));
102
10.9k
            }
103
10.9k
        }
104
105
        double BorderLinePrimitive2D::getFullWidth() const
106
10.9k
        {
107
10.9k
            double fRetval(0.0);
108
109
10.9k
            for(const auto& candidate : maBorderLines)
110
10.9k
            {
111
10.9k
                fRetval += candidate.getLineAttribute().getWidth();
112
10.9k
            }
113
114
10.9k
            return fRetval;
115
10.9k
        }
116
117
        Primitive2DReference BorderLinePrimitive2D::create2DDecomposition(const geometry::ViewInformation2D& /*rViewInformation*/) const
118
10.9k
        {
119
10.9k
            if (getStart().equal(getEnd()) || getBorderLines().empty())
120
0
                return nullptr;
121
122
            // get data and vectors
123
10.9k
            basegfx::B2DVector aVector(getEnd() - getStart());
124
10.9k
            aVector.normalize();
125
10.9k
            const basegfx::B2DVector aPerpendicular(basegfx::getPerpendicular(aVector));
126
10.9k
            const double fFullWidth(getFullWidth());
127
10.9k
            double fOffset(fFullWidth * -0.5);
128
129
10.9k
            Primitive2DContainer aContainer;
130
10.9k
            for(const auto& candidate : maBorderLines)
131
10.9k
            {
132
10.9k
                const double fWidth(candidate.getLineAttribute().getWidth());
133
134
10.9k
                if(!candidate.isGap())
135
10.9k
                {
136
10.9k
                    const basegfx::B2DVector aDeltaY(aPerpendicular * (fOffset + (fWidth * 0.5)));
137
10.9k
                    const basegfx::B2DPoint aStart(getStart() + aDeltaY);
138
10.9k
                    const basegfx::B2DPoint aEnd(getEnd() + aDeltaY);
139
10.9k
                    const bool bStartPerpendicular(rtl::math::approxEqual(candidate.getStartLeft(), candidate.getStartRight()));
140
10.9k
                    const bool bEndPerpendicular(rtl::math::approxEqual(candidate.getEndLeft(), candidate.getEndRight()));
141
142
10.9k
                    if(bStartPerpendicular && bEndPerpendicular)
143
10.9k
                    {
144
                        // start and end extends lead to an edge perpendicular to the line, so we can just use
145
                        // a PolygonStrokePrimitive2D for representation
146
10.9k
                        addPolygonStrokePrimitive2D(
147
10.9k
                            aContainer,
148
10.9k
                            aStart - (aVector * candidate.getStartLeft()),
149
10.9k
                            aEnd + (aVector * candidate.getEndLeft()),
150
10.9k
                            candidate.getLineAttribute(),
151
10.9k
                            getStrokeAttribute());
152
10.9k
                    }
153
0
                    else
154
0
                    {
155
                        // start and/or end extensions lead to a lineStart/End that is *not*
156
                        // perpendicular to the line itself
157
0
                        if(getStrokeAttribute().isDefault() || 0.0 == getStrokeAttribute().getFullDotDashLen())
158
0
                        {
159
                            // without stroke, we can simply represent that using a filled polygon
160
0
                            const basegfx::B2DVector aHalfLineOffset(aPerpendicular * (candidate.getLineAttribute().getWidth() * 0.5));
161
0
                            basegfx::B2DPolygon aPolygon;
162
163
0
                            aPolygon.append(aStart - aHalfLineOffset - (aVector * candidate.getStartLeft()));
164
0
                            aPolygon.append(aEnd - aHalfLineOffset + (aVector * candidate.getEndLeft()));
165
0
                            aPolygon.append(aEnd + aHalfLineOffset + (aVector * candidate.getEndRight()));
166
0
                            aPolygon.append(aStart + aHalfLineOffset - (aVector * candidate.getStartRight()));
167
168
0
                            aContainer.push_back(
169
0
                                new PolyPolygonColorPrimitive2D(
170
0
                                    basegfx::B2DPolyPolygon(aPolygon),
171
0
                                    candidate.getLineAttribute().getColor()));
172
0
                        }
173
0
                        else
174
0
                        {
175
                            // with stroke, we have a problem - a filled polygon would lose the
176
                            // stroke. Let's represent the start and/or end as triangles, the main
177
                            // line still as PolygonStrokePrimitive2D.
178
                            // Fill default line Start/End for stroke, so we need no adaptations in else paths
179
0
                            basegfx::B2DPoint aStrokeStart(aStart - (aVector * candidate.getStartLeft()));
180
0
                            basegfx::B2DPoint aStrokeEnd(aEnd + (aVector * candidate.getEndLeft()));
181
0
                            const basegfx::B2DVector aHalfLineOffset(aPerpendicular * (candidate.getLineAttribute().getWidth() * 0.5));
182
183
0
                            if(!bStartPerpendicular)
184
0
                            {
185
0
                                const double fMin(std::min(candidate.getStartLeft(), candidate.getStartRight()));
186
0
                                const double fMax(std::max(candidate.getStartLeft(), candidate.getStartRight()));
187
0
                                basegfx::B2DPolygon aPolygon;
188
189
                                // create a triangle with min/max values for LineStart and add
190
0
                                if(rtl::math::approxEqual(candidate.getStartLeft(), fMax))
191
0
                                {
192
0
                                    aPolygon.append(aStart - aHalfLineOffset - (aVector * candidate.getStartLeft()));
193
0
                                }
194
195
0
                                aPolygon.append(aStart - aHalfLineOffset - (aVector * fMin));
196
0
                                aPolygon.append(aStart + aHalfLineOffset - (aVector * fMin));
197
198
0
                                if(rtl::math::approxEqual(candidate.getStartRight(), fMax))
199
0
                                {
200
0
                                    aPolygon.append(aStart + aHalfLineOffset - (aVector * candidate.getStartRight()));
201
0
                                }
202
203
0
                                aContainer.push_back(
204
0
                                    new PolyPolygonColorPrimitive2D(
205
0
                                        basegfx::B2DPolyPolygon(aPolygon),
206
0
                                        candidate.getLineAttribute().getColor()));
207
208
                                // Adapt StrokeStart accordingly
209
0
                                aStrokeStart = aStart - (aVector * fMin);
210
0
                            }
211
212
0
                            if(!bEndPerpendicular)
213
0
                            {
214
0
                                const double fMin(std::min(candidate.getEndLeft(), candidate.getEndRight()));
215
0
                                const double fMax(std::max(candidate.getEndLeft(), candidate.getEndRight()));
216
0
                                basegfx::B2DPolygon aPolygon;
217
218
                                // create a triangle with min/max values for LineEnd and add
219
0
                                if(rtl::math::approxEqual(candidate.getEndLeft(), fMax))
220
0
                                {
221
0
                                    aPolygon.append(aEnd - aHalfLineOffset + (aVector * candidate.getEndLeft()));
222
0
                                }
223
224
0
                                if(rtl::math::approxEqual(candidate.getEndRight(), fMax))
225
0
                                {
226
0
                                    aPolygon.append(aEnd + aHalfLineOffset + (aVector * candidate.getEndRight()));
227
0
                                }
228
229
0
                                aPolygon.append(aEnd + aHalfLineOffset + (aVector * fMin));
230
0
                                aPolygon.append(aEnd - aHalfLineOffset + (aVector * fMin));
231
232
0
                                aContainer.push_back(
233
0
                                    new PolyPolygonColorPrimitive2D(
234
0
                                        basegfx::B2DPolyPolygon(aPolygon),
235
0
                                        candidate.getLineAttribute().getColor()));
236
237
                                // Adapt StrokeEnd accordingly
238
0
                                aStrokeEnd = aEnd + (aVector * fMin);
239
0
                            }
240
241
0
                            addPolygonStrokePrimitive2D(
242
0
                                aContainer,
243
0
                                aStrokeStart,
244
0
                                aStrokeEnd,
245
0
                                candidate.getLineAttribute(),
246
0
                                getStrokeAttribute());
247
0
                        }
248
0
                    }
249
10.9k
                }
250
251
10.9k
                fOffset += fWidth;
252
10.9k
            }
253
10.9k
            return new GroupPrimitive2D(std::move(aContainer));
254
10.9k
        }
255
256
        bool BorderLinePrimitive2D::isHorizontalOrVertical(const geometry::ViewInformation2D& rViewInformation) const
257
0
        {
258
0
            if (!getStart().equal(getEnd()))
259
0
            {
260
0
                const basegfx::B2DHomMatrix& rOTVT = rViewInformation.getObjectToViewTransformation();
261
0
                const basegfx::B2DVector aVector(rOTVT * getEnd() - rOTVT * getStart());
262
263
0
                return basegfx::fTools::equalZero(aVector.getX()) || basegfx::fTools::equalZero(aVector.getY());
264
0
            }
265
266
0
            return false;
267
0
        }
268
269
        BorderLinePrimitive2D::BorderLinePrimitive2D(
270
            const basegfx::B2DPoint& rStart,
271
            const basegfx::B2DPoint& rEnd,
272
            std::vector< BorderLine >&& rBorderLines,
273
            drawinglayer::attribute::StrokeAttribute aStrokeAttribute)
274
11.2k
        :   maStart(rStart),
275
11.2k
            maEnd(rEnd),
276
11.2k
            maBorderLines(std::move(rBorderLines)),
277
11.2k
            maStrokeAttribute(std::move(aStrokeAttribute))
278
11.2k
        {
279
11.2k
        }
280
281
        bool BorderLinePrimitive2D::operator==(const BasePrimitive2D& rPrimitive) const
282
0
        {
283
0
            if(!BufferedDecompositionPrimitive2D::operator==(rPrimitive))
284
0
                return false;
285
286
0
            const BorderLinePrimitive2D& rCompare = static_cast<const BorderLinePrimitive2D&>(rPrimitive);
287
288
0
            return (getStart() == rCompare.getStart()
289
0
                    && getEnd() == rCompare.getEnd()
290
0
                    && getStrokeAttribute() == rCompare.getStrokeAttribute()
291
0
                    && getBorderLines() == rCompare.getBorderLines());
292
0
        }
293
294
        // provide unique ID
295
        sal_uInt32 BorderLinePrimitive2D::getPrimitive2DID() const
296
10.9k
        {
297
10.9k
            return PRIMITIVE2D_ID_BORDERLINEPRIMITIVE2D;
298
10.9k
        }
299
300
        Primitive2DReference tryMergeBorderLinePrimitive2D(
301
            const BorderLinePrimitive2D* pCandidateA,
302
            const BorderLinePrimitive2D* pCandidateB)
303
19.1k
        {
304
19.1k
            assert(pCandidateA);
305
19.1k
            assert(pCandidateB);
306
307
            // start of candidate has to match end of this
308
19.1k
            if(!pCandidateA->getEnd().equal(pCandidateB->getStart()))
309
12.6k
            {
310
12.6k
                return Primitive2DReference();
311
12.6k
            }
312
313
            // candidate A needs a length
314
6.51k
            if(pCandidateA->getStart().equal(pCandidateA->getEnd()))
315
0
            {
316
0
                return Primitive2DReference();
317
0
            }
318
319
            // candidate B needs a length
320
6.51k
            if(pCandidateB->getStart().equal(pCandidateB->getEnd()))
321
0
            {
322
0
                return Primitive2DReference();
323
0
            }
324
325
            // StrokeAttribute has to be equal
326
6.51k
            if(!(pCandidateA->getStrokeAttribute() == pCandidateB->getStrokeAttribute()))
327
0
            {
328
0
                return Primitive2DReference();
329
0
            }
330
331
            // direction has to be equal -> cross product == 0.0
332
6.51k
            const basegfx::B2DVector aVT(pCandidateA->getEnd() - pCandidateA->getStart());
333
6.51k
            const basegfx::B2DVector aVC(pCandidateB->getEnd() - pCandidateB->getStart());
334
6.51k
            if(aVC.cross(aVT) != 0)
335
6.33k
            {
336
6.33k
                return Primitive2DReference();
337
6.33k
            }
338
339
            // number BorderLines has to be equal
340
172
            const size_t count(pCandidateA->getBorderLines().size());
341
172
            if(count != pCandidateB->getBorderLines().size())
342
0
            {
343
0
                return Primitive2DReference();
344
0
            }
345
346
344
            for(size_t a(0); a < count; a++)
347
172
            {
348
172
                const BorderLine& rBT(pCandidateA->getBorderLines()[a]);
349
172
                const BorderLine& rBC(pCandidateB->getBorderLines()[a]);
350
351
                // LineAttribute has to be the same
352
172
                if(!(rBC.getLineAttribute() == rBT.getLineAttribute()))
353
0
                {
354
0
                    return Primitive2DReference();
355
0
                }
356
357
                // isGap has to be the same
358
172
                if(rBC.isGap() != rBT.isGap())
359
0
                {
360
0
                    return Primitive2DReference();
361
0
                }
362
363
172
                if(rBT.isGap())
364
0
                {
365
                    // when gap, width has to be equal
366
0
                    if(!rtl::math::approxEqual(rBT.getLineAttribute().getWidth(), rBC.getLineAttribute().getWidth()))
367
0
                    {
368
0
                        return Primitive2DReference();
369
0
                    }
370
0
                }
371
172
                else
372
172
                {
373
                    // when not gap, the line extends have at least reach to the center ( > 0.0),
374
                    // else there is an extend usage. When > 0.0 they just overlap, no problem
375
172
                    if(rBT.getEndLeft() >= 0.0
376
172
                        && rBT.getEndRight() >= 0.0
377
172
                        && rBC.getStartLeft() >= 0.0
378
172
                        && rBC.getStartRight() >= 0.0)
379
172
                    {
380
                        // okay
381
172
                    }
382
0
                    else
383
0
                    {
384
0
                        return Primitive2DReference();
385
0
                    }
386
172
                }
387
172
            }
388
389
            // all conditions met, create merged primitive
390
172
            std::vector< BorderLine > aMergedBorderLines;
391
392
344
            for(size_t a(0); a < count; a++)
393
172
            {
394
172
                const BorderLine& rBT(pCandidateA->getBorderLines()[a]);
395
172
                const BorderLine& rBC(pCandidateB->getBorderLines()[a]);
396
397
172
                if(rBT.isGap())
398
0
                {
399
0
                    aMergedBorderLines.push_back(rBT);
400
0
                }
401
172
                else
402
172
                {
403
172
                    aMergedBorderLines.push_back(
404
172
                        BorderLine(
405
172
                            rBT.getLineAttribute(),
406
172
                            rBT.getStartLeft(), rBT.getStartRight(),
407
172
                            rBC.getEndLeft(), rBC.getEndRight()));
408
172
                }
409
172
            }
410
411
172
            return
412
172
                new BorderLinePrimitive2D(
413
172
                    pCandidateA->getStart(),
414
172
                    pCandidateB->getEnd(),
415
172
                    std::move(aMergedBorderLines),
416
172
                    pCandidateA->getStrokeAttribute());
417
172
        }
418
419
} // end of namespace
420
421
/* vim:set shiftwidth=4 softtabstop=4 expandtab: */