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

#include <string_view>

#include <utility>
#include <viewsh.hxx>
#include <doc.hxx>
#include <IDocumentLayoutAccess.hxx>
#include <pagefrm.hxx>
#include <rootfrm.hxx>
#include <ndtxt.hxx>
#include <SwPortionHandler.hxx>
#include <txtftn.hxx>
#include <flyfrm.hxx>
#include <fmtftn.hxx>
#include <fmtsrnd.hxx>
#include <ftninfo.hxx>
#include <charfmt.hxx>
#include <rowfrm.hxx>
#include <editeng/brushitem.hxx>
#include <editeng/charrotateitem.hxx>
#include <tabfrm.hxx>
#include <sortedobjs.hxx>

#include <swfont.hxx>
#include "porftn.hxx"
#include "porfly.hxx"
#include "porlay.hxx"
#include <txtfrm.hxx>
#include "itrform2.hxx"
#include <ftnfrm.hxx>
#include <pagedesc.hxx>
#include "redlnitr.hxx"
#include <sectfrm.hxx>
#include <layouter.hxx>
#include <frmtool.hxx>
#include <ndindex.hxx>
#include <IDocumentSettingAccess.hxx>
#include <IDocumentRedlineAccess.hxx>
#include <swmodule.hxx>
#include <unotextrange.hxx>
#include <redline.hxx>
#include <editeng/colritem.hxx>
#include <editeng/udlnitem.hxx>
#include <editeng/crossedoutitem.hxx>

#include <com/sun/star/beans/XPropertySet.hpp>
#include <com/sun/star/awt/CharSet.hpp>
#include <com/sun/star/text/XTextRange.hpp>
#include <viewopt.hxx>

using namespace ::com::sun::star;

bool SwTextFrame::IsFootnoteNumFrame_() const
{
    if (IsInTab())
        return false; // tdf#102073 first frame in cell doesn't have mpPrev set
    const SwFootnoteFrame* pFootnote = FindFootnoteFrame()->GetMaster();
    while( pFootnote && !pFootnote->ContainsContent() )
        pFootnote = pFootnote->GetMaster();
    return !pFootnote;
}

/**
 * Looks for the TextFrame matching the SwTextFootnote within a master-follow chain
 */
SwTextFrame *SwTextFrame::FindFootnoteRef( const SwTextFootnote *pFootnote )
{
    SwTextFrame *pFrame = this;
    const bool bFwd = MapModelToView(&pFootnote->GetTextNode(), pFootnote->GetStart()) >= GetOffset();
    while( pFrame )
    {
        if( SwFootnoteBossFrame::FindFootnote( pFrame, pFootnote ) )
            return pFrame;
        pFrame = bFwd ? pFrame->GetFollow() :
                      pFrame->IsFollow() ? pFrame->FindMaster() : nullptr;
    }
    return pFrame;
}

void SwTextFrame::SetHasRotatedPortions(bool bHasRotatedPortions)
{
    mbHasRotatedPortions = bHasRotatedPortions;
}

#ifdef DBG_UTIL
void SwTextFrame::CalcFootnoteFlag(TextFrameIndex nStop) // For testing the SplitFrame
#else
void SwTextFrame::CalcFootnoteFlag()
#endif
{
    mbFootnote = false;

#ifdef DBG_UTIL
    const TextFrameIndex nEnd = nStop != TextFrameIndex(COMPLETE_STRING)
        ? nStop
        : GetFollow() ? GetFollow()->GetOffset() : TextFrameIndex(COMPLETE_STRING);
#else
    const TextFrameIndex nEnd = GetFollow()
        ? GetFollow()->GetOffset()
        : TextFrameIndex(COMPLETE_STRING);
#endif

    SwTextNode const* pNode(nullptr);
    sw::MergedAttrIter iter(*this);
    for (SwTextAttr const* pHt = iter.NextAttr(&pNode); pHt; pHt = iter.NextAttr(&pNode))
    {
        if ( pHt->Which() == RES_TXTATR_FTN )
        {
            TextFrameIndex const nIdx(MapModelToView(pNode, pHt->GetStart()));
            if ( nEnd < nIdx )
                break;
            if( GetOffset() <= nIdx )
            {
                mbFootnote = true;
                break;
            }
        }
    }
}

bool SwTextFrame::CalcPrepFootnoteAdjust()
{
    OSL_ENSURE( HasFootnote(), "Who´s calling me?" );
    SwFootnoteBossFrame *pBoss = FindFootnoteBossFrame( true );
    const SwFootnoteFrame *pFootnote = pBoss->FindFirstFootnote( this );
    if (pFootnote && FTNPOS_CHAPTER != GetDoc().GetFootnoteInfo().m_ePos &&
        ( !pBoss->GetUpper()->IsSctFrame() ||
        !static_cast<SwSectionFrame*>(pBoss->GetUpper())->IsFootnoteAtEnd() ) )
    {
        const SwFootnoteContFrame *pCont = pBoss->FindFootnoteCont();
        bool bReArrange = true;

        SwRectFnSet aRectFnSet(*this);
        if ( pCont && aRectFnSet.YDiff( aRectFnSet.GetTop(pCont->getFrameArea()),
                                          aRectFnSet.GetBottom(getFrameArea()) ) > 0 )
        {
            pBoss->RearrangeFootnotes( aRectFnSet.GetBottom(getFrameArea()), false,
                                  pFootnote->GetAttr() );
            ValidateBodyFrame();
            ValidateFrame();
            pFootnote = pBoss->FindFirstFootnote( this );
        }
        else
            bReArrange = false;
        if( !pCont || !pFootnote || bReArrange != (pFootnote->FindFootnoteBossFrame() == pBoss) )
        {
            SwTextFormatInfo aInf( getRootFrame()->GetCurrShell()->GetOut(), this );
            SwTextFormatter aLine( this, &aInf );
            aLine.TruncLines();
            SetPara(nullptr); // May be deleted!
            ResetPreps();
            return false;
        }
    }
    return true;
}

/**
 * Local helper function. Checks if nLower should be taken as the boundary
 * for the footnote.
 */
static SwTwips lcl_GetFootnoteLower( const SwTextFrame* pFrame, SwTwips nLower )
{
    // nLower is an absolute value. It denotes the bottom of the line
    // containing the footnote.
    SwRectFnSet aRectFnSet(*pFrame);

    OSL_ENSURE( !pFrame->IsVertical() || !pFrame->IsSwapped(),
            "lcl_GetFootnoteLower with swapped frame" );

    SwTwips nAdd;
    SwTwips nRet = nLower;

    // Check if text is inside a table.
    if ( pFrame->IsInTab() )
    {
        // If pFrame is inside a table, we have to check if
        // a) The table is not allowed to split or
        // b) The table row is not allowed to split

        // Inside a table, there are no footnotes,
        // see SwFrame::FindFootnoteBossFrame. So we don't have to check
        // the case that pFrame is inside a (footnote collecting) section
        // within the table.
        const SwFrame* pRow = pFrame;
        while( !pRow->IsRowFrame() || !pRow->GetUpper()->IsTabFrame() )
            pRow = pRow->GetUpper();
        const SwTabFrame* pTabFrame = static_cast<const SwTabFrame*>(pRow->GetUpper());

        OSL_ENSURE( pTabFrame && pRow &&
                pRow->GetUpper()->IsTabFrame(), "Upper of row should be tab" );

        const bool bDontSplit = !pTabFrame->IsFollow() &&
                                !pTabFrame->IsLayoutSplitAllowed();

        SwTwips nMin = 0;
        if ( bDontSplit )
            nMin = aRectFnSet.GetBottom(pTabFrame->getFrameArea());
        else if ( !static_cast<const SwRowFrame*>(pRow)->IsRowSplitAllowed() )
            nMin = aRectFnSet.GetBottom(pRow->getFrameArea());

        if ( nMin && aRectFnSet.YDiff( nMin, nLower ) > 0 )
            nRet = nMin;

        nAdd = aRectFnSet.GetBottomMargin(*pRow->GetUpper());
    }
    else
        nAdd = aRectFnSet.GetBottomMargin(*pFrame);

    if( nAdd > 0 )
    {
        if ( aRectFnSet.IsVert() )
            nRet -= nAdd;
        else
            nRet += nAdd;
    }

    // #i10770#: If there are fly frames anchored at previous paragraphs,
    // the deadline should consider their lower borders.
    const SwFrame* pStartFrame = pFrame->GetUpper()->GetLower();
    OSL_ENSURE( pStartFrame, "Upper has no lower" );
    SwTwips nFlyLower = aRectFnSet.IsVert() ? LONG_MAX : 0;
    while ( pStartFrame != pFrame )
    {
        assert(pStartFrame && "Frame chain is broken");
        if ( pStartFrame->GetDrawObjs() )
        {
            const SwSortedObjs &rObjs = *pStartFrame->GetDrawObjs();
            for (SwAnchoredObject* pAnchoredObj : rObjs)
            {
                if (pAnchoredObj->GetFrameFormat()->GetSurround().GetSurround()
                        == text::WrapTextMode_THROUGH)
                {
                    continue; // tdf#161718 no effect on text flow, skip
                }

                SwRect aRect( pAnchoredObj->GetObjRect() );

                auto pFlyFrame = pAnchoredObj->DynCastFlyFrame();
                if ( !pFlyFrame ||
                     pFlyFrame->isFrameAreaDefinitionValid() )
                {
                    const SwTwips nBottom = aRectFnSet.GetBottom(aRect);
                    if ( aRectFnSet.YDiff( nBottom, nFlyLower ) > 0 )
                        nFlyLower = nBottom;
                }
            }
        }

        pStartFrame = pStartFrame->GetNext();
    }

    if ( aRectFnSet.IsVert() )
        nRet = std::min( nRet, nFlyLower );
    else
        nRet = std::max( nRet, nFlyLower );

    return nRet;
}

SwTwips SwTextFrame::GetFootnoteLine( const SwTextFootnote *pFootnote ) const
{
    OSL_ENSURE( ! IsVertical() || ! IsSwapped(),
            "SwTextFrame::GetFootnoteLine with swapped frame" );

    SwTextFrame *pThis = const_cast<SwTextFrame*>(this);

    if( !HasPara() )
    {
        // #109071# GetFormatted() does not work here, because most probably
        // the frame is currently locked. We return the previous value.
        return pThis->mnFootnoteLine > 0 ?
               pThis->mnFootnoteLine :
               IsVertical() ? getFrameArea().Left() : getFrameArea().Bottom();
    }

    SwTwips nRet;
    {
        SwSwapIfNotSwapped swap(const_cast<SwTextFrame *>(this));

        SwTextInfo aInf( pThis );
        SwTextIter aLine( pThis, &aInf );
        TextFrameIndex const nPos(MapModelToView(
                    &pFootnote->GetTextNode(), pFootnote->GetStart()));
        aLine.CharToLine( nPos );

        nRet = aLine.Y() + aLine.GetLineHeight();
        if( IsVertical() )
            nRet = SwitchHorizontalToVertical( nRet );
    }

    nRet = lcl_GetFootnoteLower( pThis, nRet );

    pThis->mnFootnoteLine = nRet;
    return nRet;
}

/**
 * Calculates the maximum reachable height for the TextFrame in the Footnote Area.
 * The cell's bottom margin with the Footnote Reference limit's this height.
 */
SwTwips SwTextFrame::GetFootnoteFrameHeight_() const
{
    OSL_ENSURE( !IsFollow() && IsInFootnote(), "SwTextFrame::SetFootnoteLine: moon walk" );

    const SwFootnoteFrame *pFootnoteFrame = FindFootnoteFrame();
    const SwTextFrame *pRef = static_cast<const SwTextFrame *>(pFootnoteFrame->GetRef());
    const SwFootnoteBossFrame *pBoss = FindFootnoteBossFrame();
    if( pBoss != pRef->FindFootnoteBossFrame( !pFootnoteFrame->GetAttr()->
                                        GetFootnote().IsEndNote() ) )
        return 0;

    SwSwapIfSwapped swap(const_cast<SwTextFrame *>(this));

    SwTwips nHeight = pRef->IsInFootnoteConnect() ?
                            1 : pRef->GetFootnoteLine( pFootnoteFrame->GetAttr() );
    if( nHeight )
    {
        // As odd as it may seem: the first Footnote on the page may not touch the
        // Footnote Reference, when entering text in the Footnote Area.
        const SwFrame *pCont = pFootnoteFrame->GetUpper();

        // Height within the Container which we're allowed to consume anyways
        SwRectFnSet aRectFnSet(*pCont);
        SwTwips nTmp = aRectFnSet.YDiff( aRectFnSet.GetPrtBottom(*pCont),
                                           aRectFnSet.GetTop(getFrameArea()) );

#if OSL_DEBUG_LEVEL > 0
        if( nTmp < 0 )
        {
            bool bInvalidPos = false;
            const SwLayoutFrame* pTmp = GetUpper();
            while( !bInvalidPos && pTmp )
            {
                const SwFrame* pLower = pTmp->Lower();
                bInvalidPos = !pTmp->isFrameAreaPositionValid() ||
                                !pLower || !pLower->isFrameAreaPositionValid();
                if( pTmp == pCont )
                    break;
                pTmp = pTmp->GetUpper();
            }
            OSL_ENSURE( bInvalidPos, "Hanging below FootnoteCont" );
        }
#endif

        if ( aRectFnSet.YDiff( aRectFnSet.GetTop(pCont->getFrameArea()), nHeight) > 0 )
        {
            // Growth potential of the container
            if ( !pRef->IsInFootnoteConnect() )
            {
                SwSaveFootnoteHeight aSave( const_cast<SwFootnoteBossFrame*>(pBoss), nHeight  );
                nHeight = const_cast<SwFootnoteContFrame*>(static_cast<const SwFootnoteContFrame*>(pCont))->Grow( LONG_MAX, true );
            }
            else
                nHeight = const_cast<SwFootnoteContFrame*>(static_cast<const SwFootnoteContFrame*>(pCont))->Grow( LONG_MAX, true );

            nHeight += nTmp;
            if( nHeight < 0 )
                nHeight = 0;
        }
        else
        {   // The container has to shrink
            nTmp += aRectFnSet.YDiff( aRectFnSet.GetTop(pCont->getFrameArea()), nHeight);
            if( nTmp > 0 )
                nHeight = nTmp;
            else
                nHeight = 0;
        }
    }

    return nHeight;
}

SwTextFrame *SwTextFrame::FindQuoVadisFrame()
{
    // Check whether we're in a FootnoteFrame
    if( GetIndPrev() || !IsInFootnote() )
        return nullptr;

    // To the preceding FootnoteFrame
    SwFootnoteFrame *pFootnoteFrame = FindFootnoteFrame()->GetMaster();
    if( !pFootnoteFrame )
        return nullptr;

    // Now the last Content
    SwContentFrame *pCnt = pFootnoteFrame->ContainsContent();
    if( !pCnt )
        return nullptr;
    SwContentFrame *pLast;
    do
    {   pLast = pCnt;
        pCnt = pCnt->GetNextContentFrame();
    } while( pCnt && pFootnoteFrame->IsAnLower( pCnt ) );
    return static_cast<SwTextFrame*>(pLast);
}

void SwTextFrame::RemoveFootnote(TextFrameIndex const nStart, TextFrameIndex const nLen)
{
    if ( !IsFootnoteAllowed() )
        return;

    bool bRollBack = nLen != TextFrameIndex(COMPLETE_STRING);
    TextFrameIndex nEnd;
    SwTextFrame* pSource;
    if( bRollBack )
    {
        nEnd = nStart + nLen;
        pSource = GetFollow();
        if( !pSource )
            return;
    }
    else
    {
        nEnd = TextFrameIndex(COMPLETE_STRING);
        pSource = this;
    }

    SwPageFrame* pUpdate = nullptr;
    bool bRemove = false;
    SwFootnoteBossFrame *pFootnoteBoss = nullptr;
    SwFootnoteBossFrame *pEndBoss = nullptr;
    bool bFootnoteEndDoc = FTNPOS_CHAPTER == GetDoc().GetFootnoteInfo().m_ePos;
    SwTextNode const* pNode(nullptr);
    sw::MergedAttrIterReverse iter(*this);
    for (SwTextAttr const* pHt = iter.PrevAttr(&pNode); pHt; pHt = iter.PrevAttr(&pNode))
    {
        if (RES_TXTATR_FTN != pHt->Which())
            continue;

        TextFrameIndex const nIdx(MapModelToView(pNode, pHt->GetStart()));
        if (nStart > nIdx)
            break;

        if (nEnd >= nIdx)
        {
            SwTextFootnote const*const pFootnote(static_cast<SwTextFootnote const*>(pHt));
            const bool bEndn = pFootnote->GetFootnote().IsEndNote();

            if (bEndn)
            {
                if (!pEndBoss)
                    pEndBoss = pSource->FindFootnoteBossFrame();
            }
            else
            {
                if (!pFootnoteBoss)
                {
                    pFootnoteBoss = pSource->FindFootnoteBossFrame( true );
                    if( pFootnoteBoss->GetUpper()->IsSctFrame() )
                    {
                        SwSectionFrame* pSect = static_cast<SwSectionFrame*>(
                                              pFootnoteBoss->GetUpper());
                        if (pSect->IsFootnoteAtEnd())
                            bFootnoteEndDoc = false;
                    }
                }
            }

            // We don't delete, but move instead.
            // Three cases are to be considered:
            // 1) There's neither Follow nor PrevFollow:
            //    -> RemoveFootnote() (maybe even a OSL_ENSURE(value))
            //
            // 2) nStart > GetOffset, I have a Follow
            //    -> Footnote moves into Follow
            //
            // 3) nStart < GetOffset, I am a Follow
            //    -> Footnote moves into the PrevFollow
            //
            // Both need to be on one Page/in one Column
            SwFootnoteFrame *pFootnoteFrame = SwFootnoteBossFrame::FindFootnote(pSource, pFootnote);

            if (pFootnoteFrame)
            {
                const bool bEndDoc = bEndn || bFootnoteEndDoc;
                if( bRollBack )
                {
                    while (pFootnoteFrame)
                    {
                        pFootnoteFrame->SetRef( this );
                        pFootnoteFrame = pFootnoteFrame->GetFollow();
                        SetFootnote( true );
                    }
                }
                else if (GetFollow())
                {
                    SwContentFrame *pDest = GetFollow();
                    while (pDest->GetFollow() && static_cast<SwTextFrame*>(pDest->
                           GetFollow())->GetOffset() <= nIdx)
                        pDest = pDest->GetFollow();
                    OSL_ENSURE( !SwFootnoteBossFrame::FindFootnote(
                        pDest,pFootnote),"SwTextFrame::RemoveFootnote: footnote exists");

                    // Never deregister; always move
                    if (bEndDoc ||
                        !pFootnoteFrame->FindFootnoteBossFrame()->IsBefore(pDest->FindFootnoteBossFrame(!bEndn))
                       )
                    {
                        SwPageFrame* pTmp = pFootnoteFrame->FindPageFrame();
                        if( pUpdate && pUpdate != pTmp )
                            pUpdate->UpdateFootnoteNum();
                        pUpdate = pTmp;
                        while ( pFootnoteFrame )
                        {
                            pFootnoteFrame->SetRef( pDest );
                            pFootnoteFrame = pFootnoteFrame->GetFollow();
                        }
                    }
                    else
                    {
                        pFootnoteBoss->MoveFootnotes( this, pDest, pFootnote );
                        bRemove = true;
                    }
                    static_cast<SwTextFrame*>(pDest)->SetFootnote( true );

                    OSL_ENSURE( SwFootnoteBossFrame::FindFootnote( pDest,
                       pFootnote),"SwTextFrame::RemoveFootnote: footnote ChgRef failed");
                }
                else
                {
                    if (!bEndDoc || ( bEndn && pEndBoss->IsInSct() &&
                        !SwLayouter::Collecting( GetDoc(),
                        pEndBoss->FindSctFrame(), nullptr ) ))
                    {
                        if( bEndn )
                            pEndBoss->RemoveFootnote( this, pFootnote );
                        else
                            pFootnoteBoss->RemoveFootnote( this, pFootnote );
                        bRemove = bRemove || !bEndDoc;
                        OSL_ENSURE( !SwFootnoteBossFrame::FindFootnote( this, pFootnote ),
                        "SwTextFrame::RemoveFootnote: can't get off that footnote" );
                    }
                }
            }
        }
    }
    if (pUpdate)
        pUpdate->UpdateFootnoteNum();

    // We break the oscillation
    if (bRemove && !bFootnoteEndDoc && HasPara())
    {
        ValidateBodyFrame();
        ValidateFrame();
    }

    // We call the RemoveFootnote from within the FindBreak, because the last line is
    // to be passed to the Follow. The Offset of the Follow is, however, outdated;
    // it'll be set soon. CalcFntFlag depends on a correctly set Follow Offset.
    // Therefore we temporarily calculate the Follow Offset here
    TextFrameIndex nOldOfst(COMPLETE_STRING);
    if( HasFollow() && nStart > GetOffset() )
    {
        nOldOfst = GetFollow()->GetOffset();
        GetFollow()->ManipOfst(nStart + (bRollBack ? nLen : TextFrameIndex(0)));
    }
    pSource->CalcFootnoteFlag();
    if (nOldOfst < TextFrameIndex(COMPLETE_STRING))
        GetFollow()->ManipOfst( nOldOfst );
}


/**
 * We basically only have two possibilities:
 *
 * a) The Footnote is already present
 *    => we move it, if another pSrcFrame has been found
 *
 * b) The Footnote is not present
 *    => we have it created for us
 *
 * Whether the Footnote ends up on our Page/Column, doesn't matter in this
 * context.
 *
 * Optimization for Endnotes.
 *
 * Another problem: if the Deadline falls within the Footnote Area, we need
 * to move the Footnote.
 *
 * @returns false on any type of error
 */
void SwTextFrame::ConnectFootnote( SwTextFootnote *pFootnote, const SwTwips nDeadLine )
{
    OSL_ENSURE( !IsVertical() || !IsSwapped(),
            "SwTextFrame::ConnectFootnote with swapped frame" );

    mbFootnote = true;
    mbInFootnoteConnect = true; // Just reset!
    // See if pFootnote is an endnote on a separate endnote page.
    const bool bEnd = pFootnote->GetFootnote().IsEndNote();

    // We want to store this value, because it is needed as a fallback
    // in GetFootnoteLine(), if there is no paragraph information available
    mnFootnoteLine = nDeadLine;

    // We always need a parent (Page/Column)
    SwSectionFrame *pSect;
    SwContentFrame *pContent = this;
    if( bEnd && IsInSct() )
    {
        pSect = FindSctFrame();
        if( pSect->IsEndnAtEnd() )
            pContent = pSect->FindLastContent( SwFindMode::EndNote );
        if( !pContent )
            pContent = this;
    }

    SwFootnoteBossFrame *pBoss = pContent->FindFootnoteBossFrame( !bEnd );

    pSect = pBoss->FindSctFrame();
    bool bDocEnd = bEnd ? !( pSect && pSect->IsEndnAtEnd() ) :
                   ( !( pSect && pSect->IsFootnoteAtEnd() ) &&
                       FTNPOS_CHAPTER == GetDoc().GetFootnoteInfo().m_ePos);

    // Footnote can be registered with the Follow
    SwContentFrame *pSrcFrame = FindFootnoteRef( pFootnote );

    if( bDocEnd )
    {
        if (pSect && pSrcFrame)
        {
            SwFootnoteFrame *pFootnoteFrame = SwFootnoteBossFrame::FindFootnote( pSrcFrame, pFootnote );
            if (pFootnoteFrame && pFootnoteFrame->IsInSct())
            {
                pBoss->RemoveFootnote( pSrcFrame, pFootnote );
                pSrcFrame = nullptr;
            }
        }
    }
    else if( bEnd && pSect )
    {
        SwFootnoteFrame *pFootnoteFrame = pSrcFrame ? SwFootnoteBossFrame::FindFootnote( pSrcFrame, pFootnote ) : nullptr;
        if( pFootnoteFrame && !pFootnoteFrame->GetUpper() )
            pFootnoteFrame = nullptr;
        SwDoc& rDoc = GetDoc();
        if( SwLayouter::Collecting( rDoc, pSect, pFootnoteFrame ) )
        {
            if( !pSrcFrame )
            {
                SwFootnoteFrame *pNew = new SwFootnoteFrame(rDoc.GetDfltFrameFormat(),this,this,pFootnote);
                SwNodeIndex aIdx( *pFootnote->GetStartNode(), 1 );
                ::InsertCnt_( pNew, rDoc, aIdx.GetIndex() );
                rDoc.getIDocumentLayoutAccess().GetLayouter()->CollectEndnote( pNew );
            }
            else if( pSrcFrame != this )
                SwFootnoteBossFrame::ChangeFootnoteRef( pSrcFrame, pFootnote, this );
            mbInFootnoteConnect = false;
            return;
        }
        else if (pSrcFrame && pFootnoteFrame)
        {
            SwFootnoteBossFrame *pFootnoteBoss = pFootnoteFrame->FindFootnoteBossFrame();
            if( !pFootnoteBoss->IsInSct() ||
                pFootnoteBoss->ImplFindSctFrame()->GetSection()!=pSect->GetSection() )
            {
                pBoss->RemoveFootnote( pSrcFrame, pFootnote );
                pSrcFrame = nullptr;
            }
        }
    }

    if( bDocEnd || bEnd )
    {
        if( !pSrcFrame )
            pBoss->AppendFootnote( this, pFootnote );
        else if( pSrcFrame != this )
            SwFootnoteBossFrame::ChangeFootnoteRef( pSrcFrame, pFootnote, this );
        mbInFootnoteConnect = false;
        return;
    }

    SwSaveFootnoteHeight aHeight( pBoss, nDeadLine );

    if( !pSrcFrame ) // No Footnote was found at all
        pBoss->AppendFootnote( this, pFootnote );
    else
    {
        SwFootnoteFrame *pFootnoteFrame = SwFootnoteBossFrame::FindFootnote( pSrcFrame, pFootnote );
        SwFootnoteBossFrame *pFootnoteBoss = pFootnoteFrame->FindFootnoteBossFrame();

        bool bBrutal = false;

        if( pFootnoteBoss == pBoss ) // Ref and Footnote are on the same Page/Column
        {
            SwFrame *pCont = pFootnoteFrame->GetUpper();

            SwRectFnSet aRectFnSet(*pCont);
            tools::Long nDiff = aRectFnSet.YDiff( aRectFnSet.GetTop(pCont->getFrameArea()),
                                             nDeadLine );

            if( nDiff >= 0 )
            {
                // If the Footnote has been registered to a Follow, we need to
                // rewire it now too
                if ( pSrcFrame != this )
                    SwFootnoteBossFrame::ChangeFootnoteRef( pSrcFrame, pFootnote, this );

                // We have some room left, so the Footnote can grow
                if ( pFootnoteFrame->GetFollow() && nDiff > 0 )
                {
                    SwFrameDeleteGuard aDeleteGuard(pCont);
                    SwTwips nHeight = aRectFnSet.GetHeight(pCont->getFrameArea());
                    pBoss->RearrangeFootnotes( nDeadLine, false, pFootnote );
                    ValidateBodyFrame();
                    ValidateFrame();
                    SwViewShell *pSh = getRootFrame()->GetCurrShell();
                    if ( pSh && nHeight == aRectFnSet.GetHeight(pCont->getFrameArea()) )
                        // So that we don't miss anything
                        pSh->InvalidateWindows( pCont->getFrameArea() );
                }
                mbInFootnoteConnect = false;
                return;
            }
            else
                bBrutal = true;
        }
        else
        {
            // Ref and Footnote are not on one Page; attempt to move is necessary
            SwFrame* pTmp = this;
            while( pTmp->GetNext() && pSrcFrame != pTmp )
                pTmp = pTmp->GetNext();
            if( pSrcFrame == pTmp )
                bBrutal = true;
            else
            {   // If our Parent is in a column Area, but the Page already has a
                // FootnoteContainer, we can only brute force it
                if( pSect && pSect->FindFootnoteBossFrame( !bEnd )->FindFootnoteCont() )
                    bBrutal = true;

                else if ( !pFootnoteFrame->GetPrev() ||
                          pFootnoteBoss->IsBefore( pBoss )
                        )
                {
                    SwFootnoteBossFrame *pSrcBoss = pSrcFrame->FindFootnoteBossFrame( !bEnd );
                    pSrcBoss->MoveFootnotes( pSrcFrame, this, pFootnote );
                }
                else
                    SwFootnoteBossFrame::ChangeFootnoteRef( pSrcFrame, pFootnote, this );
            }
        }

        // The brute force method: Remove Footnote and append.
        // We need to call SetFootnoteDeadLine(), as we can more easily adapt the
        // nMaxFootnoteHeight after RemoveFootnote
        if( bBrutal )
        {
            pBoss->RemoveFootnote( pSrcFrame, pFootnote, false );
            std::unique_ptr<SwSaveFootnoteHeight> pHeight(bEnd ? nullptr : new SwSaveFootnoteHeight( pBoss, nDeadLine ));
            pBoss->AppendFootnote( this, pFootnote );
        }
    }

    // In column Areas, that not yet reach the Page's border a RearrangeFootnotes is not
    // useful yet, as the Footnote container has not yet been calculated
    if( !pSect || !pSect->Growable() )
    {
        // Validate environment, to avoid oscillation
        SwSaveFootnoteHeight aNochmal( pBoss, nDeadLine );
        ValidateBodyFrame();
        pBoss->RearrangeFootnotes( nDeadLine, true );
        ValidateFrame();
    }
    else if( pSect->IsFootnoteAtEnd() )
    {
        ValidateBodyFrame();
        ValidateFrame();
    }

    mbInFootnoteConnect = false;
}

/**
 * The portion for the Footnote Reference in the Text
 */
SwFootnotePortion *SwTextFormatter::NewFootnotePortion( SwTextFormatInfo &rInf,
                                             SwTextAttr *pHint )
{
    OSL_ENSURE( ! m_pFrame->IsVertical() || m_pFrame->IsSwapped(),
            "NewFootnotePortion with unswapped frame" );

    if (!m_pFrame->IsFootnoteAllowed())
        return new SwFootnotePortion(u""_ustr, nullptr);

    SwTextFootnote  *pFootnote = static_cast<SwTextFootnote*>(pHint);

    const SwFormatFootnote& rFootnote = pFootnote->GetFootnote();
    SwDoc *const pDoc = &m_pFrame->GetDoc();

    if( rInf.IsTest() )
        return new SwFootnotePortion(rFootnote.GetViewNumStr(*pDoc, m_pFrame->getRootFrame()), pFootnote);

    SwSwapIfSwapped swap(m_pFrame);

    SwTwips nReal;
    {
        SwTwips nOldReal = m_pCurr->GetRealHeight();
        SwTwips nOldAscent = m_pCurr->GetAscent();
        SwTwips nOldHeight = m_pCurr->Height();
        CalcRealHeight();
        nReal = m_pCurr->GetRealHeight();
        if( nReal < nOldReal )
            nReal = nOldReal;
        m_pCurr->SetRealHeight( nOldReal );
        m_pCurr->Height( nOldHeight );
        m_pCurr->SetAscent( nOldAscent );
    }

    SwTwips nLower = Y() + nReal;

    const bool bVertical = m_pFrame->IsVertical();
    if( bVertical )
        nLower = m_pFrame->SwitchHorizontalToVertical( nLower );

    nLower = lcl_GetFootnoteLower( m_pFrame, nLower );

    // We just refresh.
    // The Connect does not do anything useful in this case, but will
    // mostly throw away the Footnote and create it anew.
    if( !rInf.IsQuick() )
        m_pFrame->ConnectFootnote( pFootnote, nLower );

    SwTextFrame *pScrFrame = m_pFrame->FindFootnoteRef( pFootnote );
    SwFootnoteBossFrame *pBoss = m_pFrame->FindFootnoteBossFrame( !rFootnote.IsEndNote() );
    SwFootnoteFrame *pFootnoteFrame = nullptr;
    if( pScrFrame )
        pFootnoteFrame = SwFootnoteBossFrame::FindFootnote( pScrFrame, pFootnote );

    // We see whether our Append has caused some Footnote to
    // still be on the Page/Column. If not, our line disappears too,
    // which will lead to the following undesired behaviour:
    // Footnote1 still fits onto the Page/Column, but Footnote2 doesn't.
    // The Footnote2 Reference remains on the Page/Column. The Footnote itself
    // is on the next Page/Column.
    //
    // Exception: If the Page/Column cannot accommodate another line,
    // the Footnote Reference should be moved to the next one.
    if( !rFootnote.IsEndNote() )
    {
        SwSectionFrame *pSct = pBoss->FindSctFrame();
        bool bAtSctEnd = pSct && pSct->IsFootnoteAtEnd();
        if( FTNPOS_CHAPTER != pDoc->GetFootnoteInfo().m_ePos || bAtSctEnd )
        {
            SwFrame* pFootnoteCont = pBoss->FindFootnoteCont();
            // If the Parent is within an Area, it can only be a Column of this
            // Area. If this one is not the first Column, we can avoid it.
            if( !m_pFrame->IsInTab() && ( GetLineNr() > 1 || m_pFrame->GetPrev() ||
                ( !bAtSctEnd && m_pFrame->GetIndPrev() ) ||
                ( pSct && pBoss->GetPrev() ) ) )
            {
                if( !pFootnoteCont )
                {
                    rInf.SetStop( true );
                    return nullptr;
                }
                else
                {
                    // There must not be any Footnote Containers in column Areas and at the same time on the
                    // Page/Page column
                    if( pSct && !bAtSctEnd ) // Is the Container in a (column) Area?
                    {
                        SwFootnoteBossFrame* pTmp = pBoss->FindSctFrame()->FindFootnoteBossFrame( true );
                        SwFootnoteContFrame* pFootnoteC = pTmp->FindFootnoteCont();
                        if( pFootnoteC )
                        {
                            SwFootnoteFrame* pTmpFrame = static_cast<SwFootnoteFrame*>(pFootnoteC->Lower());
                            if( pTmpFrame && *pTmpFrame < pFootnote )
                            {
                                rInf.SetStop( true );
                                return nullptr;
                            }
                        }
                    }
                    // Is this the last Line that fits?
                    SwTwips nTmpBot = Y() + nReal * 2;

                    if( bVertical )
                        nTmpBot = m_pFrame->SwitchHorizontalToVertical( nTmpBot );

                    SwRectFnSet aRectFnSet(*pFootnoteCont);

                    const tools::Long nDiff = aRectFnSet.YDiff(
                                            aRectFnSet.GetTop(pFootnoteCont->getFrameArea()),
                                             nTmpBot );

                    if( pScrFrame && nDiff < 0 )
                    {
                        if( pFootnoteFrame )
                        {
                            SwFootnoteBossFrame *pFootnoteBoss = pFootnoteFrame->FindFootnoteBossFrame();
                            if( pFootnoteBoss != pBoss )
                            {
                                // We're in the last Line and the Footnote has moved
                                // to another Page. We also want to be on that Page!
                                rInf.SetStop( true );
                                return nullptr;
                            }
                        }
                    }
                }
            }
        }
    }
    // Finally: Create FootnotePortion and exit ...
    SwFootnotePortion *pRet = new SwFootnotePortion(
            rFootnote.GetViewNumStr(*pDoc, m_pFrame->getRootFrame()),
                                           pFootnote, nReal );
    rInf.SetFootnoteInside( true );

    return pRet;
}

/**
 * The portion for the Footnote Numbering in the Footnote Area
 */
SwNumberPortion *SwTextFormatter::NewFootnoteNumPortion( SwTextFormatInfo const &rInf ) const
{
    OSL_ENSURE( m_pFrame->IsInFootnote() && !m_pFrame->GetIndPrev() && !rInf.IsFootnoteDone(),
            "This is the wrong place for a ftnnumber" );
    if( rInf.GetTextStart() != m_nStart ||
        rInf.GetTextStart() != rInf.GetIdx() )
        return nullptr;

    const SwFootnoteFrame* pFootnoteFrame = m_pFrame->FindFootnoteFrame();
    const SwTextFootnote* pFootnote = pFootnoteFrame->GetAttr();

    // Aha! So we're in the Footnote Area!
    SwFormatFootnote& rFootnote = const_cast<SwFormatFootnote&>(pFootnote->GetFootnote());

    SwDoc *const pDoc = &m_pFrame->GetDoc();
    OUString aFootnoteText(rFootnote.GetViewNumStr(*pDoc, m_pFrame->getRootFrame(), true));

    const SwEndNoteInfo* pInfo;
    if( rFootnote.IsEndNote() )
        pInfo = &pDoc->GetEndNoteInfo();
    else
        pInfo = &pDoc->GetFootnoteInfo();

    const SwAttrSet* pParSet = &rInf.GetCharAttr();
    const IDocumentSettingAccess* pIDSA = &pDoc->getIDocumentSettingAccess();
    std::unique_ptr<SwFont> pNumFnt(new SwFont( pParSet, pIDSA ));

    // #i37142#
    // Underline style of paragraph font should not be considered
    // Overline style of paragraph font should not be considered
    // Weight style of paragraph font should not be considered
    // Posture style of paragraph font should not be considered
    // See also #i18463# and SwTextFormatter::NewNumberPortion()
    pNumFnt->SetUnderline( LINESTYLE_NONE );
    pNumFnt->SetOverline( LINESTYLE_NONE );
    pNumFnt->SetItalic( ITALIC_NONE, SwFontScript::Latin );
    pNumFnt->SetItalic( ITALIC_NONE, SwFontScript::CJK );
    pNumFnt->SetItalic( ITALIC_NONE, SwFontScript::CTL );
    pNumFnt->SetWeight( WEIGHT_NORMAL, SwFontScript::Latin );
    pNumFnt->SetWeight( WEIGHT_NORMAL, SwFontScript::CJK );
    pNumFnt->SetWeight( WEIGHT_NORMAL, SwFontScript::CTL );

    const rtl::Reference<SwXTextRange> xAnchor = rFootnote.getAnchor(*pDoc);
    if (xAnchor.is())
    {
        auto aAny = xAnchor->getPropertyValue(u"CharFontCharSet"_ustr);
        sal_Int16 eCharSet;
        if ((aAny >>= eCharSet) && eCharSet == awt::CharSet::SYMBOL)
        {
            OUString aFontName;
            aAny = xAnchor->getPropertyValue(u"CharFontName"_ustr);
            if (aAny  >>= aFontName)
            {
                pNumFnt->SetName(aFontName, SwFontScript::Latin);
                pNumFnt->SetName(aFontName, SwFontScript::CJK);
                pNumFnt->SetName(aFontName, SwFontScript::CTL);
                pNumFnt->SetCharSet(RTL_TEXTENCODING_SYMBOL, SwFontScript::Latin);
                pNumFnt->SetCharSet(RTL_TEXTENCODING_SYMBOL, SwFontScript::CJK);
                pNumFnt->SetCharSet(RTL_TEXTENCODING_SYMBOL, SwFontScript::CTL);
            }
        }
    }

    const SwAttrSet& rSet = pInfo->GetCharFormat(*pDoc)->GetAttrSet();
    pNumFnt->SetDiffFnt(&rSet, pIDSA );
    pNumFnt->SetVertical( pNumFnt->GetOrientation(), m_pFrame->IsVertical() );

    // tdf#85610 apply redline coloring to the footnote numbering in the footnote area
    SwUnoInternalPaM aPam(*pDoc);
    if ( ::sw::XTextRangeToSwPaM(aPam, xAnchor) )
    {
        SwRedlineTable::size_type nRedlinePos = 0;
        const SwRedlineTable& rTable = pDoc->getIDocumentRedlineAccess().GetRedlineTable();
        const SwRangeRedline* pRedline = rTable.FindAtPosition( *aPam.Start(), nRedlinePos );
        if (pRedline)
        {
            SwAttrPool& rPool = pDoc->GetAttrPool();
            SfxItemSetFixed<RES_CHRATR_BEGIN, RES_CHRATR_END-1> aSet(rPool);

            std::size_t aAuthor = (1 < pRedline->GetStackCount())
                    ? pRedline->GetAuthor( 1 )
                    : pRedline->GetAuthor();

            if ( RedlineType::Delete == pRedline->GetType() )
                SwModule::get()->GetDeletedAuthorAttr(aAuthor, aSet, SwRedlineRenderMode::Standard);
            else
                SwModule::get()->GetInsertAuthorAttr(aAuthor, aSet, SwRedlineRenderMode::Standard);

            if (const SvxColorItem* pItem = aSet.GetItemIfSet(RES_CHRATR_COLOR))
                pNumFnt->SetColor(pItem->GetValue());
            if (const SvxUnderlineItem* pItem = aSet.GetItemIfSet(RES_CHRATR_UNDERLINE))
                pNumFnt->SetUnderline(pItem->GetLineStyle());
            if (const SvxCrossedOutItem* pItem = aSet.GetItemIfSet(RES_CHRATR_CROSSEDOUT))
                pNumFnt->SetStrikeout( pItem->GetStrikeout() );
        }
    }

    SwFootnoteNumPortion* pNewPor = new SwFootnoteNumPortion( aFootnoteText, std::move(pNumFnt) );
    pNewPor->SetLeft( !m_pFrame->IsRightToLeft() );
    return pNewPor;
}

