/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ /* * This file is part of the LibreOffice project. * * This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. * * This file incorporates work covered by the following license notice: * * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed * with this work for additional information regarding copyright * ownership. The ASF licenses this file to you under the Apache * License, Version 2.0 (the "License"); you may not use this file * except in compliance with the License. You may obtain a copy of * the License at http://www.apache.org/licenses/LICENSE-2.0 . */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include using namespace ::com::sun::star; SwTabFrame::SwTabFrame( SwTable &rTab, SwFrame* pSib ) : SwLayoutFrame( rTab.GetFrameFormat(), pSib ) , SwFlowFrame( static_cast(*this) ) , m_pTable( &rTab ) , m_bComplete(false) , m_bCalcLowers(false) , m_bLowersFormatted(false) , m_bLockBackMove(false) , m_bResizeHTMLTable(false) , m_bONECalcLowers(false) , m_bHasFollowFlowLine(false) , m_bIsRebuildLastLine(false) , m_bRestrictTableGrowth(false) , m_bRemoveFollowFlowLinePending(false) , m_bConsiderObjsForMinCellHeight(true) , m_bObjsDoesFit(true) , m_bInRecalcLowerRow(false) , m_bSplitRowDisabled(false) { mbFixSize = false; //Don't fall for import filter again. mnFrameType = SwFrameType::Tab; //Create the lines and insert them. const SwTableLines &rLines = rTab.GetTabLines(); SwFrame *pTmpPrev = nullptr; for ( size_t i = 0; i < rLines.size(); ++i ) { SwRowFrame *pNew = new SwRowFrame( *rLines[i], this ); if( pNew->Lower() ) { pNew->InsertBehind( this, pTmpPrev ); pTmpPrev = pNew; } else SwFrame::DestroyFrame(pNew); } OSL_ENSURE( Lower() && Lower()->IsRowFrame(), "SwTabFrame::SwTabFrame: No rows." ); } SwTabFrame::SwTabFrame( SwTabFrame &rTab ) : SwLayoutFrame( rTab.GetFormat(), &rTab ) , SwFlowFrame( static_cast(*this) ) , m_pTable( rTab.GetTable() ) , m_bComplete(false) , m_bCalcLowers(false) , m_bLowersFormatted(false) , m_bLockBackMove(false) , m_bResizeHTMLTable(false) , m_bONECalcLowers(false) , m_bHasFollowFlowLine(false) , m_bIsRebuildLastLine(false) , m_bRestrictTableGrowth(false) , m_bRemoveFollowFlowLinePending(false) , m_bConsiderObjsForMinCellHeight(true) , m_bObjsDoesFit(true) , m_bInRecalcLowerRow(false) , m_bSplitRowDisabled(false) { mbFixSize = false; //Don't fall for import filter again. mnFrameType = SwFrameType::Tab; SetFollow( rTab.GetFollow() ); rTab.SetFollow( this ); } void SwTabFrame::DestroyImpl() { // There is some terrible code in fetab.cxx, that // caches pointers to SwTabFrames. ::ClearFEShellTabCols(*GetFormat()->GetDoc(), this); SwLayoutFrame::DestroyImpl(); } SwTabFrame::~SwTabFrame() { } void SwTabFrame::JoinAndDelFollows() { SwTabFrame *pFoll = GetFollow(); if ( pFoll->HasFollow() ) pFoll->JoinAndDelFollows(); pFoll->Cut(); SetFollow( pFoll->GetFollow() ); SwFrame::DestroyFrame(pFoll); } void SwTabFrame::RegistFlys() { OSL_ENSURE( Lower() && Lower()->IsRowFrame(), "No rows." ); SwPageFrame *pPage = FindPageFrame(); if ( pPage ) { SwRowFrame *pRow = static_cast(Lower()); do { pRow->RegistFlys( pPage ); pRow = static_cast(pRow->GetNext()); } while ( pRow ); } } static void SwInvalidateAll( SwFrame *pFrame, long nBottom ); static void lcl_RecalcRow( SwRowFrame& rRow, long nBottom ); static bool lcl_ArrangeLowers( SwLayoutFrame *pLay, long lYStart, bool bInva ); // #i26945# - add parameter <_bOnlyRowsAndCells> to control // that only row and cell frames are formatted. static bool lcl_InnerCalcLayout( SwFrame *pFrame, long nBottom, bool _bOnlyRowsAndCells = false ); // OD 2004-02-18 #106629# - correct type of 1st parameter // #i26945# - add parameter <_bConsiderObjs> in order to // control, if floating screen objects have to be considered for the minimal // cell height. static SwTwips lcl_CalcMinRowHeight( const SwRowFrame *pRow, const bool _bConsiderObjs ); static SwTwips lcl_CalcTopAndBottomMargin( const SwLayoutFrame&, const SwBorderAttrs& ); static SwTwips lcl_calcHeightOfRowBeforeThisFrame(const SwRowFrame& rRow); static SwTwips lcl_GetHeightOfRows( const SwFrame* pStart, long nCount ) { if ( !nCount || !pStart) return 0; SwTwips nRet = 0; SwRectFnSet aRectFnSet(pStart); while ( pStart && nCount > 0 ) { nRet += aRectFnSet.GetHeight(pStart->getFrameArea()); pStart = pStart->GetNext(); --nCount; } return nRet; } // Local helper function to insert a new follow flow line static SwRowFrame* lcl_InsertNewFollowFlowLine( SwTabFrame& rTab, const SwFrame& rTmpRow, bool bRowSpanLine ) { OSL_ENSURE( rTmpRow.IsRowFrame(), "No row frame to copy for FollowFlowLine" ); const SwRowFrame& rRow = static_cast(rTmpRow); rTab.SetFollowFlowLine( true ); SwRowFrame *pFollowFlowLine = new SwRowFrame(*rRow.GetTabLine(), &rTab, false ); pFollowFlowLine->SetRowSpanLine( bRowSpanLine ); SwFrame* pFirstRow = rTab.GetFollow()->GetFirstNonHeadlineRow(); pFollowFlowLine->InsertBefore( rTab.GetFollow(), pFirstRow ); return pFollowFlowLine; } // #i26945# - local helper function to invalidate all lower // objects. By parameter <_bMoveObjsOutOfRange> it can be controlled, if // additionally the objects are moved 'out of range'. static void lcl_InvalidateLowerObjs( SwLayoutFrame& _rLayoutFrame, const bool _bMoveObjsOutOfRange = false, SwPageFrame* _pPageFrame = nullptr ) { // determine page frame, if needed if ( !_pPageFrame ) { _pPageFrame = _rLayoutFrame.FindPageFrame(); OSL_ENSURE( _pPageFrame, " - missing page frame -> no move of lower objects out of range" ); if ( !_pPageFrame ) { return; } } // loop on lower frames SwFrame* pLowerFrame = _rLayoutFrame.Lower(); while ( pLowerFrame ) { if ( pLowerFrame->IsLayoutFrame() ) { ::lcl_InvalidateLowerObjs( *static_cast(pLowerFrame), _bMoveObjsOutOfRange, _pPageFrame ); } if ( pLowerFrame->GetDrawObjs() ) { for (SwAnchoredObject* pAnchoredObj : *pLowerFrame->GetDrawObjs()) { // invalidate position of anchored object pAnchoredObj->SetTmpConsiderWrapInfluence( false ); pAnchoredObj->SetConsiderForTextWrap( false ); pAnchoredObj->UnlockPosition(); pAnchoredObj->InvalidateObjPos(); SwFlyFrame *pFly = dynamic_cast(pAnchoredObj); // move anchored object 'out of range' if ( _bMoveObjsOutOfRange ) { // indicate, that positioning is progress to avoid // modification of the anchored object resp. it's attributes // due to the movement SwObjPositioningInProgress aObjPosInProgress( *pAnchoredObj ); pAnchoredObj->SetObjLeft( _pPageFrame->getFrameArea().Right() ); // #115759# - reset character rectangle, // top of line and relative position in order to assure, // that anchored object is correctly positioned. pAnchoredObj->ClearCharRectAndTopOfLine(); pAnchoredObj->SetCurrRelPos( Point( 0, 0 ) ); if ( pAnchoredObj->GetFrameFormat().GetAnchor().GetAnchorId() == RndStdIds::FLY_AS_CHAR ) { pAnchoredObj->AnchorFrame() ->Prepare( PrepareHint::FlyFrameAttributesChanged, &(pAnchoredObj->GetFrameFormat()) ); } if ( pFly != nullptr ) { pFly->GetVirtDrawObj()->SetRectsDirty(); pFly->GetVirtDrawObj()->SetChanged(); } } // If anchored object is a fly frame, invalidate its lower objects if ( pFly != nullptr ) { ::lcl_InvalidateLowerObjs( *pFly, _bMoveObjsOutOfRange, _pPageFrame ); } } } pLowerFrame = pLowerFrame->GetNext(); } } // Local helper function to shrink all lowers of pRow to 0 height static void lcl_ShrinkCellsAndAllContent( SwRowFrame& rRow ) { SwCellFrame* pCurrMasterCell = static_cast(rRow.Lower()); SwRectFnSet aRectFnSet(pCurrMasterCell); bool bAllCellsCollapsed = true; while ( pCurrMasterCell ) { // NEW TABLES SwCellFrame& rToAdjust = pCurrMasterCell->GetTabBox()->getRowSpan() < 1 ? const_cast(pCurrMasterCell->FindStartEndOfRowSpanCell( true )) : *pCurrMasterCell; // #i26945# // all lowers should have the correct position lcl_ArrangeLowers( &rToAdjust, aRectFnSet.GetPrtTop(rToAdjust), false ); // TODO: Optimize number of frames which are set to 0 height // we have to start with the last lower frame, otherwise // the shrink will not shrink the current cell SwFrame* pTmp = rToAdjust.GetLastLower(); bool bAllLowersCollapsed = true; if ( pTmp && pTmp->IsRowFrame() ) { SwRowFrame* pTmpRow = static_cast(pTmp); lcl_ShrinkCellsAndAllContent( *pTmpRow ); } else { // TODO: Optimize number of frames which are set to 0 height while ( pTmp ) { // the frames have to be shrunk if ( pTmp->IsTabFrame() ) { SwRowFrame* pTmpRow = static_cast(static_cast(pTmp)->Lower()); bool bAllRowsCollapsed = true; while ( pTmpRow ) { lcl_ShrinkCellsAndAllContent( *pTmpRow ); if (aRectFnSet.GetHeight(pTmpRow->getFrameArea()) > 0) bAllRowsCollapsed = false; pTmpRow = static_cast(pTmpRow->GetNext()); } if (bAllRowsCollapsed) { // All rows of this table have 0 height -> set height of the table itself as well. SwFrameAreaDefinition::FrameAreaWriteAccess aFrm(*pTmp); aRectFnSet.SetHeight(aFrm, 0); SwFrameAreaDefinition::FramePrintAreaWriteAccess aPrt(*pTmp); aRectFnSet.SetTop(aPrt, 0); aRectFnSet.SetHeight(aPrt, 0); } else bAllLowersCollapsed = false; } else { pTmp->Shrink(aRectFnSet.GetHeight(pTmp->getFrameArea())); SwFrameAreaDefinition::FramePrintAreaWriteAccess aPrt(*pTmp); aRectFnSet.SetTop(aPrt, 0); aRectFnSet.SetHeight(aPrt, 0); if (aRectFnSet.GetHeight(pTmp->getFrameArea()) > 0) { bAllLowersCollapsed = false; } } pTmp = pTmp->GetPrev(); } // all lowers should have the correct position lcl_ArrangeLowers( &rToAdjust, aRectFnSet.GetPrtTop(rToAdjust), false ); } if (bAllLowersCollapsed) { // All lower frame of this cell have 0 height -> set height of the cell itself as well. SwFrameAreaDefinition::FrameAreaWriteAccess aFrm(*pCurrMasterCell); aRectFnSet.SetHeight(aFrm, 0); SwFrameAreaDefinition::FramePrintAreaWriteAccess aPrt(*pCurrMasterCell); aRectFnSet.SetTop(aPrt, 0); aRectFnSet.SetHeight(aPrt, 0); } else bAllCellsCollapsed = false; pCurrMasterCell = static_cast(pCurrMasterCell->GetNext()); } if (bAllCellsCollapsed) { // All cells have 0 height -> set height of row as well. SwFrameAreaDefinition::FrameAreaWriteAccess aFrm(rRow); aRectFnSet.SetHeight(aFrm, 0); SwFrameAreaDefinition::FramePrintAreaWriteAccess aPrt(rRow); aRectFnSet.SetTop(aPrt, 0); aRectFnSet.SetHeight(aPrt, 0); } } // Local helper function to move the content from rSourceLine to rDestLine // The content is inserted behind the last content in the corresponding // cell in rDestLine. static void lcl_MoveRowContent( SwRowFrame& rSourceLine, SwRowFrame& rDestLine ) { SwCellFrame* pCurrDestCell = static_cast(rDestLine.Lower()); SwCellFrame* pCurrSourceCell = static_cast(rSourceLine.Lower()); // Move content of follow cells into master cells while ( pCurrSourceCell ) { if ( pCurrSourceCell->Lower() && pCurrSourceCell->Lower()->IsRowFrame() ) { SwRowFrame* pTmpSourceRow = static_cast(pCurrSourceCell->Lower()); while ( pTmpSourceRow ) { // #125926# Attention! It is possible, // that pTmpSourceRow->IsFollowFlowRow() but pTmpDestRow // cannot be found. In this case, we have to move the complete // row. SwRowFrame* pTmpDestRow = static_cast(pCurrDestCell->Lower()); if ( pTmpSourceRow->IsFollowFlowRow() && pTmpDestRow ) { // move content from follow flow row to pTmpDestRow: while ( pTmpDestRow->GetNext() ) pTmpDestRow = static_cast(pTmpDestRow->GetNext()); assert(pTmpDestRow->GetFollowRow() == pTmpSourceRow); lcl_MoveRowContent( *pTmpSourceRow, *pTmpDestRow ); pTmpDestRow->SetFollowRow( pTmpSourceRow->GetFollowRow() ); pTmpSourceRow->RemoveFromLayout(); SwFrame::DestroyFrame(pTmpSourceRow); } else { // move complete row: pTmpSourceRow->RemoveFromLayout(); pTmpSourceRow->InsertBefore( pCurrDestCell, nullptr ); } pTmpSourceRow = static_cast(pCurrSourceCell->Lower()); } } else { SwFrame *pTmp = ::SaveContent( pCurrSourceCell ); if ( pTmp ) { // NEW TABLES SwCellFrame* pDestCell = pCurrDestCell; if ( pDestCell->GetTabBox()->getRowSpan() < 1 ) pDestCell = & const_cast(pDestCell->FindStartEndOfRowSpanCell( true )); // Find last content SwFrame* pFrame = pDestCell->GetLastLower(); ::RestoreContent( pTmp, pDestCell, pFrame ); } } pCurrDestCell = static_cast(pCurrDestCell->GetNext()); pCurrSourceCell = static_cast(pCurrSourceCell->GetNext()); } } // Local helper function to move all footnotes in rRowFrame from // the footnote boss of rSource to the footnote boss of rDest. static void lcl_MoveFootnotes( SwTabFrame& rSource, SwTabFrame& rDest, SwLayoutFrame& rRowFrame ) { if ( !rSource.GetFormat()->GetDoc()->GetFootnoteIdxs().empty() ) { SwFootnoteBossFrame* pOldBoss = rSource.FindFootnoteBossFrame( true ); SwFootnoteBossFrame* pNewBoss = rDest.FindFootnoteBossFrame( true ); rRowFrame.MoveLowerFootnotes( nullptr, pOldBoss, pNewBoss, true ); } } // Local helper function to handle nested table cells before the split process static void lcl_PreprocessRowsInCells( SwTabFrame& rTab, SwRowFrame& rLastLine, SwRowFrame& rFollowFlowLine, SwTwips nRemain ) { SwCellFrame* pCurrLastLineCell = static_cast(rLastLine.Lower()); SwCellFrame* pCurrFollowFlowLineCell = static_cast(rFollowFlowLine.Lower()); SwRectFnSet aRectFnSet(pCurrLastLineCell); // Move content of follow cells into master cells while ( pCurrLastLineCell ) { if ( pCurrLastLineCell->Lower() && pCurrLastLineCell->Lower()->IsRowFrame() ) { SwTwips nTmpCut = nRemain; SwRowFrame* pTmpLastLineRow = static_cast(pCurrLastLineCell->Lower()); // #i26945# SwTwips nCurrentHeight = lcl_CalcMinRowHeight( pTmpLastLineRow, rTab.IsConsiderObjsForMinCellHeight() ); while ( pTmpLastLineRow->GetNext() && nTmpCut > nCurrentHeight ) { nTmpCut -= nCurrentHeight; pTmpLastLineRow = static_cast(pTmpLastLineRow->GetNext()); // #i26945# nCurrentHeight = lcl_CalcMinRowHeight( pTmpLastLineRow, rTab.IsConsiderObjsForMinCellHeight() ); } // pTmpLastLineRow does not fit to the line or it is the last line // Check if we can move pTmpLastLineRow to the follow table, // or if we have to split the line: bool bTableLayoutTooComplex = false; long nMinHeight = 0; // We have to take into account: // 1. The fixed height of the row // 2. The borders of the cells inside the row // 3. The minimum height of the row if ( pTmpLastLineRow->HasFixSize() ) nMinHeight = aRectFnSet.GetHeight(pTmpLastLineRow->getFrameArea()); else { { const SwFormatFrameSize &rSz = pTmpLastLineRow->GetFormat()->GetFrameSize(); if ( rSz.GetHeightSizeType() == SwFrameSize::Minimum ) nMinHeight = rSz.GetHeight() - lcl_calcHeightOfRowBeforeThisFrame(*pTmpLastLineRow); } SwFrame* pCell = pTmpLastLineRow->Lower(); while ( pCell ) { if ( static_cast(pCell)->Lower() && static_cast(pCell)->Lower()->IsRowFrame() ) { bTableLayoutTooComplex = true; break; } SwBorderAttrAccess aAccess( SwFrame::GetCache(), pCell ); const SwBorderAttrs &rAttrs = *aAccess.Get(); nMinHeight = std::max( nMinHeight, lcl_CalcTopAndBottomMargin( *static_cast(pCell), rAttrs ) ); pCell = pCell->GetNext(); } } // 1. Case: // The line completely fits into the master table. // Nevertheless, we build a follow (otherwise painting problems // with empty cell). // 2. Case: // The line has to be split, the minimum height still fits into // the master table, and the table structure is not too complex. if ( nTmpCut > nCurrentHeight || ( pTmpLastLineRow->IsRowSplitAllowed() && !bTableLayoutTooComplex && nMinHeight < nTmpCut ) ) { // The line has to be split: SwRowFrame* pNewRow = new SwRowFrame( *pTmpLastLineRow->GetTabLine(), &rTab, false ); pNewRow->SetFollowFlowRow( true ); pNewRow->SetFollowRow( pTmpLastLineRow->GetFollowRow() ); pTmpLastLineRow->SetFollowRow( pNewRow ); pNewRow->InsertBehind( pCurrFollowFlowLineCell, nullptr ); pTmpLastLineRow = static_cast(pTmpLastLineRow->GetNext()); } // The following lines have to be moved: while ( pTmpLastLineRow ) { SwRowFrame* pTmp = static_cast(pTmpLastLineRow->GetNext()); lcl_MoveFootnotes( rTab, *rTab.GetFollow(), *pTmpLastLineRow ); pTmpLastLineRow->RemoveFromLayout(); pTmpLastLineRow->InsertBefore( pCurrFollowFlowLineCell, nullptr ); pTmpLastLineRow->Shrink( aRectFnSet.GetHeight(pTmpLastLineRow->getFrameArea()) ); pCurrFollowFlowLineCell->Grow( aRectFnSet.GetHeight(pTmpLastLineRow->getFrameArea()) ); pTmpLastLineRow = pTmp; } } pCurrLastLineCell = static_cast(pCurrLastLineCell->GetNext()); pCurrFollowFlowLineCell = static_cast(pCurrFollowFlowLineCell->GetNext()); } } // Local helper function to handle nested table cells after the split process static void lcl_PostprocessRowsInCells( SwTabFrame& rTab, SwRowFrame& rLastLine ) { SwCellFrame* pCurrMasterCell = static_cast(rLastLine.Lower()); while ( pCurrMasterCell ) { if ( pCurrMasterCell->Lower() && pCurrMasterCell->Lower()->IsRowFrame() ) { SwRowFrame* pRowFrame = static_cast(pCurrMasterCell->GetLastLower()); if ( nullptr != pRowFrame->GetPrev() && !pRowFrame->ContainsContent() ) { OSL_ENSURE( pRowFrame->GetFollowRow(), "Deleting row frame without follow" ); // The footnotes have to be moved: lcl_MoveFootnotes( rTab, *rTab.GetFollow(), *pRowFrame ); pRowFrame->Cut(); SwRowFrame* pFollowRow = pRowFrame->GetFollowRow(); pRowFrame->Paste( pFollowRow->GetUpper(), pFollowRow ); pRowFrame->SetFollowRow( pFollowRow->GetFollowRow() ); lcl_MoveRowContent( *pFollowRow, *pRowFrame ); pFollowRow->Cut(); SwFrame::DestroyFrame(pFollowRow); ::SwInvalidateAll( pCurrMasterCell, LONG_MAX ); } } pCurrMasterCell = static_cast(pCurrMasterCell->GetNext()); } } // Local helper function to re-calculate the split line. inline void TableSplitRecalcLock( SwFlowFrame *pTab ) { pTab->LockJoin(); } inline void TableSplitRecalcUnlock( SwFlowFrame *pTab ) { pTab->UnlockJoin(); } static bool lcl_RecalcSplitLine( SwRowFrame& rLastLine, SwRowFrame& rFollowLine, SwTwips nRemainingSpaceForLastRow, SwTwips nAlreadyFree ) { bool bRet = true; vcl::RenderContext* pRenderContext = rLastLine.getRootFrame()->GetCurrShell()->GetOut(); SwTabFrame& rTab = static_cast(*rLastLine.GetUpper()); SwRectFnSet aRectFnSet(rTab.GetUpper()); SwTwips nCurLastLineHeight = aRectFnSet.GetHeight(rLastLine.getFrameArea()); // If there are nested cells in rLastLine, the recalculation of the last // line needs some preprocessing. lcl_PreprocessRowsInCells( rTab, rLastLine, rFollowLine, nRemainingSpaceForLastRow ); // Here the recalculation process starts: rTab.SetRebuildLastLine( true ); // #i26945# rTab.SetDoesObjsFit( true ); // #i26945# - invalidate and move floating screen // objects 'out of range' ::lcl_InvalidateLowerObjs( rLastLine, true ); // manipulate row and cell sizes // #i26945# - Do *not* consider floating screen objects // for the minimal cell height. rTab.SetConsiderObjsForMinCellHeight( false ); ::lcl_ShrinkCellsAndAllContent( rLastLine ); rTab.SetConsiderObjsForMinCellHeight( true ); // invalidate last line ::SwInvalidateAll( &rLastLine, LONG_MAX ); // Shrink the table to account for the shrunk last row, as well as lower rows // that had been moved to follow table in SwTabFrame::Split. // It will grow later when last line will recalc its height. rTab.Shrink(nAlreadyFree + nCurLastLineHeight - nRemainingSpaceForLastRow + 1); // Lock this tab frame and its follow bool bUnlockMaster = false; SwFlowFrame * pFollow = nullptr; SwTabFrame* pMaster = rTab.IsFollow() ? rTab.FindMaster() : nullptr; if ( pMaster && !pMaster->IsJoinLocked() ) { bUnlockMaster = true; ::TableSplitRecalcLock( pMaster ); } if ( !rTab.GetFollow()->IsJoinLocked() ) { pFollow = rTab.GetFollow(); ::TableSplitRecalcLock( pFollow ); } bool bInSplit = rLastLine.IsInSplit(); rLastLine.SetInSplit(); // Do the recalculation lcl_RecalcRow( rLastLine, LONG_MAX ); // #115759# - force a format of the last line in order to // get the correct height. rLastLine.InvalidateSize(); rLastLine.Calc(pRenderContext); rLastLine.SetInSplit(bInSplit); // Unlock this tab frame and its follow if ( pFollow ) ::TableSplitRecalcUnlock( pFollow ); if ( bUnlockMaster ) ::TableSplitRecalcUnlock( pMaster ); // If there are nested cells in rLastLine, the recalculation of the last // line needs some postprocessing. lcl_PostprocessRowsInCells( rTab, rLastLine ); // Do a couple of checks on the current situation. // If we are not happy with the current situation we return false. // This will start a new try to split the table, this time we do not // try to split the table rows. // 1. Check if table fits to its upper. // #i26945# - include check, if objects fit const SwTwips nDistanceToUpperPrtBottom = aRectFnSet.BottomDist(rTab.getFrameArea(), aRectFnSet.GetPrtBottom(*rTab.GetUpper())); // tdf#125685 ignore footnotes that are anchored in follow-table of this // table - if split is successful they move to the next page/column anyway assert(rTab.GetFollow() == rFollowLine.GetUpper()); SwTwips nFollowFootnotes(0); // actually there should always be a boss frame, except if "this" isn't // connected to a page yet; not sure if that can happen if (SwFootnoteBossFrame const*const pBoss = rTab.FindFootnoteBossFrame()) { if (SwFootnoteContFrame const*const pCont = pBoss->FindFootnoteCont()) { for (SwFootnoteFrame const* pFootnote = static_cast(pCont->Lower()); pFootnote != nullptr; pFootnote = static_cast(pFootnote->GetNext())) { SwContentFrame const*const pAnchor = pFootnote->GetRef(); SwTabFrame const* pTab = pAnchor->FindTabFrame(); if (pTab) { while (pTab->GetUpper()->IsInTab()) { pTab = pTab->GetUpper()->FindTabFrame(); } // TODO currently do this only for top-level tables? // otherwise would need to check rTab's follow and any upper table's follow? if (pTab == rTab.GetFollow()) { nFollowFootnotes += aRectFnSet.GetHeight(pFootnote->getFrameArea()); } } } } } if (nDistanceToUpperPrtBottom + nFollowFootnotes < 0 || !rTab.DoesObjsFit()) bRet = false; // 2. Check if each cell in the last line has at least one content frame. // Note: a FollowFlowRow may contains empty cells! if ( bRet ) { if ( !rLastLine.IsInFollowFlowRow() ) { SwCellFrame* pCurrMasterCell = static_cast(rLastLine.Lower()); while ( pCurrMasterCell ) { if ( !pCurrMasterCell->ContainsContent() && pCurrMasterCell->GetTabBox()->getRowSpan() >= 1 ) { bRet = false; break; } pCurrMasterCell = static_cast(pCurrMasterCell->GetNext()); } } } // 3. Check if last line does not contain any content: if ( bRet ) { if ( !rLastLine.ContainsContent() ) { bRet = false; } } // 4. Check if follow flow line does not contain content: if ( bRet ) { if ( !rFollowLine.IsRowSpanLine() && !rFollowLine.ContainsContent() ) { bRet = false; } } if ( bRet ) { // Everything looks fine. Splitting seems to be successful. We invalidate // rFollowLine to force a new formatting. ::SwInvalidateAll( &rFollowLine, LONG_MAX ); } else { // Splitting the table row gave us an unexpected result. // Everything has to be prepared for a second try to split // the table, this time without splitting the row. ::SwInvalidateAll( &rLastLine, LONG_MAX ); } rTab.SetRebuildLastLine( false ); // #i26945# rTab.SetDoesObjsFit( true ); return bRet; } // Sets the correct height for all spanned cells static void lcl_AdjustRowSpanCells( SwRowFrame* pRow ) { SwRectFnSet aRectFnSet(pRow); SwCellFrame* pCellFrame = static_cast(pRow->GetLower()); while ( pCellFrame ) { const long nLayoutRowSpan = pCellFrame->GetLayoutRowSpan(); if ( nLayoutRowSpan > 1 ) { // calculate height of cell: const long nNewCellHeight = lcl_GetHeightOfRows( pRow, nLayoutRowSpan ); const long nDiff = nNewCellHeight - aRectFnSet.GetHeight(pCellFrame->getFrameArea()); if ( nDiff ) { SwFrameAreaDefinition::FrameAreaWriteAccess aFrm(*pCellFrame); aRectFnSet.AddBottom(aFrm, nDiff); } } pCellFrame = static_cast(pCellFrame->GetNext()); } } // Returns the maximum layout row span of the row // Looking for the next row that contains no covered cells: static long lcl_GetMaximumLayoutRowSpan( const SwRowFrame& rRow ) { long nRet = 1; const SwRowFrame* pCurrentRowFrame = static_cast(rRow.GetNext()); bool bNextRow = false; while ( pCurrentRowFrame ) { // if there is any covered cell, we proceed to the next row frame const SwCellFrame* pLower = static_cast( pCurrentRowFrame->Lower()); while ( pLower ) { if ( pLower->GetTabBox()->getRowSpan() < 0 ) { ++nRet; bNextRow = true; break; } pLower = static_cast(pLower->GetNext()); } pCurrentRowFrame = bNextRow ? static_cast(pCurrentRowFrame->GetNext() ) : nullptr; } return nRet; } // Function to remove the FollowFlowLine of rTab. // The content of the FollowFlowLine is moved to the associated line in the // master table. bool SwTabFrame::RemoveFollowFlowLine() { // find FollowFlowLine SwTabFrame *pFoll = GetFollow(); SwRowFrame* pFollowFlowLine = pFoll ? pFoll->GetFirstNonHeadlineRow() : nullptr; // find last row in master SwFrame* pLastLine = GetLastLower(); OSL_ENSURE( HasFollowFlowLine() && pFollowFlowLine && pLastLine, "There should be a flowline in the follow" ); // #140081# Make code robust. if ( !pFollowFlowLine || !pLastLine ) return true; if (pFollowFlowLine->IsDeleteForbidden()) { SAL_WARN("sw.layout", "Cannot remove in-use Follow Flow Line"); return false; } // We have to reset the flag here, because lcl_MoveRowContent // calls a GrowFrame(), which has a different behavior if // this flag is set. SetFollowFlowLine( false ); // Move content lcl_MoveRowContent( *pFollowFlowLine, *static_cast(pLastLine) ); // NEW TABLES // If a row span follow flow line is removed, we want to move the whole span // to the master: long nRowsToMove = lcl_GetMaximumLayoutRowSpan( *pFollowFlowLine ); if ( nRowsToMove > 1 ) { SwRectFnSet aRectFnSet(this); SwFrame* pRow = pFollowFlowLine->GetNext(); SwFrame* pInsertBehind = GetLastLower(); SwTwips nGrow = 0; while ( pRow && nRowsToMove-- > 1 ) { SwFrame* pNxt = pRow->GetNext(); nGrow += aRectFnSet.GetHeight(pRow->getFrameArea()); // The footnotes have to be moved: lcl_MoveFootnotes( *GetFollow(), *this, static_cast(*pRow) ); pRow->RemoveFromLayout(); pRow->InsertBehind( this, pInsertBehind ); pRow->InvalidateAll_(); pRow->CheckDirChange(); pInsertBehind = pRow; pRow = pNxt; } SwFrame* pFirstRow = Lower(); while ( pFirstRow ) { lcl_AdjustRowSpanCells( static_cast(pFirstRow) ); pFirstRow = pFirstRow->GetNext(); } Grow( nGrow ); GetFollow()->Shrink( nGrow ); } bool bJoin = !pFollowFlowLine->GetNext(); pFollowFlowLine->Cut(); SwFrame::DestroyFrame(pFollowFlowLine); return bJoin; } // #i26945# - Floating screen objects are no longer searched. static bool lcl_FindSectionsInRow( const SwRowFrame& rRow ) { bool bRet = false; const SwCellFrame* pLower = static_cast(rRow.Lower()); while ( pLower ) { if ( pLower->IsVertical() != rRow.IsVertical() ) return true; const SwFrame* pTmpFrame = pLower->Lower(); while ( pTmpFrame ) { if ( pTmpFrame->IsRowFrame() ) { bRet = lcl_FindSectionsInRow( *static_cast(pTmpFrame) ); } else { // #i26945# - search only for sections if (pTmpFrame->IsSctFrame()) { bRet = true; if (!rRow.IsInSct()) { // This row is not in a section. if (const SwFrame* pSectionLower = pTmpFrame->GetLower()) { if (!pSectionLower->IsColumnFrame()) { // Section has a single column only, try to // split that. bRet = false; for (const SwFrame* pFrame = pSectionLower; pFrame; pFrame = pFrame->GetNext()) { if (pFrame->IsTabFrame()) { // Section contains a table, no split in that case. bRet = true; break; } } } } } } } if ( bRet ) return true; pTmpFrame = pTmpFrame->GetNext(); } pLower = static_cast(pLower->GetNext()); } return bRet; } bool SwTabFrame::Split( const SwTwips nCutPos, bool bTryToSplit, bool bTableRowKeep ) { bool bRet = true; SwRectFnSet aRectFnSet(this); // #i26745# - format row and cell frames of table { Lower()->InvalidatePos_(); // #i43913# - correction // call method with first lower. lcl_InnerCalcLayout( Lower(), LONG_MAX, true ); } //In order to be able to compare the positions of the cells with CutPos, //they have to be calculated consecutively starting from the table. //They can definitely be invalid because of position changes of the table. SwRowFrame *pRow = static_cast(Lower()); if( !pRow ) return bRet; const sal_uInt16 nRepeat = GetTable()->GetRowsToRepeat(); sal_uInt16 nRowCount = 0; // pRow currently points to the first row SwTwips nRemainingSpaceForLastRow = aRectFnSet.YDiff(nCutPos, aRectFnSet.GetTop(getFrameArea())); nRemainingSpaceForLastRow -= aRectFnSet.GetTopMargin(*this); // Make pRow point to the line that does not fit anymore: while( pRow->GetNext() && nRemainingSpaceForLastRow >= ( aRectFnSet.GetHeight(pRow->getFrameArea()) + (IsCollapsingBorders() ? pRow->GetBottomLineSize() : 0 ) ) ) { if( bTryToSplit || !pRow->IsRowSpanLine() || 0 != aRectFnSet.GetHeight(pRow->getFrameArea()) ) ++nRowCount; nRemainingSpaceForLastRow -= aRectFnSet.GetHeight(pRow->getFrameArea()); pRow = static_cast(pRow->GetNext()); } // bSplitRowAllowed: Row may be split according to its attributes. // bTryToSplit: Row will never be split if bTryToSplit = false. // This can either be passed as a parameter, indicating // that we are currently doing the second try to split the // table, or it will be set to false under certain // conditions that are not suitable for splitting // the row. bool bSplitRowAllowed = pRow->IsRowSplitAllowed() && !IsSplitRowDisabled(); // #i29438# // #i26945# - Floating screen objects no longer forbid // a splitting of the table row. // Special DoNotSplit case 1: // Search for sections inside pRow: if ( lcl_FindSectionsInRow( *pRow ) ) { bTryToSplit = false; } // #i29771# // To avoid loops, we do some checks before actually trying to split // the row. Maybe we should keep the next row in this table. // Note: This is only done if we are at the beginning of our upper bool bKeepNextRow = false; if ( nRowCount < nRepeat ) { // First case: One of the repeated headline does not fit to the page anymore. // tdf#88496 Disable repeated headline (like for #i44910#) to avoid loops and // to fix interoperability problems (very long tables only with headline) OSL_ENSURE( !GetIndPrev(), "Table is supposed to be at beginning" ); m_pTable->SetRowsToRepeat(0); return false; } else if ( !GetIndPrev() && nRepeat == nRowCount ) { // Second case: The first non-headline row does not fit to the page. // If it is not allowed to be split, or it contains a sub-row that // is not allowed to be split, we keep the row in this table: if ( bTryToSplit && bSplitRowAllowed ) { // Check if there are (first) rows inside this row, // which are not allowed to be split. SwCellFrame* pLowerCell = static_cast(pRow->Lower()); while ( pLowerCell ) { if ( pLowerCell->Lower() && pLowerCell->Lower()->IsRowFrame() ) { const SwRowFrame* pLowerRow = static_cast(pLowerCell->Lower()); if ( !pLowerRow->IsRowSplitAllowed() && aRectFnSet.GetHeight(pLowerRow->getFrameArea()) > nRemainingSpaceForLastRow ) { bKeepNextRow = true; break; } } pLowerCell = static_cast(pLowerCell->GetNext()); } } else bKeepNextRow = true; } // Better keep the next row in this table: if ( bKeepNextRow ) { pRow = GetFirstNonHeadlineRow(); if ( pRow && pRow->IsRowSpanLine() && 0 == aRectFnSet.GetHeight(pRow->getFrameArea()) ) pRow = static_cast(pRow->GetNext()); if ( pRow ) { pRow = static_cast(pRow->GetNext()); ++nRowCount; } } // No more row to split or to move to follow table: if ( !pRow ) return bRet; // We try to split the row if // - the attributes of the row are set accordingly and // - we are allowed to do so // - it should not be kept with the next row bSplitRowAllowed = bSplitRowAllowed && bTryToSplit && ( !bTableRowKeep || !pRow->ShouldRowKeepWithNext() ); // Adjust pRow according to the keep-with-next attribute: if ( !bSplitRowAllowed && bTableRowKeep ) { SwRowFrame* pTmpRow = static_cast(pRow->GetPrev()); SwRowFrame* pOldRow = pRow; while ( pTmpRow && pTmpRow->ShouldRowKeepWithNext() && nRowCount > nRepeat ) { pRow = pTmpRow; --nRowCount; pTmpRow = static_cast(pTmpRow->GetPrev()); } // loop prevention if ( nRowCount == nRepeat && !GetIndPrev()) { pRow = pOldRow; } } // If we do not intend to split pRow, we check if we are // allowed to move pRow to a follow. Otherwise we return // false, indicating an error if ( !bSplitRowAllowed ) { SwRowFrame* pFirstNonHeadlineRow = GetFirstNonHeadlineRow(); if ( pRow == pFirstNonHeadlineRow ) return false; // #i91764# // Ignore row span lines SwRowFrame* pTmpRow = pFirstNonHeadlineRow; while ( pTmpRow && pTmpRow->IsRowSpanLine() ) { pTmpRow = static_cast(pTmpRow->GetNext()); } if ( !pTmpRow || pRow == pTmpRow ) { return false; } } // Build follow table if not already done: bool bNewFollow; SwTabFrame *pFoll; if ( GetFollow() ) { pFoll = GetFollow(); bNewFollow = false; } else { bNewFollow = true; pFoll = new SwTabFrame( *this ); // We give the follow table an initial width. { SwFrameAreaDefinition::FrameAreaWriteAccess aFrm(*pFoll); aRectFnSet.AddWidth(aFrm, aRectFnSet.GetWidth(getFrameArea())); aRectFnSet.SetLeft(aFrm, aRectFnSet.GetLeft(getFrameArea())); } { SwFrameAreaDefinition::FramePrintAreaWriteAccess aPrt(*pFoll); aRectFnSet.AddWidth(aPrt, aRectFnSet.GetWidth(getFramePrintArea())); } // Insert the new follow table pFoll->InsertBehind( GetUpper(), this ); // Repeat the headlines. for ( nRowCount = 0; nRowCount < nRepeat; ++nRowCount ) { // Insert new headlines: bDontCreateObjects = true; //frmtool SwRowFrame* pHeadline = new SwRowFrame( *GetTable()->GetTabLines()[ nRowCount ], this ); pHeadline->SetRepeatedHeadline( true ); bDontCreateObjects = false; pHeadline->InsertBefore( pFoll, nullptr ); SwPageFrame *pPage = pHeadline->FindPageFrame(); const SwFrameFormats *pTable = GetFormat()->GetDoc()->GetSpzFrameFormats(); if( !pTable->empty() ) { sal_uLong nIndex; SwContentFrame* pFrame = pHeadline->ContainsContent(); while( pFrame ) { // sw_redlinehide: the implementation of AppendObjs // takes care of iterating merged SwTextFrame nIndex = pFrame->IsTextFrame() ? static_cast(pFrame)->GetTextNodeFirst()->GetIndex() : static_cast(pFrame)->GetNode()->GetIndex(); AppendObjs( pTable, nIndex, pFrame, pPage, GetFormat()->GetDoc()); pFrame = pFrame->GetNextContentFrame(); if( !pHeadline->IsAnLower( pFrame ) ) break; } } } } SwRowFrame* pLastRow = nullptr; // points to the last remaining line in master SwRowFrame* pFollowRow = nullptr; // points to either the follow flow line or the // first regular line in the follow if ( bSplitRowAllowed ) { // If the row that does not fit anymore is allowed // to be split, the next row has to be moved to the follow table. pLastRow = pRow; pRow = static_cast(pRow->GetNext()); // new follow flow line for last row of master table pFollowRow = lcl_InsertNewFollowFlowLine( *this, *pLastRow, false ); } else { pFollowRow = pRow; // NEW TABLES // check if we will break a row span by moving pFollowRow to the follow: // In this case we want to reformat the last line. const SwCellFrame* pCellFrame = static_cast(pFollowRow->GetLower()); while ( pCellFrame ) { if ( pCellFrame->GetTabBox()->getRowSpan() < 1 ) { pLastRow = static_cast(pRow->GetPrev()); break; } pCellFrame = static_cast(pCellFrame->GetNext()); } // new follow flow line for last row of master table if ( pLastRow ) pFollowRow = lcl_InsertNewFollowFlowLine( *this, *pLastRow, true ); } SwTwips nShrink = 0; //Optimization: There is no paste needed for the new Follow and the //optimized insert can be used (large numbers of rows luckily only occur in //such situations). if ( bNewFollow ) { SwFrame* pInsertBehind = pFoll->GetLastLower(); while ( pRow ) { SwFrame* pNxt = pRow->GetNext(); nShrink += aRectFnSet.GetHeight(pRow->getFrameArea()); // The footnotes do not have to be moved, this is done in the // MoveFwd of the follow table!!! pRow->RemoveFromLayout(); pRow->InsertBehind( pFoll, pInsertBehind ); pRow->InvalidateAll_(); pInsertBehind = pRow; pRow = static_cast(pNxt); } } else { SwFrame* pPasteBefore = HasFollowFlowLine() ? pFollowRow->GetNext() : pFoll->GetFirstNonHeadlineRow(); while ( pRow ) { SwFrame* pNxt = pRow->GetNext(); nShrink += aRectFnSet.GetHeight(pRow->getFrameArea()); // The footnotes have to be moved: lcl_MoveFootnotes( *this, *GetFollow(), *pRow ); pRow->RemoveFromLayout(); pRow->Paste( pFoll, pPasteBefore ); pRow->CheckDirChange(); pRow = static_cast(pNxt); } } if ( !pLastRow ) Shrink( nShrink ); else { // we rebuild the last line to assure that it will be fully formatted // we also don't shrink here, because we will be doing that in lcl_RecalcSplitLine // recalculate the split line bRet = lcl_RecalcSplitLine( *pLastRow, *pFollowRow, nRemainingSpaceForLastRow, nShrink ); // RecalcSplitLine did not work. In this case we conceal the split error: if (!bRet && !bSplitRowAllowed) { bRet = true; } // NEW TABLES // check if each cell in the row span line has a good height if ( bRet && pFollowRow->IsRowSpanLine() ) lcl_AdjustRowSpanCells( pFollowRow ); } return bRet; } void SwTabFrame::Join() { OSL_ENSURE( !HasFollowFlowLine(), "Joining follow flow line" ); SwTabFrame *pFoll = GetFollow(); if (pFoll && !pFoll->IsJoinLocked()) { SwRectFnSet aRectFnSet(this); pFoll->Cut(); //Cut out first to avoid unnecessary notifications. SwFrame *pRow = pFoll->GetFirstNonHeadlineRow(), *pNxt; SwFrame* pPrv = GetLastLower(); SwTwips nHeight = 0; //Total height of the inserted rows as return value. while ( pRow ) { pNxt = pRow->GetNext(); nHeight += aRectFnSet.GetHeight(pRow->getFrameArea()); pRow->RemoveFromLayout(); pRow->InvalidateAll_(); pRow->InsertBehind( this, pPrv ); pRow->CheckDirChange(); pPrv = pRow; pRow = pNxt; } SetFollow( pFoll->GetFollow() ); SetFollowFlowLine( pFoll->HasFollowFlowLine() ); SwFrame::DestroyFrame(pFoll); Grow( nHeight ); } } static void SwInvalidatePositions( SwFrame *pFrame, long nBottom ) { // LONG_MAX == nBottom means we have to calculate all bool bAll = LONG_MAX == nBottom; SwRectFnSet aRectFnSet(pFrame); do { pFrame->InvalidatePos_(); pFrame->InvalidateSize_(); if( pFrame->IsLayoutFrame() ) { if ( static_cast(pFrame)->Lower() ) { ::SwInvalidatePositions( static_cast(pFrame)->Lower(), nBottom); // #i26945# ::lcl_InvalidateLowerObjs( *static_cast(pFrame) ); } } else pFrame->Prepare( PrepareHint::AdjustSizeWithoutFormatting ); pFrame = pFrame->GetNext(); } while ( pFrame && ( bAll || aRectFnSet.YDiff( aRectFnSet.GetTop(pFrame->getFrameArea()), nBottom ) < 0 ) ); } void SwInvalidateAll( SwFrame *pFrame, long nBottom ) { // LONG_MAX == nBottom means we have to calculate all bool bAll = LONG_MAX == nBottom; SwRectFnSet aRectFnSet(pFrame); do { pFrame->InvalidatePos_(); pFrame->InvalidateSize_(); pFrame->InvalidatePrt_(); if( pFrame->IsLayoutFrame() ) { // NEW TABLES SwLayoutFrame* pToInvalidate = static_cast(pFrame); SwCellFrame* pThisCell = dynamic_cast(pFrame); if ( pThisCell && pThisCell->GetTabBox()->getRowSpan() < 1 ) { pToInvalidate = & const_cast(pThisCell->FindStartEndOfRowSpanCell( true )); pToInvalidate->InvalidatePos_(); pToInvalidate->InvalidateSize_(); pToInvalidate->InvalidatePrt_(); } if ( pToInvalidate->Lower() ) ::SwInvalidateAll( pToInvalidate->Lower(), nBottom); } else pFrame->Prepare(); pFrame = pFrame->GetNext(); } while ( pFrame && ( bAll || aRectFnSet.YDiff( aRectFnSet.GetTop(pFrame->getFrameArea()), nBottom ) < 0 ) ); } // #i29550# static void lcl_InvalidateAllLowersPrt( SwLayoutFrame* pLayFrame ) { pLayFrame->InvalidatePrt_(); pLayFrame->InvalidateSize_(); pLayFrame->SetCompletePaint(); SwFrame* pFrame = pLayFrame->Lower(); while ( pFrame ) { if ( pFrame->IsLayoutFrame() ) lcl_InvalidateAllLowersPrt( static_cast(pFrame) ); else { pFrame->InvalidatePrt_(); pFrame->InvalidateSize_(); pFrame->SetCompletePaint(); } pFrame = pFrame->GetNext(); } } bool SwContentFrame::CalcLowers(SwLayoutFrame & rLay, SwLayoutFrame const& rDontLeave, long nBottom, bool bSkipRowSpanCells ) { vcl::RenderContext* pRenderContext = rLay.getRootFrame()->GetCurrShell()->GetOut(); // LONG_MAX == nBottom means we have to calculate all bool bAll = LONG_MAX == nBottom; bool bRet = false; SwContentFrame *pCnt = rLay.ContainsContent(); SwRectFnSet aRectFnSet(&rLay); // FME 2007-08-30 #i81146# new loop control int nLoopControlRuns = 0; const int nLoopControlMax = 10; const SwModify* pLoopControlCond = nullptr; while (pCnt && rDontLeave.IsAnLower(pCnt)) { // #115759# - check, if a format of content frame is // possible. Thus, 'copy' conditions, found at the beginning of // , and check these. const bool bFormatPossible = !pCnt->IsJoinLocked() && ( !pCnt->IsTextFrame() || !static_cast(pCnt)->IsLocked() ) && ( pCnt->IsFollow() || !StackHack::IsLocked() ); // NEW TABLES bool bSkipContent = false; if ( bSkipRowSpanCells && pCnt->IsInTab() ) { const SwFrame* pCell = pCnt->GetUpper(); while ( pCell && !pCell->IsCellFrame() ) pCell = pCell->GetUpper(); if ( pCell && 1 != static_cast( pCell )->GetLayoutRowSpan() ) bSkipContent = true; } if ( bFormatPossible && !bSkipContent ) { bRet |= !pCnt->isFrameAreaDefinitionValid(); // #i26945# - no extra invalidation of floating // screen objects needed. // Thus, delete call of method pCnt->Calc(pRenderContext); // OD 2004-05-11 #i28701# - usage of new method <::FormatObjsAtFrame(..)> // to format the floating screen objects // #i46941# - frame has to be valid // Note: frame could be invalid after calling its format, if it's locked. OSL_ENSURE( !pCnt->IsTextFrame() || pCnt->isFrameAreaDefinitionValid() || static_cast(pCnt)->IsJoinLocked(), " - text frame invalid and not locked." ); if ( pCnt->IsTextFrame() && pCnt->isFrameAreaDefinitionValid() ) { // #i23129#, #i36347# - pass correct page frame to // the object formatter if ( !SwObjectFormatter::FormatObjsAtFrame( *pCnt, *(pCnt->FindPageFrame()) ) ) { SwTextNode const*const pTextNode( static_cast(pCnt)->GetTextNodeFirst()); if (pTextNode == pLoopControlCond) ++nLoopControlRuns; else { nLoopControlRuns = 0; pLoopControlCond = pTextNode; } if ( nLoopControlRuns < nLoopControlMax ) { // restart format with first content pCnt = rLay.ContainsContent(); continue; } #if OSL_DEBUG_LEVEL > 1 OSL_FAIL( "LoopControl in SwContentFrame::CalcLowers" ); #endif } } if (!rDontLeave.IsAnLower(pCnt)) // moved backward? { pCnt = rLay.ContainsContent(); continue; // avoid formatting new upper on different page } pCnt->GetUpper()->Calc(pRenderContext); } if( ! bAll && aRectFnSet.YDiff(aRectFnSet.GetTop(pCnt->getFrameArea()), nBottom) > 0 ) break; pCnt = pCnt->GetNextContentFrame(); } return bRet; } // #i26945# - add parameter <_bOnlyRowsAndCells> to control // that only row and cell frames are formatted. static bool lcl_InnerCalcLayout( SwFrame *pFrame, long nBottom, bool _bOnlyRowsAndCells ) { vcl::RenderContext* pRenderContext = pFrame->getRootFrame()->GetCurrShell() ? pFrame->getRootFrame()->GetCurrShell()->GetOut() : nullptr; // LONG_MAX == nBottom means we have to calculate all bool bAll = LONG_MAX == nBottom; bool bRet = false; const SwFrame* pOldUp = pFrame->GetUpper(); SwRectFnSet aRectFnSet(pFrame); do { // #i26945# - parameter <_bOnlyRowsAndCells> controls, // if only row and cell frames are formatted. if ( pFrame->IsLayoutFrame() && ( !_bOnlyRowsAndCells || pFrame->IsRowFrame() || pFrame->IsCellFrame() ) ) { // #130744# An invalid locked table frame will // not be calculated => It will not become valid => // Loop in lcl_RecalcRow(). Therefore we do not consider them for bRet. bRet |= !pFrame->isFrameAreaDefinitionValid() && ( !pFrame->IsTabFrame() || !static_cast(pFrame)->IsJoinLocked() ); pFrame->Calc(pRenderContext); if( static_cast(pFrame)->Lower() ) bRet |= lcl_InnerCalcLayout( static_cast(pFrame)->Lower(), nBottom); // NEW TABLES SwCellFrame* pThisCell = dynamic_cast(pFrame); if ( pThisCell && pThisCell->GetTabBox()->getRowSpan() < 1 ) { SwCellFrame& rToCalc = const_cast(pThisCell->FindStartEndOfRowSpanCell( true )); bRet |= !rToCalc.isFrameAreaDefinitionValid(); rToCalc.Calc(pRenderContext); if ( rToCalc.Lower() ) bRet |= lcl_InnerCalcLayout( rToCalc.Lower(), nBottom); } } pFrame = pFrame->GetNext(); } while( pFrame && ( bAll || aRectFnSet.YDiff(aRectFnSet.GetTop(pFrame->getFrameArea()), nBottom) < 0 ) && pFrame->GetUpper() == pOldUp ); return bRet; } static void lcl_RecalcRow(SwRowFrame & rRow, long const nBottom) { // FME 2007-08-30 #i81146# new loop control int nLoopControlRuns_1 = 0; sal_uInt16 nLoopControlStage_1 = 0; const int nLoopControlMax = 10; bool bCheck = true; do { // FME 2007-08-30 #i81146# new loop control int nLoopControlRuns_2 = 0; sal_uInt16 nLoopControlStage_2 = 0; while (lcl_InnerCalcLayout(&rRow, nBottom)) { if ( ++nLoopControlRuns_2 > nLoopControlMax ) { SAL_WARN_IF(nLoopControlStage_2 == 0, "sw.layout", "LoopControl_2 in lcl_RecalcRow: Stage 1!"); SAL_WARN_IF(nLoopControlStage_2 == 1, "sw.layout", "LoopControl_2 in lcl_RecalcRow: Stage 2!!"); SAL_WARN_IF(nLoopControlStage_2 >= 2, "sw.layout", "LoopControl_2 in lcl_RecalcRow: Stage 3!!!"); rRow.ValidateThisAndAllLowers( nLoopControlStage_2++ ); nLoopControlRuns_2 = 0; if( nLoopControlStage_2 > 2 ) break; } bCheck = true; } if( bCheck ) { // #115759# - force another format of the // lowers, if at least one of it was invalid. bCheck = SwContentFrame::CalcLowers(rRow, *rRow.GetUpper(), nBottom, true); // NEW TABLES // First we calculate the cells with row span of < 1, afterwards // all cells with row span of > 1: for ( int i = 0; i < 2; ++i ) { SwCellFrame* pCellFrame = static_cast(rRow.Lower()); while ( pCellFrame ) { const bool bCalc = 0 == i ? pCellFrame->GetLayoutRowSpan() < 1 : pCellFrame->GetLayoutRowSpan() > 1; if ( bCalc ) { SwCellFrame& rToRecalc = 0 == i ? const_cast(pCellFrame->FindStartEndOfRowSpanCell( true )) : *pCellFrame; bCheck |= SwContentFrame::CalcLowers(rToRecalc, rToRecalc, nBottom, false); } pCellFrame = static_cast(pCellFrame->GetNext()); } } if ( bCheck ) { if ( ++nLoopControlRuns_1 > nLoopControlMax ) { SAL_WARN_IF(nLoopControlStage_1 == 0, "sw.layout", "LoopControl_1 in lcl_RecalcRow: Stage 1!"); SAL_WARN_IF(nLoopControlStage_1 == 1, "sw.layout", "LoopControl_1 in lcl_RecalcRow: Stage 2!!"); SAL_WARN_IF(nLoopControlStage_1 >= 2, "sw.layout", "LoopControl_1 in lcl_RecalcRow: Stage 3!!!"); rRow.ValidateThisAndAllLowers( nLoopControlStage_1++ ); nLoopControlRuns_1 = 0; if( nLoopControlStage_1 > 2 ) break; } continue; } } break; } while( true ); } static void lcl_RecalcTable( SwTabFrame& rTab, SwLayoutFrame *pFirstRow, SwLayNotify &rNotify ) { if ( rTab.Lower() ) { if ( !pFirstRow ) { pFirstRow = static_cast(rTab.Lower()); rNotify.SetLowersComplete( true ); } ::SwInvalidatePositions( pFirstRow, LONG_MAX ); lcl_RecalcRow( *static_cast(pFirstRow), LONG_MAX ); } } // This is a new function to check the first condition whether // a tab frame may move backward. It replaces the formerly used // GetIndPrev(), which did not work correctly for #i5947# static bool lcl_NoPrev( const SwFrame& rFrame ) { // #i79774# // skip empty sections on investigation of direct previous frame. // use information, that at least one empty section is skipped in the following code. bool bSkippedDirectPrevEmptySection( false ); if ( rFrame.GetPrev() ) { const SwFrame* pPrev( rFrame.GetPrev() ); while ( pPrev && pPrev->IsSctFrame() && !dynamic_cast(*pPrev).GetSection() ) { pPrev = pPrev->GetPrev(); bSkippedDirectPrevEmptySection = true; } if ( pPrev ) { return false; } } if ( ( !bSkippedDirectPrevEmptySection && !rFrame.GetIndPrev() ) || ( bSkippedDirectPrevEmptySection && ( !rFrame.IsInSct() || !rFrame.GetIndPrev_() ) ) ) { return true; } // I do not have a direct prev, but I have an indirect prev. // In section frames I have to check if I'm located inside // the first column: if ( rFrame.IsInSct() ) { const SwFrame* pSct = rFrame.GetUpper(); if ( pSct && pSct->IsColBodyFrame() && pSct->GetUpper()->GetUpper()->IsSctFrame() ) { const SwFrame* pPrevCol = rFrame.GetUpper()->GetUpper()->GetPrev(); if ( pPrevCol ) // I'm not inside the first column and do not have a direct // prev. I can try to go backward. return true; } } return false; } #define KEEPTAB ( !GetFollow() && !IsFollow() ) // - helper method to find next content frame of // a table frame and format it to assure keep attribute. // method return true, if a next content frame is formatted. // Precondition: The given table frame hasn't a follow and isn't a follow. SwFrame* sw_FormatNextContentForKeep( SwTabFrame* pTabFrame ) { vcl::RenderContext* pRenderContext = pTabFrame->getRootFrame()->GetCurrShell()->GetOut(); // find next content, table or section SwFrame* pNxt = pTabFrame->FindNext(); // skip empty sections while ( pNxt && pNxt->IsSctFrame() && !static_cast(pNxt)->GetSection() ) { pNxt = pNxt->FindNext(); } // if found next frame is a section, get its first content. if ( pNxt && pNxt->IsSctFrame() ) { pNxt = static_cast(pNxt)->ContainsAny(); } // format found next frame. // if table frame is inside another table, method is // called to avoid that the superior table frame is formatted. if ( pNxt ) { if ( pTabFrame->GetUpper()->IsInTab() ) pNxt->MakeAll(pNxt->getRootFrame()->GetCurrShell()->GetOut()); else pNxt->Calc(pRenderContext); } return pNxt; } namespace { bool AreAllRowsKeepWithNext( const SwRowFrame* pFirstRowFrame, const bool bCheckParents = true ) { bool bRet = pFirstRowFrame != nullptr && pFirstRowFrame->ShouldRowKeepWithNext( bCheckParents ); while ( bRet && pFirstRowFrame->GetNext() != nullptr ) { pFirstRowFrame = dynamic_cast(pFirstRowFrame->GetNext()); bRet = pFirstRowFrame != nullptr && pFirstRowFrame->ShouldRowKeepWithNext( bCheckParents ); } return bRet; } } void SwTabFrame::MakeAll(vcl::RenderContext* pRenderContext) { if ( IsJoinLocked() || StackHack::IsLocked() || StackHack::Count() > 50 ) return; if ( HasFollow() ) { SwTabFrame* pFollowFrame = GetFollow(); OSL_ENSURE( !pFollowFrame->IsJoinLocked() || !pFollowFrame->IsRebuildLastLine(), "SwTabFrame::MakeAll for master while follow is in RebuildLastLine()" ); if ( pFollowFrame->IsJoinLocked() && pFollowFrame->IsRebuildLastLine() ) return; } PROTOCOL_ENTER( this, PROT::MakeAll, DbgAction::NONE, nullptr ) LockJoin(); //I don't want to be destroyed on the way. SwLayNotify aNotify( this ); //does the notification in the DTor // If pos is invalid, we have to call a SetInvaKeep at aNotify. // Otherwise the keep attribute would not work in front of a table. const bool bOldValidPos = isFrameAreaPositionValid(); //If my neighbour is my Follow at the same time, I'll swallow it up. // OD 09.04.2003 #108698# - join all follows, which are placed on the // same page/column. // OD 29.04.2003 #109213# - join follow, only if join for the follow // is not locked. Otherwise, join will not be performed and this loop // will be endless. while ( GetNext() && GetNext() == GetFollow() && !GetFollow()->IsJoinLocked() ) { if ( HasFollowFlowLine() ) RemoveFollowFlowLine(); Join(); } // The bRemoveFollowFlowLinePending is set if the split attribute of the // last line is set: if ( IsRemoveFollowFlowLinePending() && HasFollowFlowLine() ) { if ( RemoveFollowFlowLine() ) Join(); SetRemoveFollowFlowLinePending( false ); } if (m_bResizeHTMLTable) //Optimized interplay with grow/shrink of the content { m_bResizeHTMLTable = false; SwHTMLTableLayout *pLayout = GetTable()->GetHTMLTableLayout(); if ( pLayout ) m_bCalcLowers = pLayout->Resize( pLayout->GetBrowseWidthByTabFrame( *this ) ); } // as long as bMakePage is true, a new page can be created (exactly once) bool bMakePage = true; // bMovedBwd gets set to true when the frame flows backwards bool bMovedBwd = false; // as long as bMovedFwd is false, the Frame may flow backwards (until // it has been moved forward once) bool bMovedFwd = false; // gets set to true when the Frame is split bool bSplit = false; const bool bFootnotesInDoc = !GetFormat()->GetDoc()->GetFootnoteIdxs().empty(); const bool bFly = IsInFly(); auto pAccess = std::make_unique(SwFrame::GetCache(), this); const SwBorderAttrs *pAttrs = pAccess->Get(); const bool bLargeTable = GetTable()->GetTabLines().size() > 64; //arbitrary value, virtually guaranteed to be larger than one page. const bool bEmulateTableKeep = !bLargeTable && AreAllRowsKeepWithNext( GetFirstNonHeadlineRow(), /*bCheckParents=*/false ); // The beloved keep attribute const bool bKeep = IsKeep(pAttrs->GetAttrSet().GetKeep(), GetBreakItem(), bEmulateTableKeep); // All rows should keep together const bool bDontSplit = !IsFollow() && ( !GetFormat()->GetLayoutSplit().GetValue() ); // The number of repeated headlines const sal_uInt16 nRepeat = GetTable()->GetRowsToRepeat(); // This flag indicates that we are allowed to try to split the // table rows. bool bTryToSplit = true; // Indicates that two individual rows may keep together, based on the keep // attribute set at the first paragraph in the first cell. const bool bTableRowKeep = !bDontSplit && GetFormat()->GetDoc()->GetDocumentSettingManager().get(DocumentSettingId::TABLE_ROW_KEEP); // The Magic Move: Used for the table row keep feature. // If only the last row of the table wants to keep (implicitly by setting // keep for the first paragraph in the first cell), and this table does // not have a next, the last line will be cut. Loop prevention: Only // one try. // WHAT IS THIS??? It "magically" hides last line (paragraph) in a table, // if first is set to keep with next??? bool bLastRowHasToMoveToFollow = false; bool bLastRowMoveNoMoreTries = false; // Join follow table, if this table is not allowed to split: if ( bDontSplit ) { while ( GetFollow() && !GetFollow()->IsJoinLocked() ) { if ( HasFollowFlowLine() ) RemoveFollowFlowLine(); Join(); } } // Join follow table, if this does not have enough (repeated) lines: if ( nRepeat ) { if( GetFollow() && !GetFollow()->IsJoinLocked() && nullptr == GetFirstNonHeadlineRow() ) { if ( HasFollowFlowLine() ) RemoveFollowFlowLine(); Join(); } } // Join follow table, if last row of this table should keep: if ( bTableRowKeep && GetFollow() && !GetFollow()->IsJoinLocked() ) { const SwRowFrame* pTmpRow = static_cast(GetLastLower()); if ( pTmpRow && pTmpRow->ShouldRowKeepWithNext() ) { if ( HasFollowFlowLine() ) RemoveFollowFlowLine(); Join(); } } // a new one is moved forwards immediately if ( !getFrameArea().Top() && IsFollow() ) { SwFrame *pPre = GetPrev(); if ( pPre && pPre->IsTabFrame() && static_cast(pPre)->GetFollow() == this) { // don't make the effort to move fwd if its known // conditions that are known not to work if (IsInFootnote() && ForbiddenForFootnoteCntFwd()) bMakePage = false; else if (!MoveFwd(bMakePage, false)) bMakePage = false; bMovedFwd = true; } } int nUnSplitted = 5; // Just another loop control :-( int nThrowAwayValidLayoutLimit = 5; // And another one :-( SwRectFnSet aRectFnSet(this); while ( !isFrameAreaPositionValid() || !isFrameAreaSizeValid() || !isFramePrintAreaValid() ) { const bool bMoveable = IsMoveable(); if (bMoveable && !(bMovedFwd && bEmulateTableKeep) ) if ( CheckMoveFwd( bMakePage, bKeep && KEEPTAB, bEmulateTableKeep ) ) { bMovedFwd = true; m_bCalcLowers = true; // #i99267# // reset after forward move to assure that follows // can be joined, if further space is available. bSplit = false; } Point aOldPos( aRectFnSet.GetPos(getFrameArea()) ); MakePos(); if ( aOldPos != aRectFnSet.GetPos(getFrameArea()) ) { if ( aOldPos.Y() != aRectFnSet.GetTop(getFrameArea()) ) { SwHTMLTableLayout *pLayout = GetTable()->GetHTMLTableLayout(); if( pLayout ) { pAccess.reset(); m_bCalcLowers |= pLayout->Resize( pLayout->GetBrowseWidthByTabFrame( *this ) ); pAccess = std::make_unique(SwFrame::GetCache(), this); pAttrs = pAccess->Get(); } setFramePrintAreaValid(false); aNotify.SetLowersComplete( false ); } SwFrame *pPre; if ( bKeep || (nullptr != (pPre = FindPrev()) && pPre->GetAttrSet()->GetKeep().GetValue()) ) { m_bCalcLowers = true; } } //We need to know the height of the first row, because the master needs //to be invalidated if it shrinks and then absorb the row if possible. long n1StLineHeight = 0; if ( IsFollow() ) { SwFrame* pFrame = GetFirstNonHeadlineRow(); if ( pFrame ) n1StLineHeight = aRectFnSet.GetHeight(pFrame->getFrameArea()); } if ( !isFrameAreaSizeValid() || !isFramePrintAreaValid() ) { const long nOldPrtWidth = aRectFnSet.GetWidth(getFramePrintArea()); const long nOldFrameWidth = aRectFnSet.GetWidth(getFrameArea()); const Point aOldPrtPos = aRectFnSet.GetPos(getFramePrintArea()); Format( getRootFrame()->GetCurrShell()->GetOut(), pAttrs ); SwHTMLTableLayout *pLayout = GetTable()->GetHTMLTableLayout(); if ( pLayout && (aRectFnSet.GetWidth(getFramePrintArea()) != nOldPrtWidth || aRectFnSet.GetWidth(getFrameArea()) != nOldFrameWidth) ) { pAccess.reset(); m_bCalcLowers |= pLayout->Resize( pLayout->GetBrowseWidthByTabFrame( *this ) ); pAccess = std::make_unique(SwFrame::GetCache(), this); pAttrs = pAccess->Get(); } if ( aOldPrtPos != aRectFnSet.GetPos(getFramePrintArea()) ) aNotify.SetLowersComplete( false ); } // If this is the first one in a chain, check if this can flow // backwards (if this is movable at all). // To prevent oscillations/loops, check that this has not just // flowed forwards. if ( !bMovedFwd && (bMoveable || bFly) && lcl_NoPrev( *this ) ) { // for Follows notify Master. // only move Follow if it has to skip empty pages. if ( IsFollow() ) { // Only if the height of the first line got smaller. SwFrame *pFrame = GetFirstNonHeadlineRow(); if( pFrame && n1StLineHeight >aRectFnSet.GetHeight(pFrame->getFrameArea()) ) { SwTabFrame *pMaster = FindMaster(); bool bDummy; if ( ShouldBwdMoved( pMaster->GetUpper(), bDummy ) ) pMaster->InvalidatePos(); } } SwFootnoteBossFrame *pOldBoss = bFootnotesInDoc ? FindFootnoteBossFrame( true ) : nullptr; bool bReformat; if ( MoveBwd( bReformat ) ) { aRectFnSet.Refresh(this); bMovedBwd = true; aNotify.SetLowersComplete( false ); if ( bFootnotesInDoc ) MoveLowerFootnotes( nullptr, pOldBoss, nullptr, true ); if ( bReformat || bKeep ) { long nOldTop = aRectFnSet.GetTop(getFrameArea()); MakePos(); if( nOldTop != aRectFnSet.GetTop(getFrameArea()) ) { SwHTMLTableLayout *pHTMLLayout = GetTable()->GetHTMLTableLayout(); if( pHTMLLayout ) { pAccess.reset(); m_bCalcLowers |= pHTMLLayout->Resize( pHTMLLayout->GetBrowseWidthByTabFrame( *this ) ); pAccess = std::make_unique(SwFrame::GetCache(), this); pAttrs = pAccess->Get(); } setFramePrintAreaValid(false); Format( getRootFrame()->GetCurrShell()->GetOut(), pAttrs ); } lcl_RecalcTable( *this, nullptr, aNotify ); m_bLowersFormatted = true; if ( bKeep && KEEPTAB ) { // Consider case that table is inside another table, // because it has to be avoided, that superior table // is formatted. // Thus, find next content, table or section // and, if a section is found, get its first // content. if ( nullptr != sw_FormatNextContentForKeep( this ) && !GetNext() ) { setFrameAreaPositionValid(false); } } } } } //Again an invalid value? - do it again... if ( !isFrameAreaPositionValid() || !isFrameAreaSizeValid() || !isFramePrintAreaValid() ) continue; // check, if calculation of table frame is ready. // Local variable // Introduce local variable and init it with the distance from the // table frame bottom to the bottom of the upper printing area. // Note: negative values denotes the situation that table frame doesn't fit in its upper. SwTwips nDistanceToUpperPrtBottom = aRectFnSet.BottomDist(getFrameArea(), aRectFnSet.GetPrtBottom(*GetUpper())); /// In online layout try to grow upper of table frame, if table frame doesn't fit in its upper. const SwViewShell *pSh = getRootFrame()->GetCurrShell(); const bool bBrowseMode = pSh && pSh->GetViewOptions()->getBrowseMode(); if ( nDistanceToUpperPrtBottom < 0 && bBrowseMode ) { if ( GetUpper()->Grow( -nDistanceToUpperPrtBottom ) ) { // upper is grown --> recalculate nDistanceToUpperPrtBottom = aRectFnSet.BottomDist(getFrameArea(), aRectFnSet.GetPrtBottom(*GetUpper())); } } // If there is still some space left in the upper, we check if we // can join some rows of the follow. // Setting bLastRowHasToMoveToFollow to true means we want to force // the table to be split! Only skip this if condition once. if( nDistanceToUpperPrtBottom >= 0 && !bLastRowHasToMoveToFollow ) { // If there is space left in the upper printing area, join as for trial // at least one further row of an existing follow. if ( !bSplit && GetFollow() ) { bool bDummy; if ( GetFollow()->ShouldBwdMoved( GetUpper(), bDummy ) ) { SwFrame *pTmp = GetUpper(); SwTwips nDeadLine = aRectFnSet.GetPrtBottom(*pTmp); if ( bBrowseMode ) nDeadLine += pTmp->Grow( LONG_MAX, true ); bool bFits = aRectFnSet.BottomDist(getFrameArea(), nDeadLine) > 0; if (!bFits && aRectFnSet.GetHeight(GetFollow()->getFrameArea()) == 0) // The follow should move backwards, so allow the case // when the upper has no space, but the follow is // empty. bFits = aRectFnSet.BottomDist(getFrameArea(), nDeadLine) >= 0; if (bFits) { // First, we remove an existing follow flow line. if ( HasFollowFlowLine() ) { SwFrame* pLastLine = GetLastLower(); RemoveFollowFlowLine(); // invalidate and rebuild last row if ( pLastLine ) { ::SwInvalidateAll( pLastLine, LONG_MAX ); SetRebuildLastLine( true ); lcl_RecalcRow(*static_cast(pLastLine), LONG_MAX); SetRebuildLastLine( false ); } SwFrame* pRow = GetFollow()->GetFirstNonHeadlineRow(); if ( !pRow || !pRow->GetNext() ) // The follow became empty and hence useless Join(); continue; } // If there is no follow flow line, we move the first // row in the follow table to the master table. SwRowFrame *pRow = GetFollow()->GetFirstNonHeadlineRow(); // The follow became empty and hence useless if ( !pRow ) { Join(); continue; } const SwTwips nOld = aRectFnSet.GetHeight(getFrameArea()); long nRowsToMove = lcl_GetMaximumLayoutRowSpan( *pRow ); SwFrame* pRowToMove = pRow; while ( pRowToMove && nRowsToMove-- > 0 ) { const bool bMoveFootnotes = bFootnotesInDoc && !GetFollow()->IsJoinLocked(); SwFootnoteBossFrame *pOldBoss = nullptr; if ( bMoveFootnotes ) pOldBoss = pRowToMove->FindFootnoteBossFrame( true ); SwFrame* pNextRow = pRowToMove->GetNext(); if ( !pNextRow ) { // The follow became empty and hence useless Join(); } else { pRowToMove->Cut(); pRowToMove->Paste( this ); } // Move the footnotes! if ( bMoveFootnotes ) if ( static_cast(pRowToMove)->MoveLowerFootnotes( nullptr, pOldBoss, FindFootnoteBossFrame( true ), true ) ) GetUpper()->Calc(pRenderContext); pRowToMove = pNextRow; } if ( nOld != aRectFnSet.GetHeight(getFrameArea()) ) lcl_RecalcTable( *this, static_cast(pRow), aNotify ); continue; } } } else if ( KEEPTAB ) { bool bFormat = false; if ( bKeep ) bFormat = true; else if ( bTableRowKeep && !bLastRowMoveNoMoreTries ) { // We only want to give the last row one chance to move // to the follow table. Set the flag as early as possible: bLastRowMoveNoMoreTries = true; // The last line of the table has to be cut off if: // 1. The table does not want to keep with its next // 2. The compatibility option is set and the table is allowed to split // 3. We did not already cut off the last row // 4. There is not break after attribute set at the table // 5. There is no break before attribute set behind the table // 6. There is no section change behind the table (see IsKeep) // 7. The last table row wants to keep with its next. const SwRowFrame* pLastRow = static_cast(GetLastLower()); if (pLastRow && IsKeep(pAttrs->GetAttrSet().GetKeep(), GetBreakItem(), true) && pLastRow->ShouldRowKeepWithNext()) { bFormat = true; } } if ( bFormat ) { pAccess.reset(); // Consider case that table is inside another table, because // it has to be avoided, that superior table is formatted. // Thus, find next content, table or section and, if a section // is found, get its first content. const SwFrame* pTmpNxt = sw_FormatNextContentForKeep( this ); pAccess = std::make_unique(SwFrame::GetCache(), this); pAttrs = pAccess->Get(); // The last row wants to keep with the frame behind the table. // Check if the next frame is on a different page and valid. // In this case we do a magic trick: if ( !bKeep && !GetNext() && pTmpNxt && pTmpNxt->isFrameAreaDefinitionValid() ) { setFrameAreaPositionValid(false); bLastRowHasToMoveToFollow = true; } } } if ( isFrameAreaDefinitionValid() ) { if (m_bCalcLowers) { lcl_RecalcTable( *this, nullptr, aNotify ); m_bLowersFormatted = true; m_bCalcLowers = false; } else if (m_bONECalcLowers) { lcl_RecalcRow(*static_cast(Lower()), LONG_MAX); m_bONECalcLowers = false; } } continue; } // I don't fit in the upper Frame anymore, therefore it's the // right moment to do some preferably constructive changes. // If I'm NOT allowed to leave the upper Frame, I've got a problem. // Following Arthur Dent, we do the only thing that you can do with // an unsolvable problem: We ignore it with all our power. if ( !bMoveable ) { if (m_bCalcLowers && isFrameAreaDefinitionValid()) { lcl_RecalcTable( *this, nullptr, aNotify ); m_bLowersFormatted = true; m_bCalcLowers = false; } else if (m_bONECalcLowers) { lcl_RecalcRow(*static_cast(Lower()), LONG_MAX); m_bONECalcLowers = false; } // It does not make sense to cut off the last line if we are // not moveable: bLastRowHasToMoveToFollow = false; continue; } if (m_bCalcLowers && isFrameAreaDefinitionValid()) { lcl_RecalcTable( *this, nullptr, aNotify ); m_bLowersFormatted = true; m_bCalcLowers = false; if( !isFrameAreaDefinitionValid() ) continue; } // First try to split the table. Condition: // 1. We have at least one non headline row // 2. If this row wants to keep, we need an additional row // 3. The table is allowed to split or we do not have a pIndPrev: SwFrame* pIndPrev = GetIndPrev(); const SwRowFrame* pFirstNonHeadlineRow = GetFirstNonHeadlineRow(); // #i120016# if this row wants to keep, allow split in case that all rows want to keep with next, // the table can not move forward as it is the first one and a split is in general allowed. const bool bAllowSplitOfRow = ( bTableRowKeep && AreAllRowsKeepWithNext( pFirstNonHeadlineRow ) ) && !pIndPrev && !bDontSplit; const bool bEmulateTableKeepFwdMoveAllowed = IsKeepFwdMoveAllowed(bEmulateTableKeep); if ( pFirstNonHeadlineRow && nUnSplitted > 0 && ( !bEmulateTableKeepFwdMoveAllowed || ( ( !bTableRowKeep || pFirstNonHeadlineRow->GetNext() || !pFirstNonHeadlineRow->ShouldRowKeepWithNext() || bAllowSplitOfRow ) && ( !bDontSplit || !pIndPrev ) ) ) ) { // #i29438# // Special DoNotSplit cases: // We better avoid splitting if the table keeps with next paragraph and can move fwd still. // We better avoid splitting of a row frame if we are inside a columned // section which has a height of 0, because this is not growable and thus // all kinds of unexpected things could happen. if ( !bEmulateTableKeepFwdMoveAllowed || ( IsInSct() && FindSctFrame()->Lower()->IsColumnFrame() && 0 == aRectFnSet.GetHeight(GetUpper()->getFrameArea()) ) ) { bTryToSplit = false; } // 1. Try: bTryToSplit = true => Try to split the row. // 2. Try: bTryToSplit = false => Split the table between the rows. if ( pFirstNonHeadlineRow->GetNext() || bTryToSplit ) { SwTwips nDeadLine = aRectFnSet.GetPrtBottom(*GetUpper()); if( IsInSct() || GetUpper()->IsInTab() ) // TABLE IN TABLE) nDeadLine = aRectFnSet.YInc( nDeadLine, GetUpper()->Grow( LONG_MAX, true ) ); { SetInRecalcLowerRow( true ); ::lcl_RecalcRow(*static_cast(Lower()), nDeadLine); SetInRecalcLowerRow( false ); } m_bLowersFormatted = true; aNotify.SetLowersComplete( true ); // One more check if it's really necessary to split the table. // 1. The table either has to exceed the deadline or // 2. We explicitly want to cut off the last row. if( aRectFnSet.BottomDist( getFrameArea(), nDeadLine ) > 0 && !bLastRowHasToMoveToFollow ) { continue; } // Set to false again as early as possible. bLastRowHasToMoveToFollow = false; // #i52781# // YaSC - Yet another special case: // If our upper is inside a table cell which is not allowed // to split, we do not try to split: if ( GetUpper()->IsInTab() ) { const SwFrame* pTmpRow = GetUpper(); while ( pTmpRow && !pTmpRow->IsRowFrame() ) pTmpRow = pTmpRow->GetUpper(); if ( pTmpRow && !static_cast(pTmpRow)->IsRowSplitAllowed() ) continue; } sal_uInt16 nMinNumOfLines = nRepeat; if ( bTableRowKeep ) { const SwRowFrame* pTmpRow = GetFirstNonHeadlineRow(); while ( pTmpRow && pTmpRow->ShouldRowKeepWithNext() ) { ++nMinNumOfLines; pTmpRow = static_cast(pTmpRow->GetNext()); } } if ( !bTryToSplit ) ++nMinNumOfLines; const SwTwips nBreakLine = aRectFnSet.YInc( aRectFnSet.GetTop(getFrameArea()), aRectFnSet.GetTopMargin(*this) + lcl_GetHeightOfRows( GetLower(), nMinNumOfLines ) ); // Some more checks if we want to call the split algorithm or not: // The repeating lines / keeping lines still fit into the upper or // if we do not have an (in)direct Prev, we split anyway. if( aRectFnSet.YDiff(nDeadLine, nBreakLine) >=0 || !pIndPrev || !bEmulateTableKeepFwdMoveAllowed ) { aNotify.SetLowersComplete( false ); bSplit = true; // An existing follow flow line has to be removed. if ( HasFollowFlowLine() ) { if (!nThrowAwayValidLayoutLimit) continue; const bool bInitialLoopEndCondition(isFrameAreaDefinitionValid()); RemoveFollowFlowLine(); const bool bFinalLoopEndCondition(isFrameAreaDefinitionValid()); if (bInitialLoopEndCondition && !bFinalLoopEndCondition) { --nThrowAwayValidLayoutLimit; } } const bool bSplitError = !Split( nDeadLine, bTryToSplit, ( bTableRowKeep && !(bAllowSplitOfRow || !bEmulateTableKeepFwdMoveAllowed) ) ); if (!bTryToSplit && !bSplitError) { --nUnSplitted; } // #i29771# Two tries to split the table // If an error occurred during splitting. We start a second // try, this time without splitting of table rows. if ( bSplitError && HasFollowFlowLine() ) RemoveFollowFlowLine(); // If splitting the table was successful or not, // we do not want to have 'empty' follow tables. if ( GetFollow() && !GetFollow()->GetFirstNonHeadlineRow() ) Join(); // We want to restore the situation before the failed // split operation as good as possible. Therefore we // do some more calculations. Note: Restricting this // to nDeadLine may not be enough. if ( bSplitError && bTryToSplit ) // no restart if we did not try to split: i72847, i79426 { lcl_RecalcRow(*static_cast(Lower()), LONG_MAX); setFrameAreaPositionValid(false); bTryToSplit = false; continue; } bTryToSplit = !bSplitError; //To avoid oscillations the Follow must become valid now if ( GetFollow() ) { // #i80924# // After a successful split assure that the first row // is invalid. When graphics are present, this isn't hold. // Note: defect i80924 could also be fixed, if it is // assured, that is only // set, if all lower are valid *and* are correct laid out. if ( !bSplitError && GetFollow()->GetLower() ) { GetFollow()->GetLower()->InvalidatePos(); } SwRectFnSet fnRectX(GetFollow()); static sal_uInt8 nStack = 0; if ( !StackHack::IsLocked() && nStack < 4 ) { ++nStack; StackHack aHack; pAccess.reset(); GetFollow()->MakeAll(pRenderContext); pAccess = std::make_unique(SwFrame::GetCache(), this); pAttrs = pAccess->Get(); GetFollow()->SetLowersFormatted(false); // #i43913# - lock follow table // to avoid its formatting during the format of // its content. const bool bOldJoinLock = GetFollow()->IsJoinLocked(); GetFollow()->LockJoin(); ::lcl_RecalcRow(*static_cast(GetFollow()->Lower()), fnRectX.GetBottom(GetFollow()->GetUpper()->getFrameArea()) ); // #i43913# // #i63632# Do not unlock the // follow if it wasn't locked before. if ( !bOldJoinLock ) GetFollow()->UnlockJoin(); if ( !GetFollow()->GetFollow() ) { SwFrame* pNxt = static_cast(GetFollow())->FindNext(); if ( pNxt ) { // #i18103# - no formatting of found next // frame, if it's a follow section of the // 'ColLocked' section, the follow table is // in. bool bCalcNxt = true; if ( GetFollow()->IsInSct() && pNxt->IsSctFrame() ) { SwSectionFrame* pSct = GetFollow()->FindSctFrame(); if ( pSct->IsColLocked() && pSct->GetFollow() == pNxt ) { bCalcNxt = false; } } if ( bCalcNxt ) { // tdf#119109 follow was just formatted, // don't do it again now FlowFrameJoinLockGuard g(GetFollow()); pNxt->Calc(pRenderContext); } } } --nStack; } else if ( GetFollow() == GetNext() ) GetFollow()->MoveFwd( true, false ); } continue; } } } // Set to false again as early as possible. bLastRowHasToMoveToFollow = false; if( IsInSct() && bMovedFwd && bMakePage && GetUpper()->IsColBodyFrame() && GetUpper()->GetUpper()->GetUpper()->IsSctFrame() && ( GetUpper()->GetUpper()->GetPrev() || GetIndPrev() ) && static_cast(GetUpper()->GetUpper()->GetUpper())->MoveAllowed(this) ) { bMovedFwd = false; } // #i29771# Reset bTryToSplit flag on change of upper const SwFrame* pOldUpper = GetUpper(); //Let's see if we find some place anywhere... if (!bMovedFwd) { // don't make the effort to move fwd if its known // conditions that are known not to work if (IsInFootnote() && ForbiddenForFootnoteCntFwd()) bMakePage = false; else if (!MoveFwd(bMakePage, false)) bMakePage = false; } // #i29771# Reset bSplitError flag on change of upper if ( GetUpper() != pOldUpper ) { bTryToSplit = true; nUnSplitted = 5; } aRectFnSet.Refresh(this); m_bCalcLowers = true; bMovedFwd = true; aNotify.SetLowersComplete( false ); if ( IsFollow() ) { // To avoid oscillations, master should not remain invalid SwTabFrame *pTab = FindMaster(); if ( pTab->GetUpper() ) pTab->GetUpper()->Calc(pRenderContext); pTab->Calc(pRenderContext); pTab->SetLowersFormatted( false ); } //If my neighbour is my Follow at the same time, I'll swallow it up. if ( ( GetNext() && GetNext() == GetFollow() ) || !GetLower() ) { if ( HasFollowFlowLine() ) RemoveFollowFlowLine(); if ( GetFollow() ) Join(); } if ( bMovedBwd && GetUpper() ) { //During flowing back the upper was animated to do a full repaint, //we can now skip this after the whole flowing back and forth. GetUpper()->ResetCompletePaint(); } if (m_bCalcLowers && isFrameAreaDefinitionValid()) { // #i44910# - format of lower frames unnecessary // and can cause layout loops, if table doesn't fit and isn't // allowed to split. SwTwips nDistToUpperPrtBottom = aRectFnSet.BottomDist( getFrameArea(), aRectFnSet.GetPrtBottom(*GetUpper())); if ( nDistToUpperPrtBottom >= 0 || bTryToSplit ) { lcl_RecalcTable( *this, nullptr, aNotify ); m_bLowersFormatted = true; m_bCalcLowers = false; if (!isFramePrintAreaValid()) m_pTable->SetRowsToRepeat(1); } #if OSL_DEBUG_LEVEL > 0 else { OSL_FAIL( "debug assertion: - format of table lowers suppressed by fix i44910" ); } #endif } } //while ( !isFrameAreaPositionValid() || !isFrameAreaSizeValid() || !isFramePrintAreaValid() ) //If my direct predecessor is my master now, it can destroy me during the //next best opportunity. if ( IsFollow() ) { SwFrame *pPre = GetPrev(); if ( pPre && pPre->IsTabFrame() && static_cast(pPre)->GetFollow() == this) pPre->InvalidatePos(); } m_bCalcLowers = m_bONECalcLowers = false; pAccess.reset(); UnlockJoin(); if ( bMovedFwd || bMovedBwd || !bOldValidPos ) aNotify.SetInvaKeep(); } /// Calculate the offsets arising because of FlyFrames bool SwTabFrame::CalcFlyOffsets( SwTwips& rUpper, long& rLeftOffset, long& rRightOffset ) const { bool bInvalidatePrtArea = false; const SwPageFrame *pPage = FindPageFrame(); const SwFlyFrame* pMyFly = FindFlyFrame(); // --> #108724# Page header/footer content doesn't have to wrap around // floating screen objects const IDocumentSettingAccess& rIDSA = GetFormat()->getIDocumentSettingAccess(); const bool bWrapAllowed = rIDSA.get(DocumentSettingId::USE_FORMER_TEXT_WRAPPING) || ( !IsInFootnote() && nullptr == FindFooterOrHeader() ); if ( pPage->GetSortedObjs() && bWrapAllowed ) { SwRectFnSet aRectFnSet(this); const bool bConsiderWrapOnObjPos = rIDSA.get(DocumentSettingId::CONSIDER_WRAP_ON_OBJECT_POSITION); long nPrtPos = aRectFnSet.GetTop(getFrameArea()); nPrtPos = aRectFnSet.YInc( nPrtPos, rUpper ); SwRect aRect( getFrameArea() ); long nYDiff = aRectFnSet.YDiff( aRectFnSet.GetTop(getFramePrintArea()), rUpper ); if( nYDiff > 0 ) aRectFnSet.AddBottom( aRect, -nYDiff ); bool bAddVerticalFlyOffsets = rIDSA.get(DocumentSettingId::ADD_VERTICAL_FLY_OFFSETS); for ( size_t i = 0; i < pPage->GetSortedObjs()->size(); ++i ) { SwAnchoredObject* pAnchoredObj = (*pPage->GetSortedObjs())[i]; if ( dynamic_cast< const SwFlyFrame *>( pAnchoredObj ) != nullptr ) { SwFlyFrame *pFly = static_cast(pAnchoredObj); const SwRect aFlyRect = pFly->GetObjRectWithSpaces(); // #i26945# - correction of conditions, // if Writer fly frame has to be considered: // - no need to check, if top of Writer fly frame differs // from FAR_AWAY, because it's also checked, if the Writer // fly frame rectangle overlaps with // - no check, if bottom of anchor frame is prior the top of // the table, because Writer fly frames can be negative positioned. // - correct check, if the Writer fly frame is a lower of the // table, because table lines/rows can split and an at-character // anchored Writer fly frame could be positioned in the follow // flow line. // - add condition, that an existing anchor character text frame // has to be on the same page as the table. // E.g., it could happen, that the fly frame is still registered // at the page frame, the table is on, but it's anchor character // text frame has already changed its page. const SwTextFrame* pAnchorCharFrame = pFly->FindAnchorCharFrame(); bool bConsiderFly = // #i46807# - do not consider invalid // Writer fly frames. (pFly->isFrameAreaDefinitionValid() || bAddVerticalFlyOffsets) && // fly anchored at character pFly->IsFlyAtContentFrame() && // fly overlaps with corresponding table rectangle aFlyRect.IsOver( aRect ) && // fly isn't lower of table and // anchor character frame of fly isn't lower of table ( !IsAnLower( pFly ) && ( !pAnchorCharFrame || !IsAnLower( pAnchorCharFrame ) ) ) && // table isn't lower of fly !pFly->IsAnLower( this ) && // fly is lower of fly, the table is in // #123274# - correction // assure that fly isn't a lower of a fly, the table isn't in. // E.g., a table in the body doesn't wrap around a graphic, // which is inside a frame. ( ( !pMyFly || pMyFly->IsAnLower( pFly ) ) && pMyFly == pFly->GetAnchorFrameContainingAnchPos()->FindFlyFrame() ) && // anchor frame not on following page pPage->GetPhyPageNum() >= pFly->GetAnchorFrame()->FindPageFrame()->GetPhyPageNum() && // anchor character text frame on same page ( !pAnchorCharFrame || pAnchorCharFrame->FindPageFrame()->GetPhyPageNum() == pPage->GetPhyPageNum() ); if ( bConsiderFly ) { const SwFrame* pFlyHeaderFooterFrame = pFly->GetAnchorFrame()->FindFooterOrHeader(); const SwFrame* pThisHeaderFooterFrame = FindFooterOrHeader(); if ( pFlyHeaderFooterFrame != pThisHeaderFooterFrame && // #148493# If bConsiderWrapOnObjPos is set, // we want to consider the fly if it is located in the header and // the table is located in the body: ( !bConsiderWrapOnObjPos || nullptr != pThisHeaderFooterFrame || !pFlyHeaderFooterFrame->IsHeaderFrame() ) ) bConsiderFly = false; } if ( bConsiderFly ) { const SwFormatSurround &rSur = pFly->GetFormat()->GetSurround(); const SwFormatHoriOrient &rHori= pFly->GetFormat()->GetHoriOrient(); bool bShiftDown = css::text::WrapTextMode_NONE == rSur.GetSurround(); if (!bShiftDown && bAddVerticalFlyOffsets) { if (rSur.GetSurround() == text::WrapTextMode_PARALLEL && rHori.GetHoriOrient() == text::HoriOrientation::NONE) { // We know that wrapping was requested and the table frame overlaps with // the fly frame. Check if the print area overlaps with the fly frame as // well (in case the table does not use all the available width). basegfx::B1DRange aTabRange( aRectFnSet.GetLeft(aRect) + aRectFnSet.GetLeft(getFramePrintArea()), aRectFnSet.GetLeft(aRect) + aRectFnSet.GetLeft(getFramePrintArea()) + aRectFnSet.GetWidth(getFramePrintArea())); basegfx::B1DRange aFlyRange(aRectFnSet.GetLeft(aFlyRect), aRectFnSet.GetRight(aFlyRect)); // If it does, shift the table down. Do this only in the compat case, // normally an SwFlyPortion is created instead that increases the height // of the first table row. bShiftDown = aTabRange.overlaps(aFlyRange); } } if (bShiftDown) { long nBottom = aRectFnSet.GetBottom(aFlyRect); if( aRectFnSet.YDiff( nPrtPos, nBottom ) < 0 ) nPrtPos = nBottom; bInvalidatePrtArea = true; } if ( (css::text::WrapTextMode_RIGHT == rSur.GetSurround() || css::text::WrapTextMode_PARALLEL == rSur.GetSurround())&& text::HoriOrientation::LEFT == rHori.GetHoriOrient() ) { const long nWidth = aRectFnSet.XDiff( aRectFnSet.GetRight(aFlyRect), aRectFnSet.GetLeft(pFly->GetAnchorFrame()->getFrameArea()) ); rLeftOffset = std::max( rLeftOffset, nWidth ); bInvalidatePrtArea = true; } if ( (css::text::WrapTextMode_LEFT == rSur.GetSurround() || css::text::WrapTextMode_PARALLEL == rSur.GetSurround())&& text::HoriOrientation::RIGHT == rHori.GetHoriOrient() ) { const long nWidth = aRectFnSet.XDiff( aRectFnSet.GetRight(pFly->GetAnchorFrame()->getFrameArea()), aRectFnSet.GetLeft(aFlyRect) ); rRightOffset = std::max( rRightOffset, nWidth ); bInvalidatePrtArea = true; } } } } rUpper = aRectFnSet.YDiff( nPrtPos, aRectFnSet.GetTop(getFrameArea()) ); } return bInvalidatePrtArea; } /// "Formats" the frame; Frame and PrtArea. /// The fixed size is not adjusted here. void SwTabFrame::Format( vcl::RenderContext* /*pRenderContext*/, const SwBorderAttrs *pAttrs ) { OSL_ENSURE( pAttrs, "TabFrame::Format, pAttrs is 0." ); SwRectFnSet aRectFnSet(this); if ( !isFrameAreaSizeValid() ) { long nDiff = aRectFnSet.GetWidth(GetUpper()->getFramePrintArea()) - aRectFnSet.GetWidth(getFrameArea()); if( nDiff ) { SwFrameAreaDefinition::FrameAreaWriteAccess aFrm(*this); aRectFnSet.AddRight( aFrm, nDiff ); } } //VarSize is always the height. //For the upper/lower margins the same rules apply as for ContentFrames (see //MakePrtArea() of those). SwTwips nUpper = CalcUpperSpace( pAttrs ); // We want to dodge the flys. Two possibilities: // 1. There are flys with SurroundNone, dodge them completely // 2. There are flys which only wrap on the right or the left side and // those are right or left aligned, those set the minimum for the margins long nTmpRight = -1000000, nLeftOffset = 0; if( CalcFlyOffsets( nUpper, nLeftOffset, nTmpRight ) ) { setFramePrintAreaValid(false); } long nRightOffset = std::max( 0L, nTmpRight ); SwTwips nLower = pAttrs->CalcBottomLine(); // #i29550# if ( IsCollapsingBorders() ) nLower += GetBottomLineSize(); if ( !isFramePrintAreaValid() ) { setFramePrintAreaValid(true); // The width of the PrintArea is given by the FrameFormat, the margins // have to be set accordingly. // Minimum margins are determined depending on borders and shadows. // The margins are set so that the PrintArea is aligned into the // Frame according to the adjustment. // If the adjustment is 0, the margins are set according to the border // attributes. const SwTwips nOldHeight = aRectFnSet.GetHeight(getFramePrintArea()); const SwTwips nMax = aRectFnSet.GetWidth(getFrameArea()); // OD 14.03.2003 #i9040# - adjust variable names. const SwTwips nLeftLine = pAttrs->CalcLeftLine(); const SwTwips nRightLine = pAttrs->CalcRightLine(); // The width possibly is a percentage value. If the table is inside // something else, the value refers to the environment. If it's in the // body then in the BrowseView the value refers to the screen width. const SwFormatFrameSize &rSz = GetFormat()->GetFrameSize(); // OD 14.03.2003 #i9040# - adjust variable name. const SwTwips nWishedTableWidth = CalcRel( rSz ); bool bCheckBrowseWidth = false; // OD 14.03.2003 #i9040# - insert new variables for left/right spacing. SwTwips nLeftSpacing = 0; SwTwips nRightSpacing = 0; switch ( GetFormat()->GetHoriOrient().GetHoriOrient() ) { case text::HoriOrientation::LEFT: { // left indent: nLeftSpacing = nLeftLine + nLeftOffset; // OD 06.03.2003 #i9040# - correct calculation of right indent: // - Consider right indent given by right line attributes. // - Consider negative right indent. // wished right indent determined by wished table width and // left offset given by surround fly frames on the left: const SwTwips nWishRight = nMax - nWishedTableWidth - nLeftOffset; if ( nRightOffset > 0 ) { // surrounding fly frames on the right // -> right indent is maximum of given right offset // and wished right offset. nRightSpacing = nRightLine + std::max( nRightOffset, nWishRight ); } else { // no surrounding fly frames on the right // If intrinsic right indent (intrinsic means not considering // determined left indent) is negative, // then hold this intrinsic indent, // otherwise non negative wished right indent is hold. nRightSpacing = nRightLine + ( ( (nWishRight+nLeftOffset) < 0 ) ? (nWishRight+nLeftOffset) : std::max( 0L, nWishRight ) ); } } break; case text::HoriOrientation::RIGHT: { // right indent: nRightSpacing = nRightLine + nRightOffset; // OD 06.03.2003 #i9040# - correct calculation of left indent: // - Consider left indent given by left line attributes. // - Consider negative left indent. // wished left indent determined by wished table width and // right offset given by surrounding fly frames on the right: const SwTwips nWishLeft = nMax - nWishedTableWidth - nRightOffset; if ( nLeftOffset > 0 ) { // surrounding fly frames on the left // -> right indent is maximum of given left offset // and wished left offset. nLeftSpacing = nLeftLine + std::max( nLeftOffset, nWishLeft ); } else { // no surrounding fly frames on the left // If intrinsic left indent (intrinsic = not considering // determined right indent) is negative, // then hold this intrinsic indent, // otherwise non negative wished left indent is hold. nLeftSpacing = nLeftLine + ( ( (nWishLeft+nRightOffset) < 0 ) ? (nWishLeft+nRightOffset) : std::max( 0L, nWishLeft ) ); } } break; case text::HoriOrientation::CENTER: { // OD 07.03.2003 #i9040# - consider left/right line attribute. // OD 10.03.2003 #i9040# - const SwTwips nCenterSpacing = ( nMax - nWishedTableWidth ) / 2; nLeftSpacing = nLeftLine + ( (nLeftOffset > 0) ? std::max( nCenterSpacing, nLeftOffset ) : nCenterSpacing ); nRightSpacing = nRightLine + ( (nRightOffset > 0) ? std::max( nCenterSpacing, nRightOffset ) : nCenterSpacing ); } break; case text::HoriOrientation::FULL: //This things grows over the whole width. //Only the free space needed for the border is taken into //account. The attribute values of LRSpace are ignored //intentionally. bCheckBrowseWidth = true; nLeftSpacing = nLeftLine + nLeftOffset; nRightSpacing = nRightLine + nRightOffset; break; case text::HoriOrientation::NONE: { // The margins are defined by the LRSpace attribute. nLeftSpacing = pAttrs->CalcLeft( this ); if( nLeftOffset ) { // OD 07.03.2003 #i9040# - surround fly frames only, if // they overlap with the table. // Thus, take maximum of left spacing and left offset. // OD 10.03.2003 #i9040# - consider left line attribute. nLeftSpacing = std::max( nLeftSpacing, ( nLeftOffset + nLeftLine ) ); } // OD 23.01.2003 #106895# - add 1st param to nRightSpacing = pAttrs->CalcRight( this ); if( nRightOffset ) { // OD 07.03.2003 #i9040# - surround fly frames only, if // they overlap with the table. // Thus, take maximum of right spacing and right offset. // OD 10.03.2003 #i9040# - consider right line attribute. nRightSpacing = std::max( nRightSpacing, ( nRightOffset + nRightLine ) ); } } break; case text::HoriOrientation::LEFT_AND_WIDTH: { // count left border and width (Word specialty) // OD 10.03.2003 #i9040# - no width alignment in online mode. //bCheckBrowseWidth = true; nLeftSpacing = pAttrs->CalcLeft( this ); if( nLeftOffset ) { // OD 10.03.2003 #i9040# - surround fly frames only, if // they overlap with the table. // Thus, take maximum of right spacing and right offset. // OD 10.03.2003 #i9040# - consider left line attribute. nLeftSpacing = std::max( nLeftSpacing, ( pAttrs->CalcLeftLine() + nLeftOffset ) ); } // OD 10.03.2003 #i9040# - consider right and left line attribute. const SwTwips nWishRight = nMax - (nLeftSpacing-pAttrs->CalcLeftLine()) - nWishedTableWidth; nRightSpacing = nRightLine + ( (nRightOffset > 0) ? std::max( nWishRight, nRightOffset ) : nWishRight ); } break; default: OSL_FAIL( "Invalid orientation for table." ); } // #i26250# - extend bottom printing area, if table // is last content inside a table cell. if ( GetFormat()->getIDocumentSettingAccess().get(DocumentSettingId::ADD_PARA_SPACING_TO_TABLE_CELLS) && GetUpper()->IsInTab() && !GetIndNext() ) { nLower += pAttrs->GetULSpace().GetLower(); } aRectFnSet.SetYMargins( *this, nUpper, nLower ); if( (nMax - MINLAY) < (nLeftSpacing + nRightSpacing) ) aRectFnSet.SetXMargins( *this, 0, 0 ); else aRectFnSet.SetXMargins( *this, nLeftSpacing, nRightSpacing ); SwViewShell *pSh = getRootFrame()->GetCurrShell(); if ( bCheckBrowseWidth && pSh && pSh->GetViewOptions()->getBrowseMode() && GetUpper()->IsPageBodyFrame() && // only PageBodyFrames and not ColBodyFrames pSh->VisArea().Width() ) { //Don't go beyond the edge of the visible area. //The page width can be bigger because objects with //"over-size" are possible (RootFrame::ImplCalcBrowseWidth()) long nWidth = pSh->GetBrowseWidth(); nWidth -= getFramePrintArea().Left(); nWidth -= pAttrs->CalcRightLine(); SwFrameAreaDefinition::FramePrintAreaWriteAccess aPrt(*this); aPrt.Width( std::min( nWidth, aPrt.Width() ) ); } if ( nOldHeight != aRectFnSet.GetHeight(getFramePrintArea()) ) { setFrameAreaSizeValid(false); } } if ( !isFrameAreaSizeValid() ) { setFrameAreaSizeValid(true); // The size is defined by the content plus the margins. SwTwips nRemaining = 0, nDiff; SwFrame *pFrame = m_pLower; while ( pFrame ) { nRemaining += aRectFnSet.GetHeight(pFrame->getFrameArea()); pFrame = pFrame->GetNext(); } // And now add the margins nRemaining += nUpper + nLower; nDiff = aRectFnSet.GetHeight(getFrameArea()) - nRemaining; if ( nDiff > 0 ) Shrink( nDiff ); else if ( nDiff < 0 ) Grow( -nDiff ); } } SwTwips SwTabFrame::GrowFrame( SwTwips nDist, bool bTst, bool bInfo ) { SwRectFnSet aRectFnSet(this); SwTwips nHeight = aRectFnSet.GetHeight(getFrameArea()); if( nHeight > 0 && nDist > ( LONG_MAX - nHeight ) ) nDist = LONG_MAX - nHeight; if ( bTst && !IsRestrictTableGrowth() ) return nDist; if ( GetUpper() ) { SwRect aOldFrame( getFrameArea() ); //The upper only grows as far as needed. nReal provides the distance //which is already available. SwTwips nReal = aRectFnSet.GetHeight(GetUpper()->getFramePrintArea()); SwFrame *pFrame = GetUpper()->Lower(); while ( pFrame && GetFollow() != pFrame ) { nReal -= aRectFnSet.GetHeight(pFrame->getFrameArea()); pFrame = pFrame->GetNext(); } if ( nReal < nDist ) { long nTmp = GetUpper()->Grow( nDist - std::max(nReal, 0), bTst, bInfo ); if ( IsRestrictTableGrowth() ) { nTmp = std::min( nDist, nReal + nTmp ); nDist = nTmp < 0 ? 0 : nTmp; } } if ( !bTst ) { { SwFrameAreaDefinition::FrameAreaWriteAccess aFrm(*this); aRectFnSet.AddBottom( aFrm, nDist ); } SwRootFrame *pRootFrame = getRootFrame(); if( pRootFrame && pRootFrame->IsAnyShellAccessible() && pRootFrame->GetCurrShell() ) { pRootFrame->GetCurrShell()->Imp()->MoveAccessibleFrame( this, aOldFrame ); } } } if ( !bTst && ( nDist || IsRestrictTableGrowth() ) ) { SwPageFrame *pPage = FindPageFrame(); if ( GetNext() ) { GetNext()->InvalidatePos_(); if ( GetNext()->IsContentFrame() ) GetNext()->InvalidatePage( pPage ); } // #i28701# - Due to the new object positioning the // frame on the next page/column can flow backward (e.g. it was moved // forward due to the positioning of its objects ). Thus, invalivate this // next frame, if document compatibility option 'Consider wrapping style // influence on object positioning' is ON. else if ( GetFormat()->getIDocumentSettingAccess().get(DocumentSettingId::CONSIDER_WRAP_ON_OBJECT_POSITION) ) { InvalidateNextPos(); } InvalidateAll_(); InvalidatePage( pPage ); SetComplete(); std::shared_ptr aBack = GetFormat()->makeBackgroundBrushItem(); const SvxGraphicPosition ePos = aBack ? aBack->GetGraphicPos() : GPOS_NONE; if ( GPOS_NONE != ePos && GPOS_TILED != ePos ) SetCompletePaint(); } return nDist; } void SwTabFrame::Modify( const SfxPoolItem* pOld, const SfxPoolItem * pNew ) { sal_uInt8 nInvFlags = 0; bool bAttrSetChg = pNew && RES_ATTRSET_CHG == pNew->Which(); if( bAttrSetChg ) { SfxItemIter aNIter( *static_cast(pNew)->GetChgSet() ); SfxItemIter aOIter( *static_cast(pOld)->GetChgSet() ); const SfxPoolItem* pNItem = aNIter.GetCurItem(); const SfxPoolItem* pOItem = aOIter.GetCurItem(); SwAttrSetChg aOldSet( *static_cast(pOld) ); SwAttrSetChg aNewSet( *static_cast(pNew) ); do { UpdateAttr_(pOItem, pNItem, nInvFlags, &aOldSet, &aNewSet); pNItem = aNIter.NextItem(); pOItem = aOIter.NextItem(); } while (pNItem); if ( aOldSet.Count() || aNewSet.Count() ) SwLayoutFrame::Modify( &aOldSet, &aNewSet ); } else UpdateAttr_( pOld, pNew, nInvFlags ); if ( nInvFlags != 0 ) { SwPageFrame *pPage = FindPageFrame(); InvalidatePage( pPage ); if ( nInvFlags & 0x02 ) InvalidatePrt_(); if ( nInvFlags & 0x40 ) InvalidatePos_(); SwFrame *pTmp; if ( nullptr != (pTmp = GetIndNext()) ) { if ( nInvFlags & 0x04 ) { pTmp->InvalidatePrt_(); if ( pTmp->IsContentFrame() ) pTmp->InvalidatePage( pPage ); } if ( nInvFlags & 0x10 ) pTmp->SetCompletePaint(); } if ( nInvFlags & 0x08 && nullptr != (pTmp = GetPrev()) ) { pTmp->InvalidatePrt_(); if ( pTmp->IsContentFrame() ) pTmp->InvalidatePage( pPage ); } if ( nInvFlags & 0x20 ) { if ( pPage && pPage->GetUpper() && !IsFollow() ) static_cast(pPage->GetUpper())->InvalidateBrowseWidth(); } if ( nInvFlags & 0x80 ) InvalidateNextPos(); } } void SwTabFrame::UpdateAttr_( const SfxPoolItem *pOld, const SfxPoolItem *pNew, sal_uInt8 &rInvFlags, SwAttrSetChg *pOldSet, SwAttrSetChg *pNewSet ) { bool bClear = true; const sal_uInt16 nWhich = pOld ? pOld->Which() : pNew ? pNew->Which() : 0; switch( nWhich ) { case RES_TBLHEADLINECHG: if ( IsFollow() ) { // Delete remaining headlines: SwRowFrame* pLowerRow = nullptr; while ( nullptr != ( pLowerRow = static_cast(Lower()) ) && pLowerRow->IsRepeatedHeadline() ) { pLowerRow->Cut(); SwFrame::DestroyFrame(pLowerRow); } // insert new headlines const sal_uInt16 nNewRepeat = GetTable()->GetRowsToRepeat(); for ( sal_uInt16 nIdx = 0; nIdx < nNewRepeat; ++nIdx ) { bDontCreateObjects = true; //frmtool SwRowFrame* pHeadline = new SwRowFrame( *GetTable()->GetTabLines()[ nIdx ], this ); pHeadline->SetRepeatedHeadline( true ); bDontCreateObjects = false; pHeadline->Paste( this, pLowerRow ); } } rInvFlags |= 0x02; break; case RES_FRM_SIZE: case RES_HORI_ORIENT: rInvFlags |= 0x22; break; case RES_PAGEDESC: //Attribute changes (on/off) if ( IsInDocBody() ) { rInvFlags |= 0x40; SwPageFrame *pPage = FindPageFrame(); if (pPage) { if ( !GetPrev() ) CheckPageDescs( pPage ); if (GetFormat()->GetPageDesc().GetNumOffset()) static_cast(pPage->GetUpper())->SetVirtPageNum( true ); SwDocPosUpdate aMsgHint( pPage->getFrameArea().Top() ); GetFormat()->GetDoc()->getIDocumentFieldsAccess().UpdatePageFields( &aMsgHint ); } } break; case RES_BREAK: rInvFlags |= 0xC0; break; case RES_LAYOUT_SPLIT: if ( !IsFollow() ) rInvFlags |= 0x40; break; case RES_FRAMEDIR : SetDerivedR2L( false ); CheckDirChange(); break; case RES_COLLAPSING_BORDERS : rInvFlags |= 0x02; lcl_InvalidateAllLowersPrt( this ); break; case RES_UL_SPACE: rInvFlags |= 0x1C; [[fallthrough]]; default: bClear = false; } if ( bClear ) { if ( pOldSet || pNewSet ) { if ( pOldSet ) pOldSet->ClearItem( nWhich ); if ( pNewSet ) pNewSet->ClearItem( nWhich ); } else SwLayoutFrame::Modify( pOld, pNew ); } } bool SwTabFrame::GetInfo( SfxPoolItem &rHint ) const { if ( RES_VIRTPAGENUM_INFO == rHint.Which() && IsInDocBody() && !IsFollow() ) { SwVirtPageNumInfo &rInfo = static_cast(rHint); const SwPageFrame *pPage = FindPageFrame(); if ( pPage ) { if ( pPage == rInfo.GetOrigPage() && !GetPrev() ) { // Should be the one (can temporarily be different, should we be // concerned about this possibility?) rInfo.SetInfo( pPage, this ); return false; } if ( pPage->GetPhyPageNum() < rInfo.GetOrigPage()->GetPhyPageNum() && (!rInfo.GetPage() || pPage->GetPhyPageNum() > rInfo.GetPage()->GetPhyPageNum())) { //This could be the one. rInfo.SetInfo( pPage, this ); } } } return true; } SwFrame *SwTabFrame::FindLastContentOrTable() { SwFrame *pRet = m_pLower; while ( pRet && !pRet->IsContentFrame() ) { SwFrame *pOld = pRet; SwFrame *pTmp = pRet; // To skip empty section frames while ( pRet->GetNext() ) { pRet = pRet->GetNext(); if( !pRet->IsSctFrame() || static_cast(pRet)->GetSection() ) pTmp = pRet; } pRet = pTmp; if ( pRet->GetLower() ) pRet = pRet->GetLower(); if ( pRet == pOld ) { // Check all other columns if there is a column based section with // an empty last column at the end of the last cell - this is done // by SwSectionFrame::FindLastContent if( pRet->IsColBodyFrame() ) { #if OSL_DEBUG_LEVEL > 0 SwSectionFrame* pSect = pRet->FindSctFrame(); OSL_ENSURE( pSect, "Where does this column come from?"); OSL_ENSURE( IsAnLower( pSect ), "Split cell?" ); #endif return pRet->FindSctFrame()->FindLastContent(); } // pRet may be a cell frame without a lower (cell has been split). // We have to find the last content the hard way: OSL_ENSURE( pRet->IsCellFrame(), "SwTabFrame::FindLastContent failed" ); const SwFrame* pRow = pRet->GetUpper(); while ( pRow && !pRow->GetUpper()->IsTabFrame() ) pRow = pRow->GetUpper(); const SwContentFrame* pContentFrame = pRow ? static_cast(pRow)->ContainsContent() : nullptr; pRet = nullptr; while ( pContentFrame && static_cast(pRow)->IsAnLower( pContentFrame ) ) { pRet = const_cast(pContentFrame); pContentFrame = pContentFrame->GetNextContentFrame(); } } } // #112929# There actually is a situation, which results in pRet = 0: // Insert frame, insert table via text <-> table. This gives you a frame // containing a table without any other content frames. Split the table // and undo the splitting. This operation gives us a table frame without // a lower. if ( pRet ) { while ( pRet->GetNext() ) pRet = pRet->GetNext(); if (pRet->IsSctFrame()) pRet = static_cast(pRet)->FindLastContent(); } assert(pRet == nullptr || dynamic_cast(pRet) || dynamic_cast(pRet)); return pRet; } SwContentFrame *SwTabFrame::FindLastContent() { SwFrame * pRet(FindLastContentOrTable()); while (pRet && pRet->IsTabFrame()) // possibly there's only tables here! { // tdf#126138 skip table, don't look inside pRet = pRet->GetPrev(); } assert(pRet == nullptr || dynamic_cast(pRet)); return static_cast(pRet); } /// Return value defines if the frm needs to be relocated bool SwTabFrame::ShouldBwdMoved( SwLayoutFrame *pNewUpper, bool &rReformat ) { rReformat = false; if ( SwFlowFrame::IsMoveBwdJump() || !IsPrevObjMove() ) { //Flowing back Frames is quite time consuming unfortunately. //Most often the location where the Frame wants to flow to has the same //FixSize as the Frame itself. In such a situation it's easy to check if //the Frame will find enough space for its VarSize, if this is not the //case, the relocation can be skipped. //Checking if the Frame will find enough space is done by the Frame itself, //this also takes the possibility of splitting the Frame into account. //If the FixSize is different or Flys are involved (at the old or the //new position) the checks are pointless, the Frame then //needs to be relocated tentatively (if a bit of space is available). //The FixSize of the environments which contain tables is always the //width. SwPageFrame *pOldPage = FindPageFrame(), *pNewPage = pNewUpper->FindPageFrame(); bool bMoveAnyway = false; SwTwips nSpace = 0; SwRectFnSet aRectFnSet(this); if ( !SwFlowFrame::IsMoveBwdJump() ) { long nOldWidth = aRectFnSet.GetWidth(GetUpper()->getFramePrintArea()); SwRectFnSet fnRectX(pNewUpper); long nNewWidth = fnRectX.GetWidth(pNewUpper->getFramePrintArea()); if( std::abs( nNewWidth - nOldWidth ) < 2 ) { bMoveAnyway = BwdMoveNecessary( pOldPage, getFrameArea() ) > 1; if( !bMoveAnyway ) { SwRect aRect( pNewUpper->getFramePrintArea() ); aRect.Pos() += pNewUpper->getFrameArea().Pos(); const SwFrame *pPrevFrame = pNewUpper->Lower(); while ( pPrevFrame && pPrevFrame != this ) { fnRectX.SetTop( aRect, fnRectX.GetBottom(pPrevFrame->getFrameArea()) ); pPrevFrame = pPrevFrame->GetNext(); } bMoveAnyway = BwdMoveNecessary( pNewPage, aRect) > 1; // #i54861# Due to changes made in PrepareMake, // the tabfrm may not have a correct position. Therefore // it is possible that pNewUpper->getFramePrintArea().Height == 0. In this // case the above calculation of nSpace might give wrong // results and we really do not want to MoveBackward into a // 0 height frame. If nTmpSpace is already <= 0, we take this // value: const SwTwips nTmpSpace = fnRectX.GetHeight(aRect); if ( fnRectX.GetHeight(pNewUpper->getFramePrintArea()) > 0 || nTmpSpace <= 0 ) nSpace = nTmpSpace; const SwViewShell *pSh = getRootFrame()->GetCurrShell(); if( pSh && pSh->GetViewOptions()->getBrowseMode() ) nSpace += pNewUpper->Grow( LONG_MAX, true ); } } else if (!m_bLockBackMove) bMoveAnyway = true; } else if (!m_bLockBackMove) bMoveAnyway = true; if ( bMoveAnyway ) { rReformat = true; return true; } bool bFits = nSpace > 0; if (!bFits && aRectFnSet.GetHeight(getFrameArea()) == 0) // This frame fits into pNewUpper in case it has no space, but this // frame is empty. bFits = nSpace >= 0; if (!m_bLockBackMove && bFits) { // #i26945# - check, if follow flow line // contains frame, which are moved forward due to its object // positioning. SwRowFrame* pFirstRow = GetFirstNonHeadlineRow(); if ( pFirstRow && pFirstRow->IsInFollowFlowRow() && SwLayouter::DoesRowContainMovedFwdFrame( *(pFirstRow->GetFormat()->GetDoc()), *pFirstRow ) ) { return false; } SwTwips nTmpHeight = CalcHeightOfFirstContentLine(); // For some mysterious reason, I changed the good old // 'return nHeight <= nSpace' to 'return nTmpHeight < nSpace'. // This obviously results in problems with table frames in // sections. Remember: Every twip is sacred. return nTmpHeight <= nSpace; } } return false; } void SwTabFrame::Cut() { OSL_ENSURE( GetUpper(), "Cut without Upper()." ); SwPageFrame *pPage = FindPageFrame(); InvalidatePage( pPage ); SwFrame *pFrame = GetNext(); if( pFrame ) { // Possibly the old follow calculated a spacing to the predecessor // which is obsolete now when it becomes the first frame pFrame->InvalidatePrt_(); pFrame->InvalidatePos_(); if ( pFrame->IsContentFrame() ) pFrame->InvalidatePage( pPage ); if( IsInSct() && !GetPrev() ) { SwSectionFrame* pSct = FindSctFrame(); if( !pSct->IsFollow() ) { pSct->InvalidatePrt_(); pSct->InvalidatePage( pPage ); } } } else { InvalidateNextPos(); //Someone has to do the retouch: predecessor or upper if ( nullptr != (pFrame = GetPrev()) ) { pFrame->SetRetouche(); pFrame->Prepare( PrepareHint::WidowsOrphans ); pFrame->InvalidatePos_(); if ( pFrame->IsContentFrame() ) pFrame->InvalidatePage( pPage ); } //If I am (was) the only FlowFrame in my own upper, it has to do //the retouch. Moreover a new empty page might be created. else { SwRootFrame *pRoot = static_cast(pPage->GetUpper()); pRoot->SetSuperfluous(); GetUpper()->SetCompletePaint(); if( IsInSct() ) { SwSectionFrame* pSct = FindSctFrame(); if( !pSct->IsFollow() ) { pSct->InvalidatePrt_(); pSct->InvalidatePage( pPage ); } } } } //First remove, then shrink the upper. SwLayoutFrame *pUp = GetUpper(); SwRectFnSet aRectFnSet(this); RemoveFromLayout(); if ( pUp ) { OSL_ENSURE( !pUp->IsFootnoteFrame(), "Table in Footnote." ); SwSectionFrame *pSct = nullptr; // #126020# - adjust check for empty section // #130797# - correct fix #126020# if ( !pUp->Lower() && pUp->IsInSct() && !(pSct = pUp->FindSctFrame())->ContainsContent() && !pSct->ContainsAny( true ) ) { if ( pUp->GetUpper() ) { pSct->DelEmpty( false ); pSct->InvalidateSize_(); } } // table-in-footnote: delete empty footnote frames (like SwContentFrame::Cut) else if (!pUp->Lower() && pUp->IsFootnoteFrame() && !pUp->IsColLocked()) { if (pUp->GetNext() && !pUp->GetPrev()) { if (SwFrame *const pTmp = static_cast(pUp->GetNext())->ContainsAny()) { pTmp->InvalidatePrt_(); } } if (!pUp->IsDeleteForbidden()) { pUp->Cut(); SwFrame::DestroyFrame(pUp); } } else if( aRectFnSet.GetHeight(getFrameArea()) ) { // OD 26.08.2003 #i18103# - *no* 'ColUnlock' of section - // undo changes of fix for #104992# pUp->Shrink( getFrameArea().Height() ); } } if ( pPage && !IsFollow() && pPage->GetUpper() ) static_cast(pPage->GetUpper())->InvalidateBrowseWidth(); } void SwTabFrame::Paste( SwFrame* pParent, SwFrame* pSibling ) { OSL_ENSURE( pParent, "No parent for pasting." ); OSL_ENSURE( pParent->IsLayoutFrame(), "Parent is ContentFrame." ); OSL_ENSURE( pParent != this, "I'm the parent myself." ); OSL_ENSURE( pSibling != this, "I'm my own neighbour." ); OSL_ENSURE( !GetPrev() && !GetNext() && !GetUpper(), "I'm still registered somewhere." ); //Insert in the tree. InsertBefore( static_cast(pParent), pSibling ); InvalidateAll_(); SwPageFrame *pPage = FindPageFrame(); InvalidatePage( pPage ); if ( GetNext() ) { GetNext()->InvalidatePos_(); GetNext()->InvalidatePrt_(); if ( GetNext()->IsContentFrame() ) GetNext()->InvalidatePage( pPage ); } SwRectFnSet aRectFnSet(this); if( aRectFnSet.GetHeight(getFrameArea()) ) pParent->Grow( aRectFnSet.GetHeight(getFrameArea()) ); if( aRectFnSet.GetWidth(getFrameArea()) != aRectFnSet.GetWidth(pParent->getFramePrintArea()) ) Prepare( PrepareHint::FixSizeChanged ); if ( GetPrev() ) { if ( !IsFollow() ) { GetPrev()->InvalidateSize(); if ( GetPrev()->IsContentFrame() ) GetPrev()->InvalidatePage( pPage ); } } else if ( GetNext() ) // Take the spacing into account when dealing with ContentFrames. // There are two situations (both always happen at the same time): // a) The Content becomes the first in a chain // b) The new follower was previously the first in a chain GetNext()->InvalidatePrt_(); if ( pPage && !IsFollow() ) { if ( pPage->GetUpper() ) static_cast(pPage->GetUpper())->InvalidateBrowseWidth(); if ( !GetPrev() )//At least needed for HTML with a table at the beginning. { const SwPageDesc *pDesc = GetFormat()->GetPageDesc().GetPageDesc(); if ( (pDesc && pDesc != pPage->GetPageDesc()) || (!pDesc && pPage->GetPageDesc() != &GetFormat()->GetDoc()->GetPageDesc(0)) ) CheckPageDescs( pPage ); } } } bool SwTabFrame::Prepare( const PrepareHint eHint, const void *, bool ) { if( PrepareHint::BossChanged == eHint ) CheckDirChange(); return false; } SwRowFrame::SwRowFrame(const SwTableLine &rLine, SwFrame* pSib, bool bInsertContent) : SwLayoutFrame( rLine.GetFrameFormat(), pSib ) , m_pTabLine( &rLine ) , m_pFollowRow( nullptr ) // #i29550# , mnTopMarginForLowers( 0 ) , mnBottomMarginForLowers( 0 ) , mnBottomLineSize( 0 ) // --> split table rows , m_bIsFollowFlowRow( false ) // <-- split table rows , m_bIsRepeatedHeadline( false ) , m_bIsRowSpanLine( false ) , m_bIsInSplit( false ) { mnFrameType = SwFrameType::Row; //Create the boxes and insert them. const SwTableBoxes &rBoxes = rLine.GetTabBoxes(); SwFrame *pTmpPrev = nullptr; for ( size_t i = 0; i < rBoxes.size(); ++i ) { SwCellFrame *pNew = new SwCellFrame( *rBoxes[i], this, bInsertContent ); pNew->InsertBehind( this, pTmpPrev ); pTmpPrev = pNew; } } void SwRowFrame::DestroyImpl() { SwModify* pMod = GetFormat(); if( pMod ) { pMod->Remove( this ); if( !pMod->HasWriterListeners() ) delete pMod; } SwLayoutFrame::DestroyImpl(); } SwRowFrame::~SwRowFrame() { } void SwRowFrame::RegistFlys( SwPageFrame *pPage ) { ::RegistFlys( pPage ? pPage : FindPageFrame(), this ); } void SwRowFrame::Modify( const SfxPoolItem* pOld, const SfxPoolItem * pNew ) { bool bAttrSetChg = pNew && RES_ATTRSET_CHG == pNew->Which(); const SfxPoolItem *pItem = nullptr; if( bAttrSetChg ) { const SwAttrSet* pChgSet = static_cast(pNew)->GetChgSet(); pChgSet->GetItemState( RES_FRM_SIZE, false, &pItem); if ( !pItem ) pChgSet->GetItemState( RES_ROW_SPLIT, false, &pItem); } else if (pNew && (RES_FRM_SIZE == pNew->Which() || RES_ROW_SPLIT == pNew->Which())) pItem = pNew; if ( pItem ) { SwTabFrame *pTab = FindTabFrame(); if ( pTab ) { const bool bInFirstNonHeadlineRow = pTab->IsFollow() && this == pTab->GetFirstNonHeadlineRow(); // #i35063# // Invalidation required is pRow is last row if ( bInFirstNonHeadlineRow || !GetNext() ) { if ( bInFirstNonHeadlineRow ) pTab = pTab->FindMaster(); pTab->InvalidatePos(); } } } SwLayoutFrame::Modify( pOld, pNew ); } void SwRowFrame::MakeAll(vcl::RenderContext* pRenderContext) { if ( !GetNext() ) { setFrameAreaSizeValid(false); } SwLayoutFrame::MakeAll(pRenderContext); } long CalcHeightWithFlys( const SwFrame *pFrame ) { SwRectFnSet aRectFnSet(pFrame); long nHeight = 0; const SwFrame* pTmp = pFrame->IsSctFrame() ? static_cast(pFrame)->ContainsContent() : pFrame; while( pTmp ) { // #i26945# - consider follow text frames const SwSortedObjs* pObjs( nullptr ); bool bIsFollow( false ); if ( pTmp->IsTextFrame() && static_cast(pTmp)->IsFollow() ) { const SwFrame* pMaster; // #i46450# Master does not necessarily have // to exist if this function is called from JoinFrame() -> // Cut() -> Shrink() const SwTextFrame* pTmpFrame = static_cast(pTmp); if ( pTmpFrame->GetPrev() && pTmpFrame->GetPrev()->IsTextFrame() && static_cast(pTmpFrame->GetPrev())->GetFollow() && static_cast(pTmpFrame->GetPrev())->GetFollow() != pTmp ) pMaster = nullptr; else pMaster = pTmpFrame->FindMaster(); if ( pMaster ) { pObjs = static_cast(pTmp)->FindMaster()->GetDrawObjs(); bIsFollow = true; } } else { pObjs = pTmp->GetDrawObjs(); } if ( pObjs ) { for (SwAnchoredObject* pAnchoredObj : *pObjs) { // #i26945# - if is follow, the // anchor character frame has to be . if ( bIsFollow && pAnchoredObj->FindAnchorCharFrame() != pTmp ) { continue; } // #i26945# - consider also drawing objects { // OD 30.09.2003 #i18732# - only objects, which follow // the text flow have to be considered. const SwFrameFormat& rFrameFormat = pAnchoredObj->GetFrameFormat(); bool bFollowTextFlow = rFrameFormat.GetFollowTextFlow().GetValue(); const bool bConsiderObj = (rFrameFormat.GetAnchor().GetAnchorId() != RndStdIds::FLY_AS_CHAR) && pAnchoredObj->GetObjRect().Top() != FAR_AWAY && bFollowTextFlow && pAnchoredObj->GetPageFrame() == pTmp->FindPageFrame(); bool bWrapThrough = rFrameFormat.GetSurround().GetValue() == text::WrapTextMode_THROUGH; if (pFrame->IsInTab() && bFollowTextFlow && bWrapThrough) { // Ignore wrap-through objects when determining the cell height. // Normally FollowTextFlow requires a resize of the cell, but not in case of // wrap-through. continue; } if ( bConsiderObj ) { const SwFormatFrameSize &rSz = rFrameFormat.GetFrameSize(); if( !rSz.GetHeightPercent() ) { const SwTwips nDistOfFlyBottomToAnchorTop = aRectFnSet.GetHeight(pAnchoredObj->GetObjRect()) + ( aRectFnSet.IsVert() ? pAnchoredObj->GetCurrRelPos().X() : pAnchoredObj->GetCurrRelPos().Y() ); const SwTwips nFrameDiff = aRectFnSet.YDiff( aRectFnSet.GetTop(pTmp->getFrameArea()), aRectFnSet.GetTop(pFrame->getFrameArea()) ); nHeight = std::max( nHeight, nDistOfFlyBottomToAnchorTop + nFrameDiff - aRectFnSet.GetHeight(pFrame->getFrameArea()) ); // #i56115# The first height calculation // gives wrong results if pFrame->getFramePrintArea().Y() > 0. We do // a second calculation based on the actual rectangles of // pFrame and pAnchoredObj, and use the maximum of the results. // I do not want to remove the first calculation because // if clipping has been applied, using the GetCurrRelPos // might be the better option to calculate nHeight. const SwTwips nDistOfFlyBottomToAnchorTop2 = aRectFnSet.YDiff( aRectFnSet.GetBottom(pAnchoredObj->GetObjRect()), aRectFnSet.GetBottom(pFrame->getFrameArea()) ); nHeight = std::max( nHeight, nDistOfFlyBottomToAnchorTop2 ); } } } } } if( !pFrame->IsSctFrame() ) break; pTmp = pTmp->FindNextCnt(); if( !static_cast(pFrame)->IsAnLower( pTmp ) ) break; } return nHeight; } static SwTwips lcl_CalcTopAndBottomMargin( const SwLayoutFrame& rCell, const SwBorderAttrs& rAttrs ) { const SwTabFrame* pTab = rCell.FindTabFrame(); SwTwips nTopSpace = 0; SwTwips nBottomSpace = 0; // #i29550# if ( pTab->IsCollapsingBorders() && rCell.Lower() && !rCell.Lower()->IsRowFrame() ) { nTopSpace = static_cast(rCell.GetUpper())->GetTopMarginForLowers(); nBottomSpace = static_cast(rCell.GetUpper())->GetBottomMarginForLowers(); } else { if ( pTab->IsVertical() != rCell.IsVertical() ) { nTopSpace = rAttrs.CalcLeft( &rCell ); nBottomSpace = rAttrs.CalcRight( &rCell ); } else { nTopSpace = rAttrs.CalcTop(); nBottomSpace = rAttrs.CalcBottom(); } } return nTopSpace + nBottomSpace; } // #i26945# - add parameter <_bConsiderObjs> in order to // control, if floating screen objects have to be considered for the minimal // cell height. static SwTwips lcl_CalcMinCellHeight( const SwLayoutFrame *_pCell, const bool _bConsiderObjs, const SwBorderAttrs *pAttrs = nullptr ) { SwRectFnSet aRectFnSet(_pCell); SwTwips nHeight = 0; const SwFrame* pLow = _pCell->Lower(); if ( pLow ) { long nFlyAdd = 0; while ( pLow ) { // OD 2004-02-18 #106629# - change condition and switch then-body // and else-body if ( pLow->IsRowFrame() ) { // #i26945# nHeight += ::lcl_CalcMinRowHeight( static_cast(pLow), _bConsiderObjs ); } else { long nLowHeight = aRectFnSet.GetHeight(pLow->getFrameArea()); nHeight += nLowHeight; // #i26945# if ( _bConsiderObjs ) { nFlyAdd = std::max( 0L, nFlyAdd - nLowHeight ); nFlyAdd = std::max( nFlyAdd, ::CalcHeightWithFlys( pLow ) ); } } pLow = pLow->GetNext(); } if ( nFlyAdd ) nHeight += nFlyAdd; } // The border/margin needs to be considered too, unfortunately it can't be // calculated using PrintArea and FrameArea because any or all of those // may be invalid. if ( _pCell->Lower() ) { if ( pAttrs ) nHeight += lcl_CalcTopAndBottomMargin( *_pCell, *pAttrs ); else { SwBorderAttrAccess aAccess( SwFrame::GetCache(), _pCell ); const SwBorderAttrs &rAttrs = *aAccess.Get(); nHeight += lcl_CalcTopAndBottomMargin( *_pCell, rAttrs ); } } return nHeight; } // OD 2004-02-18 #106629# - correct type of 1st parameter // #i26945# - add parameter <_bConsiderObjs> in order to control, // if floating screen objects have to be considered for the minimal cell height static SwTwips lcl_CalcMinRowHeight( const SwRowFrame* _pRow, const bool _bConsiderObjs ) { SwTwips nHeight = 0; if ( !_pRow->IsRowSpanLine() ) { const SwFormatFrameSize &rSz = _pRow->GetFormat()->GetFrameSize(); if ( _pRow->HasFixSize() ) { OSL_ENSURE(SwFrameSize::Fixed == rSz.GetHeightSizeType(), "pRow claims to have fixed size"); return rSz.GetHeight(); } // If this row frame is being split, then row's minimal height shouldn't restrict // this frame's minimal height, because the rest will go to follow frame. else if ( !_pRow->IsInSplit() && rSz.GetHeightSizeType() == SwFrameSize::Minimum ) { nHeight = rSz.GetHeight() - lcl_calcHeightOfRowBeforeThisFrame(*_pRow); } } SwRectFnSet aRectFnSet(_pRow); const SwCellFrame* pLow = static_cast(_pRow->Lower()); while ( pLow ) { SwTwips nTmp = 0; const long nRowSpan = pLow->GetLayoutRowSpan(); // --> NEW TABLES // Consider height of // 1. current cell if RowSpan == 1 // 2. current cell if cell is "follow" cell of a cell with RowSpan == -1 // 3. master cell if RowSpan == -1 if ( 1 == nRowSpan ) { nTmp = ::lcl_CalcMinCellHeight( pLow, _bConsiderObjs ); } else if ( -1 == nRowSpan ) { // Height of the last cell of a row span is height of master cell // minus the height of the other rows which are covered by the master // cell: const SwCellFrame& rMaster = pLow->FindStartEndOfRowSpanCell( true ); nTmp = ::lcl_CalcMinCellHeight( &rMaster, _bConsiderObjs ); const SwFrame* pMasterRow = rMaster.GetUpper(); while ( pMasterRow && pMasterRow != _pRow ) { nTmp -= aRectFnSet.GetHeight(pMasterRow->getFrameArea()); pMasterRow = pMasterRow->GetNext(); } } // <-- NEW TABLES // Do not consider rotated cells: if ( pLow->IsVertical() == aRectFnSet.IsVert() && nTmp > nHeight ) nHeight = nTmp; pLow = static_cast(pLow->GetNext()); } return nHeight; } // #i29550# // Calculate the maximum of (TopLineSize + TopLineDist) over all lowers: static sal_uInt16 lcl_GetTopSpace( const SwRowFrame& rRow ) { sal_uInt16 nTopSpace = 0; for ( const SwCellFrame* pCurrLower = static_cast(rRow.Lower()); pCurrLower; pCurrLower = static_cast(pCurrLower->GetNext()) ) { sal_uInt16 nTmpTopSpace = 0; if ( pCurrLower->Lower() && pCurrLower->Lower()->IsRowFrame() ) nTmpTopSpace = lcl_GetTopSpace( *static_cast(pCurrLower->Lower()) ); else { const SwAttrSet& rSet = const_cast(pCurrLower)->GetFormat()->GetAttrSet(); const SvxBoxItem& rBoxItem = rSet.GetBox(); nTmpTopSpace = rBoxItem.CalcLineSpace( SvxBoxItemLine::TOP, true ); } nTopSpace = std::max( nTopSpace, nTmpTopSpace ); } return nTopSpace; } // Calculate the maximum of TopLineDist over all lowers: static sal_uInt16 lcl_GetTopLineDist( const SwRowFrame& rRow ) { sal_uInt16 nTopLineDist = 0; for ( const SwCellFrame* pCurrLower = static_cast(rRow.Lower()); pCurrLower; pCurrLower = static_cast(pCurrLower->GetNext()) ) { sal_uInt16 nTmpTopLineDist = 0; if ( pCurrLower->Lower() && pCurrLower->Lower()->IsRowFrame() ) nTmpTopLineDist = lcl_GetTopLineDist( *static_cast(pCurrLower->Lower()) ); else { const SwAttrSet& rSet = const_cast(pCurrLower)->GetFormat()->GetAttrSet(); const SvxBoxItem& rBoxItem = rSet.GetBox(); nTmpTopLineDist = rBoxItem.GetDistance( SvxBoxItemLine::TOP ); } nTopLineDist = std::max( nTopLineDist, nTmpTopLineDist ); } return nTopLineDist; } // Calculate the maximum of BottomLineSize over all lowers: static sal_uInt16 lcl_GetBottomLineSize( const SwRowFrame& rRow ) { sal_uInt16 nBottomLineSize = 0; for ( const SwCellFrame* pCurrLower = static_cast(rRow.Lower()); pCurrLower; pCurrLower = static_cast(pCurrLower->GetNext()) ) { sal_uInt16 nTmpBottomLineSize = 0; if ( pCurrLower->Lower() && pCurrLower->Lower()->IsRowFrame() ) { const SwFrame* pRow = pCurrLower->GetLastLower(); nTmpBottomLineSize = lcl_GetBottomLineSize( *static_cast(pRow) ); } else { const SwAttrSet& rSet = const_cast(pCurrLower)->GetFormat()->GetAttrSet(); const SvxBoxItem& rBoxItem = rSet.GetBox(); nTmpBottomLineSize = rBoxItem.CalcLineSpace( SvxBoxItemLine::BOTTOM, true ) - rBoxItem.GetDistance( SvxBoxItemLine::BOTTOM ); } nBottomLineSize = std::max( nBottomLineSize, nTmpBottomLineSize ); } return nBottomLineSize; } // Calculate the maximum of BottomLineDist over all lowers: static sal_uInt16 lcl_GetBottomLineDist( const SwRowFrame& rRow ) { sal_uInt16 nBottomLineDist = 0; for ( const SwCellFrame* pCurrLower = static_cast(rRow.Lower()); pCurrLower; pCurrLower = static_cast(pCurrLower->GetNext()) ) { sal_uInt16 nTmpBottomLineDist = 0; if ( pCurrLower->Lower() && pCurrLower->Lower()->IsRowFrame() ) { const SwFrame* pRow = pCurrLower->GetLastLower(); nTmpBottomLineDist = lcl_GetBottomLineDist( *static_cast(pRow) ); } else { const SwAttrSet& rSet = const_cast(pCurrLower)->GetFormat()->GetAttrSet(); const SvxBoxItem& rBoxItem = rSet.GetBox(); nTmpBottomLineDist = rBoxItem.GetDistance( SvxBoxItemLine::BOTTOM ); } nBottomLineDist = std::max( nBottomLineDist, nTmpBottomLineDist ); } return nBottomLineDist; } // tdf#104425: calculate the height of all row frames, // for which this frame is a follow. // When a row has fixed/minimum height, it may span over // several pages. The minimal height on this page should // take into account the sum of all the heights of previous // frames that constitute the table row on previous pages. // Otherwise, trying to split a too high row frame will // result in loop trying to create that too high row // on each following page static SwTwips lcl_calcHeightOfRowBeforeThisFrame(const SwRowFrame& rRow) { // We don't need to account for previous instances of repeated headlines if (rRow.IsRepeatedHeadline()) return 0; SwRectFnSet aRectFnSet(&rRow); const SwTableLine* pLine = rRow.GetTabLine(); const SwTabFrame* pTab = rRow.FindTabFrame(); if (!pLine || !pTab || !pTab->IsFollow()) return 0; SwTwips nResult = 0; SwIterator aIter(*pLine->GetFrameFormat()); for (const SwRowFrame* pCurRow = aIter.First(); pCurRow; pCurRow = aIter.Next()) { if (pCurRow != &rRow && pCurRow->GetTabLine() == pLine) { // We've found another row frame that is part of the same table row const SwTabFrame* pCurTab = pCurRow->FindTabFrame(); // A row frame may not belong to a table frame, when it is being cut, e.g., in // lcl_PostprocessRowsInCells(). // Its SwRowFrame::Cut() has been called; it in turn called SwLayoutFrame::Cut(), // which nullified row's upper in RemoveFromLayout(), and then called Shrink() // for its former upper. // Regardless of whether it will be pasted back, or destroyed, currently it's not // part of layout, and its height does not count if (pCurTab && pCurTab->IsAnFollow(pTab)) { // The found row frame belongs to a table frame that precedes // (above) this one in chain. So, include it in the sum nResult += aRectFnSet.GetHeight(pCurRow->getFrameArea()); } } } return nResult; } void SwRowFrame::Format( vcl::RenderContext* /*pRenderContext*/, const SwBorderAttrs *pAttrs ) { SwRectFnSet aRectFnSet(this); OSL_ENSURE( pAttrs, "SwRowFrame::Format without Attrs." ); const bool bFix = mbFixSize; if ( !isFramePrintAreaValid() ) { // RowFrames don't have borders/margins therefore the PrintArea always // matches the FrameArea. setFramePrintAreaValid(true); { SwFrameAreaDefinition::FramePrintAreaWriteAccess aPrt(*this); aPrt.Left( 0 ); aPrt.Top( 0 ); aPrt.Width ( getFrameArea().Width() ); aPrt.Height( getFrameArea().Height() ); } // #i29550# // Here we calculate the top-printing area for the lower cell frames SwTabFrame* pTabFrame = FindTabFrame(); if ( pTabFrame->IsCollapsingBorders() ) { const sal_uInt16 nTopSpace = lcl_GetTopSpace( *this ); const sal_uInt16 nTopLineDist = lcl_GetTopLineDist( *this ); const sal_uInt16 nBottomLineSize = lcl_GetBottomLineSize( *this ); const sal_uInt16 nBottomLineDist = lcl_GetBottomLineDist( *this ); const SwRowFrame* pPreviousRow = nullptr; // #i32456# // In order to calculate the top printing area for the lower cell // frames, we have to find the 'previous' row frame and compare // the bottom values of the 'previous' row with the 'top' values // of this row. The best way to find the 'previous' row is to // use the table structure: const SwTable* pTable = pTabFrame->GetTable(); const SwTableLine* pPrevTabLine = nullptr; const SwRowFrame* pTmpRow = this; while ( pTmpRow && !pPrevTabLine ) { size_t nIdx = 0; const SwTableLines& rLines = pTmpRow->GetTabLine()->GetUpper() ? pTmpRow->GetTabLine()->GetUpper()->GetTabLines() : pTable->GetTabLines(); while ( rLines[ nIdx ] != pTmpRow->GetTabLine() ) ++nIdx; if ( nIdx > 0 ) { // pTmpRow has a 'previous' row in the table structure: pPrevTabLine = rLines[ nIdx - 1 ]; } else { // pTmpRow is a first row in the table structure. // We go up in the table structure: pTmpRow = pTmpRow->GetUpper()->GetUpper() && pTmpRow->GetUpper()->GetUpper()->IsRowFrame() ? static_cast( pTmpRow->GetUpper()->GetUpper() ) : nullptr; } } // If we found a 'previous' row, we look for the appropriate row frame: if ( pPrevTabLine ) { SwIterator aIter( *pPrevTabLine->GetFrameFormat() ); for ( SwRowFrame* pRow = aIter.First(); pRow; pRow = aIter.Next() ) { // #115759# - do *not* take repeated // headlines, because during split of table it can be // invalid and thus can't provide correct border values. if ( pRow->GetTabLine() == pPrevTabLine && !pRow->IsRepeatedHeadline() ) { pPreviousRow = pRow; break; } } } sal_uInt16 nTopPrtMargin = nTopSpace; if ( pPreviousRow ) { const sal_uInt16 nTmpPrtMargin = pPreviousRow->GetBottomLineSize() + nTopLineDist; if ( nTmpPrtMargin > nTopPrtMargin ) nTopPrtMargin = nTmpPrtMargin; } // table has to be notified if it has to change its lower // margin due to changes of nBottomLineSize: if ( !GetNext() && nBottomLineSize != GetBottomLineSize() ) pTabFrame->InvalidatePrt_(); // If there are rows nested inside this row, the nested rows // may not have been calculated yet. Therefore the // ::lcl_CalcMinRowHeight( this ) operation later in this // function cannot consider the correct border values. We // have to trigger the invalidation of the outer row frame // manually: // Note: If any further invalidations should be necessary, we // should consider moving the invalidation stuff to the // appropriate SwNotify object. if ( GetUpper()->GetUpper()->IsRowFrame() && ( nBottomLineDist != GetBottomMarginForLowers() || nTopPrtMargin != GetTopMarginForLowers() ) ) GetUpper()->GetUpper()->InvalidateSize_(); SetBottomMarginForLowers( nBottomLineDist ); // 3. SetBottomLineSize( nBottomLineSize ); // 4. SetTopMarginForLowers( nTopPrtMargin ); // 5. } } while ( !isFrameAreaSizeValid() ) { setFrameAreaSizeValid(true); #if OSL_DEBUG_LEVEL > 0 if ( HasFixSize() ) { const SwFormatFrameSize &rFrameSize = GetFormat()->GetFrameSize(); OSL_ENSURE( rFrameSize.GetSize().Height() > 0, "Has it" ); } #endif const SwTwips nDiff = aRectFnSet.GetHeight(getFrameArea()) - ( HasFixSize() && !IsRowSpanLine() ? pAttrs->GetSize().Height() // #i26945# : ::lcl_CalcMinRowHeight( this, FindTabFrame()->IsConsiderObjsForMinCellHeight() ) ); if ( nDiff ) { mbFixSize = false; if ( nDiff > 0 ) Shrink( nDiff, false, true ); else if ( nDiff < 0 ) Grow( -nDiff ); mbFixSize = bFix; } } // last row will fill the space in its upper. if ( !GetNext() ) { //The last fills the remaining space in the upper. SwTwips nDiff = aRectFnSet.GetHeight(GetUpper()->getFramePrintArea()); SwFrame *pSibling = GetUpper()->Lower(); do { nDiff -= aRectFnSet.GetHeight(pSibling->getFrameArea()); pSibling = pSibling->GetNext(); } while ( pSibling ); if ( nDiff > 0 ) { mbFixSize = false; Grow( nDiff ); mbFixSize = bFix; setFrameAreaSizeValid(true); } } } void SwRowFrame::AdjustCells( const SwTwips nHeight, const bool bHeight ) { SwFrame *pFrame = Lower(); if ( bHeight ) { SwRootFrame *pRootFrame = getRootFrame(); SwRectFnSet aRectFnSet(this); SwRect aOldFrame; while ( pFrame ) { SwFrame* pNotify = nullptr; SwCellFrame* pCellFrame = static_cast(pFrame); // NEW TABLES // Which cells need to be adjusted if the current row changes // its height? // Current frame is a covered frame: // Set new height for covered cell and adjust master cell: if ( pCellFrame->GetTabBox()->getRowSpan() < 1 ) { // Set height of current (covered) cell to new line height. const long nDiff = nHeight - aRectFnSet.GetHeight(pCellFrame->getFrameArea()); if ( nDiff ) { { SwFrameAreaDefinition::FrameAreaWriteAccess aFrm(*pCellFrame); aRectFnSet.AddBottom( aFrm, nDiff ); } pCellFrame->InvalidatePrt_(); } } SwCellFrame* pToAdjust = nullptr; SwFrame* pToAdjustRow = nullptr; // If current frame is covered frame, we still want to adjust the // height of the cell starting the row span if ( pCellFrame->GetLayoutRowSpan() < 1 ) { pToAdjust = const_cast< SwCellFrame*>(&pCellFrame->FindStartEndOfRowSpanCell( true )); pToAdjustRow = pToAdjust->GetUpper(); } else { pToAdjust = pCellFrame; pToAdjustRow = this; } // Set height of master cell to height of all lines spanned by this line. long nRowSpan = pToAdjust->GetLayoutRowSpan(); SwTwips nSumRowHeight = 0; while ( pToAdjustRow ) { // Use new height for the current row: nSumRowHeight += pToAdjustRow == this ? nHeight : aRectFnSet.GetHeight(pToAdjustRow->getFrameArea()); if ( nRowSpan-- == 1 ) break; pToAdjustRow = pToAdjustRow->GetNext(); } if ( pToAdjustRow && pToAdjustRow != this ) pToAdjustRow->InvalidateSize_(); const long nDiff = nSumRowHeight - aRectFnSet.GetHeight(pToAdjust->getFrameArea()); if ( nDiff ) { aOldFrame = pToAdjust->getFrameArea(); SwFrameAreaDefinition::FrameAreaWriteAccess aFrm(*pToAdjust); aRectFnSet.AddBottom( aFrm, nDiff ); pNotify = pToAdjust; } if ( pNotify ) { if( pRootFrame && pRootFrame->IsAnyShellAccessible() && pRootFrame->GetCurrShell() ) pRootFrame->GetCurrShell()->Imp()->MoveAccessibleFrame( pNotify, aOldFrame ); pNotify->InvalidatePrt_(); } pFrame = pFrame->GetNext(); } } else { while ( pFrame ) { pFrame->InvalidateAll_(); pFrame = pFrame->GetNext(); } } InvalidatePage(); } void SwRowFrame::Cut() { SwTabFrame *pTab = FindTabFrame(); if ( pTab && pTab->IsFollow() && this == pTab->GetFirstNonHeadlineRow() ) { pTab->FindMaster()->InvalidatePos(); } SwLayoutFrame::Cut(); } SwTwips SwRowFrame::GrowFrame( SwTwips nDist, bool bTst, bool bInfo ) { SwTwips nReal = 0; SwTabFrame* pTab = FindTabFrame(); SwRectFnSet aRectFnSet(pTab); bool bRestrictTableGrowth; bool bHasFollowFlowLine = pTab->HasFollowFlowLine(); if ( GetUpper()->IsTabFrame() ) { const SwRowFrame* pFollowFlowRow = IsInSplitTableRow(); bRestrictTableGrowth = pFollowFlowRow && !pFollowFlowRow->IsRowSpanLine(); } else { OSL_ENSURE( GetUpper()->IsCellFrame(), "RowFrame->GetUpper neither table nor cell" ); bRestrictTableGrowth = GetFollowRow() && bHasFollowFlowLine; OSL_ENSURE( !bRestrictTableGrowth || !GetNext(), "GetFollowRow for row frame that has a Next" ); // There may still be some space left in my direct upper: const SwTwips nAdditionalSpace = aRectFnSet.BottomDist( getFrameArea(), aRectFnSet.GetPrtBottom(*GetUpper()->GetUpper()) ); if ( bRestrictTableGrowth && nAdditionalSpace > 0 ) { nReal = std::min( nAdditionalSpace, nDist ); nDist -= nReal; if ( !bTst ) { SwFrameAreaDefinition::FrameAreaWriteAccess aFrm(*this); aRectFnSet.AddBottom( aFrm, nReal ); } } } if ( bRestrictTableGrowth ) pTab->SetRestrictTableGrowth( true ); else { // Ok, this looks like a hack, indeed, it is a hack. // If the current row frame is inside another cell frame, // and the current row frame has no follow, it should not // be allowed to grow. In fact, setting bRestrictTableGrowth // to 'false' does not work, because the surrounding RowFrame // would set this to 'true'. pTab->SetFollowFlowLine( false ); } nReal += SwLayoutFrame::GrowFrame( nDist, bTst, bInfo); pTab->SetRestrictTableGrowth( false ); pTab->SetFollowFlowLine( bHasFollowFlowLine ); //Update the height of the cells to the newest value. if ( !bTst ) { SwRectFnSet fnRectX(this); AdjustCells( fnRectX.GetHeight(getFramePrintArea()) + nReal, true ); if ( nReal ) SetCompletePaint(); } return nReal; } SwTwips SwRowFrame::ShrinkFrame( SwTwips nDist, bool bTst, bool bInfo ) { SwRectFnSet aRectFnSet(this); if( HasFixSize() ) { AdjustCells( aRectFnSet.GetHeight(getFramePrintArea()), true ); return 0; } // bInfo may be set to true by SwRowFrame::Format; we need to handle this // here accordingly const bool bShrinkAnyway = bInfo; //Only shrink as much as the content of the biggest cell allows. SwTwips nRealDist = nDist; SwFormat* pMod = GetFormat(); if (pMod) { const SwFormatFrameSize &rSz = pMod->GetFrameSize(); SwTwips nMinHeight = 0; if (rSz.GetHeightSizeType() == SwFrameSize::Minimum) nMinHeight = std::max(rSz.GetHeight() - lcl_calcHeightOfRowBeforeThisFrame(*this), 0L); // Only necessary to calculate minimal row height if height // of pRow is at least nMinHeight. Otherwise nMinHeight is the // minimum height. if( nMinHeight < aRectFnSet.GetHeight(getFrameArea()) ) { // #i26945# OSL_ENSURE( FindTabFrame(), " - no table frame -> crash." ); const bool bConsiderObjs( FindTabFrame()->IsConsiderObjsForMinCellHeight() ); nMinHeight = lcl_CalcMinRowHeight( this, bConsiderObjs ); } if ( (aRectFnSet.GetHeight(getFrameArea()) - nRealDist) < nMinHeight ) nRealDist = aRectFnSet.GetHeight(getFrameArea()) - nMinHeight; } if ( nRealDist < 0 ) nRealDist = 0; SwTwips nReal = nRealDist; if ( nReal ) { if ( !bTst ) { SwTwips nHeight = aRectFnSet.GetHeight(getFrameArea()); SwFrameAreaDefinition::FrameAreaWriteAccess aFrm(*this); aRectFnSet.SetHeight( aFrm, nHeight - nReal ); if( IsVertical() && !IsVertLR() ) { aFrm.Pos().AdjustX(nReal ); } } SwLayoutFrame* pFrame = GetUpper(); SwTwips nTmp = pFrame ? pFrame->Shrink(nReal, bTst) : 0; if ( !bShrinkAnyway && !GetNext() && nTmp != nReal ) { //The last one gets the leftover in the upper and therefore takes //care (otherwise: endless loop) if ( !bTst ) { nReal -= nTmp; SwTwips nHeight = aRectFnSet.GetHeight(getFrameArea()); SwFrameAreaDefinition::FrameAreaWriteAccess aFrm(*this); aRectFnSet.SetHeight( aFrm, nHeight + nReal ); if( IsVertical() && !IsVertLR() ) { aFrm.Pos().AdjustX( -nReal ); } } nReal = nTmp; } } // Invalidate appropriately and update the height to the newest value. if ( !bTst ) { if ( nReal ) { if ( GetNext() ) GetNext()->InvalidatePos_(); InvalidateAll_(); SetCompletePaint(); SwTabFrame *pTab = FindTabFrame(); if ( !pTab->IsRebuildLastLine() && pTab->IsFollow() && this == pTab->GetFirstNonHeadlineRow() && !pTab->IsInRecalcLowerRow() ) { SwTabFrame* pMasterTab = pTab->FindMaster(); pMasterTab->InvalidatePos(); } } AdjustCells( aRectFnSet.GetHeight(getFramePrintArea()) - nReal, true ); } return nReal; } bool SwRowFrame::IsRowSplitAllowed() const { // Fixed size rows are never allowed to split: if ( HasFixSize() ) { OSL_ENSURE( SwFrameSize::Fixed == GetFormat()->GetFrameSize().GetHeightSizeType(), "pRow claims to have fixed size" ); return false; } // Repeated headlines are never allowed to split: const SwTabFrame* pTabFrame = FindTabFrame(); if ( pTabFrame->GetTable()->GetRowsToRepeat() > 0 && pTabFrame->IsInHeadline( *this ) ) return false; const SwTableLineFormat* pFrameFormat = static_cast(GetTabLine()->GetFrameFormat()); const SwFormatRowSplit& rLP = pFrameFormat->GetRowSplit(); return rLP.GetValue(); } bool SwRowFrame::ShouldRowKeepWithNext( const bool bCheckParents ) const { // No KeepWithNext if nested in another table if ( GetUpper()->GetUpper()->IsCellFrame() ) return false; const SwCellFrame* pCell = static_cast(Lower()); const SwFrame* pText = pCell->Lower(); return pText && pText->IsTextFrame() && static_cast(pText)->GetTextNodeForParaProps()->GetSwAttrSet().GetKeep(bCheckParents).GetValue(); } SwCellFrame::SwCellFrame(const SwTableBox &rBox, SwFrame* pSib, bool bInsertContent) : SwLayoutFrame( rBox.GetFrameFormat(), pSib ) , m_pTabBox( &rBox ) { mnFrameType = SwFrameType::Cell; if ( !bInsertContent ) return; //If a StartIdx is available, ContentFrames are added in the cell, otherwise //Rows have to be present and those are added. if ( rBox.GetSttIdx() ) { sal_uLong nIndex = rBox.GetSttIdx(); ::InsertCnt_( this, rBox.GetFrameFormat()->GetDoc(), ++nIndex ); } else { const SwTableLines &rLines = rBox.GetTabLines(); SwFrame *pTmpPrev = nullptr; for ( size_t i = 0; i < rLines.size(); ++i ) { SwRowFrame *pNew = new SwRowFrame( *rLines[i], this, bInsertContent ); pNew->InsertBehind( this, pTmpPrev ); pTmpPrev = pNew; } } } void SwCellFrame::DestroyImpl() { SwModify* pMod = GetFormat(); if( pMod ) { // At this stage the lower frames aren't destroyed already, // therefore we have to do a recursive dispose. SwRootFrame *pRootFrame = getRootFrame(); if( pRootFrame && pRootFrame->IsAnyShellAccessible() && pRootFrame->GetCurrShell() ) { pRootFrame->GetCurrShell()->Imp()->DisposeAccessibleFrame( this, true ); } pMod->Remove( this ); if( !pMod->HasWriterListeners() ) delete pMod; } SwLayoutFrame::DestroyImpl(); } SwCellFrame::~SwCellFrame() { } static bool lcl_ArrangeLowers( SwLayoutFrame *pLay, long lYStart, bool bInva ) { bool bRet = false; SwFrame *pFrame = pLay->Lower(); SwRectFnSet aRectFnSet(pLay); while ( pFrame ) { long nFrameTop = aRectFnSet.GetTop(pFrame->getFrameArea()); if( nFrameTop != lYStart ) { bRet = true; const long lDiff = aRectFnSet.YDiff( lYStart, nFrameTop ); const long lDiffX = lYStart - nFrameTop; { SwFrameAreaDefinition::FrameAreaWriteAccess aFrm(*pFrame); aRectFnSet.SubTop( aFrm, -lDiff ); aRectFnSet.AddBottom( aFrm, lDiff ); } pFrame->SetCompletePaint(); if ( !pFrame->GetNext() ) pFrame->SetRetouche(); if( bInva ) pFrame->Prepare( PrepareHint::FramePositionChanged ); if ( pFrame->IsLayoutFrame() && static_cast(pFrame)->Lower() ) lcl_ArrangeLowers( static_cast(pFrame), aRectFnSet.GetTop(static_cast(pFrame)->Lower()->getFrameArea()) + lDiffX, bInva ); if ( pFrame->GetDrawObjs() ) { for ( size_t i = 0; i < pFrame->GetDrawObjs()->size(); ++i ) { SwAnchoredObject* pAnchoredObj = (*pFrame->GetDrawObjs())[i]; // #i26945# - check, if anchored object // is lower of layout frame by checking, if the anchor // frame, which contains the anchor position, is a lower // of the layout frame. if ( !pLay->IsAnLower( pAnchoredObj->GetAnchorFrameContainingAnchPos() ) ) { continue; } // #i52904# - distinguish between anchored // objects, whose vertical position depends on its anchor // frame and whose vertical position is independent // from its anchor frame. bool bVertPosDepOnAnchor( true ); { SwFormatVertOrient aVert( pAnchoredObj->GetFrameFormat().GetVertOrient() ); switch ( aVert.GetRelationOrient() ) { case text::RelOrientation::PAGE_FRAME: case text::RelOrientation::PAGE_PRINT_AREA: bVertPosDepOnAnchor = false; break; default: break; } } if ( dynamic_cast< const SwFlyFrame *>( pAnchoredObj ) != nullptr ) { SwFlyFrame *pFly = static_cast(pAnchoredObj); // OD 2004-05-18 #i28701# - no direct move of objects, // which are anchored to-paragraph/to-character, if // the wrapping style influence has to be considered // on the object positioning. // #i52904# - no direct move of objects, // whose vertical position doesn't depend on anchor frame. const bool bDirectMove = FAR_AWAY != pFly->getFrameArea().Top() && bVertPosDepOnAnchor && !pFly->ConsiderObjWrapInfluenceOnObjPos(); if ( bDirectMove ) { { SwFrameAreaDefinition::FrameAreaWriteAccess aFrm(*pFly); aRectFnSet.SubTop( aFrm, -lDiff ); aRectFnSet.AddBottom( aFrm, lDiff ); } pFly->GetVirtDrawObj()->SetRectsDirty(); // --> OD 2004-08-17 - also notify view of // instance, which represents the Writer fly frame in // the drawing layer pFly->GetVirtDrawObj()->SetChanged(); // #i58280# pFly->InvalidateObjRectWithSpaces(); } if ( pFly->IsFlyInContentFrame() ) { static_cast(pFly)->AddRefOfst( lDiff ); // #115759# - reset current relative // position to get re-positioned, if not directly moved. if ( !bDirectMove ) { pAnchoredObj->SetCurrRelPos( Point( 0, 0 ) ); } } else if( pFly->IsAutoPos() ) { pFly->AddLastCharY( lDiff ); // OD 2004-05-18 #i28701# - follow-up of #i22341# // has also been adjusted. pFly->AddLastTopOfLineY( lDiff ); } // #i26945# - re-registration at // page frame of anchor frame, if table frame isn't // a follow table and table frame isn't in its // rebuild of last line. const SwTabFrame* pTabFrame = pLay->FindTabFrame(); // - save: check, if table frame is found. if ( pTabFrame && !( pTabFrame->IsFollow() && pTabFrame->FindMaster()->IsRebuildLastLine() ) && pFly->IsFlyFreeFrame() ) { SwPageFrame* pPageFrame = pFly->GetPageFrame(); SwPageFrame* pPageOfAnchor = pFrame->FindPageFrame(); if ( pPageFrame != pPageOfAnchor ) { pFly->InvalidatePos(); if ( pPageFrame ) pPageFrame->MoveFly( pFly, pPageOfAnchor ); else pPageOfAnchor->AppendFlyToPage( pFly ); } } // OD 2004-05-11 #i28701# - Because of the introduction // of new positionings and alignments (e.g. aligned at // page area, but anchored at-character), the position // of the Writer fly frame has to be invalidated. pFly->InvalidatePos(); // #i26945# - follow-up of #i3317# // No arrangement of lowers, if Writer fly frame isn't // moved if ( bDirectMove && ::lcl_ArrangeLowers( pFly, aRectFnSet.GetPrtTop(*pFly), bInva ) ) { pFly->SetCompletePaint(); } } else if ( dynamic_cast< const SwAnchoredDrawObject *>( pAnchoredObj ) != nullptr ) { // #i26945# const SwTabFrame* pTabFrame = pLay->FindTabFrame(); if ( pTabFrame && !( pTabFrame->IsFollow() && pTabFrame->FindMaster()->IsRebuildLastLine() ) && (pAnchoredObj->GetFrameFormat().GetAnchor().GetAnchorId() != RndStdIds::FLY_AS_CHAR)) { SwPageFrame* pPageFrame = pAnchoredObj->GetPageFrame(); SwPageFrame* pPageOfAnchor = pFrame->FindPageFrame(); if ( pPageFrame != pPageOfAnchor ) { pAnchoredObj->InvalidateObjPos(); if ( pPageFrame ) { pPageFrame->RemoveDrawObjFromPage( *pAnchoredObj ); } pPageOfAnchor->AppendDrawObjToPage( *pAnchoredObj ); } } // #i28701# - adjust last character // rectangle and last top of line. pAnchoredObj->AddLastCharY( lDiff ); pAnchoredObj->AddLastTopOfLineY( lDiff ); // #i52904# - re-introduce direct move // of drawing objects const bool bDirectMove = static_cast(pAnchoredObj->GetFrameFormat()).IsPosAttrSet() && bVertPosDepOnAnchor && !pAnchoredObj->ConsiderObjWrapInfluenceOnObjPos(); if ( bDirectMove ) { SwObjPositioningInProgress aObjPosInProgress( *pAnchoredObj ); if ( aRectFnSet.IsVert() ) { pAnchoredObj->DrawObj()->Move( Size( lDiff, 0 ) ); } else { pAnchoredObj->DrawObj()->Move( Size( 0, lDiff ) ); } // #i58280# pAnchoredObj->InvalidateObjRectWithSpaces(); } pAnchoredObj->InvalidateObjPos(); } else { OSL_FAIL( " - unknown type of anchored object!" ); } } } } // Columns and cells are ordered horizontal, not vertical if( !pFrame->IsColumnFrame() && !pFrame->IsCellFrame() ) lYStart = aRectFnSet.YInc( lYStart, aRectFnSet.GetHeight(pFrame->getFrameArea()) ); // Nowadays, the content inside a cell can flow into the follow table. // Thus, the cell may only grow up to the end of the environment. // So the content may have grown, but the cell could not grow. // Therefore we have to trigger a formatting for the frames, which do // not fit into the cell anymore: SwTwips nDistanceToUpperPrtBottom = aRectFnSet.BottomDist( pFrame->getFrameArea(), aRectFnSet.GetPrtBottom(*pLay) ); // #i56146# - Revise fix of issue #i26945# // do *not* consider content inside fly frames, if it's an undersized paragraph. // #i26945# - consider content inside fly frames if ( nDistanceToUpperPrtBottom < 0 && ( ( pFrame->IsInFly() && ( !pFrame->IsTextFrame() || !static_cast(pFrame)->IsUndersized() ) ) || pFrame->IsInSplitTableRow() ) ) { pFrame->InvalidatePos(); } pFrame = pFrame->GetNext(); } return bRet; } void SwCellFrame::Format( vcl::RenderContext* /*pRenderContext*/, const SwBorderAttrs *pAttrs ) { OSL_ENSURE( pAttrs, "CellFrame::Format, pAttrs is 0." ); const SwTabFrame* pTab = FindTabFrame(); SwRectFnSet aRectFnSet(pTab); if ( !isFramePrintAreaValid() ) { setFramePrintAreaValid(true); //Adjust position. if ( Lower() ) { SwTwips nTopSpace, nBottomSpace, nLeftSpace, nRightSpace; // #i29550# if ( pTab->IsCollapsingBorders() && !Lower()->IsRowFrame() ) { const SvxBoxItem& rBoxItem = pAttrs->GetBox(); nLeftSpace = rBoxItem.GetDistance( SvxBoxItemLine::LEFT ); nRightSpace = rBoxItem.GetDistance( SvxBoxItemLine::RIGHT ); nTopSpace = static_cast(GetUpper())->GetTopMarginForLowers(); nBottomSpace = static_cast(GetUpper())->GetBottomMarginForLowers(); } else { // OD 23.01.2003 #106895# - add 1st param to nLeftSpace = pAttrs->CalcLeft( this ); nRightSpace = pAttrs->CalcRight( this ); nTopSpace = pAttrs->CalcTop(); nBottomSpace = pAttrs->CalcBottom(); } aRectFnSet.SetXMargins( *this, nLeftSpace, nRightSpace ); aRectFnSet.SetYMargins( *this, nTopSpace, nBottomSpace ); } } // #i26945# long nRemaining = GetTabBox()->getRowSpan() >= 1 ? ::lcl_CalcMinCellHeight( this, pTab->IsConsiderObjsForMinCellHeight(), pAttrs ) : 0; if ( !isFrameAreaSizeValid() ) { setFrameAreaSizeValid(true); //The VarSize of the CellFrames is always the width. //The width is not variable though, it is defined by the format. //This predefined value however does not necessary match the actual //width. The width is calculated based on the attribute, the value in //the attribute matches the desired value of the TabFrame. Changes which //were done there are taken into account here proportionately. //If the cell doesn't have a neighbour anymore, it does not take the //attribute into account and takes the rest of the upper instead. SwTwips nWidth; if ( GetNext() ) { const SwTwips nWish = pTab->GetFormat()->GetFrameSize().GetWidth(); nWidth = pAttrs->GetSize().Width(); OSL_ENSURE( nWish, "Table without width?" ); OSL_ENSURE( nWidth <= nWish, "Width of cell larger than table." ); OSL_ENSURE( nWidth > 0, "Box without width" ); const long nPrtWidth = aRectFnSet.GetWidth(pTab->getFramePrintArea()); if ( nWish != nPrtWidth ) { // Avoid rounding problems, at least for the new table model if ( pTab->GetTable()->IsNewModel() ) { // 1. sum of widths of cells up to this cell (in model) const SwTableLine* pTabLine = GetTabBox()->GetUpper(); const SwTableBoxes& rBoxes = pTabLine->GetTabBoxes(); const SwTableBox* pTmpBox = nullptr; SwTwips nSumWidth = 0; size_t i = 0; do { pTmpBox = rBoxes[ i++ ]; nSumWidth += pTmpBox->GetFrameFormat()->GetFrameSize().GetWidth(); } while ( pTmpBox != GetTabBox() ); // 2. calculate actual width of cells up to this one double nTmpWidth = nSumWidth; nTmpWidth *= nPrtWidth; nTmpWidth /= nWish; nWidth = static_cast(nTmpWidth); // 3. calculate frame widths of cells up to this one: const SwFrame* pTmpCell = static_cast(GetUpper())->Lower(); SwTwips nSumFrameWidths = 0; while ( pTmpCell != this ) { nSumFrameWidths += aRectFnSet.GetWidth(pTmpCell->getFrameArea()); pTmpCell = pTmpCell->GetNext(); } nWidth = nWidth - nSumFrameWidths; } else { // #i12092# use double instead of long, // otherwise this could lead to overflows double nTmpWidth = nWidth; nTmpWidth *= nPrtWidth; nTmpWidth /= nWish; nWidth = static_cast(nTmpWidth); } } } else { OSL_ENSURE( pAttrs->GetSize().Width() > 0, "Box without width" ); nWidth = aRectFnSet.GetWidth(GetUpper()->getFramePrintArea()); SwFrame *pPre = GetUpper()->Lower(); while ( pPre != this ) { nWidth -= aRectFnSet.GetWidth(pPre->getFrameArea()); pPre = pPre->GetNext(); } } const long nDiff = nWidth - aRectFnSet.GetWidth(getFrameArea()); { SwFrameAreaDefinition::FrameAreaWriteAccess aFrm(*this); if( IsNeighbourFrame() && IsRightToLeft() ) { aRectFnSet.SubLeft( aFrm, nDiff ); } else { aRectFnSet.AddRight( aFrm, nDiff ); } } { SwFrameAreaDefinition::FramePrintAreaWriteAccess aPrt(*this); aRectFnSet.AddRight( aPrt, nDiff ); } //Adjust the height, it's defined through the content and the margins. const long nDiffHeight = nRemaining - aRectFnSet.GetHeight(getFrameArea()); if ( nDiffHeight ) { if ( nDiffHeight > 0 ) { //Validate again if no growth happened. Invalidation is done //through AdjustCells of the row. if ( !Grow( nDiffHeight ) ) { setFrameAreaSizeValid(true); setFramePrintAreaValid(true); } } else { // Only keep invalidated if shrinking was actually done; the // attempt can be ignored because all horizontally adjoined // cells have to be the same height. if ( !Shrink( -nDiffHeight ) ) { setFrameAreaSizeValid(true); setFramePrintAreaValid(true); } } } } const SwFormatVertOrient &rOri = pAttrs->GetAttrSet().GetVertOrient(); if ( !Lower() ) return; // From now on, all operations are related to the table cell. aRectFnSet.Refresh(this); SwPageFrame* pPg = nullptr; if ( !FindTabFrame()->IsRebuildLastLine() && text::VertOrientation::NONE != rOri.GetVertOrient() && // #158225# no vertical alignment of covered cells !IsCoveredCell() && (pPg = FindPageFrame())!=nullptr ) { if ( !Lower()->IsContentFrame() && !Lower()->IsSctFrame() && !Lower()->IsTabFrame() ) { // OSL_ENSURE(for HTML-import! OSL_ENSURE( false, "VAlign to cell without content" ); return; } bool bVertDir = true; // #i43913# - no vertical alignment, if wrapping // style influence is considered on object positioning and // an object is anchored inside the cell. const bool bConsiderWrapOnObjPos( GetFormat()->getIDocumentSettingAccess().get(DocumentSettingId::CONSIDER_WRAP_ON_OBJECT_POSITION) ); // No alignment if fly with wrap overlaps the cell. if ( pPg->GetSortedObjs() ) { SwRect aRect( getFramePrintArea() ); aRect += getFrameArea().Pos(); for (SwAnchoredObject* pAnchoredObj : *pPg->GetSortedObjs()) { SwRect aTmp( pAnchoredObj->GetObjRect() ); const SwFrame* pAnch = pAnchoredObj->GetAnchorFrame(); if ( (bConsiderWrapOnObjPos && IsAnLower( pAnch )) || (!bConsiderWrapOnObjPos && aTmp.IsOver( aRect )) ) { const SwFrameFormat& rAnchoredObjFrameFormat = pAnchoredObj->GetFrameFormat(); const SwFormatSurround &rSur = rAnchoredObjFrameFormat.GetSurround(); if ( bConsiderWrapOnObjPos || css::text::WrapTextMode_THROUGH != rSur.GetSurround() ) { // frames, which the cell is a lower of, aren't relevant if ( auto pFly = dynamic_cast< const SwFlyFrame *>( pAnchoredObj ) ) { if ( pFly->IsAnLower( this ) ) continue; } // #i43913# // #i52904# - no vertical alignment, // if object, anchored inside cell, has temporarily // consider its wrapping style on object positioning. // #i58806# - no vertical alignment // if object does not follow the text flow. if ( bConsiderWrapOnObjPos || !IsAnLower( pAnch ) || pAnchoredObj->IsTmpConsiderWrapInfluence() || !rAnchoredObjFrameFormat.GetFollowTextFlow().GetValue() ) { bVertDir = false; break; } } } } } long nPrtHeight = aRectFnSet.GetHeight(getFramePrintArea()); if( ( bVertDir && ( nRemaining -= lcl_CalcTopAndBottomMargin( *this, *pAttrs ) ) < nPrtHeight ) || aRectFnSet.GetTop(Lower()->getFrameArea()) != aRectFnSet.GetPrtTop(*this) ) { long nDiff = aRectFnSet.GetHeight(getFramePrintArea()) - nRemaining; if ( nDiff >= 0 ) { long lTopOfst = 0; if ( bVertDir ) { switch ( rOri.GetVertOrient() ) { case text::VertOrientation::CENTER: lTopOfst = nDiff / 2; break; case text::VertOrientation::BOTTOM: lTopOfst = nDiff; break; default: break; } } long nTmp = aRectFnSet.YInc( aRectFnSet.GetPrtTop(*this), lTopOfst ); if ( lcl_ArrangeLowers( this, nTmp, !bVertDir ) ) SetCompletePaint(); } } } else { //Was an old alignment taken into account? if ( Lower()->IsContentFrame() ) { const long lYStart = aRectFnSet.GetPrtTop(*this); lcl_ArrangeLowers( this, lYStart, true ); } } // Handle rotated portions of lowers: it's possible that we have changed amount of vertical // space since the last format, and this affects how many rotated portions we need. So throw // away the current portions to build them using the new line width. for (SwFrame* pFrame = Lower(); pFrame; pFrame = pFrame->GetNext()) { if (!pFrame->IsTextFrame()) { continue; } auto pTextFrame = static_cast(pFrame); if (!pTextFrame->GetHasRotatedPortions()) { continue; } pTextFrame->Prepare(); } } void SwCellFrame::Modify( const SfxPoolItem* pOld, const SfxPoolItem * pNew ) { bool bAttrSetChg = pNew && RES_ATTRSET_CHG == pNew->Which(); const SfxPoolItem *pItem = nullptr; if( bAttrSetChg ) static_cast(pNew)->GetChgSet()->GetItemState( RES_VERT_ORIENT, false, &pItem); else if (pNew && RES_VERT_ORIENT == pNew->Which()) pItem = pNew; if ( pItem ) { bool bInva = true; if ( text::VertOrientation::NONE == static_cast(pItem)->GetVertOrient() && // OD 04.11.2003 #112910# Lower() && Lower()->IsContentFrame() ) { SwRectFnSet aRectFnSet(this); const long lYStart = aRectFnSet.GetPrtTop(*this); bInva = lcl_ArrangeLowers( this, lYStart, false ); } if ( bInva ) { SetCompletePaint(); InvalidatePrt(); } } if ( ( bAttrSetChg && SfxItemState::SET == static_cast(pNew)->GetChgSet()->GetItemState( RES_PROTECT, false ) ) || ( pNew && RES_PROTECT == pNew->Which()) ) { SwViewShell *pSh = getRootFrame()->GetCurrShell(); if( pSh && pSh->GetLayout()->IsAnyShellAccessible() ) pSh->Imp()->InvalidateAccessibleEditableState( true, this ); } if ( bAttrSetChg && SfxItemState::SET == static_cast(pNew)->GetChgSet()->GetItemState( RES_FRAMEDIR, false, &pItem ) ) { SetDerivedVert( false ); CheckDirChange(); } // #i29550# if ( bAttrSetChg && SfxItemState::SET == static_cast(pNew)->GetChgSet()->GetItemState( RES_BOX, false, &pItem ) ) { SwFrame* pTmpUpper = GetUpper(); while ( pTmpUpper->GetUpper() && !pTmpUpper->GetUpper()->IsTabFrame() ) pTmpUpper = pTmpUpper->GetUpper(); SwTabFrame* pTabFrame = static_cast(pTmpUpper->GetUpper()); if ( pTabFrame->IsCollapsingBorders() ) { // Invalidate lowers of this and next row: lcl_InvalidateAllLowersPrt( static_cast(pTmpUpper) ); pTmpUpper = pTmpUpper->GetNext(); if ( pTmpUpper ) lcl_InvalidateAllLowersPrt( static_cast(pTmpUpper) ); else pTabFrame->InvalidatePrt(); } } SwLayoutFrame::Modify( pOld, pNew ); } long SwCellFrame::GetLayoutRowSpan() const { long nRet = GetTabBox()->getRowSpan(); if ( nRet < 1 ) { const SwFrame* pRow = GetUpper(); const SwTabFrame* pTab = pRow ? static_cast(pRow->GetUpper()) : nullptr; if ( pTab && pTab->IsFollow() && pRow == pTab->GetFirstNonHeadlineRow() ) nRet = -nRet; } return nRet; } void SwCellFrame::dumpAsXmlAttributes(xmlTextWriterPtr pWriter) const { SwFrame::dumpAsXmlAttributes(pWriter); if (SwCellFrame* pFollow = GetFollowCell()) xmlTextWriterWriteFormatAttribute(pWriter, BAD_CAST("follow"), "%" SAL_PRIuUINT32, pFollow->GetFrameId()); if (SwCellFrame* pPrevious = GetPreviousCell()) xmlTextWriterWriteFormatAttribute(pWriter, BAD_CAST("precede"), "%" SAL_PRIuUINT32, pPrevious->GetFrameId()); } // #i103961# void SwCellFrame::Cut() { // notification for accessibility { SwRootFrame *pRootFrame = getRootFrame(); if( pRootFrame && pRootFrame->IsAnyShellAccessible() ) { SwViewShell* pVSh = pRootFrame->GetCurrShell(); if ( pVSh && pVSh->Imp() ) { pVSh->Imp()->DisposeAccessibleFrame( this ); } } } SwLayoutFrame::Cut(); } // Helper functions for repeated headlines: bool SwTabFrame::IsInHeadline( const SwFrame& rFrame ) const { OSL_ENSURE( IsAnLower( &rFrame ) && rFrame.IsInTab(), "SwTabFrame::IsInHeadline called for frame not lower of table" ); const SwFrame* pTmp = &rFrame; while ( !pTmp->GetUpper()->IsTabFrame() ) pTmp = pTmp->GetUpper(); return GetTable()->IsHeadline( *static_cast(pTmp)->GetTabLine() ); } /* * If this is a master table, we can may assume, that there are at least * nRepeat lines in the table. * If this is a follow table, there are intermediate states for the table * layout, e.g., during deletion of rows, which makes it necessary to find * the first non-headline row by evaluating the headline flag at the row frame. */ SwRowFrame* SwTabFrame::GetFirstNonHeadlineRow() const { SwRowFrame* pRet = const_cast(static_cast(Lower())); if ( pRet ) { if ( IsFollow() ) { while ( pRet && pRet->IsRepeatedHeadline() ) pRet = static_cast(pRet->GetNext()); } else { sal_uInt16 nRepeat = GetTable()->GetRowsToRepeat(); while ( pRet && nRepeat > 0 ) { pRet = static_cast(pRet->GetNext()); --nRepeat; } } } return pRet; } bool SwTable::IsHeadline( const SwTableLine& rLine ) const { for ( sal_uInt16 i = 0; i < GetRowsToRepeat(); ++i ) if ( GetTabLines()[ i ] == &rLine ) return true; return false; } bool SwTabFrame::IsLayoutSplitAllowed() const { return GetFormat()->GetLayoutSplit().GetValue(); } // #i29550# sal_uInt16 SwTabFrame::GetBottomLineSize() const { OSL_ENSURE( IsCollapsingBorders(), "BottomLineSize only required for collapsing borders" ); OSL_ENSURE( Lower(), "Warning! Trying to prevent a crash" ); const SwFrame* pTmp = GetLastLower(); // #124755# Try to make code robust if ( !pTmp ) return 0; return static_cast(pTmp)->GetBottomLineSize(); } bool SwTabFrame::IsCollapsingBorders() const { return GetFormat()->GetAttrSet().Get( RES_COLLAPSING_BORDERS ).GetValue(); } /// Local helper function to calculate height of first text row static SwTwips lcl_CalcHeightOfFirstContentLine( const SwRowFrame& rSourceLine ) { // Find corresponding split line in master table const SwTabFrame* pTab = rSourceLine.FindTabFrame(); SwRectFnSet aRectFnSet(pTab); const SwCellFrame* pCurrSourceCell = static_cast(rSourceLine.Lower()); // 1. Case: rSourceLine is a follow flow line. // In this case we have to return the minimum of the heights // of the first lines in rSourceLine. // 2. Case: rSourceLine is not a follow flow line. // In this case we have to return the maximum of the heights // of the first lines in rSourceLine. bool bIsInFollowFlowLine = rSourceLine.IsInFollowFlowRow(); SwTwips nHeight = bIsInFollowFlowLine ? LONG_MAX : 0; while ( pCurrSourceCell ) { // NEW TABLES // Skip cells which are not responsible for the height of // the follow flow line: if ( bIsInFollowFlowLine && pCurrSourceCell->GetLayoutRowSpan() > 1 ) { pCurrSourceCell = static_cast(pCurrSourceCell->GetNext()); continue; } const SwFrame *pTmp = pCurrSourceCell->Lower(); if ( pTmp ) { SwTwips nTmpHeight = USHRT_MAX; // #i32456# Consider lower row frames if ( pTmp->IsRowFrame() ) { const SwRowFrame* pTmpSourceRow = static_cast(pCurrSourceCell->Lower()); nTmpHeight = lcl_CalcHeightOfFirstContentLine( *pTmpSourceRow ); } else if ( pTmp->IsTabFrame() ) { nTmpHeight = static_cast(pTmp)->CalcHeightOfFirstContentLine(); } else if (pTmp->IsTextFrame() || (pTmp->IsSctFrame() && pTmp->GetLower() && pTmp->GetLower()->IsTextFrame())) { // Section frames don't influence the size/position of text // frames, so 'text frame' and 'text frame in section frame' is // the same case. SwTextFrame* pTextFrame = nullptr; if (pTmp->IsTextFrame()) pTextFrame = const_cast(static_cast(pTmp)); else pTextFrame = const_cast(static_cast(pTmp->GetLower())); pTextFrame->GetFormatted(); nTmpHeight = pTextFrame->FirstLineHeight(); } if ( USHRT_MAX != nTmpHeight ) { const SwCellFrame* pPrevCell = pCurrSourceCell->GetPreviousCell(); if ( pPrevCell ) { // If we are in a split row, there may be some space // left in the cell frame of the master row. // We look for the minimum of all first line heights; SwTwips nReal = aRectFnSet.GetHeight(pPrevCell->getFramePrintArea()); const SwFrame* pFrame = pPrevCell->Lower(); const SwFrame* pLast = pFrame; while ( pFrame ) { nReal -= aRectFnSet.GetHeight(pFrame->getFrameArea()); pLast = pFrame; pFrame = pFrame->GetNext(); } // #i26831#, #i26520# // The additional lower space of the current last. // #115759# - do *not* consider the // additional lower space for 'master' text frames if ( pLast && pLast->IsFlowFrame() && ( !pLast->IsTextFrame() || !static_cast(pLast)->GetFollow() ) ) { nReal += SwFlowFrame::CastFlowFrame(pLast)->CalcAddLowerSpaceAsLastInTableCell(); } // Don't forget the upper space and lower space, // #115759# - do *not* consider the upper // and the lower space for follow text frames. if ( pTmp->IsFlowFrame() && ( !pTmp->IsTextFrame() || !static_cast(pTmp)->IsFollow() ) ) { nTmpHeight += SwFlowFrame::CastFlowFrame(pTmp)->CalcUpperSpace( nullptr, pLast); nTmpHeight += SwFlowFrame::CastFlowFrame(pTmp)->CalcLowerSpace(); } // #115759# - consider additional lower // space of , if contains only one line. // In this case it would be the new last text frame, which // would have no follow and thus would add this space. if ( pTmp->IsTextFrame() && const_cast(static_cast(pTmp)) ->GetLineCount(TextFrameIndex(COMPLETE_STRING)) == 1) { nTmpHeight += SwFlowFrame::CastFlowFrame(pTmp) ->CalcAddLowerSpaceAsLastInTableCell(); } if ( nReal > 0 ) nTmpHeight -= nReal; } else { // pFirstRow is not a FollowFlowRow. In this case, // we look for the maximum of all first line heights: SwBorderAttrAccess aAccess( SwFrame::GetCache(), pCurrSourceCell ); const SwBorderAttrs &rAttrs = *aAccess.Get(); nTmpHeight += rAttrs.CalcTop() + rAttrs.CalcBottom(); // #i26250# // Don't forget the upper space and lower space, if ( pTmp->IsFlowFrame() ) { nTmpHeight += SwFlowFrame::CastFlowFrame(pTmp)->CalcUpperSpace(); nTmpHeight += SwFlowFrame::CastFlowFrame(pTmp)->CalcLowerSpace(); } } } if ( bIsInFollowFlowLine ) { // minimum if ( nTmpHeight < nHeight ) nHeight = nTmpHeight; } else { // maximum if ( nTmpHeight > nHeight && USHRT_MAX != nTmpHeight ) nHeight = nTmpHeight; } } pCurrSourceCell = static_cast(pCurrSourceCell->GetNext()); } return ( LONG_MAX == nHeight ) ? 0 : nHeight; } /// Function to calculate height of first text row SwTwips SwTabFrame::CalcHeightOfFirstContentLine() const { SwRectFnSet aRectFnSet(this); const bool bDontSplit = !IsFollow() && !GetFormat()->GetLayoutSplit().GetValue(); if ( bDontSplit ) { // Table is not allowed to split: Take the whole height, that's all return aRectFnSet.GetHeight(getFrameArea()); } SwTwips nTmpHeight = 0; const SwRowFrame* pFirstRow = GetFirstNonHeadlineRow(); OSL_ENSURE( !IsFollow() || pFirstRow, "FollowTable without Lower" ); // NEW TABLES if ( pFirstRow && pFirstRow->IsRowSpanLine() && pFirstRow->GetNext() ) pFirstRow = static_cast(pFirstRow->GetNext()); // Calculate the height of the headlines: const sal_uInt16 nRepeat = GetTable()->GetRowsToRepeat(); SwTwips nRepeatHeight = nRepeat ? lcl_GetHeightOfRows( GetLower(), nRepeat ) : 0; // Calculate the height of the keeping lines // (headlines + following keeping lines): SwTwips nKeepHeight = nRepeatHeight; if ( GetFormat()->GetDoc()->GetDocumentSettingManager().get(DocumentSettingId::TABLE_ROW_KEEP) ) { sal_uInt16 nKeepRows = nRepeat; // Check how many rows want to keep together while ( pFirstRow && pFirstRow->ShouldRowKeepWithNext() ) { ++nKeepRows; pFirstRow = static_cast(pFirstRow->GetNext()); } if ( nKeepRows > nRepeat ) nKeepHeight = lcl_GetHeightOfRows( GetLower(), nKeepRows ); } // For master tables, the height of the headlines + the height of the // keeping lines (if any) has to be considered. For follow tables, we // only consider the height of the keeping rows without the repeated lines: if ( !IsFollow() ) { nTmpHeight = nKeepHeight; } else { nTmpHeight = nKeepHeight - nRepeatHeight; } // pFirstRow row is the first non-heading row. // nTmpHeight is the height of the heading row if we are a follow. if ( pFirstRow ) { const bool bSplittable = pFirstRow->IsRowSplitAllowed(); const SwTwips nFirstLineHeight = aRectFnSet.GetHeight(pFirstRow->getFrameArea()); if ( !bSplittable ) { // pFirstRow is not splittable, but it is still possible that the line height of pFirstRow // actually is determined by a lower cell with rowspan = -1. In this case we should not // just return the height of the first line. Basically we need to get the height of the // line as it would be on the last page. Since this is quite complicated to calculate, // we only calculate the height of the first line. SwFormatFrameSize const& rFrameSize(pFirstRow->GetAttrSet()->GetFrameSize()); if ( pFirstRow->GetPrev() && static_cast(pFirstRow->GetPrev())->IsRowSpanLine() && rFrameSize.GetHeightSizeType() != SwFrameSize::Fixed) { // Calculate maximum height of all cells with rowspan = 1: SwTwips nMaxHeight = rFrameSize.GetHeightSizeType() == SwFrameSize::Minimum ? rFrameSize.GetHeight() : 0; const SwCellFrame* pLower2 = static_cast(pFirstRow->Lower()); while ( pLower2 ) { if ( 1 == pLower2->GetTabBox()->getRowSpan() ) { const SwTwips nCellHeight = lcl_CalcMinCellHeight( pLower2, true ); nMaxHeight = std::max( nCellHeight, nMaxHeight ); } pLower2 = static_cast(pLower2->GetNext()); } nTmpHeight += nMaxHeight; } else { nTmpHeight += nFirstLineHeight; } } // Optimization: lcl_CalcHeightOfFirstContentLine actually can trigger // a formatting of the row frame (via the GetFormatted()). We don't // want this formatting if the row does not have a height. else if ( 0 != nFirstLineHeight ) { const bool bOldJoinLock = IsJoinLocked(); const_cast(this)->LockJoin(); const SwTwips nHeightOfFirstContentLine = lcl_CalcHeightOfFirstContentLine( *pFirstRow ); // Consider minimum row height: const SwFormatFrameSize &rSz = pFirstRow->GetFormat()->GetFrameSize(); SwTwips nMinRowHeight = 0; if (rSz.GetHeightSizeType() == SwFrameSize::Minimum) { nMinRowHeight = std::max(rSz.GetHeight() - lcl_calcHeightOfRowBeforeThisFrame(*pFirstRow), 0L); } nTmpHeight += std::max( nHeightOfFirstContentLine, nMinRowHeight ); if ( !bOldJoinLock ) const_cast(this)->UnlockJoin(); } } return nTmpHeight; } // Some more functions for covered/covering cells. This way inclusion of // SwCellFrame can be avoided bool SwFrame::IsLeaveUpperAllowed() const { return false; } bool SwCellFrame::IsLeaveUpperAllowed() const { return GetLayoutRowSpan() > 1; } bool SwFrame::IsCoveredCell() const { return false; } bool SwCellFrame::IsCoveredCell() const { return GetLayoutRowSpan() < 1; } bool SwFrame::IsInCoveredCell() const { bool bRet = false; const SwFrame* pThis = this; while ( pThis && !pThis->IsCellFrame() ) pThis = pThis->GetUpper(); if ( pThis ) bRet = pThis->IsCoveredCell(); return bRet; } /* vim:set shiftwidth=4 softtabstop=4 expandtab: */