/* -*- 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 // Move methods /// Return value tells whether the Frame should be moved. bool SwContentFrame::ShouldBwdMoved( SwLayoutFrame *pNewUpper, bool, bool & ) { if ( SwFlowFrame::IsMoveBwdJump() || !IsPrevObjMove() ) { // Floating back a frm uses a bit of time unfortunately. // The most common case is the following: The Frame wants to float to // somewhere where the FixSize is the same that the Frame itself has already. // In that case it's pretty easy to check if the Frame has enough space // for its VarSize. If this is NOT the case, we already know that // we don't need to move. // The Frame checks itself whether it has enough space - respecting the fact // that it could possibly split itself if needed. // If, however, the FixSize differs from the Frame or Flys are involved // (either in the old or the new position), checking is pointless, // and we have to move the Frame just to see what happens - if there's // some space available to do it, that is. // The FixSize of the containers of Contents is always the width. // If we moved more than one sheet back (for example jumping over empty // pages), we have to move either way. Otherwise, if the Frame doesn't fit // into the page, empty pages wouldn't be respected anymore. sal_uInt8 nMoveAnyway = 0; SwPageFrame * const pNewPage = pNewUpper->FindPageFrame(); SwPageFrame *pOldPage = FindPageFrame(); if ( SwFlowFrame::IsMoveBwdJump() ) return true; if( IsInFootnote() && IsInSct() ) { SwFootnoteFrame* pFootnote = FindFootnoteFrame(); SwSectionFrame* pMySect = pFootnote->FindSctFrame(); if( pMySect && pMySect->IsFootnoteLock() ) { SwSectionFrame *pSect = pNewUpper->FindSctFrame(); while( pSect && pSect->IsInFootnote() ) pSect = pSect->GetUpper()->FindSctFrame(); OSL_ENSURE( pSect, "Escaping footnote" ); if( pSect != pMySect ) return false; } } SwRectFnSet aRectFnSet(this); SwRectFnSet fnRectX(pNewUpper); if( std::abs( fnRectX.GetWidth(pNewUpper->getFramePrintArea()) - aRectFnSet.GetWidth(GetUpper()->getFramePrintArea()) ) > 1 ) { // In this case, only a WouldFit_ with test move is possible nMoveAnyway = 2; } // Do *not* move backward, if equals 3 and no space is left in new upper. nMoveAnyway |= BwdMoveNecessary( pOldPage, getFrameArea() ); { const IDocumentSettingAccess& rIDSA = pNewPage->GetFormat()->getIDocumentSettingAccess(); SwTwips nSpace = 0; SwRect aRect( pNewUpper->getFramePrintArea() ); aRect.Pos() += pNewUpper->getFrameArea().Pos(); const SwFrame *pPrevFrame = pNewUpper->Lower(); while ( pPrevFrame ) { SwTwips nNewTop = fnRectX.GetBottom(pPrevFrame->getFrameArea()); // Consider lower spacing of last frame in a table cell { // Check if last frame is inside table and if it includes its lower spacing. if ( !pPrevFrame->GetNext() && pPrevFrame->IsInTab() && rIDSA.get(DocumentSettingId::ADD_PARA_SPACING_TO_TABLE_CELLS) ) { const SwFrame* pLastFrame = pPrevFrame; // if last frame is a section, take its last content if ( pPrevFrame->IsSctFrame() ) { pLastFrame = static_cast(pPrevFrame)->FindLastContent(); if ( pLastFrame && pLastFrame->FindTabFrame() != pPrevFrame->FindTabFrame() ) { pLastFrame = pLastFrame->FindTabFrame(); } } if ( pLastFrame ) { SwBorderAttrAccess aAccess( SwFrame::GetCache(), pLastFrame ); const SwBorderAttrs& rAttrs = *aAccess.Get(); nNewTop -= rAttrs.GetULSpace().GetLower(); } } } fnRectX.SetTop( aRect, nNewTop ); pPrevFrame = pPrevFrame->GetNext(); } nMoveAnyway |= BwdMoveNecessary( pNewPage, aRect); //determine space left in new upper frame nSpace = fnRectX.GetHeight(aRect); const SwViewShell *pSh = pNewUpper->getRootFrame()->GetCurrShell(); if ( IsInFootnote() || (pSh && pSh->GetViewOptions()->getBrowseMode()) || pNewUpper->IsCellFrame() || ( pNewUpper->IsInSct() && ( pNewUpper->IsSctFrame() || ( pNewUpper->IsColBodyFrame() && !pNewUpper->GetUpper()->GetPrev() && !pNewUpper->GetUpper()->GetNext() ) ) ) ) nSpace += pNewUpper->Grow( LONG_MAX, true ); if ( nMoveAnyway < 3 ) { if ( nSpace ) { // Do not notify footnotes which are stuck to the paragraph: // This would require extremely confusing code, taking into // account the widths // and Flys, that in turn influence the footnotes, ... // WouldFit_ can only be used if the width is the same and // ONLY self-anchored Flys are present. // WouldFit_ can also be used if ONLY Flys anchored // somewhere else are present. // In this case, the width doesn't even matter, // because we're running a TestFormat in the new upper. const sal_uInt8 nBwdMoveNecessaryResult = BwdMoveNecessary( pNewPage, aRect); const bool bObjsInNewUpper( nBwdMoveNecessaryResult == 2 || nBwdMoveNecessaryResult == 3 ); return WouldFit_( nSpace, pNewUpper, nMoveAnyway == 2, bObjsInNewUpper ); } // It's impossible for WouldFit_ to return a usable result if // we have a fresh multi-column section - so we really have to // float back unless there is no space. return pNewUpper->IsInSct() && pNewUpper->IsColBodyFrame() && !fnRectX.GetWidth(pNewUpper->getFramePrintArea()) && ( pNewUpper->GetUpper()->GetPrev() || pNewUpper->GetUpper()->GetNext() ); } // Check for space left in new upper return nSpace != 0; } } return false; } // Calc methods // Two little friendships form a secret society inline void PrepareLock( SwFlowFrame *pTab ) { pTab->LockJoin(); } inline void PrepareUnlock( SwFlowFrame *pTab ) { pTab->UnlockJoin(); } // hopefully, one day this function simply will return 'false' static bool lcl_IsCalcUpperAllowed( const SwFrame& rFrame ) { return !rFrame.GetUpper()->IsSctFrame() && !rFrame.GetUpper()->IsFooterFrame() && // No format of upper Writer fly frame !rFrame.GetUpper()->IsFlyFrame() && !( rFrame.GetUpper()->IsTabFrame() && rFrame.GetUpper()->GetUpper()->IsInTab() ) && !( rFrame.IsTabFrame() && rFrame.GetUpper()->IsInTab() ); } /** Prepares the Frame for "formatting" (MakeAll()). * * This method serves to save stack space: To calculate the position of the Frame * we have to make sure that the positions of Upper and Prev respectively are * valid. This may require a recursive call (a loop would be quite expensive, * as it's not required very often). * * Every call of MakeAll requires around 500 bytes on the stack - you easily * see where this leads to. This method requires only a little bit of stack * space, so the recursive call should not be a problem here. * * Another advantage is that one nice day, this method and with it the * formatting of predecessors could be avoided. Then it could probably be * possible to jump "quickly" to the document's end. * * @see MakeAll() */ void SwFrame::PrepareMake(vcl::RenderContext* pRenderContext) { StackHack aHack; if ( GetUpper() ) { SwFrameDeleteGuard aDeleteGuard(this); if ( lcl_IsCalcUpperAllowed( *this ) ) GetUpper()->Calc(pRenderContext); OSL_ENSURE( GetUpper(), ":-( Layout unstable (Upper gone)." ); if ( !GetUpper() ) return; const bool bCnt = IsContentFrame(); const bool bTab = IsTabFrame(); bool bNoSect = IsInSct(); bool bOldTabLock = false, bFoll = false; SwFlowFrame* pThis = bCnt ? static_cast(this) : nullptr; if ( bTab ) { pThis = static_cast(this); bOldTabLock = static_cast(this)->IsJoinLocked(); ::PrepareLock( static_cast(this) ); bFoll = pThis->IsFollow(); } else if( IsSctFrame() ) { pThis = static_cast(this); bFoll = pThis->IsFollow(); bNoSect = false; } else if ( bCnt ) { bFoll = pThis->IsFollow(); if ( bFoll && GetPrev() ) { // Do not follow the chain when we need only one instance const SwTextFrame* pMaster = static_cast(this)->FindMaster(); if ( pMaster && pMaster->IsLocked() ) { MakeAll(pRenderContext); return; } } } // There is no format of previous frame, if current frame is a table // frame and its previous frame wants to keep with it. const bool bFormatPrev = !bTab || !GetPrev() || !GetPrev()->GetAttrSet()->GetKeep().GetValue(); if ( bFormatPrev ) { SwFrame *pFrame = GetUpper()->Lower(); while ( pFrame != this ) { OSL_ENSURE( pFrame, ":-( Layout unstable (this not found)." ); if ( !pFrame ) return; //Oioioioi ... if ( !pFrame->isFrameAreaDefinitionValid() ) { // A small interference that hopefully improves on the stability: // If I'm Follow AND neighbor of a Frame before me, it would delete // me when formatting. This as you can see could easily become a // confusing situation that we want to avoid. if ( bFoll && pFrame->IsFlowFrame() && SwFlowFrame::CastFlowFrame(pFrame)->IsAnFollow( pThis ) ) break; pFrame->MakeAll(pRenderContext); if( IsSctFrame() && !static_cast(this)->GetSection() ) break; } // With ContentFrames, the chain may be broken while walking through // it. Therefore we have to figure out the follower in a bit more // complicated way. However, I'll HAVE to get back to myself // sometime again. pFrame = pFrame->FindNext(); // If we started out in a SectionFrame, it might have happened that // we landed in a Section Follow via the MakeAll calls. // FindNext only gives us the SectionFrame, not it's content - we // won't find ourselves anymore! if( bNoSect && pFrame && pFrame->IsSctFrame() ) { SwFrame* pCnt = static_cast(pFrame)->ContainsAny(); if( pCnt ) pFrame = pCnt; } } OSL_ENSURE( GetUpper(), "Layout unstable (Upper gone II)." ); if ( !GetUpper() ) return; if ( lcl_IsCalcUpperAllowed( *this ) ) GetUpper()->Calc(pRenderContext); OSL_ENSURE( GetUpper(), "Layout unstable (Upper gone III)." ); } if ( bTab && !bOldTabLock ) ::PrepareUnlock( static_cast(this) ); } MakeAll(pRenderContext); } void SwFrame::OptPrepareMake() { // #i23129#, #i36347# - no format of upper Writer fly frame if ( GetUpper() && !GetUpper()->IsFooterFrame() && !GetUpper()->IsFlyFrame() ) { { SwFrameDeleteGuard aDeleteGuard(this); GetUpper()->Calc(getRootFrame()->GetCurrShell() ? getRootFrame()->GetCurrShell()->GetOut() : nullptr); } OSL_ENSURE( GetUpper(), ":-( Layout unstable (Upper gone)." ); if ( !GetUpper() ) return; } if ( GetPrev() && !GetPrev()->isFrameAreaDefinitionValid() ) { PrepareMake(getRootFrame()->GetCurrShell() ? getRootFrame()->GetCurrShell()->GetOut() : nullptr); } else { StackHack aHack; MakeAll(IsRootFrame() ? nullptr : getRootFrame()->GetCurrShell()->GetOut()); } } void SwFrame::PrepareCursor() { StackHack aHack; if( GetUpper() && !GetUpper()->IsSctFrame() ) { GetUpper()->PrepareCursor(); GetUpper()->Calc(getRootFrame()->GetCurrShell() ? getRootFrame()->GetCurrShell()->GetOut() : nullptr); OSL_ENSURE( GetUpper(), ":-( Layout unstable (Upper gone)." ); if ( !GetUpper() ) return; const bool bCnt = IsContentFrame(); const bool bTab = IsTabFrame(); bool bNoSect = IsInSct(); bool bOldTabLock = false, bFoll; SwFlowFrame* pThis = bCnt ? static_cast(this) : nullptr; if ( bTab ) { bOldTabLock = static_cast(this)->IsJoinLocked(); ::PrepareLock( static_cast(this) ); pThis = static_cast(this); } else if( IsSctFrame() ) { pThis = static_cast(this); bNoSect = false; } bFoll = pThis && pThis->IsFollow(); SwFrame *pFrame = GetUpper()->Lower(); while ( pFrame != this ) { OSL_ENSURE( pFrame, ":-( Layout unstable (this not found)." ); if ( !pFrame ) return; //Oioioioi ... if ( !pFrame->isFrameAreaDefinitionValid() ) { // A small interference that hopefully improves on the stability: // If I'm Follow AND neighbor of a Frame before me, it would delete // me when formatting. This as you can see could easily become a // confusing situation that we want to avoid. if ( bFoll && pFrame->IsFlowFrame() && SwFlowFrame::CastFlowFrame(pFrame)->IsAnFollow( pThis ) ) break; pFrame->MakeAll(getRootFrame()->GetCurrShell()->GetOut()); } // With ContentFrames, the chain may be broken while walking through // it. Therefore we have to figure out the follower in a bit more // complicated way. However, I'll HAVE to get back to myself // sometime again. pFrame = pFrame->FindNext(); if( bNoSect && pFrame && pFrame->IsSctFrame() ) { SwFrame* pCnt = static_cast(pFrame)->ContainsAny(); if( pCnt ) pFrame = pCnt; } } OSL_ENSURE( GetUpper(), "Layout unstable (Upper gone II)." ); if ( !GetUpper() ) return; GetUpper()->Calc(getRootFrame()->GetCurrShell()->GetOut()); OSL_ENSURE( GetUpper(), "Layout unstable (Upper gone III)." ); if ( bTab && !bOldTabLock ) ::PrepareUnlock( static_cast(this) ); } Calc(getRootFrame()->GetCurrShell() ? getRootFrame()->GetCurrShell()->GetOut() : nullptr); } // Here we return GetPrev(); however we will ignore empty SectionFrames static SwFrame* lcl_Prev( SwFrame* pFrame, bool bSectPrv = true ) { SwFrame* pRet = pFrame->GetPrev(); if( !pRet && pFrame->GetUpper() && pFrame->GetUpper()->IsSctFrame() && bSectPrv && !pFrame->IsColumnFrame() ) pRet = pFrame->GetUpper()->GetPrev(); while( pRet && pRet->IsSctFrame() && !static_cast(pRet)->GetSection() ) pRet = pRet->GetPrev(); return pRet; } static SwFrame* lcl_NotHiddenPrev( SwFrame* pFrame ) { SwFrame *pRet = pFrame; do { pRet = lcl_Prev( pRet ); } while ( pRet && pRet->IsTextFrame() && static_cast(pRet)->IsHiddenNow() ); return pRet; } void SwFrame::MakePos() { if ( !isFrameAreaPositionValid() ) { setFrameAreaPositionValid(true); bool bUseUpper = false; SwFrame* pPrv = lcl_Prev( this ); if ( pPrv && ( !pPrv->IsContentFrame() || ( static_cast(pPrv)->GetFollow() != this ) ) ) { if ( !StackHack::IsLocked() && ( !IsInSct() || IsSctFrame() ) && !pPrv->IsSctFrame() && !pPrv->GetAttrSet()->GetKeep().GetValue() ) { pPrv->Calc(getRootFrame()->GetCurrShell() ? getRootFrame()->GetCurrShell()->GetOut() : nullptr); // This may cause Prev to vanish! } else if ( pPrv->getFrameArea().Top() == 0 ) { bUseUpper = true; } } pPrv = lcl_Prev( this, false ); const SwFrameType nMyType = GetType(); SwRectFnSet aRectFnSet((IsCellFrame() && GetUpper() ? GetUpper() : this)); if ( !bUseUpper && pPrv ) { SwFrameAreaDefinition::FrameAreaWriteAccess aFrm(*this); aFrm.Pos( pPrv->getFrameArea().Pos() ); if( FRM_NEIGHBOUR & nMyType ) { const bool bR2L = IsRightToLeft(); if( bR2L ) { aRectFnSet.SetPosX( aFrm, aRectFnSet.GetLeft(aFrm) - aRectFnSet.GetWidth(aFrm) ); } else { aRectFnSet.SetPosX( aFrm, aRectFnSet.GetLeft(aFrm) + aRectFnSet.GetWidth(pPrv->getFrameArea()) ); } // cells may now leave their uppers if( aRectFnSet.IsVert() && SwFrameType::Cell & nMyType ) { aFrm.Pos().setX(aFrm.Pos().getX() - aFrm.Width() + pPrv->getFrameArea().Width()); } } else if( aRectFnSet.IsVert() && FRM_NOTE_VERT & nMyType ) { if ( aRectFnSet.IsVertL2R() ) { aFrm.Pos().setX(aFrm.Pos().getX() + pPrv->getFrameArea().Width()); } else { aFrm.Pos().setX(aFrm.Pos().getX() - aFrm.Width()); } } else { aFrm.Pos().setY(aFrm.Pos().getY() + pPrv->getFrameArea().Height()); } } else if ( GetUpper() ) { // If parent frame is a footer frame and its , then // do *not* calculate it. // NOTE: Footer frame is during its // , which is called from , which // is called from , which is called from . // #i56850# // - no format of upper Writer fly frame, which is anchored // at-paragraph or at-character. if ( !GetUpper()->IsTabFrame() && !( IsTabFrame() && GetUpper()->IsInTab() ) && !GetUpper()->IsSctFrame() && !dynamic_cast(GetUpper()) && !( GetUpper()->IsFooterFrame() && GetUpper()->IsColLocked() ) ) { GetUpper()->Calc(getRootFrame()->GetCurrShell()->GetOut()); } pPrv = lcl_Prev( this, false ); if ( !bUseUpper && pPrv ) { SwFrameAreaDefinition::FrameAreaWriteAccess aFrm(*this); aFrm.Pos( pPrv->getFrameArea().Pos() ); if( FRM_NEIGHBOUR & nMyType ) { const bool bR2L = IsRightToLeft(); if( bR2L ) { aRectFnSet.SetPosX( aFrm, aRectFnSet.GetLeft(aFrm) - aRectFnSet.GetWidth(aFrm) ); } else { aRectFnSet.SetPosX( aFrm, aRectFnSet.GetLeft(aFrm) + aRectFnSet.GetWidth(pPrv->getFrameArea()) ); } // cells may now leave their uppers if( aRectFnSet.IsVert() && SwFrameType::Cell & nMyType ) { aFrm.Pos().setX(aFrm.Pos().getX() - aFrm.Width() + pPrv->getFrameArea().Width()); } } else if( aRectFnSet.IsVert() && FRM_NOTE_VERT & nMyType ) { aFrm.Pos().setX(aFrm.Pos().getX() - aFrm.Width()); } else { aFrm.Pos().setY(aFrm.Pos().getY() + pPrv->getFrameArea().Height()); } } else { SwFrameAreaDefinition::FrameAreaWriteAccess aFrm(*this); aFrm.Pos( GetUpper()->getFrameArea().Pos() ); if( GetUpper()->IsFlyFrame() ) { aFrm.Pos() += static_cast(GetUpper())->ContentPos(); } else { aFrm.Pos() += GetUpper()->getFramePrintArea().Pos(); } if( FRM_NEIGHBOUR & nMyType && IsRightToLeft() ) { if( aRectFnSet.IsVert() ) { aFrm.Pos().setY(aFrm.Pos().getY() + GetUpper()->getFramePrintArea().Height() - aFrm.Height()); } else { aFrm.Pos().setX(aFrm.Pos().getX() + GetUpper()->getFramePrintArea().Width() - aFrm.Width()); } } else if( aRectFnSet.IsVert() && !aRectFnSet.IsVertL2R() && FRM_NOTE_VERT & nMyType ) { aFrm.Pos().setX(aFrm.Pos().getX() - aFrm.Width() + GetUpper()->getFramePrintArea().Width()); } } } else { SwFrameAreaDefinition::FrameAreaWriteAccess aFrm(*this); aFrm.Pos().setX(0); aFrm.Pos().setY(0); } if( IsBodyFrame() && aRectFnSet.IsVert() && !aRectFnSet.IsVertL2R() && GetUpper() ) { SwFrameAreaDefinition::FrameAreaWriteAccess aFrm(*this); aFrm.Pos().setX(aFrm.Pos().getX() + GetUpper()->getFramePrintArea().Width() - aFrm.Width()); } setFrameAreaPositionValid(true); } } // #i28701# - new type static void lcl_CheckObjects(SwSortedObjs& rSortedObjs, const SwFrame* pFrame, long& rBot) { // And then there can be paragraph anchored frames that sit below their paragraph. long nMax = 0; for (SwAnchoredObject* pObj : rSortedObjs) { // #i28701# - consider changed type of // entries. long nTmp = 0; if ( dynamic_cast( pObj) != nullptr ) { SwFlyFrame *pFly = static_cast(pObj); if( pFly->getFrameArea().Top() != FAR_AWAY && ( pFrame->IsPageFrame() ? pFly->IsFlyLayFrame() : ( pFly->IsFlyAtContentFrame() && ( pFrame->IsBodyFrame() ? pFly->GetAnchorFrame()->IsInDocBody() : pFly->GetAnchorFrame()->IsInFootnote() ) ) ) ) { nTmp = pFly->getFrameArea().Bottom(); } } else nTmp = pObj->GetObjRect().Bottom(); nMax = std::max( nTmp, nMax ); } ++nMax; // Lower edge vs. height! rBot = std::max( rBot, nMax ); } size_t SwPageFrame::GetContentHeight(const long nTop, const long nBottom) const { OSL_ENSURE(!(FindBodyCont() && FindBodyCont()->Lower() && FindBodyCont()->Lower()->IsColumnFrame()), "SwPageFrame::GetContentHeight(): No support for columns."); // In pages without columns, the content defines the size. long nBot = getFrameArea().Top() + nTop; const SwFrame *pFrame = Lower(); while (pFrame) { long nTmp = 0; const SwFrame *pCnt = static_cast(pFrame)->ContainsAny(); while (pCnt && (pCnt->GetUpper() == pFrame || static_cast(pFrame)->IsAnLower(pCnt))) { nTmp += pCnt->getFrameArea().Height(); if (pCnt->IsTextFrame() && static_cast(pCnt)->IsUndersized()) { // This TextFrame would like to be a bit bigger. nTmp += static_cast(pCnt)->GetParHeight() - pCnt->getFramePrintArea().Height(); } else if (pCnt->IsSctFrame()) { // Grow if undersized, but don't shrink if oversized. const auto delta = static_cast(pCnt)->CalcUndersize(); if (delta > 0) nTmp += delta; } pCnt = pCnt->FindNext(); } // Consider invalid body frame properties if (pFrame->IsBodyFrame() && (!pFrame->isFrameAreaSizeValid() || !pFrame->isFramePrintAreaValid()) && (pFrame->getFrameArea().Height() < pFrame->getFramePrintArea().Height()) ) { nTmp = std::min(nTmp, pFrame->getFrameArea().Height()); } else { // Assert invalid lower property OSL_ENSURE(!(pFrame->getFrameArea().Height() < pFrame->getFramePrintArea().Height()), "SwPageFrame::GetContentHeight(): Lower with frame height < printing height"); nTmp += pFrame->getFrameArea().Height() - pFrame->getFramePrintArea().Height(); } if (!pFrame->IsBodyFrame()) nTmp = std::min(nTmp, pFrame->getFrameArea().Height()); nBot += nTmp; // Here we check whether paragraph anchored objects // protrude outside the Body/FootnoteCont. if (m_pSortedObjs && !pFrame->IsHeaderFrame() && !pFrame->IsFooterFrame()) lcl_CheckObjects(*m_pSortedObjs, pFrame, nBot); pFrame = pFrame->GetNext(); } nBot += nBottom; // And the page anchored ones if (m_pSortedObjs) lcl_CheckObjects(*m_pSortedObjs, this, nBot); nBot -= getFrameArea().Top(); return nBot; } void SwPageFrame::MakeAll(vcl::RenderContext* pRenderContext) { PROTOCOL_ENTER( this, PROT::MakeAll, DbgAction::NONE, nullptr ) const SwRect aOldRect( getFrameArea() ); // Adjust root size const SwLayNotify aNotify( this ); // takes care of the notification in the dtor std::unique_ptr pAccess; const SwBorderAttrs*pAttrs = nullptr; while ( !isFrameAreaPositionValid() || !isFrameAreaSizeValid() || !isFramePrintAreaValid() ) { if ( !isFrameAreaPositionValid() ) { setFrameAreaPositionValid(true); // positioning of the pages is taken care of by the root frame } if ( !isFrameAreaSizeValid() || !isFramePrintAreaValid() ) { if ( IsEmptyPage() ) { SwFrameAreaDefinition::FrameAreaWriteAccess aFrm(*this); aFrm.Width( 0 ); aFrm.Height( 0 ); SwFrameAreaDefinition::FramePrintAreaWriteAccess aPrt(*this); aPrt.Width( 0 ); aPrt.Height( 0 ); aPrt.Left( 0 ); aPrt.Top( 0 ); setFrameAreaSizeValid(true); setFramePrintAreaValid(true); } else { if (!pAccess) { pAccess = std::make_unique(SwFrame::GetCache(), this); pAttrs = pAccess->Get(); } assert(pAttrs); SwRootFrame* pRootFrame = getRootFrame(); SwViewShell* pSh = pRootFrame->GetCurrShell(); if (pSh && pSh->GetViewOptions()->getBrowseMode()) { // In BrowseView, we use fixed settings const Size aBorder = pRenderContext->PixelToLogic( pSh->GetBrowseBorder() ); const long nTop = pAttrs->CalcTopLine() + aBorder.Height(); const long nBottom = pAttrs->CalcBottomLine()+ aBorder.Height(); long nWidth = GetUpper() ? static_cast(GetUpper())->GetBrowseWidth() : 0; const auto nDefWidth = pSh->GetBrowseWidth(); if (nWidth < nDefWidth) nWidth = nDefWidth; nWidth += + 2 * aBorder.Width(); nWidth = std::max( nWidth, 2L * aBorder.Width() + 4*MM50 ); { SwFrameAreaDefinition::FrameAreaWriteAccess aFrm(*this); aFrm.Width( nWidth ); SwLayoutFrame *pBody = FindBodyCont(); if ( pBody && pBody->Lower() && pBody->Lower()->IsColumnFrame() ) { // Columns have a fixed height aFrm.Height( pAttrs->GetSize().Height() ); } else { // In pages without columns, the content defines the size. long nBot = GetContentHeight(nTop, nBottom); // #i35143# - If second page frame // exists, the first page doesn't have to fulfill the // visible area. if ( !GetPrev() && !GetNext() ) { nBot = std::max( nBot, pSh->VisArea().Height() ); } // #i35143# - Assure, that the page // doesn't exceed the defined browse height. aFrm.Height( std::min( nBot, BROWSE_HEIGHT ) ); } } { SwFrameAreaDefinition::FramePrintAreaWriteAccess aPrt(*this); aPrt.Left ( pAttrs->CalcLeftLine() + aBorder.Width() ); aPrt.Top ( nTop ); aPrt.Width( getFrameArea().Width() - ( aPrt.Left() + pAttrs->CalcRightLine() + aBorder.Width() ) ); aPrt.Height( getFrameArea().Height() - (nTop + nBottom) ); } setFrameAreaSizeValid(true); setFramePrintAreaValid(true); continue; } else if (pSh && pSh->GetViewOptions()->IsWhitespaceHidden() && pRootFrame->GetLastPage() != this) { long height = 0; SwLayoutFrame *pBody = FindBodyCont(); if ( pBody && pBody->Lower() && pBody->Lower()->IsColumnFrame() ) { // Columns have a fixed height height = pAttrs->GetSize().Height(); } else { // No need for borders. height = GetContentHeight(0, 0); } if (height > 0) { ChgSize(Size(getFrameArea().Width(), height)); SwFrameAreaDefinition::FramePrintAreaWriteAccess aPrt(*this); aPrt.Top(0); aPrt.Height(height); setFrameAreaSizeValid(true); setFramePrintAreaValid(true); continue; } // Fallback to default formatting. Especially relevant // when loading a doc when Hide Whitespace is enabled. // Heights are zero initially. } // Set FixSize. For pages, this is not done from Upper, but from // the attribute. //FIXME: This resets the size when (isFrameAreaSizeValid() && !isFramePrintAreaValid()). { SwFrameAreaDefinition::FrameAreaWriteAccess aFrm(*this); aFrm.SSize( pAttrs->GetSize() ); } Format( pRenderContext, pAttrs ); } } } //while ( !isFrameAreaPositionValid() || !isFrameAreaSizeValid() || !isFramePrintAreaValid() ) if ( getFrameArea() != aOldRect && GetUpper() ) static_cast(GetUpper())->CheckViewLayout( nullptr, nullptr ); OSL_ENSURE( !GetUpper() || GetUpper()->getFramePrintArea().Width() >= getFrameArea().Width(), "Upper (Root) must be wide enough to contain the widest page"); } void SwLayoutFrame::MakeAll(vcl::RenderContext* /*pRenderContext*/) { PROTOCOL_ENTER( this, PROT::MakeAll, DbgAction::NONE, nullptr ) // takes care of the notification in the dtor const SwLayNotify aNotify( this ); bool bVert = IsVertical(); SwRectFn fnRect = ( IsNeighbourFrame() == bVert )? fnRectHori : ( IsVertLR() ? fnRectVertL2R : fnRectVert ); std::unique_ptr pAccess; const SwBorderAttrs*pAttrs = nullptr; while ( !isFrameAreaPositionValid() || !isFrameAreaSizeValid() || !isFramePrintAreaValid() ) { if ( !isFrameAreaPositionValid() ) MakePos(); if ( GetUpper() ) { // NEW TABLES if ( IsLeaveUpperAllowed() ) { if ( !isFrameAreaSizeValid() ) { setFramePrintAreaValid(false); } } else { if ( !isFrameAreaSizeValid() ) { // Set FixSize; VarSize is set by Format() after calculating the PrtArea setFramePrintAreaValid(false); SwTwips nPrtWidth = (GetUpper()->getFramePrintArea().*fnRect->fnGetWidth)(); if( bVert && ( IsBodyFrame() || IsFootnoteContFrame() ) ) { SwFrame* pNxt = GetPrev(); while( pNxt && !pNxt->IsHeaderFrame() ) pNxt = pNxt->GetPrev(); if( pNxt ) nPrtWidth -= pNxt->getFrameArea().Height(); pNxt = GetNext(); while( pNxt && !pNxt->IsFooterFrame() ) pNxt = pNxt->GetNext(); if( pNxt ) nPrtWidth -= pNxt->getFrameArea().Height(); } const long nDiff = nPrtWidth - (getFrameArea().*fnRect->fnGetWidth)(); SwFrameAreaDefinition::FrameAreaWriteAccess aFrm(*this); if( IsNeighbourFrame() && IsRightToLeft() ) { (aFrm.*fnRect->fnSubLeft)( nDiff ); } else { (aFrm.*fnRect->fnAddRight)( nDiff ); } } else { // Don't leave your upper const SwTwips nDeadLine = (GetUpper()->*fnRect->fnGetPrtBottom)(); if( (getFrameArea().*fnRect->fnOverStep)( nDeadLine ) ) { setFrameAreaSizeValid(false); } } } } if ( !isFrameAreaSizeValid() || !isFramePrintAreaValid() ) { if ( !pAccess ) { pAccess = std::make_unique(SwFrame::GetCache(), this); pAttrs = pAccess->Get(); } Format( getRootFrame()->GetCurrShell()->GetOut(), pAttrs ); } } //while ( !isFrameAreaPositionValid() || !isFrameAreaSizeValid() || !isFramePrintAreaValid() ) } bool SwTextNode::IsCollapse() const { if (GetDoc()->GetDocumentSettingManager().get( DocumentSettingId::COLLAPSE_EMPTY_CELL_PARA ) && GetText().isEmpty()) { sal_uLong nIdx=GetIndex(); const SwEndNode *pNdBefore=GetNodes()[nIdx-1]->GetEndNode(); const SwEndNode *pNdAfter=GetNodes()[nIdx+1]->GetEndNode(); // The paragraph is collapsed only if the NdAfter is the end of a cell bool bInTable = FindTableNode( ) != nullptr; SwSortedObjs* pObjs = getLayoutFrame( GetDoc()->getIDocumentLayoutAccess().GetCurrentLayout() )->GetDrawObjs( ); const size_t nObjs = ( pObjs != nullptr ) ? pObjs->size( ) : 0; return pNdBefore!=nullptr && pNdAfter!=nullptr && nObjs == 0 && bInTable; } return false; } bool SwFrame::IsCollapse() const { if (!IsTextFrame()) return false; const SwTextFrame *pTextFrame = static_cast(this); const SwTextNode *pTextNode = pTextFrame->GetTextNodeForParaProps(); // TODO this SwTextNode function is pointless and should be merged in here return pTextFrame->GetText().isEmpty() && pTextNode && pTextNode->IsCollapse(); } void SwContentFrame::MakePrtArea( const SwBorderAttrs &rAttrs ) { if ( !isFramePrintAreaValid() ) { setFramePrintAreaValid(true); SwRectFnSet aRectFnSet(this); const bool bTextFrame = IsTextFrame(); SwTwips nUpper = 0; if ( bTextFrame && static_cast(this)->IsHiddenNow() ) { if ( static_cast(this)->HasFollow() ) static_cast(this)->JoinFrame(); if( aRectFnSet.GetHeight(getFramePrintArea()) ) { static_cast(this)->HideHidden(); } { SwFrameAreaDefinition::FramePrintAreaWriteAccess aPrt(*this); aPrt.Pos().setX(0); aPrt.Pos().setY(0); aRectFnSet.SetWidth( aPrt, aRectFnSet.GetWidth(getFrameArea()) ); aRectFnSet.SetHeight( aPrt, 0 ); } nUpper = -( aRectFnSet.GetHeight(getFrameArea()) ); } else { // Simplification: ContentFrames are always variable in height! // At the FixSize, the surrounding Frame enforces the size; // the borders are simply subtracted. const long nLeft = rAttrs.CalcLeft( this ); const long nRight = rAttrs.CalcRight( this ); aRectFnSet.SetXMargins( *this, nLeft, nRight ); SwViewShell *pSh = getRootFrame()->GetCurrShell(); SwTwips nWidthArea; if( pSh && 0!=(nWidthArea=aRectFnSet.GetWidth(pSh->VisArea())) && GetUpper()->IsPageBodyFrame() && // but not for BodyFrames in Columns pSh->GetViewOptions()->getBrowseMode() ) { // Do not protrude the edge of the visible area. The page may be // wider, because there may be objects with excess width // (RootFrame::ImplCalcBrowseWidth()) long nMinWidth = 0; for (size_t i = 0; GetDrawObjs() && i < GetDrawObjs()->size(); ++i) { // #i28701# - consider changed type of // entries SwAnchoredObject* pObj = (*GetDrawObjs())[i]; const SwFrameFormat& rFormat = pObj->GetFrameFormat(); const bool bFly = dynamic_cast( pObj) != nullptr; if ((bFly && (FAR_AWAY == pObj->GetObjRect().Width())) || rFormat.GetFrameSize().GetWidthPercent()) { continue; } if ( RndStdIds::FLY_AS_CHAR == rFormat.GetAnchor().GetAnchorId() ) { nMinWidth = std::max( nMinWidth, bFly ? rFormat.GetFrameSize().GetWidth() : pObj->GetObjRect().Width() ); } } const Size aBorder = pSh->GetOut()->PixelToLogic( pSh->GetBrowseBorder() ); long nWidth = nWidthArea - 2 * ( IsVertical() ? aBorder.Height() : aBorder.Width() ); nWidth -= aRectFnSet.GetLeft(getFramePrintArea()); nWidth -= rAttrs.CalcRightLine(); nWidth = std::max( nMinWidth, nWidth ); SwFrameAreaDefinition::FramePrintAreaWriteAccess aPrt(*this); aRectFnSet.SetWidth( aPrt, std::min( nWidth, aRectFnSet.GetWidth(aPrt) ) ); } if ( aRectFnSet.GetWidth(getFramePrintArea()) <= MINLAY ) { // The PrtArea should already be at least MINLAY wide, matching the // minimal values of the UI SwFrameAreaDefinition::FramePrintAreaWriteAccess aPrt(*this); aRectFnSet.SetWidth( aPrt, std::min( long(MINLAY), aRectFnSet.GetWidth(getFrameArea()) ) ); SwTwips nTmp = aRectFnSet.GetWidth(getFrameArea()) - aRectFnSet.GetWidth(aPrt); if( aRectFnSet.GetLeft(aPrt) > nTmp ) { aRectFnSet.SetLeft( aPrt, nTmp ); } } // The following rules apply for VarSize: // 1. The first entry of a chain has no top border // 2. There is never a bottom border // 3. The top border is the maximum of the distance // of Prev downwards and our own distance upwards // Those three rules apply when calculating spacings // that are given by UL- and LRSpace. There might be a spacing // in all directions however; this may be caused by borders // and / or shadows. // 4. The spacing for TextFrames corresponds to the interline lead, // at a minimum. nUpper = CalcUpperSpace( &rAttrs ); SwTwips nLower = CalcLowerSpace( &rAttrs ); if (IsCollapse()) { nUpper=0; nLower=0; } { SwFrameAreaDefinition::FramePrintAreaWriteAccess aPrt(*this); aRectFnSet.SetPosY( aPrt, !aRectFnSet.IsVert() ? nUpper : nLower); } nUpper += nLower; nUpper -= aRectFnSet.GetHeight(getFrameArea()) - aRectFnSet.GetHeight(getFramePrintArea()); } // If there's a difference between old and new size, call Grow() or // Shrink() respectively. if ( nUpper ) { if ( nUpper > 0 ) GrowFrame( nUpper ); else ShrinkFrame( -nUpper ); } } } #define STOP_FLY_FORMAT 10 // - loop prevention const int cnStopFormat = 15; inline void ValidateSz( SwFrame *pFrame ) { if ( pFrame ) { pFrame->setFrameAreaSizeValid(true); pFrame->setFramePrintAreaValid(true); } } void SwContentFrame::MakeAll(vcl::RenderContext* /*pRenderContext*/) { OSL_ENSURE( GetUpper(), "no Upper?" ); OSL_ENSURE( IsTextFrame(), "MakeAll(), NoText" ); if ( !IsFollow() && StackHack::IsLocked() ) return; if ( IsJoinLocked() ) return; OSL_ENSURE( !static_cast(this)->IsSwapped(), "Calculation of a swapped frame" ); StackHack aHack; if ( static_cast(this)->IsLocked() ) { OSL_FAIL( "Format for locked TextFrame." ); return; } auto xDeleteGuard = std::make_unique(this); LockJoin(); long nFormatCount = 0; // - loop prevention int nConsecutiveFormatsWithoutChange = 0; PROTOCOL_ENTER( this, PROT::MakeAll, DbgAction::NONE, nullptr ) // takes care of the notification in the dtor std::unique_ptr> pNotify(new SwContentNotify( 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; sal_Bool bFormatted = false; // For the widow/orphan rules, we encourage the // last ContentFrame of a chain to format. This only // needs to happen once. Every time the Frame is // moved, the flag will have to be reset. bool bMustFit = false; // Once the emergency brake is pulled, // no other prepares will be triggered bool bFitPromise = false; // If a paragraph didn't fit, but promises // with WouldFit that it would adjust accordingly, // this flag is set. If it turns out that it // didn't keep it's promise, we can act in a // controlled fashion. const bool bFly = IsInFly(); const bool bTab = IsInTab(); const bool bFootnote = IsInFootnote(); const bool bSct = IsInSct(); Point aOldFramePos; // This is so we can compare with the last pos Point aOldPrtPos; // and determine whether it makes sense to Prepare SwBorderAttrAccess aAccess( SwFrame::GetCache(), this ); const SwBorderAttrs &rAttrs = *aAccess.Get(); if ( !IsFollow() && rAttrs.JoinedWithPrev( *(this) ) ) { pNotify->SetBordersJoinedWithPrev(); } const bool bKeep = IsKeep(rAttrs.GetAttrSet().GetKeep(), GetBreakItem()); std::unique_ptr pSaveFootnote; if ( bFootnote ) { SwFootnoteFrame *pFootnote = FindFootnoteFrame(); SwSectionFrame* pSct = pFootnote->FindSctFrame(); if ( !static_cast(pFootnote->GetRef())->IsLocked() ) { SwFootnoteBossFrame* pBoss = pFootnote->GetRef()->FindFootnoteBossFrame( pFootnote->GetAttr()->GetFootnote().IsEndNote() ); if( !pSct || pSct->IsColLocked() || !pSct->Growable() ) pSaveFootnote.reset( new SwSaveFootnoteHeight( pBoss, static_cast(pFootnote->GetRef())->GetFootnoteLine( pFootnote->GetAttr() ) ) ); } } if ( GetUpper()->IsSctFrame() && HasFollow() && &GetFollow()->GetFrame() == GetNext() ) { dynamic_cast(*this).JoinFrame(); } // #i28701# - move master forward, if it has to move, // because of its object positioning. if ( !static_cast(this)->IsFollow() ) { sal_uInt32 nToPageNum = 0; const bool bMoveFwdByObjPos = SwLayouter::FrameMovedFwdByObjPos( *(GetAttrSet()->GetDoc()), *static_cast(this), nToPageNum ); // #i58182# // Also move a paragraph forward, which is the first one inside a table cell. if ( bMoveFwdByObjPos && FindPageFrame()->GetPhyPageNum() < nToPageNum && ( lcl_Prev( this ) || GetUpper()->IsCellFrame() || ( GetUpper()->IsSctFrame() && GetUpper()->GetUpper()->IsCellFrame() ) ) && IsMoveable() ) { bMovedFwd = true; MoveFwd( bMakePage, false ); } } // If a Follow sits next to its Master and doesn't fit, we know it can // be moved right now. if ( lcl_Prev( this ) && static_cast(this)->IsFollow() && IsMoveable() ) { bMovedFwd = true; // If follow frame is in table, its master will be the last in the // current table cell. Thus, invalidate the printing area of the master. if ( IsInTab() ) { lcl_Prev( this )->InvalidatePrt(); } MoveFwd( bMakePage, false ); } // Check footnote content for forward move. // If a content of a footnote is on a prior page/column as its invalid // reference, it can be moved forward. if ( bFootnote && !isFrameAreaPositionValid() ) { SwFootnoteFrame* pFootnote = FindFootnoteFrame(); SwContentFrame* pRefCnt = pFootnote ? pFootnote->GetRef() : nullptr; if ( pRefCnt && !pRefCnt->isFrameAreaDefinitionValid() ) { SwFootnoteBossFrame* pFootnoteBossOfFootnote = pFootnote->FindFootnoteBossFrame(); SwFootnoteBossFrame* pFootnoteBossOfRef = pRefCnt->FindFootnoteBossFrame(); // if ( pFootnoteBossOfFootnote && pFootnoteBossOfRef && pFootnoteBossOfFootnote != pFootnoteBossOfRef && pFootnoteBossOfFootnote->IsBefore( pFootnoteBossOfRef ) ) { bMovedFwd = true; MoveFwd( bMakePage, false ); } } } SwRectFnSet aRectFnSet(this); SwFrame const* pMoveBwdPre(nullptr); bool isMoveBwdPreValid(false); SwRect aOldFrame_StopFormat, aOldFrame_StopFormat2; SwRect aOldPrt_StopFormat, aOldPrt_StopFormat2; while ( !isFrameAreaPositionValid() || !isFrameAreaSizeValid() || !isFramePrintAreaValid() ) { // - loop prevention aOldFrame_StopFormat2 = aOldFrame_StopFormat; aOldPrt_StopFormat2 = aOldPrt_StopFormat; aOldFrame_StopFormat = getFrameArea(); aOldPrt_StopFormat = getFramePrintArea(); bool bMoveable = IsMoveable(); if (bMoveable) { SwFrame *pPre = GetIndPrev(); if ( CheckMoveFwd( bMakePage, bKeep, bMovedBwd ) ) { aRectFnSet.Refresh(this); bMovedFwd = true; if ( bMovedBwd ) { // While flowing back, the Upper was encouraged to // completely re-paint itself. We can skip this now after // flowing back and forth. GetUpper()->ResetCompletePaint(); // The predecessor was invalidated, so this is obsolete as well now. assert(pPre); if ((pPre == pMoveBwdPre && isMoveBwdPreValid) && !pPre->IsSctFrame()) ::ValidateSz( pPre ); } bMoveable = IsMoveable(); } } aOldFramePos = aRectFnSet.GetPos(getFrameArea()); aOldPrtPos = aRectFnSet.GetPos(getFramePrintArea()); if ( !isFrameAreaPositionValid() ) MakePos(); //Set FixSize. VarSize is being adjusted by Format(). if ( !isFrameAreaSizeValid() ) { // invalidate printing area flag, if the following conditions are hold: // - current frame width is 0. // - current printing area width is 0. // - frame width is adjusted to a value greater than 0. // - printing area flag is true. // Thus, it's assured that the printing area is adjusted, if the // frame area width changes its width from 0 to something greater // than 0. // Note: A text frame can be in such a situation, if the format is // triggered by method call after // loading the document. const SwTwips nNewFrameWidth = aRectFnSet.GetWidth(GetUpper()->getFramePrintArea()); if ( isFramePrintAreaValid() && nNewFrameWidth > 0 && aRectFnSet.GetWidth(getFrameArea()) == 0 && aRectFnSet.GetWidth(getFramePrintArea()) == 0 ) { setFramePrintAreaValid(false); } { SwFrameAreaDefinition::FrameAreaWriteAccess aFrm(*this); aRectFnSet.SetWidth( aFrm, nNewFrameWidth ); } // When a lower of a vertically aligned fly frame changes its size we need to recalculate content pos. if( GetUpper() && GetUpper()->IsFlyFrame() && GetUpper()->GetFormat()->GetTextVertAdjust().GetValue() != SDRTEXTVERTADJUST_TOP ) { static_cast(GetUpper())->InvalidateContentPos(); GetUpper()->SetCompletePaint(); } } if ( !isFramePrintAreaValid() ) { const long nOldW = aRectFnSet.GetWidth(getFramePrintArea()); // #i34730# - keep current frame height const SwTwips nOldH = aRectFnSet.GetHeight(getFrameArea()); MakePrtArea( rAttrs ); if ( nOldW != aRectFnSet.GetWidth(getFramePrintArea()) ) Prepare( PREP_FIXSIZE_CHG ); // #i34730# - check, if frame height has changed. // If yes, send a PREP_ADJUST_FRM and invalidate the size flag to // force a format. The format will check in its method // , if the already formatted lines still // fit and if not, performs necessary actions. // #i40150# - no check, if frame is undersized. if ( isFrameAreaSizeValid() && !IsUndersized() && nOldH != aRectFnSet.GetHeight(getFrameArea()) ) { // #115759# - no PREP_ADJUST_FRM and size // invalidation, if height decreases only by the additional // lower space as last content of a table cell and an existing // follow containing one line exists. const SwTwips nHDiff = nOldH - aRectFnSet.GetHeight(getFrameArea()); const bool bNoPrepAdjustFrame = nHDiff > 0 && IsInTab() && GetFollow() && (1 == static_cast(GetFollow())->GetLineCount(TextFrameIndex(COMPLETE_STRING)) || aRectFnSet.GetWidth(static_cast(GetFollow())->getFrameArea()) < 0) && GetFollow()->CalcAddLowerSpaceAsLastInTableCell() == nHDiff; if ( !bNoPrepAdjustFrame ) { Prepare( PREP_ADJUST_FRM ); setFrameAreaSizeValid(false); } } } // To make the widow and orphan rules work, we need to notify the ContentFrame. // Criteria: // - It needs to be movable (otherwise, splitting doesn't make sense) // - It needs to overlap with the lower edge of the PrtArea of the Upper if ( !bMustFit ) { bool bWidow = true; const SwTwips nDeadLine = aRectFnSet.GetPrtBottom(*GetUpper()); if( bMoveable && !bFormatted && ( GetFollow() || aRectFnSet.OverStep( getFrameArea(), nDeadLine ) ) ) { Prepare( PREP_WIDOWS_ORPHANS, nullptr, false ); setFrameAreaSizeValid(false); bWidow = false; } if( aRectFnSet.GetPos(getFrameArea()) != aOldFramePos || aRectFnSet.GetPos(getFramePrintArea()) != aOldPrtPos ) { // In this Prepare, an InvalidateSize_() might happen. // isFrameAreaSizeValid() becomes false and Format() gets called. Prepare( PREP_POS_CHGD, static_cast(&bFormatted), false ); if ( bWidow && GetFollow() ) { Prepare( PREP_WIDOWS_ORPHANS, nullptr, false ); setFrameAreaSizeValid(false); } } } if ( !isFrameAreaSizeValid() ) { setFrameAreaSizeValid(true); bFormatted = true; ++nFormatCount; if( nFormatCount > STOP_FLY_FORMAT ) SetFlyLock( true ); // - loop prevention // No format any longer, if consecutive formats // without change occur. if ( nConsecutiveFormatsWithoutChange <= cnStopFormat ) { Format(getRootFrame()->GetCurrShell()->GetOut()); } #if OSL_DEBUG_LEVEL > 0 else { OSL_FAIL( "debug assertion: - format of text frame suppressed by fix b6448963" ); } #endif } // 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. bool bDummy; auto const pTemp(GetIndPrev()); auto const bTemp(pTemp && pTemp->isFrameAreaSizeValid() && pTemp->isFramePrintAreaValid()); if ( !lcl_Prev( this ) && !bMovedFwd && ( bMoveable || ( bFly && !bTab ) ) && ( !bFootnote || !GetUpper()->FindFootnoteFrame()->GetPrev() ) && MoveBwd( bDummy ) ) { aRectFnSet.Refresh(this); pMoveBwdPre = pTemp; isMoveBwdPreValid = bTemp; bMovedBwd = true; bFormatted = false; if ( bKeep && bMoveable ) { if( CheckMoveFwd( bMakePage, false, bMovedBwd ) ) { bMovedFwd = true; bMoveable = IsMoveable(); aRectFnSet.Refresh(this); } Point aOldPos = aRectFnSet.GetPos(getFrameArea()); MakePos(); if( aOldPos != aRectFnSet.GetPos(getFrameArea()) ) { Prepare( PREP_POS_CHGD, static_cast(&bFormatted), false ); if ( !isFrameAreaSizeValid() ) { { SwFrameAreaDefinition::FrameAreaWriteAccess aFrm(*this); aRectFnSet.SetWidth( aFrm, aRectFnSet.GetWidth(GetUpper()->getFramePrintArea()) ); } if ( !isFramePrintAreaValid() ) { const long nOldW = aRectFnSet.GetWidth(getFramePrintArea()); MakePrtArea( rAttrs ); if( nOldW != aRectFnSet.GetWidth(getFramePrintArea()) ) Prepare( PREP_FIXSIZE_CHG, nullptr, false ); } if( GetFollow() ) { Prepare( PREP_WIDOWS_ORPHANS, nullptr, false ); } setFrameAreaSizeValid(true); bFormatted = true; Format(getRootFrame()->GetCurrShell()->GetOut()); } } SwFrame *pNxt = HasFollow() ? nullptr : FindNext(); while( pNxt && pNxt->IsSctFrame() ) { // Leave empty sections out, go into the other ones. if( static_cast(pNxt)->GetSection() ) { SwFrame* pTmp = static_cast(pNxt)->ContainsAny(); if( pTmp ) { pNxt = pTmp; break; } } pNxt = pNxt->FindNext(); } if ( pNxt ) { pNxt->Calc(getRootFrame()->GetCurrShell()->GetOut()); if( isFrameAreaPositionValid() && !GetIndNext() ) { SwSectionFrame *pSct = FindSctFrame(); if( pSct && !pSct->isFrameAreaSizeValid() ) { SwSectionFrame* pNxtSct = pNxt->FindSctFrame(); if( pNxtSct && pSct->IsAnFollow( pNxtSct ) ) { setFrameAreaPositionValid(false); } } else { setFrameAreaPositionValid(false); } } } } } // In footnotes, the TextFrame may validate itself, which can lead to the // situation that it's position is wrong despite being "valid". if ( isFrameAreaPositionValid() ) { // #i59341# // Workaround for inadequate layout algorithm: // suppress invalidation and calculation of position, if paragraph // has formatted itself at least STOP_FLY_FORMAT times and // has anchored objects. // Thus, the anchored objects get the possibility to format itself // and this probably solve the layout loop. if ( bFootnote && nFormatCount <= STOP_FLY_FORMAT && !GetDrawObjs() ) { setFrameAreaPositionValid(false); MakePos(); aOldFramePos = aRectFnSet.GetPos(getFrameArea()); aOldPrtPos = aRectFnSet.GetPos(getFramePrintArea()); } } // - loop prevention { if ( (aOldFrame_StopFormat == getFrameArea() || aOldFrame_StopFormat2 == getFrameArea() ) && (aOldPrt_StopFormat == getFramePrintArea() || aOldPrt_StopFormat2 == getFramePrintArea())) { ++nConsecutiveFormatsWithoutChange; } else { nConsecutiveFormatsWithoutChange = 0; } } // Yet again an invalid value? Repeat from the start... if ( !isFrameAreaPositionValid() || !isFrameAreaSizeValid() || !isFramePrintAreaValid() ) continue; // Done? // Attention: because height == 0, it's better to use Top()+Height() instead of // Bottom(). This might happen with undersized TextFrames on the lower edge of a // multi-column section const long nPrtBottom = aRectFnSet.GetPrtBottom(*GetUpper()); long nBottomDist = aRectFnSet.BottomDist(getFrameArea(), nPrtBottom); // Hide whitespace may require not to insert a new page. SwPageFrame* pPageFrame = FindPageFrame(); const bool bHeightValid = pPageFrame->CheckPageHeightValidForHideWhitespace(nBottomDist); if (!bHeightValid) { pPageFrame->InvalidateSize(); nBottomDist = 0; } if( nBottomDist >= 0 ) { if ( bKeep && bMoveable ) { // We make sure the successor will be formatted the same. // This way, we keep control until (almost) everything is stable, // allowing us to avoid endless loops caused by ever repeating // retries. // bMoveFwdInvalid is required for #38407#. This was originally solved // in flowfrm.cxx rev 1.38, but broke the above schema and // preferred to play towers of hanoi (#43669#). SwFrame *pNxt = HasFollow() ? nullptr : FindNext(); // For sections we prefer the content, because it can change // the page if required. while( pNxt && pNxt->IsSctFrame() ) { if( static_cast(pNxt)->GetSection() ) { pNxt = static_cast(pNxt)->ContainsAny(); break; } pNxt = pNxt->FindNext(); } if ( pNxt ) { const bool bMoveFwdInvalid = nullptr != GetIndNext(); const bool bNxtNew = ( 0 == aRectFnSet.GetHeight(pNxt->getFramePrintArea()) ) && (!pNxt->IsTextFrame() ||!static_cast(pNxt)->IsHiddenNow()); pNxt->Calc(getRootFrame()->GetCurrShell()->GetOut()); if ( !bMovedBwd && ((bMoveFwdInvalid && !GetIndNext()) || bNxtNew) ) { if( bMovedFwd ) pNotify->SetInvaKeep(); bMovedFwd = false; } } } continue; } // I don't fit into my parents, so it's time to make changes // as constructively as possible. //If I'm NOT allowed to leave the parent 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 || IsUndersized() ) { if( !bMoveable && IsInTab() ) { long nDiff = -aRectFnSet.BottomDist( getFrameArea(), aRectFnSet.GetPrtBottom(*GetUpper()) ); long nReal = GetUpper()->Grow( nDiff ); if( nReal ) continue; } break; } // If there's no way I can make myself fit into my Upper, the situation // could still probably be mitigated by splitting up. // This situation arises with freshly created Follows that had been moved // to the next page but is still too big for it - ie. needs to be split // as well. // If I'm unable to split (WouldFit()) and can't be fitted, I'm going // to tell my TextFrame part that, if possible, we still need to split despite // the "don't split" attribute. bool bMoveOrFit = false; bool bDontMoveMe = !GetIndPrev(); if( bDontMoveMe && IsInSct() ) { SwFootnoteBossFrame* pBoss = FindFootnoteBossFrame(); bDontMoveMe = !pBoss->IsInSct() || ( !pBoss->Lower()->GetNext() && !pBoss->GetPrev() ); } // Finally, we are able to split table rows. Therefore, bDontMoveMe // can be set to false: if( bDontMoveMe && IsInTab() && nullptr != GetNextCellLeaf() ) bDontMoveMe = false; assert(bMoveable); if ( bDontMoveMe && aRectFnSet.GetHeight(getFrameArea()) > aRectFnSet.GetHeight(GetUpper()->getFramePrintArea()) ) { if ( !bFitPromise ) { SwTwips nTmp = aRectFnSet.GetHeight(GetUpper()->getFramePrintArea()) - aRectFnSet.GetTop(getFramePrintArea()); bool bSplit = !IsFwdMoveAllowed(); if ( nTmp > 0 && WouldFit( nTmp, bSplit, false ) ) { Prepare( PREP_WIDOWS_ORPHANS, nullptr, false ); setFrameAreaSizeValid(false); bFitPromise = true; continue; } /* * In earlier days, we never tried to fit TextFrames in * frames and sections using bMoveOrFit by ignoring * its attributes (Widows, Keep). * This should have been done at least for column frames; * as it must be tried anyway with linked frames and sections. * Exception: If we sit in FormatWidthCols, we must not ignore * the attributes. */ else if ( !bFootnote && ( !bFly || !FindFlyFrame()->IsColLocked() ) && ( !bSct || !FindSctFrame()->IsColLocked() ) ) bMoveOrFit = true; } #if OSL_DEBUG_LEVEL > 0 else { OSL_FAIL( "+TextFrame didn't respect WouldFit promise." ); } #endif } // Let's see if I can find some space somewhere... // footnotes in the neighbourhood are moved into _MoveFootnoteCntFwd SwFrame *pPre = GetIndPrev(); SwFrame *pOldUp = GetUpper(); /* MA 13. Oct. 98: What is this supposed to be!? * AMA 14. Dec 98: If a column section can't find any space for its first ContentFrame, it should be * moved not only to the next column, but probably even to the next page, creating * a section-follow there. */ if( IsInSct() && bMovedFwd && bMakePage && pOldUp->IsColBodyFrame() && pOldUp->GetUpper()->GetUpper()->IsSctFrame() && ( pPre || pOldUp->GetUpper()->GetPrev() ) && static_cast(pOldUp->GetUpper()->GetUpper())->MoveAllowed(this) ) { bMovedFwd = false; } const bool bCheckForGrownBody = pOldUp->IsBodyFrame(); const long nOldBodyHeight = aRectFnSet.GetHeight(pOldUp->getFrameArea()); if ( !bMovedFwd && !MoveFwd( bMakePage, false ) ) bMakePage = false; aRectFnSet.Refresh(this); if (!bMovedFwd && bFootnote && GetIndPrev() != pPre) { // SwFlowFrame::CutTree() could have formatted and deleted pPre auto const pPrevFootnoteFrame(static_cast( FindFootnoteFrame())->GetMaster()); bool bReset = true; if (pPrevFootnoteFrame) { // use GetIndNext() in case there are sections for (auto p = pPrevFootnoteFrame->Lower(); p; p = p->GetIndNext()) { if (p == pPre) { bReset = false; break; } } } if (bReset) { pPre = nullptr; } } // If MoveFwd moves the paragraph to the next page, a following // paragraph, which contains footnotes can cause the old upper // frame to grow. In this case we explicitly allow a new check // for MoveBwd. Robust: We also check the bMovedBwd flag again. // If pOldUp was a footnote frame, it has been deleted inside MoveFwd. // Therefore we only check for growing body frames. bMovedFwd = !bCheckForGrownBody || bMovedBwd || pOldUp == GetUpper() || aRectFnSet.GetHeight(pOldUp->getFrameArea()) <= nOldBodyHeight; bFormatted = false; if ( bMoveOrFit && GetUpper() == pOldUp ) { // FME 2007-08-30 #i81146# new loop control if ( nConsecutiveFormatsWithoutChange <= cnStopFormat ) { Prepare( PREP_MUST_FIT, nullptr, false ); setFrameAreaSizeValid(false); bMustFit = true; continue; } #if OSL_DEBUG_LEVEL > 0 OSL_FAIL( "LoopControl in SwContentFrame::MakeAll" ); #endif } if ( bMovedBwd && GetUpper() ) { // Retire invalidations that have become useless. GetUpper()->ResetCompletePaint(); if( pPre && !pPre->IsSctFrame() ) ::ValidateSz( pPre ); } } //while ( !isFrameAreaPositionValid() || !isFrameAreaSizeValid() || !isFramePrintAreaValid() ) // NEW: Looping Louie (Light). Should not be applied in balanced sections. // Should only be applied if there is no better solution! LOOPING_LOUIE_LIGHT( bMovedFwd && bMovedBwd && !IsInBalancedSection() && ( ( bFootnote && !FindFootnoteFrame()->GetRef()->IsInSct() ) || // #i33887# ( IsInSct() && bKeep ) // ... add your conditions here ... ), static_cast(*this) ); pSaveFootnote.reset(); UnlockJoin(); xDeleteGuard.reset(); if ( bMovedFwd || bMovedBwd ) pNotify->SetInvaKeep(); if ( bMovedFwd ) { pNotify->SetInvalidatePrevPrtArea(); } pNotify.reset(); SetFlyLock( false ); } void MakeNxt( SwFrame *pFrame, SwFrame *pNxt ) { // fix(25455): Validate, otherwise this leads to a recursion. // The first try, cancelling with pFrame = 0 if !Valid, leads to a problem, as // the Keep may not be considered properly anymore (27417). const bool bOldPos = pFrame->isFrameAreaPositionValid(); const bool bOldSz = pFrame->isFrameAreaSizeValid(); const bool bOldPrt = pFrame->isFramePrintAreaValid(); pFrame->setFrameAreaPositionValid(true); pFrame->setFrameAreaSizeValid(true); pFrame->setFramePrintAreaValid(true); // fix(29272): Don't call MakeAll - there, pFrame might be invalidated again, and // we recursively end up in here again. if ( pNxt->IsContentFrame() ) { SwContentNotify aNotify( static_cast(pNxt) ); SwBorderAttrAccess aAccess( SwFrame::GetCache(), pNxt ); const SwBorderAttrs &rAttrs = *aAccess.Get(); if ( !pNxt->isFrameAreaSizeValid() ) { SwFrameAreaDefinition::FrameAreaWriteAccess aFrm(*pNxt); if( pNxt->IsVertical() ) { aFrm.Height( pNxt->GetUpper()->getFramePrintArea().Height() ); } else { aFrm.Width( pNxt->GetUpper()->getFramePrintArea().Width() ); } } static_cast(pNxt)->MakePrtArea( rAttrs ); pNxt->Format( pNxt->getRootFrame()->GetCurrShell()->GetOut(), &rAttrs ); } else { SwLayNotify aNotify( static_cast(pNxt) ); SwBorderAttrAccess aAccess( SwFrame::GetCache(), pNxt ); const SwBorderAttrs &rAttrs = *aAccess.Get(); if ( !pNxt->isFrameAreaSizeValid() ) { SwFrameAreaDefinition::FrameAreaWriteAccess aFrm(*pNxt); if( pNxt->IsVertical() ) { aFrm.Height( pNxt->GetUpper()->getFramePrintArea().Height() ); } else { aFrm.Width( pNxt->GetUpper()->getFramePrintArea().Width() ); } } pNxt->Format( pNxt->getRootFrame()->GetCurrShell()->GetOut(), &rAttrs ); } pFrame->setFrameAreaPositionValid(bOldPos); pFrame->setFrameAreaSizeValid(bOldSz); pFrame->setFramePrintAreaValid(bOldPrt); } /// This routine checks whether there are no other FootnoteBosses /// between the pFrame's FootnoteBoss and the pNxt's FootnoteBoss. static bool lcl_IsNextFootnoteBoss( const SwFrame *pFrame, const SwFrame* pNxt ) { assert(pFrame && pNxt && "lcl_IsNextFootnoteBoss: No Frames?"); pFrame = pFrame->FindFootnoteBossFrame(); pNxt = pNxt->FindFootnoteBossFrame(); // If pFrame is a last column, we use the page instead. while( pFrame && pFrame->IsColumnFrame() && !pFrame->GetNext() ) pFrame = pFrame->GetUpper()->FindFootnoteBossFrame(); // If pNxt is a first column, we use the page instead. while( pNxt && pNxt->IsColumnFrame() && !pNxt->GetPrev() ) pNxt = pNxt->GetUpper()->FindFootnoteBossFrame(); // So.. now pFrame and pNxt are either two adjacent pages or columns. return pFrame && pNxt && pFrame->GetNext() == pNxt; } bool SwContentFrame::WouldFit_( SwTwips nSpace, SwLayoutFrame *pNewUpper, bool bTstMove, const bool bObjsInNewUpper ) { // To have the footnote select its place carefully, it needs // to be moved in any case if there is at least one page/column // between the footnote and the new Upper. SwFootnoteFrame* pFootnoteFrame = nullptr; if ( IsInFootnote() ) { if( !lcl_IsNextFootnoteBoss( pNewUpper, this ) ) return true; pFootnoteFrame = FindFootnoteFrame(); } bool bRet; bool bSplit = !pNewUpper->Lower(); SwContentFrame *pFrame = this; const SwFrame *pTmpPrev = pNewUpper->Lower(); if( pTmpPrev && pTmpPrev->IsFootnoteFrame() ) pTmpPrev = static_cast(pTmpPrev)->Lower(); while ( pTmpPrev && pTmpPrev->GetNext() ) pTmpPrev = pTmpPrev->GetNext(); do { // #i46181# SwTwips nSecondCheck = 0; SwTwips nOldSpace = nSpace; bool bOldSplit = bSplit; if ( bTstMove || IsInFly() || ( IsInSct() && ( pFrame->GetUpper()->IsColBodyFrame() || ( pFootnoteFrame && pFootnoteFrame->GetUpper()->GetUpper()->IsColumnFrame() ) ) ) ) { // This is going to get a bit insidious now. If you're faint of heart, // you'd better look away here. If a Fly contains columns, then the Contents // are movable, except ones in the last column (see SwFrame::IsMoveable()). // Of course they're allowed to float back. WouldFit() only returns a usable // value if the Frame is movable. To fool WouldFit() into believing there's // a movable Frame, I'm just going to hang it somewhere else for the time. // The same procedure applies for column sections to make SwSectionFrame::Growable() // return the proper value. // Within footnotes, we may even need to put the SwFootnoteFrame somewhere else, if // there's no SwFootnoteFrame there. SwFrame* pTmpFrame = pFrame->IsInFootnote() && !pNewUpper->FindFootnoteFrame() ? static_cast(pFrame->FindFootnoteFrame()) : pFrame; SwLayoutFrame *pUp = pTmpFrame->GetUpper(); SwFrame *pOldNext = pTmpFrame->GetNext(); pTmpFrame->RemoveFromLayout(); pTmpFrame->InsertBefore( pNewUpper, nullptr ); // tdf#107126 for a section in a footnote, we have only inserted // the SwTextFrame but no SwSectionFrame - reset mbInfSct flag // to avoid crashing (but perhaps we should create a temp // SwSectionFrame here because WidowsAndOrphans checks for that?) pTmpFrame->InvalidateInfFlags(); if ( pFrame->IsTextFrame() && ( bTstMove || static_cast(pFrame)->HasFollow() || ( !static_cast(pFrame)->HasPara() && !static_cast(pFrame)->IsEmpty() ) ) ) { bTstMove = true; bRet = static_cast(pFrame)->TestFormat( pTmpPrev, nSpace, bSplit ); } else bRet = pFrame->WouldFit( nSpace, bSplit, false ); pTmpFrame->RemoveFromLayout(); pTmpFrame->InsertBefore( pUp, pOldNext ); pTmpFrame->InvalidateInfFlags(); // restore flags } else { bRet = pFrame->WouldFit( nSpace, bSplit, false ); nSecondCheck = !bSplit ? 1 : 0; } SwBorderAttrAccess aAccess( SwFrame::GetCache(), pFrame ); const SwBorderAttrs &rAttrs = *aAccess.Get(); // Sad but true: We need to consider the spacing in our calculation. // This already happened in TestFormat. if ( bRet && !bTstMove ) { SwTwips nUpper; if ( pTmpPrev ) { nUpper = CalcUpperSpace( nullptr, pTmpPrev ); // in balanced columned section frames we do not want the // common border bool bCommonBorder = true; if ( pFrame->IsInSct() && pFrame->GetUpper()->IsColBodyFrame() ) { const SwSectionFrame* pSct = pFrame->FindSctFrame(); bCommonBorder = pSct->GetFormat()->GetBalancedColumns().GetValue(); } // #i46181# nSecondCheck = ( 1 == nSecondCheck && pFrame == this && IsTextFrame() && bCommonBorder && !static_cast(this)->IsEmpty() ) ? nUpper : 0; nUpper += bCommonBorder ? rAttrs.GetBottomLine( *pFrame ) : rAttrs.CalcBottomLine(); } else { // #i46181# nSecondCheck = 0; if( pFrame->IsVertical() ) nUpper = pFrame->getFrameArea().Width() - pFrame->getFramePrintArea().Width(); else nUpper = pFrame->getFrameArea().Height() - pFrame->getFramePrintArea().Height(); } nSpace -= nUpper; if ( nSpace < 0 ) { bRet = false; // #i46181# if ( nSecondCheck > 0 ) { // The following code is intended to solve a (rare) problem // causing some frames not to move backward: // SwTextFrame::WouldFit() claims that the whole paragraph // fits into the given space and subtracts the height of // all lines from nSpace. nSpace - nUpper is not a valid // indicator if the frame should be allowed to move backward. // We do a second check with the original remaining space // reduced by the required upper space: nOldSpace -= nSecondCheck; const bool bSecondRet = nOldSpace >= 0 && pFrame->WouldFit( nOldSpace, bOldSplit, false ); if ( bSecondRet && bOldSplit && nOldSpace >= 0 ) { bRet = true; bSplit = true; } } } } // Also consider lower spacing in table cells if ( bRet && IsInTab() && pNewUpper->GetFormat()->getIDocumentSettingAccess().get(DocumentSettingId::ADD_PARA_SPACING_TO_TABLE_CELLS) ) { nSpace -= rAttrs.GetULSpace().GetLower(); if ( nSpace < 0 ) { bRet = false; } } if (bRet && !bSplit && pFrame->IsKeep(rAttrs.GetAttrSet().GetKeep(), GetBreakItem())) { if( bTstMove ) { while( pFrame->IsTextFrame() && static_cast(pFrame)->HasFollow() ) { pFrame = static_cast(pFrame)->GetFollow(); } // If last follow frame of text frame isn't valid, // a formatting of the next content frame doesn't makes sense. // Thus, return true. if ( IsAnFollow( pFrame ) && !pFrame->isFrameAreaDefinitionValid() ) { OSL_FAIL( "Only a warning for task 108824:/nFindNext()) && pNxt->IsContentFrame() && ( !pFootnoteFrame || ( pNxt->IsInFootnote() && pNxt->FindFootnoteFrame()->GetAttr() == pFootnoteFrame->GetAttr() ) ) ) { // TestFormat(?) does not like paragraph- or character anchored objects. // current solution for the test formatting doesn't work, if // objects are present in the remaining area of the new upper if ( bTstMove && ( pNxt->GetDrawObjs() || bObjsInNewUpper ) ) { return true; } if ( !pNxt->isFrameAreaDefinitionValid() ) { MakeNxt( pFrame, pNxt ); } // Little trick: if the next has a predecessor, then the paragraph // spacing has been calculated already, and we don't need to re-calculate // it in an expensive way. if( lcl_NotHiddenPrev( pNxt ) ) pTmpPrev = nullptr; else { if( pFrame->IsTextFrame() && static_cast(pFrame)->IsHiddenNow() ) pTmpPrev = lcl_NotHiddenPrev( pFrame ); else pTmpPrev = pFrame; } pFrame = static_cast(pNxt); } else pFrame = nullptr; } else pFrame = nullptr; } while ( bRet && pFrame ); return bRet; } /* vim:set shiftwidth=4 softtabstop=4 expandtab: */