Coverage Report

Created: 2026-07-10 11:04

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/libreoffice/chart2/source/view/main/Clipping.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 <Clipping.hxx>
21
#include <CommonConverters.hxx>
22
#include <BaseGFXHelper.hxx>
23
24
#include <o3tl/safeint.hxx>
25
#include <osl/diagnose.h>
26
27
#include <com/sun/star/drawing/Position3D.hpp>
28
#include <com/sun/star/drawing/DoubleSequence.hpp>
29
30
namespace chart
31
{
32
using namespace ::com::sun::star;
33
using ::basegfx::B2DRectangle;
34
using ::basegfx::B2DTuple;
35
36
namespace{
37
/** @descr  This is a supporting function for lcl_clip2d.  It computes a new parametric
38
            value for an entering (dTE) or leaving (dTL) intersection point with one
39
            of the edges bounding the clipping area.
40
            For explanation of the parameters please refer to :
41
42
            Liang-Biarsky parametric line-clipping algorithm as described in:
43
            Computer Graphics: principles and practice, 2nd ed.,
44
            James D. Foley et al.,
45
            Section 3.12.4 on page 117.
46
*/
47
bool lcl_CLIPt(double fDenom,double fNum, double & fTE, double & fTL)
48
0
{
49
0
    double  fT;
50
51
0
    if (fDenom > 0)             //  Intersection enters: PE
52
0
    {
53
0
        fT = fNum / fDenom;     //  Parametric value at the intersection.
54
0
        if (fT > fTL)           //  fTE and fTL crossover
55
0
            return false;       //    therefore reject the line.
56
0
        else if (fT > fTE)      //  A new fTE has been found.
57
0
            fTE = fT;
58
0
    }
59
0
    else if (fDenom < 0)        //  Intersection leaves: PL
60
0
    {
61
0
        fT = fNum / fDenom;     //  Parametric Value at the intersection.
62
0
        if (fT < fTE)           //  fTE and fTL crossover
63
0
            return false;       //    therefore reject the line.
64
0
        else if (fT < fTL)      //  A new fTL has been found.
65
0
            fTL = fT;
66
0
    }
67
0
    else if (fNum > 0)
68
0
        return false;           //  Line lies on the outside of the edge.
69
70
0
    return true;
71
0
}
72
73
/** @descr  The line given by its two endpoints rP0 and rP1 is clipped at the rectangle
74
            rRectangle.  If there is at least a part of it visible then sal_True is returned and
75
            the endpoints of that part are stored in rP0 and rP1.  The points rP0 and rP1
76
            may have the same coordinates.
77
        @param  rP0 Start point of the line to clip.  Modified to contain a start point inside
78
            the clipping area if possible.
79
        @param  rP1 End point of the line to clip.  Modified to contain an end point inside
80
            the clipping area if possible.
81
        @param  rRectangle Clipping area.
82
        @return If the line lies completely or partly inside the clipping area then TRUE
83
            is returned.  If the line lies completely outside then sal_False is returned and rP0 and
84
            rP1 are left unmodified.
85
*/
86
bool lcl_clip2d(B2DTuple& rPoint0, B2DTuple& rPoint1, const B2DRectangle& rRectangle)
87
0
{
88
    //Direction vector of the line.
89
0
    B2DTuple aDirection = rPoint1 - rPoint0;
90
91
0
    if( aDirection.getX()==0 && aDirection.getY()==0 && rRectangle.isInside(rPoint0) )
92
0
    {
93
        //  Degenerate case of a zero length line.
94
0
        return true;
95
0
    }
96
0
    else
97
0
    {
98
        //  Values of the line parameter where the line enters resp. leaves the rectangle.
99
0
        double fTE = 0,
100
0
               fTL = 1;
101
102
        //  Test whether at least a part lies in the four half-planes with respect to
103
        //  the rectangles four edges.
104
0
        if( lcl_CLIPt(aDirection.getX(), rRectangle.getMinX() - rPoint0.getX(), fTE, fTL) )
105
0
            if( lcl_CLIPt(-aDirection.getX(), rPoint0.getX() - rRectangle.getMaxX(), fTE, fTL) )
106
0
                if( lcl_CLIPt(aDirection.getY(), rRectangle.getMinY() - rPoint0.getY(), fTE, fTL) )
107
0
                    if( lcl_CLIPt(-aDirection.getY(), rPoint0.getY() - rRectangle.getMaxY(), fTE, fTL) )
108
0
                    {
109
                        //  At least a part is visible.
110
0
                        if (fTL < 1)
111
0
                        {
112
                            //  Compute the new end point.
113
0
                            rPoint1.setX( rPoint0.getX() + fTL * aDirection.getX() );
114
0
                            rPoint1.setY( rPoint0.getY() + fTL * aDirection.getY() );
115
0
                        }
116
0
                        if (fTE > 0)
117
0
                        {
118
                            //  Compute the new starting point.
119
0
                            rPoint0.setX( rPoint0.getX() + fTE * aDirection.getX() );
120
0
                            rPoint0.setY( rPoint0.getY() + fTE * aDirection.getY() );
121
0
                        }
122
0
                        return true;
123
0
                    }
124
125
        //  Line is not visible.
126
0
        return false;
127
0
    }
128
0
}
129
130
bool lcl_clip2d_(drawing::Position3D& rPoint0, drawing::Position3D& rPoint1, const B2DRectangle& rRectangle)
131
0
{
132
0
    B2DTuple aP0(rPoint0.PositionX,rPoint0.PositionY);
133
0
    B2DTuple aP1(rPoint1.PositionX,rPoint1.PositionY);
134
0
    bool bRet = lcl_clip2d( aP0, aP1, rRectangle );
135
136
0
    rPoint0.PositionX = aP0.getX();
137
0
    rPoint0.PositionY = aP0.getY();
138
0
    rPoint1.PositionX = aP1.getX();
139
0
    rPoint1.PositionY = aP1.getY();
140
141
0
    return bRet;
142
0
}
143
144
unsigned int round_up_nearest_pow2(unsigned int v)
145
0
{
146
    // compute the next highest power of 2 of 32-bit v
147
0
    --v;
148
0
    v |= v >> 1;
149
0
    v |= v >> 2;
150
0
    v |= v >> 4;
151
0
    v |= v >> 8;
152
0
    v |= v >> 16;
153
0
    ++v;
154
0
    return v;
155
0
}
156
157
void lcl_addPointToPoly( drawing::PolyPolygonShape3D& rPoly
158
        , const drawing::Position3D& rPos
159
        , sal_Int32 nPolygonIndex
160
        , std::vector< sal_Int32 >& rResultPointCount
161
        , sal_Int32 nReservePointCount )
162
0
{
163
0
    if(nPolygonIndex<0)
164
0
    {
165
0
        OSL_FAIL( "The polygon index needs to be > 0");
166
0
        nPolygonIndex=0;
167
0
    }
168
169
    //make sure that we have enough polygons
170
0
    if(nPolygonIndex >= rPoly.SequenceX.getLength() )
171
0
    {
172
0
        rPoly.SequenceX.realloc(nPolygonIndex+1);
173
0
        rPoly.SequenceY.realloc(nPolygonIndex+1);
174
0
        rPoly.SequenceZ.realloc(nPolygonIndex+1);
175
0
        rResultPointCount.resize(nPolygonIndex+1,0);
176
0
    }
177
178
0
    drawing::DoubleSequence* pOuterSequenceX = &rPoly.SequenceX.getArray()[nPolygonIndex];
179
0
    drawing::DoubleSequence* pOuterSequenceY = &rPoly.SequenceY.getArray()[nPolygonIndex];
180
0
    drawing::DoubleSequence* pOuterSequenceZ = &rPoly.SequenceZ.getArray()[nPolygonIndex];
181
182
0
    sal_Int32 nNewResultPointCount = rResultPointCount[nPolygonIndex]+1;
183
0
    sal_Int32 nSeqLength = pOuterSequenceX->getLength();
184
185
0
    if( nSeqLength <= nNewResultPointCount )
186
0
    {
187
0
        sal_Int32 nReallocLength = nReservePointCount > SAL_MAX_INT16 ? round_up_nearest_pow2(nNewResultPointCount) * 2 : nReservePointCount;
188
0
        if( nNewResultPointCount > nReallocLength )
189
0
        {
190
0
            nReallocLength = nNewResultPointCount;
191
0
            OSL_FAIL("this should not be the case to avoid performance problems");
192
0
        }
193
0
        pOuterSequenceX->realloc(nReallocLength);
194
0
        pOuterSequenceY->realloc(nReallocLength);
195
0
        pOuterSequenceZ->realloc(nReallocLength);
196
0
    }
197
198
0
    double* pInnerSequenceX = pOuterSequenceX->getArray();
199
0
    double* pInnerSequenceY = pOuterSequenceY->getArray();
200
0
    double* pInnerSequenceZ = pOuterSequenceZ->getArray();
201
202
0
    pInnerSequenceX[nNewResultPointCount-1] = rPos.PositionX;
203
0
    pInnerSequenceY[nNewResultPointCount-1] = rPos.PositionY;
204
0
    pInnerSequenceZ[nNewResultPointCount-1] = rPos.PositionZ;
205
0
    rResultPointCount[nPolygonIndex]=nNewResultPointCount;
206
0
}
207
208
void lcl_addPointToPoly( std::vector<std::vector<css::drawing::Position3D>>& rPoly
209
        , const drawing::Position3D& rPos
210
        , sal_Int32 nPolygonIndex
211
        , std::vector< sal_Int32 >& rResultPointCount
212
        , sal_Int32 nReservePointCount )
213
0
{
214
0
    if(nPolygonIndex<0)
215
0
    {
216
0
        OSL_FAIL( "The polygon index needs to be > 0");
217
0
        nPolygonIndex=0;
218
0
    }
219
220
    //make sure that we have enough polygons
221
0
    if(o3tl::make_unsigned(nPolygonIndex) >= rPoly.size() )
222
0
    {
223
0
        rPoly.resize(nPolygonIndex+1);
224
0
        rResultPointCount.resize(nPolygonIndex+1,0);
225
0
    }
226
227
0
    std::vector<css::drawing::Position3D>* pOuterSequence = &rPoly[nPolygonIndex];
228
229
0
    sal_Int32 nNewResultPointCount = rResultPointCount[nPolygonIndex]+1;
230
0
    sal_Int32 nSeqLength = pOuterSequence->size();
231
232
0
    if( nSeqLength <= nNewResultPointCount )
233
0
    {
234
0
        sal_Int32 nReallocLength = nReservePointCount > SAL_MAX_INT16 ? round_up_nearest_pow2(nNewResultPointCount) * 2 : nReservePointCount;
235
0
        if( nNewResultPointCount > nReallocLength )
236
0
        {
237
0
            nReallocLength = nNewResultPointCount;
238
0
            OSL_FAIL("this should not be the case to avoid performance problems");
239
0
        }
240
0
        pOuterSequence->resize(nReallocLength);
241
0
    }
242
243
0
    css::drawing::Position3D* pInnerSequence = pOuterSequence->data();
244
245
0
    pInnerSequence[nNewResultPointCount-1] = rPos;
246
0
    rResultPointCount[nPolygonIndex]=nNewResultPointCount;
247
0
}
248
249
}//end anonymous namespace
250
251
void Clipping::clipPolygonAtRectangle( const drawing::PolyPolygonShape3D& rPolygon
252
                                      , const B2DRectangle& rRectangle
253
                                      , drawing::PolyPolygonShape3D& aResult
254
                                      , bool bSplitPiecesToDifferentPolygons )
255
0
{
256
0
    aResult.SequenceX.realloc(0);
257
0
    aResult.SequenceY.realloc(0);
258
0
    aResult.SequenceZ.realloc(0);
259
260
0
    if(!rPolygon.SequenceX.hasElements())
261
0
        return;
262
263
    //need clipping?:
264
0
    {
265
0
        ::basegfx::B3DRange a3DRange( BaseGFXHelper::getBoundVolume( rPolygon ) );
266
0
        ::basegfx::B2DRange a2DRange( a3DRange.getMinX(), a3DRange.getMinY(), a3DRange.getMaxX(), a3DRange.getMaxY() );
267
0
        if( rRectangle.isInside( a2DRange ) )
268
0
        {
269
0
            aResult = rPolygon;
270
0
            return;
271
0
        }
272
0
        else
273
0
        {
274
0
            a2DRange.intersect( rRectangle );
275
0
            if( a2DRange.isEmpty() )
276
0
                return;
277
0
        }
278
0
    }
279
280
0
    std::vector< sal_Int32 > aResultPointCount;//per polygon index
281
282
    //apply clipping:
283
0
    drawing::Position3D aFrom;
284
0
    drawing::Position3D aTo;
285
286
0
    sal_Int32 nNewPolyIndex = 0;
287
0
    sal_Int32 nOldPolyCount = rPolygon.SequenceX.getLength();
288
0
    for(sal_Int32 nOldPolyIndex=0; nOldPolyIndex<nOldPolyCount; nOldPolyIndex++, nNewPolyIndex++ )
289
0
    {
290
0
        sal_Int32 nOldPointCount = rPolygon.SequenceX[nOldPolyIndex].getLength();
291
292
        // set last point to a position outside the rectangle, such that the first
293
        // time lcl_clip2d returns true, the comparison to last will always yield false
294
0
        drawing::Position3D aLast(rRectangle.getMinX()-1.0,rRectangle.getMinY()-1.0, 0.0 );
295
296
0
        for(sal_Int32 nOldPoint=1; nOldPoint<nOldPointCount; nOldPoint++)
297
0
        {
298
0
            aFrom = getPointFromPoly(rPolygon,nOldPoint-1,nOldPolyIndex);
299
0
            aTo = getPointFromPoly(rPolygon,nOldPoint,nOldPolyIndex);
300
0
            if( lcl_clip2d_(aFrom, aTo, rRectangle) )
301
0
            {
302
                // compose a Polygon of as many consecutive points as possible
303
0
                if(aFrom == aLast)
304
0
                {
305
0
                    if( aTo != aFrom )
306
0
                    {
307
0
                        lcl_addPointToPoly( aResult, aTo, nNewPolyIndex, aResultPointCount, nOldPointCount );
308
0
                    }
309
0
                }
310
0
                else
311
0
                {
312
0
                    if( bSplitPiecesToDifferentPolygons && nOldPoint!=1 )
313
0
                    {
314
0
                        if( nNewPolyIndex < aResult.SequenceX.getLength()
315
0
                                && aResultPointCount[nNewPolyIndex]>0 )
316
0
                            nNewPolyIndex++;
317
0
                    }
318
0
                    lcl_addPointToPoly( aResult, aFrom, nNewPolyIndex, aResultPointCount, nOldPointCount );
319
0
                    if( aTo != aFrom )
320
0
                        lcl_addPointToPoly( aResult, aTo, nNewPolyIndex, aResultPointCount, nOldPointCount );
321
0
                }
322
0
                aLast = aTo;
323
0
            }
324
0
        }
325
0
    }
326
    //free unused space
327
0
    for( sal_Int32 nPolygonIndex = aResultPointCount.size(); nPolygonIndex--; )
328
0
    {
329
0
        drawing::DoubleSequence* pOuterSequenceX = &aResult.SequenceX.getArray()[nPolygonIndex];
330
0
        drawing::DoubleSequence* pOuterSequenceY = &aResult.SequenceY.getArray()[nPolygonIndex];
331
0
        drawing::DoubleSequence* pOuterSequenceZ = &aResult.SequenceZ.getArray()[nPolygonIndex];
332
333
0
        sal_Int32 nUsedPointCount = aResultPointCount[nPolygonIndex];
334
0
        pOuterSequenceX->realloc(nUsedPointCount);
335
0
        pOuterSequenceY->realloc(nUsedPointCount);
336
0
        pOuterSequenceZ->realloc(nUsedPointCount);
337
0
    }
338
0
}
339
340
void Clipping::clipPolygonAtRectangle( const std::vector<std::vector<css::drawing::Position3D>>& rPolygon
341
                                      , const B2DRectangle& rRectangle
342
                                      , std::vector<std::vector<css::drawing::Position3D>>& aResult
343
                                      , bool bSplitPiecesToDifferentPolygons )
344
0
{
345
0
    aResult.clear();
346
347
0
    if(rPolygon.empty())
348
0
        return;
349
350
    //need clipping?:
351
0
    {
352
0
        ::basegfx::B3DRange a3DRange( BaseGFXHelper::getBoundVolume( rPolygon ) );
353
0
        ::basegfx::B2DRange a2DRange( a3DRange.getMinX(), a3DRange.getMinY(), a3DRange.getMaxX(), a3DRange.getMaxY() );
354
0
        if( rRectangle.isInside( a2DRange ) )
355
0
        {
356
0
            aResult = rPolygon;
357
0
            return;
358
0
        }
359
0
        else
360
0
        {
361
0
            a2DRange.intersect( rRectangle );
362
0
            if( a2DRange.isEmpty() )
363
0
                return;
364
0
        }
365
0
    }
366
367
0
    std::vector< sal_Int32 > aResultPointCount;//per polygon index
368
369
    //apply clipping:
370
0
    drawing::Position3D aFrom;
371
0
    drawing::Position3D aTo;
372
373
0
    sal_Int32 nNewPolyIndex = 0;
374
0
    sal_Int32 nOldPolyCount = rPolygon.size();
375
0
    for(sal_Int32 nOldPolyIndex=0; nOldPolyIndex<nOldPolyCount; nOldPolyIndex++, nNewPolyIndex++ )
376
0
    {
377
0
        sal_Int32 nOldPointCount = rPolygon[nOldPolyIndex].size();
378
379
        // set last point to a position outside the rectangle, such that the first
380
        // time lcl_clip2d returns true, the comparison to last will always yield false
381
0
        drawing::Position3D aLast(rRectangle.getMinX()-1.0,rRectangle.getMinY()-1.0, 0.0 );
382
383
0
        for(sal_Int32 nOldPoint=1; nOldPoint<nOldPointCount; nOldPoint++)
384
0
        {
385
0
            aFrom = getPointFromPoly(rPolygon,nOldPoint-1,nOldPolyIndex);
386
0
            aTo = getPointFromPoly(rPolygon,nOldPoint,nOldPolyIndex);
387
0
            if( lcl_clip2d_(aFrom, aTo, rRectangle) )
388
0
            {
389
                // compose a Polygon of as many consecutive points as possible
390
0
                if(aFrom == aLast)
391
0
                {
392
0
                    if( aTo != aFrom )
393
0
                    {
394
0
                        lcl_addPointToPoly( aResult, aTo, nNewPolyIndex, aResultPointCount, nOldPointCount );
395
0
                    }
396
0
                }
397
0
                else
398
0
                {
399
0
                    if( bSplitPiecesToDifferentPolygons && nOldPoint!=1 )
400
0
                    {
401
0
                        if( nNewPolyIndex < static_cast<sal_Int32>(aResult.size())
402
0
                                && aResultPointCount[nNewPolyIndex]>0 )
403
0
                            nNewPolyIndex++;
404
0
                    }
405
0
                    lcl_addPointToPoly( aResult, aFrom, nNewPolyIndex, aResultPointCount, nOldPointCount );
406
0
                    if( aTo != aFrom )
407
0
                        lcl_addPointToPoly( aResult, aTo, nNewPolyIndex, aResultPointCount, nOldPointCount );
408
0
                }
409
0
                aLast = aTo;
410
0
            }
411
0
        }
412
0
    }
413
    //free unused space
414
0
    for( sal_Int32 nPolygonIndex = aResultPointCount.size(); nPolygonIndex--; )
415
0
    {
416
0
        std::vector<css::drawing::Position3D>* pOuterSequence = &aResult[nPolygonIndex];
417
418
0
        sal_Int32 nUsedPointCount = aResultPointCount[nPolygonIndex];
419
0
        pOuterSequence->resize(nUsedPointCount);
420
0
    }
421
0
}
422
423
} //namespace chart
424
425
/* vim:set shiftwidth=4 softtabstop=4 expandtab: */