static OUString lcl_GetPageNumber( const SwPageFrame* pPage )
{
    assert(pPage && "GetPageNumber: Homeless TextFrame");
    const sal_uInt16 nVirtNum = pPage->GetVirtPageNum();
    const SvxNumberType& rNum = pPage->GetPageDesc()->GetNumType();
    return rNum.GetNumStr( nVirtNum );
}

SwErgoSumPortion *SwTextFormatter::NewErgoSumPortion( SwTextFormatInfo const &rInf ) const
{
    // We cannot assume we're a Follow
    if( !m_pFrame->IsInFootnote()  || m_pFrame->GetPrev() ||
        rInf.IsErgoDone() || rInf.GetIdx() != m_pFrame->GetOffset() ||
        m_pFrame->ImplFindFootnoteFrame()->GetAttr()->GetFootnote().IsEndNote() )
        return nullptr;

    // we are in the footnote container
    const SwFootnoteInfo &rFootnoteInfo = m_pFrame->GetDoc().GetFootnoteInfo();
    SwTextFrame *pQuoFrame = m_pFrame->FindQuoVadisFrame();
    if( !pQuoFrame )
        return nullptr;
    const SwPageFrame* pPage = m_pFrame->FindPageFrame();
    const SwPageFrame* pQuoPage = pQuoFrame->FindPageFrame();
    if( pPage == pQuoFrame->FindPageFrame() )
        return nullptr; // If the QuoVadis is on the same Column/Page
    const OUString aPage = lcl_GetPageNumber( pPage );
    SwParaPortion *pPara = pQuoFrame->GetPara();
    if( pPara )
        pPara->SetErgoSumNum( aPage );
    if( rFootnoteInfo.m_aErgoSum.isEmpty() )
        return nullptr;
    SwErgoSumPortion *pErgo = new SwErgoSumPortion( rFootnoteInfo.m_aErgoSum,
                                lcl_GetPageNumber( pQuoPage ) );
    return pErgo;
}

TextFrameIndex SwTextFormatter::FormatQuoVadis(TextFrameIndex const nOffset)
{
    OSL_ENSURE( ! m_pFrame->IsVertical() || ! m_pFrame->IsSwapped(),
            "SwTextFormatter::FormatQuoVadis with swapped frame" );

    if( !m_pFrame->IsInFootnote() || m_pFrame->ImplFindFootnoteFrame()->GetAttr()->GetFootnote().IsEndNote() )
        return nOffset;

    const SwFrame* pErgoFrame = m_pFrame->FindFootnoteFrame()->GetFollow();
    if( !pErgoFrame && m_pFrame->HasFollow() )
        pErgoFrame = m_pFrame->GetFollow();
    if( !pErgoFrame )
        return nOffset;

    if( pErgoFrame == m_pFrame->GetNext() )
    {
        SwFrame *pCol = m_pFrame->FindColFrame();
        while( pCol && !pCol->GetNext() )
            pCol = pCol->GetUpper()->FindColFrame();
        if( pCol )
            return nOffset;
    }
    else
    {
        const SwPageFrame* pPage = m_pFrame->FindPageFrame();
        const SwPageFrame* pErgoPage = pErgoFrame->FindPageFrame();
        if( pPage == pErgoPage )
            return nOffset; // If the ErgoSum is on the same Page
    }

    SwTextFormatInfo &rInf = GetInfo();
    const SwFootnoteInfo &rFootnoteInfo = m_pFrame->GetDoc().GetFootnoteInfo();
    if( rFootnoteInfo.m_aQuoVadis.isEmpty() )
        return nOffset;

    // A remark on QuoVadis/ErgoSum:
    // We use the Font set for the Paragraph for these texts.
    // Thus, we initialize:
    // TODO: ResetFont();
    FeedInf( rInf );
    SeekStartAndChg( rInf, true );
    if( GetRedln() && m_pCurr->HasRedline() )
    {
        std::pair<SwTextNode const*, sal_Int32> const pos(
                GetTextFrame()->MapViewToModel(nOffset));
        GetRedln()->Seek(*m_pFont, pos.first->GetIndex(), pos.second, 0);
    }

    // A tricky special case: Flyfrms extend into the Line and are at the
    // position we want to insert the Quovadis text
    // Let's see if it is that bad indeed:
    SwLinePortion *pPor = m_pCurr->GetFirstPortion();
    SwTwips nLastLeft = 0;
    while( pPor )
    {
        if ( pPor->IsFlyPortion() )
            nLastLeft = static_cast<SwFlyPortion*>(pPor)->GetFix() +
                        static_cast<SwFlyPortion*>(pPor)->Width();
        pPor = pPor->GetNextPortion();
    }

    // The old game all over again: we want the Line to wrap around
    // at a certain point, so we adjust the width.
    // nLastLeft is now basically the right margin
    const SwTwips nOldRealWidth = rInf.RealWidth();
    rInf.RealWidth( nOldRealWidth - nLastLeft );

    OUString aErgo = lcl_GetPageNumber( pErgoFrame->FindPageFrame() );
    SwQuoVadisPortion *pQuo = new SwQuoVadisPortion(rFootnoteInfo.m_aQuoVadis, aErgo );
    pQuo->SetAscent( rInf.GetAscent()  );
    pQuo->Height( rInf.GetTextHeight() );
    pQuo->Format( rInf );
    SwTwips nQuoWidth = pQuo->Width();
    SwLinePortion* pCurrPor = pQuo;

    while ( rInf.GetRest() )
    {
        SwLinePortion* pFollow = rInf.GetRest();
        rInf.SetRest( nullptr );
        pCurrPor->Move( rInf );

        OSL_ENSURE( pFollow->IsQuoVadisPortion(),
                "Quo Vadis, rest of QuoVadisPortion" );

        // format the rest and append it to the other QuoVadis parts
        pFollow->Format( rInf );
        nQuoWidth = nQuoWidth + pFollow->Width();

        pCurrPor->Append( pFollow );
        pCurrPor = pFollow;
    }

    Right( Right() - nQuoWidth );

    TextFrameIndex nRet;
    {
        SwSwapIfNotSwapped swap(m_pFrame);

        nRet = FormatLine( m_nStart );
    }

    Right( rInf.Left() + nOldRealWidth - 1 );

    nLastLeft = nOldRealWidth - m_pCurr->Width();
    FeedInf( rInf );

    // It's possible that there's a Margin Portion at the end, which would
    // just cause a lot of trouble, when respanning
    pPor = m_pCurr->FindLastPortion();
    SwGluePortion *pGlue = pPor->IsMarginPortion() ? static_cast<SwMarginPortion*>(pPor) : nullptr;
    if( pGlue )
    {
        pGlue->Height( 0 );
        pGlue->Width( 0 );
        pGlue->SetLen(TextFrameIndex(0));
        pGlue->SetAscent( 0 );
        pGlue->SetNextPortion( nullptr );
        pGlue->SetFixWidth(0);
    }

    // Luxury: We make sure the QuoVadis text appears on the right, by
    // using Glues.
    nLastLeft = nLastLeft - nQuoWidth;
    if( nLastLeft )
    {
        if( nLastLeft > pQuo->GetAscent() ) // Minimum distance
        {
            switch( GetAdjust() )
            {
                case SvxAdjust::Block:
                {
                    if( !m_pCurr->GetLen() ||
                        CH_BREAK != GetInfo().GetChar(m_nStart + m_pCurr->GetLen() - TextFrameIndex(1)))
                        nLastLeft = pQuo->GetAscent();
                    nQuoWidth = nQuoWidth + nLastLeft;
                    break;
                }
                case SvxAdjust::Right:
                {
                    nLastLeft = pQuo->GetAscent();
                    nQuoWidth = nQuoWidth + nLastLeft;
                    break;
                }
                case SvxAdjust::Center:
                {
                    nQuoWidth = nQuoWidth + pQuo->GetAscent();
                    tools::Long nDiff = nLastLeft - nQuoWidth;
                    if( nDiff < 0 )
                    {
                        nLastLeft = pQuo->GetAscent();
                        nQuoWidth = -nDiff + nLastLeft;
                    }
                    else
                    {
                        nQuoWidth = 0;
                        nLastLeft = (pQuo->GetAscent() + nDiff) / 2;
                    }
                    break;
                }
                default:
                    nQuoWidth = nQuoWidth + nLastLeft;
            }
        }
        else
            nQuoWidth = nQuoWidth + nLastLeft;
        if( nLastLeft )
        {
            pGlue = new SwGluePortion(0);
            pGlue->Width( nLastLeft );
            pPor->Append( pGlue );
            pPor = pPor->GetNextPortion();
        }
    }

    // Finally: we insert the QuoVadis Portion
    pCurrPor = pQuo;
    while ( pCurrPor )
    {
        // pPor->Append deletes the pPortion pointer of pPor.
        // Therefore we have to keep a pointer to the next portion
        pQuo = static_cast<SwQuoVadisPortion*>(pCurrPor->GetNextPortion());
        pPor->Append( pCurrPor );
        pPor = pPor->GetNextPortion();
        pCurrPor = pQuo;
    }

    m_pCurr->Width( m_pCurr->Width() + nQuoWidth );

    // And adjust again, due to the adjustment and due to the following special
    // case:
    // The DummyUser has set a smaller Font in the Line than the one used
    // by the QuoVadis text ...
    CalcAdjustLine( m_pCurr );

    return nRet;
}

/**
 * This function creates a Line that reaches to the other Page Margin.
 * DummyLines or DummyPortions make sure, that oscillations stop, because
 * there's no way to flow back.
 * They are used for Footnotes in paragraph-bound Frames and for Footnote
 * oscillations
 */
void SwTextFormatter::MakeDummyLine()
{
    SwTwips nRstHeight = GetFrameRstHeight();
    if( m_pCurr && nRstHeight > m_pCurr->Height() )
    {
        SwLineLayout *pLay = new SwLineLayout;
        nRstHeight = nRstHeight - m_pCurr->Height();
        pLay->Height( nRstHeight );
        pLay->SetAscent( nRstHeight );
        Insert( pLay );
        Next();
    }
}

namespace {

class SwFootnoteSave
{
    SwTextSizeInfo* m_pInf;
    SwFont* m_pFnt;
    std::unique_ptr<SwFont> m_pOld;

    SwFootnoteSave(const SwFootnoteSave&) = delete;
    SwFootnoteSave& operator=(const SwFootnoteSave&) = delete;

public:
    SwFootnoteSave( const SwTextSizeInfo &rInf,
               const SwTextFootnote *pTextFootnote,
               const bool bApplyGivenScriptType,
               const SwFontScript nGivenScriptType );
    ~SwFootnoteSave() COVERITY_NOEXCEPT_FALSE;
};

}

SwFootnoteSave::SwFootnoteSave(const SwTextSizeInfo& rInf, const SwTextFootnote* pTextFootnote,
                               const bool bApplyGivenScriptType,
                               const SwFontScript nGivenScriptType)
    : m_pInf(&const_cast<SwTextSizeInfo&>(rInf))
    , m_pFnt(nullptr)
{
    if( pTextFootnote && rInf.GetTextFrame() )
    {
        m_pFnt = const_cast<SwTextSizeInfo&>(rInf).GetFont();
        m_pOld.reset(new SwFont(*m_pFnt));
        m_pOld->GetTox() = m_pFnt->GetTox();
        m_pFnt->GetTox() = 0;
        SwFormatFootnote& rFootnote = const_cast<SwFormatFootnote&>(pTextFootnote->GetFootnote());
        const SwDoc *const pDoc = &rInf.GetTextFrame()->GetDoc();

        // #i98418#
        if ( bApplyGivenScriptType )
        {
            m_pFnt->SetActual(nGivenScriptType);
        }
        else
        {
            // examine text and set script
            OUString aTmpStr(rFootnote.GetViewNumStr(*pDoc, rInf.GetTextFrame()->getRootFrame()));
            m_pFnt->SetActual(SwScriptInfo::WhichFont(0, aTmpStr));
        }

        const SwEndNoteInfo* pInfo;
        if( rFootnote.IsEndNote() )
            pInfo = &pDoc->GetEndNoteInfo();
        else
            pInfo = &pDoc->GetFootnoteInfo();
        const SwAttrSet& rSet = pInfo->GetAnchorCharFormat(const_cast<SwDoc&>(*pDoc))->GetAttrSet();
        m_pFnt->SetDiffFnt(&rSet, &pDoc->getIDocumentSettingAccess());

        // we reduce footnote size, if we are inside a double line portion
        if (!m_pOld->GetEscapement() && 50 == m_pOld->GetPropr())
        {
            Size aSize = m_pFnt->GetSize(m_pFnt->GetActual());
            m_pFnt->SetSize(Size(aSize.Width() / 2, aSize.Height() / 2), m_pFnt->GetActual());
        }

        // set the correct rotation at the footnote font
        if( const SvxCharRotateItem* pItem = rSet.GetItemIfSet( RES_CHRATR_ROTATE ) )
            m_pFnt->SetVertical(pItem->GetValue(),
                                rInf.GetTextFrame()->IsVertical());

        m_pFnt->ChgPhysFnt(m_pInf->GetVsh(), *m_pInf->GetOut());

        if( const SvxBrushItem* pItem = rSet.GetItemIfSet( RES_CHRATR_BACKGROUND ) )
            m_pFnt->SetBackColor(pItem->GetColor());
    }
    else
        m_pFnt = nullptr;
}

SwFootnoteSave::~SwFootnoteSave() COVERITY_NOEXCEPT_FALSE
{
    if (m_pFnt)
    {
        // Put back SwFont
        *m_pFnt = *m_pOld;
        m_pFnt->GetTox() = m_pOld->GetTox();
        m_pFnt->ChgPhysFnt(m_pInf->GetVsh(), *m_pInf->GetOut());
        m_pOld.reset();
    }
}

SwFootnotePortion::SwFootnotePortion(const OUString& rExpand, SwTextFootnote* pFootn, SwTwips nReal)
        : SwFieldPortion( rExpand, nullptr )
        , m_pFootnote(pFootn)
        , m_nOrigHeight( nReal )
        // #i98418#
        , mbPreferredScriptTypeSet( false )
        , mnPreferredScriptType( SwFontScript::Latin )
{
    SetLen(TextFrameIndex(1));
    SetWhichPor( PortionType::Footnote );
}

bool SwFootnotePortion::GetExpText( const SwTextSizeInfo &, OUString &rText ) const
{
    rText = m_aExpand;
    return true;
}

bool SwFootnotePortion::Format( SwTextFormatInfo &rInf )
{
    // #i98418#
//    SwFootnoteSave aFootnoteSave( rInf, pFootnote );
    SwFootnoteSave aFootnoteSave( rInf, m_pFootnote, mbPreferredScriptTypeSet, mnPreferredScriptType );
    // the idx is manipulated in SwExpandPortion::Format
    // this flag indicates, that a footnote is allowed to trigger
    // an underflow during SwTextGuess::Guess
    rInf.SetFakeLineStart( rInf.GetIdx() > rInf.GetLineStart() );
    const bool bFull = SwFieldPortion::Format( rInf );
    rInf.SetFakeLineStart( false );
    SetAscent( rInf.GetAscent() );
    Height( rInf.GetTextHeight() );
    rInf.SetFootnoteDone( !bFull );
    if (!bFull && m_pFootnote)
        rInf.SetParaFootnote();
    return bFull;
}

void SwFootnotePortion::Paint( const SwTextPaintInfo &rInf ) const
{
    // #i98418#
//    SwFootnoteSave aFootnoteSave( rInf, pFootnote );
    SwFootnoteSave aFootnoteSave( rInf, m_pFootnote, mbPreferredScriptTypeSet, mnPreferredScriptType );
    rInf.DrawViewOpt( *this, PortionType::Footnote );
    SwExpandPortion::Paint( rInf );
}

SwPositiveSize SwFootnotePortion::GetTextSize( const SwTextSizeInfo &rInfo ) const
{
    // #i98418#
//    SwFootnoteSave aFootnoteSave( rInfo, pFootnote );
    SwFootnoteSave aFootnoteSave( rInfo, m_pFootnote, mbPreferredScriptTypeSet, mnPreferredScriptType );
    return SwExpandPortion::GetTextSize( rInfo );
}

// #i98418#
void SwFootnotePortion::SetPreferredScriptType( SwFontScript nPreferredScriptType )
{
    mbPreferredScriptTypeSet = true;
    mnPreferredScriptType = nPreferredScriptType;
}

SwFieldPortion *SwQuoVadisPortion::Clone( const OUString &rExpand ) const
{
    return new SwQuoVadisPortion( rExpand, m_aErgo );
}

SwQuoVadisPortion::SwQuoVadisPortion( const OUString &rExp, OUString aStr )
    : SwFieldPortion( rExp ), m_aErgo(std::move(aStr))
{
    SetLen(TextFrameIndex(0));
    SetWhichPor( PortionType::QuoVadis );
}

bool SwQuoVadisPortion::Format( SwTextFormatInfo &rInf )
{
    // First try; maybe the Text fits
    CheckScript( rInf );
    bool bFull = SwFieldPortion::Format( rInf );
    SetLen(TextFrameIndex(0));

    if( bFull )
    {
        // Second try; we make the String shorter
        m_aExpand = "...";
        bFull = SwFieldPortion::Format( rInf );
        SetLen(TextFrameIndex(0));
        if( bFull  )
            // Third try; we're done: we crush
            Width(rInf.Width() - rInf.X());

        // No multiline Fields for QuoVadis and ErgoSum
        if( rInf.GetRest() )
        {
            delete rInf.GetRest();
            rInf.SetRest( nullptr );
        }
    }
    return bFull;
}

bool SwQuoVadisPortion::GetExpText( const SwTextSizeInfo &, OUString &rText ) const
{
    rText = m_aExpand;
    // if this QuoVadisPortion has a follow, the follow is responsible for
    // the ergo text.
    if ( ! HasFollow() )
        rText += m_aErgo;
    return true;
}

void SwQuoVadisPortion::HandlePortion( SwPortionHandler& rPH ) const
{
    rPH.Special( GetLen(), m_aExpand + m_aErgo, GetWhichPor() );
}

void SwQuoVadisPortion::Paint( const SwTextPaintInfo &rInf ) const
{
    // We _always_ want to output per DrawStretchText, because nErgo
    // can quickly switch
    if( PrtWidth() )
    {
        rInf.DrawViewOpt( *this, PortionType::QuoVadis );
        SwTextSlot aDiffText( &rInf, this, true, false );
        SwFontSave aSave( rInf, m_pFont.get() );
        rInf.DrawText( *this, rInf.GetLen(), true );
    }
}

SwFieldPortion *SwErgoSumPortion::Clone( const OUString &rExpand ) const
{
    return new SwErgoSumPortion( rExpand, std::u16string_view() );
}

SwErgoSumPortion::SwErgoSumPortion(const OUString &rExp, std::u16string_view rStr)
    : SwFieldPortion( rExp )
{
    SetLen(TextFrameIndex(0));
    m_aExpand += rStr;

    // One blank distance to the text
    m_aExpand += " ";
    SetWhichPor( PortionType::ErgoSum );
}

TextFrameIndex SwErgoSumPortion::GetModelPositionForViewPoint(const SwTwips) const
{
    return TextFrameIndex(0);
}

bool SwErgoSumPortion::Format( SwTextFormatInfo &rInf )
{
    const bool bFull = SwFieldPortion::Format( rInf );
    SetLen(TextFrameIndex(0));
    rInf.SetErgoDone( true );

    // No multiline Fields for QuoVadis and ErgoSum
    if( bFull && rInf.GetRest() )
    {
        delete rInf.GetRest();
        rInf.SetRest( nullptr );
    }

    // We return false in order to get some text into the current line,
    // even if it's full (better than looping)
    return false;
}

void SwParaPortion::SetErgoSumNum( const OUString& rErgo )
{
    SwLineLayout *pLay = this;
    while( pLay->GetNext() )
    {
        pLay = pLay->GetNext();
    }
    SwLinePortion     *pPor = pLay;
    SwQuoVadisPortion *pQuo = nullptr;
    while( pPor && !pQuo )
    {
        if ( pPor->IsQuoVadisPortion() )
            pQuo = static_cast<SwQuoVadisPortion*>(pPor);
        pPor = pPor->GetNextPortion();
    }
    if( pQuo )
        pQuo->SetNumber( rErgo );
}

/**
 * Is called in SwTextFrame::Prepare()
 */
bool SwParaPortion::UpdateQuoVadis( std::u16string_view rQuo )
{
    SwLineLayout *pLay = this;
    while( pLay->GetNext() )
    {
        pLay = pLay->GetNext();
    }
    SwLinePortion     *pPor = pLay;
    SwQuoVadisPortion *pQuo = nullptr;
    while( pPor && !pQuo )
    {
        if ( pPor->IsQuoVadisPortion() )
            pQuo = static_cast<SwQuoVadisPortion*>(pPor);
        pPor = pPor->GetNextPortion();
    }

    if( !pQuo )
        return false;

    return pQuo->GetQuoText() == rQuo;
}

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