diff options
Diffstat (limited to 'sw/source/core/layout/calcmove.cxx')
-rw-r--r-- | sw/source/core/layout/calcmove.cxx | 2215 |
1 files changed, 2215 insertions, 0 deletions
diff --git a/sw/source/core/layout/calcmove.cxx b/sw/source/core/layout/calcmove.cxx new file mode 100644 index 000000000..184373585 --- /dev/null +++ b/sw/source/core/layout/calcmove.cxx @@ -0,0 +1,2215 @@ +/* -*- 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 <memory> +#include <rootfrm.hxx> +#include <pagefrm.hxx> +#include <viewopt.hxx> +#include <frmatr.hxx> +#include <frmtool.hxx> +#include <txtftn.hxx> +#include <fmtftn.hxx> +#include <ndtxt.hxx> +#include <editeng/ulspitem.hxx> +#include <editeng/keepitem.hxx> +#include <osl/diagnose.h> +#include <svx/sdtaitm.hxx> + +#include <fmtfsize.hxx> +#include <fmtanchr.hxx> +#include <fmtclbl.hxx> + +#include <tabfrm.hxx> +#include <ftnfrm.hxx> +#include <txtfrm.hxx> +#include <sectfrm.hxx> +#include <dbg_lay.hxx> + +#include <sortedobjs.hxx> +#include <layouter.hxx> +#include <flyfrms.hxx> + +#include <DocumentSettingManager.hxx> +#include <IDocumentLayoutAccess.hxx> + +// Move methods + +/// Return value tells whether the Frame should be moved. +bool SwContentFrame::ShouldBwdMoved( SwLayoutFrame *pNewUpper, 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 <nMoveAnyway> 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<const SwSectionFrame*>(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(); + if (rIDSA.get(DocumentSettingId::ADD_PARA_LINE_SPACING_TO_TABLE_CELLS)) + { + nNewTop -= rAttrs.CalcLineSpacing(); + } + } + } + } + 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<SwContentFrame*>(this) : nullptr; + + if ( bTab ) + { + pThis = static_cast<SwTabFrame*>(this); + bOldTabLock = static_cast<SwTabFrame*>(this)->IsJoinLocked(); + ::PrepareLock( static_cast<SwTabFrame*>(this) ); + bFoll = pThis->IsFollow(); + } + else if( IsSctFrame() ) + { + pThis = static_cast<SwSectionFrame*>(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<SwContentFrame*>(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; + + bool const isLast(pFrame->GetNext() == this); + // note: this seems obvious but does *not* hold, a MakeAll() + // could move more than 1 frame backwards! + // that's why FindNext() is used below + // assert(pFrame->GetUpper() == GetUpper()); + pFrame->MakeAll(pRenderContext); + if( IsSctFrame() && !static_cast<SwSectionFrame*>(this)->GetSection() ) + break; + if (isLast && pFrame->GetUpper() != GetUpper()) + { + assert(GetUpper()->Lower() == this + // empty section frames are created all the time... + || GetUpper()->Lower()->IsSctFrame() + // tab frame/section frame may split multiple times + || ( SwFlowFrame::CastFlowFrame(pFrame) + && SwFlowFrame::CastFlowFrame(GetUpper()->Lower()) + && SwFlowFrame::CastFlowFrame(pFrame)->IsAnFollow( + SwFlowFrame::CastFlowFrame(GetUpper()->Lower())) + && (GetUpper()->Lower()->GetNext() == this + // if it's more than 10 pages long... + || (SwFlowFrame::CastFlowFrame(GetUpper()->Lower())->GetFollow() + == SwFlowFrame::CastFlowFrame(GetUpper()->Lower()->GetNext()) + && GetUpper()->Lower()->GetNext()->GetNext() == this) + // pre-existing empty section frames may end up between them... + || GetUpper()->Lower()->GetNext()->IsSctFrame()))); + break; // tdf#119109 frame was moved backward, prevent + // FindNext() returning a frame inside this if + } // this is a table! + } + // With ContentFrames, the chain may be broken while walking through + // it. Therefore we have to figure out the next frame 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<SwSectionFrame*>(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<SwTabFrame*>(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() ) + { + const bool bCnt = IsContentFrame(); + const bool bTab = IsTabFrame(); + bool bNoSect = IsInSct(); + + std::optional<FlowFrameJoinLockGuard> tabGuard; + std::optional<SwFrameDeleteGuard> rowGuard; + SwFlowFrame* pThis = bCnt ? static_cast<SwContentFrame*>(this) : nullptr; + + if ( bTab ) + { + tabGuard.emplace(static_cast<SwTabFrame*>(this)); // tdf#125741 + pThis = static_cast<SwTabFrame*>(this); + } + else if (IsRowFrame()) + { + rowGuard.emplace(this); // tdf#125741 keep this alive + } + else if( IsSctFrame() ) + { + pThis = static_cast<SwSectionFrame*>(this); + bNoSect = false; + } + + GetUpper()->PrepareCursor(); + GetUpper()->Calc(getRootFrame()->GetCurrShell() ? getRootFrame()->GetCurrShell()->GetOut() : nullptr); + + OSL_ENSURE( GetUpper(), ":-( Layout unstable (Upper gone)." ); + if ( !GetUpper() ) + return; + + bool const 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; + + bool const isLast(pFrame->GetNext() == this); + pFrame->MakeAll(getRootFrame()->GetCurrShell()->GetOut()); + if (isLast && pFrame->GetUpper() != GetUpper()) + { + assert(GetUpper()->Lower() == this + // empty section frames are created all the time... + || GetUpper()->Lower()->IsSctFrame() + // tab frame/section frame may split multiple times + || ( SwFlowFrame::CastFlowFrame(pFrame) + && SwFlowFrame::CastFlowFrame(GetUpper()->Lower()) + && SwFlowFrame::CastFlowFrame(pFrame)->IsAnFollow( + SwFlowFrame::CastFlowFrame(GetUpper()->Lower())) + && (GetUpper()->Lower()->GetNext() == this + // if it's more than 10 pages long... + || (SwFlowFrame::CastFlowFrame(GetUpper()->Lower())->GetFollow() + == SwFlowFrame::CastFlowFrame(GetUpper()->Lower()->GetNext()) + && GetUpper()->Lower()->GetNext()->GetNext() == this) + // pre-existing empty section frames may end up between them... + || GetUpper()->Lower()->GetNext()->IsSctFrame()))); + break; // tdf#119109 frame was moved backward, prevent + // FindNext() returning a frame inside this if + } // this is a table! + } + // With ContentFrames, the chain may be broken while walking through + // it. Therefore we have to figure out the next frame 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<SwSectionFrame*>(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)." ); + } + 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<SwSectionFrame*>(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<SwTextFrame*>(pRet)->IsHiddenNow() ); + return pRet; +} + +void SwFrame::MakePos() +{ + if ( isFrameAreaPositionValid() ) + return; + + setFrameAreaPositionValid(true); + bool bUseUpper = false; + SwFrame* pPrv = lcl_Prev( this ); + if ( pPrv && + ( !pPrv->IsContentFrame() || + ( static_cast<SwContentFrame*>(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 <ColLocked()>, then + // do *not* calculate it. + // NOTE: Footer frame is <ColLocked()> during its + // <FormatSize(..)>, which is called from <Format(..)>, which + // is called from <MakeAll()>, which is called from <Calc()>. + // #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<SwFlyAtContentFrame*>(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<SwFlyFrame*>(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 <SwSortedObjs> +static void lcl_CheckObjects(SwSortedObjs& rSortedObjs, const SwFrame* pFrame, tools::Long& rBot) +{ + // And then there can be paragraph anchored frames that sit below their paragraph. + tools::Long nMax = 0; + for (SwAnchoredObject* pObj : rSortedObjs) + { + // #i28701# - consider changed type of <SwSortedObjs> + // entries. + tools::Long nTmp = 0; + if ( auto pFly = pObj->DynCastFlyFrame() ) + { + 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 tools::Long nTop, const tools::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. + tools::Long nBot = getFrameArea().Top() + nTop; + const SwFrame *pFrame = Lower(); + while (pFrame) + { + tools::Long nTmp = 0; + const SwFrame *pCnt = static_cast<const SwLayoutFrame*>(pFrame)->ContainsAny(); + while (pCnt && (pCnt->GetUpper() == pFrame || + static_cast<const SwLayoutFrame*>(pFrame)->IsAnLower(pCnt))) + { + nTmp += pCnt->getFrameArea().Height(); + if (pCnt->IsTextFrame() && + static_cast<const SwTextFrame*>(pCnt)->IsUndersized()) + { + // This TextFrame would like to be a bit bigger. + nTmp += static_cast<const SwTextFrame*>(pCnt)->GetParHeight() + - pCnt->getFramePrintArea().Height(); + } + else if (pCnt->IsSctFrame()) + { + // Grow if undersized, but don't shrink if oversized. + const auto delta = static_cast<const SwSectionFrame*>(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::optional<SwBorderAttrAccess> oAccess; + 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 (!oAccess) + { + oAccess.emplace(SwFrame::GetCache(), this); + pAttrs = oAccess->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 tools::Long nTop = pAttrs->CalcTopLine() + aBorder.Height(); + const tools::Long nBottom = pAttrs->CalcBottomLine()+ aBorder.Height(); + + tools::Long nWidth = GetUpper() ? static_cast<SwRootFrame*>(GetUpper())->GetBrowseWidth() : 0; + const auto nDefWidth = pSh->GetBrowseWidth(); + if (nWidth < nDefWidth) + nWidth = nDefWidth; + nWidth += + 2 * aBorder.Width(); + + constexpr tools::Long constTwips_2cm = o3tl::toTwips(2, o3tl::Length::cm); + nWidth = std::max(nWidth, 2L * aBorder.Width() + constTwips_2cm); + + { + 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. + tools::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) + { + tools::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<SwRootFrame*>(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() ? (IsVertLRBT() ? fnRectVertL2RB2T : fnRectVertL2R) : fnRectVert ); + + std::optional<SwBorderAttrAccess> oAccess; + 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 tools::Long nDiff = nPrtWidth - (getFrameArea().*fnRect->fnGetWidth)(); + SwFrameAreaDefinition::FrameAreaWriteAccess aFrm(*this); + // SwRectFn switched between horizontal and vertical when bVert == IsNeighbourFrame(). + // We pick fnSubLeft or fnAddRight that is correspondant to SwRectFn->fnAddBottom + if( ( IsCellFrame() && IsRightToLeft() ) || ( IsColumnFrame() && bVert && !IsVertLR() ) ) + { + (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 ( !oAccess ) + { + oAccess.emplace(SwFrame::GetCache(), this); + pAttrs = oAccess->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()) + { + SwNodeOffset 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<const SwTextFrame*>(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() ) + return; + + setFramePrintAreaValid(true); + SwRectFnSet aRectFnSet(this); + const bool bTextFrame = IsTextFrame(); + SwTwips nUpper = 0; + if ( bTextFrame && static_cast<SwTextFrame*>(this)->IsHiddenNow() ) + { + if ( static_cast<SwTextFrame*>(this)->HasFollow() ) + static_cast<SwTextFrame*>(this)->JoinFrame(); + + if( aRectFnSet.GetHeight(getFramePrintArea()) ) + { + static_cast<SwTextFrame*>(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 tools::Long nLeft = rAttrs.CalcLeft( this ); + const tools::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()) + tools::Long nMinWidth = 0; + + for (size_t i = 0; GetDrawObjs() && i < GetDrawObjs()->size(); ++i) + { + // #i28701# - consider changed type of + // <SwSortedObjs> entries + SwAnchoredObject* pObj = (*GetDrawObjs())[i]; + const SwFrameFormat& rFormat = pObj->GetFrameFormat(); + const bool bFly = pObj->DynCastFlyFrame() != 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() ); + tools::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( tools::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<SwTextFrame*>(this)->IsSwapped(), "Calculation of a swapped frame" ); + + StackHack aHack; + + if ( static_cast<SwTextFrame*>(this)->IsLocked() ) + { + OSL_FAIL( "Format for locked TextFrame." ); + return; + } + + std::optional<SwFrameDeleteGuard> oDeleteGuard(std::in_place, this); + LockJoin(); + tools::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::optional<SwContentNotify> oNotify( std::in_place, 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) ) ) + { + oNotify->SetBordersJoinedWithPrev(); + } + + const bool bKeep = IsKeep(rAttrs.GetAttrSet().GetKeep(), GetBreakItem()); + + std::unique_ptr<SwSaveFootnoteHeight> pSaveFootnote; + if ( bFootnote ) + { + SwFootnoteFrame *pFootnote = FindFootnoteFrame(); + SwSectionFrame* pSct = pFootnote->FindSctFrame(); + if ( !static_cast<SwTextFrame*>(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<SwTextFrame*>(pFootnote->GetRef())->GetFootnoteLine( pFootnote->GetAttr() ) ) ); + } + } + + if ( GetUpper()->IsSctFrame() && + HasFollow() && !GetFollow()->IsDeleteForbidden() && + &GetFollow()->GetFrame() == GetNext() ) + { + static_cast<SwTextFrame&>(*this).JoinFrame(); + } + + // #i28701# - move master forward, if it has to move, + // because of its object positioning. + if ( !static_cast<SwTextFrame*>(this)->IsFollow() ) + { + sal_uInt32 nToPageNum = 0; + const bool bMoveFwdByObjPos = SwLayouter::FrameMovedFwdByObjPos( + *(GetAttrSet()->GetDoc()), + *static_cast<SwTextFrame*>(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<SwTextFrame*>(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(); + //<loop of movefwd until condition held or no move> + 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 <SwCursorShell::SetCursor()> 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<SwFlyFrame*>(GetUpper())->InvalidateContentPos(); + GetUpper()->SetCompletePaint(); + } + } + if ( !isFramePrintAreaValid() ) + { + const tools::Long nOldW = aRectFnSet.GetWidth(getFramePrintArea()); + // #i34730# - keep current frame height + const SwTwips nOldH = aRectFnSet.GetHeight(getFrameArea()); + MakePrtArea( rAttrs ); + if ( nOldW != aRectFnSet.GetWidth(getFramePrintArea()) ) + Prepare( PrepareHint::FixSizeChanged ); + // #i34730# - check, if frame height has changed. + // If yes, send a PrepareHint::AdjustSizeWithoutFormatting and invalidate the size flag to + // force a format. The format will check in its method + // <SwTextFrame::CalcPreps()>, 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 PrepareHint::AdjustSizeWithoutFormatting 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<SwTextFrame*>(GetFollow())->GetLineCount(TextFrameIndex(COMPLETE_STRING)) + || aRectFnSet.GetWidth(static_cast<SwTextFrame*>(GetFollow())->getFrameArea()) < 0) && + GetFollow()->CalcAddLowerSpaceAsLastInTableCell() == nHDiff; + if ( !bNoPrepAdjustFrame ) + { + Prepare( PrepareHint::AdjustSizeWithoutFormatting ); + 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( PrepareHint::WidowsOrphans, 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( PrepareHint::FramePositionChanged, static_cast<const void*>(&bFormatted), false ); + if ( bWidow && GetFollow() ) + { + Prepare( PrepareHint::WidowsOrphans, 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 <cnStopFormat> consecutive formats + // without change occur. + if ( nConsecutiveFormatsWithoutChange <= cnStopFormat ) + { + Format(getRootFrame()->GetCurrShell()->GetOut()); + } +#if OSL_DEBUG_LEVEL > 0 + else + { + OSL_FAIL( "debug assertion: <SwContentFrame::MakeAll()> - 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( PrepareHint::FramePositionChanged, static_cast<const void*>(&bFormatted), false ); + if ( !isFrameAreaSizeValid() ) + { + { + SwFrameAreaDefinition::FrameAreaWriteAccess aFrm(*this); + aRectFnSet.SetWidth( aFrm, aRectFnSet.GetWidth(GetUpper()->getFramePrintArea()) ); + } + + if ( !isFramePrintAreaValid() ) + { + const tools::Long nOldW = aRectFnSet.GetWidth(getFramePrintArea()); + MakePrtArea( rAttrs ); + if( nOldW != aRectFnSet.GetWidth(getFramePrintArea()) ) + Prepare( PrepareHint::FixSizeChanged, nullptr, false ); + } + if( GetFollow() ) + { + Prepare( PrepareHint::WidowsOrphans, 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<SwSectionFrame*>(pNxt)->GetSection() ) + { + SwFrame* pTmp = static_cast<SwSectionFrame*>(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 tools::Long nPrtBottom = aRectFnSet.GetPrtBottom(*GetUpper()); + tools::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<SwSectionFrame*>(pNxt)->GetSection() ) + { + pNxt = static_cast<SwSectionFrame*>(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<SwTextFrame*>(pNxt)->IsHiddenNow()); + + pNxt->Calc(getRootFrame()->GetCurrShell()->GetOut()); + + if ( !bMovedBwd && + ((bMoveFwdInvalid && !GetIndNext()) || + bNxtNew) ) + { + if( bMovedFwd ) + oNotify->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() ) + { + 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, false)) + { + Prepare( PrepareHint::WidowsOrphans, 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<SwSectionFrame*>(pOldUp->GetUpper()->GetUpper())->MoveAllowed(this) ) + { + bMovedFwd = false; + } + + const bool bCheckForGrownBody = pOldUp->IsBodyFrame(); + const tools::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<SwFootnoteFrame const*>( + 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( PrepareHint::MustFit, 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<SwTextFrame&>(*this) ); + + pSaveFootnote.reset(); + + UnlockJoin(); + oDeleteGuard.reset(); + if ( bMovedFwd || bMovedBwd ) + oNotify->SetInvaKeep(); + if ( bMovedFwd ) + { + oNotify->SetInvalidatePrevPrtArea(); + } + oNotify.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<SwContentFrame*>(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<SwContentFrame*>(pNxt)->MakePrtArea( rAttrs ); + pNxt->Format( pNxt->getRootFrame()->GetCurrShell()->GetOut(), &rAttrs ); + } + else + { + SwLayNotify aNotify( static_cast<SwLayoutFrame*>(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<const SwFootnoteFrame*>(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<SwFrame*>(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<SwTextFrame*>(pFrame)->HasFollow() || + ( !static_cast<SwTextFrame*>(pFrame)->HasPara() && + !static_cast<SwTextFrame*>(pFrame)->IsEmpty() + ) + ) + ) + { + bTstMove = true; + bRet = static_cast<SwTextFrame*>(pFrame)->TestFormat( pTmpPrev, nSpace, bSplit ); + } + else + bRet = pFrame->WouldFit(nSpace, bSplit, false, true); + + pTmpFrame->RemoveFromLayout(); + pTmpFrame->InsertBefore( pUp, pOldNext ); + pTmpFrame->InvalidateInfFlags(); // restore flags + } + else + { + bRet = pFrame->WouldFit(nSpace, bSplit, false, true); + 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<const SwTextFrame*>(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, true); + if ( bSecondRet && bOldSplit && nOldSpace >= 0 ) + { + bRet = true; + bSplit = true; + } + } + } + } + + // Also consider lower spacing in table cells + IDocumentSettingAccess const& rIDSA(pNewUpper->GetFormat()->getIDocumentSettingAccess()); + if ( bRet && IsInTab() && + rIDSA.get(DocumentSettingId::ADD_PARA_SPACING_TO_TABLE_CELLS)) + { + nSpace -= rAttrs.GetULSpace().GetLower(); + + if (rIDSA.get(DocumentSettingId::ADD_PARA_LINE_SPACING_TO_TABLE_CELLS)) + { + nSpace -= rAttrs.CalcLineSpacing(); + } + if ( nSpace < 0 ) + { + bRet = false; + } + } + + if (bRet && !bSplit && pFrame->IsKeep(rAttrs.GetAttrSet().GetKeep(), GetBreakItem())) + { + if( bTstMove ) + { + while( pFrame->IsTextFrame() && static_cast<SwTextFrame*>(pFrame)->HasFollow() ) + { + pFrame = static_cast<SwTextFrame*>(pFrame)->GetFollow(); + } + // If last follow frame of <this> 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:/n<SwContentFrame::WouldFit_(..) - follow not valid!" ); + return true; + } + } + SwFrame *pNxt; + if( nullptr != (pNxt = pFrame->FindNext()) && 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<SwTextFrame*>(pFrame)->IsHiddenNow() ) + pTmpPrev = lcl_NotHiddenPrev( pFrame ); + else + pTmpPrev = pFrame; + } + pFrame = static_cast<SwContentFrame*>(pNxt); + } + else + pFrame = nullptr; + } + else + pFrame = nullptr; + + } while ( bRet && pFrame ); + + return bRet; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ |