Coverage Report

Created: 2026-05-16 09:25

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/libreoffice/svx/source/dialog/framelinkarray.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/framelinkarray.hxx>
21
22
#include <math.h>
23
#include <vector>
24
#include <unordered_set>
25
#include <algorithm>
26
#include <o3tl/hash_combine.hxx>
27
#include <sal/log.hxx>
28
#include <tools/debug.hxx>
29
#include <tools/gen.hxx>
30
#include <vcl/canvastools.hxx>
31
#include <svx/sdr/primitive2d/sdrframeborderprimitive2d.hxx>
32
#include <basegfx/matrix/b2dhommatrixtools.hxx>
33
#include <basegfx/polygon/b2dpolypolygon.hxx>
34
#include <basegfx/polygon/b2dpolygonclipper.hxx>
35
#include <basegfx/polygon/b2dpolygon.hxx>
36
37
//#define OPTICAL_CHECK_CLIPRANGE_FOR_MERGED_CELL
38
#ifdef OPTICAL_CHECK_CLIPRANGE_FOR_MERGED_CELL
39
#include <basegfx/polygon/b2dpolygontools.hxx>
40
#include <drawinglayer/primitive2d/PolygonHairlinePrimitive2D.hxx>
41
#endif
42
43
namespace svx::frame {
44
45
namespace {
46
47
class Cell final
48
{
49
private:
50
    Style               maLeft;
51
    Style               maRight;
52
    Style               maTop;
53
    Style               maBottom;
54
    Style               maTLBR;
55
    Style               maBLTR;
56
57
    basegfx::B2DHomMatrix HelperCreateB2DHomMatrixFromB2DRange(
58
        const basegfx::B2DRange& rRange ) const;
59
60
public:
61
    sal_Int32                mnAddLeft;
62
    sal_Int32                mnAddRight;
63
    sal_Int32                mnAddTop;
64
    sal_Int32                mnAddBottom;
65
66
    SvxRotateMode       meRotMode;
67
    double              mfOrientation;
68
69
    bool                mbMergeOrig;
70
    bool                mbOverlapX;
71
    bool                mbOverlapY;
72
73
public:
74
    explicit Cell();
75
    explicit Cell(const Cell&) = default;
76
77
    bool operator==( const Cell& ) const;
78
    size_t hashCode() const;
79
80
0
    void SetStyleLeft(const Style& rStyle) { maLeft = rStyle; }
81
0
    void SetStyleRight(const Style& rStyle) { maRight = rStyle; }
82
0
    void SetStyleTop(const Style& rStyle) { maTop = rStyle; }
83
0
    void SetStyleBottom(const Style& rStyle) { maBottom = rStyle; }
84
0
    void SetStyleTLBR(const Style& rStyle) { maTLBR = rStyle; }
85
0
    void SetStyleBLTR(const Style& rStyle) { maBLTR = rStyle; }
86
87
0
    const Style& GetStyleLeft() const { return maLeft; }
88
0
    const Style& GetStyleRight() const { return maRight; }
89
0
    const Style& GetStyleTop() const { return maTop; }
90
0
    const Style& GetStyleBottom() const { return maBottom; }
91
0
    const Style& GetStyleTLBR() const { return maTLBR; }
92
0
    const Style& GetStyleBLTR() const { return maBLTR; }
93
94
0
    bool                IsMerged() const { return mbMergeOrig || mbOverlapX || mbOverlapY; }
95
0
    bool                IsRotated() const { return mfOrientation != 0.0; }
96
97
    void                MirrorSelfX();
98
99
    basegfx::B2DHomMatrix CreateCoordinateSystemSingleCell(
100
        const Array& rArray, sal_Int32 nCol, sal_Int32 nRow ) const;
101
    basegfx::B2DHomMatrix CreateCoordinateSystemMergedCell(
102
        const Array& rArray, sal_Int32 nColLeft, sal_Int32 nRowTop, sal_Int32 nColRight, sal_Int32 nRowBottom ) const;
103
};
104
105
}
106
107
typedef std::vector< const Cell* >     CellVec;
108
109
basegfx::B2DHomMatrix Cell::HelperCreateB2DHomMatrixFromB2DRange(
110
    const basegfx::B2DRange& rRange ) const
111
0
{
112
0
    if( rRange.isEmpty() )
113
0
        return basegfx::B2DHomMatrix();
114
115
0
    basegfx::B2DPoint aOrigin(rRange.getMinimum());
116
0
    basegfx::B2DVector aX(rRange.getWidth(), 0.0);
117
0
    basegfx::B2DVector aY(0.0, rRange.getHeight());
118
119
0
    if (IsRotated() && SvxRotateMode::SVX_ROTATE_MODE_STANDARD != meRotMode )
120
0
    {
121
        // tdf#143377 We need to limit applying Skew to geometry since the closer
122
        // we get to 0.0 or PI the more sin(mfOrientation) will get to zero and the
123
        // huger the Skew effect will be. For that, use an epsilon-radius of 1/2
124
        // degree around the dangerous points 0.0 and PI.
125
126
        // Snap to modulo to [0.0 .. 2PI[ to make compare easier
127
0
        const double fSnapped(::basegfx::snapToZeroRange(mfOrientation, M_PI * 2.0));
128
129
        // As a compromise, allow up to 1/2 degree
130
0
        static const double fMinAng(M_PI/360.0);
131
132
        // Check if Skew makes sense or would be too huge
133
0
        const bool bForbidSkew(
134
0
            fSnapped < fMinAng || // range [0.0 .. fMinAng]
135
0
            fSnapped > (M_PI * 2.0) - fMinAng || // range [PI-fMinAng .. 2PI[
136
0
            fabs(fSnapped - M_PI) < fMinAng); // range [PI-fMinAng .. PI+fMinAng]
137
138
0
        if(!bForbidSkew)
139
0
        {
140
            // when rotated, adapt values. Get Skew (cos/sin == 1/tan)
141
0
            const double fSkew(aY.getY() * (cos(mfOrientation) / sin(mfOrientation)));
142
143
0
            switch (meRotMode)
144
0
            {
145
0
            case SvxRotateMode::SVX_ROTATE_MODE_TOP:
146
                // shear Y-Axis
147
0
                aY.setX(-fSkew);
148
0
                break;
149
0
            case SvxRotateMode::SVX_ROTATE_MODE_CENTER:
150
                // shear origin half, Y full
151
0
                aOrigin.setX(aOrigin.getX() + (fSkew * 0.5));
152
0
                aY.setX(-fSkew);
153
0
                break;
154
0
            case SvxRotateMode::SVX_ROTATE_MODE_BOTTOM:
155
                // shear origin full, Y full
156
0
                aOrigin.setX(aOrigin.getX() + fSkew);
157
0
                aY.setX(-fSkew);
158
0
                break;
159
0
            default: // SvxRotateMode::SVX_ROTATE_MODE_STANDARD, already excluded above
160
0
                break;
161
0
            }
162
0
        }
163
0
    }
164
165
    // use column vectors as coordinate axes, homogen column for translation
166
0
    return basegfx::utils::createCoordinateSystemTransform( aOrigin, aX, aY );
167
0
}
168
169
basegfx::B2DHomMatrix Cell::CreateCoordinateSystemSingleCell(
170
    const Array& rArray, sal_Int32 nCol, sal_Int32 nRow) const
171
0
{
172
0
    const Point aPoint( rArray.GetColPosition( nCol ), rArray.GetRowPosition( nRow ) );
173
0
    const Size aSize( rArray.GetColWidth( nCol, nCol ) + 1, rArray.GetRowHeight( nRow, nRow ) + 1 );
174
0
    const basegfx::B2DRange aRange( vcl::unotools::b2DRectangleFromRectangle( tools::Rectangle( aPoint, aSize ) ) );
175
176
0
    return HelperCreateB2DHomMatrixFromB2DRange( aRange );
177
0
}
178
179
basegfx::B2DHomMatrix Cell::CreateCoordinateSystemMergedCell(
180
    const Array& rArray, sal_Int32 nColLeft, sal_Int32 nRowTop, sal_Int32 nColRight, sal_Int32 nRowBottom) const
181
0
{
182
0
    basegfx::B2DRange aRange( rArray.GetB2DRange(
183
0
        nColLeft, nRowTop, nColRight, nRowBottom ) );
184
185
    // adjust rectangle for partly visible merged cells
186
0
    if( IsMerged() )
187
0
    {
188
        // not *sure* what exactly this is good for,
189
        // it is just a hard set extension at merged cells,
190
        // probably *should* be included in the above extended
191
        // GetColPosition/GetColWidth already. This might be
192
        // added due to GetColPosition/GetColWidth not working
193
        // correctly over PageChanges (if used), but not sure.
194
0
        aRange.expand(
195
0
            basegfx::B2DRange(
196
0
                aRange.getMinX() - mnAddLeft,
197
0
                aRange.getMinY() - mnAddTop,
198
0
                aRange.getMaxX() + mnAddRight,
199
0
                aRange.getMaxY() + mnAddBottom ) );
200
0
    }
201
202
0
    return HelperCreateB2DHomMatrixFromB2DRange( aRange );
203
0
}
204
205
Cell::Cell() :
206
54
    mnAddLeft( 0 ),
207
54
    mnAddRight( 0 ),
208
54
    mnAddTop( 0 ),
209
54
    mnAddBottom( 0 ),
210
54
    meRotMode(SvxRotateMode::SVX_ROTATE_MODE_STANDARD ),
211
54
    mfOrientation( 0.0 ),
212
54
    mbMergeOrig( false ),
213
54
    mbOverlapX( false ),
214
54
    mbOverlapY( false )
215
54
{
216
54
}
217
218
bool Cell::operator==(const Cell& rOther) const
219
0
{
220
0
    if (this == &rOther)
221
        // ptr compare (same instance)
222
0
        return true;
223
224
0
    return maLeft == rOther.maLeft
225
0
        && maRight == rOther.maRight
226
0
        && maTop == rOther.maTop
227
0
        && maBottom == rOther.maBottom
228
0
        && maTLBR == rOther.maTLBR
229
0
        && maBLTR == rOther.maBLTR
230
0
        && mnAddLeft == rOther.mnAddLeft
231
0
        && mnAddRight == rOther.mnAddRight
232
0
        && mnAddTop == rOther.mnAddTop
233
0
        && mnAddBottom == rOther.mnAddBottom
234
0
        && meRotMode == rOther.meRotMode
235
0
        && mfOrientation == rOther.mfOrientation
236
0
        && mbMergeOrig == rOther.mbMergeOrig
237
0
        && mbOverlapX == rOther.mbOverlapX
238
0
        && mbOverlapY == rOther.mbOverlapY;
239
0
}
240
241
size_t Cell::hashCode() const
242
0
{
243
0
    std::size_t seed = 0;
244
0
    o3tl::hash_combine(seed, maLeft.hashCode());
245
0
    o3tl::hash_combine(seed, maRight.hashCode());
246
0
    o3tl::hash_combine(seed, maTop.hashCode());
247
0
    o3tl::hash_combine(seed, maBottom.hashCode());
248
0
    o3tl::hash_combine(seed, maTLBR.hashCode());
249
0
    o3tl::hash_combine(seed, maBLTR.hashCode());
250
0
    o3tl::hash_combine(seed, mnAddLeft);
251
0
    o3tl::hash_combine(seed, mnAddRight);
252
0
    o3tl::hash_combine(seed, mnAddTop);
253
0
    o3tl::hash_combine(seed, mnAddBottom);
254
0
    o3tl::hash_combine(seed, meRotMode);
255
0
    o3tl::hash_combine(seed, mfOrientation);
256
0
    o3tl::hash_combine(seed, mbMergeOrig);
257
0
    o3tl::hash_combine(seed, mbOverlapX);
258
0
    o3tl::hash_combine(seed, mbOverlapY);
259
0
    return seed;
260
0
}
261
262
void Cell::MirrorSelfX()
263
0
{
264
0
    std::swap( maLeft, maRight );
265
0
    std::swap( mnAddLeft, mnAddRight );
266
0
    maLeft.MirrorSelf();
267
0
    maRight.MirrorSelf();
268
0
    mfOrientation = -mfOrientation;
269
0
}
270
271
272
static void lclRecalcCoordVec( std::vector<sal_Int32>& rCoords, const std::vector<sal_Int32>& rSizes )
273
0
{
274
0
    DBG_ASSERT( rCoords.size() == rSizes.size() + 1, "lclRecalcCoordVec - inconsistent vectors" );
275
0
    auto aCIt = rCoords.begin();
276
0
    for( const auto& rSize : rSizes )
277
0
    {
278
0
        *(aCIt + 1) = *aCIt + rSize;
279
0
        ++aCIt;
280
0
    }
281
0
}
282
283
const Style OBJ_STYLE_NONE;
284
const Cell OBJ_CELL_NONE;
285
286
/** use hashing to speed up finding duplicates */
287
namespace
288
{
289
struct RegisteredCellHash
290
{
291
    size_t operator()(const Cell* pCell) const
292
0
    {
293
0
        return pCell->hashCode();
294
0
    }
295
};
296
297
struct RegisteredCellEquals
298
{
299
    bool operator()(const Cell* pCell1, const Cell* pCell2) const
300
0
    {
301
0
        return *pCell1 == *pCell2;
302
0
    }
303
};
304
}
305
306
struct ArrayImpl
307
{
308
    std::unordered_set<Cell*, RegisteredCellHash, RegisteredCellEquals> maRegisteredCells;
309
    CellVec             maCells;
310
    std::vector<sal_Int32>   maWidths;
311
    std::vector<sal_Int32>   maHeights;
312
    mutable std::vector<sal_Int32>     maXCoords;
313
    mutable std::vector<sal_Int32>     maYCoords;
314
    sal_Int32           mnWidth;
315
    sal_Int32           mnHeight;
316
    sal_Int32           mnFirstClipCol;
317
    sal_Int32           mnFirstClipRow;
318
    sal_Int32           mnLastClipCol;
319
    sal_Int32           mnLastClipRow;
320
    mutable bool        mbXCoordsDirty;
321
    mutable bool        mbYCoordsDirty;
322
    bool                mbMayHaveCellRotation;
323
    bool mbXMirrored = false;
324
325
    explicit            ArrayImpl( sal_Int32 nWidth, sal_Int32 nHeight );
326
    ~ArrayImpl();
327
328
    bool         IsValidPos( sal_Int32 nCol, sal_Int32 nRow ) const
329
0
                            { return (nCol < mnWidth) && (nRow < mnHeight); }
330
    sal_Int32       GetIndex( sal_Int32 nCol, sal_Int32 nRow ) const
331
0
                            { return nRow * mnWidth + nCol; }
332
333
    const Cell*         GetCell( sal_Int32 nCol, sal_Int32 nRow ) const;
334
    void                PutCell( sal_Int32 nCol, sal_Int32 nRow, const Cell& );
335
336
    sal_Int32              GetMergedFirstCol( sal_Int32 nCol, sal_Int32 nRow ) const;
337
    sal_Int32              GetMergedFirstRow( sal_Int32 nCol, sal_Int32 nRow ) const;
338
    sal_Int32              GetMergedLastCol( sal_Int32 nCol, sal_Int32 nRow ) const;
339
    sal_Int32              GetMergedLastRow( sal_Int32 nCol, sal_Int32 nRow ) const;
340
341
    const Cell* GetMergedStyleSourceCell(sal_Int32 nCol, sal_Int32 nRow) const;
342
343
    bool                IsMergedOverlappedLeft( sal_Int32 nCol, sal_Int32 nRow ) const;
344
    bool                IsMergedOverlappedRight( sal_Int32 nCol, sal_Int32 nRow ) const;
345
    bool                IsMergedOverlappedTop( sal_Int32 nCol, sal_Int32 nRow ) const;
346
    bool                IsMergedOverlappedBottom( sal_Int32 nCol, sal_Int32 nRow ) const;
347
348
    bool                IsInClipRange( sal_Int32 nCol, sal_Int32 nRow ) const;
349
    bool                IsColInClipRange( sal_Int32 nCol ) const;
350
    bool                IsRowInClipRange( sal_Int32 nRow ) const;
351
352
    bool                OverlapsClipRange( sal_Int32 nFirstCol, sal_Int32 nFirstRow, sal_Int32 nLastCol, sal_Int32 nLastRow ) const;
353
354
0
    sal_Int32           GetMirrorCol( sal_Int32 nCol ) const { return mnWidth - nCol - 1; }
355
356
    sal_Int32           GetColPosition( sal_Int32 nCol ) const;
357
    sal_Int32           GetRowPosition( sal_Int32 nRow ) const;
358
359
    bool                HasCellRotation() const;
360
361
    const Cell* createOrFind(const Cell& rCell);
362
};
363
364
static void lclSetMergedRange( ArrayImpl& rImpl, CellVec& rCells, sal_Int32 nWidth, sal_Int32 nFirstCol, sal_Int32 nFirstRow, sal_Int32 nLastCol, sal_Int32 nLastRow )
365
0
{
366
0
    for( sal_Int32 nCol = nFirstCol; nCol <= nLastCol; ++nCol )
367
0
    {
368
0
        for( sal_Int32 nRow = nFirstRow; nRow <= nLastRow; ++nRow )
369
0
        {
370
0
            const Cell* pCell = rCells[ nRow * nWidth + nCol ];
371
0
            Cell aTempCell(*pCell);
372
0
            aTempCell.mbMergeOrig = false;
373
0
            aTempCell.mbOverlapX = nCol > nFirstCol;
374
0
            aTempCell.mbOverlapY = nRow > nFirstRow;
375
0
            rCells[ nRow * nWidth + nCol ] = rImpl.createOrFind(aTempCell);
376
0
        }
377
0
    }
378
0
    Cell aTempCell(*rCells[ nFirstRow * nWidth + nFirstCol ]);
379
0
    aTempCell.mbMergeOrig = true;
380
0
    rCells[ nFirstRow * nWidth + nFirstCol ] = rImpl.createOrFind(aTempCell);
381
0
}
382
383
ArrayImpl::ArrayImpl( sal_Int32 nWidth, sal_Int32 nHeight ) :
384
0
    maRegisteredCells(),
385
0
    mnWidth( nWidth ),
386
0
    mnHeight( nHeight ),
387
0
    mnFirstClipCol( 0 ),
388
0
    mnFirstClipRow( 0 ),
389
0
    mnLastClipCol( nWidth - 1 ),
390
0
    mnLastClipRow( nHeight - 1 ),
391
0
    mbXCoordsDirty( false ),
392
0
    mbYCoordsDirty( false ),
393
0
    mbMayHaveCellRotation( false )
394
0
{
395
0
    const Cell* pDefaultCell = createOrFind(Cell());
396
    // default-construct all vectors
397
0
    maCells.resize( mnWidth * mnHeight, pDefaultCell );
398
0
    maWidths.resize( mnWidth, 0 );
399
0
    maHeights.resize( mnHeight, 0 );
400
0
    maXCoords.resize( mnWidth + 1, 0 );
401
0
    maYCoords.resize( mnHeight + 1, 0 );
402
0
}
403
404
ArrayImpl::~ArrayImpl()
405
0
{
406
0
    for (auto* pCell : maRegisteredCells)
407
0
        delete pCell;
408
0
}
409
410
const Cell* ArrayImpl::createOrFind(const Cell& rCell)
411
0
{
412
0
    auto it = maRegisteredCells.find(const_cast<Cell*>(&rCell));
413
0
    if (it != maRegisteredCells.end())
414
0
        return *it;
415
416
0
    Cell* pRetval(new Cell(rCell));
417
0
    maRegisteredCells.insert(pRetval);
418
0
    return pRetval;
419
0
}
420
421
const Cell* ArrayImpl::GetCell( sal_Int32 nCol, sal_Int32 nRow ) const
422
0
{
423
0
    return IsValidPos( nCol, nRow ) ? maCells[ GetIndex( nCol, nRow ) ] : &OBJ_CELL_NONE;
424
0
}
425
426
void ArrayImpl::PutCell( sal_Int32 nCol, sal_Int32 nRow, const Cell & rCell )
427
0
{
428
0
    if (IsValidPos( nCol, nRow ))
429
0
        maCells[ GetIndex( nCol, nRow ) ] = createOrFind(rCell);
430
0
}
431
432
sal_Int32 ArrayImpl::GetMergedFirstCol( sal_Int32 nCol, sal_Int32 nRow ) const
433
0
{
434
0
    sal_Int32 nFirstCol = nCol;
435
0
    while( (nFirstCol > 0) && GetCell( nFirstCol, nRow )->mbOverlapX ) --nFirstCol;
436
0
    return nFirstCol;
437
0
}
438
439
sal_Int32 ArrayImpl::GetMergedFirstRow( sal_Int32 nCol, sal_Int32 nRow ) const
440
0
{
441
0
    sal_Int32 nFirstRow = nRow;
442
0
    while( (nFirstRow > 0) && GetCell( nCol, nFirstRow )->mbOverlapY ) --nFirstRow;
443
0
    return nFirstRow;
444
0
}
445
446
sal_Int32 ArrayImpl::GetMergedLastCol( sal_Int32 nCol, sal_Int32 nRow ) const
447
0
{
448
0
    sal_Int32 nLastCol = nCol + 1;
449
0
    while( (nLastCol < mnWidth) && GetCell( nLastCol, nRow )->mbOverlapX ) ++nLastCol;
450
0
    return nLastCol - 1;
451
0
}
452
453
sal_Int32 ArrayImpl::GetMergedLastRow( sal_Int32 nCol, sal_Int32 nRow ) const
454
0
{
455
0
    sal_Int32 nLastRow = nRow + 1;
456
0
    while( (nLastRow < mnHeight) && GetCell( nCol, nLastRow )->mbOverlapY ) ++nLastRow;
457
0
    return nLastRow - 1;
458
0
}
459
460
const Cell* ArrayImpl::GetMergedStyleSourceCell(sal_Int32 nCol, sal_Int32 nRow) const
461
0
{
462
0
    if (mbXMirrored)
463
0
    {
464
0
        return GetCell(GetMergedLastCol(nCol, nRow), GetMergedFirstRow(nCol, nRow));
465
0
    }
466
467
0
    return GetCell(GetMergedFirstCol(nCol, nRow), GetMergedFirstRow(nCol, nRow));
468
0
}
469
470
bool ArrayImpl::IsMergedOverlappedLeft( sal_Int32 nCol, sal_Int32 nRow ) const
471
0
{
472
0
    const Cell* pCell(GetCell( nCol, nRow ));
473
0
    return pCell->mbOverlapX || (pCell->mnAddLeft > 0);
474
0
}
475
476
bool ArrayImpl::IsMergedOverlappedRight( sal_Int32 nCol, sal_Int32 nRow ) const
477
0
{
478
0
    return GetCell( nCol + 1, nRow )->mbOverlapX || (GetCell( nCol, nRow )->mnAddRight > 0);
479
0
}
480
481
bool ArrayImpl::IsMergedOverlappedTop( sal_Int32 nCol, sal_Int32 nRow ) const
482
0
{
483
0
    const Cell* pCell(GetCell( nCol, nRow ));
484
0
    return pCell->mbOverlapY || (pCell->mnAddTop > 0);
485
0
}
486
487
bool ArrayImpl::IsMergedOverlappedBottom( sal_Int32 nCol, sal_Int32 nRow ) const
488
0
{
489
0
    return GetCell( nCol, nRow + 1 )->mbOverlapY || (GetCell( nCol, nRow )->mnAddBottom > 0);
490
0
}
491
492
bool ArrayImpl::IsColInClipRange( sal_Int32 nCol ) const
493
0
{
494
0
    return (mnFirstClipCol <= nCol) && (nCol <= mnLastClipCol);
495
0
}
496
497
bool ArrayImpl::IsRowInClipRange( sal_Int32 nRow ) const
498
0
{
499
0
    return (mnFirstClipRow <= nRow) && (nRow <= mnLastClipRow);
500
0
}
501
502
bool ArrayImpl::OverlapsClipRange( sal_Int32 nFirstCol, sal_Int32 nFirstRow, sal_Int32 nLastCol, sal_Int32 nLastRow ) const
503
0
{
504
0
    if(nLastCol < mnFirstClipCol)
505
0
        return false;
506
507
0
    if(nFirstCol > mnLastClipCol)
508
0
        return false;
509
510
0
    if(nLastRow < mnFirstClipRow)
511
0
        return false;
512
513
0
    if(nFirstRow > mnLastClipRow)
514
0
        return false;
515
516
0
    return true;
517
0
}
518
519
bool ArrayImpl::IsInClipRange( sal_Int32 nCol, sal_Int32 nRow ) const
520
0
{
521
0
    return IsColInClipRange( nCol ) && IsRowInClipRange( nRow );
522
0
}
523
524
sal_Int32 ArrayImpl::GetColPosition( sal_Int32 nCol ) const
525
0
{
526
0
    if( mbXCoordsDirty )
527
0
    {
528
0
        lclRecalcCoordVec( maXCoords, maWidths );
529
0
        mbXCoordsDirty = false;
530
0
    }
531
0
    return maXCoords[ nCol ];
532
0
}
533
534
sal_Int32 ArrayImpl::GetRowPosition( sal_Int32 nRow ) const
535
0
{
536
0
    if( mbYCoordsDirty )
537
0
    {
538
0
        lclRecalcCoordVec( maYCoords, maHeights );
539
0
        mbYCoordsDirty = false;
540
0
    }
541
0
    return maYCoords[ nRow ];
542
0
}
543
544
bool ArrayImpl::HasCellRotation() const
545
0
{
546
    // check cell array
547
0
    for (const auto& aCell : maCells)
548
0
    {
549
0
        if (aCell->IsRotated())
550
0
        {
551
0
            return true;
552
0
        }
553
0
    }
554
555
0
    return false;
556
0
}
557
558
namespace {
559
560
class MergedCellIterator
561
{
562
public:
563
    explicit            MergedCellIterator( const Array& rArray, sal_Int32 nCol, sal_Int32 nRow );
564
565
0
    bool         Is() const { return (mnCol <= mnLastCol) && (mnRow <= mnLastRow); }
566
0
    sal_Int32       Col() const { return mnCol; }
567
0
    sal_Int32       Row() const { return mnRow; }
568
569
    MergedCellIterator& operator++();
570
571
private:
572
    sal_Int32              mnFirstCol;
573
    sal_Int32              mnFirstRow;
574
    sal_Int32              mnLastCol;
575
    sal_Int32              mnLastRow;
576
    sal_Int32              mnCol;
577
    sal_Int32              mnRow;
578
};
579
580
}
581
582
MergedCellIterator::MergedCellIterator( const Array& rArray, sal_Int32 nCol, sal_Int32 nRow )
583
0
{
584
0
    rArray.GetMergedRange( mnFirstCol, mnFirstRow, mnLastCol, mnLastRow, nCol, nRow );
585
0
    mnCol = mnFirstCol;
586
0
    mnRow = mnFirstRow;
587
0
}
588
589
MergedCellIterator& MergedCellIterator::operator++()
590
0
{
591
0
    DBG_ASSERT( Is(), "svx::frame::MergedCellIterator::operator++() - already invalid" );
592
0
    if( ++mnCol > mnLastCol )
593
0
    {
594
0
        mnCol = mnFirstCol;
595
0
        ++mnRow;
596
0
    }
597
0
    return *this;
598
0
}
599
600
0
#define DBG_FRAME_CHECK( cond, funcname, error )        DBG_ASSERT( cond, "svx::frame::Array::" funcname " - " error )
601
0
#define DBG_FRAME_CHECK_COL( col, funcname )            DBG_FRAME_CHECK( (col) < GetColCount(), funcname, "invalid column index" )
602
0
#define DBG_FRAME_CHECK_ROW( row, funcname )            DBG_FRAME_CHECK( (row) < GetRowCount(), funcname, "invalid row index" )
603
0
#define DBG_FRAME_CHECK_COLROW( col, row, funcname )    DBG_FRAME_CHECK( ((col) < GetColCount()) && ((row) < GetRowCount()), funcname, "invalid cell index" )
604
0
#define DBG_FRAME_CHECK_COL_1( col, funcname )          DBG_FRAME_CHECK( (col) <= GetColCount(), funcname, "invalid column index" )
605
0
#define DBG_FRAME_CHECK_ROW_1( row, funcname )          DBG_FRAME_CHECK( (row) <= GetRowCount(), funcname, "invalid row index" )
606
607
Array::Array()
608
0
{
609
0
    Initialize( 0, 0 );
610
0
}
611
612
Array::~Array()
613
0
{
614
0
}
615
616
// array size and column/row indexes
617
void Array::Initialize( sal_Int32 nWidth, sal_Int32 nHeight )
618
0
{
619
0
    mxImpl.reset( new ArrayImpl( nWidth, nHeight ) );
620
0
}
621
622
sal_Int32 Array::GetColCount() const
623
0
{
624
0
    return mxImpl->mnWidth;
625
0
}
626
627
sal_Int32 Array::GetRowCount() const
628
0
{
629
0
    return mxImpl->mnHeight;
630
0
}
631
632
sal_Int32 Array::GetCellCount() const
633
0
{
634
0
    return mxImpl->maCells.size();
635
0
}
636
637
sal_Int32 Array::GetCellIndex( sal_Int32 nCol, sal_Int32 nRow, bool bRTL ) const
638
0
{
639
0
    DBG_FRAME_CHECK_COLROW( nCol, nRow, "GetCellIndex" );
640
0
    if (bRTL)
641
0
        nCol = mxImpl->GetMirrorCol(nCol);
642
0
    return mxImpl->GetIndex( nCol, nRow );
643
0
}
644
645
// cell border styles
646
void Array::SetCellStyleLeft( sal_Int32 nCol, sal_Int32 nRow, const Style& rStyle )
647
0
{
648
0
    DBG_FRAME_CHECK_COLROW( nCol, nRow, "SetCellStyleLeft" );
649
0
    const Cell* pTempCell(mxImpl->GetCell(nCol, nRow));
650
0
    if (pTempCell->GetStyleLeft() == rStyle)
651
0
        return;
652
0
    Cell aTempCell(*pTempCell);
653
0
    aTempCell.SetStyleLeft(rStyle);
654
0
    mxImpl->PutCell( nCol, nRow, aTempCell );
655
0
}
656
657
void Array::SetCellStyleRight( sal_Int32 nCol, sal_Int32 nRow, const Style& rStyle )
658
0
{
659
0
    DBG_FRAME_CHECK_COLROW( nCol, nRow, "SetCellStyleRight" );
660
0
    const Cell* pTempCell(mxImpl->GetCell(nCol, nRow));
661
0
    if (pTempCell->GetStyleRight() == rStyle)
662
0
        return;
663
0
    Cell aTempCell(*pTempCell);
664
0
    aTempCell.SetStyleRight(rStyle);
665
0
    mxImpl->PutCell( nCol, nRow, aTempCell );
666
0
}
667
668
void Array::SetCellStyleTop( sal_Int32 nCol, sal_Int32 nRow, const Style& rStyle )
669
0
{
670
0
    DBG_FRAME_CHECK_COLROW( nCol, nRow, "SetCellStyleTop" );
671
0
    const Cell* pTempCell(mxImpl->GetCell(nCol, nRow));
672
0
    if (pTempCell->GetStyleTop() == rStyle)
673
0
        return;
674
0
    Cell aTempCell(*pTempCell);
675
0
    aTempCell.SetStyleTop(rStyle);
676
0
    mxImpl->PutCell( nCol, nRow, aTempCell );
677
0
}
678
679
void Array::SetCellStyleBottom( sal_Int32 nCol, sal_Int32 nRow, const Style& rStyle )
680
0
{
681
0
    DBG_FRAME_CHECK_COLROW( nCol, nRow, "SetCellStyleBottom" );
682
0
    const Cell* pTempCell(mxImpl->GetCell(nCol, nRow));
683
0
    if (pTempCell->GetStyleBottom() == rStyle)
684
0
        return;
685
0
    Cell aTempCell(*pTempCell);
686
0
    aTempCell.SetStyleBottom(rStyle);
687
0
    mxImpl->PutCell( nCol, nRow, aTempCell );
688
0
}
689
690
void Array::SetCellStyleTLBR( sal_Int32 nCol, sal_Int32 nRow, const Style& rStyle )
691
0
{
692
0
    DBG_FRAME_CHECK_COLROW( nCol, nRow, "SetCellStyleTLBR" );
693
0
    const Cell* pTempCell(mxImpl->GetCell(nCol, nRow));
694
0
    if (pTempCell->GetStyleTLBR() == rStyle)
695
0
        return;
696
0
    Cell aTempCell(*pTempCell);
697
0
    aTempCell.SetStyleTLBR(rStyle);
698
0
    mxImpl->PutCell( nCol, nRow, aTempCell );
699
0
}
700
701
void Array::SetCellStyleBLTR( sal_Int32 nCol, sal_Int32 nRow, const Style& rStyle )
702
0
{
703
0
    DBG_FRAME_CHECK_COLROW( nCol, nRow, "SetCellStyleBLTR" );
704
0
    const Cell* pTempCell(mxImpl->GetCell(nCol, nRow));
705
0
    if (pTempCell->GetStyleBLTR() == rStyle)
706
0
        return;
707
0
    Cell aTempCell(*pTempCell);
708
0
    aTempCell.SetStyleBLTR(rStyle);
709
0
    mxImpl->PutCell( nCol, nRow, aTempCell );
710
0
}
711
712
void Array::SetCellStyleDiag( sal_Int32 nCol, sal_Int32 nRow, const Style& rTLBR, const Style& rBLTR )
713
0
{
714
0
    DBG_FRAME_CHECK_COLROW( nCol, nRow, "SetCellStyleDiag" );
715
0
    const Cell* pTempCell(mxImpl->GetCell(nCol, nRow));
716
0
    if (pTempCell->GetStyleTLBR() == rTLBR && pTempCell->GetStyleBLTR() == rBLTR)
717
0
        return;
718
0
    Cell aTempCell(*pTempCell);
719
0
    aTempCell.SetStyleTLBR(rTLBR);
720
0
    aTempCell.SetStyleBLTR(rBLTR);
721
0
    mxImpl->PutCell( nCol, nRow, aTempCell );
722
0
}
723
724
void Array::SetColumnStyleLeft( sal_Int32 nCol, const Style& rStyle )
725
0
{
726
0
    DBG_FRAME_CHECK_COL( nCol, "SetColumnStyleLeft" );
727
0
    for( sal_Int32 nRow = 0; nRow < mxImpl->mnHeight; ++nRow )
728
0
        SetCellStyleLeft( nCol, nRow, rStyle );
729
0
}
730
731
void Array::SetColumnStyleRight( sal_Int32 nCol, const Style& rStyle )
732
0
{
733
0
    DBG_FRAME_CHECK_COL( nCol, "SetColumnStyleRight" );
734
0
    for( sal_Int32 nRow = 0; nRow < mxImpl->mnHeight; ++nRow )
735
0
        SetCellStyleRight( nCol, nRow, rStyle );
736
0
}
737
738
void Array::SetRowStyleTop( sal_Int32 nRow, const Style& rStyle )
739
0
{
740
0
    DBG_FRAME_CHECK_ROW( nRow, "SetRowStyleTop" );
741
0
    for( sal_Int32 nCol = 0; nCol < mxImpl->mnWidth; ++nCol )
742
0
        SetCellStyleTop( nCol, nRow, rStyle );
743
0
}
744
745
void Array::SetRowStyleBottom( sal_Int32 nRow, const Style& rStyle )
746
0
{
747
0
    DBG_FRAME_CHECK_ROW( nRow, "SetRowStyleBottom" );
748
0
    for( sal_Int32 nCol = 0; nCol < mxImpl->mnWidth; ++nCol )
749
0
        SetCellStyleBottom( nCol, nRow, rStyle );
750
0
}
751
752
void Array::SetCellRotation(sal_Int32 nCol, sal_Int32 nRow, SvxRotateMode eRotMode, double fOrientation)
753
0
{
754
0
    DBG_FRAME_CHECK_COLROW(nCol, nRow, "SetCellRotation");
755
0
    const Cell* pTempCell(mxImpl->GetCell(nCol, nRow));
756
0
    if (pTempCell->meRotMode == eRotMode && pTempCell->mfOrientation == fOrientation)
757
0
        return;
758
0
    Cell aTempCell(*pTempCell);
759
0
    aTempCell.meRotMode = eRotMode;
760
0
    aTempCell.mfOrientation = fOrientation;
761
0
    mxImpl->PutCell( nCol, nRow, aTempCell );
762
763
0
    if (!mxImpl->mbMayHaveCellRotation)
764
0
    {
765
        // activate once when a cell gets actually rotated to allow fast
766
        // answering HasCellRotation() calls
767
0
        mxImpl->mbMayHaveCellRotation = aTempCell.IsRotated();
768
0
    }
769
0
}
770
771
bool Array::HasCellRotation() const
772
0
{
773
0
    if (!mxImpl->mbMayHaveCellRotation)
774
0
    {
775
        // never set, no need to check
776
0
        return false;
777
0
    }
778
779
0
    return mxImpl->HasCellRotation();
780
0
}
781
782
const Style& Array::GetCellStyleLeft( sal_Int32 nCol, sal_Int32 nRow ) const
783
0
{
784
    // outside clipping rows or overlapped in merged cells: invisible
785
0
    if( !mxImpl->IsRowInClipRange( nRow ) || mxImpl->IsMergedOverlappedLeft( nCol, nRow ) )
786
0
        return OBJ_STYLE_NONE;
787
    // left clipping border: always own left style
788
0
    if( nCol == mxImpl->mnFirstClipCol )
789
0
        return mxImpl->GetMergedStyleSourceCell(nCol, nRow)->GetStyleLeft();
790
    // right clipping border: always right style of left neighbor cell
791
0
    if( nCol == mxImpl->mnLastClipCol + 1 )
792
0
        return mxImpl->GetMergedStyleSourceCell(nCol - 1, nRow)->GetStyleRight();
793
    // outside clipping columns: invisible
794
0
    if( !mxImpl->IsColInClipRange( nCol ) )
795
0
        return OBJ_STYLE_NONE;
796
    // inside clipping range: maximum of own left style and right style of left neighbor cell
797
0
    return std::max(mxImpl->GetMergedStyleSourceCell(nCol, nRow)->GetStyleLeft(),
798
0
                    mxImpl->GetMergedStyleSourceCell(nCol - 1, nRow)->GetStyleRight());
799
0
}
800
801
const Style& Array::GetCellStyleRight( sal_Int32 nCol, sal_Int32 nRow ) const
802
0
{
803
    // outside clipping rows or overlapped in merged cells: invisible
804
0
    if( !mxImpl->IsRowInClipRange( nRow ) || mxImpl->IsMergedOverlappedRight( nCol, nRow ) )
805
0
        return OBJ_STYLE_NONE;
806
    // left clipping border: always left style of right neighbor cell
807
0
    if( nCol + 1 == mxImpl->mnFirstClipCol )
808
0
        return mxImpl->GetMergedStyleSourceCell(nCol + 1, nRow)->GetStyleLeft();
809
    // right clipping border: always own right style
810
0
    if( nCol == mxImpl->mnLastClipCol )
811
0
        return mxImpl->GetMergedStyleSourceCell(nCol, nRow)->GetStyleRight();
812
    // outside clipping columns: invisible
813
0
    if( !mxImpl->IsColInClipRange( nCol ) )
814
0
        return OBJ_STYLE_NONE;
815
    // inside clipping range: maximum of own right style and left style of right neighbor cell
816
0
    return std::max(mxImpl->GetMergedStyleSourceCell(nCol, nRow)->GetStyleRight(),
817
0
                    mxImpl->GetMergedStyleSourceCell(nCol + 1, nRow)->GetStyleLeft());
818
0
}
819
820
const Style& Array::GetCellStyleTop( sal_Int32 nCol, sal_Int32 nRow ) const
821
0
{
822
    // outside clipping columns or overlapped in merged cells: invisible
823
0
    if( !mxImpl->IsColInClipRange( nCol ) || mxImpl->IsMergedOverlappedTop( nCol, nRow ) )
824
0
        return OBJ_STYLE_NONE;
825
    // top clipping border: always own top style
826
0
    if( nRow == mxImpl->mnFirstClipRow )
827
0
        return mxImpl->GetMergedStyleSourceCell(nCol, nRow)->GetStyleTop();
828
    // bottom clipping border: always bottom style of top neighbor cell
829
0
    if( nRow == mxImpl->mnLastClipRow + 1 )
830
0
        return mxImpl->GetMergedStyleSourceCell(nCol, nRow - 1)->GetStyleBottom();
831
    // outside clipping rows: invisible
832
0
    if( !mxImpl->IsRowInClipRange( nRow ) )
833
0
        return OBJ_STYLE_NONE;
834
    // inside clipping range: maximum of own top style and bottom style of top neighbor cell
835
0
    return std::max(mxImpl->GetMergedStyleSourceCell(nCol, nRow)->GetStyleTop(),
836
0
                    mxImpl->GetMergedStyleSourceCell(nCol, nRow - 1)->GetStyleBottom());
837
0
}
838
839
const Style& Array::GetCellStyleBottom( sal_Int32 nCol, sal_Int32 nRow ) const
840
0
{
841
    // outside clipping columns or overlapped in merged cells: invisible
842
0
    if( !mxImpl->IsColInClipRange( nCol ) || mxImpl->IsMergedOverlappedBottom( nCol, nRow ) )
843
0
        return OBJ_STYLE_NONE;
844
    // top clipping border: always top style of bottom neighbor cell
845
0
    if( nRow + 1 == mxImpl->mnFirstClipRow )
846
0
        return mxImpl->GetMergedStyleSourceCell(nCol, nRow + 1)->GetStyleTop();
847
    // bottom clipping border: always own bottom style
848
0
    if( nRow == mxImpl->mnLastClipRow )
849
0
        return mxImpl->GetMergedStyleSourceCell(nCol, nRow)->GetStyleBottom();
850
    // outside clipping rows: invisible
851
0
    if( !mxImpl->IsRowInClipRange( nRow ) )
852
0
        return OBJ_STYLE_NONE;
853
    // inside clipping range: maximum of own bottom style and top style of bottom neighbor cell
854
0
    return std::max(mxImpl->GetMergedStyleSourceCell(nCol, nRow)->GetStyleBottom(),
855
0
                    mxImpl->GetMergedStyleSourceCell(nCol, nRow + 1)->GetStyleTop());
856
0
}
857
858
const Style& Array::GetCellStyleTLBR( sal_Int32 nCol, sal_Int32 nRow ) const
859
0
{
860
0
    return mxImpl->GetCell( nCol, nRow )->GetStyleTLBR();
861
0
}
862
863
const Style& Array::GetCellStyleBLTR( sal_Int32 nCol, sal_Int32 nRow ) const
864
0
{
865
0
    return mxImpl->GetCell( nCol, nRow )->GetStyleBLTR();
866
0
}
867
868
const Style& Array::GetCellStyleTL( sal_Int32 nCol, sal_Int32 nRow ) const
869
0
{
870
    // not in clipping range: always invisible
871
0
    if( !mxImpl->IsInClipRange( nCol, nRow ) )
872
0
        return OBJ_STYLE_NONE;
873
    // return style only for top-left cell
874
0
    sal_Int32 nFirstCol = mxImpl->GetMergedFirstCol( nCol, nRow );
875
0
    sal_Int32 nFirstRow = mxImpl->GetMergedFirstRow( nCol, nRow );
876
0
    return ((nCol == nFirstCol) && (nRow == nFirstRow)) ?
877
0
        mxImpl->GetCell( nFirstCol, nFirstRow )->GetStyleTLBR() : OBJ_STYLE_NONE;
878
0
}
879
880
const Style& Array::GetCellStyleBR( sal_Int32 nCol, sal_Int32 nRow ) const
881
0
{
882
    // not in clipping range: always invisible
883
0
    if( !mxImpl->IsInClipRange( nCol, nRow ) )
884
0
        return OBJ_STYLE_NONE;
885
    // return style only for bottom-right cell
886
0
    sal_Int32 nLastCol = mxImpl->GetMergedLastCol( nCol, nRow );
887
0
    sal_Int32 nLastRow = mxImpl->GetMergedLastRow( nCol, nRow );
888
0
    return ((nCol == nLastCol) && (nRow == nLastRow)) ?
889
0
        mxImpl->GetCell( mxImpl->GetMergedFirstCol( nCol, nRow ), mxImpl->GetMergedFirstRow( nCol, nRow ) )->GetStyleTLBR() : OBJ_STYLE_NONE;
890
0
}
891
892
const Style& Array::GetCellStyleBL( sal_Int32 nCol, sal_Int32 nRow ) const
893
0
{
894
    // not in clipping range: always invisible
895
0
    if( !mxImpl->IsInClipRange( nCol, nRow ) )
896
0
        return OBJ_STYLE_NONE;
897
    // return style only for bottom-left cell
898
0
    sal_Int32 nFirstCol = mxImpl->GetMergedFirstCol( nCol, nRow );
899
0
    sal_Int32 nLastRow = mxImpl->GetMergedLastRow( nCol, nRow );
900
0
    return ((nCol == nFirstCol) && (nRow == nLastRow)) ?
901
0
        mxImpl->GetCell( nFirstCol, mxImpl->GetMergedFirstRow( nCol, nRow ) )->GetStyleBLTR() : OBJ_STYLE_NONE;
902
0
}
903
904
const Style& Array::GetCellStyleTR( sal_Int32 nCol, sal_Int32 nRow ) const
905
0
{
906
    // not in clipping range: always invisible
907
0
    if( !mxImpl->IsInClipRange( nCol, nRow ) )
908
0
        return OBJ_STYLE_NONE;
909
    // return style only for top-right cell
910
0
    sal_Int32 nFirstRow = mxImpl->GetMergedFirstRow( nCol, nRow );
911
0
    sal_Int32 nLastCol = mxImpl->GetMergedLastCol( nCol, nRow );
912
0
    return ((nCol == nLastCol) && (nRow == nFirstRow)) ?
913
0
        mxImpl->GetCell( mxImpl->GetMergedFirstCol( nCol, nRow ), nFirstRow )->GetStyleBLTR() : OBJ_STYLE_NONE;
914
0
}
915
916
// cell merging
917
void Array::SetMergedRange( sal_Int32 nFirstCol, sal_Int32 nFirstRow, sal_Int32 nLastCol, sal_Int32 nLastRow )
918
0
{
919
0
    DBG_FRAME_CHECK_COLROW( nFirstCol, nFirstRow, "SetMergedRange" );
920
0
    DBG_FRAME_CHECK_COLROW( nLastCol, nLastRow, "SetMergedRange" );
921
#if OSL_DEBUG_LEVEL >= 2
922
    {
923
        bool bFound = false;
924
        for( sal_Int32 nCurrCol = nFirstCol; !bFound && (nCurrCol <= nLastCol); ++nCurrCol )
925
            for( sal_Int32 nCurrRow = nFirstRow; !bFound && (nCurrRow <= nLastRow); ++nCurrRow )
926
                bFound = mxImpl->GetCell( nCurrCol, nCurrRow )->IsMerged();
927
        DBG_FRAME_CHECK( !bFound, "SetMergedRange", "overlapping merged ranges" );
928
    }
929
#endif
930
0
    if( mxImpl->IsValidPos( nFirstCol, nFirstRow ) && mxImpl->IsValidPos( nLastCol, nLastRow ) )
931
0
        lclSetMergedRange( *mxImpl, mxImpl->maCells, mxImpl->mnWidth, nFirstCol, nFirstRow, nLastCol, nLastRow );
932
0
}
933
934
void Array::SetAddMergedLeftSize( sal_Int32 nCol, sal_Int32 nRow, sal_Int32 nAddSize )
935
0
{
936
0
    DBG_FRAME_CHECK_COLROW( nCol, nRow, "SetAddMergedLeftSize" );
937
0
    DBG_FRAME_CHECK( mxImpl->GetMergedFirstCol( nCol, nRow ) == 0, "SetAddMergedLeftSize", "additional border inside array" );
938
0
    for( MergedCellIterator aIt( *this, nCol, nRow ); aIt.Is(); ++aIt )
939
0
    {
940
0
        const Cell* pTempCell(mxImpl->GetCell(aIt.Col(), aIt.Row()));
941
0
        if (pTempCell->mnAddLeft == nAddSize)
942
0
            return;
943
0
        Cell aTempCell(*pTempCell);
944
0
        aTempCell.mnAddLeft = nAddSize;
945
0
        mxImpl->PutCell( aIt.Col(), aIt.Row(), aTempCell );
946
0
    }
947
0
}
948
949
void Array::SetAddMergedRightSize( sal_Int32 nCol, sal_Int32 nRow, sal_Int32 nAddSize )
950
0
{
951
0
    DBG_FRAME_CHECK_COLROW( nCol, nRow, "SetAddMergedRightSize" );
952
0
    DBG_FRAME_CHECK( mxImpl->GetMergedLastCol( nCol, nRow ) + 1 == mxImpl->mnWidth, "SetAddMergedRightSize", "additional border inside array" );
953
0
    for( MergedCellIterator aIt( *this, nCol, nRow ); aIt.Is(); ++aIt )
954
0
    {
955
0
        const Cell* pTempCell(mxImpl->GetCell(aIt.Col(), aIt.Row()));
956
0
        if (pTempCell->mnAddRight == nAddSize)
957
0
            return;
958
0
        Cell aTempCell(*pTempCell);
959
0
        aTempCell.mnAddRight = nAddSize;
960
0
        mxImpl->PutCell( aIt.Col(), aIt.Row(), aTempCell );
961
0
    }
962
0
}
963
964
void Array::SetAddMergedTopSize( sal_Int32 nCol, sal_Int32 nRow, sal_Int32 nAddSize )
965
0
{
966
0
    DBG_FRAME_CHECK_COLROW( nCol, nRow, "SetAddMergedTopSize" );
967
0
    DBG_FRAME_CHECK( mxImpl->GetMergedFirstRow( nCol, nRow ) == 0, "SetAddMergedTopSize", "additional border inside array" );
968
0
    for( MergedCellIterator aIt( *this, nCol, nRow ); aIt.Is(); ++aIt )
969
0
    {
970
0
        const Cell* pTempCell(mxImpl->GetCell(aIt.Col(), aIt.Row()));
971
0
        if (pTempCell->mnAddTop == nAddSize)
972
0
            return;
973
0
        Cell aTempCell(*pTempCell);
974
0
        aTempCell.mnAddTop = nAddSize;
975
0
        mxImpl->PutCell( aIt.Col(), aIt.Row(), aTempCell );
976
0
    }
977
0
}
978
979
void Array::SetAddMergedBottomSize( sal_Int32 nCol, sal_Int32 nRow, sal_Int32 nAddSize )
980
0
{
981
0
    DBG_FRAME_CHECK_COLROW( nCol, nRow, "SetAddMergedBottomSize" );
982
0
    DBG_FRAME_CHECK( mxImpl->GetMergedLastRow( nCol, nRow ) + 1 == mxImpl->mnHeight, "SetAddMergedBottomSize", "additional border inside array" );
983
0
    for( MergedCellIterator aIt( *this, nCol, nRow ); aIt.Is(); ++aIt )
984
0
    {
985
0
        const Cell* pTempCell(mxImpl->GetCell(aIt.Col(), aIt.Row()));
986
0
        if (pTempCell->mnAddBottom == nAddSize)
987
0
            return;
988
0
        Cell aTempCell(*pTempCell);
989
0
        aTempCell.mnAddBottom = nAddSize;
990
0
        mxImpl->PutCell( aIt.Col(), aIt.Row(), aTempCell );
991
0
    }
992
0
}
993
994
bool Array::IsMerged( sal_Int32 nCol, sal_Int32 nRow ) const
995
0
{
996
0
    DBG_FRAME_CHECK_COLROW( nCol, nRow, "IsMerged" );
997
0
    return mxImpl->GetCell( nCol, nRow )->IsMerged();
998
0
}
999
1000
void Array::GetMergedOrigin( sal_Int32& rnFirstCol, sal_Int32& rnFirstRow, sal_Int32 nCol, sal_Int32 nRow ) const
1001
0
{
1002
0
    DBG_FRAME_CHECK_COLROW( nCol, nRow, "GetMergedOrigin" );
1003
0
    rnFirstCol = mxImpl->GetMergedFirstCol( nCol, nRow );
1004
0
    rnFirstRow = mxImpl->GetMergedFirstRow( nCol, nRow );
1005
0
}
1006
1007
void Array::GetMergedRange( sal_Int32& rnFirstCol, sal_Int32& rnFirstRow,
1008
        sal_Int32& rnLastCol, sal_Int32& rnLastRow, sal_Int32 nCol, sal_Int32 nRow ) const
1009
0
{
1010
0
    GetMergedOrigin( rnFirstCol, rnFirstRow, nCol, nRow );
1011
0
    rnLastCol = mxImpl->GetMergedLastCol( nCol, nRow );
1012
0
    rnLastRow = mxImpl->GetMergedLastRow( nCol, nRow );
1013
0
}
1014
1015
// clipping
1016
void Array::SetClipRange( sal_Int32 nFirstCol, sal_Int32 nFirstRow, sal_Int32 nLastCol, sal_Int32 nLastRow )
1017
0
{
1018
0
    DBG_FRAME_CHECK_COLROW( nFirstCol, nFirstRow, "SetClipRange" );
1019
0
    DBG_FRAME_CHECK_COLROW( nLastCol, nLastRow, "SetClipRange" );
1020
0
    mxImpl->mnFirstClipCol = nFirstCol;
1021
0
    mxImpl->mnFirstClipRow = nFirstRow;
1022
0
    mxImpl->mnLastClipCol = nLastCol;
1023
0
    mxImpl->mnLastClipRow = nLastRow;
1024
0
}
1025
1026
// cell coordinates
1027
void Array::SetXOffset( sal_Int32 nXOffset )
1028
0
{
1029
0
    mxImpl->maXCoords[ 0 ] = nXOffset;
1030
0
    mxImpl->mbXCoordsDirty = true;
1031
0
}
1032
1033
void Array::SetYOffset( sal_Int32 nYOffset )
1034
0
{
1035
0
    mxImpl->maYCoords[ 0 ] = nYOffset;
1036
0
    mxImpl->mbYCoordsDirty = true;
1037
0
}
1038
1039
void Array::SetColWidth( sal_Int32 nCol, sal_Int32 nWidth )
1040
0
{
1041
0
    DBG_FRAME_CHECK_COL( nCol, "SetColWidth" );
1042
0
    mxImpl->maWidths[ nCol ] = nWidth;
1043
0
    mxImpl->mbXCoordsDirty = true;
1044
0
}
1045
1046
void Array::SetRowHeight( sal_Int32 nRow, sal_Int32 nHeight )
1047
0
{
1048
0
    DBG_FRAME_CHECK_ROW( nRow, "SetRowHeight" );
1049
0
    mxImpl->maHeights[ nRow ] = nHeight;
1050
0
    mxImpl->mbYCoordsDirty = true;
1051
0
}
1052
1053
void Array::SetAllColWidths( sal_Int32 nWidth )
1054
0
{
1055
0
    std::fill( mxImpl->maWidths.begin(), mxImpl->maWidths.end(), nWidth );
1056
0
    mxImpl->mbXCoordsDirty = true;
1057
0
}
1058
1059
void Array::SetAllRowHeights( sal_Int32 nHeight )
1060
0
{
1061
0
    std::fill( mxImpl->maHeights.begin(), mxImpl->maHeights.end(), nHeight );
1062
0
    mxImpl->mbYCoordsDirty = true;
1063
0
}
1064
1065
sal_Int32 Array::GetColPosition( sal_Int32 nCol ) const
1066
0
{
1067
0
    DBG_FRAME_CHECK_COL_1( nCol, "GetColPosition" );
1068
0
    return mxImpl->GetColPosition( nCol );
1069
0
}
1070
1071
sal_Int32 Array::GetRowPosition( sal_Int32 nRow ) const
1072
0
{
1073
0
    DBG_FRAME_CHECK_ROW_1( nRow, "GetRowPosition" );
1074
0
    return mxImpl->GetRowPosition( nRow );
1075
0
}
1076
1077
sal_Int32 Array::GetColWidth( sal_Int32 nFirstCol, sal_Int32 nLastCol ) const
1078
0
{
1079
0
    DBG_FRAME_CHECK_COL( nFirstCol, "GetColWidth" );
1080
0
    DBG_FRAME_CHECK_COL( nLastCol, "GetColWidth" );
1081
0
    return GetColPosition( nLastCol + 1 ) - GetColPosition( nFirstCol );
1082
0
}
1083
1084
sal_Int32 Array::GetRowHeight( sal_Int32 nFirstRow, sal_Int32 nLastRow ) const
1085
0
{
1086
0
    DBG_FRAME_CHECK_ROW( nFirstRow, "GetRowHeight" );
1087
0
    DBG_FRAME_CHECK_ROW( nLastRow, "GetRowHeight" );
1088
0
    return GetRowPosition( nLastRow + 1 ) - GetRowPosition( nFirstRow );
1089
0
}
1090
1091
sal_Int32 Array::GetWidth() const
1092
0
{
1093
0
    return GetColPosition( mxImpl->mnWidth ) - GetColPosition( 0 );
1094
0
}
1095
1096
sal_Int32 Array::GetHeight() const
1097
0
{
1098
0
    return GetRowPosition( mxImpl->mnHeight ) - GetRowPosition( 0 );
1099
0
}
1100
1101
basegfx::B2DRange Array::GetCellRange( sal_Int32 nCol, sal_Int32 nRow ) const
1102
0
{
1103
    // get the Range of the fully expanded cell (if merged)
1104
0
    const sal_Int32 nFirstCol(mxImpl->GetMergedFirstCol( nCol, nRow ));
1105
0
    const sal_Int32 nFirstRow(mxImpl->GetMergedFirstRow( nCol, nRow ));
1106
0
    const sal_Int32 nLastCol(mxImpl->GetMergedLastCol( nCol, nRow ));
1107
0
    const sal_Int32 nLastRow(mxImpl->GetMergedLastRow( nCol, nRow ));
1108
0
    const Point aPoint( GetColPosition( nFirstCol ), GetRowPosition( nFirstRow ) );
1109
0
    const Size aSize( GetColWidth( nFirstCol, nLastCol ) + 1, GetRowHeight( nFirstRow, nLastRow ) + 1 );
1110
0
    tools::Rectangle aRect(aPoint, aSize);
1111
1112
    // adjust rectangle for partly visible merged cells
1113
0
    const Cell* pCell(mxImpl->GetCell( nCol, nRow ));
1114
1115
0
    if( pCell->IsMerged() )
1116
0
    {
1117
        // not *sure* what exactly this is good for,
1118
        // it is just a hard set extension at merged cells,
1119
        // probably *should* be included in the above extended
1120
        // GetColPosition/GetColWidth already. This might be
1121
        // added due to GetColPosition/GetColWidth not working
1122
        // correctly over PageChanges (if used), but not sure.
1123
0
        aRect.AdjustLeft( -(pCell->mnAddLeft) );
1124
0
        aRect.AdjustRight(pCell->mnAddRight );
1125
0
        aRect.AdjustTop( -(pCell->mnAddTop) );
1126
0
        aRect.AdjustBottom(pCell->mnAddBottom );
1127
0
    }
1128
1129
0
    return vcl::unotools::b2DRectangleFromRectangle(aRect);
1130
0
}
1131
1132
// return output range of given row/col range in logical coordinates
1133
basegfx::B2DRange Array::GetB2DRange(sal_Int32 nFirstCol, sal_Int32 nFirstRow, sal_Int32 nLastCol, sal_Int32 nLastRow) const
1134
0
{
1135
0
    const Point aPoint( GetColPosition( nFirstCol ), GetRowPosition( nFirstRow ) );
1136
0
    const Size aSize( GetColWidth( nFirstCol, nLastCol ) + 1, GetRowHeight( nFirstRow, nLastRow ) + 1 );
1137
1138
0
    return vcl::unotools::b2DRectangleFromRectangle(tools::Rectangle(aPoint, aSize));
1139
0
}
1140
1141
// mirroring
1142
void Array::MirrorSelfX()
1143
0
{
1144
0
    CellVec aNewCells;
1145
0
    aNewCells.reserve( GetCellCount() );
1146
1147
0
    sal_Int32 nCol, nRow;
1148
0
    for( nRow = 0; nRow < mxImpl->mnHeight; ++nRow )
1149
0
    {
1150
0
        for( nCol = 0; nCol < mxImpl->mnWidth; ++nCol )
1151
0
        {
1152
0
            Cell aTempCell(*mxImpl->GetCell(mxImpl->GetMirrorCol( nCol ), nRow));
1153
0
            aTempCell.MirrorSelfX();
1154
0
            aNewCells.push_back( mxImpl->createOrFind(aTempCell) );
1155
0
        }
1156
0
    }
1157
0
    for( nRow = 0; nRow < mxImpl->mnHeight; ++nRow )
1158
0
    {
1159
0
        for( nCol = 0; nCol < mxImpl->mnWidth; ++nCol )
1160
0
        {
1161
0
            if( mxImpl->GetCell( nCol, nRow )->mbMergeOrig )
1162
0
            {
1163
0
                sal_Int32 nLastCol = mxImpl->GetMergedLastCol( nCol, nRow );
1164
0
                sal_Int32 nLastRow = mxImpl->GetMergedLastRow( nCol, nRow );
1165
0
                lclSetMergedRange( *mxImpl, aNewCells, mxImpl->mnWidth,
1166
0
                    mxImpl->GetMirrorCol( nLastCol ), nRow,
1167
0
                    mxImpl->GetMirrorCol( nCol ), nLastRow );
1168
0
            }
1169
0
        }
1170
0
    }
1171
0
    mxImpl->maCells.swap( aNewCells );
1172
1173
0
    std::reverse( mxImpl->maWidths.begin(), mxImpl->maWidths.end() );
1174
0
    mxImpl->mbXCoordsDirty = true;
1175
0
    mxImpl->mbXMirrored = !mxImpl->mbXMirrored;
1176
0
}
1177
1178
// drawing
1179
static void HelperCreateHorizontalEntry(
1180
    const Array& rArray,
1181
    const Style& rStyle,
1182
    sal_Int32 col,
1183
    sal_Int32 row,
1184
    const basegfx::B2DPoint& rOrigin,
1185
    const basegfx::B2DVector& rX,
1186
    const basegfx::B2DVector& rY,
1187
    drawinglayer::primitive2d::SdrFrameBorderDataVector& rData,
1188
    bool bUpper,
1189
    const Color* pForceColor)
1190
0
{
1191
    // prepare SdrFrameBorderData
1192
0
    rData.emplace_back(
1193
0
        bUpper ? rOrigin : basegfx::B2DPoint(rOrigin + rY),
1194
0
        rX,
1195
0
        rStyle,
1196
0
        pForceColor);
1197
0
    drawinglayer::primitive2d::SdrFrameBorderData& rInstance(rData.back());
1198
1199
    // get involved styles at start
1200
0
    const Style& rStartFromTR(rArray.GetCellStyleBL( col, row - 1 ));
1201
0
    const Style& rStartLFromT(rArray.GetCellStyleLeft( col, row - 1 ));
1202
0
    const Style& rStartLFromL(rArray.GetCellStyleTop( col - 1, row ));
1203
0
    const Style& rStartLFromB(rArray.GetCellStyleLeft( col, row ));
1204
0
    const Style& rStartFromBR(rArray.GetCellStyleTL( col, row ));
1205
1206
0
    rInstance.addSdrConnectStyleData(true, rStartFromTR, rX - rY, false);
1207
0
    rInstance.addSdrConnectStyleData(true, rStartLFromT, -rY, true);
1208
0
    rInstance.addSdrConnectStyleData(true, rStartLFromL, -rX, true);
1209
0
    rInstance.addSdrConnectStyleData(true, rStartLFromB, rY, false);
1210
0
    rInstance.addSdrConnectStyleData(true, rStartFromBR, rX + rY, false);
1211
1212
    // get involved styles at end
1213
0
    const Style& rEndFromTL(rArray.GetCellStyleBR( col, row - 1 ));
1214
0
    const Style& rEndRFromT(rArray.GetCellStyleRight( col, row - 1 ));
1215
0
    const Style& rEndRFromR(rArray.GetCellStyleTop( col + 1, row ));
1216
0
    const Style& rEndRFromB(rArray.GetCellStyleRight( col, row ));
1217
0
    const Style& rEndFromBL(rArray.GetCellStyleTR( col, row ));
1218
1219
0
    rInstance.addSdrConnectStyleData(false, rEndFromTL, -rX - rY, true);
1220
0
    rInstance.addSdrConnectStyleData(false, rEndRFromT, -rY, true);
1221
0
    rInstance.addSdrConnectStyleData(false, rEndRFromR, rX, false);
1222
0
    rInstance.addSdrConnectStyleData(false, rEndRFromB, rY, false);
1223
0
    rInstance.addSdrConnectStyleData(false, rEndFromBL, rY - rX, true);
1224
0
}
1225
1226
static void HelperCreateVerticalEntry(
1227
    const Array& rArray,
1228
    const Style& rStyle,
1229
    sal_Int32 col,
1230
    sal_Int32 row,
1231
    const basegfx::B2DPoint& rOrigin,
1232
    const basegfx::B2DVector& rX,
1233
    const basegfx::B2DVector& rY,
1234
    drawinglayer::primitive2d::SdrFrameBorderDataVector& rData,
1235
    bool bLeft,
1236
    const Color* pForceColor)
1237
0
{
1238
    // prepare SdrFrameBorderData
1239
0
    rData.emplace_back(
1240
0
        bLeft ? rOrigin : basegfx::B2DPoint(rOrigin + rX),
1241
0
        rY,
1242
0
        rStyle,
1243
0
        pForceColor);
1244
0
    drawinglayer::primitive2d::SdrFrameBorderData& rInstance(rData.back());
1245
1246
    // get involved styles at start
1247
0
    const Style& rStartFromBL(rArray.GetCellStyleTR( col - 1, row ));
1248
0
    const Style& rStartTFromL(rArray.GetCellStyleTop( col - 1, row ));
1249
0
    const Style& rStartTFromT(rArray.GetCellStyleLeft( col, row - 1 ));
1250
0
    const Style& rStartTFromR(rArray.GetCellStyleTop( col, row ));
1251
0
    const Style& rStartFromBR(rArray.GetCellStyleTL( col, row ));
1252
1253
0
    rInstance.addSdrConnectStyleData(true, rStartFromBR, rX + rY, false);
1254
0
    rInstance.addSdrConnectStyleData(true, rStartTFromR, rX, false);
1255
0
    rInstance.addSdrConnectStyleData(true, rStartTFromT, -rY, true);
1256
0
    rInstance.addSdrConnectStyleData(true, rStartTFromL, -rX, true);
1257
0
    rInstance.addSdrConnectStyleData(true, rStartFromBL, rY - rX, true);
1258
1259
    // get involved styles at end
1260
0
    const Style& rEndFromTL(rArray.GetCellStyleBR( col - 1, row ));
1261
0
    const Style& rEndBFromL(rArray.GetCellStyleBottom( col - 1, row ));
1262
0
    const Style& rEndBFromB(rArray.GetCellStyleLeft( col, row + 1 ));
1263
0
    const Style& rEndBFromR(rArray.GetCellStyleBottom( col, row ));
1264
0
    const Style& rEndFromTR(rArray.GetCellStyleBL( col, row ));
1265
1266
0
    rInstance.addSdrConnectStyleData(false, rEndFromTR, rX - rY, false);
1267
0
    rInstance.addSdrConnectStyleData(false, rEndBFromR, rX, false);
1268
0
    rInstance.addSdrConnectStyleData(false, rEndBFromB, rY, false);
1269
0
    rInstance.addSdrConnectStyleData(false, rEndBFromL, -rX, true);
1270
0
    rInstance.addSdrConnectStyleData(false, rEndFromTL, -rY - rX, true);
1271
0
}
1272
1273
static void HelperClipLine(
1274
    basegfx::B2DPoint& rStart,
1275
    basegfx::B2DVector& rDirection,
1276
    const basegfx::B2DRange& rClipRange)
1277
0
{
1278
0
    basegfx::B2DPolygon aLine({rStart, rStart + rDirection});
1279
0
    const basegfx::B2DPolyPolygon aResultPP(
1280
0
        basegfx::utils::clipPolygonOnRange(
1281
0
            aLine,
1282
0
            rClipRange,
1283
0
            true, // bInside
1284
0
            true)); // bStroke
1285
1286
0
    if(aResultPP.count() > 0)
1287
0
    {
1288
0
        const basegfx::B2DPolygon& aResultP(aResultPP.getB2DPolygon(0));
1289
1290
0
        if(aResultP.count() > 0)
1291
0
        {
1292
0
            const basegfx::B2DPoint aResultStart(aResultP.getB2DPoint(0));
1293
0
            const basegfx::B2DPoint aResultEnd(aResultP.getB2DPoint(aResultP.count() - 1));
1294
1295
0
            if(aResultStart != aResultEnd)
1296
0
            {
1297
0
                rStart = aResultStart;
1298
0
                rDirection = aResultEnd - aResultStart;
1299
0
            }
1300
0
        }
1301
0
    }
1302
0
}
1303
1304
static void HelperCreateTLBREntry(
1305
    const Array& rArray,
1306
    const Style& rStyle,
1307
    drawinglayer::primitive2d::SdrFrameBorderDataVector& rData,
1308
    const basegfx::B2DPoint& rOrigin,
1309
    const basegfx::B2DVector& rX,
1310
    const basegfx::B2DVector& rY,
1311
    sal_Int32 nColLeft,
1312
    sal_Int32 nColRight,
1313
    sal_Int32 nRowTop,
1314
    sal_Int32 nRowBottom,
1315
    const Color* pForceColor,
1316
    const basegfx::B2DRange* pClipRange)
1317
0
{
1318
0
    if(rStyle.IsUsed())
1319
0
    {
1320
        /// prepare geometry line data
1321
0
        basegfx::B2DPoint aStart(rOrigin);
1322
0
        basegfx::B2DVector aDirection(rX + rY);
1323
1324
        /// check if we need to clip geometry line data and do it
1325
0
        if(nullptr != pClipRange)
1326
0
        {
1327
0
            HelperClipLine(aStart, aDirection, *pClipRange);
1328
0
        }
1329
1330
        /// top-left and bottom-right Style Tables
1331
0
        rData.emplace_back(
1332
0
            aStart,
1333
0
            aDirection,
1334
0
            rStyle,
1335
0
            pForceColor);
1336
0
        drawinglayer::primitive2d::SdrFrameBorderData& rInstance(rData.back());
1337
1338
        /// Fill top-left Style Table
1339
0
        const Style& rTLFromRight(rArray.GetCellStyleTop(nColLeft, nRowTop));
1340
0
        const Style& rTLFromBottom(rArray.GetCellStyleLeft(nColLeft, nRowTop));
1341
1342
0
        rInstance.addSdrConnectStyleData(true, rTLFromRight, rX, false);
1343
0
        rInstance.addSdrConnectStyleData(true, rTLFromBottom, rY, false);
1344
1345
        /// Fill bottom-right Style Table
1346
0
        const Style& rBRFromBottom(rArray.GetCellStyleRight(nColRight, nRowBottom));
1347
0
        const Style& rBRFromLeft(rArray.GetCellStyleBottom(nColRight, nRowBottom));
1348
1349
0
        rInstance.addSdrConnectStyleData(false, rBRFromBottom, -rY, true);
1350
0
        rInstance.addSdrConnectStyleData(false, rBRFromLeft, -rX, true);
1351
0
    }
1352
0
}
1353
1354
static void HelperCreateBLTREntry(
1355
    const Array& rArray,
1356
    const Style& rStyle,
1357
    drawinglayer::primitive2d::SdrFrameBorderDataVector& rData,
1358
    const basegfx::B2DPoint& rOrigin,
1359
    const basegfx::B2DVector& rX,
1360
    const basegfx::B2DVector& rY,
1361
    sal_Int32 nColLeft,
1362
    sal_Int32 nColRight,
1363
    sal_Int32 nRowTop,
1364
    sal_Int32 nRowBottom,
1365
    const Color* pForceColor,
1366
    const basegfx::B2DRange* pClipRange)
1367
0
{
1368
0
    if(rStyle.IsUsed())
1369
0
    {
1370
        /// prepare geometry line data
1371
0
        basegfx::B2DPoint aStart(rOrigin + rY);
1372
0
        basegfx::B2DVector aDirection(rX - rY);
1373
1374
        /// check if we need to clip geometry line data and do it
1375
0
        if(nullptr != pClipRange)
1376
0
        {
1377
0
            HelperClipLine(aStart, aDirection, *pClipRange);
1378
0
        }
1379
1380
        /// bottom-left and top-right Style Tables
1381
0
        rData.emplace_back(
1382
0
            aStart,
1383
0
            aDirection,
1384
0
            rStyle,
1385
0
            pForceColor);
1386
0
        drawinglayer::primitive2d::SdrFrameBorderData& rInstance(rData.back());
1387
1388
        /// Fill bottom-left Style Table
1389
0
        const Style& rBLFromTop(rArray.GetCellStyleLeft(nColLeft, nRowBottom));
1390
0
        const Style& rBLFromBottom(rArray.GetCellStyleBottom(nColLeft, nRowBottom));
1391
1392
0
        rInstance.addSdrConnectStyleData(true, rBLFromTop, -rY, true);
1393
0
        rInstance.addSdrConnectStyleData(true, rBLFromBottom, rX, false);
1394
1395
        /// Fill top-right Style Table
1396
0
        const Style& rTRFromLeft(rArray.GetCellStyleTop(nColRight, nRowTop));
1397
0
        const Style& rTRFromBottom(rArray.GetCellStyleRight(nColRight, nRowTop));
1398
1399
0
        rInstance.addSdrConnectStyleData(false, rTRFromLeft, -rX, true);
1400
0
        rInstance.addSdrConnectStyleData(false, rTRFromBottom, rY, false);
1401
0
    }
1402
0
}
1403
1404
drawinglayer::primitive2d::Primitive2DContainer Array::CreateB2DPrimitiveRange(
1405
    sal_Int32 nFirstCol, sal_Int32 nFirstRow, sal_Int32 nLastCol, sal_Int32 nLastRow,
1406
    const Color* pForceColor ) const
1407
0
{
1408
0
    DBG_FRAME_CHECK_COLROW( nFirstCol, nFirstRow, "CreateB2DPrimitiveRange" );
1409
0
    DBG_FRAME_CHECK_COLROW( nLastCol, nLastRow, "CreateB2DPrimitiveRange" );
1410
1411
    // Bail out if indices are out of range
1412
0
    if (nFirstCol < 0 || nFirstRow < 0 || nLastCol >= GetColCount() || nLastRow >= GetRowCount())
1413
0
    {
1414
0
        SAL_WARN("svx.dialog", "CreateB2DPrimitiveRange indices out of range: "
1415
0
            "nFirstCol=" << nFirstCol << " nFirstRow=" << nFirstRow
1416
0
            << " nLastCol=" << nLastCol << " nLastRow=" << nLastRow
1417
0
            << " ColCount=" << GetColCount() << " RowCount=" << GetRowCount());
1418
0
        return drawinglayer::primitive2d::Primitive2DContainer();
1419
0
    }
1420
1421
#ifdef OPTICAL_CHECK_CLIPRANGE_FOR_MERGED_CELL
1422
    std::vector<basegfx::B2DRange> aClipRanges;
1423
#endif
1424
1425
    // It may be necessary to extend the loop ranges by one cell to the outside,
1426
    // when possible. This is needed e.g. when there is in Calc a Cell with an
1427
    // upper CellBorder using DoubleLine and that is right/left connected upwards
1428
    // to also DoubleLine. These upper DoubleLines will be extended to meet the
1429
    // lower of the upper CellBorder and thus have graphical parts that are
1430
    // displayed one cell below and right/left of the target cell - analog to
1431
    // other examples in all other directions.
1432
    // It would be possible to explicitly test this (if possible by indices at all)
1433
    // looping and testing the styles in the outer cells to detect this, but since
1434
    // for other usages (e.g. UI) usually nFirstRow==0 and nLastRow==GetRowCount()-1
1435
    // (and analog for Col) it is okay to just expand the range when available.
1436
    // Do *not* change nFirstRow/nLastRow due to these needed to the boolean tests
1437
    // below (!)
1438
    // Checked usages, this method is used in Calc EditView/Print/Export stuff and
1439
    // in UI (Dialog), not for Writer Tables and Draw/Impress tables. All usages
1440
    // seem okay with this change, so I will add it.
1441
0
    const sal_Int32 nStartRow(nFirstRow > 0 ? nFirstRow - 1 : nFirstRow);
1442
0
    const sal_Int32 nEndRow(nLastRow < GetRowCount() - 1 ? nLastRow + 1 : nLastRow);
1443
0
    const sal_Int32 nStartCol(nFirstCol > 0 ? nFirstCol - 1 : nFirstCol);
1444
0
    const sal_Int32 nEndCol(nLastCol < GetColCount() - 1 ? nLastCol + 1 : nLastCol);
1445
1446
    // prepare SdrFrameBorderDataVector
1447
0
    drawinglayer::primitive2d::SdrFrameBorderDataVector aData;
1448
1449
    // remember for which merged cells crossed lines were already created. To
1450
    // do so, hold the sal_Int32 cell index in a set for fast check
1451
0
    std::unordered_set< sal_Int32 > aMergedCells;
1452
1453
0
    for (sal_Int32 nRow(nStartRow); nRow <= nEndRow; ++nRow)
1454
0
    {
1455
0
        for (sal_Int32 nCol(nStartCol); nCol <= nEndCol; ++nCol)
1456
0
        {
1457
            // get Cell and CoordinateSystem (*only* for this Cell, do *not* expand for
1458
            // merged cells (!)), check if used (non-empty vectors)
1459
0
            const Cell* pCell(mxImpl->GetCell(nCol, nRow));
1460
0
            basegfx::B2DHomMatrix aCoordinateSystem(pCell->CreateCoordinateSystemSingleCell(*this, nCol, nRow));
1461
0
            basegfx::B2DVector aX(basegfx::utils::getColumn(aCoordinateSystem, 0));
1462
0
            basegfx::B2DVector aY(basegfx::utils::getColumn(aCoordinateSystem, 1));
1463
1464
            // get needed local values
1465
0
            basegfx::B2DPoint aOrigin(basegfx::utils::getColumn(aCoordinateSystem, 2));
1466
0
            const bool bOverlapX(pCell->mbOverlapX);
1467
0
            const bool bFirstCol(nCol == nFirstCol);
1468
1469
            // handle rotation: If cell is rotated, handle lower/right edge inside
1470
            // this local geometry due to the created CoordinateSystem already representing
1471
            // the needed transformations.
1472
0
            const bool bRotated(pCell->IsRotated());
1473
1474
            // Additionally avoid double-handling by suppressing handling when self not rotated,
1475
            // but above/left is rotated and thus already handled. Two directly connected
1476
            // rotated will paint/create both edges, they might be rotated differently.
1477
0
            const bool bSuppressLeft(!bRotated && nCol > nFirstCol && mxImpl->GetCell(nCol - 1, nRow)->IsRotated());
1478
0
            const bool bSuppressAbove(!bRotated && nRow > nFirstRow && mxImpl->GetCell(nCol, nRow - 1)->IsRotated());
1479
1480
0
            if(!aX.equalZero() && !aY.equalZero())
1481
0
            {
1482
                // additionally needed local values
1483
0
                const bool bOverlapY(pCell->mbOverlapY);
1484
0
                const bool bLastCol(nCol == nLastCol);
1485
0
                const bool bFirstRow(nRow == nFirstRow);
1486
0
                const bool bLastRow(nRow == nLastRow);
1487
1488
                // create upper line for this Cell
1489
0
                if ((!bOverlapY         // true for first line in merged cells or cells
1490
0
                    || bFirstRow)       // true for non_Calc usages of this tooling
1491
0
                    && !bSuppressAbove) // true when above is not rotated, so edge is already handled (see bRotated)
1492
0
                {
1493
                    // get CellStyle - method will take care to get the correct one, e.g.
1494
                    // for merged cells (it uses mxImpl->GetMergedOriginCell that works with topLeft's of these)
1495
0
                    const Style& rTop(GetCellStyleTop(nCol, nRow));
1496
1497
0
                    if(rTop.IsUsed())
1498
0
                    {
1499
0
                        HelperCreateHorizontalEntry(*this, rTop, nCol, nRow, aOrigin, aX, aY, aData, true, pForceColor);
1500
0
                    }
1501
0
                }
1502
1503
                // create lower line for this Cell
1504
0
                if (bLastRow       // true for non_Calc usages of this tooling
1505
0
                    || bRotated)   // true if cell is rotated, handle lower edge in local geometry
1506
0
                {
1507
0
                    const Style& rBottom(GetCellStyleBottom(nCol, nRow));
1508
1509
0
                    if(rBottom.IsUsed())
1510
0
                    {
1511
0
                        HelperCreateHorizontalEntry(*this, rBottom, nCol, nRow + 1, aOrigin, aX, aY, aData, false, pForceColor);
1512
0
                    }
1513
0
                }
1514
1515
                // create left line for this Cell
1516
0
                if ((!bOverlapX         // true for first column in merged cells or cells
1517
0
                    || bFirstCol)       // true for non_Calc usages of this tooling
1518
0
                    && !bSuppressLeft)  // true when left is not rotated, so edge is already handled (see bRotated)
1519
0
                {
1520
0
                    const Style& rLeft(GetCellStyleLeft(nCol, nRow));
1521
1522
0
                    if(rLeft.IsUsed())
1523
0
                    {
1524
0
                        HelperCreateVerticalEntry(*this, rLeft, nCol, nRow, aOrigin, aX, aY, aData, true, pForceColor);
1525
0
                    }
1526
0
                }
1527
1528
                // create right line for this Cell
1529
0
                if (bLastCol        // true for non_Calc usages of this tooling
1530
0
                    || bRotated)    // true if cell is rotated, handle right edge in local geometry
1531
0
                {
1532
0
                    const Style& rRight(GetCellStyleRight(nCol, nRow));
1533
1534
0
                    if(rRight.IsUsed())
1535
0
                    {
1536
0
                        HelperCreateVerticalEntry(*this, rRight, nCol + 1, nRow, aOrigin, aX, aY, aData, false, pForceColor);
1537
0
                    }
1538
0
                }
1539
1540
                // tdf#126269 check for crossed lines, these need special treatment, especially
1541
                // for merged cells (see comments in task). Separate treatment of merged and
1542
                // non-merged cells to allow better handling of both types
1543
0
                if(pCell->IsMerged())
1544
0
                {
1545
                    // first check if this merged cell was already handled. To do so,
1546
                    // calculate and use the index of the TopLeft cell
1547
0
                    sal_Int32 nColLeft(nCol), nRowTop(nRow), nColRight(nCol), nRowBottom(nRow);
1548
0
                    GetMergedRange(nColLeft, nRowTop, nColRight, nRowBottom, nCol, nRow);
1549
0
                    const sal_Int32 nIndexOfMergedCell(mxImpl->GetIndex(nColLeft, nRowTop));
1550
1551
0
                    auto aItInsertedPair = aMergedCells.insert(nIndexOfMergedCell);
1552
0
                    if(aItInsertedPair.second)
1553
0
                    {
1554
                        // not found, so not yet handled.
1555
1556
                        // Get and check if diagonal styles are used
1557
                        // Note: For GetCellStyleBLTR below I tried to use nRowBottom
1558
                        //       as Y-value what seemed more logical, but that
1559
                        //       is wrong. Despite defining a line starting at
1560
                        //       bottom-left, the Style is defined in the cell at top-left
1561
0
                        const Style& rTLBR(GetCellStyleTLBR(nColLeft, nRowTop));
1562
0
                        const Style& rBLTR(GetCellStyleBLTR(nColLeft, nRowTop));
1563
1564
0
                        if(rTLBR.IsUsed() || rBLTR.IsUsed())
1565
0
                        {
1566
                            // test if merged cell overlaps ClipRange at all (needs visualization)
1567
0
                            if(mxImpl->OverlapsClipRange(nColLeft, nRowTop, nColRight, nRowBottom))
1568
0
                            {
1569
                                // when merged, get extended coordinate system and derived values
1570
                                // for the full range of this merged cell. Only work with rMergedCell
1571
                                // (which is the top-left single cell of the merged cell) from here on
1572
0
                                aCoordinateSystem = mxImpl->GetCell(nColLeft, nRowTop)->CreateCoordinateSystemMergedCell(
1573
0
                                    *this, nColLeft, nRowTop, nColRight, nRowBottom);
1574
0
                                aX = basegfx::utils::getColumn(aCoordinateSystem, 0);
1575
0
                                aY = basegfx::utils::getColumn(aCoordinateSystem, 1);
1576
0
                                aOrigin = basegfx::utils::getColumn(aCoordinateSystem, 2);
1577
1578
                                // check if clip is needed
1579
0
                                basegfx::B2DRange aClipRange;
1580
1581
                                // first use row/col ClipTest for raw check
1582
0
                                bool bNeedToClip(
1583
0
                                    !mxImpl->IsColInClipRange(nColLeft) ||
1584
0
                                    !mxImpl->IsRowInClipRange(nRowTop) ||
1585
0
                                    !mxImpl->IsColInClipRange(nColRight) ||
1586
0
                                    !mxImpl->IsRowInClipRange(nRowBottom));
1587
1588
0
                                if(bNeedToClip)
1589
0
                                {
1590
                                    // now get ClipRange and CellRange in logical coordinates
1591
0
                                    aClipRange = GetB2DRange(
1592
0
                                        mxImpl->mnFirstClipCol, mxImpl->mnFirstClipRow,
1593
0
                                        mxImpl->mnLastClipCol, mxImpl->mnLastClipRow);
1594
1595
0
                                    basegfx::B2DRange aCellRange(
1596
0
                                        GetB2DRange(
1597
0
                                            nColLeft, nRowTop,
1598
0
                                            nColRight, nRowBottom));
1599
1600
                                    // intersect these to get the target ClipRange, ensure
1601
                                    // that clip is needed
1602
0
                                    aClipRange.intersect(aCellRange);
1603
0
                                    bNeedToClip = !aClipRange.isEmpty();
1604
1605
#ifdef OPTICAL_CHECK_CLIPRANGE_FOR_MERGED_CELL
1606
                                    aClipRanges.push_back(aClipRange);
1607
#endif
1608
0
                                }
1609
1610
                                // create top-left to bottom-right geometry
1611
0
                                HelperCreateTLBREntry(*this, rTLBR, aData, aOrigin, aX, aY,
1612
0
                                    nColLeft, nRowTop, nColRight, nRowBottom, pForceColor,
1613
0
                                    bNeedToClip ? &aClipRange : nullptr);
1614
1615
                                // create bottom-left to top-right geometry
1616
0
                                HelperCreateBLTREntry(*this, rBLTR, aData, aOrigin, aX, aY,
1617
0
                                    nColLeft, nRowTop, nColRight, nRowBottom, pForceColor,
1618
0
                                    bNeedToClip ? &aClipRange : nullptr);
1619
0
                            }
1620
0
                        }
1621
0
                    }
1622
0
                }
1623
0
                else
1624
0
                {
1625
                    // must be in clipping range: else not visible. This
1626
                    // already clips completely for non-merged cells
1627
0
                    if( mxImpl->IsInClipRange( nCol, nRow ) )
1628
0
                    {
1629
                        // get and check if diagonal styles are used
1630
0
                        const Style& rTLBR(GetCellStyleTLBR(nCol, nRow));
1631
0
                        const Style& rBLTR(GetCellStyleBLTR(nCol, nRow));
1632
1633
0
                        if(rTLBR.IsUsed() || rBLTR.IsUsed())
1634
0
                        {
1635
0
                            HelperCreateTLBREntry(*this, rTLBR, aData, aOrigin, aX, aY,
1636
0
                                nCol, nRow, nCol, nRow, pForceColor, nullptr);
1637
1638
0
                            HelperCreateBLTREntry(*this, rBLTR, aData, aOrigin, aX, aY,
1639
0
                                nCol, nRow, nCol, nRow, pForceColor, nullptr);
1640
0
                        }
1641
0
                    }
1642
0
                }
1643
0
            }
1644
0
            else if(!aY.equalZero())
1645
0
            {
1646
                // cell has height, but no width. Create left vertical line for this Cell
1647
0
                if ((!bOverlapX         // true for first column in merged cells or cells
1648
0
                    || bFirstCol)       // true for non_Calc usages of this tooling
1649
0
                    && !bSuppressLeft)  // true when left is not rotated, so edge is already handled (see bRotated)
1650
0
                {
1651
0
                    const Style& rLeft(GetCellStyleLeft(nCol, nRow));
1652
1653
0
                    if (rLeft.IsUsed())
1654
0
                    {
1655
0
                        HelperCreateVerticalEntry(*this, rLeft, nCol, nRow, aOrigin, aX, aY, aData, true, pForceColor);
1656
0
                    }
1657
0
                }
1658
0
            }
1659
0
            else
1660
0
            {
1661
                // Cell has *no* size, thus no visualization
1662
0
            }
1663
0
        }
1664
0
    }
1665
1666
    // create instance of SdrFrameBorderPrimitive2D if
1667
    // SdrFrameBorderDataVector is used
1668
0
    drawinglayer::primitive2d::Primitive2DContainer aSequence;
1669
1670
0
    if(!aData.empty())
1671
0
    {
1672
0
        aSequence.append(
1673
0
                new drawinglayer::primitive2d::SdrFrameBorderPrimitive2D(
1674
0
                    std::move(aData),
1675
0
                    true));    // force visualization to minimal one discrete unit (pixel)
1676
0
    }
1677
1678
#ifdef OPTICAL_CHECK_CLIPRANGE_FOR_MERGED_CELL
1679
    for(auto const& rClipRange : aClipRanges)
1680
    {
1681
        // draw ClipRange in yellow to allow simple interactive optical control in office
1682
        aSequence.append(
1683
                new drawinglayer::primitive2d::PolygonHairlinePrimitive2D(
1684
                    basegfx::utils::createPolygonFromRect(rClipRange),
1685
                    basegfx::BColor(1.0, 1.0, 0.0)));
1686
    }
1687
#endif
1688
1689
0
    return aSequence;
1690
0
}
1691
1692
drawinglayer::primitive2d::Primitive2DContainer Array::CreateB2DPrimitiveArray() const
1693
0
{
1694
0
    drawinglayer::primitive2d::Primitive2DContainer aPrimitives;
1695
1696
0
    if (mxImpl->mnWidth && mxImpl->mnHeight)
1697
0
    {
1698
0
        aPrimitives = CreateB2DPrimitiveRange(0, 0, mxImpl->mnWidth - 1, mxImpl->mnHeight - 1, nullptr);
1699
0
    }
1700
1701
0
    return aPrimitives;
1702
0
}
1703
1704
#undef DBG_FRAME_CHECK_ROW_1
1705
#undef DBG_FRAME_CHECK_COL_1
1706
#undef DBG_FRAME_CHECK_COLROW
1707
#undef DBG_FRAME_CHECK_ROW
1708
#undef DBG_FRAME_CHECK_COL
1709
#undef DBG_FRAME_CHECK
1710
1711
}
1712
1713
/* vim:set shiftwidth=4 softtabstop=4 expandtab: */