Bump version to 6.4-15
[LibreOffice.git] / svx / source / table / tablelayouter.cxx
blob3e7ff37c00ac326d52b99899e10bd35b4bc6d3ec
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 <tools/gen.hxx>
25 #include <libxml/xmlwriter.h>
27 #include <cell.hxx>
28 #include "cellrange.hxx"
29 #include <o3tl/safeint.hxx>
30 #include <tablemodel.hxx>
31 #include "tablerow.hxx"
32 #include "tablerows.hxx"
33 #include "tablecolumn.hxx"
34 #include "tablecolumns.hxx"
35 #include "tablelayouter.hxx"
36 #include <svx/svdotable.hxx>
37 #include <editeng/borderline.hxx>
38 #include <editeng/boxitem.hxx>
39 #include <svx/svdmodel.hxx>
41 using ::editeng::SvxBorderLine;
42 using namespace ::com::sun::star::uno;
43 using namespace ::com::sun::star::lang;
44 using namespace ::com::sun::star::container;
45 using namespace ::com::sun::star::beans;
46 using namespace ::com::sun::star::table;
47 using namespace ::com::sun::star::text;
50 namespace sdr { namespace table {
53 static SvxBorderLine gEmptyBorder;
55 static const OUStringLiteral gsSize( "Size" );
57 TableLayouter::TableLayouter( const TableModelRef& xTableModel )
58 : mxTable( xTableModel )
63 TableLayouter::~TableLayouter()
65 ClearBorderLayout();
69 basegfx::B2ITuple TableLayouter::getCellSize( const CellRef& xCell, const CellPos& rPos ) const
71 sal_Int32 width = 0;
72 sal_Int32 height = 0;
74 try
76 if( xCell.is() && !xCell->isMerged() )
78 CellPos aPos( rPos );
80 sal_Int32 nRowCount = getRowCount();
81 sal_Int32 nRowSpan = std::max( xCell->getRowSpan(), sal_Int32(1) );
82 while( nRowSpan && (aPos.mnRow < nRowCount) )
84 if( static_cast<sal_Int32>(maRows.size()) <= aPos.mnRow )
85 break;
87 height = o3tl::saturating_add(height, maRows[aPos.mnRow++].mnSize);
88 nRowSpan--;
91 sal_Int32 nColCount = getColumnCount();
92 sal_Int32 nColSpan = std::max( xCell->getColumnSpan(), sal_Int32(1) );
93 while( nColSpan && (aPos.mnCol < nColCount ) )
95 if( static_cast<sal_Int32>(maColumns.size()) <= aPos.mnCol )
96 break;
98 width = o3tl::saturating_add(width, maColumns[aPos.mnCol++].mnSize);
99 nColSpan--;
103 catch( Exception& )
105 OSL_FAIL( "TableLayouter::getCellSize(), exception caught!" );
108 return basegfx::B2ITuple( width, height );
112 bool TableLayouter::getCellArea( const CellRef& xCell, const CellPos& rPos, basegfx::B2IRectangle& rArea ) const
116 if( xCell.is() && !xCell->isMerged() && isValid(rPos) )
118 const basegfx::B2ITuple aCellSize( getCellSize( xCell, rPos ) );
119 const bool bRTL = (mxTable->getSdrTableObj()->GetWritingMode() == WritingMode_RL_TB);
121 if( (rPos.mnCol < static_cast<sal_Int32>(maColumns.size())) && (rPos.mnRow < static_cast<sal_Int32>(maRows.size()) ) )
123 const sal_Int32 y = maRows[rPos.mnRow].mnPos;
125 sal_Int32 endy;
126 if (o3tl::checked_add(y, aCellSize.getY(), endy))
127 return false;
129 if(bRTL)
131 ///For RTL Table Calculate the Right End of cell instead of Left
132 const sal_Int32 x = maColumns[rPos.mnCol].mnPos + maColumns[rPos.mnCol].mnSize;
133 sal_Int32 startx;
134 if (o3tl::checked_sub(x, aCellSize.getX(), startx))
135 return false;
136 rArea = basegfx::B2IRectangle(startx, y, x, endy);
138 else
140 const sal_Int32 x = maColumns[rPos.mnCol].mnPos;
141 sal_Int32 endx;
142 if (o3tl::checked_add(x, aCellSize.getX(), endx))
143 return false;
144 rArea = basegfx::B2IRectangle(x, y, endx, endy);
146 return true;
150 catch( Exception& )
152 OSL_FAIL( "TableLayouter::getCellSize(), exception caught!" );
154 return false;
158 sal_Int32 TableLayouter::getRowHeight( sal_Int32 nRow ) const
160 if( isValidRow(nRow) )
161 return maRows[nRow].mnSize;
162 else
163 return 0;
167 sal_Int32 TableLayouter::getColumnWidth( sal_Int32 nColumn ) const
169 if( isValidColumn(nColumn) )
170 return maColumns[nColumn].mnSize;
171 else
172 return 0;
175 sal_Int32 TableLayouter::calcPreferredColumnWidth( sal_Int32 nColumn, Size aSize ) const
177 sal_Int32 nRet = 0;
178 for ( sal_uInt32 nRow = 0; nRow < static_cast<sal_uInt32>(maRows.size()); ++nRow )
180 // Account for the space desired by spanned columns.
181 // Only the last spanned cell will try to ensure sufficient space,
182 // by looping through the previous columns, subtracting their portion.
183 sal_Int32 nWish = 0;
184 sal_Int32 nSpannedColumn = nColumn;
185 bool bFindSpan = true;
186 while ( bFindSpan && isValidColumn(nSpannedColumn) )
188 // recursive function call gets earlier portion of spanned column.
189 if ( nSpannedColumn < nColumn )
190 nWish -= calcPreferredColumnWidth( nSpannedColumn, aSize );
192 CellRef xCell( getCell( CellPos( nSpannedColumn, nRow ) ) );
193 if ( xCell.is() && !xCell->isMerged()
194 && (xCell->getColumnSpan() == 1 || nSpannedColumn < nColumn) )
196 nWish += xCell->calcPreferredWidth(aSize);
197 bFindSpan = false;
199 else if ( xCell.is() && xCell->isMerged()
200 && nColumn == nSpannedColumn
201 && isValidColumn(nColumn + 1) )
203 xCell = getCell( CellPos( nColumn + 1, nRow ) );
204 bFindSpan = xCell.is() && !xCell->isMerged();
206 nSpannedColumn--;
208 nRet = std::max( nRet, nWish );
210 return nRet;
214 bool TableLayouter::isEdgeVisible( sal_Int32 nEdgeX, sal_Int32 nEdgeY, bool bHorizontal ) const
216 const BorderLineMap& rMap = bHorizontal ? maHorizontalBorders : maVerticalBorders;
218 if( (nEdgeX >= 0) && (nEdgeX < sal::static_int_cast<sal_Int32>(rMap.size())) &&
219 (nEdgeY >= 0) && (nEdgeY < sal::static_int_cast<sal_Int32>(rMap[nEdgeX].size())) )
221 return rMap[nEdgeX][nEdgeY] != nullptr;
223 else
225 OSL_FAIL( "sdr::table::TableLayouter::getBorderLine(), invalid edge!" );
228 return false;
232 /** returns the requested borderline in rpBorderLine or a null pointer if there is no border at this edge */
233 SvxBorderLine* TableLayouter::getBorderLine( sal_Int32 nEdgeX, sal_Int32 nEdgeY, bool bHorizontal )const
235 SvxBorderLine* pLine = nullptr;
237 const BorderLineMap& rMap = bHorizontal ? maHorizontalBorders : maVerticalBorders;
239 if( (nEdgeX >= 0) && (nEdgeX < sal::static_int_cast<sal_Int32>(rMap.size())) &&
240 (nEdgeY >= 0) && (nEdgeY < sal::static_int_cast<sal_Int32>(rMap[nEdgeX].size())) )
242 pLine = rMap[nEdgeX][nEdgeY];
243 if( pLine == &gEmptyBorder )
244 pLine = nullptr;
246 else
248 OSL_FAIL( "sdr::table::TableLayouter::getBorderLine(), invalid edge!" );
251 return pLine;
254 std::vector<EdgeInfo> TableLayouter::getHorizontalEdges()
256 std::vector<EdgeInfo> aReturn;
257 sal_Int32 nRowSize = sal_Int32(maRows.size());
258 for (sal_Int32 i = 0; i <= nRowSize; i++)
260 sal_Int32 nEdgeMin = 0;
261 sal_Int32 nEdgeMax = 0;
262 sal_Int32 nEdge = getHorizontalEdge(i, &nEdgeMin, &nEdgeMax);
263 nEdgeMin -= nEdge;
264 nEdgeMax -= nEdge;
265 aReturn.emplace_back(i, nEdge, nEdgeMin, nEdgeMax);
267 return aReturn;
270 std::vector<EdgeInfo> TableLayouter::getVerticalEdges()
272 std::vector<EdgeInfo> aReturn;
273 sal_Int32 nColumnSize = sal_Int32(maColumns.size());
274 for (sal_Int32 i = 0; i <= nColumnSize; i++)
276 sal_Int32 nEdgeMin = 0;
277 sal_Int32 nEdgeMax = 0;
278 sal_Int32 nEdge = getVerticalEdge(i, &nEdgeMin, &nEdgeMax);
279 nEdgeMin -= nEdge;
280 nEdgeMax -= nEdge;
281 aReturn.emplace_back(i, nEdge, nEdgeMin, nEdgeMax);
283 return aReturn;
286 sal_Int32 TableLayouter::getHorizontalEdge( int nEdgeY, sal_Int32* pnMin /*= 0*/, sal_Int32* pnMax /*= 0*/ )
288 sal_Int32 nRet = 0;
289 const sal_Int32 nRowCount = getRowCount();
290 if( (nEdgeY >= 0) && (nEdgeY <= nRowCount ) )
291 nRet = maRows[std::min(static_cast<sal_Int32>(nEdgeY),nRowCount-1)].mnPos;
293 if( nEdgeY == nRowCount )
294 nRet += maRows[nEdgeY - 1].mnSize;
296 if( pnMin )
298 if( (nEdgeY > 0) && (nEdgeY <= nRowCount ) )
300 *pnMin = maRows[nEdgeY-1].mnPos + 600; // todo
302 else
304 *pnMin = nRet;
308 if( pnMax )
310 *pnMax = 0x0fffffff;
312 return nRet;
316 sal_Int32 TableLayouter::getVerticalEdge( int nEdgeX, sal_Int32* pnMin /*= 0*/, sal_Int32* pnMax /*= 0*/ )
318 sal_Int32 nRet = 0;
320 const sal_Int32 nColCount = getColumnCount();
321 if( (nEdgeX >= 0) && (nEdgeX <= nColCount ) )
322 nRet = maColumns[std::min(static_cast<sal_Int32>(nEdgeX),nColCount-1)].mnPos;
324 const bool bRTL = (mxTable->getSdrTableObj()->GetWritingMode() == WritingMode_RL_TB);
325 if( bRTL )
327 if( (nEdgeX >= 0) && (nEdgeX < nColCount) )
328 nRet += maColumns[nEdgeX].mnSize;
330 else
332 if( nEdgeX == nColCount )
333 nRet += maColumns[nEdgeX - 1].mnSize;
336 if( pnMin )
338 *pnMin = nRet;
339 if( bRTL )
341 if( nEdgeX < nColCount )
342 *pnMin = nRet - maColumns[nEdgeX].mnSize + getMinimumColumnWidth(nEdgeX);
344 else
346 if( (nEdgeX > 0) && (nEdgeX <= nColCount ) )
347 *pnMin = maColumns[nEdgeX-1].mnPos + getMinimumColumnWidth( nEdgeX-1 );
351 if( pnMax )
353 *pnMax = 0x0fffffff; // todo
354 if( bRTL )
356 if( nEdgeX > 0 )
357 *pnMax = nRet + maColumns[nEdgeX-1].mnSize - getMinimumColumnWidth( nEdgeX-1 );
359 else
361 if( (nEdgeX >= 0) && (nEdgeX < nColCount ) )
362 *pnMax = maColumns[nEdgeX].mnPos + maColumns[nEdgeX].mnSize - getMinimumColumnWidth( nEdgeX );
366 return nRet;
370 static bool checkMergeOrigin( const TableModelRef& xTable, sal_Int32 nMergedX, sal_Int32 nMergedY, sal_Int32 nCellX, sal_Int32 nCellY, bool& bRunning )
372 Reference< XMergeableCell > xCell( xTable->getCellByPosition( nCellX, nCellY ), UNO_QUERY );
373 if( xCell.is() && !xCell->isMerged() )
375 const sal_Int32 nRight = xCell->getColumnSpan() + nCellX;
376 const sal_Int32 nBottom = xCell->getRowSpan() + nCellY;
377 if( (nMergedX < nRight) && (nMergedY < nBottom) )
378 return true;
380 bRunning = false;
382 return false;
385 /** returns true if the cell(nMergedX,nMergedY) is merged with other cells.
386 the returned cell( rOriginX, rOriginY ) is the origin( top left cell ) of the merge.
388 bool findMergeOrigin( const TableModelRef& xTable, sal_Int32 nMergedX, sal_Int32 nMergedY, sal_Int32& rOriginX, sal_Int32& rOriginY )
390 rOriginX = nMergedX;
391 rOriginY = nMergedY;
393 if( xTable.is() ) try
395 // check if this cell already the origin or not merged at all
396 Reference< XMergeableCell > xCell( xTable->getCellByPosition( nMergedX, nMergedY ), UNO_QUERY_THROW );
397 if( !xCell->isMerged() )
398 return true;
400 bool bCheckVert = true;
401 bool bCheckHorz = true;
403 sal_Int32 nMinCol = 0;
404 sal_Int32 nMinRow = 0;
406 sal_Int32 nStep = 1, i;
408 sal_Int32 nRow, nCol;
411 if( bCheckVert )
413 nRow = nMergedY - nStep;
414 if( nRow >= nMinRow )
416 nCol = nMergedX;
417 for( i = 0; (i <= nStep) && (nCol >= nMinCol); i++, nCol-- )
419 if( checkMergeOrigin( xTable, nMergedX, nMergedY, nCol, nRow, bCheckVert ) )
421 rOriginX = nCol; rOriginY = nRow;
422 return true;
425 if( !bCheckVert )
427 if( nCol == nMergedX )
429 nMinRow = nRow+1;
431 else
433 bCheckVert = true;
435 break;
439 else
441 bCheckVert = false;
445 if( bCheckHorz )
447 nCol = nMergedX - nStep;
448 if( nCol >= nMinCol )
450 nRow = nMergedY;
451 for( i = 0; (i < nStep) && (nRow >= nMinRow); i++, nRow-- )
453 if( checkMergeOrigin( xTable, nMergedX, nMergedY, nCol, nRow, bCheckHorz ) )
455 rOriginX = nCol; rOriginY = nRow;
456 return true;
459 if( !bCheckHorz )
461 if( nRow == nMergedY )
463 nMinCol = nCol+1;
465 else
467 bCheckHorz = true;
469 break;
473 else
475 bCheckHorz = false;
478 nStep++;
480 while( bCheckVert || bCheckHorz );
482 catch( Exception& )
484 OSL_FAIL("sdr::table::TableLayouter::findMergeOrigin(), exception caught!");
486 return false;
490 sal_Int32 TableLayouter::getMinimumColumnWidth( sal_Int32 nColumn )
492 if( isValidColumn( nColumn ) )
494 return maColumns[nColumn].mnMinSize;
496 else
498 OSL_FAIL( "TableLayouter::getMinimumColumnWidth(), column out of range!" );
499 return 0;
504 sal_Int32 TableLayouter::distribute( LayoutVector& rLayouts, sal_Int32 nDistribute )
506 // break loops after 100 runs to avoid freezing office due to developer error
507 sal_Int32 nSafe = 100;
509 const std::size_t nCount = rLayouts.size();
510 std::size_t nIndex;
512 bool bConstrainsBroken = false;
516 bConstrainsBroken = false;
518 // first enforce minimum size constrains on all entities
519 for( nIndex = 0; nIndex < nCount; ++nIndex )
521 Layout& rLayout = rLayouts[nIndex];
522 if( rLayout.mnSize < rLayout.mnMinSize )
524 sal_Int32 nDiff(0);
525 bConstrainsBroken |= o3tl::checked_sub(rLayout.mnMinSize, rLayout.mnSize, nDiff);
526 bConstrainsBroken |= o3tl::checked_sub(nDistribute, nDiff, nDistribute);
527 rLayout.mnSize = rLayout.mnMinSize;
531 // calculate current width
532 // if nDistribute is < 0 (shrinking), entities that are already
533 // at minimum width are not counted
534 sal_Int32 nCurrentWidth = 0;
535 for( nIndex = 0; nIndex < nCount; ++nIndex )
537 Layout& rLayout = rLayouts[nIndex];
538 if( (nDistribute > 0) || (rLayout.mnSize > rLayout.mnMinSize) )
539 nCurrentWidth = o3tl::saturating_add(nCurrentWidth, rLayout.mnSize);
542 // now distribute over entities
543 if( (nCurrentWidth != 0) && (nDistribute != 0) )
545 sal_Int32 nDistributed = nDistribute;
546 for( nIndex = 0; nIndex < nCount; ++nIndex )
548 Layout& rLayout = rLayouts[nIndex];
549 if( (nDistribute > 0) || (rLayout.mnSize > rLayout.mnMinSize) )
551 sal_Int32 n(nDistributed); // for last entity use up rest
552 if (nIndex != (nCount-1))
554 bConstrainsBroken |= o3tl::checked_multiply(nDistribute, rLayout.mnSize, n);
555 n /= nCurrentWidth;
558 bConstrainsBroken |= o3tl::checked_add(rLayout.mnSize, n, rLayout.mnSize);
559 nDistributed -= n;
561 if( rLayout.mnSize < rLayout.mnMinSize )
562 bConstrainsBroken = true;
566 } while( bConstrainsBroken && --nSafe );
568 sal_Int32 nSize = 0;
569 for( nIndex = 0; nIndex < nCount; ++nIndex )
570 nSize = o3tl::saturating_add(nSize, rLayouts[nIndex].mnSize);
572 return nSize;
576 typedef std::vector< CellRef > MergeableCellVector;
577 typedef std::vector< MergeableCellVector > MergeVector;
580 void TableLayouter::LayoutTableWidth( tools::Rectangle& rArea, bool bFit )
582 const sal_Int32 nColCount = getColumnCount();
583 const sal_Int32 nRowCount = getRowCount();
584 if( nColCount == 0 )
585 return;
587 MergeVector aMergedCells( nColCount );
588 std::vector<sal_Int32> aOptimalColumns;
590 const OUString sOptimalSize("OptimalSize");
592 if( sal::static_int_cast< sal_Int32 >( maColumns.size() ) != nColCount )
593 maColumns.resize( nColCount );
595 Reference< XTableColumns > xCols( mxTable->getColumns(), UNO_SET_THROW );
597 // first calculate current width and initial minimum width per column,
598 // merged cells will be counted later
599 sal_Int32 nCurrentWidth = 0;
600 sal_Int32 nCol = 0, nRow = 0;
601 for( nCol = 0; nCol < nColCount; nCol++ )
603 sal_Int32 nMinWidth = 0;
605 bool bIsEmpty = true; // check if all cells in this column are merged
607 for( nRow = 0; nRow < nRowCount; ++nRow )
609 CellRef xCell( getCell( CellPos( nCol, nRow ) ) );
610 if( xCell.is() && !xCell->isMerged() )
612 bIsEmpty = false;
614 sal_Int32 nColSpan = xCell->getColumnSpan();
615 if( nColSpan > 1 )
617 // merged cells will be evaluated later
618 aMergedCells[nCol+nColSpan-1].push_back( xCell );
620 else
622 nMinWidth = std::max( nMinWidth, xCell->getMinimumWidth() );
627 maColumns[nCol].mnMinSize = nMinWidth;
629 if( bIsEmpty )
631 maColumns[nCol].mnSize = 0;
633 else
635 sal_Int32 nColWidth = 0;
636 Reference< XPropertySet > xColSet( xCols->getByIndex( nCol ), UNO_QUERY_THROW );
637 bool bOptimal = false;
638 xColSet->getPropertyValue( sOptimalSize ) >>= bOptimal;
639 if( bOptimal )
641 aOptimalColumns.push_back(nCol);
643 else
645 xColSet->getPropertyValue( gsSize ) >>= nColWidth;
648 maColumns[nCol].mnSize = std::max( nColWidth, nMinWidth);
650 nCurrentWidth = o3tl::saturating_add(nCurrentWidth, maColumns[nCol].mnSize);
654 // if we have optimal sized rows, distribute what is given (left)
655 if( !bFit && !aOptimalColumns.empty() && (nCurrentWidth < rArea.getWidth()) )
657 sal_Int32 nLeft = rArea.getWidth() - nCurrentWidth;
658 sal_Int32 nDistribute = nLeft / aOptimalColumns.size();
660 auto iter( aOptimalColumns.begin() );
661 while( iter != aOptimalColumns.end() )
663 sal_Int32 nOptCol = *iter++;
664 if( iter == aOptimalColumns.end() )
665 nDistribute = nLeft;
667 maColumns[nOptCol].mnSize += nDistribute;
668 nLeft -= nDistribute;
671 DBG_ASSERT( nLeft == 0, "svx::TableLayouter::LayoutTableWidtht(), layouting failed!" );
674 // now check if merged cells fit
675 for( nCol = 1; nCol < nColCount; ++nCol )
677 bool bChanges = false;
679 const sal_Int32 nOldSize = maColumns[nCol].mnSize;
681 for( const CellRef& xCell : aMergedCells[nCol] )
683 sal_Int32 nMinWidth = xCell->getMinimumWidth();
685 for( sal_Int32 nMCol = nCol - xCell->getColumnSpan() + 1; (nMCol > 0) && (nMCol < nCol); ++nMCol )
686 nMinWidth -= maColumns[nMCol].mnSize;
688 if( nMinWidth > maColumns[nCol].mnMinSize )
689 maColumns[nCol].mnMinSize = nMinWidth;
691 if( nMinWidth > maColumns[nCol].mnSize )
693 maColumns[nCol].mnSize = nMinWidth;
694 bChanges = true;
698 if( bChanges )
700 nCurrentWidth = o3tl::saturating_add(nCurrentWidth, maColumns[nCol].mnSize - nOldSize);
704 // now scale if wanted and needed
705 if( bFit && (nCurrentWidth != rArea.getWidth()) )
706 distribute( maColumns, rArea.getWidth() - nCurrentWidth );
708 // last step, update left edges
709 sal_Int32 nNewWidth = 0;
711 const bool bRTL = (mxTable->getSdrTableObj()->GetWritingMode() == WritingMode_RL_TB);
712 RangeIterator<sal_Int32> coliter( 0, nColCount, !bRTL );
713 while( coliter.next(nCol ) )
715 maColumns[nCol].mnPos = nNewWidth;
716 nNewWidth = o3tl::saturating_add(nNewWidth, maColumns[nCol].mnSize);
717 if( bFit )
719 Reference< XPropertySet > xColSet( xCols->getByIndex(nCol), UNO_QUERY_THROW );
720 xColSet->setPropertyValue( gsSize, Any( maColumns[nCol].mnSize ) );
724 rArea.SetSize( Size( nNewWidth, rArea.GetHeight() ) );
725 updateCells( rArea );
729 void TableLayouter::LayoutTableHeight( tools::Rectangle& rArea, bool bFit )
731 const sal_Int32 nColCount = getColumnCount();
732 const sal_Int32 nRowCount = getRowCount();
734 if( nRowCount == 0 )
735 return;
737 Reference< XTableRows > xRows( mxTable->getRows() );
739 MergeVector aMergedCells( nRowCount );
740 std::vector<sal_Int32> aOptimalRows;
742 const OUString sOptimalSize("OptimalSize");
744 // first calculate current height and initial minimum size per column,
745 // merged cells will be counted later
746 sal_Int32 nCurrentHeight = 0;
747 sal_Int32 nCol, nRow;
748 for( nRow = 0; nRow < nRowCount; ++nRow )
750 Reference< XPropertySet > xRowSet( xRows->getByIndex(nRow), UNO_QUERY_THROW );
751 sal_Int32 nRowPropHeight = 0;
752 xRowSet->getPropertyValue( gsSize ) >>= nRowPropHeight;
753 sal_Int32 nMinHeight = 0;
755 bool bIsEmpty = true; // check if all cells in this row are merged
756 bool bRowHasText = false;
757 bool bRowHasCellInEditMode = false;
759 for( nCol = 0; nCol < nColCount; ++nCol )
761 CellRef xCell( getCell( CellPos( nCol, nRow ) ) );
762 if( xCell.is() && !xCell->isMerged() )
764 bIsEmpty = false;
766 sal_Int32 nRowSpan = xCell->getRowSpan();
767 if( nRowSpan > 1 )
769 // merged cells will be evaluated later
770 aMergedCells[nRow+nRowSpan-1].push_back( xCell );
772 else
774 bool bCellHasText = xCell->hasText();
775 bool bCellInEditMode = xCell->IsTextEditActive();
777 if (!bRowHasCellInEditMode && bCellInEditMode)
778 bRowHasCellInEditMode = true;
780 if ((bRowHasText == bCellHasText) || (bRowHasText && bCellInEditMode))
782 nMinHeight = std::max( nMinHeight, xCell->getMinimumHeight() );
784 else if ( !bRowHasText && bCellHasText )
786 bRowHasText = true;
787 nMinHeight = xCell->getMinimumHeight();
790 // tdf#137949 We should consider "Heigth" property while calculating minimum height.
791 // This control decides when we use "Heigth" property value instead of calculated minimum height
792 // Case 1: * Row has "Heigth" property
793 // * Calculated minimum heigth is smaller than Height propery value.
794 // Case 2: * Row has "Heigth" property
795 // * Calculated minimum heigth is bigger than Height propery value and
796 // * Row has not any text of any cell in edit mode in the row (means completely empty)
797 if ((nMinHeight < nRowPropHeight && nRowPropHeight > 0 ) ||
798 (nMinHeight > nRowPropHeight && nRowPropHeight > 0 && (!bRowHasText && !bRowHasCellInEditMode)))
800 nMinHeight = nRowPropHeight;
806 maRows[nRow].mnMinSize = nMinHeight;
808 if( bIsEmpty )
810 maRows[nRow].mnSize = 0;
812 else
814 sal_Int32 nRowHeight = 0;
816 bool bOptimal = false;
817 xRowSet->getPropertyValue( sOptimalSize ) >>= bOptimal;
818 if( bOptimal )
820 aOptimalRows.push_back( nRow );
822 else
824 xRowSet->getPropertyValue( gsSize ) >>= nRowHeight;
827 maRows[nRow].mnSize = nRowHeight;
829 if( maRows[nRow].mnSize < nMinHeight )
830 maRows[nRow].mnSize = nMinHeight;
832 nCurrentHeight = o3tl::saturating_add(nCurrentHeight, maRows[nRow].mnSize);
836 // if we have optimal sized rows, distribute what is given (left)
837 if( !bFit && !aOptimalRows.empty() && (nCurrentHeight < rArea.getHeight()) )
839 sal_Int32 nLeft = rArea.getHeight() - nCurrentHeight;
840 sal_Int32 nDistribute = nLeft / aOptimalRows.size();
842 auto iter( aOptimalRows.begin() );
843 while( iter != aOptimalRows.end() )
845 sal_Int32 nOptRow = *iter++;
846 if( iter == aOptimalRows.end() )
847 nDistribute = nLeft;
849 maRows[nOptRow].mnSize += nDistribute;
850 nLeft -= nDistribute;
854 DBG_ASSERT( nLeft == 0, "svx::TableLayouter::LayoutTableHeight(), layouting failed!" );
857 // now check if merged cells fit
858 for( nRow = 1; nRow < nRowCount; ++nRow )
860 bool bChanges = false;
861 sal_Int32 nOldSize = maRows[nRow].mnSize;
863 for( const CellRef& xCell : aMergedCells[nRow] )
865 sal_Int32 nMinHeight = xCell->getMinimumHeight();
867 for( sal_Int32 nMRow = nRow - xCell->getRowSpan() + 1; (nMRow > 0) && (nMRow < nRow); ++nMRow )
868 nMinHeight -= maRows[nMRow].mnSize;
870 if( nMinHeight > maRows[nRow].mnMinSize )
871 maRows[nRow].mnMinSize = nMinHeight;
873 if( nMinHeight > maRows[nRow].mnSize )
875 maRows[nRow].mnSize = nMinHeight;
876 bChanges = true;
879 if( bChanges )
880 nCurrentHeight = o3tl::saturating_add(nCurrentHeight, maRows[nRow].mnSize - nOldSize);
883 // now scale if wanted and needed
884 if( bFit && nCurrentHeight != rArea.getHeight() )
885 distribute(maRows, o3tl::saturating_sub<sal_Int32>(rArea.getHeight(), nCurrentHeight));
887 // last step, update left edges
888 sal_Int32 nNewHeight = 0;
889 for( nRow = 0; nRow < nRowCount; ++nRow )
891 maRows[nRow].mnPos = nNewHeight;
892 nNewHeight = o3tl::saturating_add(nNewHeight, maRows[nRow].mnSize);
894 if( bFit )
896 Reference< XPropertySet > xRowSet( xRows->getByIndex(nRow), UNO_QUERY_THROW );
897 xRowSet->setPropertyValue( gsSize, Any( maRows[nRow].mnSize ) );
901 rArea.SetSize( Size( rArea.GetWidth(), nNewHeight ) );
902 updateCells( rArea );
906 /** try to fit the table into the given rectangle.
907 If the rectangle is too small, it will be grown to fit the table. */
908 void TableLayouter::LayoutTable( tools::Rectangle& rRectangle, bool bFitWidth, bool bFitHeight )
910 if( !mxTable.is() )
911 return;
913 const sal_Int32 nRowCount = mxTable->getRowCount();
914 const sal_Int32 nColCount = mxTable->getColumnCount();
916 if( (nRowCount != getRowCount()) || (nColCount != getColumnCount()) )
918 if( static_cast< sal_Int32 >( maRows.size() ) != nRowCount )
919 maRows.resize( nRowCount );
921 for( sal_Int32 nRow = 0; nRow < nRowCount; nRow++ )
922 maRows[nRow].clear();
924 if( static_cast< sal_Int32 >( maColumns.size() ) != nColCount )
925 maColumns.resize( nColCount );
927 for( sal_Int32 nCol = 0; nCol < nColCount; nCol++ )
928 maColumns[nCol].clear();
931 LayoutTableWidth( rRectangle, bFitWidth );
932 LayoutTableHeight( rRectangle, bFitHeight );
933 UpdateBorderLayout();
937 void TableLayouter::updateCells( tools::Rectangle const & rRectangle )
939 const sal_Int32 nColCount = getColumnCount();
940 const sal_Int32 nRowCount = getRowCount();
942 CellPos aPos;
943 for( aPos.mnRow = 0; aPos.mnRow < nRowCount; aPos.mnRow++ )
945 for( aPos.mnCol = 0; aPos.mnCol < nColCount; aPos.mnCol++ )
947 CellRef xCell( getCell( aPos ) );
948 if( xCell.is() )
950 basegfx::B2IRectangle aCellArea;
951 if( getCellArea( xCell, aPos, aCellArea ) )
953 tools::Rectangle aCellRect;
954 aCellRect.SetLeft( aCellArea.getMinX() );
955 aCellRect.SetRight( aCellArea.getMaxX() );
956 aCellRect.SetTop( aCellArea.getMinY() );
957 aCellRect.SetBottom( aCellArea.getMaxY() );
958 aCellRect.Move( rRectangle.Left(), rRectangle.Top() );
959 xCell->setCellRect( aCellRect );
967 CellRef TableLayouter::getCell( const CellPos& rPos ) const
969 CellRef xCell;
970 if( mxTable.is() ) try
972 xCell.set( dynamic_cast< Cell* >( mxTable->getCellByPosition( rPos.mnCol, rPos.mnRow ).get() ) );
974 catch( Exception& )
976 OSL_FAIL( "sdr::table::TableLayouter::getCell(), exception caught!" );
978 return xCell;
982 bool TableLayouter::HasPriority( const SvxBorderLine* pThis, const SvxBorderLine* pOther )
984 if (!pThis || ((pThis == &gEmptyBorder) && (pOther != nullptr)))
985 return false;
986 if (!pOther || (pOther == &gEmptyBorder))
987 return true;
989 sal_uInt16 nThisSize = pThis->GetScaledWidth();
990 sal_uInt16 nOtherSize = pOther->GetScaledWidth();
992 if (nThisSize > nOtherSize)
993 return true;
995 else if (nThisSize < nOtherSize)
997 return false;
999 else
1001 if ( pOther->GetInWidth() && !pThis->GetInWidth() )
1003 return true;
1005 else if ( pThis->GetInWidth() && !pOther->GetInWidth() )
1007 return false;
1009 else
1011 return true; //! ???
1016 void TableLayouter::SetBorder( sal_Int32 nCol, sal_Int32 nRow, bool bHorizontal, const SvxBorderLine* pLine )
1018 if (!pLine)
1019 pLine = &gEmptyBorder;
1021 BorderLineMap& rMap = bHorizontal ? maHorizontalBorders : maVerticalBorders;
1023 if( (nCol >= 0) && (nCol < sal::static_int_cast<sal_Int32>(rMap.size())) &&
1024 (nRow >= 0) && (nRow < sal::static_int_cast<sal_Int32>(rMap[nCol].size())) )
1026 SvxBorderLine *pOld = rMap[nCol][nRow];
1028 if (HasPriority(pLine, pOld))
1030 if (pOld && pOld != &gEmptyBorder)
1031 delete pOld;
1033 SvxBorderLine* pNew = (pLine != &gEmptyBorder) ? new SvxBorderLine(*pLine) : &gEmptyBorder;
1035 rMap[nCol][nRow] = pNew;
1038 else
1040 OSL_FAIL( "sdr::table::TableLayouter::SetBorder(), invalid border!" );
1044 void TableLayouter::ClearBorderLayout()
1046 ClearBorderLayout(maHorizontalBorders);
1047 ClearBorderLayout(maVerticalBorders);
1050 void TableLayouter::ClearBorderLayout(BorderLineMap& rMap)
1052 const sal_Int32 nColCount = rMap.size();
1054 for( sal_Int32 nCol = 0; nCol < nColCount; nCol++ )
1056 const sal_Int32 nRowCount = rMap[nCol].size();
1057 for( sal_Int32 nRow = 0; nRow < nRowCount; nRow++ )
1059 SvxBorderLine* pLine = rMap[nCol][nRow];
1060 if( pLine )
1062 if( pLine != &gEmptyBorder )
1063 delete pLine;
1065 rMap[nCol][nRow] = nullptr;
1071 void TableLayouter::ResizeBorderLayout()
1073 ClearBorderLayout();
1074 ResizeBorderLayout(maHorizontalBorders);
1075 ResizeBorderLayout(maVerticalBorders);
1079 void TableLayouter::ResizeBorderLayout( BorderLineMap& rMap )
1081 const sal_Int32 nColCount = getColumnCount() + 1;
1082 const sal_Int32 nRowCount = getRowCount() + 1;
1084 if( sal::static_int_cast<sal_Int32>(rMap.size()) != nColCount )
1085 rMap.resize( nColCount );
1087 for( sal_Int32 nCol = 0; nCol < nColCount; nCol++ )
1089 if( sal::static_int_cast<sal_Int32>(rMap[nCol].size()) != nRowCount )
1090 rMap[nCol].resize( nRowCount );
1095 void TableLayouter::UpdateBorderLayout()
1097 // make sure old border layout is cleared and border maps have correct size
1098 ResizeBorderLayout();
1100 const sal_Int32 nColCount = getColumnCount();
1101 const sal_Int32 nRowCount = getRowCount();
1103 CellPos aPos;
1104 for( aPos.mnRow = 0; aPos.mnRow < nRowCount; aPos.mnRow++ )
1106 for( aPos.mnCol = 0; aPos.mnCol < nColCount; aPos.mnCol++ )
1108 CellRef xCell( getCell( aPos ) );
1109 if( !xCell.is() )
1110 continue;
1112 const SvxBoxItem* pThisAttr = xCell->GetItemSet().GetItem<SvxBoxItem>( SDRATTR_TABLE_BORDER );
1113 OSL_ENSURE(pThisAttr,"sdr::table::TableLayouter::UpdateBorderLayout(), no border attribute?");
1115 if( !pThisAttr )
1116 continue;
1118 const sal_Int32 nLastRow = xCell->getRowSpan() + aPos.mnRow;
1119 const sal_Int32 nLastCol = xCell->getColumnSpan() + aPos.mnCol;
1121 for( sal_Int32 nRow = aPos.mnRow; nRow < nLastRow; nRow++ )
1123 SetBorder( aPos.mnCol, nRow, false, pThisAttr->GetLeft() );
1124 SetBorder( nLastCol, nRow, false, pThisAttr->GetRight() );
1127 for( sal_Int32 nCol = aPos.mnCol; nCol < nLastCol; nCol++ )
1129 SetBorder( nCol, aPos.mnRow, true, pThisAttr->GetTop() );
1130 SetBorder( nCol, nLastRow, true, pThisAttr->GetBottom() );
1137 void TableLayouter::DistributeColumns( ::tools::Rectangle& rArea,
1138 sal_Int32 nFirstCol,
1139 sal_Int32 nLastCol,
1140 const bool bOptimize,
1141 const bool bMinimize )
1143 if( mxTable.is() ) try
1145 const sal_Int32 nColCount = getColumnCount();
1146 Reference< XTableColumns > xCols( mxTable->getColumns(), UNO_SET_THROW );
1147 const Size aSize(0xffffff, 0xffffff);
1149 //special case - optimize a single column
1150 if ( (bOptimize || bMinimize) && nFirstCol == nLastCol )
1152 const sal_Int32 nWish = calcPreferredColumnWidth(nFirstCol, aSize);
1153 if ( nWish < getColumnWidth(nFirstCol) )
1155 Reference< XPropertySet > xColSet( xCols->getByIndex(nFirstCol), UNO_QUERY_THROW );
1156 xColSet->setPropertyValue( gsSize, Any( nWish ) );
1158 //FitWidth automatically distributes the new excess space
1159 LayoutTable( rArea, /*bFitWidth=*/!bMinimize, /*bFitHeight=*/false );
1163 if( (nFirstCol < 0) || (nFirstCol>= nLastCol) || (nLastCol >= nColCount) )
1164 return;
1166 sal_Int32 nAllWidth = 0;
1167 float fAllWish = 0;
1168 sal_Int32 nUnused = 0;
1169 std::vector<sal_Int32> aWish(nColCount);
1171 for( sal_Int32 nCol = nFirstCol; nCol <= nLastCol; ++nCol )
1172 nAllWidth += getColumnWidth(nCol);
1174 const sal_Int32 nEqualWidth = nAllWidth / (nLastCol-nFirstCol+1);
1176 //pass 1 - collect unneeded space (from an equal width perspective)
1177 if ( bMinimize || bOptimize )
1179 for( sal_Int32 nCol = nFirstCol; nCol <= nLastCol; ++nCol )
1181 const sal_Int32 nIndex = nCol - nFirstCol;
1182 aWish[nIndex] = calcPreferredColumnWidth(nCol, aSize);
1183 fAllWish += aWish[nIndex];
1184 if ( aWish[nIndex] < nEqualWidth )
1185 nUnused += nEqualWidth - aWish[nIndex];
1188 const sal_Int32 nDistributeExcess = nAllWidth - fAllWish;
1190 sal_Int32 nWidth = nEqualWidth;
1191 for( sal_Int32 nCol = nFirstCol; nCol <= nLastCol; ++nCol )
1193 if ( !bMinimize && nCol == nLastCol )
1194 nWidth = nAllWidth; // last column gets rounding/logic errors
1195 else if ( (bMinimize || bOptimize) && fAllWish )
1197 //pass 2 - first come, first served when requesting from the
1198 // unneeded pool, or proportionally allocate excess.
1199 const sal_Int32 nIndex = nCol - nFirstCol;
1200 if ( aWish[nIndex] > nEqualWidth + nUnused )
1202 nWidth = nEqualWidth + nUnused;
1203 nUnused = 0;
1205 else
1207 nWidth = aWish[nIndex];
1208 if ( aWish[nIndex] > nEqualWidth )
1209 nUnused -= aWish[nIndex] - nEqualWidth;
1211 if ( !bMinimize && nDistributeExcess > 0 )
1212 nWidth += nWidth / fAllWish * nDistributeExcess;
1216 Reference< XPropertySet > xColSet( xCols->getByIndex( nCol ), UNO_QUERY_THROW );
1217 xColSet->setPropertyValue( gsSize, Any( nWidth ) );
1219 nAllWidth -= nWidth;
1222 LayoutTable( rArea, !bMinimize, false );
1224 catch( Exception& )
1226 OSL_FAIL("sdr::table::TableLayouter::DistributeColumns(), exception caught!");
1231 void TableLayouter::DistributeRows( ::tools::Rectangle& rArea,
1232 sal_Int32 nFirstRow,
1233 sal_Int32 nLastRow,
1234 const bool bOptimize,
1235 const bool bMinimize )
1237 if( mxTable.is() ) try
1239 const sal_Int32 nRowCount = mxTable->getRowCount();
1240 Reference< XTableRows > xRows( mxTable->getRows(), UNO_SET_THROW );
1241 sal_Int32 nMinHeight = 0;
1243 //special case - minimize a single row
1244 if ( bMinimize && nFirstRow == nLastRow )
1246 const sal_Int32 nWish = std::max( maRows[nFirstRow].mnMinSize, nMinHeight );
1247 if ( nWish < getRowHeight(nFirstRow) )
1249 Reference< XPropertySet > xRowSet( xRows->getByIndex( nFirstRow ), UNO_QUERY_THROW );
1250 xRowSet->setPropertyValue( gsSize, Any( nWish ) );
1252 LayoutTable( rArea, /*bFitWidth=*/false, /*bFitHeight=*/!bMinimize );
1256 if( (nFirstRow < 0) || (nFirstRow>= nLastRow) || (nLastRow >= nRowCount) )
1257 return;
1259 sal_Int32 nAllHeight = 0;
1260 sal_Int32 nMaxHeight = 0;
1262 for( sal_Int32 nRow = nFirstRow; nRow <= nLastRow; ++nRow )
1264 nMinHeight = std::max( maRows[nRow].mnMinSize, nMinHeight );
1265 nMaxHeight = std::max( maRows[nRow].mnSize, nMaxHeight );
1266 nAllHeight += maRows[nRow].mnSize;
1269 const sal_Int32 nRows = nLastRow-nFirstRow+1;
1270 sal_Int32 nHeight = nAllHeight / nRows;
1272 if ( !bMinimize && nHeight < nMaxHeight )
1274 if ( !bOptimize )
1276 sal_Int32 nNeededHeight = nRows * nMaxHeight;
1277 rArea.AdjustBottom(nNeededHeight - nAllHeight );
1278 nHeight = nMaxHeight;
1279 nAllHeight = nRows * nMaxHeight;
1281 else if ( nHeight < nMinHeight )
1283 sal_Int32 nNeededHeight = nRows * nMinHeight;
1284 rArea.AdjustBottom(nNeededHeight - nAllHeight );
1285 nHeight = nMinHeight;
1286 nAllHeight = nRows * nMinHeight;
1290 for( sal_Int32 nRow = nFirstRow; nRow <= nLastRow; ++nRow )
1292 if ( bMinimize )
1293 nHeight = maRows[nRow].mnMinSize;
1294 else if ( nRow == nLastRow )
1295 nHeight = nAllHeight; // last row get round errors
1297 Reference< XPropertySet > xRowSet( xRows->getByIndex( nRow ), UNO_QUERY_THROW );
1298 xRowSet->setPropertyValue( gsSize, Any( nHeight ) );
1300 nAllHeight -= nHeight;
1303 LayoutTable( rArea, false, !bMinimize );
1305 catch( Exception& )
1307 OSL_FAIL("sdr::table::TableLayouter::DistributeRows(), exception caught!");
1311 void TableLayouter::dumpAsXml(xmlTextWriterPtr pWriter) const
1313 xmlTextWriterStartElement(pWriter, BAD_CAST("TableLayouter"));
1315 xmlTextWriterStartElement(pWriter, BAD_CAST("columns"));
1316 for (const auto& rColumn : maColumns)
1317 rColumn.dumpAsXml(pWriter);
1318 xmlTextWriterEndElement(pWriter);
1320 xmlTextWriterStartElement(pWriter, BAD_CAST("rows"));
1321 for (const auto& rRow : maRows)
1322 rRow.dumpAsXml(pWriter);
1323 xmlTextWriterEndElement(pWriter);
1325 xmlTextWriterEndElement(pWriter);
1328 void TableLayouter::Layout::dumpAsXml(xmlTextWriterPtr pWriter) const
1330 xmlTextWriterStartElement(pWriter, BAD_CAST("TableLayouter_Layout"));
1332 xmlTextWriterWriteAttribute(pWriter, BAD_CAST("pos"), BAD_CAST(OString::number(mnPos).getStr()));
1333 xmlTextWriterWriteAttribute(pWriter, BAD_CAST("size"), BAD_CAST(OString::number(mnSize).getStr()));
1334 xmlTextWriterWriteAttribute(pWriter, BAD_CAST("minSize"), BAD_CAST(OString::number(mnMinSize).getStr()));
1336 xmlTextWriterEndElement(pWriter);
1341 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */