/* -*- 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 <config_wasm_strip.h>

#include <sal/config.h>
#include <sal/log.hxx>

#include <IDocumentRedlineAccess.hxx>
#include <anchoredobject.hxx>
#include <bodyfrm.hxx>
#include <hintids.hxx>
#include <editeng/keepitem.hxx>
#include <editeng/hyphenzoneitem.hxx>
#include <pagefrm.hxx>
#include <ndtxt.hxx>
#include <ftnfrm.hxx>
#include <txtftn.hxx>
#include <fmtftn.hxx>
#include <paratr.hxx>
#include <viewopt.hxx>
#include <viewsh.hxx>
#include <frmatr.hxx>
#include <pam.hxx>
#include <fmtanchr.hxx>
#include "itrform2.hxx"
#include "widorp.hxx"
#include <sectfrm.hxx>
#include <rootfrm.hxx>
#include <frmfmt.hxx>
#include <sortedobjs.hxx>
#include <editeng/tstpitem.hxx>
#include <redline.hxx>
#include <comphelper/lok.hxx>
#include <flyfrms.hxx>
#include <frmtool.hxx>
#include <layouter.hxx>
#include <fmtsrnd.hxx>

// Tolerance in formatting and text output
#define SLOPPY_TWIPS    5

namespace {

class FormatLevel
{
    static sal_uInt16 s_nLevel;
public:
    FormatLevel() { ++s_nLevel; }
    ~FormatLevel() { --s_nLevel; }
    static sal_uInt16 GetLevel() { return s_nLevel; }
    static bool LastLevel() { return 10 < s_nLevel; }
};

}

sal_uInt16 FormatLevel::s_nLevel = 0;

void ValidateText( SwFrame *pFrame )     // Friend of frame
{
    if ( ( ! pFrame->IsVertical() &&
             pFrame->getFrameArea().Width() == pFrame->GetUpper()->getFramePrintArea().Width() ) ||
         (   pFrame->IsVertical() &&
             pFrame->getFrameArea().Height() == pFrame->GetUpper()->getFramePrintArea().Height() ) )
    {
        pFrame->setFrameAreaSizeValid(true);
    }
}

void SwTextFrame::ValidateFrame()
{
    vcl::RenderContext* pRenderContext = getRootFrame()->GetCurrShell()->GetOut();
    // Validate surroundings to avoid oscillation
    SwSwapIfSwapped swap( this );

    if ( !IsInFly() && !IsInTab() )
    {   // Only validate 'this' when inside a fly, the rest should actually only be
        // needed for footnotes, which do not exist in flys.
        SwSectionFrame* pSct = FindSctFrame();
        if( pSct )
        {
            if( !pSct->IsColLocked() )
                pSct->ColLock();
            else
                pSct = nullptr;
        }

        SwFrame *pUp = GetUpper();
        pUp->Calc(pRenderContext);
        if( pSct )
            pSct->ColUnlock();
    }
    ValidateText( this );

    // We at least have to save the MustFit flag!
    assert(HasPara() && "ResetPreps(), missing ParaPortion, SwCache bug?");
    SwParaPortion *pPara = GetPara();
    const bool bMustFit = pPara->IsPrepMustFit();
    ResetPreps();
    pPara->SetPrepMustFit( bMustFit );
}

// After a RemoveFootnote the BodyFrame and all Frames contained within it, need to be
// recalculated, so that the DeadLine is right.
// First we search outwards, on the way back we calculate everything.
static void ValidateBodyFrame_( SwFrame *pFrame )
{
    vcl::RenderContext* pRenderContext = pFrame ? pFrame->getRootFrame()->GetCurrShell()->GetOut() : nullptr;
    if( !pFrame || pFrame->IsCellFrame() )
        return;

    if( !pFrame->IsBodyFrame() && pFrame->GetUpper() )
        ValidateBodyFrame_( pFrame->GetUpper() );
    if( !pFrame->IsSctFrame() )
        pFrame->Calc(pRenderContext);
    else
    {
        const bool bOld = static_cast<SwSectionFrame*>(pFrame)->IsContentLocked();
        static_cast<SwSectionFrame*>(pFrame)->SetContentLock( true );
        pFrame->Calc(pRenderContext);
        if( !bOld )
            static_cast<SwSectionFrame*>(pFrame)->SetContentLock( false );
    }
}

void SwTextFrame::ValidateBodyFrame()
{
    SwSwapIfSwapped swap( this );

     // See comment in ValidateFrame()
    if ( !IsInFly() && !IsInTab())
    {
        if (SwSectionFrame* pSctFrame = FindSctFrame())
        {
            SwFrame* pLower = pSctFrame->Lower();
            if (pLower && !pLower->IsColumnFrame())
                ValidateBodyFrame_( GetUpper() );
        }
    }
}

bool SwTextFrame::GetDropRect_( SwRect &rRect ) const
{
    SwSwapIfNotSwapped swap(const_cast<SwTextFrame *>(this));

    OSL_ENSURE( HasPara(), "SwTextFrame::GetDropRect_: try again next year." );
    SwTextSizeInfo aInf( const_cast<SwTextFrame*>(this) );
    SwTextMargin aLine( const_cast<SwTextFrame*>(this), &aInf );
    if( aLine.GetDropLines() )
    {
        rRect.Top( aLine.Y() );
        rRect.Left( aLine.GetLineStart() );
        rRect.Height( aLine.GetDropHeight() );
        rRect.Width( aLine.GetDropLeft() );

        if ( IsRightToLeft() )
            SwitchLTRtoRTL( rRect );

        if ( IsVertical() )
            SwitchHorizontalToVertical( rRect );
        return true;
    }

    return false;
}

bool SwTextFrame::CalcFollow(TextFrameIndex const nTextOfst)
{
    vcl::RenderContext* pRenderContext = getRootFrame()->GetCurrShell()->GetOut();
    SwSwapIfSwapped swap( this );

    OSL_ENSURE( HasFollow(), "CalcFollow: missing Follow." );

    SwTextFrame* pMyFollow = GetFollow();

    SwParaPortion *pPara = GetPara();
    const bool bFollowField = pPara && pPara->IsFollowField();

    if( !pMyFollow->GetOffset() || pMyFollow->GetOffset() != nTextOfst ||
        bFollowField || pMyFollow->IsFieldFollow() ||
        ( pMyFollow->IsVertical() && !pMyFollow->getFramePrintArea().Width() ) ||
        ( ! pMyFollow->IsVertical() && !pMyFollow->getFramePrintArea().Height() ) )
    {
#if OSL_DEBUG_LEVEL > 0
        const SwFrame *pOldUp = GetUpper();
#endif

        SwRectFnSet aRectFnSet(*this);
        SwTwips nOldBottom = aRectFnSet.GetBottom(GetUpper()->getFrameArea());
        SwTwips nMyPos = aRectFnSet.GetTop(getFrameArea());

        const SwPageFrame *pPage = nullptr;
        bool bOldInvaContent = true;
        if ( !IsInFly() && GetNext() )
        {
            pPage = FindPageFrame();
            // Minimize (reset if possible) invalidations: see below
            bOldInvaContent  = pPage->IsInvalidContent();
        }

        pMyFollow->SetOffset_( nTextOfst );
        pMyFollow->SetFieldFollow( bFollowField );
        if( HasFootnote() || pMyFollow->HasFootnote() )
        {
            ValidateFrame();
            ValidateBodyFrame();
            if( pPara )
            {
                pPara->GetReformat() = SwCharRange();
                pPara->SetDelta(0);
            }
        }

        // The footnote area must not get larger
        SwSaveFootnoteHeight aSave( FindFootnoteBossFrame( true ), LONG_MAX );

        pMyFollow->CalcFootnoteFlag();
        if ( !pMyFollow->GetNext() && !pMyFollow->HasFootnote() )
            nOldBottom =  aRectFnSet.IsVert() ? 0 : LONG_MAX;

        // tdf#122892 check flag:
        // 1. WidowsAndOrphans::FindWidows() determines follow is a widow
        // 2. SwTextFrame::PrepWidows() calls SetPrepWidows() on master;
        //    if it can spare lines, master truncates one line
        // 3. SwTextFrame::CalcPreps() on master (below);
        //    unless IsPrepMustFit(), if master hasn't shrunk via 2., it will SetWidow()
        // 4. loop must exit then, because the follow didn't grow so nothing will ever change
        while (!IsWidow())
        {
            if( !FormatLevel::LastLevel() )
            {
                // If the follow is contained within a column section or column
                // frame, we need to calculate that first. This is because the
                // FormatWidthCols() does not work if it is called from MakeAll
                // of the _locked_ follow.
                SwSectionFrame* pSct = pMyFollow->FindSctFrame();
                if( pSct && !pSct->IsAnLower( this ) )
                {
                    if( pSct->GetFollow() )
                        pSct->SimpleFormat();
                    else if( ( pSct->IsVertical() && !pSct->getFrameArea().Width() ) ||
                             ( ! pSct->IsVertical() && !pSct->getFrameArea().Height() ) )
                        break;
                }
                // i#11760 - Intrinsic format of follow is controlled.
                if ( FollowFormatAllowed() )
                {
                    // i#11760 - No nested format of follows, if
                    // text frame is contained in a column frame.
                    // Thus, forbid intrinsic format of follow.
                    {
                        bool bIsFollowInColumn = false;
                        SwFrame* pFollowUpper = pMyFollow->GetUpper();
                        while ( pFollowUpper )
                        {
                            if ( pFollowUpper->IsColumnFrame() )
                            {
                                bIsFollowInColumn = true;
                                break;
                            }
                            if ( pFollowUpper->IsPageFrame() ||
                                 pFollowUpper->IsFlyFrame() )
                            {
                                break;
                            }
                            pFollowUpper = pFollowUpper->GetUpper();
                        }
                        if ( bIsFollowInColumn )
                        {
                            pMyFollow->ForbidFollowFormat();
                        }
                    }

                    pMyFollow->Calc(pRenderContext);
                    // The Follow can tell from its getFrameArea().Height() that something went wrong
                    OSL_ENSURE( !pMyFollow->GetPrev(), "SwTextFrame::CalcFollow: cheesy follow" );
                    if( pMyFollow->GetPrev() )
                    {
                        pMyFollow->Prepare();
                        pMyFollow->Calc(pRenderContext);
                        OSL_ENSURE( !pMyFollow->GetPrev(), "SwTextFrame::CalcFollow: very cheesy follow" );
                    }

                    // i#11760 - Reset control flag for follow format.
                    pMyFollow->AllowFollowFormat();
                }

                // Make sure that the Follow gets painted
                pMyFollow->SetCompletePaint();
            }

            pPara = GetPara();
            // As long as the Follow requests lines due to Orphans, it is
            // passed these and is formatted again if possible
            if( pPara && pPara->IsPrepWidows() )
                CalcPreps();
            else
                break;
        }

        if( HasFootnote() || pMyFollow->HasFootnote() )
        {
            ValidateBodyFrame();
            ValidateFrame();
            if( pPara )
            {
                pPara->GetReformat() = SwCharRange();
                pPara->SetDelta(0);
            }
        }

        if ( pPage  && !bOldInvaContent )
            pPage->ValidateContent();

#if OSL_DEBUG_LEVEL > 0
        OSL_ENSURE( pOldUp == GetUpper(), "SwTextFrame::CalcFollow: heavy follow" );
#endif

        const tools::Long nRemaining =
                 - aRectFnSet.BottomDist( GetUpper()->getFrameArea(), nOldBottom );
        if (  nRemaining > 0 &&
              nRemaining != ( aRectFnSet.IsVert() ?
                              nMyPos - getFrameArea().Right() :
                              getFrameArea().Top() - nMyPos ) )
        {
            return true;
        }
    }

    return false;
}

void SwTextFrame::MakePos()
{
    Point aOldPos = getFrameArea().Pos();
    SwFrame::MakePos();

    // Recalc split flys if our position changed.
    if (aOldPos != getFrameArea().Pos())
    {
        // Find the master frame.
        const SwTextFrame* pMaster = this;
        while (pMaster->IsFollow())
        {
            pMaster = pMaster->FindMaster();
        }
        // Find which flys are effectively anchored to this frame.
        for (const auto& pFly : pMaster->GetSplitFlyDrawObjs())
        {
            SwTextFrame* pFlyAnchor = pFly->FindAnchorCharFrame();
            if (pFlyAnchor != this)
            {
                continue;
            }
            // Possibly this fly was positioned relative to us, invalidate its position now that our
            // position is changed.
            SwPageFrame* pPageFrame = pFly->FindPageFrame();
            bool bFlyNeedsPositioning = false;
            bool bFlyPageMismatch = false;
            if (pPageFrame)
            {
                // Was the position just adjusted to be inside the page frame?
                bFlyNeedsPositioning = pFly->getFrameArea().Pos() == pPageFrame->getFrameArea().Pos();
                // Is the fly on a page different than the anchor frame?
                bFlyPageMismatch = pPageFrame != FindPageFrame();
            }
            if (bFlyNeedsPositioning || bFlyPageMismatch)
            {
                // Not really positioned, unlock the position once to allow a recalc.
                pFly->UnlockPosition();
            }
            pFly->InvalidatePos();
        }
    }

    // Inform LOK clients about change in position of redlines (if any)
    if(!comphelper::LibreOfficeKit::isActive())
        return;

    SwTextNode const* pTextNode = GetTextNodeFirst();
    const SwRedlineTable& rTable = pTextNode->getIDocumentRedlineAccess().GetRedlineTable();
    for (SwRedlineTable::size_type nRedlnPos = 0; nRedlnPos < rTable.size(); ++nRedlnPos)
    {
        SwRangeRedline* pRedln = rTable[nRedlnPos];
        if (pTextNode->GetIndex() == pRedln->GetPoint()->GetNode().GetIndex())
        {
            pRedln->MaybeNotifyRedlinePositionModification(getFrameArea().Top());
            if (GetMergedPara()
                && pRedln->GetType() == RedlineType::Delete
                && pRedln->GetPoint()->GetNode() != pRedln->GetMark()->GetNode())
            {
                pTextNode = pRedln->End()->GetNode().GetTextNode();
            }
        }
    }
}

void SwTextFrame::AdjustFrame( const SwTwips nChgHght, bool bHasToFit )
{
    vcl::RenderContext* pRenderContext = getRootFrame()->GetCurrShell()->GetOut();
    if( IsUndersized() )
    {
        if( GetOffset() && !IsFollow() ) // A scrolled paragraph (undersized)
            return;
        SetUndersized( nChgHght == 0 || bHasToFit );
    }

    // AdjustFrame is called with a swapped frame during
    // formatting but the frame is not swapped during FormatEmpty
    SwSwapIfSwapped swap( this );
    SwRectFnSet aRectFnSet(*this);

    // The Frame's size variable is incremented by Grow or decremented by Shrink.
    // If the size cannot change, nothing should happen!
    if( nChgHght >= 0)
    {
        SwTwips nChgHeight = nChgHght;
        if( nChgHght && !bHasToFit )
        {
            if( IsInFootnote() && !IsInSct() )
            {
                SwTwips nReal = Grow( nChgHght, true );
                if( nReal < nChgHght )
                {
                    SwTwips nBot = aRectFnSet.YInc( aRectFnSet.GetBottom(getFrameArea()),
                                                      nChgHght - nReal );
                    SwFrame* pCont = FindFootnoteFrame()->GetUpper();

                    if( aRectFnSet.BottomDist( pCont->getFrameArea(), nBot ) > 0 )
                    {
                        SwFrameAreaDefinition::FrameAreaWriteAccess aFrm(*this);
                        aRectFnSet.AddBottom( aFrm, nChgHght );

                        SwFrameAreaDefinition::FramePrintAreaWriteAccess aPrt(*this);

                        if( aRectFnSet.IsVert() )
                        {
                            aPrt.AddWidth(nChgHght );
                        }
                        else
                        {
                            aPrt.AddHeight(nChgHght );
                        }

                        return;
                    }
                }
            }

            Grow( nChgHght );

            if ( IsInFly() )
            {
                // If one of the Upper is a Fly, it's very likely that this fly changes its
                // position by the Grow. Therefore, my position has to be corrected also or
                // the check further down is not meaningful.
                // The predecessors need to be calculated, so that the position can be
                // calculated correctly.
                if ( GetPrev() )
                {
                    SwFrame *pPre = GetUpper()->Lower();
                    do
                    {   pPre->Calc(pRenderContext);
                        pPre = pPre->GetNext();
                    } while ( pPre && pPre != this );
                }
                const Point aOldPos( getFrameArea().Pos() );
                MakePos();
                if ( aOldPos != getFrameArea().Pos() )
                {
                    InvalidateObjs(false);
                }
            }
            nChgHeight = 0;
        }
        // A Grow() is always accepted by the Layout, even if the
        // FixSize of the surrounding layout frame should not allow it.
        // We text for this case and correct the values.
        // The Frame must NOT be shrunk further than its size permits
        // even in the case of an emergency.
        SwTwips nRstHeight;
        if ( IsVertical() )
        {
            OSL_ENSURE( ! IsSwapped(),"Swapped frame while calculating nRstHeight" );

            if ( IsVertLR() )
                    nRstHeight = GetUpper()->getFrameArea().Left()
                               + GetUpper()->getFramePrintArea().Left()
                               + GetUpper()->getFramePrintArea().Width()
                               - getFrameArea().Left();
            else
                nRstHeight = getFrameArea().Left() + getFrameArea().Width() -
                            ( GetUpper()->getFrameArea().Left() + GetUpper()->getFramePrintArea().Left() );
        }
        else
            nRstHeight = GetUpper()->getFrameArea().Top()
                       + GetUpper()->getFramePrintArea().Top()
                       + GetUpper()->getFramePrintArea().Height()
                       - getFrameArea().Top();

        // We can get a bit of space in table cells, because there could be some
        // left through a vertical alignment to the top.
        // Assure that first lower in upper is the current one or is valid.
        if (IsInTab())
        {
            SwFrame* pLower = GetUpper()->Lower();
            if ( pLower == this || (pLower && pLower->isFrameAreaDefinitionValid()) )
            {
                tools::Long nAdd = aRectFnSet.YDiff( aRectFnSet.GetTop(pLower->getFrameArea()),
                                                aRectFnSet.GetPrtTop(*GetUpper()) );
                OSL_ENSURE( nAdd >= 0, "Ey" );
                nRstHeight += nAdd;
            }
        }

        // nRstHeight < 0 means that the TextFrame is located completely outside of its Upper.
        // This can happen, if it's located within a FlyAtContentFrame, which changed sides by a
        // Grow(). In such a case, it's wrong to execute the following Grow().
        // In the case of a bug, we end up with an infinite loop.
        SwTwips nFrameHeight = aRectFnSet.GetHeight(getFrameArea());
        SwTwips nPrtHeight = aRectFnSet.GetHeight(getFramePrintArea());

        if( nRstHeight < nFrameHeight )
        {
            // It can be that I have the right size, but the Upper is too small and can get me some room
            if( ( nRstHeight >= 0 || ( IsInFootnote() && IsInSct() ) ) && !bHasToFit )
                nRstHeight += GetUpper()->Grow( nFrameHeight - nRstHeight );
            // In column sections we do not want to get too big or else more areas are created by
            // GetNextSctLeaf. Instead, we shrink and remember bUndersized, so that FormatWidthCols
            // can calculate the right column size.
            if ( nRstHeight < nFrameHeight )
            {
                if( bHasToFit || !IsMoveable() ||
                    ( IsInSct() && !FindSctFrame()->MoveAllowed(this) ) )
                {
                    SetUndersized( true );
                    Shrink( std::min( ( nFrameHeight - nRstHeight), nPrtHeight ) );
                }
                else
                    SetUndersized( false );
            }
        }
        else if( nChgHeight )
        {
            if( nRstHeight - nFrameHeight < nChgHeight )
                nChgHeight = nRstHeight - nFrameHeight;
            if( nChgHeight )
                Grow( nChgHeight );
        }
    }
    else
        Shrink( -nChgHght );
}

css::uno::Sequence< css::style::TabStop > SwTextFrame::GetTabStopInfo( SwTwips CurrentPos )
{
    SwTextFormatInfo     aInf( getRootFrame()->GetCurrShell()->GetOut(), this );
    SwTextFormatter      aLine( this, &aInf );
    SwTextCursor         TextCursor( this, &aInf );
    const Point aCharPos( TextCursor.GetTopLeft() );

    SwTwips nRight = aLine.Right();
    CurrentPos -= aCharPos.X();

    // get current tab stop information stored in the Frame
    const SvxTabStop *pTS = aLine.GetLineInfo().GetTabStop( CurrentPos, nRight );

    if( !pTS )
    {
        return {};
    }

    // copy tab stop information into a Sequence, which only contains one element.
    css::style::TabStop ts;
    ts.Position = pTS->GetTabPos();
    ts.DecimalChar = pTS->GetDecimal();
    ts.FillChar = pTS->GetFill();
    switch( pTS->GetAdjustment() )
    {
    case SvxTabAdjust::Left   : ts.Alignment = css::style::TabAlign_LEFT; break;
    case SvxTabAdjust::Center : ts.Alignment = css::style::TabAlign_CENTER; break;
    case SvxTabAdjust::Right  : ts.Alignment = css::style::TabAlign_RIGHT; break;
    case SvxTabAdjust::Decimal: ts.Alignment = css::style::TabAlign_DECIMAL; break;
    case SvxTabAdjust::Default: ts.Alignment = css::style::TabAlign_DEFAULT; break;
    default: break; // prevent warning
    }

    return { ts };
}

// AdjustFollow expects the following situation:
// The SwTextIter points to the lower end of the Master, the Offset is set in the Follow.
// nOffset holds the Offset in the text string, from which the Master closes
// and the Follow starts.
// If it's 0, the FollowFrame is deleted.
void SwTextFrame::AdjustFollow_( SwTextFormatter &rLine,
                 const TextFrameIndex nOffset, const TextFrameIndex nEnd,
                             const bool bDontJoin)
{
    SwFrameSwapper aSwapper( this, false );

    // We got the rest of the text mass: Delete all Follows
    // DummyPortions() are a special case.
    if( HasFollow() && !bDontJoin && nOffset == nEnd )
    {
        while( GetFollow() )
        {
            if( GetFollow()->IsLocked() )
            {
                // this can happen when follow calls pMaster->GetFormatted()
                SAL_INFO("sw.core", "+SwTextFrame::JoinFrame: Follow is locked." );
                return;
            }
            if (GetFollow()->IsDeleteForbidden())
                return;

            if (HasNonLastSplitFlyDrawObj())
            {
                // If a fly frame is anchored to us that has a follow, then don't join the anchor.
                // First those fly frames have to be joined.
                return;
            }

            JoinFrame();
        }

        return;
    }

    // Dancing on the volcano: We'll just format the last line quickly
    // for the QuoVadis stuff.
    // The Offset can move of course:
    const TextFrameIndex nNewOfst = (IsInFootnote() && (!GetIndNext() || HasFollow()))
        ? rLine.FormatQuoVadis(nOffset) : nOffset;

    bool bHasNonLastSplitFlyDrawObj = false;
    if (GetFollow() && GetOffset() == GetFollow()->GetOffset())
    {
        bHasNonLastSplitFlyDrawObj = HasNonLastSplitFlyDrawObj();
    }

    if( !bDontJoin )
    {
        // We steal text mass from our Follows
        // It can happen that we have to join some of them
        while( GetFollow() && GetFollow()->GetFollow() &&
               nNewOfst >= GetFollow()->GetFollow()->GetOffset() )
        {
            if (bHasNonLastSplitFlyDrawObj)
            {
                // A non-last split fly is anchored to us, don't move content from the last frame to
                // this one and don't join.
                return;
            }

            JoinFrame();
        }
    }

    if (IsEmptyMasterWithSplitFly())
    {
        // A split fly is anchored to us, don't move content from the follow frame to this one.
        return;
    }

    // The Offset moved
    if( GetFollow() )
    {
        if (!bDontJoin && bHasNonLastSplitFlyDrawObj)
        {
            // A non-last split fly is anchored to us, our follow is the last one in the text frame
            // chain. No move of text from that follow to this text frame.
            return;
        }

        if (bDontJoin)
            GetFollow()->ManipOfst(TextFrameIndex(0));

        if ( CalcFollow( nNewOfst ) )   // CalcFollow only at the end, we do a SetOffset there
            rLine.SetOnceMore( true );
    }
}

SwContentFrame *SwTextFrame::JoinFrame()
{
    OSL_ENSURE( GetFollow(), "+SwTextFrame::JoinFrame: no follow" );
    SwTextFrame  *pFoll = GetFollow();

    SwTextFrame *pNxt = pFoll->GetFollow();

    // All footnotes of the to-be-destroyed Follow are relocated to us
    TextFrameIndex nStart = pFoll->GetOffset();
    if ( pFoll->HasFootnote() )
    {
        SwFootnoteBossFrame *pFootnoteBoss = nullptr;
        SwFootnoteBossFrame *pEndBoss = nullptr;
        SwTextNode const* pNode(nullptr);
        sw::MergedAttrIter iter(*pFoll);
        for (SwTextAttr const* pHt = iter.NextAttr(&pNode); pHt; pHt = iter.NextAttr(&pNode))
        {
            if (RES_TXTATR_FTN == pHt->Which()
                && nStart <= pFoll->MapModelToView(pNode, pHt->GetStart()))
            {
                if (pHt->GetFootnote().IsEndNote())
                {
                    if (!pEndBoss)
                        pEndBoss = pFoll->FindFootnoteBossFrame();
                    SwFootnoteBossFrame::ChangeFootnoteRef( pFoll, static_cast<const SwTextFootnote*>(pHt), this );
                }
                else
                {
                    if (!pFootnoteBoss)
                        pFootnoteBoss = pFoll->FindFootnoteBossFrame( true );
                    SwFootnoteBossFrame::ChangeFootnoteRef( pFoll, static_cast<const SwTextFootnote*>(pHt), this );
                }
                SetFootnote( true );
            }
        }
    }

#ifdef DBG_UTIL
    else if ( pFoll->isFramePrintAreaValid() ||
              pFoll->isFrameAreaSizeValid() )
    {
        pFoll->CalcFootnoteFlag();
        OSL_ENSURE( !pFoll->HasFootnote(), "Missing FootnoteFlag." );
    }
#endif

    pFoll->MoveFlyInCnt( this, nStart, TextFrameIndex(COMPLETE_STRING) );
    pFoll->SetFootnote( false );
    // i#27138
    // Notify accessibility paragraphs objects about changed CONTENT_FLOWS_FROM/_TO relation.
    // Relation CONTENT_FLOWS_FROM for current next paragraph will change
    // and relation CONTENT_FLOWS_TO for current previous paragraph, which
    // is <this>, will change.
#if !ENABLE_WASM_STRIP_ACCESSIBILITY
    {
        SwViewShell* pViewShell( pFoll->getRootFrame()->GetCurrShell() );
        if ( pViewShell && pViewShell->GetLayout() &&
             pViewShell->GetLayout()->IsAnyShellAccessible() )
        {
            auto pNext = pFoll->FindNextCnt( true );
            pViewShell->InvalidateAccessibleParaFlowRelation(
                            pNext ? pNext->DynCastTextFrame() : nullptr,
                            this );
        }
    }
#endif

    pFoll->Cut();
    SetFollow(pNxt);
    SwFrame::DestroyFrame(pFoll);
    return pNxt;
}

void SwTextFrame::SplitFrame(TextFrameIndex const nTextPos)
{
    SwSwapIfSwapped swap( this );

    // The Paste sends a Modify() to me
    // I lock myself, so that my data does not disappear
    TextFrameLockGuard aLock( this );
    SwTextFrame *const pNew = static_cast<SwTextFrame *>(GetTextNodeFirst()->MakeFrame(this));

    pNew->SetFollow( GetFollow() );
    SetFollow( pNew );

    pNew->Paste( GetUpper(), GetNext() );
    // i#27138
    // notify accessibility paragraphs objects about changed CONTENT_FLOWS_FROM/_TO relation.
    // Relation CONTENT_FLOWS_FROM for current next paragraph will change
    // and relation CONTENT_FLOWS_TO for current previous paragraph, which
    // is <this>, will change.
#if !ENABLE_WASM_STRIP_ACCESSIBILITY
    {
        SwViewShell* pViewShell( pNew->getRootFrame()->GetCurrShell() );
        if ( pViewShell && pViewShell->GetLayout() &&
             pViewShell->GetLayout()->IsAnyShellAccessible() )
        {
            auto pNext = pNew->FindNextCnt( true );
            pViewShell->InvalidateAccessibleParaFlowRelation(
                            pNext ? pNext->DynCastTextFrame() : nullptr,
                            this );
        }
    }
#endif

    // If footnotes end up in pNew bz our actions, we need
    // to re-register them
    if ( HasFootnote() )
    {
        SwFootnoteBossFrame *pFootnoteBoss = nullptr;
        SwFootnoteBossFrame *pEndBoss = nullptr;
        SwTextNode const* pNode(nullptr);
        sw::MergedAttrIter iter(*this);
        for (SwTextAttr const* pHt = iter.NextAttr(&pNode); pHt; pHt = iter.NextAttr(&pNode))
        {
            if (RES_TXTATR_FTN == pHt->Which()
                && nTextPos <= MapModelToView(pNode, pHt->GetStart()))
            {
                if (pHt->GetFootnote().IsEndNote())
                {
                    if (!pEndBoss)
                        pEndBoss = FindFootnoteBossFrame();
                    SwFootnoteBossFrame::ChangeFootnoteRef( this, static_cast<const SwTextFootnote*>(pHt), pNew );
                }
                else
                {
                    if (!pFootnoteBoss)
                        pFootnoteBoss = FindFootnoteBossFrame( true );
                    SwFootnoteBossFrame::ChangeFootnoteRef( this, static_cast<const SwTextFootnote*>(pHt), pNew );
                }
                pNew->SetFootnote( true );
            }
        }
    }

#ifdef DBG_UTIL
    else
    {
        CalcFootnoteFlag( nTextPos - TextFrameIndex(1) );
        OSL_ENSURE( !HasFootnote(), "Missing FootnoteFlag." );
    }
#endif

    MoveFlyInCnt( pNew, nTextPos, TextFrameIndex(COMPLETE_STRING) );

    // No SetOffset or CalcFollow, because an AdjustFollow follows immediately anyways

    pNew->ManipOfst( nTextPos );
}

void SwTextFrame::SetOffset_(TextFrameIndex const nNewOfst)
{
    // We do not need to invalidate our Follow.
    // We are a Follow, get formatted right away and call
    // SetOffset() from there
    mnOffset = nNewOfst;
    SwParaPortion *pPara = GetPara();
    if( pPara )
    {
        SwCharRange &rReformat = pPara->GetReformat();
        rReformat.Start() = TextFrameIndex(0);
        rReformat.Len() = TextFrameIndex(GetText().getLength());
        pPara->SetDelta(sal_Int32(rReformat.Len()));
    }
    InvalidateSize();
}

bool SwTextFrame::CalcPreps()
{
    OSL_ENSURE( ! IsVertical() || ! IsSwapped(), "SwTextFrame::CalcPreps with swapped frame" );
    SwRectFnSet aRectFnSet(*this);

    SwParaPortion *pPara = GetPara();
    if ( !pPara )
        return false;
    const bool bPrep = pPara->IsPrep();
    const bool bPrepWidows = pPara->IsPrepWidows();
    const bool bPrepAdjust = pPara->IsPrepAdjust();
    const bool bPrepMustFit = pPara->IsPrepMustFit();
    ResetPreps();

    bool bRet = false;
    if( bPrep && !pPara->GetReformat().Len() )
    {
        // PrepareHint::Widows means that the orphans rule got activated in the Follow.
        // In unfortunate cases we could also have a PrepAdjust!
        if( bPrepWidows )
        {
            if( !GetFollow() )
            {
                OSL_ENSURE( GetFollow(), "+SwTextFrame::CalcPreps: no credits" );
                return false;
            }

            // We need to prepare for two cases:
            // We were able to hand over a few lines to the Follow
            // -> we need to shrink
            // or we need to go on the next page
            // -> we let our Frame become too big

            SwTwips nChgHeight = GetParHeight();
            if( nChgHeight >= aRectFnSet.GetHeight(getFramePrintArea()) )
            {
                if( bPrepMustFit )
                {
                    GetFollow()->SetJustWidow( true );
                    GetFollow()->Prepare();
                }
                else if ( aRectFnSet.IsVert() )
                {
                    // Replicate the same overflow behavior that is used for horizontal portions.
                    SwTwips const nTmp = sw::WIDOW_MAGIC - (getFrameArea().Left() + 10000);
                    SwTwips nDiff = nTmp - getFrameArea().Width();

                    {
                        SwFrameAreaDefinition::FrameAreaWriteAccess aFrm(*this);
                        aFrm.Width(nTmp);
                    }

                    {
                        SwFrameAreaDefinition::FramePrintAreaWriteAccess aPrt(*this);
                        aPrt.Width(aPrt.Width() + nDiff);
                    }

                    SetWidow( true );
                }
                else
                {
                    // nTmp should be very large, but not so large as to cause overflow later (e.g.,
                    // GetFrameOfModify in sw/source/core/layout/frmtool.cxx calculates nCurrentDist
                    // from, among others, the square of aDiff.getY(), which can be close to nTmp);
                    // the previously used value TWIPS_MAX/2 (i.e., (LONG_MAX - 1)/2) depended on
                    // the range of 'long', while the value (SAL_MAX_INT32 - 1)/2 (which matches the
                    // old value on platforms where 'long' is 'sal_Int32') is empirically shown to
                    // be large enough in practice even on platforms where 'long' is 'sal_Int64':
                    SwTwips const nTmp = sw::WIDOW_MAGIC - (getFrameArea().Top()+10000);
                    SwTwips nDiff = nTmp - getFrameArea().Height();

                    {
                        SwFrameAreaDefinition::FrameAreaWriteAccess aFrm(*this);
                        aFrm.Height( nTmp );
                    }

                    {
                        SwFrameAreaDefinition::FramePrintAreaWriteAccess aPrt(*this);
                        aPrt.Height( aPrt.Height() + nDiff );
                    }

                    SetWidow( true );
                }
            }
            else
            {
                OSL_ENSURE( nChgHeight < aRectFnSet.GetHeight(getFramePrintArea()),
                        "+SwTextFrame::CalcPrep: want to shrink" );

                nChgHeight = aRectFnSet.GetHeight(getFramePrintArea()) - nChgHeight;

                GetFollow()->SetJustWidow( true );
                GetFollow()->Prepare();
                Shrink( nChgHeight );
                SwRect &rRepaint = pPara->GetRepaint();

                if ( aRectFnSet.IsVert() )
                {
                    SwRect aRepaint( getFrameArea().Pos() + getFramePrintArea().Pos(), getFramePrintArea().SSize() );
                    SwitchVerticalToHorizontal( aRepaint );
                    rRepaint.Chg( aRepaint.Pos(), aRepaint.SSize() );
                }
                else
                    rRepaint.Chg( getFrameArea().Pos() + getFramePrintArea().Pos(), getFramePrintArea().SSize() );

                if( 0 >= rRepaint.Width() )
                    rRepaint.Width(1);
            }
            bRet = true;
        }
        else if ( bPrepAdjust )
        {
            if ( HasFootnote() )
            {
                if( !CalcPrepFootnoteAdjust() )
                {
                    if( bPrepMustFit )
                        EnsurePara()->SetPrepMustFit(true);
                    return false;
                }
            }

            {
                SwSwapIfNotSwapped swap( this );

                SwTextFormatInfo aInf( getRootFrame()->GetCurrShell()->GetOut(), this );
                SwTextFormatter aLine( this, &aInf );

                WidowsAndOrphans aFrameBreak( this );
                // Whatever the attributes say: we split the paragraph in
                // MustFit case if necessary
                if( bPrepMustFit )
                {
                    aFrameBreak.SetKeep( false );
                    aFrameBreak.ClrOrphLines();
                }
                // Before calling FormatAdjust, we need to make sure
                // that the lines protruding at the bottom get indeed
                // truncated
                bool bBreak = aFrameBreak.IsBreakNowWidAndOrp( aLine );
                bRet = true;
                while( !bBreak && aLine.Next() )
                {
                    bBreak = aFrameBreak.IsBreakNowWidAndOrp( aLine );
                }
                if( bBreak )
                {
                    // We run into troubles: when TruncLines is called, the
                    // conditions in IsInside change immediately such that
                    // IsBreakNow can return different results.
                    // For this reason, we tell rFrameBreak that the
                    // end is reached at the location of rLine.
                    // Let's see if it works ...
                    aLine.TruncLines();
                    aFrameBreak.SetRstHeight( aLine );
                    FormatAdjust( aLine, aFrameBreak, TextFrameIndex(aInf.GetText().getLength()), aInf.IsStop() );
                }
                else
                {
                    if( !GetFollow() )
                    {
                        FormatAdjust( aLine, aFrameBreak,
                            TextFrameIndex(aInf.GetText().getLength()), aInf.IsStop() );
                    }
                    else if ( !aFrameBreak.IsKeepAlways() )
                    {
                        // We delete a line before the Master, because the Follow
                        // could hand over a line
                        const SwCharRange aFollowRg(GetFollow()->GetOffset(), TextFrameIndex(1));
                        pPara->GetReformat() += aFollowRg;
                        // We should continue!
                        bRet = false;
                    }
                }
            }

            // A final check, if FormatAdjust() didn't help we need to
            // truncate
            if( bPrepMustFit )
            {
                const SwTwips nMust = aRectFnSet.GetPrtBottom(*GetUpper());
                const SwTwips nIs   = aRectFnSet.GetBottom(getFrameArea());

                if( aRectFnSet.IsVert() && nIs < nMust )
                {
                    Shrink( nMust - nIs );

                    if( getFramePrintArea().Width() < 0 )
                    {
                        SwFrameAreaDefinition::FramePrintAreaWriteAccess aPrt(*this);
                        aPrt.Width( 0 );
                    }

                    SetUndersized( true );
                }
                else if ( ! aRectFnSet.IsVert() && nIs > nMust )
                {
                    Shrink( nIs - nMust );

                    if( getFramePrintArea().Height() < 0 )
                    {
                        SwFrameAreaDefinition::FramePrintAreaWriteAccess aPrt(*this);
                        aPrt.Height( 0 );
                    }

                    SetUndersized( true );
                }
            }
        }
    }
    pPara->SetPrepMustFit( bPrepMustFit );
    return bRet;
}

// Move the as-character objects - footnotes must be moved by RemoveFootnote!
void SwTextFrame::ChangeOffset( SwTextFrame* pFrame, TextFrameIndex nNew )
{
    if( pFrame->GetOffset() < nNew )
        pFrame->MoveFlyInCnt( this, TextFrameIndex(0), nNew );
    else if( pFrame->GetOffset() > nNew )
        MoveFlyInCnt( pFrame, nNew, TextFrameIndex(COMPLETE_STRING) );
}

static bool isFirstVisibleFrameInPageBody(const SwTextFrame* pFrame)
{
    const SwFrame* pBodyFrame = pFrame->FindBodyFrame();
    while (pBodyFrame && !pBodyFrame->IsPageBodyFrame())
        pBodyFrame = pBodyFrame->GetUpper()->FindBodyFrame();
    if (!pBodyFrame)
        return false;
    for (const SwFrame* pCur = pFrame;;)
    {
        for (const SwFrame* pPrev = pCur->GetPrev(); pPrev; pPrev = pPrev->GetPrev())
            if (!pPrev->IsHiddenNow())
                return false;
        pCur = pCur->GetUpper();
        assert(pCur); // We found pBodyFrame, right?
        if (pCur == pBodyFrame)
            return true;
    }
}

static bool hasFly(const SwTextFrame* pFrame)
{
    if (auto pDrawObjs = pFrame->GetDrawObjs(); pDrawObjs && pDrawObjs->size())
    {
        auto anchorId = (*pDrawObjs)[0]->GetFrameFormat()->GetAnchor().GetAnchorId();
        if (anchorId == RndStdIds::FLY_AT_PARA || anchorId == RndStdIds::FLY_AT_CHAR)
            return true;
    }
    return false;
}

static bool hasAtPageFly(const SwFrame* pFrame)
{
    auto pPageFrame = pFrame->FindPageFrame();
    if (!pPageFrame)
        return false;
    auto pPageDrawObjs = pPageFrame->GetDrawObjs();
    if (pPageDrawObjs)
    {
        for (const auto pObject : *pPageDrawObjs)
            if (pObject->GetFrameFormat()->GetAnchor().GetAnchorId() == RndStdIds::FLY_AT_PAGE)
                return true;
    }
    return false;
}

static bool isReallyEmptyMaster(const SwTextFrame* pFrame)
{
    return pFrame->IsEmptyMaster() && (!pFrame->GetDrawObjs() || !pFrame->GetDrawObjs()->size());
}

namespace
{
/// Determines if pFrame has at least one anchored object which is positioned against the page frame
/// and uses all space available for body text.
bool HasFullPageFly(const SwTextFrame* pFrame)
{
    const SwFrame* pBodyFrame = pFrame->FindBodyFrame();
    if (!pBodyFrame)
    {
        // Inside a fly frame, not interesting.
        return false;
    }

    const SwRect& rBodyFrameArea = pBodyFrame->getFrameArea();
    const SwSortedObjs* pDrawObjs = pFrame->GetDrawObjs();
    if (!pDrawObjs)
    {
        return false;
    }

    for (SwAnchoredObject* pDrawObj : *pDrawObjs)
    {
        SwFrameFormat* pFrameFormat = pDrawObj->GetFrameFormat();
        if (pFrameFormat->GetHoriOrient().GetRelationOrient() != text::RelOrientation::PAGE_FRAME)
        {
            continue;
        }

        if (pFrameFormat->GetVertOrient().GetRelationOrient() != text::RelOrientation::PAGE_FRAME)
        {
            continue;
        }

        if (pFrameFormat->GetSurround().GetValue() != text::WrapTextMode::WrapTextMode_NONE)
        {
            // Not a case where the request is to wrap the content around the object, ignore.
            continue;
        }

        if (pDrawObj->GetObjRectWithSpaces().Contains(rBodyFrameArea))
        {
            // Wrap is requested, but the object uses all available space: this is a full page
            // object.
            return true;
        }
    }

    return false;
}
}

void SwTextFrame::FormatAdjust( SwTextFormatter &rLine,
                             WidowsAndOrphans &rFrameBreak,
                             TextFrameIndex const nStrLen,
                             const bool bDummy )
{
    SwSwapIfNotSwapped swap( this );

    SwParaPortion *pPara = rLine.GetInfo().GetParaPortion();

    TextFrameIndex nEnd = rLine.GetStart();

    const bool bHasToFit = pPara->IsPrepMustFit();

    // The StopFlag is set by footnotes which want to go onto the next page
    // Call base class method <SwTextFrameBreak::IsBreakNow(..)>
    // instead of method <WidowsAndOrphans::IsBreakNow(..)> to get a break,
    // even if due to widow rule no enough lines exists.
    bool createNew = ( !GetFollow() &&
                       nEnd < nStrLen &&
                       ( rLine.IsStop() ||
                         ( bHasToFit
                           ? ( rLine.GetLineNr() > 1 &&
                               !rFrameBreak.IsInside( rLine ) )
                           : rFrameBreak.IsBreakNow( rLine ) ) ) );

    SwTextFormatInfo& rInf = rLine.GetInfo();
    bool bEmptyWithSplitFly = false;
    if (!createNew && !nStrLen && !rInf.GetTextFly().IsOn() && IsEmptyWithSplitFly())
    {
        // Empty paragraph, so IsBreakNow() is not called, but we should split the fly portion and
        // the paragraph marker.
        createNew = true;
        bEmptyWithSplitFly = true;
    }

    const SwFrame *pBodyFrame = FindBodyFrame();

    // i#84870
    // no split of text frame, which only contains an as-character anchored object
    bool bLoneAsCharAnchoredObj =
            pBodyFrame &&
            !IsFollow() && nStrLen == TextFrameIndex(1) &&
            GetDrawObjs() && GetDrawObjs()->size() == 1 &&
            (*GetDrawObjs())[0]->GetFrameFormat()->GetAnchor().GetAnchorId() == RndStdIds::FLY_AS_CHAR;

    if (bLoneAsCharAnchoredObj)
    {
        // Still try split text frame if we have columns.
        if (FindColFrame())
            bLoneAsCharAnchoredObj = false;
        // tdf#160526: only no split if there is no preceding frames on same page
        else if (!isFirstVisibleFrameInPageBody(this))
            bLoneAsCharAnchoredObj = false;
        else
            createNew = false;
    }
    else if (createNew)
    {
        if (IsFollow())
        {
            // tdf#160549: do not split the frame at the very beginning again, if its master was empty
            auto precede = static_cast<SwTextFrame*>(GetPrecede());
            assert(precede);
            auto precedeText = precede->DynCastTextFrame();
            assert(precedeText);
            if (isReallyEmptyMaster(precedeText))
                createNew = false;
        }
        else if (!bEmptyWithSplitFly)
        {
            // Do not split immediately in the beginning of page (unless there is an at-para or
            // at-char or at-page fly, which pushes the rest down); tdf#136040: still try split text
            // frame if we have columns.
            if (pBodyFrame && !FindColFrame() && isFirstVisibleFrameInPageBody(this)
                && !hasFly(this) && !hasAtPageFly(pBodyFrame))
                createNew = false;
        }
    }

    if (createNew && nEnd == TextFrameIndex(0) && !bEmptyWithSplitFly && HasFullPageFly(this))
    {
        // We intended to split at start, due to an anchored object which would use all space on the
        // current page. It makes no sense to split & move all text of the frame forward: the
        // current page would be empty and we would move back later anyway.
        createNew = false;
    }

    if (createNew)
    {
        SplitFrame( nEnd );
    }
    bool dontJoin = createNew;

    const tools::Long nBodyHeight = pBodyFrame ? ( IsVertical() ?
                                          pBodyFrame->getFrameArea().Width() :
                                          pBodyFrame->getFrameArea().Height() ) : 0;

    // If the current values have been calculated, show that they
    // are valid now
    pPara->GetReformat() = SwCharRange();
    bool bDelta = pPara->GetDelta() != 0;
    pPara->SetDelta(0);

    if( rLine.IsStop() )
    {
        rLine.TruncLines( true );
        dontJoin = true;
    }

    // FindBreak truncates the last line
    if( !rFrameBreak.FindBreak( this, rLine, bHasToFit ) )
    {
        // If we're done formatting, we set nEnd to the end.
        // AdjustFollow might execute JoinFrame() because of this.
        // Else, nEnd is the end of the last line in the Master.
        TextFrameIndex nOld = nEnd;
        // Make sure content from the last floating table anchor is not shifted to previous anchors.
        if (!HasNonLastSplitFlyDrawObj())
        {
            nEnd = rLine.GetEnd();
        }
        if( GetFollow() )
        {
            if (dontJoin && nOld < nEnd)
                RemoveFootnote( nOld, nEnd - nOld );
            ChangeOffset( GetFollow(), nEnd );
            if( !bDelta )
                GetFollow()->ManipOfst( nEnd );
        }
    }
    else
    {   // If we pass over lines, we must not call Join in Follows, instead we even
        // need to create a Follow.
        // We also need to do this if the whole mass of text remains in the Master,
        // because a hard line break could necessitate another line (without text mass)!
        TextFrameIndex const nOld(nEnd);
        nEnd = rLine.GetEnd();
        if( GetFollow() )
        {
            // Another case for not joining the follow:
            // Text frame has no content, but a numbering. Then, do *not* join.
            // Example of this case: When an empty, but numbered paragraph
            // at the end of page is completely displaced by a fly frame.
            // Thus, the text frame introduced a follow by a
            // <SwTextFrame::SplitFrame(..)> - see below. The follow then shows
            // the numbering and must stay.
            if ( GetFollow()->GetOffset() != nEnd ||
                 GetFollow()->IsFieldFollow() ||
                 (nStrLen == TextFrameIndex(0) && GetTextNodeForParaProps()->GetNumRule()))
            {
                dontJoin = true;
            }
            else if (FindTabFrame() && nEnd > TextFrameIndex(0) &&
                rLine.GetInfo().GetChar(nEnd - TextFrameIndex(1)) == CH_BREAK)
            {
                // We are in a table, the paragraph has a follow and the text
                // ends with a hard line break. Don't join the follow just
                // because the follow would have no content, we may still need it
                // for the paragraph mark.
                dontJoin = true;
            }
            // move footnotes if the follow is kept - if RemoveFootnote() is
            // called in next format iteration, it will be with the *new*
            // offset so no effect!
            if (dontJoin && nOld < nEnd)
            {
                RemoveFootnote(nOld, nEnd - nOld);
            }
            ChangeOffset( GetFollow(), nEnd );

            SwFlyAtContentFrame* pNonLastSplitFlyDrawObj = HasNonLastSplitFlyDrawObj();
            if (pNonLastSplitFlyDrawObj && !pNonLastSplitFlyDrawObj->IsWrapOnAllPages())
            {
                // Make sure content from the last floating table anchor is not shifted to previous
                // anchors, unless we're in the special "wrap on all pages" mode.
                nEnd = TextFrameIndex(0);
            }

            GetFollow()->ManipOfst( nEnd );
        }
        else
        {
            const SwTextNode* pTextNode = GetTextNodeForParaProps();
            bool bHasVisibleNumRule = nStrLen == TextFrameIndex(0) && pTextNode->GetNumRule();

            if (!pTextNode->HasVisibleNumberingOrBullet())
            {
                bHasVisibleNumRule = false;
            }

            // Only split frame, if the frame contains
            // content or contains no content, but has a numbering.
            // i#84870 - No split, if text frame only contains one
            // as-character anchored object.
            if (!bLoneAsCharAnchoredObj
                && (bHasVisibleNumRule
                    || (nStrLen > TextFrameIndex(0)
                        && (nEnd != rLine.GetStart() || rInf.GetRest()
                            // tdf#164718: Multiline field?
                            || rLine.GetCurr()->IsRest())))
               )
            {
                SplitFrame( nEnd );
                dontJoin = true;
            }
        }
        // If the remaining height changed e.g by RemoveFootnote() we need to
        // fill up in order to avoid oscillation.
        if( bDummy && pBodyFrame &&
           nBodyHeight < ( IsVertical() ?
                           pBodyFrame->getFrameArea().Width() :
                           pBodyFrame->getFrameArea().Height() ) )
            rLine.MakeDummyLine();
    }

    // In AdjustFrame() we set ourselves via Grow/Shrink
    // In AdjustFollow() we set our FollowFrame

    const SwTwips nDocPrtTop = getFrameArea().Top() + getFramePrintArea().Top();
    const SwTwips nOldHeight = getFramePrintArea().SSize().Height();
    SwTwips nChg = rLine.CalcBottomLine() - nDocPrtTop - nOldHeight;

    //#i84870# - no shrink of text frame, if it only contains one as-character anchored object.
    if (nChg < 0 && !bDelta && bLoneAsCharAnchoredObj)
    {
        nChg = 0;
    }

    // Vertical Formatting:
    // The (rotated) repaint rectangle's x coordinate refers to the frame.
    // If the frame grows (or shirks) the repaint rectangle cannot simply
    // be rotated back after formatting, because we use the upper left point
    // of the frame for rotation. This point changes when growing/shrinking.

    if ( IsVertical() && !IsVertLR() && nChg )
    {
        SwRect &rRepaint = pPara->GetRepaint();
        rRepaint.Left( rRepaint.Left() - nChg );
        rRepaint.Width( rRepaint.Width() - nChg );
    }

    AdjustFrame( nChg, bHasToFit );

    if( HasFollow() || IsInFootnote() )
        AdjustFollow_(rLine, nEnd, nStrLen, dontJoin);

    pPara->SetPrepMustFit( false );
}

// bPrev is set whether Reformat.Start() was called because of Prev().
// Else, we don't know whether we can limit the repaint or not.
bool SwTextFrame::FormatLine( SwTextFormatter &rLine, const bool bPrev )
{
    OSL_ENSURE( ! IsVertical() || IsSwapped(),
            "SwTextFrame::FormatLine( rLine, bPrev) with unswapped frame" );
    SwParaPortion *pPara = rLine.GetInfo().GetParaPortion();
    const SwLineLayout *pOldCur = rLine.GetCurr();
    const TextFrameIndex nOldLen = pOldCur->GetLen();
    const SwTwips nOldAscent = pOldCur->GetAscent();
    const SwTwips nOldHeight = pOldCur->Height();
    const SwTwips nOldWidth = pOldCur->Width() + pOldCur->GetHangingMargin();
    const bool bOldHyph = pOldCur->IsEndHyph();
    SwTwips nOldTop = 0;
    SwTwips nOldBottom = 0;
    if( rLine.GetCurr()->IsClipping() )
        rLine.CalcUnclipped( nOldTop, nOldBottom );

    TextFrameIndex const nNewStart = rLine.FormatLine( rLine.GetStart() );

    OSL_ENSURE( getFrameArea().Pos().Y() + getFramePrintArea().Pos().Y() == rLine.GetFirstPos(),
            "SwTextFrame::FormatLine: frame leaves orbit." );
    OSL_ENSURE( rLine.GetCurr()->Height(),
            "SwTextFrame::FormatLine: line height is zero" );

    // The current line break object
    const SwLineLayout *pNew = rLine.GetCurr();

    bool bUnChg = nOldLen == pNew->GetLen() &&
                  bOldHyph == pNew->IsEndHyph();
    if ( bUnChg && !bPrev )
    {
        const tools::Long nWidthDiff = nOldWidth > pNew->Width()
                                ? nOldWidth - pNew->Width()
                                : pNew->Width() - nOldWidth;

        // we only declare a line as unchanged, if its main values have not
        // changed and it is not the last line (!paragraph end symbol!)
        bUnChg = nOldHeight == pNew->Height() &&
                 nOldAscent == pNew->GetAscent() &&
                 nWidthDiff <= SLOPPY_TWIPS &&
                 pOldCur->GetNext();
    }

    // Calculate rRepaint
    const SwTwips nBottom = rLine.Y() + rLine.GetLineHeight();
    SwRepaint &rRepaint = pPara->GetRepaint();
    if( bUnChg && rRepaint.Top() == rLine.Y()
               && (bPrev || nNewStart <= pPara->GetReformat().Start())
               && (nNewStart < TextFrameIndex(GetText().getLength())))
    {
        rRepaint.Top( nBottom );
        rRepaint.Height( 0 );
    }
    else
    {
        if( nOldTop )
        {
            if( nOldTop < rRepaint.Top() )
                rRepaint.Top( nOldTop );
            if( !rLine.IsUnclipped() || nOldBottom > rRepaint.Bottom() )
            {
                rRepaint.Bottom( nOldBottom - 1 );
                rLine.SetUnclipped( true );
            }
        }
        if( rLine.GetCurr()->IsClipping() && rLine.IsFlyInCntBase() )
        {
            SwTwips nTmpTop, nTmpBottom;
            rLine.CalcUnclipped( nTmpTop, nTmpBottom );
            if( nTmpTop < rRepaint.Top() )
                rRepaint.Top( nTmpTop );
            if( !rLine.IsUnclipped() || nTmpBottom > rRepaint.Bottom() )
            {
                rRepaint.Bottom( nTmpBottom - 1 );
                rLine.SetUnclipped( true );
            }
        }
        else
        {
            if( !rLine.IsUnclipped() || nBottom > rRepaint.Bottom() )
            {
                rRepaint.Bottom( nBottom - 1 );
                rLine.SetUnclipped( false );
            }
        }
        SwTwips nRght = std::max( nOldWidth, pNew->Width() +
                             pNew->GetHangingMargin() );
        SwViewShell *pSh = getRootFrame()->GetCurrShell();
        const SwViewOption *pOpt = pSh ? pSh->GetViewOptions() : nullptr;
        if( pOpt && (pOpt->IsParagraph() || pOpt->IsLineBreak()) )
            nRght += ( std::max( nOldAscent, pNew->GetAscent() ) );
        else
            nRght += ( std::max( nOldAscent, pNew->GetAscent() ) / 4);
        nRght += rLine.GetLeftMargin();
        if( rRepaint.GetOffset() || rRepaint.GetRightOfst() < nRght )
            rRepaint.SetRightOfst( nRght );

        // Finally we enlarge the repaint rectangle if we found an underscore
        // or another glyph extending beyond the line height within the line.
        auto nBaseAscent = pNew->GetAscent();
        auto nMaxExtraAscent
            = std::max({ SwTwips{ 0 }, rLine.GetInfo().GetExtraAscent() - nBaseAscent,
                         rLine.GetCurr()->GetExtraAscent() });
        rRepaint.Top(rRepaint.Top() - nMaxExtraAscent);
        const_cast<SwLineLayout*>(rLine.GetCurr())->SetExtraAscent(nMaxExtraAscent);

        auto nBaseDescent = pNew->Height() - pNew->GetAscent();
        auto nMaxExtraDescent
            = std::max({ SwTwips{ 0 }, rLine.GetInfo().GetExtraDescent() - nBaseDescent,
                         rLine.GetCurr()->GetExtraDescent() });
        rRepaint.Bottom(rRepaint.Bottom() + nMaxExtraDescent);
        const_cast<SwLineLayout*>(rLine.GetCurr())->SetExtraDescent(nMaxExtraDescent);
    }

    // Calculating the good ol' nDelta
    const sal_Int32 nDiff = sal_Int32(pNew->GetLen()) - sal_Int32(nOldLen);
    pPara->SetDelta(pPara->GetDelta() - nDiff);

    // Stop!
    if( rLine.IsStop() )
        return false;

    // Absolutely another line
    if( rLine.IsNewLine() )
        return true;

    // Until the String's end?
    if (nNewStart >= TextFrameIndex(GetText().getLength()))
        return false;

    if( rLine.GetInfo().IsShift() )
        return true;

    // Reached the Reformat's end?
    const TextFrameIndex nEnd = pPara->GetReformat().Start() +
                        pPara->GetReformat().Len();

    if( nNewStart <= nEnd )
        return true;

    return 0 != pPara->GetDelta();
}

void SwTextFrame::Format_( SwTextFormatter &rLine, SwTextFormatInfo &rInf,
                        const bool bAdjust )
{
    OSL_ENSURE( ! IsVertical() || IsSwapped(),"SwTextFrame::Format_ with unswapped frame" );

    SwParaPortion *pPara = rLine.GetInfo().GetParaPortion();
    rLine.SetUnclipped( false );

    const OUString & rString = GetText();
    const TextFrameIndex nStrLen(rString.getLength());

    SwCharRange &rReformat = pPara->GetReformat();
    SwRepaint   &rRepaint = pPara->GetRepaint();
    std::unique_ptr<SwRepaint> pFreeze;

    // Due to performance reasons we set rReformat to COMPLETE_STRING in Init()
    // In this case we adjust rReformat
    if (auto nMax = nStrLen - rReformat.Start(); rReformat.Len() > nMax)
        rReformat.Len() = nMax;

    SwTwips nOldBottom;
    if( GetOffset() && !IsFollow() )
    {
        rLine.Bottom();
        nOldBottom = rLine.Y();
        rLine.Top();
    }
    else
        nOldBottom = 0;
    rLine.CharToLine( rReformat.Start() );

    // When inserting or removing a Space, words can be moved out of the edited
    // line and into the preceding line, hence the preceding line must be
    // formatted as well.
    // Optimization: If rReformat starts after the first word of the line,
    // this line cannot possibly influence the previous one.
    // ...Turns out that unfortunately it can: Text size changes + FlyFrames;
    // the feedback can affect multiple lines (Frames!)!

    // i#46560
    // FME: Yes, consider this case: "(word )" has to go to the next line
    // because ")" is a forbidden character at the beginning of a line although
    // "(word" would still fit on the previous line. Adding text right in front
    // of ")" would not trigger a reformatting of the previous line. Adding 1
    // to the result of FindBrk() does not solve the problem in all cases,
    // nevertheless it should be sufficient.
    bool bPrev = rLine.GetPrev() &&
                     (FindBrk(rString, rLine.GetStart(), rReformat.Start() + TextFrameIndex(1))
                       // i#46560
                       + TextFrameIndex(1)
                       >= rReformat.Start() ||
                       rLine.GetCurr()->IsRest() );
    if( bPrev )
    {
        while( rLine.Prev() )
            if( rLine.GetCurr()->GetLen() && !rLine.GetCurr()->IsRest() )
            {
                if( !rLine.GetStart() )
                    rLine.Top(); // So that NumDone doesn't get confused
                break;
            }
        TextFrameIndex nNew = rLine.GetStart() + rLine.GetLength();
        if( nNew )
        {
            --nNew;
            if (CH_BREAK == rString[sal_Int32(nNew)])
            {
                ++nNew;
                rLine.Next();
                bPrev = false;
            }
        }
        rReformat.Len()  += rReformat.Start() - nNew;
        rReformat.Start() = nNew;
    }

    rRepaint.SetOffset( 0 );
    rRepaint.SetRightOfst( 0 );
    rRepaint.Chg( getFrameArea().Pos() + getFramePrintArea().Pos(), getFramePrintArea().SSize() );
    if( pPara->IsMargin() )
        rRepaint.Width( rRepaint.Width() + pPara->GetHangingMargin() );
    rRepaint.Top( rLine.Y() );
    if( 0 >= rRepaint.Width() )
        rRepaint.Width(1);
    WidowsAndOrphans aFrameBreak( this, rInf.IsTest() ? 1 : 0 );

    // rLine is now set to the first line which needs formatting.
    // The bFirst flag makes sure that Next() is not called.
    // The whole thing looks weird, but we need to make sure that
    // rLine stops at the last non-fitting line when calling IsBreakNow.
    bool bFirst  = true;
    bool bFormat = true;

    // The CharToLine() can also get us into the danger zone.
    // In that case we need to walk back until rLine is set
    // to the non-fitting line. Or else the mass of text is lost,
    // because the Ofst was set wrongly in the Follow.

    bool bBreak = ( !pPara->IsPrepMustFit() || rLine.GetLineNr() > 1 )
                    && aFrameBreak.IsBreakNowWidAndOrp( rLine );
    if( bBreak )
    {
        bool bPrevDone = nullptr != rLine.Prev();
        while( bPrevDone && aFrameBreak.IsBreakNowWidAndOrp(rLine) )
            bPrevDone = nullptr != rLine.Prev();
        if( bPrevDone )
        {
            aFrameBreak.SetKeep( false );
            rLine.Next();
        }
        rLine.TruncLines();

        // Play it safe
        aFrameBreak.IsBreakNowWidAndOrp(rLine);
    }

 /* Meaning if the following flags are set:

    Watch(End/Mid)Hyph: we need to format if we have a break at
    the line end/Fly, as long as MaxHyph is reached

    Jump(End/Mid)Flag: the next line which has no break (line end/Fly),
    needs to be formatted, because we could wrap now. This might have been
    forbidden earlier by MaxHyph

    Watch(End/Mid)Hyph: if the last formatted line got a cutoff point, but
    didn't have one before

    Jump(End/Mid)Hyph: if a cutoff point disappears
 */
    bool bJumpEndHyph  = false;
    bool bWatchEndHyph = false;
    bool bJumpMidHyph  = false;
    bool bWatchMidHyph = false;

    const SwAttrSet& rAttrSet = GetTextNodeForParaProps()->GetSwAttrSet();
    rInf.MaxHyph() = rAttrSet.GetHyphenZone().GetMaxHyphens();
    bool bMaxHyph = 0 != rInf.MaxHyph();
    if ( bMaxHyph )
        rLine.InitCntHyph();

    if( IsFollow() && IsFieldFollow() && rLine.GetStart() == GetOffset() )
    {
        SwTextFrame *pMaster = FindMaster();
        OSL_ENSURE( pMaster, "SwTextFrame::Format: homeless follow" );
        const SwLineLayout* pLine=nullptr;
        if (pMaster)
        {
            if (!pMaster->HasPara())
            {   // master could be locked because it's being formatted upstack
                SAL_WARN("sw", "SwTextFrame::Format_: master not formatted!");
            }
            else
            {
                SwTextSizeInfo aInf( pMaster );
                SwTextIter aMasterLine( pMaster, &aInf );
                aMasterLine.Bottom();
                pLine = aMasterLine.GetCurr();
                assert(aMasterLine.GetEnd() == GetOffset());
            }
        }
        SwLinePortion* pRest = pLine ?
            rLine.MakeRestPortion(pLine, GetOffset()) : nullptr;
        if( pRest )
            rInf.SetRest( pRest );
        else
            SetFieldFollow( false );
    }

    /* Ad cancel criterion:
     * In order to recognize, whether a line does not fit onto the page
     * anymore, we need to format it. This overflow is removed again in
     * e.g. AdjustFollow.
     * Another complication: if we are the Master, we need to traverse
     * the lines, because it could happen that one line can overflow
     * from the Follow to the Master.
     */
    do
    {
        if( bFirst )
            bFirst = false;
        else
        {
            if ( bMaxHyph )
            {
                if ( rLine.GetCurr()->IsEndHyph() )
                    rLine.CntEndHyph()++;
                else
                    rLine.CntEndHyph() = 0;
                if ( rLine.GetCurr()->IsMidHyph() )
                    rLine.CntMidHyph()++;
                else
                    rLine.CntMidHyph() = 0;
            }
            if( !rLine.Next() )
            {
                if( !bFormat )
                {
                    SwLinePortion* pRest =
                        rLine.MakeRestPortion( rLine.GetCurr(), rLine.GetEnd() );
                    if( pRest )
                        rInf.SetRest( pRest );
                }
                rLine.Insert( new SwLineLayout() );
                rLine.Next();
                bFormat = true;
            }
        }
        if ( !bFormat && bMaxHyph &&
              (bWatchEndHyph || bJumpEndHyph || bWatchMidHyph || bJumpMidHyph) )
        {
            if ( rLine.GetCurr()->IsEndHyph() )
            {
                if ( bWatchEndHyph )
                    bFormat = ( rLine.CntEndHyph() == rInf.MaxHyph() );
            }
            else
            {
                bFormat = bJumpEndHyph;
                bWatchEndHyph = false;
                bJumpEndHyph = false;
            }
            if ( rLine.GetCurr()->IsMidHyph() )
            {
                if ( bWatchMidHyph && !bFormat )
                    bFormat = ( rLine.CntEndHyph() == rInf.MaxHyph() );
            }
            else
            {
                bFormat |= bJumpMidHyph;
                bWatchMidHyph = false;
                bJumpMidHyph = false;
            }
        }
        if( bFormat )
        {
            const bool bOldEndHyph = rLine.GetCurr()->IsEndHyph();
            const bool bOldMidHyph = rLine.GetCurr()->IsMidHyph();
            bFormat = FormatLine( rLine, bPrev );
            // There can only be one bPrev ... (???)
            bPrev = false;
            if ( bMaxHyph )
            {
                if ( rLine.GetCurr()->IsEndHyph() != bOldEndHyph )
                {
                    bWatchEndHyph = !bOldEndHyph;
                    bJumpEndHyph = bOldEndHyph;
                }
                if ( rLine.GetCurr()->IsMidHyph() != bOldMidHyph )
                {
                    bWatchMidHyph = !bOldMidHyph;
                    bJumpMidHyph = bOldMidHyph;
                }
            }
        }

        if( !rInf.IsNewLine() )
        {
            if( !bFormat )
                 bFormat = nullptr != rInf.GetRest();
            if( rInf.IsStop() || rInf.GetIdx() >= nStrLen )
                break;
            if( !bFormat && ( !bMaxHyph || ( !bWatchEndHyph &&
                    !bJumpEndHyph && !bWatchMidHyph && !bJumpMidHyph ) ) )
            {
                if( GetFollow() )
                {
                    while( rLine.Next() )
                        ; //Nothing
                    pFreeze.reset(new SwRepaint( rRepaint )); // to minimize painting
                }
                else
                    break;
            }
        }
        bBreak = aFrameBreak.IsBreakNowWidAndOrp(rLine);
    }while( !bBreak );

    if( pFreeze )
    {
        rRepaint = *pFreeze;
        pFreeze.reset();
    }

    if( !rLine.IsStop() )
    {
        // If we're finished formatting the text and we still
        // have other line objects left, these are superfluous
        // now because the text has gotten shorter.
        bool bTruncLines = false;
        if( rLine.GetStart() + rLine.GetLength() >= nStrLen &&
            rLine.GetCurr()->GetNext() )
        {
            bTruncLines = true;
        }
        else if (GetMergedPara() && rLine.GetCurr()->GetNext())
        {
            // We can also have superfluous lines with redlining in case the current line is shorter
            // than the text length, but the total length of lines is still more than expected.
            // Truncate in this case as well.
            TextFrameIndex nLen(0);
            for (const SwLineLayout* pLine = pPara; pLine; pLine = pLine->GetNext())
            {
                nLen += pLine->GetLen();
            }
            bTruncLines = nLen > nStrLen;
        }

        if (bTruncLines)
        {
            rLine.TruncLines();
            rLine.SetTruncLines( true );
        }
    }

    if( rInf.IsTest() )
        return;

    // FormatAdjust does not pay off at OnceMore
    if( bAdjust || !rLine.GetDropFormat() || !rLine.CalcOnceMore() )
    {
        FormatAdjust( rLine, aFrameBreak, nStrLen, rInf.IsStop() );
    }
    if( rRepaint.HasArea() )
        SetRepaint();
    rLine.SetTruncLines( false );
    if( nOldBottom ) // We check whether paragraphs that need scrolling can
                     // be shrunk, so that they don't need scrolling anymore
    {
        rLine.Bottom();
        SwTwips nNewBottom = rLine.Y();
        if( nNewBottom < nOldBottom )
            SetOffset_(TextFrameIndex(0));
    }
}

void SwTextFrame::FormatOnceMore( SwTextFormatter &rLine, SwTextFormatInfo &rInf )
{
    OSL_ENSURE( ! IsVertical() || IsSwapped(),
            "A frame is not swapped in SwTextFrame::FormatOnceMore" );

    SwParaPortion *pPara = rLine.GetInfo().GetParaPortion();
    if( !pPara )
        return;

    // If necessary the pPara
    sal_uInt16 nOld  = static_cast<const SwTextMargin&>(rLine).GetDropHeight();
    bool bShrink = false;
    bool bGrow   = false;
    bool bGoOn   = rLine.IsOnceMore();
    sal_uInt8 nGo    = 0;
    while( bGoOn )
    {
        ++nGo;
        rInf.Init();
        rLine.Top();
        if( !rLine.GetDropFormat() )
            rLine.SetOnceMore( false );
        SwCharRange aRange(TextFrameIndex(0), TextFrameIndex(rInf.GetText().getLength()));
        pPara->GetReformat() = aRange;
        Format_( rLine, rInf );

        bGoOn = rLine.IsOnceMore();
        if( bGoOn )
        {
            const sal_uInt16 nNew = static_cast<const SwTextMargin&>(rLine).GetDropHeight();
            if( nOld == nNew )
                bGoOn = false;
            else
            {
                if( nOld > nNew )
                    bShrink = true;
                else
                    bGrow = true;

                if( bShrink == bGrow || 5 < nGo )
                    bGoOn = false;

                nOld = nNew;
            }

            // If something went wrong, we need to reformat again
            if( !bGoOn )
            {
                rInf.CtorInitTextFormatInfo( getRootFrame()->GetCurrShell()->GetOut(), this );
                rLine.CtorInitTextFormatter( this, &rInf );
                rLine.SetDropLines( 1 );
                rLine.CalcDropHeight( 1 );
                SwCharRange aTmpRange(TextFrameIndex(0), TextFrameIndex(rInf.GetText().getLength()));
                pPara->GetReformat() = aTmpRange;
                Format_( rLine, rInf, true );
                // We paint everything ...
                SetCompletePaint();
            }
        }
    }
}

void SwTextFrame::FormatImpl(vcl::RenderContext* pRenderContext, SwParaPortion *pPara,
        std::vector<SwAnchoredObject *> & rIntersectingObjs)
{
    const bool bIsEmpty = GetText().isEmpty();

    if ( bIsEmpty )
    {
        // Empty lines do not get tortured for very long:
        // pPara is cleared, which is the same as:
        // *pPara = SwParaPortion;
        const bool bMustFit = pPara->IsPrepMustFit();
        pPara->Truncate();
        pPara->FormatReset();

        // delete pSpaceAdd and pKanaComp
        pPara->FinishSpaceAdd();
        pPara->FinishKanaComp();
        pPara->ResetFlags();
        pPara->SetPrepMustFit( bMustFit );
    }

    OSL_ENSURE( ! IsSwapped(), "A frame is swapped before Format_" );

    if ( IsVertical() )
        SwapWidthAndHeight();

    SwTextFormatInfo aInf( pRenderContext, this );
    SwTextFormatter  aLine( this, &aInf );

    HideAndShowObjects();

    Format_( aLine, aInf );

    if( aLine.IsOnceMore() )
        FormatOnceMore( aLine, aInf );

    if (aInf.GetTextFly().IsOn())
    {
        SwRect const aRect(aInf.GetTextFly().GetFrameArea());
        for (SwAnchoredObject *const pObj : aInf.GetTextFly().GetAnchoredObjList())
        {
            if (!aInf.GetTextFly().AnchoredObjToRect(pObj, aRect).IsEmpty())
            {
                rIntersectingObjs.push_back(pObj);
            }
        }
    }

    if ( IsVertical() )
        SwapWidthAndHeight();

    OSL_ENSURE( ! IsSwapped(), "A frame is swapped after Format_" );

    if( 1 >= aLine.GetDropLines() )
        return;

    if( SvxAdjust::Left != aLine.GetAdjust() &&
        SvxAdjust::Block != aLine.GetAdjust() )
    {
        aLine.CalcDropAdjust();
        aLine.SetPaintDrop( true );
    }

    if( aLine.IsPaintDrop() )
    {
        aLine.CalcDropRepaint();
        aLine.SetPaintDrop( false );
    }
}

namespace
{
class SwTextFrameFormatScopeGuard
{
private:
    VclPtr<OutputDevice> m_pOut = nullptr;
    VclPtr<OutputDevice> m_pRef = nullptr;

public:
    SwTextFrameFormatScopeGuard(OutputDevice* pOut, SwTextFrame* pFrame)
        : m_pOut(pOut)
    {
        auto pVsh = pFrame->getRootFrame()->GetCurrShell();
        if (pVsh)
        {
            m_pRef = &pVsh->GetRefDev();
        }

        if (m_pOut)
        {
            m_pOut->Push(vcl::PushFlags::ALL);
        }

        if (m_pRef)
        {
            m_pRef->Push(vcl::PushFlags::ALL);
        }
    }

    ~SwTextFrameFormatScopeGuard()
    {
        if (m_pRef)
        {
            m_pRef->Pop();
        }

        if (m_pOut)
        {
            m_pOut->Pop();
        }
    }
};
}

// We calculate the text frame's size and send a notification.
// Shrink() or Grow() to adjust the frame's size to the changed required space.
void SwTextFrame::Format( vcl::RenderContext* pRenderContext, const SwBorderAttrs * )
{
    // tdf#92091: SwTextFrame::Format is re-entrant, but may change VCL global state.
    // The previous state is saved here and restored after returning.
    SwTextFrameFormatScopeGuard stSg{ pRenderContext, this };

    SwRectFnSet aRectFnSet(*this);

    CalcAdditionalFirstLineOffset();

    // The range autopilot or the BASIC interface pass us TextFrames with
    // a width <= 0 from time to time
    if( aRectFnSet.GetWidth(getFramePrintArea()) <= 0 )
    {
        // If MustFit is set, we shrink to the Upper's bottom edge if needed.

        if( EnsurePara()->IsPrepMustFit() )
        {
            const SwTwips nLimit = aRectFnSet.GetPrtBottom(*GetUpper());
            const SwTwips nDiff = - aRectFnSet.BottomDist( getFrameArea(), nLimit );
            if( nDiff > 0 )
                Shrink( nDiff );
        }

        tools::Long nFrameHeight = aRectFnSet.GetHeight(getFrameArea());
        const tools::Long nTop = aRectFnSet.GetTopMargin(*this);

        if( nTop > nFrameHeight )
        {
            aRectFnSet.SetYMargins( *this, nFrameHeight, 0 );
        }
        else if( aRectFnSet.GetHeight(getFramePrintArea()) < 0 )
        {
            SwFrameAreaDefinition::FramePrintAreaWriteAccess aPrt(*this);
            aRectFnSet.SetHeight( aPrt, 0 );
        }

        return;
    }

    TextFrameIndex nStrLen(GetText().getLength());

    if (HasNonLastSplitFlyDrawObj())
    {
        // Non-last part of split fly anchor: consider this empty.
        nStrLen = TextFrameIndex(0);
    }

    if ( nStrLen || !FormatEmpty() )
    {

        SetEmpty( false );
        // In order to not get confused by nested Formats
        FormatLevel aLevel;
        if( 12 == FormatLevel::GetLevel() )
            return;

        // We could be possibly not allowed to alter the format information
        if( IsLocked() )
            return;

        // Attention: Format() could be triggered by GetFormatted()
        if( IsHiddenNow() )
        {
            tools::Long nPrtHeight = aRectFnSet.GetHeight(getFramePrintArea());
            if( nPrtHeight )
            {
                HideHidden();
                Shrink( nPrtHeight );
            }
            else
            {
                // Assure that objects anchored
                // at paragraph resp. at/as character inside paragraph
                // are hidden.
                HideAndShowObjects();
            }
            ChgThisLines();
            return;
        }

        // We do not want to be interrupted during formatting
        TextFrameLockGuard aLock(this);

        // this is to ensure that the similar code in SwTextFrame::Format_
        // finds the master formatted in case it's needed
        if (IsFollow() && IsFieldFollow())
        {
            SwTextFrame *pMaster = FindMaster();
            assert(pMaster);
            if (!pMaster->HasPara())
            {
                pMaster->GetFormatted();
            }
            if (!pMaster->HasPara())
            {   // master could be locked because it's being formatted upstack
                SAL_WARN("sw", "SwTextFrame::Format: failed to format master!");
            }
            else
            {
                SwTextSizeInfo aInf( pMaster );
                SwTextIter aMasterLine( pMaster, &aInf );
                aMasterLine.Bottom();
                SetOffset(aMasterLine.GetEnd());
            }
        }

        const bool bNew = !m_xParaPortion;
        EnsurePara(); // force creation of m_xParaPortion
        // We have to work with a shared_ptr here because code like CalcAdditionalFirstLineOffset
        // wants to swap out and then restore the SwParaPortion underneath us.
        std::shared_ptr<SwParaPortion> xPara = m_xParaPortion;
        const bool bSetOffset =
            (GetOffset() && GetOffset() > TextFrameIndex(GetText().getLength()));

        if( CalcPreps() )
            ; // nothing
        // We return if already formatted, but if the TextFrame was just created
        // and does not have any format information
        else if( !bNew && !xPara->GetReformat().Len() )
        {
            if (GetTextNodeForParaProps()->GetSwAttrSet().GetRegister().GetValue())
            {
                xPara->SetPrepAdjust();
                xPara->SetPrep();
                CalcPreps();
            }
            SetWidow( false );
        }
        else if( bSetOffset && IsFollow() )
        {
            SwTextFrame *pMaster = FindMaster();
            OSL_ENSURE( pMaster, "SwTextFrame::Format: homeless follow" );
            if( pMaster )
                pMaster->Prepare( PrepareHint::FollowFollows );
            SwTwips nMaxY = aRectFnSet.GetPrtBottom(*GetUpper());

            if( aRectFnSet.OverStep( getFrameArea(), nMaxY  ) )
            {
                aRectFnSet.SetLimit( *this, nMaxY );
            }
            else if( aRectFnSet.BottomDist( getFrameArea(), nMaxY  ) < 0 )
            {
                SwFrameAreaDefinition::FrameAreaWriteAccess aFrm(*this);
                aRectFnSet.AddBottom( aFrm, -aRectFnSet.GetHeight(aFrm) );
            }
        }
        else
        {
            // bSetOffset here means that we have the "red arrow situation"
            if ( bSetOffset )
                SetOffset_(TextFrameIndex(0));

            const bool bOrphan = IsWidow();
            const SwFootnoteBossFrame* pFootnoteBoss = HasFootnote() ? FindFootnoteBossFrame() : nullptr;
            SwTwips nFootnoteHeight = 0;
            if( pFootnoteBoss )
            {
                const SwFootnoteContFrame* pCont = pFootnoteBoss->FindFootnoteCont();
                nFootnoteHeight = pCont ? aRectFnSet.GetHeight(pCont->getFrameArea()) : 0;
            }
            do
            {
                ::std::vector<SwAnchoredObject *> intersectingObjs;
                ::std::vector<SwFrame const*> nexts;
                for (SwFrame const* pNext = GetNext(); pNext; pNext = pNext->GetNext())
                {
                    nexts.push_back(pNext);
                }
                FormatImpl(pRenderContext, xPara.get(), intersectingObjs);
                if( pFootnoteBoss && nFootnoteHeight )
                {
                    const SwFootnoteContFrame* pCont = pFootnoteBoss->FindFootnoteCont();
                    SwTwips nNewHeight = pCont ? aRectFnSet.GetHeight(pCont->getFrameArea()) : 0;
                    // If we lost some footnotes, we may have more space
                    // for our main text, so we have to format again ...
                    if( nNewHeight < nFootnoteHeight )
                    {
                        nFootnoteHeight = nNewHeight;
                        continue;
                    }
                }
                if (!intersectingObjs.empty())
                {
                    // assumption is that FormatImpl() only moves frames
                    // in the next-chain to next page
                    SwPageFrame *const pPage(FindPageFrame());
                    SwTextFrame * pLastMovedAnchor(nullptr);
                    auto lastIter(nexts.end());
                    for (SwAnchoredObject *const pObj : intersectingObjs)
                    {
                        SwFrame *const pAnchor(pObj->AnchorFrame());
                        SwPageFrame *const pAnchorPage(pAnchor->FindPageFrame());
                        if (pAnchorPage != pPage)
                        {
                            auto const iter(::std::find(nexts.begin(), nexts.end(), pAnchor));
                            if (iter != nexts.end())
                            {
                                assert(pAnchor->IsTextFrame());
                                // (can't check SwOszControl::IsInProgress()?)
                                // called in loop in FormatAnchorFrameAndItsPrevs()
                                if (static_cast<SwTextFrame const*>(pAnchor)->IsJoinLocked()
                                    // called in loop in SwFrame::PrepareMake()
                                    || pAnchor->IsDeleteForbidden())
                                {
                                    // when called via FormatAnchorFrameAndItsPrevs():
                                    // don't do anything, caller will handle it
                                    pLastMovedAnchor = nullptr;
                                    break;
                                }
                                assert(pPage->GetPhyPageNum() < pAnchorPage->GetPhyPageNum()); // how could it move backward?

                                if (!pLastMovedAnchor || iter < lastIter)
                                {
                                    pLastMovedAnchor = static_cast<SwTextFrame *>(pAnchor);
                                    lastIter = iter;
                                }
                            }
                        }
                    }
                    SwPageFrame const*const pPrevPage(static_cast<SwPageFrame const*>(pPage->GetPrev()));
                    if (pLastMovedAnchor)
                    {
                        for (SwAnchoredObject *const pObj : intersectingObjs)
                        {
                            if (pObj->AnchorFrame() == pLastMovedAnchor)
                            {
                                SwPageFrame *const pAnchorPage(pLastMovedAnchor->FindPageFrame());
                                SAL_INFO("sw.layout", "SwTextFrame::Format: move anchored " << pObj << " from " << pPage->GetPhyPageNum() << " to " << pAnchorPage->GetPhyPageNum());
                                pObj->RegisterAtPage(*pAnchorPage);
                                // tdf#143239 if the position remains valid, it may not be
                                // positioned again so would remain on the wrong page!
                                pObj->InvalidateObjPos();
                                ::Notify_Background(pObj->GetDrawObj(), pPage,
                                    pObj->GetObjRect(), PrepareHint::FlyFrameLeave, false);
                                pObj->SetForceNotifyNewBackground(true);
                            }
                        }
                        if (GetFollow() // this frame was split
                            && (!pPrevPage // prev page is still valid
                                || (!pPrevPage->IsInvalid()
                                    && (!pPrevPage->GetSortedObjs() || !pPrevPage->IsInvalidFly()))))
                        {   // this seems a bit risky...
                            SwLayouter::InsertMovedFwdFrame(GetTextNodeFirst()->GetDoc(),
                                *pLastMovedAnchor, FindPageFrame()->GetPhyPageNum() + 1);
                        }
                        continue; // try again without the fly
                    }
                }
                break;
            } while ( pFootnoteBoss );
            if( bOrphan )
            {
                ValidateFrame();
                SetWidow( false );
            }
        }
        if( IsEmptyMaster() )
        {
            SwFrame* pPre = GetPrev();
            if( pPre &&
                // i#10826 It's the first, it cannot keep!
                pPre->GetIndPrev() &&
                pPre->GetAttrSet()->GetKeep().GetValue() )
            {
                pPre->InvalidatePos();
            }

            if (IsEmptyMasterWithSplitFly())
            {
                // A fly is anchored to us, reduce size, so we definitely still fit the current
                // page.
                SwFrameAreaDefinition::FrameAreaWriteAccess aFrm(*this);
                aRectFnSet.SetHeight(aFrm, 0);

                SwFrameAreaDefinition::FramePrintAreaWriteAccess aPrt(*this);
                aRectFnSet.SetTop(aPrt, 0);
                aRectFnSet.SetHeight(aPrt, 0);
            }
        }
    }

    ChgThisLines();

    // the PrepMustFit should not survive a Format operation
    SwParaPortion *pPara = GetPara();
    if ( pPara )
           pPara->SetPrepMustFit( false );

    CalcBaseOfstForFly();
    CalcHeightOfLastLine(); // i#11860 - Adjust spacing implementation for
                             // object positioning - Compatibility to MS Word
    // tdf#117982 -- Fix cell spacing hides content
    // Check if the cell's content has greater size than the row height
    if (IsInTab() && GetUpper() && ((GetUpper()->getFramePrintArea().Height() < getFramePrintArea().Height())
            || (getFramePrintArea().Height() <= 0)))
    {
           SAL_INFO("sw.core", "Warn: Cell content has greater size than cell height!");
           //get font size...
           SwTwips aTmpHeight = getFrameArea().Height();
           //...and push it into the text frame
           SwFrameAreaDefinition::FramePrintAreaWriteAccess aPrt(*this);
           //if only bottom margin what we have:
           if (GetTopMargin() == 0)
               //set the frame to its original location
               aPrt.SetTopAndHeight(0, aTmpHeight);
    }
}

// bForceQuickFormat is set if GetFormatted() has been called during the
// painting process. Actually I cannot imagine a situation which requires
// a full formatting of the paragraph during painting, on the other hand
// a full formatting can cause the invalidation of other layout frames,
// e.g., if there are footnotes in this paragraph, and invalid layout
// frames will not calculated during the painting. So I actually want to
// avoid a formatting during painting, but since I'm a coward, I'll only
// force the quick formatting in the situation of issue i29062.
bool SwTextFrame::FormatQuick( bool bForceQuickFormat )
{
    OSL_ENSURE( ! IsVertical() || ! IsSwapped(),
            "SwTextFrame::FormatQuick with swapped frame" );

    if( IsEmpty() && FormatEmpty() )
        return true;

    // We're very picky:
    if( HasPara() || IsWidow() || IsLocked()
        || !isFrameAreaSizeValid() ||
        ( ( IsVertical() ? getFramePrintArea().Width() : getFramePrintArea().Height() ) && IsHiddenNow() ) )
        return false;

    SwParaPortion *pPara = GetPara();
    if (!pPara)
        return false;

    SwFrameSwapper aSwapper( this, true );

    TextFrameLockGuard aLock(this);
    SwTextFormatInfo aInf( getRootFrame()->GetCurrShell()->GetOut(), this, false, true );
    if( 0 != aInf.MaxHyph() )   // Respect MaxHyphen!
        return false;

    SwTextFormatter  aLine( this, &aInf );

    // DropCaps are too complicated ...
    if( aLine.GetDropFormat() )
        return false;

    TextFrameIndex nStart = GetOffset();
    const TextFrameIndex nEnd = GetFollow()
                  ? GetFollow()->GetOffset()
                  : TextFrameIndex(aInf.GetText().getLength());

    int nLoopProtection = 0;
    do
    {
        TextFrameIndex nNewStart = aLine.FormatLine(nStart);
        if (nNewStart == nStart)
            ++nLoopProtection;
        else
            nLoopProtection = 0;
        nStart = nNewStart;
        const bool bWillEndlessInsert = nLoopProtection > 250;
        SAL_WARN_IF(bWillEndlessInsert, "sw", "loop detection triggered");
        if ((!bWillEndlessInsert) // Check for special case: line is invisible,
                                  // like in too thin table cell: tdf#66141
         && (aInf.IsNewLine() || (!aInf.IsStop() && nStart < nEnd)))
            aLine.Insert( new SwLineLayout() );
    } while( aLine.Next() );

    // Last exit: the heights need to match
    Point aTopLeft( getFrameArea().Pos() );
    aTopLeft += getFramePrintArea().Pos();
    const SwTwips nNewHeight = aLine.Y() + aLine.GetLineHeight();
    const SwTwips nOldHeight = aTopLeft.Y() + getFramePrintArea().Height();

    if( !bForceQuickFormat && nNewHeight != nOldHeight && !IsUndersized() )
    {
        // Attention: This situation can occur due to FormatLevel==12. Don't panic!
        TextFrameIndex const nStrt = GetOffset();
        InvalidateRange_( SwCharRange( nStrt, nEnd - nStrt) );
        return false;
    }

    if (m_pFollow && nStart != static_cast<SwTextFrame*>(m_pFollow)->GetOffset())
        return false; // can be caused by e.g. Orphans

    // We made it!

    // Set repaint
    pPara->GetRepaint().Pos( aTopLeft );
    pPara->GetRepaint().SSize( getFramePrintArea().SSize() );

    // Delete reformat
    pPara->GetReformat() = SwCharRange();
    pPara->SetDelta(0);

    return true;
}

/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
