Version 7.6.3.2-android, tag libreoffice-7.6.3.2-android
[LibreOffice.git] / svx / source / table / tablelayouter.cxx
blob72c692c96748257ffa98c3ef98e2c79204c8536c
1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
2 /*
3 * This file is part of the LibreOffice project.
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/.
9 * This file incorporates work covered by the following license notice:
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 .
21 #include <com/sun/star/table/XMergeableCell.hpp>
23 #include <tools/debug.hxx>
24 #include <comphelper/diagnose_ex.hxx>
25 #include <tools/gen.hxx>
26 #include <libxml/xmlwriter.h>
28 #include <cell.hxx>
29 #include <o3tl/safeint.hxx>
30 #include <tablemodel.hxx>
31 #include "tablelayouter.hxx"
32 #include <svx/svdotable.hxx>
33 #include <editeng/borderline.hxx>
34 #include <editeng/boxitem.hxx>
35 #include <utility>
37 using ::editeng::SvxBorderLine;
38 using namespace ::com::sun::star::uno;
39 using namespace ::com::sun::star::lang;
40 using namespace ::com::sun::star::container;
41 using namespace ::com::sun::star::beans;
42 using namespace ::com::sun::star::table;
43 using namespace ::com::sun::star::text;
46 namespace sdr::table {
49 static SvxBorderLine gEmptyBorder;
51 constexpr OUStringLiteral gsSize( u"Size" );
53 TableLayouter::TableLayouter( TableModelRef xTableModel )
54 : mxTable(std::move( xTableModel ))
59 TableLayouter::~TableLayouter()
61 ClearBorderLayout();
65 basegfx::B2ITuple TableLayouter::getCellSize( const CellRef& xCell, const CellPos& rPos ) const
67 sal_Int32 width = 0;
68 sal_Int32 height = 0;
70 try
72 if( xCell.is() && !xCell->isMerged() )
74 CellPos aPos( rPos );
76 sal_Int32 nRowCount = getRowCount();
77 sal_Int32 nRowSpan = std::max( xCell->getRowSpan(), sal_Int32(1) );
78 while( nRowSpan && (aPos.mnRow < nRowCount) )
80 if( static_cast<sal_Int32>(maRows.size()) <= aPos.mnRow )
81 break;
83 height = o3tl::saturating_add(height, maRows[aPos.mnRow++].mnSize);
84 nRowSpan--;
87 sal_Int32 nColCount = getColumnCount();
88 sal_Int32 nColSpan = std::max( xCell->getColumnSpan(), sal_Int32(1) );
89 while( nColSpan && (aPos.mnCol < nColCount ) )
91 if( static_cast<sal_Int32>(maColumns.size()) <= aPos.mnCol )
92 break;
94 width = o3tl::saturating_add(width, maColumns[aPos.mnCol++].mnSize);
95 nColSpan--;
99 catch( Exception& )
101 TOOLS_WARN_EXCEPTION("svx", "");
104 return basegfx::B2ITuple( width, height );
108 bool TableLayouter::getCellArea( const CellRef& xCell, const CellPos& rPos, basegfx::B2IRectangle& rArea ) const
112 if( xCell.is() && !xCell->isMerged() && isValid(rPos) )
114 const basegfx::B2ITuple aCellSize( getCellSize( xCell, rPos ) );
115 const bool bRTL = (mxTable->getSdrTableObj()->GetWritingMode() == WritingMode_RL_TB);
117 if( (rPos.mnCol < static_cast<sal_Int32>(maColumns.size())) && (rPos.mnRow < static_cast<sal_Int32>(maRows.size()) ) )
119 const sal_Int32 y = maRows[rPos.mnRow].mnPos;
121 sal_Int32 endy;
122 if (o3tl::checked_add(y, aCellSize.getY(), endy))
123 return false;
125 if(bRTL)
127 ///For RTL Table Calculate the Right End of cell instead of Left
128 const sal_Int32 x = maColumns[rPos.mnCol].mnPos + maColumns[rPos.mnCol].mnSize;
129 sal_Int32 startx;
130 if (o3tl::checked_sub(x, aCellSize.getX(), startx))
131 return false;
132 rArea = basegfx::B2IRectangle(startx, y, x, endy);
134 else
136 const sal_Int32 x = maColumns[rPos.mnCol].mnPos;
137 sal_Int32 endx;
138 if (o3tl::checked_add(x, aCellSize.getX(), endx))
139 return false;
140 rArea = basegfx::B2IRectangle(x, y, endx, endy);
142 return true;
146 catch( Exception& )
148 TOOLS_WARN_EXCEPTION("svx", "");
150 return false;
154 sal_Int32 TableLayouter::getRowHeight( sal_Int32 nRow ) const
156 if( isValidRow(nRow) )
157 return maRows[nRow].mnSize;
158 else
159 return 0;
163 sal_Int32 TableLayouter::getColumnWidth( sal_Int32 nColumn ) const
165 if( isValidColumn(nColumn) )
166 return maColumns[nColumn].mnSize;
167 else
168 return 0;
171 sal_Int32 TableLayouter::calcPreferredColumnWidth( sal_Int32 nColumn, Size aSize ) const
173 sal_Int32 nRet = 0;
174 for ( sal_uInt32 nRow = 0; nRow < static_cast<sal_uInt32>(maRows.size()); ++nRow )
176 // Account for the space desired by spanned columns.
177 // Only the last spanned cell will try to ensure sufficient space,
178 // by looping through the previous columns, subtracting their portion.
179 sal_Int32 nWish = 0;
180 sal_Int32 nSpannedColumn = nColumn;
181 bool bFindSpan = true;
182 while ( bFindSpan && isValidColumn(nSpannedColumn) )
184 // recursive function call gets earlier portion of spanned column.
185 if ( nSpannedColumn < nColumn )
186 nWish -= calcPreferredColumnWidth( nSpannedColumn, aSize );
188 CellRef xCell( getCell( CellPos( nSpannedColumn, nRow ) ) );
189 if ( xCell.is() && !xCell->isMerged()
190 && (xCell->getColumnSpan() == 1 || nSpannedColumn < nColumn) )
192 nWish += xCell->calcPreferredWidth(aSize);
193 bFindSpan = false;
195 else if ( xCell.is() && xCell->isMerged()
196 && nColumn == nSpannedColumn
197 && isValidColumn(nColumn + 1) )
199 xCell = getCell( CellPos( nColumn + 1, nRow ) );
200 bFindSpan = xCell.is() && !xCell->isMerged();
202 nSpannedColumn--;
204 nRet = std::max( nRet, nWish );
206 return nRet;
210 bool TableLayouter::isEdgeVisible( sal_Int32 nEdgeX, sal_Int32 nEdgeY, bool bHorizontal ) const
212 const BorderLineMap& rMap = bHorizontal ? maHorizontalBorders : maVerticalBorders;
214 if( (nEdgeX >= 0) && (nEdgeX < sal::static_int_cast<sal_Int32>(rMap.size())) &&
215 (nEdgeY >= 0) && (nEdgeY < sal::static_int_cast<sal_Int32>(rMap[nEdgeX].size())) )
217 return rMap[nEdgeX][nEdgeY] != nullptr;
219 else
221 OSL_FAIL( "sdr::table::TableLayouter::getBorderLine(), invalid edge!" );
224 return false;
228 /** returns the requested borderline in rpBorderLine or a null pointer if there is no border at this edge */
229 SvxBorderLine* TableLayouter::getBorderLine( sal_Int32 nEdgeX, sal_Int32 nEdgeY, bool bHorizontal )const
231 SvxBorderLine* pLine = nullptr;
233 const BorderLineMap& rMap = bHorizontal ? maHorizontalBorders : maVerticalBorders;
235 if( (nEdgeX >= 0) && (nEdgeX < sal::static_int_cast<sal_Int32>(rMap.size())) &&
236 (nEdgeY >= 0) && (nEdgeY < sal::static_int_cast<sal_Int32>(rMap[nEdgeX].size())) )
238 pLine = rMap[nEdgeX][nEdgeY];
239 if( pLine == &gEmptyBorder )
240 pLine = nullptr;
242 else
244 OSL_FAIL( "sdr::table::TableLayouter::getBorderLine(), invalid edge!" );
247 return pLine;
250 std::vector<EdgeInfo> TableLayouter::getHorizontalEdges()
252 std::vector<EdgeInfo> aReturn;
253 sal_Int32 nRowSize = sal_Int32(maRows.size());
254 for (sal_Int32 i = 0; i <= nRowSize; i++)
256 sal_Int32 nEdgeMin = 0;
257 sal_Int32 nEdgeMax = 0;
258 sal_Int32 nEdge = getHorizontalEdge(i, &nEdgeMin, &nEdgeMax);
259 nEdgeMin -= nEdge;
260 nEdgeMax -= nEdge;
261 aReturn.emplace_back(i, nEdge, nEdgeMin, nEdgeMax);
263 return aReturn;
266 std::vector<EdgeInfo> TableLayouter::getVerticalEdges()
268 std::vector<EdgeInfo> aReturn;
269 sal_Int32 nColumnSize = sal_Int32(maColumns.size());
270 for (sal_Int32 i = 0; i <= nColumnSize; i++)
272 sal_Int32 nEdgeMin = 0;
273 sal_Int32 nEdgeMax = 0;
274 sal_Int32 nEdge = getVerticalEdge(i, &nEdgeMin, &nEdgeMax);
275 nEdgeMin -= nEdge;
276 nEdgeMax -= nEdge;
277 aReturn.emplace_back(i, nEdge, nEdgeMin, nEdgeMax);
279 return aReturn;
282 sal_Int32 TableLayouter::getHorizontalEdge( int nEdgeY, sal_Int32* pnMin /*= 0*/, sal_Int32* pnMax /*= 0*/ )
284 sal_Int32 nRet = 0;
285 const sal_Int32 nRowCount = getRowCount();
286 if( (nEdgeY >= 0) && (nEdgeY <= nRowCount ) )
287 nRet = maRows[std::min(static_cast<sal_Int32>(nEdgeY),nRowCount-1)].mnPos;
289 if( nEdgeY == nRowCount )
290 nRet += maRows[nEdgeY - 1].mnSize;
292 if( pnMin )
294 if( (nEdgeY > 0) && (nEdgeY <= nRowCount ) )
296 *pnMin = maRows[nEdgeY-1].mnPos + 600; // todo
298 else
300 *pnMin = nRet;
304 if( pnMax )
306 *pnMax = 0x0fffffff;
308 return nRet;
312 sal_Int32 TableLayouter::getVerticalEdge( int nEdgeX, sal_Int32* pnMin /*= 0*/, sal_Int32* pnMax /*= 0*/ )
314 sal_Int32 nRet = 0;
316 const sal_Int32 nColCount = getColumnCount();
317 if( (nEdgeX >= 0) && (nEdgeX <= nColCount ) )
318 nRet = maColumns[std::min(static_cast<sal_Int32>(nEdgeX),nColCount-1)].mnPos;
320 const bool bRTL = (mxTable->getSdrTableObj()->GetWritingMode() == WritingMode_RL_TB);
321 if( bRTL )
323 if( (nEdgeX >= 0) && (nEdgeX < nColCount) )
324 nRet += maColumns[nEdgeX].mnSize;
326 else
328 if( nEdgeX == nColCount )
329 nRet += maColumns[nEdgeX - 1].mnSize;
332 if( pnMin )
334 *pnMin = nRet;
335 if( bRTL )
337 if( nEdgeX < nColCount )
338 *pnMin = nRet - maColumns[nEdgeX].mnSize + getMinimumColumnWidth(nEdgeX);
340 else
342 if( (nEdgeX > 0) && (nEdgeX <= nColCount ) )
343 *pnMin = maColumns[nEdgeX-1].mnPos + getMinimumColumnWidth( nEdgeX-1 );
347 if( pnMax )
349 *pnMax = 0x0fffffff; // todo
350 if( bRTL )
352 if( nEdgeX > 0 )
353 *pnMax = nRet + maColumns[nEdgeX-1].mnSize - getMinimumColumnWidth( nEdgeX-1 );
355 else
357 if( (nEdgeX >= 0) && (nEdgeX < nColCount ) )
358 *pnMax = maColumns[nEdgeX].mnPos + maColumns[nEdgeX].mnSize - getMinimumColumnWidth( nEdgeX );
362 return nRet;
366 static bool checkMergeOrigin( const TableModelRef& xTable, sal_Int32 nMergedX, sal_Int32 nMergedY, sal_Int32 nCellX, sal_Int32 nCellY, bool& bRunning )
368 Reference< XMergeableCell > xCell( xTable->getCellByPosition( nCellX, nCellY ), UNO_QUERY );
369 if( xCell.is() && !xCell->isMerged() )
371 const sal_Int32 nRight = xCell->getColumnSpan() + nCellX;
372 const sal_Int32 nBottom = xCell->getRowSpan() + nCellY;
373 if( (nMergedX < nRight) && (nMergedY < nBottom) )
374 return true;
376 bRunning = false;
378 return false;
381 /** returns true if the cell(nMergedX,nMergedY) is merged with other cells.
382 the returned cell( rOriginX, rOriginY ) is the origin( top left cell ) of the merge.
384 bool findMergeOrigin( const TableModelRef& xTable, sal_Int32 nMergedX, sal_Int32 nMergedY, sal_Int32& rOriginX, sal_Int32& rOriginY )
386 rOriginX = nMergedX;
387 rOriginY = nMergedY;
389 if( xTable.is() ) try
391 // check if this cell already the origin or not merged at all
392 Reference< XMergeableCell > xCell( xTable->getCellByPosition( nMergedX, nMergedY ), UNO_QUERY_THROW );
393 if( !xCell->isMerged() )
394 return true;
396 bool bCheckVert = true;
397 bool bCheckHorz = true;
399 sal_Int32 nMinCol = 0;
400 sal_Int32 nMinRow = 0;
402 sal_Int32 nStep = 1, i;
404 sal_Int32 nRow, nCol;
407 if( bCheckVert )
409 nRow = nMergedY - nStep;
410 if( nRow >= nMinRow )
412 nCol = nMergedX;
413 for( i = 0; (i <= nStep) && (nCol >= nMinCol); i++, nCol-- )
415 if( checkMergeOrigin( xTable, nMergedX, nMergedY, nCol, nRow, bCheckVert ) )
417 rOriginX = nCol; rOriginY = nRow;
418 return true;
421 if( !bCheckVert )
423 if( nCol == nMergedX )
425 nMinRow = nRow+1;
427 else
429 bCheckVert = true;
431 break;
435 else
437 bCheckVert = false;
441 if( bCheckHorz )
443 nCol = nMergedX - nStep;
444 if( nCol >= nMinCol )
446 nRow = nMergedY;
447 for( i = 0; (i < nStep) && (nRow >= nMinRow); i++, nRow-- )
449 if( checkMergeOrigin( xTable, nMergedX, nMergedY, nCol, nRow, bCheckHorz ) )
451 rOriginX = nCol; rOriginY = nRow;
452 return true;
455 if( !bCheckHorz )
457 if( nRow == nMergedY )
459 nMinCol = nCol+1;
461 else
463 bCheckHorz = true;
465 break;
469 else
471 bCheckHorz = false;
474 nStep++;
476 while( bCheckVert || bCheckHorz );
478 catch( Exception& )
480 TOOLS_WARN_EXCEPTION("svx", "");
482 return false;
486 sal_Int32 TableLayouter::getMinimumColumnWidth( sal_Int32 nColumn )
488 if( isValidColumn( nColumn ) )
490 return maColumns[nColumn].mnMinSize;
492 else
494 OSL_FAIL( "TableLayouter::getMinimumColumnWidth(), column out of range!" );
495 return 0;
500 sal_Int32 TableLayouter::distribute( LayoutVector& rLayouts, sal_Int32 nDistribute )
502 // break loops after 100 runs to avoid freezing office due to developer error
503 sal_Int32 nSafe = 100;
505 const std::size_t nCount = rLayouts.size();
506 std::size_t nIndex;
508 bool bConstrainsBroken = false;
512 bConstrainsBroken = false;
514 // first enforce minimum size constrains on all entities
515 for( nIndex = 0; nIndex < nCount; ++nIndex )
517 Layout& rLayout = rLayouts[nIndex];
518 if( rLayout.mnSize < rLayout.mnMinSize )
520 sal_Int32 nDiff(0);
521 bConstrainsBroken |= o3tl::checked_sub(rLayout.mnMinSize, rLayout.mnSize, nDiff);
522 bConstrainsBroken |= o3tl::checked_sub(nDistribute, nDiff, nDistribute);
523 rLayout.mnSize = rLayout.mnMinSize;
527 // calculate current width
528 // if nDistribute is < 0 (shrinking), entities that are already
529 // at minimum width are not counted
530 sal_Int32 nCurrentWidth = 0;
531 for( nIndex = 0; nIndex < nCount; ++nIndex )
533 Layout& rLayout = rLayouts[nIndex];
534 if( (nDistribute > 0) || (rLayout.mnSize > rLayout.mnMinSize) )
535 nCurrentWidth = o3tl::saturating_add(nCurrentWidth, rLayout.mnSize);
538 // now distribute over entities
539 if( (nCurrentWidth != 0) && (nDistribute != 0) )
541 sal_Int32 nDistributed = nDistribute;
542 for( nIndex = 0; nIndex < nCount; ++nIndex )
544 Layout& rLayout = rLayouts[nIndex];
545 if( (nDistribute > 0) || (rLayout.mnSize > rLayout.mnMinSize) )
547 sal_Int32 n(nDistributed); // for last entity use up rest
548 if (nIndex != (nCount-1))
550 bConstrainsBroken |= o3tl::checked_multiply(nDistribute, rLayout.mnSize, n);
551 n /= nCurrentWidth;
554 bConstrainsBroken |= o3tl::checked_add(rLayout.mnSize, n, rLayout.mnSize);
555 nDistributed -= n;
557 if( rLayout.mnSize < rLayout.mnMinSize )
558 bConstrainsBroken = true;
562 } while( bConstrainsBroken && --nSafe );
564 sal_Int32 nSize = 0;
565 for( nIndex = 0; nIndex < nCount; ++nIndex )
566 nSize = o3tl::saturating_add(nSize, rLayouts[nIndex].mnSize);
568 return nSize;
572 typedef std::vector< CellRef > MergeableCellVector;
573 typedef std::vector< MergeableCellVector > MergeVector;
576 void TableLayouter::LayoutTableWidth( tools::Rectangle& rArea, bool bFit )
578 const sal_Int32 nColCount = getColumnCount();
579 const sal_Int32 nRowCount = getRowCount();
580 if( nColCount == 0 )
581 return;
583 MergeVector aMergedCells( nColCount );
584 std::vector<sal_Int32> aOptimalColumns;
586 static const OUStringLiteral sOptimalSize(u"OptimalSize");
588 if( sal::static_int_cast< sal_Int32 >( maColumns.size() ) != nColCount )
589 maColumns.resize( nColCount );
591 Reference< XTableColumns > xCols( mxTable->getColumns(), UNO_SET_THROW );
593 // first calculate current width and initial minimum width per column,
594 // merged cells will be counted later
595 sal_Int32 nCurrentWidth = 0;
596 sal_Int32 nCol = 0, nRow = 0;
597 for( nCol = 0; nCol < nColCount; nCol++ )
599 sal_Int32 nMinWidth = 0;
601 bool bIsEmpty = true; // check if all cells in this column are merged
603 for( nRow = 0; nRow < nRowCount; ++nRow )
605 CellRef xCell( getCell( CellPos( nCol, nRow ) ) );
606 if( xCell.is() && !xCell->isMerged() )
608 bIsEmpty = false;
610 sal_Int32 nColSpan = xCell->getColumnSpan();
611 if( nColSpan > 1 )
613 // merged cells will be evaluated later
614 aMergedCells[nCol+nColSpan-1].push_back( xCell );
616 else
618 nMinWidth = std::max( nMinWidth, xCell->getMinimumWidth() );
623 maColumns[nCol].mnMinSize = nMinWidth;
625 if( bIsEmpty )
627 maColumns[nCol].mnSize = 0;
629 else
631 sal_Int32 nColWidth = 0;
632 Reference< XPropertySet > xColSet( xCols->getByIndex( nCol ), UNO_QUERY_THROW );
633 bool bOptimal = false;
634 xColSet->getPropertyValue( sOptimalSize ) >>= bOptimal;
635 if( bOptimal )
637 aOptimalColumns.push_back(nCol);
639 else
641 xColSet->getPropertyValue( gsSize ) >>= nColWidth;
644 maColumns[nCol].mnSize = std::max( nColWidth, nMinWidth);
646 nCurrentWidth = o3tl::saturating_add(nCurrentWidth, maColumns[nCol].mnSize);
650 // if we have optimal sized rows, distribute what is given (left)
651 if( !bFit && !aOptimalColumns.empty() && (nCurrentWidth < rArea.getOpenWidth()) )
653 sal_Int32 nLeft = rArea.getOpenWidth() - nCurrentWidth;
654 sal_Int32 nDistribute = nLeft / aOptimalColumns.size();
656 auto iter( aOptimalColumns.begin() );
657 while( iter != aOptimalColumns.end() )
659 sal_Int32 nOptCol = *iter++;
660 if( iter == aOptimalColumns.end() )
661 nDistribute = nLeft;
663 maColumns[nOptCol].mnSize += nDistribute;
664 nLeft -= nDistribute;
667 DBG_ASSERT( nLeft == 0, "svx::TableLayouter::LayoutTableWidtht(), layouting failed!" );
670 // now check if merged cells fit
671 for( nCol = 1; nCol < nColCount; ++nCol )
673 bool bChanges = false;
675 const sal_Int32 nOldSize = maColumns[nCol].mnSize;
677 for( const CellRef& xCell : aMergedCells[nCol] )
679 sal_Int32 nMinWidth = xCell->getMinimumWidth();
681 for( sal_Int32 nMCol = nCol - xCell->getColumnSpan() + 1; (nMCol > 0) && (nMCol < nCol); ++nMCol )
682 nMinWidth -= maColumns[nMCol].mnSize;
684 if( nMinWidth > maColumns[nCol].mnMinSize )
685 maColumns[nCol].mnMinSize = nMinWidth;
687 if( nMinWidth > maColumns[nCol].mnSize )
689 maColumns[nCol].mnSize = nMinWidth;
690 bChanges = true;
694 if( bChanges )
696 nCurrentWidth = o3tl::saturating_add(nCurrentWidth, maColumns[nCol].mnSize - nOldSize);
700 // now scale if wanted and needed
701 if( bFit && (nCurrentWidth != rArea.getOpenWidth()) )
702 distribute( maColumns, rArea.getOpenWidth() - nCurrentWidth );
704 // last step, update left edges
705 sal_Int32 nNewWidth = 0;
707 const bool bRTL = (mxTable->getSdrTableObj()->GetWritingMode() == WritingMode_RL_TB);
708 RangeIterator<sal_Int32> coliter( 0, nColCount, !bRTL );
709 while( coliter.next(nCol ) )
711 maColumns[nCol].mnPos = nNewWidth;
712 nNewWidth = o3tl::saturating_add(nNewWidth, maColumns[nCol].mnSize);
713 if( bFit )
715 Reference< XPropertySet > xColSet( xCols->getByIndex(nCol), UNO_QUERY_THROW );
716 xColSet->setPropertyValue( gsSize, Any( maColumns[nCol].mnSize ) );
720 rArea.SetSize( Size( nNewWidth, rArea.GetHeight() ) );
721 updateCells( rArea );
725 void TableLayouter::LayoutTableHeight( tools::Rectangle& rArea, bool bFit )
727 const sal_Int32 nColCount = getColumnCount();
728 const sal_Int32 nRowCount = getRowCount();
730 if( nRowCount == 0 )
731 return;
733 Reference< XTableRows > xRows( mxTable->getRows() );
735 MergeVector aMergedCells( nRowCount );
736 std::vector<sal_Int32> aOptimalRows;
738 static const OUStringLiteral sOptimalSize(u"OptimalSize");
740 // first calculate current height and initial minimum size per column,
741 // merged cells will be counted later
742 sal_Int32 nCurrentHeight = 0;
743 sal_Int32 nCol, nRow;
744 for( nRow = 0; nRow < nRowCount; ++nRow )
746 sal_Int32 nMinHeight = 0;
748 bool bIsEmpty = true; // check if all cells in this row are merged
750 for( nCol = 0; nCol < nColCount; ++nCol )
752 CellRef xCell( getCell( CellPos( nCol, nRow ) ) );
753 if( xCell.is() && !xCell->isMerged() )
755 bIsEmpty = false;
757 sal_Int32 nRowSpan = xCell->getRowSpan();
758 if( nRowSpan > 1 )
760 // merged cells will be evaluated later
761 aMergedCells[nRow+nRowSpan-1].push_back( xCell );
763 else
765 nMinHeight = std::max( nMinHeight, xCell->getMinimumHeight() );
770 maRows[nRow].mnMinSize = nMinHeight;
772 if( bIsEmpty )
774 maRows[nRow].mnSize = 0;
776 else
778 sal_Int32 nRowHeight = 0;
779 Reference<XPropertySet> xRowSet(xRows->getByIndex(nRow), UNO_QUERY_THROW);
781 bool bOptimal = false;
782 xRowSet->getPropertyValue( sOptimalSize ) >>= bOptimal;
783 if( bOptimal )
785 aOptimalRows.push_back( nRow );
787 else
789 xRowSet->getPropertyValue( gsSize ) >>= nRowHeight;
792 maRows[nRow].mnSize = nRowHeight;
794 if( maRows[nRow].mnSize < nMinHeight )
795 maRows[nRow].mnSize = nMinHeight;
797 nCurrentHeight = o3tl::saturating_add(nCurrentHeight, maRows[nRow].mnSize);
801 // if we have optimal sized rows, distribute what is given (left)
802 if( !bFit && !aOptimalRows.empty() && (nCurrentHeight < rArea.getOpenHeight()) )
804 sal_Int32 nLeft = rArea.getOpenHeight() - nCurrentHeight;
805 sal_Int32 nDistribute = nLeft / aOptimalRows.size();
807 auto iter( aOptimalRows.begin() );
808 while( iter != aOptimalRows.end() )
810 sal_Int32 nOptRow = *iter++;
811 if( iter == aOptimalRows.end() )
812 nDistribute = nLeft;
814 maRows[nOptRow].mnSize += nDistribute;
815 nLeft -= nDistribute;
819 DBG_ASSERT( nLeft == 0, "svx::TableLayouter::LayoutTableHeight(), layouting failed!" );
822 // now check if merged cells fit
823 for( nRow = 1; nRow < nRowCount; ++nRow )
825 bool bChanges = false;
826 sal_Int32 nOldSize = maRows[nRow].mnSize;
828 for( const CellRef& xCell : aMergedCells[nRow] )
830 sal_Int32 nMinHeight = xCell->getMinimumHeight();
832 for( sal_Int32 nMRow = nRow - xCell->getRowSpan() + 1; (nMRow > 0) && (nMRow < nRow); ++nMRow )
833 nMinHeight -= maRows[nMRow].mnSize;
835 if( nMinHeight > maRows[nRow].mnMinSize )
836 maRows[nRow].mnMinSize = nMinHeight;
838 if( nMinHeight > maRows[nRow].mnSize )
840 maRows[nRow].mnSize = nMinHeight;
841 bChanges = true;
844 if( bChanges )
845 nCurrentHeight = o3tl::saturating_add(nCurrentHeight, maRows[nRow].mnSize - nOldSize);
848 // now scale if wanted and needed
849 if( bFit && nCurrentHeight != rArea.getOpenHeight() )
850 distribute(maRows, o3tl::saturating_sub<sal_Int32>(rArea.getOpenHeight(), nCurrentHeight));
852 // last step, update left edges
853 sal_Int32 nNewHeight = 0;
854 for( nRow = 0; nRow < nRowCount; ++nRow )
856 maRows[nRow].mnPos = nNewHeight;
857 nNewHeight = o3tl::saturating_add(nNewHeight, maRows[nRow].mnSize);
859 if( bFit )
861 Reference< XPropertySet > xRowSet( xRows->getByIndex(nRow), UNO_QUERY_THROW );
862 xRowSet->setPropertyValue( gsSize, Any( maRows[nRow].mnSize ) );
866 rArea.SetSize( Size( rArea.GetWidth(), nNewHeight ) );
867 updateCells( rArea );
871 /** try to fit the table into the given rectangle.
872 If the rectangle is too small, it will be grown to fit the table. */
873 void TableLayouter::LayoutTable( tools::Rectangle& rRectangle, bool bFitWidth, bool bFitHeight )
875 if( !mxTable.is() )
876 return;
878 const sal_Int32 nRowCount = mxTable->getRowCount();
879 const sal_Int32 nColCount = mxTable->getColumnCount();
881 if( (nRowCount != getRowCount()) || (nColCount != getColumnCount()) )
883 if( static_cast< sal_Int32 >( maRows.size() ) != nRowCount )
884 maRows.resize( nRowCount );
886 for( sal_Int32 nRow = 0; nRow < nRowCount; nRow++ )
887 maRows[nRow].clear();
889 if( static_cast< sal_Int32 >( maColumns.size() ) != nColCount )
890 maColumns.resize( nColCount );
892 for( sal_Int32 nCol = 0; nCol < nColCount; nCol++ )
893 maColumns[nCol].clear();
896 LayoutTableWidth( rRectangle, bFitWidth );
897 LayoutTableHeight( rRectangle, bFitHeight );
898 UpdateBorderLayout();
902 void TableLayouter::updateCells( tools::Rectangle const & rRectangle )
904 const sal_Int32 nColCount = getColumnCount();
905 const sal_Int32 nRowCount = getRowCount();
907 CellPos aPos;
908 for( aPos.mnRow = 0; aPos.mnRow < nRowCount; aPos.mnRow++ )
910 for( aPos.mnCol = 0; aPos.mnCol < nColCount; aPos.mnCol++ )
912 CellRef xCell( getCell( aPos ) );
913 if( xCell.is() )
915 basegfx::B2IRectangle aCellArea;
916 if( getCellArea( xCell, aPos, aCellArea ) )
918 tools::Rectangle aCellRect;
919 aCellRect.SetLeft( aCellArea.getMinX() );
920 aCellRect.SetRight( aCellArea.getMaxX() );
921 aCellRect.SetTop( aCellArea.getMinY() );
922 aCellRect.SetBottom( aCellArea.getMaxY() );
923 aCellRect.Move( rRectangle.Left(), rRectangle.Top() );
924 xCell->setCellRect( aCellRect );
932 CellRef TableLayouter::getCell( const CellPos& rPos ) const
934 CellRef xCell;
935 if( mxTable.is() ) try
937 xCell.set( dynamic_cast< Cell* >( mxTable->getCellByPosition( rPos.mnCol, rPos.mnRow ).get() ) );
939 catch( Exception& )
941 TOOLS_WARN_EXCEPTION("svx", "");
943 return xCell;
947 bool TableLayouter::HasPriority( const SvxBorderLine* pThis, const SvxBorderLine* pOther )
949 if (!pThis || ((pThis == &gEmptyBorder) && (pOther != nullptr)))
950 return false;
951 if (!pOther || (pOther == &gEmptyBorder))
952 return true;
954 sal_uInt16 nThisSize = pThis->GetScaledWidth();
955 sal_uInt16 nOtherSize = pOther->GetScaledWidth();
957 if (nThisSize > nOtherSize)
958 return true;
960 else if (nThisSize < nOtherSize)
962 return false;
964 else
966 if ( pOther->GetInWidth() && !pThis->GetInWidth() )
968 return true;
970 else if ( pThis->GetInWidth() && !pOther->GetInWidth() )
972 return false;
974 else
976 return true; //! ???
981 void TableLayouter::SetBorder( sal_Int32 nCol, sal_Int32 nRow, bool bHorizontal, const SvxBorderLine* pLine )
983 if (!pLine)
984 pLine = &gEmptyBorder;
986 BorderLineMap& rMap = bHorizontal ? maHorizontalBorders : maVerticalBorders;
988 if( (nCol >= 0) && (nCol < sal::static_int_cast<sal_Int32>(rMap.size())) &&
989 (nRow >= 0) && (nRow < sal::static_int_cast<sal_Int32>(rMap[nCol].size())) )
991 SvxBorderLine *pOld = rMap[nCol][nRow];
993 if (HasPriority(pLine, pOld))
995 if (pOld && pOld != &gEmptyBorder)
996 delete pOld;
998 SvxBorderLine* pNew = (pLine != &gEmptyBorder) ? new SvxBorderLine(*pLine) : &gEmptyBorder;
1000 rMap[nCol][nRow] = pNew;
1003 else
1005 OSL_FAIL( "sdr::table::TableLayouter::SetBorder(), invalid border!" );
1009 void TableLayouter::ClearBorderLayout()
1011 ClearBorderLayout(maHorizontalBorders);
1012 ClearBorderLayout(maVerticalBorders);
1015 void TableLayouter::ClearBorderLayout(BorderLineMap& rMap)
1017 const sal_Int32 nColCount = rMap.size();
1019 for( sal_Int32 nCol = 0; nCol < nColCount; nCol++ )
1021 const sal_Int32 nRowCount = rMap[nCol].size();
1022 for( sal_Int32 nRow = 0; nRow < nRowCount; nRow++ )
1024 SvxBorderLine* pLine = rMap[nCol][nRow];
1025 if( pLine )
1027 if( pLine != &gEmptyBorder )
1028 delete pLine;
1030 rMap[nCol][nRow] = nullptr;
1036 void TableLayouter::ResizeBorderLayout()
1038 ClearBorderLayout();
1039 ResizeBorderLayout(maHorizontalBorders);
1040 ResizeBorderLayout(maVerticalBorders);
1044 void TableLayouter::ResizeBorderLayout( BorderLineMap& rMap )
1046 const sal_Int32 nColCount = getColumnCount() + 1;
1047 const sal_Int32 nRowCount = getRowCount() + 1;
1049 if( sal::static_int_cast<sal_Int32>(rMap.size()) != nColCount )
1050 rMap.resize( nColCount );
1052 for( sal_Int32 nCol = 0; nCol < nColCount; nCol++ )
1054 if( sal::static_int_cast<sal_Int32>(rMap[nCol].size()) != nRowCount )
1055 rMap[nCol].resize( nRowCount );
1060 void TableLayouter::UpdateBorderLayout()
1062 // make sure old border layout is cleared and border maps have correct size
1063 ResizeBorderLayout();
1065 const sal_Int32 nColCount = getColumnCount();
1066 const sal_Int32 nRowCount = getRowCount();
1068 CellPos aPos;
1069 for( aPos.mnRow = 0; aPos.mnRow < nRowCount; aPos.mnRow++ )
1071 for( aPos.mnCol = 0; aPos.mnCol < nColCount; aPos.mnCol++ )
1073 CellRef xCell( getCell( aPos ) );
1074 if( !xCell.is() )
1075 continue;
1077 const SvxBoxItem* pThisAttr = xCell->GetItemSet().GetItem<SvxBoxItem>( SDRATTR_TABLE_BORDER );
1078 OSL_ENSURE(pThisAttr,"sdr::table::TableLayouter::UpdateBorderLayout(), no border attribute?");
1080 if( !pThisAttr )
1081 continue;
1083 const sal_Int32 nLastRow = xCell->getRowSpan() + aPos.mnRow;
1084 const sal_Int32 nLastCol = xCell->getColumnSpan() + aPos.mnCol;
1086 for( sal_Int32 nRow = aPos.mnRow; nRow < nLastRow; nRow++ )
1088 SetBorder( aPos.mnCol, nRow, false, pThisAttr->GetLeft() );
1089 SetBorder( nLastCol, nRow, false, pThisAttr->GetRight() );
1092 for( sal_Int32 nCol = aPos.mnCol; nCol < nLastCol; nCol++ )
1094 SetBorder( nCol, aPos.mnRow, true, pThisAttr->GetTop() );
1095 SetBorder( nCol, nLastRow, true, pThisAttr->GetBottom() );
1102 void TableLayouter::DistributeColumns( ::tools::Rectangle& rArea,
1103 sal_Int32 nFirstCol,
1104 sal_Int32 nLastCol,
1105 const bool bOptimize,
1106 const bool bMinimize )
1108 if( !mxTable.is() )
1109 return;
1113 const sal_Int32 nColCount = getColumnCount();
1114 Reference< XTableColumns > xCols( mxTable->getColumns(), UNO_SET_THROW );
1115 const Size aSize(0xffffff, 0xffffff);
1117 //special case - optimize a single column
1118 if ( (bOptimize || bMinimize) && nFirstCol == nLastCol )
1120 const sal_Int32 nWish = calcPreferredColumnWidth(nFirstCol, aSize);
1121 if ( nWish < getColumnWidth(nFirstCol) )
1123 Reference< XPropertySet > xColSet( xCols->getByIndex(nFirstCol), UNO_QUERY_THROW );
1124 xColSet->setPropertyValue( gsSize, Any( nWish ) );
1126 //FitWidth automatically distributes the new excess space
1127 LayoutTable( rArea, /*bFitWidth=*/!bMinimize, /*bFitHeight=*/false );
1131 if( (nFirstCol < 0) || (nFirstCol>= nLastCol) || (nLastCol >= nColCount) )
1132 return;
1134 sal_Int32 nAllWidth = 0;
1135 float fAllWish = 0;
1136 sal_Int32 nUnused = 0;
1137 std::vector<sal_Int32> aWish(nColCount);
1139 for( sal_Int32 nCol = nFirstCol; nCol <= nLastCol; ++nCol )
1140 nAllWidth += getColumnWidth(nCol);
1142 const sal_Int32 nEqualWidth = nAllWidth / (nLastCol-nFirstCol+1);
1144 //pass 1 - collect unneeded space (from an equal width perspective)
1145 if ( bMinimize || bOptimize )
1147 for( sal_Int32 nCol = nFirstCol; nCol <= nLastCol; ++nCol )
1149 const sal_Int32 nIndex = nCol - nFirstCol;
1150 aWish[nIndex] = calcPreferredColumnWidth(nCol, aSize);
1151 fAllWish += aWish[nIndex];
1152 if ( aWish[nIndex] < nEqualWidth )
1153 nUnused += nEqualWidth - aWish[nIndex];
1156 const sal_Int32 nDistributeExcess = nAllWidth - fAllWish;
1158 sal_Int32 nWidth = nEqualWidth;
1159 for( sal_Int32 nCol = nFirstCol; nCol <= nLastCol; ++nCol )
1161 if ( !bMinimize && nCol == nLastCol )
1162 nWidth = nAllWidth; // last column gets rounding/logic errors
1163 else if ( (bMinimize || bOptimize) && fAllWish )
1165 //pass 2 - first come, first served when requesting from the
1166 // unneeded pool, or proportionally allocate excess.
1167 const sal_Int32 nIndex = nCol - nFirstCol;
1168 if ( aWish[nIndex] > nEqualWidth + nUnused )
1170 nWidth = nEqualWidth + nUnused;
1171 nUnused = 0;
1173 else
1175 nWidth = aWish[nIndex];
1176 if ( aWish[nIndex] > nEqualWidth )
1177 nUnused -= aWish[nIndex] - nEqualWidth;
1179 if ( !bMinimize && nDistributeExcess > 0 )
1180 nWidth += nWidth / fAllWish * nDistributeExcess;
1184 Reference< XPropertySet > xColSet( xCols->getByIndex( nCol ), UNO_QUERY_THROW );
1185 xColSet->setPropertyValue( gsSize, Any( nWidth ) );
1187 nAllWidth -= nWidth;
1190 LayoutTable( rArea, !bMinimize, false );
1192 catch( Exception& )
1194 TOOLS_WARN_EXCEPTION("svx", "");
1199 void TableLayouter::DistributeRows( ::tools::Rectangle& rArea,
1200 sal_Int32 nFirstRow,
1201 sal_Int32 nLastRow,
1202 const bool bOptimize,
1203 const bool bMinimize )
1205 if( !mxTable.is() )
1206 return;
1210 const sal_Int32 nRowCount = mxTable->getRowCount();
1211 Reference< XTableRows > xRows( mxTable->getRows(), UNO_SET_THROW );
1212 sal_Int32 nMinHeight = 0;
1214 //special case - minimize a single row
1215 if ( bMinimize && nFirstRow == nLastRow )
1217 const sal_Int32 nWish = std::max( maRows[nFirstRow].mnMinSize, nMinHeight );
1218 if ( nWish < getRowHeight(nFirstRow) )
1220 Reference< XPropertySet > xRowSet( xRows->getByIndex( nFirstRow ), UNO_QUERY_THROW );
1221 xRowSet->setPropertyValue( gsSize, Any( nWish ) );
1223 LayoutTable( rArea, /*bFitWidth=*/false, /*bFitHeight=*/!bMinimize );
1227 if( (nFirstRow < 0) || (nFirstRow>= nLastRow) || (nLastRow >= nRowCount) )
1228 return;
1230 sal_Int32 nAllHeight = 0;
1231 sal_Int32 nMaxHeight = 0;
1233 for( sal_Int32 nRow = nFirstRow; nRow <= nLastRow; ++nRow )
1235 nMinHeight = std::max( maRows[nRow].mnMinSize, nMinHeight );
1236 nMaxHeight = std::max( maRows[nRow].mnSize, nMaxHeight );
1237 nAllHeight += maRows[nRow].mnSize;
1240 const sal_Int32 nRows = nLastRow-nFirstRow+1;
1241 sal_Int32 nHeight = nAllHeight / nRows;
1243 if ( !bMinimize && nHeight < nMaxHeight )
1245 if ( !bOptimize )
1247 sal_Int32 nNeededHeight = nRows * nMaxHeight;
1248 rArea.AdjustBottom(nNeededHeight - nAllHeight );
1249 nHeight = nMaxHeight;
1250 nAllHeight = nRows * nMaxHeight;
1252 else if ( nHeight < nMinHeight )
1254 sal_Int32 nNeededHeight = nRows * nMinHeight;
1255 rArea.AdjustBottom(nNeededHeight - nAllHeight );
1256 nHeight = nMinHeight;
1257 nAllHeight = nRows * nMinHeight;
1261 for( sal_Int32 nRow = nFirstRow; nRow <= nLastRow; ++nRow )
1263 if ( bMinimize )
1264 nHeight = maRows[nRow].mnMinSize;
1265 else if ( nRow == nLastRow )
1266 nHeight = nAllHeight; // last row get round errors
1268 Reference< XPropertySet > xRowSet( xRows->getByIndex( nRow ), UNO_QUERY_THROW );
1269 xRowSet->setPropertyValue( gsSize, Any( nHeight ) );
1271 nAllHeight -= nHeight;
1274 LayoutTable( rArea, false, !bMinimize );
1276 catch( Exception& )
1278 TOOLS_WARN_EXCEPTION("svx", "");
1282 void TableLayouter::dumpAsXml(xmlTextWriterPtr pWriter) const
1284 (void)xmlTextWriterStartElement(pWriter, BAD_CAST("TableLayouter"));
1286 (void)xmlTextWriterStartElement(pWriter, BAD_CAST("columns"));
1287 for (const auto& rColumn : maColumns)
1288 rColumn.dumpAsXml(pWriter);
1289 (void)xmlTextWriterEndElement(pWriter);
1291 (void)xmlTextWriterStartElement(pWriter, BAD_CAST("rows"));
1292 for (const auto& rRow : maRows)
1293 rRow.dumpAsXml(pWriter);
1294 (void)xmlTextWriterEndElement(pWriter);
1296 (void)xmlTextWriterEndElement(pWriter);
1299 void TableLayouter::Layout::dumpAsXml(xmlTextWriterPtr pWriter) const
1301 (void)xmlTextWriterStartElement(pWriter, BAD_CAST("TableLayouter_Layout"));
1303 (void)xmlTextWriterWriteAttribute(pWriter, BAD_CAST("pos"), BAD_CAST(OString::number(mnPos).getStr()));
1304 (void)xmlTextWriterWriteAttribute(pWriter, BAD_CAST("size"), BAD_CAST(OString::number(mnSize).getStr()));
1305 (void)xmlTextWriterWriteAttribute(pWriter, BAD_CAST("minSize"), BAD_CAST(OString::number(mnMinSize).getStr()));
1307 (void)xmlTextWriterEndElement(pWriter);
1312 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */