    /* -*- 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 <algorithm>

    #include <hintids.hxx>
    #include <editeng/charscaleitem.hxx>
    #include <txtatr.hxx>
    #include <svl/itemiter.hxx>
    #include <sfx2/printer.hxx>
    #include <svx/svdobj.hxx>
    #include <vcl/window.hxx>
    #include <vcl/svapp.hxx>
    #include <fmtanchr.hxx>
    #include <fmtfsize.hxx>
    #include <fmtornt.hxx>
    #include <fmtflcnt.hxx>
    #include <fmtcntnt.hxx>
    #include <fmtftn.hxx>
    #include <frmatr.hxx>
    #include <frmfmt.hxx>
    #include <fmtfld.hxx>
    #include <doc.hxx>
    #include <IDocumentSettingAccess.hxx>
    #include <IDocumentDeviceAccess.hxx>
    #include <IDocumentLayoutAccess.hxx>
    #include <viewsh.hxx>
    #include <rootfrm.hxx>
    #include <docary.hxx>
    #include <ndtxt.hxx>
    #include <dcontact.hxx>
    #include <fldbas.hxx>
    #include <pam.hxx>
    #include "itratr.hxx"
    #include <htmltbl.hxx>
    #include <swtable.hxx>
    #include "redlnitr.hxx"
    #include <redline.hxx>
    #include <fmtsrnd.hxx>
    #include "itrtxt.hxx"
    #include <breakit.hxx>
    #include <com/sun/star/i18n/WordType.hpp>
    #include <com/sun/star/i18n/XBreakIterator.hpp>
    #include <editeng/lrspitem.hxx>
    #include <editeng/rsiditem.hxx>
    #include <calbck.hxx>

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

    static sal_Int32 GetNextAttrImpl(SwTextNode const* pTextNode,
            size_t nStartIndex, size_t nEndIndex, sal_Int32 nPosition);

    SwAttrIter::SwAttrIter(SwTextNode const * pTextNode)
        : m_pViewShell(nullptr)
        , m_pFont(nullptr)
        , m_pScriptInfo(nullptr)
        , m_pLastOut(nullptr)
        , m_nChgCnt(0)
        , m_nStartIndex(0)
        , m_nEndIndex(0)
        , m_nPosition(0)
        , m_nPropFont(0)
        , m_pTextNode(pTextNode)
        , m_pMergedPara(nullptr)
    {
        m_aFontCacheIds[SwFontScript::Latin] = m_aFontCacheIds[SwFontScript::CJK] = m_aFontCacheIds[SwFontScript::CTL] = nullptr;
    }

    SwAttrIter::SwAttrIter(SwTextNode& rTextNode, SwScriptInfo& rScrInf, SwTextFrame const*const pFrame)
        : m_pViewShell(nullptr)
        , m_pFont(nullptr)
        , m_pScriptInfo(nullptr)
        , m_pLastOut(nullptr)
        , m_nChgCnt(0)
        , m_nPropFont(0)
        , m_pTextNode(&rTextNode)
        , m_pMergedPara(nullptr)
    {
        CtorInitAttrIter(rTextNode, rScrInf, pFrame);
    }

    void SwAttrIter::Chg( SwTextAttr const *pHt )
    {
        assert(pHt && m_pFont && "No attribute of font available for change");
        if( m_pRedline && m_pRedline->IsOn() )
            m_pRedline->ChangeTextAttr( m_pFont, *pHt, true );
        else
            m_aAttrHandler.PushAndChg( *pHt, *m_pFont );
        m_nChgCnt++;
    }

    void SwAttrIter::Rst( SwTextAttr const *pHt )
    {
        assert(pHt && m_pFont && "No attribute of font available for reset");
        // get top from stack after removing pHt
        if( m_pRedline && m_pRedline->IsOn() )
            m_pRedline->ChangeTextAttr( m_pFont, *pHt, false );
        else
            m_aAttrHandler.PopAndChg( *pHt, *m_pFont );
        m_nChgCnt--;
    }

    SwAttrIter::~SwAttrIter()
    {
        m_pRedline.reset();
        delete m_pFont;
    }

    bool SwAttrIter::MaybeHasHints() const
    {
        return nullptr != m_pTextNode->GetpSwpHints() || nullptr != m_pMergedPara;
    }

    /**
     * Returns the attribute for a position
     *
     * Only if the attribute is exactly at the position @param nPos and
     * does not have an EndIndex
     *
     * We need this function for attributes which should alter formatting without
     * changing the content of the string.
     * Such "degenerated" attributes are e.g.: fields which retain expanded text and
     * line-bound Frames.
     * In order to avoid ambiguities between different such attributes, we insert a
     * special character at the start of the string, when creating such an attribute.
     * The Formatter later on encounters such a special character and retrieves the
     * degenerate attribute via GetAttr().
     */
    SwTextAttr *SwAttrIter::GetAttr(TextFrameIndex const nPosition) const
    {
        std::pair<SwTextNode const*, sal_Int32> const pos( m_pMergedPara
            ? sw::MapViewToModel(*m_pMergedPara, nPosition)
            : std::make_pair(m_pTextNode, sal_Int32(nPosition)));
        return pos.first->GetTextAttrForCharAt(pos.second);
    }

    bool SwAttrIter::SeekAndChgAttrIter(TextFrameIndex const nNewPos, OutputDevice* pOut)
    {
        std::pair<SwTextNode const*, sal_Int32> const pos( m_pMergedPara
            ? sw::MapViewToModel(*m_pMergedPara, nNewPos)
            : std::make_pair(m_pTextNode, sal_Int32(nNewPos)));
        bool bChg = m_nStartIndex && pos.first == m_pTextNode && pos.second == m_nPosition
            ? m_pFont->IsFntChg()
            : Seek( nNewPos );
        if ( m_pLastOut.get() != pOut )
        {
            m_pLastOut = pOut;
            m_pFont->SetFntChg( true );
            bChg = true;
        }
        if( bChg )
        {
            // if the change counter is zero, we know the cache id of the wanted font
            if ( !m_nChgCnt && !m_nPropFont )
                m_pFont->SetFontCacheId( m_aFontCacheIds[ m_pFont->GetActual() ],
                    m_aFontIdx[ m_pFont->GetActual() ], m_pFont->GetActual() );
            m_pFont->ChgPhysFnt( m_pViewShell, *pOut );
        }

        return bChg;
    }

    bool SwAttrIter::IsSymbol(TextFrameIndex const nNewPos)
    {
        Seek( nNewPos );
        if ( !m_nChgCnt && !m_nPropFont )
            m_pFont->SetFontCacheId( m_aFontCacheIds[ m_pFont->GetActual() ],
                m_aFontIdx[ m_pFont->GetActual() ], m_pFont->GetActual() );
        return m_pFont->IsSymbol( m_pViewShell );
    }

    bool SwAttrIter::SeekStartAndChgAttrIter( OutputDevice* pOut, const bool bParaFont )
    {
        SwTextNode const*const pFirstTextNode(m_pMergedPara ? m_pMergedPara->pFirstNode : m_pTextNode);
        if ( m_pRedline && m_pRedline->ExtOn() )
            m_pRedline->LeaveExtend(*m_pFont, pFirstTextNode->GetIndex(), 0);

        if (m_pTextNode != pFirstTextNode)
        {
            assert(m_pMergedPara);
            m_pTextNode = m_pMergedPara->pFirstNode;
            InitFontAndAttrHandler(*m_pTextNode, m_pMergedPara->mergedText, nullptr);
        }

        // reset font to its original state
        m_aAttrHandler.Reset();
        m_aAttrHandler.ResetFont( *m_pFont );

        m_nStartIndex = 0;
        m_nEndIndex = 0;
        m_nPosition = 0;
        m_nChgCnt = 0;
        if( m_nPropFont )
            m_pFont->SetProportion( m_nPropFont );
        if( m_pRedline )
        {
            m_pRedline->Clear( m_pFont );
            if( !bParaFont )
                m_nChgCnt = m_nChgCnt + m_pRedline->Seek(*m_pFont, pFirstTextNode->GetIndex(), 0, COMPLETE_STRING);
            else
                m_pRedline->Reset();
        }

        SwpHints const*const pHints(m_pTextNode->GetpSwpHints());
        if (pHints && !bParaFont)
        {
            SwTextAttr *pTextAttr;
            // While we've not reached the end of the StartArray && the TextAttribute starts at position 0...
            while ((m_nStartIndex < pHints->Count()) &&
                   !((pTextAttr = pHints->Get(m_nStartIndex))->GetStart()))
            {
                // open the TextAttributes
                Chg( pTextAttr );
                m_nStartIndex++;
            }
        }

        bool bChg = m_pFont->IsFntChg();
        if ( m_pLastOut.get() != pOut )
        {
            m_pLastOut = pOut;
            m_pFont->SetFntChg( true );
            bChg = true;
        }
        if( bChg )
        {
            // if the application counter is zero, we know the cache id of the wanted font
            if ( !m_nChgCnt && !m_nPropFont )
                m_pFont->SetFontCacheId( m_aFontCacheIds[ m_pFont->GetActual() ],
                    m_aFontIdx[ m_pFont->GetActual() ], m_pFont->GetActual() );
            m_pFont->ChgPhysFnt( m_pViewShell, *pOut );
        }
        return bChg;
    }

    // AMA: New AttrIter Nov 94
    void SwAttrIter::SeekFwd( const sal_Int32 nNewPos )
    {
        SwpHints const*const pHints(m_pTextNode->GetpSwpHints());
        SwTextAttr *pTextAttr;

        if ( m_nStartIndex ) // If attributes have been opened at all ...
        {
            // Close attributes that are currently open, but stop at nNewPos+1

            // As long as we've not yet reached the end of EndArray and the
            // TextAttribute ends before or at the new position ...
            while ((m_nEndIndex < pHints->Count()) &&
                   (*(pTextAttr = pHints->GetSortedByEnd(m_nEndIndex))->GetAnyEnd() <= nNewPos))
            {
                // Close the TextAttributes, whose StartPos were before or at
                // the old nPos and are currently open
                if (pTextAttr->GetStart() <= m_nPosition)  Rst( pTextAttr );
                m_nEndIndex++;
            }
        }
        else // skip the not opened ends
        {
            while ((m_nEndIndex < pHints->Count()) &&
                   (*pHints->GetSortedByEnd(m_nEndIndex)->GetAnyEnd() <= nNewPos))
            {
                m_nEndIndex++;
            }
        }

        // As long as we've not yet reached the end of EndArray and the
        // TextAttribute ends before or at the new position...
        while ((m_nStartIndex < pHints->Count()) &&
                ((pTextAttr = pHints->Get(m_nStartIndex))->GetStart() <= nNewPos))
        {

            // open the TextAttributes, whose ends lie behind the new position
            if ( *pTextAttr->GetAnyEnd() > nNewPos )  Chg( pTextAttr );
            m_nStartIndex++;
        }

    }

    bool SwAttrIter::Seek(TextFrameIndex const nNewPos)
    {
        // note: nNewPos isn't necessarily a index returned from GetNextAttr
        std::pair<SwTextNode const*, sal_Int32> const newPos( m_pMergedPara
            ? sw::MapViewToModel(*m_pMergedPara, nNewPos)
            : std::make_pair(m_pTextNode, sal_Int32(nNewPos)));

        if ( m_pRedline && m_pRedline->ExtOn() )
            m_pRedline->LeaveExtend(*m_pFont, newPos.first->GetIndex(), newPos.second);
        if (m_pTextNode->GetIndex() < newPos.first->GetIndex())
        {
            // Skipping to a different node - first seek until the end of this node
            // to get rid of all hint items
            sal_Int32 nPos(m_nPosition);
            do
            {
                nPos = GetNextAttrImpl(m_pTextNode, m_nStartIndex, m_nEndIndex, nPos);
                if (nPos <= m_pTextNode->Len())
                {
                    SeekFwd(nPos);
                }
                else
                {
                    SeekFwd(m_pTextNode->Len());
                }
            }
            while (nPos < m_pTextNode->Len());
            assert(m_nChgCnt == 0); // should have reset it all? there cannot be ExtOn() inside of a Delete redline, surely?
            // Unapply current para items:
            // the SwAttrHandler doesn't appear to be capable of *unapplying*
            // items at all; it can only apply a previously effective item.
            // So do this by recreating the font from scratch.
            // Apply new para items:
            InitFontAndAttrHandler(*newPos.first, m_pMergedPara->mergedText, nullptr);
            // reset to next
            m_pTextNode = newPos.first;
            m_nStartIndex = 0;
            m_nEndIndex = 0;
            m_nPosition = 0;
            assert(m_pRedline);
        }

        if (!nNewPos || newPos.second < m_nPosition)
        {
            if (m_pMergedPara)
            {
                if (m_pTextNode != m_pMergedPara->pFirstNode)
                {
                    m_pTextNode = m_pMergedPara->pFirstNode;
                    // sw_redlinehide: hope it's okay to use the current text node
                    // here; the AttrHandler shouldn't care about non-char items
                    InitFontAndAttrHandler(*m_pTextNode, m_pMergedPara->mergedText, nullptr);
                }
            }
            if (m_pMergedPara || m_pTextNode->GetpSwpHints())
            {
                if( m_pRedline )
                    m_pRedline->Clear( nullptr );

                // reset font to its original state
                m_aAttrHandler.Reset();
                m_aAttrHandler.ResetFont( *m_pFont );

                if( m_nPropFont )
                    m_pFont->SetProportion( m_nPropFont );
                m_nStartIndex = 0;
                m_nEndIndex = 0;
                m_nPosition = 0;
                m_nChgCnt = 0;

                // Attention!
                // resetting the font here makes it necessary to apply any
                // changes for extended input directly to the font
                if ( m_pRedline && m_pRedline->ExtOn() )
                {
                    m_pRedline->UpdateExtFont( *m_pFont );
                    ++m_nChgCnt;
                }
            }
        }

        if (m_pTextNode->GetpSwpHints())
        {
            if (m_pMergedPara)
            {
                // iterate hint by hint: SeekFwd does not mix ends and starts,
                // it always applies all the starts last, so it must be called once
                // per position where hints start/end!
                sal_Int32 nPos(m_nPosition);
                do
                {
                    nPos = GetNextAttrImpl(m_pTextNode, m_nStartIndex, m_nEndIndex, nPos);
                    if (nPos <= newPos.second)
                    {
                        SeekFwd(nPos);
                    }
                    else
                    {
                        SeekFwd(newPos.second);
                    }
                }
                while (nPos < newPos.second);
            }
            else
            {
                SeekFwd(newPos.second);
            }
        }

        m_pFont->SetActual( m_pScriptInfo->WhichFont(nNewPos) );

        if( m_pRedline )
            m_nChgCnt = m_nChgCnt + m_pRedline->Seek(*m_pFont, newPos.first->GetIndex(), newPos.second, m_nPosition);
        m_nPosition = newPos.second;

        if( m_nPropFont )
            m_pFont->SetProportion( m_nPropFont );

        return m_pFont->IsFntChg();
    }

    static void InsertCharAttrs(SfxPoolItem const** pAttrs, SfxItemSet const& rItems)
    {
        SfxItemIter iter(rItems);
        for (SfxPoolItem const* pItem = iter.FirstItem(); pItem; pItem = iter.NextItem())
        {
            auto const nWhich(pItem->Which());
            if (isCHRATR(nWhich) && RES_CHRATR_RSID != nWhich)
            {
                pAttrs[nWhich - RES_CHRATR_BEGIN] = pItem;
            }
            else if (nWhich == RES_TXTATR_UNKNOWN_CONTAINER)
            {
                pAttrs[RES_CHRATR_END] = pItem;
            }
        }
    }

    // if return false: portion ends at start of redline, indexes unchanged
    // if return true: portion end not known (past end of redline), indexes point to first hint past end of redline
    bool CanSkipOverRedline(SwRangeRedline const& rRedline,
            size_t & rStartIndex, size_t & rEndIndex)
    {
        size_t nStartIndex(rStartIndex);
        size_t nEndIndex(rEndIndex);
        SwPosition const*const pRLStart(rRedline.Start());
        SwPosition const*const pRLEnd(rRedline.End());
        if (pRLEnd->nContent == pRLEnd->nNode.GetNode().GetTextNode()->Len())
        {
            // shortcut: nothing follows redline
            // current state is end state
            return false;
        }
        std::vector<SwTextAttr*> activeCharFmts;
        // can't compare the SwFont that's stored somewhere, it doesn't have compare
        // operator, so try to recreate the situation with some temp arrays here
        SfxPoolItem const* activeCharAttrsStart[RES_CHRATR_END - RES_CHRATR_BEGIN + 1] = { nullptr, };
        if (pRLStart->nNode != pRLEnd->nNode)
        {   // nodes' attributes are only needed if there are different nodes
            InsertCharAttrs(activeCharAttrsStart,
                    pRLStart->nNode.GetNode().GetTextNode()->GetSwAttrSet());
        }
        if (SwpHints *const pStartHints = pRLStart->nNode.GetNode().GetTextNode()->GetpSwpHints())
        {
            // check hint ends of hints that start before and end within
            sal_Int32 const nRedlineEnd(pRLStart->nNode == pRLEnd->nNode
                    ? pRLEnd->nContent.GetIndex()
                    : pRLStart->nNode.GetNode().GetTextNode()->Len());
            for ( ; nEndIndex < pStartHints->Count(); ++nEndIndex)
            {
                SwTextAttr *const pAttr(pStartHints->GetSortedByEnd(nEndIndex));
                if (nRedlineEnd < *pAttr->End())
                {
                    break;
                }
                if (!pAttr->End())
                    continue;
                if (pRLStart->nContent.GetIndex() <= pAttr->GetStart())
                {
                    continue;
                }
                if (pAttr->IsFormatIgnoreEnd())
                {
                    continue;
                }
                switch (pAttr->Which())
                {
                    // if any of these ends inside RL then we need a new portion
                    case RES_TXTATR_REFMARK:
                    case RES_TXTATR_TOXMARK:
                    case RES_TXTATR_META: // actually these 2 aren't allowed to overlap ???
                    case RES_TXTATR_METAFIELD:
                    case RES_TXTATR_INETFMT:
                    case RES_TXTATR_CJK_RUBY:
                    case RES_TXTATR_INPUTFIELD:
                        {
                            return false; // always break
                        }
                        break;
                    // these are guaranteed not to overlap
                    // and come in order of application
                    case RES_TXTATR_AUTOFMT:
                    case RES_TXTATR_CHARFMT:
                        {
                            if (pAttr->Which() == RES_TXTATR_CHARFMT)
                            {
                                activeCharFmts.push_back(pAttr);
                            }
                            // pure formatting hints may end inside the redline &
                            // start again inside the redline, which must not cause
                            // a new text portion if they have the same items - so
                            // store the effective items & compare all at the end
                            SfxItemSet const& rSet((pAttr->Which() == RES_TXTATR_CHARFMT)
                                ? static_cast<SfxItemSet const&>(pAttr->GetCharFormat().GetCharFormat()->GetAttrSet())
                                : *pAttr->GetAutoFormat().GetStyleHandle().get());
                            InsertCharAttrs(activeCharAttrsStart, rSet);
                        }
                        break;
                    // SwTextNode::SetAttr puts it into AUTOFMT which is quite
                    // sensible so it doesn't actually exist as a hint
                    case RES_TXTATR_UNKNOWN_CONTAINER:
                    default: assert(false);
                }
            }
            assert(nEndIndex == pStartHints->Count() ||
                pRLEnd->nContent.GetIndex() < *pStartHints->GetSortedByEnd(nEndIndex)->GetAnyEnd());
        }

        if (pRLStart->nNode != pRLEnd->nNode)
        {
            nStartIndex = 0;
            nEndIndex = 0;
        }

        // treat para properties as text properties
            // ... with the FormatToTextAttr we get autofmts that correspond to the *effective* attr set difference
            // effective attr set: para props + charfmts + autofmt *in that order*
            // ... and the charfmt must be *nominally* the same

        SfxPoolItem const* activeCharAttrsEnd[RES_CHRATR_END - RES_CHRATR_BEGIN + 1] = { nullptr, };
        if (pRLStart->nNode != pRLEnd->nNode)
        {   // nodes' attributes are only needed if there are different nodes
            InsertCharAttrs(activeCharAttrsEnd,
                    pRLEnd->nNode.GetNode().GetTextNode()->GetSwAttrSet());
        }

        if (SwpHints *const pEndHints = pRLEnd->nNode.GetNode().GetTextNode()->GetpSwpHints())
        {
            // check hint starts of hints that start within and end after
    #ifndef NDEBUG
            sal_Int32 const nRedlineStart(pRLStart->nNode == pRLEnd->nNode
                    ? pRLStart->nContent.GetIndex()
                    : 0);
    #endif
            for ( ; nStartIndex < pEndHints->Count(); ++nStartIndex)
            {
                SwTextAttr *const pAttr(pEndHints->Get(nStartIndex));
                // compare with < here, not <=, to get the effective formatting
                // of the 1st char after the redline; should not cause problems
                // with consecutive delete redlines because those are handed by
                // GetNextRedln() and here we have the last end pos.
                if (pRLEnd->nContent.GetIndex() < pAttr->GetStart())
                {
                    break;
                }
                if (!pAttr->End())
                    continue;
                if (pAttr->IsFormatIgnoreStart())
                {
                    continue;
                }
                assert(nRedlineStart <= pAttr->GetStart()); // we wouldn't be here otherwise?
                if (*pAttr->End() <= pRLEnd->nContent.GetIndex())
                {
                    continue;
                }
                switch (pAttr->Which())
                {
                    case RES_TXTATR_REFMARK:
                    case RES_TXTATR_TOXMARK:
                    case RES_TXTATR_META: // actually these 2 aren't allowed to overlap ???
                    case RES_TXTATR_METAFIELD:
                    case RES_TXTATR_INETFMT:
                    case RES_TXTATR_CJK_RUBY:
                    case RES_TXTATR_INPUTFIELD:
                        {
                            return false;
                        }
                        break;
                    case RES_TXTATR_AUTOFMT:
                    case RES_TXTATR_CHARFMT:
                        {
                            // char formats must be *nominally* the same
                            if (pAttr->Which() == RES_TXTATR_CHARFMT)
                            {
                                bool isFound(false);
                                for (auto iter = activeCharFmts.begin(); iter != activeCharFmts.end(); ++iter)
                                {
                                    if (**iter == *pAttr)
                                    {
                                        activeCharFmts.erase(iter);
                                        isFound = true;
                                        break;
                                    }
                                }
                                if (!isFound)
                                {
                                    return false;
                                }
                            }
                            SfxItemSet const& rSet((pAttr->Which() == RES_TXTATR_CHARFMT)
                                ? static_cast<SfxItemSet const&>(pAttr->GetCharFormat().GetCharFormat()->GetAttrSet())
                                : *pAttr->GetAutoFormat().GetStyleHandle().get());
                            InsertCharAttrs(activeCharAttrsEnd, rSet);

                        }
                        break;
                    // SwTextNode::SetAttr puts it into AUTOFMT which is quite
                    // sensible so it doesn't actually exist as a hint
                    case RES_TXTATR_UNKNOWN_CONTAINER:
                    default: assert(false);
                }
            }
            if (pRLStart->nNode != pRLEnd->nNode)
            {
                // need to iterate the nEndIndex forward too so the loop in the
                // caller can look for the right ends in the next iteration
                for (nEndIndex = 0; nEndIndex < pEndHints->Count(); ++nEndIndex)
                {
                    SwTextAttr *const pAttr(pEndHints->GetSortedByEnd(nEndIndex));
                    if (!pAttr->End())
                        continue;
                    if (pRLEnd->nContent.GetIndex() < *pAttr->End())
                    {
                        break;
                    }
                }
            }
        }

        // if we didn't find a matching start for any end, then it really ends inside
        if (!activeCharFmts.empty())
        {
            return false;
        }
        for (size_t i = 0; i < SAL_N_ELEMENTS(activeCharAttrsStart); ++i)
        {
            // all of these are poolable
    //        assert(!activeCharAttrsStart[i] || activeCharAttrsStart[i]->GetItemPool()->IsItemPoolable(*activeCharAttrsStart[i]));
            if (activeCharAttrsStart[i] != activeCharAttrsEnd[i])
            {
                return false;
            }
        }
        rStartIndex = nStartIndex;
        rEndIndex = nEndIndex;
        return true;
    }

    static sal_Int32 GetNextAttrImpl(SwTextNode const*const pTextNode,
            size_t const nStartIndex, size_t const nEndIndex,
            sal_Int32 const nPosition)
    {
        // note: this used to be COMPLETE_STRING, but was set to Len() + 1 below,
        // which is rather silly, so set it to Len() instead
        sal_Int32 nNext = pTextNode->Len();
        if (SwpHints const*const pHints = pTextNode->GetpSwpHints())
        {
            // are there attribute starts left?
            for (size_t i = nStartIndex; i < pHints->Count(); ++i)
            {
                SwTextAttr *const pAttr(pHints->Get(i));
                if (!pAttr->IsFormatIgnoreStart())
                {
                    nNext = pAttr->GetStart();
                    break;
                }
            }
            // are there attribute ends left?
            for (size_t i = nEndIndex; i < pHints->Count(); ++i)
            {
                SwTextAttr *const pAttr(pHints->GetSortedByEnd(i));
                if (!pAttr->IsFormatIgnoreEnd())
                {
                    sal_Int32 const nNextEnd = *pAttr->GetAnyEnd();
                    nNext = std::min(nNext, nNextEnd); // pick nearest one
                    break;
                }
            }
        }
        // TODO: maybe use hints like FieldHints for this instead of looking at the text...
        const sal_Int32 l = std::min(nNext, pTextNode->Len());
        sal_Int32 p = nPosition;
        const sal_Unicode* pStr = pTextNode->GetText().getStr();
        while (p < l)
        {
            sal_Unicode aChar = pStr[p];
            if (aChar < CH_TXT_ATR_FORMELEMENT
                || aChar > CH_TXT_ATR_FIELDEND)
            {
                ++p;
            }
            else
            {
                break;
            }
        }
        assert(p <= nNext);
        if (p < l)
        {
            // found a CH_TXT_ATR_FIELD*: if it's same as current position,
            // skip behind it so that both before- and after-positions are returned
            nNext = (nPosition < p) ? p : p + 1;
        }
        return nNext;
    }

    TextFrameIndex SwAttrIter::GetNextAttr() const
    {
        size_t nStartIndex(m_nStartIndex);
        size_t nEndIndex(m_nEndIndex);
        size_t nPosition(m_nPosition);
        SwTextNode const* pTextNode(m_pTextNode);
        SwRedlineTable::size_type nActRedline(m_pRedline ? m_pRedline->GetAct() : SwRedlineTable::npos);

        while (true)
        {
            sal_Int32 nNext = GetNextAttrImpl(pTextNode, nStartIndex, nEndIndex, nPosition);
            if( m_pRedline )
            {
                std::pair<sal_Int32, std::pair<SwRangeRedline const*, size_t>> const redline(
                        m_pRedline->GetNextRedln(nNext, pTextNode, nActRedline));
                if (redline.second.first)
                {
                    assert(m_pMergedPara);
                    if (CanSkipOverRedline(*redline.second.first, nStartIndex, nEndIndex))
                    {
                        nActRedline += redline.second.second;
                        if (&redline.second.first->End()->nNode.GetNode() != pTextNode)
                        {
                            pTextNode = redline.second.first->End()->nNode.GetNode().GetTextNode();
                            nPosition = redline.second.first->End()->nContent.GetIndex();
                        }
                        else
                        {
                            nPosition = redline.second.first->End()->nContent.GetIndex();
                        }
                    }
                    else
                    {
                        return sw::MapModelToView(*m_pMergedPara, pTextNode, redline.first);
                    }
                }
                else
                {
                    return m_pMergedPara
                        ? sw::MapModelToView(*m_pMergedPara, pTextNode, redline.first)
                        : TextFrameIndex(redline.first);
                }
            }
            else
            {
                return TextFrameIndex(nNext);
            }
        }
    }

    class SwMinMaxArgs
    {
    public:
        VclPtr<OutputDevice> pOut;
        SwViewShell const * pSh;
        sal_uLong &rMin;
        sal_uLong &rAbsMin;
        long nRowWidth;
        long nWordWidth;
        long nWordAdd;
        sal_Int32 nNoLineBreak;
        SwMinMaxArgs( OutputDevice* pOutI, SwViewShell const * pShI, sal_uLong& rMinI, sal_uLong &rAbsI )
            : pOut( pOutI ), pSh( pShI ), rMin( rMinI ), rAbsMin( rAbsI )
            { nRowWidth = nWordWidth = nWordAdd = 0; nNoLineBreak = COMPLETE_STRING; }
        void Minimum( long nNew ) const { if( static_cast<long>(rMin) < nNew ) rMin = nNew; }
        void NewWord() { nWordAdd = nWordWidth = 0; }
    };

    static bool lcl_MinMaxString( SwMinMaxArgs& rArg, SwFont* pFnt, const OUString &rText,
        sal_Int32 nIdx, sal_Int32 nEnd )
    {
        bool bRet = false;
        while( nIdx < nEnd )
        {
            sal_Int32 nStop = nIdx;
            LanguageType eLang = pFnt->GetLanguage();
            assert(g_pBreakIt && g_pBreakIt->GetBreakIter().is());

            bool bClear = CH_BLANK == rText[ nStop ];
            Boundary aBndry( g_pBreakIt->GetBreakIter()->getWordBoundary( rText, nIdx,
                             g_pBreakIt->GetLocale( eLang ),
                             WordType::DICTIONARY_WORD, true ) );
            nStop = aBndry.endPos;
            if( nIdx <= aBndry.startPos && nIdx && nIdx-1 != rArg.nNoLineBreak )
                rArg.NewWord();
            if( nStop == nIdx )
                ++nStop;
            if( nStop > nEnd )
                nStop = nEnd;

            SwDrawTextInfo aDrawInf(rArg.pSh, *rArg.pOut, rText, nIdx, nStop - nIdx);
            long nCurrentWidth = pFnt->GetTextSize_( aDrawInf ).Width();
            rArg.nRowWidth += nCurrentWidth;
            if( bClear )
                rArg.NewWord();
            else
            {
                rArg.nWordWidth += nCurrentWidth;
                if( static_cast<long>(rArg.rAbsMin) < rArg.nWordWidth )
                    rArg.rAbsMin = rArg.nWordWidth;
                rArg.Minimum( rArg.nWordWidth + rArg.nWordAdd );
                bRet = true;
            }
            nIdx = nStop;
        }
        return bRet;
    }

    bool SwTextNode::IsSymbolAt(const sal_Int32 nBegin) const
    {
        SwScriptInfo aScriptInfo;
        SwAttrIter aIter( *const_cast<SwTextNode*>(this), aScriptInfo );
        aIter.Seek( nBegin );
        return aIter.GetFnt()->IsSymbol( getIDocumentLayoutAccess().GetCurrentViewShell() );
    }

    class SwMinMaxNodeArgs
    {
    public:
        sal_uLong nMaxWidth;    // sum of all frame widths
        long nMinWidth;         // biggest frame
        long nLeftRest;         // space not already covered by frames in the left margin
        long nRightRest;        // space not already covered by frames in the right margin
        long nLeftDiff;         // Min/Max-difference of the frame in the left margin
        long nRightDiff;        // Min/Max-difference of the frame in the right margin
        sal_uLong nIndx;        // index of the node
        void Minimum( long nNew ) { if( nNew > nMinWidth ) nMinWidth = nNew; }
    };

    static void lcl_MinMaxNode( SwFrameFormat* pNd, SwMinMaxNodeArgs* pIn )
    {
        const SwFormatAnchor& rFormatA = pNd->GetAnchor();

        if ((RndStdIds::FLY_AT_PARA != rFormatA.GetAnchorId()) &&
            (RndStdIds::FLY_AT_CHAR != rFormatA.GetAnchorId()))
        {
            return;
        }

        const SwPosition *pPos = rFormatA.GetContentAnchor();
        OSL_ENSURE(pPos && pIn, "Unexpected NULL arguments");
        if (!pPos || !pIn || pIn->nIndx != pPos->nNode.GetIndex())
            return;

        long nMin, nMax;
        SwHTMLTableLayout *pLayout = nullptr;
        const bool bIsDrawFrameFormat = pNd->Which()==RES_DRAWFRMFMT;
        if( !bIsDrawFrameFormat )
        {
            // Does the frame contain a table at the start or the end?
            const SwNodes& rNodes = pNd->GetDoc()->GetNodes();
            const SwFormatContent& rFlyContent = pNd->GetContent();
            sal_uLong nStt = rFlyContent.GetContentIdx()->GetIndex();
            SwTableNode* pTableNd = rNodes[nStt+1]->GetTableNode();
            if( !pTableNd )
            {
                SwNode *pNd2 = rNodes[nStt];
                pNd2 = rNodes[pNd2->EndOfSectionIndex()-1];
                if( pNd2->IsEndNode() )
                    pTableNd = pNd2->StartOfSectionNode()->GetTableNode();
            }

            if( pTableNd )
                pLayout = pTableNd->GetTable().GetHTMLTableLayout();
        }

        const SwFormatHoriOrient& rOrient = pNd->GetHoriOrient();
        sal_Int16 eHoriOri = rOrient.GetHoriOrient();

        long nDiff;
        if( pLayout )
        {
            nMin = pLayout->GetMin();
            nMax = pLayout->GetMax();
            nDiff = nMax - nMin;
        }
        else
        {
            if( bIsDrawFrameFormat )
            {
                const SdrObject* pSObj = pNd->FindSdrObject();
                if( pSObj )
                    nMin = pSObj->GetCurrentBoundRect().GetWidth();
                else
                    nMin = 0;

            }
            else
            {
                const SwFormatFrameSize &rSz = pNd->GetFrameSize();
                nMin = rSz.GetWidth();
            }
            nMax = nMin;
            nDiff = 0;
        }

        const SvxLRSpaceItem &rLR = pNd->GetLRSpace();
        nMin += rLR.GetLeft();
        nMin += rLR.GetRight();
        nMax += rLR.GetLeft();
        nMax += rLR.GetRight();

        if( css::text::WrapTextMode_THROUGH == pNd->GetSurround().GetSurround() )
        {
            pIn->Minimum( nMin );
            return;
        }

        // Frames, which are left- or right-aligned are only party considered
        // when calculating the maximum, since the border is already being considered.
        // Only if the frame extends into the text body, this part is being added
        switch( eHoriOri )
        {
            case text::HoriOrientation::RIGHT:
            {
                if( nDiff )
                {
                    pIn->nRightRest -= pIn->nRightDiff;
                    pIn->nRightDiff = nDiff;
                }
                if( text::RelOrientation::FRAME != rOrient.GetRelationOrient() )
                {
                    if( pIn->nRightRest > 0 )
                        pIn->nRightRest = 0;
                }
                pIn->nRightRest -= nMin;
                break;
            }
            case text::HoriOrientation::LEFT:
            {
                if( nDiff )
                {
                    pIn->nLeftRest -= pIn->nLeftDiff;
                    pIn->nLeftDiff = nDiff;
                }
                if( text::RelOrientation::FRAME != rOrient.GetRelationOrient() &&
                    pIn->nLeftRest < 0 )
                    pIn->nLeftRest = 0;
                pIn->nLeftRest -= nMin;
                break;
            }
            default:
            {
                pIn->nMaxWidth += nMax;
                pIn->Minimum( nMin );
            }
        }
    }

    #define FLYINCNT_MIN_WIDTH 284

    /**
     * Changing this method very likely requires changing of GetScalingOfSelectedText
     */
    void SwTextNode::GetMinMaxSize( sal_uLong nIndex, sal_uLong& rMin, sal_uLong &rMax,
                                   sal_uLong& rAbsMin ) const
    {
        SwViewShell const * pSh = GetDoc()->getIDocumentLayoutAccess().GetCurrentViewShell();
        OutputDevice* pOut = nullptr;
        if( pSh )
            pOut = pSh->GetWin();
        if( !pOut )
            pOut = Application::GetDefaultDevice();

        MapMode aOldMap( pOut->GetMapMode() );
        pOut->SetMapMode( MapMode( MapUnit::MapTwip ) );

        rMin = 0;
        rMax = 0;
        rAbsMin = 0;

        const SvxLRSpaceItem &rSpace = GetSwAttrSet().GetLRSpace();
        long nLROffset = rSpace.GetTextLeft() + GetLeftMarginWithNum( true );
        short nFLOffs;
        // For enumerations a negative first line indentation is probably filled already
        if( !GetFirstLineOfsWithNum( nFLOffs ) || nFLOffs > nLROffset )
            nLROffset = nFLOffs;

        SwMinMaxNodeArgs aNodeArgs;
        aNodeArgs.nMinWidth = 0;
        aNodeArgs.nMaxWidth = 0;
        aNodeArgs.nLeftRest = nLROffset;
        aNodeArgs.nRightRest = rSpace.GetRight();
        aNodeArgs.nLeftDiff = 0;
        aNodeArgs.nRightDiff = 0;
        if( nIndex )
        {
            SwFrameFormats* pTmp = const_cast<SwFrameFormats*>(GetDoc()->GetSpzFrameFormats());
            if( pTmp )
            {
                aNodeArgs.nIndx = nIndex;
                for( SwFrameFormat *pFormat : *pTmp )
                    lcl_MinMaxNode( pFormat, &aNodeArgs );
            }
        }
        if( aNodeArgs.nLeftRest < 0 )
            aNodeArgs.Minimum( nLROffset - aNodeArgs.nLeftRest );
        aNodeArgs.nLeftRest -= aNodeArgs.nLeftDiff;
        if( aNodeArgs.nLeftRest < 0 )
            aNodeArgs.nMaxWidth -= aNodeArgs.nLeftRest;

        if( aNodeArgs.nRightRest < 0 )
            aNodeArgs.Minimum( rSpace.GetRight() - aNodeArgs.nRightRest );
        aNodeArgs.nRightRest -= aNodeArgs.nRightDiff;
        if( aNodeArgs.nRightRest < 0 )
            aNodeArgs.nMaxWidth -= aNodeArgs.nRightRest;

        SwScriptInfo aScriptInfo;
        SwAttrIter aIter( *const_cast<SwTextNode*>(this), aScriptInfo );
        sal_Int32 nIdx = 0;
        aIter.SeekAndChgAttrIter( nIdx, pOut );
        sal_Int32 nLen = m_Text.getLength();
        long nCurrentWidth = 0;
        long nAdd = 0;
        SwMinMaxArgs aArg( pOut, pSh, rMin, rAbsMin );
        while( nIdx < nLen )
        {
            sal_Int32 nNextChg = aIter.GetNextAttr();
            sal_Int32 nStop = aScriptInfo.NextScriptChg( nIdx );
            if( nNextChg > nStop )
                nNextChg = nStop;
            SwTextAttr *pHint = nullptr;
            sal_Unicode cChar = CH_BLANK;
            nStop = nIdx;
            while( nStop < nLen && nStop < nNextChg &&
                   CH_TAB != ( cChar = m_Text[nStop] ) &&
                   CH_BREAK != cChar && CHAR_HARDBLANK != cChar &&
                   CHAR_HARDHYPHEN != cChar && CHAR_SOFTHYPHEN != cChar &&
                   !pHint )
            {
                if( ( CH_TXTATR_BREAKWORD != cChar && CH_TXTATR_INWORD != cChar )
                    || ( nullptr == ( pHint = aIter.GetAttr( nStop ) ) ) )
                    ++nStop;
            }
            if ( lcl_MinMaxString( aArg, aIter.GetFnt(), m_Text, nIdx, nStop ) )
            {
                nAdd = 20;
            }
            nIdx = nStop;
            aIter.SeekAndChgAttrIter( nIdx, pOut );
            switch( cChar )
            {
                case CH_BREAK  :
                {
                    if( static_cast<long>(rMax) < aArg.nRowWidth )
                        rMax = aArg.nRowWidth;
                    aArg.nRowWidth = 0;
                    aArg.NewWord();
                    aIter.SeekAndChgAttrIter( ++nIdx, pOut );
                }
                break;
                case CH_TAB    :
                {
                    aArg.NewWord();
                    aIter.SeekAndChgAttrIter( ++nIdx, pOut );
                }
                break;
                case CHAR_SOFTHYPHEN:
                    ++nIdx;
                break;
                case CHAR_HARDBLANK:
                case CHAR_HARDHYPHEN:
                {
                    OUString sTmp( cChar );
                    SwDrawTextInfo aDrawInf( getIDocumentLayoutAccess().GetCurrentViewShell(),
                        *pOut, sTmp, 0, 1, 0, false );
                    nCurrentWidth = aIter.GetFnt()->GetTextSize_( aDrawInf ).Width();
                    aArg.nWordWidth += nCurrentWidth;
                    aArg.nRowWidth += nCurrentWidth;
                    if( static_cast<long>(rAbsMin) < aArg.nWordWidth )
                        rAbsMin = aArg.nWordWidth;
                    aArg.Minimum( aArg.nWordWidth + aArg.nWordAdd );
                    aArg.nNoLineBreak = nIdx++;
                }
                break;
                case CH_TXTATR_BREAKWORD:
                case CH_TXTATR_INWORD:
                {
                    if( !pHint )
                        break;
                    long nOldWidth = aArg.nWordWidth;
                    long nOldAdd = aArg.nWordAdd;
                    aArg.NewWord();

                    switch( pHint->Which() )
                    {
                        case RES_TXTATR_FLYCNT :
                        {
                            SwFrameFormat *pFrameFormat = pHint->GetFlyCnt().GetFrameFormat();
                            const SvxLRSpaceItem &rLR = pFrameFormat->GetLRSpace();
                            if( RES_DRAWFRMFMT == pFrameFormat->Which() )
                            {
                                const SdrObject* pSObj = pFrameFormat->FindSdrObject();
                                if( pSObj )
                                    nCurrentWidth = pSObj->GetCurrentBoundRect().GetWidth();
                                else
                                    nCurrentWidth = 0;
                            }
                            else
                            {
                                const SwFormatFrameSize& rTmpSize = pFrameFormat->GetFrameSize();
                                if( RES_FLYFRMFMT == pFrameFormat->Which()
                                    && rTmpSize.GetWidthPercent() )
                                {
                                    // This is a hack for the following situation: In the paragraph there's a
                                    // text frame with relative size. Then let's take 0.5 cm as minimum width
                                    // and USHRT_MAX as maximum width
                                    // It were cleaner and maybe necessary later on to iterate over the content
                                    // of the text frame and call GetMinMaxSize recursively
                                    nCurrentWidth = FLYINCNT_MIN_WIDTH; // 0.5 cm
                                    rMax = std::max(rMax, sal_uLong(USHRT_MAX));
                                }
                                else
                                    nCurrentWidth = pFrameFormat->GetFrameSize().GetWidth();
                            }
                            nCurrentWidth += rLR.GetLeft();
                            nCurrentWidth += rLR.GetRight();
                            aArg.nWordAdd = nOldWidth + nOldAdd;
                            aArg.nWordWidth = nCurrentWidth;
                            aArg.nRowWidth += nCurrentWidth;
                            if( static_cast<long>(rAbsMin) < aArg.nWordWidth )
                                rAbsMin = aArg.nWordWidth;
                            aArg.Minimum( aArg.nWordWidth + aArg.nWordAdd );
                            break;
                        }
                        case RES_TXTATR_FTN :
                        {
                            const OUString aText = pHint->GetFootnote().GetNumStr();
                            if( lcl_MinMaxString( aArg, aIter.GetFnt(), aText, 0,
                                aText.getLength() ) )
                                nAdd = 20;
                            break;
                        }

                        case RES_TXTATR_FIELD :
                        case RES_TXTATR_ANNOTATION :
                            {
                                SwField *pField = const_cast<SwField*>(pHint->GetFormatField().GetField());
                                const OUString aText = pField->ExpandField(true);
                                if( lcl_MinMaxString( aArg, aIter.GetFnt(), aText, 0,
                                    aText.getLength() ) )
                                    nAdd = 20;
                                break;
                            }
                        default: aArg.nWordWidth = nOldWidth;
                            aArg.nWordAdd = nOldAdd;

                    }
                    aIter.SeekAndChgAttrIter( ++nIdx, pOut );
                }
                break;
            }
        }
        if( static_cast<long>(rMax) < aArg.nRowWidth )
            rMax = aArg.nRowWidth;

        nLROffset += rSpace.GetRight();

        rAbsMin += nLROffset;
        rAbsMin += nAdd;
        rMin += nLROffset;
        rMin += nAdd;
        if( static_cast<long>(rMin) < aNodeArgs.nMinWidth )
            rMin = aNodeArgs.nMinWidth;
        if( static_cast<long>(rAbsMin) < aNodeArgs.nMinWidth )
            rAbsMin = aNodeArgs.nMinWidth;
        rMax += aNodeArgs.nMaxWidth;
        rMax += nLROffset;
        rMax += nAdd;
        if( rMax < rMin ) // e.g. Frames with flow through only contribute to the minimum
            rMax = rMin;
        pOut->SetMapMode( aOldMap );
    }

    /**
     * Calculates the width of the text part specified by nStt and nEnd,
     * the height of the line containing nStt is divided by this width,
     * indicating the scaling factor, if the text part is rotated.
     * Having CH_BREAKs in the text part, this method returns the scaling
     * factor for the longest of the text parts separated by the CH_BREAK
     *
     * Changing this method very likely requires changing of "GetMinMaxSize"
     */
    sal_uInt16 SwTextNode::GetScalingOfSelectedText( sal_Int32 nStt, sal_Int32 nEnd )
        const
    {
        SwViewShell const * pSh = GetDoc()->getIDocumentLayoutAccess().GetCurrentViewShell();
        OutputDevice* pOut = nullptr;

        if ( pSh )
            pOut = &pSh->GetRefDev();
        else
        {
            // Access via StarONE, there's no need for an existing or active shell
            if ( getIDocumentSettingAccess()->get(DocumentSettingId::HTML_MODE) )
                pOut = Application::GetDefaultDevice();
            else
                pOut = getIDocumentDeviceAccess().getReferenceDevice( true );
        }

        OSL_ENSURE( pOut, "GetScalingOfSelectedText without outdev" );

        MapMode aOldMap( pOut->GetMapMode() );
        pOut->SetMapMode( MapMode( MapUnit::MapTwip ) );

        if ( nStt == nEnd )
        {
            assert(g_pBreakIt && g_pBreakIt->GetBreakIter().is());

            SwScriptInfo aScriptInfo;
            SwAttrIter aIter( *const_cast<SwTextNode*>(this), aScriptInfo );
            aIter.SeekAndChgAttrIter( nStt, pOut );

            Boundary aBound =
                g_pBreakIt->GetBreakIter()->getWordBoundary( GetText(), nStt,
                g_pBreakIt->GetLocale( aIter.GetFnt()->GetLanguage() ),
                WordType::DICTIONARY_WORD, true );

            if ( nStt == aBound.startPos )
            {
                // cursor is at left or right border of word
                pOut->SetMapMode( aOldMap );
                return 100;
            }

            nStt = aBound.startPos;
            nEnd = aBound.endPos;

            if ( nStt == nEnd )
            {
                pOut->SetMapMode( aOldMap );
                return 100;
            }
        }

        SwScriptInfo aScriptInfo;
        SwAttrIter aIter( *const_cast<SwTextNode*>(this), aScriptInfo );

        // We do not want scaling attributes to be considered during this
        // calculation. For this, we push a temporary scaling attribute with
        // scaling value 100 and priority flag on top of the scaling stack
        SwAttrHandler& rAH = aIter.GetAttrHandler();
        SvxCharScaleWidthItem aItem(100, RES_CHRATR_SCALEW);
        SwTextAttrEnd aAttr( aItem, nStt, nEnd );
        aAttr.SetPriorityAttr( true );
        rAH.PushAndChg( aAttr, *(aIter.GetFnt()) );

        sal_Int32 nIdx = nStt;

        sal_uLong nWidth = 0;
        sal_uLong nProWidth = 0;

        while( nIdx < nEnd )
        {
            aIter.SeekAndChgAttrIter( nIdx, pOut );

            // scan for end of portion
            const sal_Int32 nNextChg = std::min(aIter.GetNextAttr(), aScriptInfo.NextScriptChg(nIdx));

            sal_Int32 nStop = nIdx;
            sal_Unicode cChar = CH_BLANK;
            SwTextAttr* pHint = nullptr;

            // stop at special characters in [ nIdx, nNextChg ]
            while( nStop < nEnd && nStop < nNextChg )
            {
                cChar = m_Text[nStop];
                if (
                    CH_TAB == cChar ||
                    CH_BREAK == cChar ||
                    CHAR_HARDBLANK == cChar ||
                    CHAR_HARDHYPHEN == cChar ||
                    CHAR_SOFTHYPHEN == cChar ||
                    (
                      (CH_TXTATR_BREAKWORD == cChar || CH_TXTATR_INWORD == cChar) &&
                      (nullptr == (pHint = aIter.GetAttr(nStop)))
                    )
                   )
                {
                    break;
                }
                else
                    ++nStop;
            }

            // calculate text widths up to cChar
            if ( nStop > nIdx )
            {
                SwDrawTextInfo aDrawInf(pSh, *pOut, GetText(), nIdx, nStop - nIdx);
                nProWidth += aIter.GetFnt()->GetTextSize_( aDrawInf ).Width();
            }

            nIdx = nStop;
            aIter.SeekAndChgAttrIter( nIdx, pOut );

            if ( cChar == CH_BREAK )
            {
                nWidth = std::max( nWidth, nProWidth );
                nProWidth = 0;
                nIdx++;
            }
            else if ( cChar == CH_TAB )
            {
                // tab receives width of one space
                OUString sTmp( CH_BLANK );
                SwDrawTextInfo aDrawInf( pSh, *pOut, sTmp, 0, 1 );
                nProWidth += aIter.GetFnt()->GetTextSize_( aDrawInf ).Width();
                nIdx++;
            }
            else if ( cChar == CHAR_SOFTHYPHEN )
                ++nIdx;
            else if ( cChar == CHAR_HARDBLANK || cChar == CHAR_HARDHYPHEN )
            {
                OUString sTmp( cChar );
                SwDrawTextInfo aDrawInf( pSh, *pOut, sTmp, 0, 1 );
                nProWidth += aIter.GetFnt()->GetTextSize_( aDrawInf ).Width();
                nIdx++;
            }
            else if ( pHint && ( cChar == CH_TXTATR_BREAKWORD || cChar == CH_TXTATR_INWORD ) )
            {
                switch( pHint->Which() )
                {
                case RES_TXTATR_FTN :
                    {
                        const OUString aText = pHint->GetFootnote().GetNumStr();
                        SwDrawTextInfo aDrawInf(pSh, *pOut, aText, 0, aText.getLength());

                        nProWidth += aIter.GetFnt()->GetTextSize_( aDrawInf ).Width();
                        break;
                    }

                case RES_TXTATR_FIELD :
                case RES_TXTATR_ANNOTATION :
                    {
                        SwField *pField = const_cast<SwField*>(pHint->GetFormatField().GetField());
                        OUString const aText = pField->ExpandField(true);
                        SwDrawTextInfo aDrawInf(pSh, *pOut, aText, 0, aText.getLength());

                        nProWidth += aIter.GetFnt()->GetTextSize_( aDrawInf ).Width();
                        break;
                    }

                default:
                    {
                        // any suggestions for a default action?
                    }
                } // end of switch
                nIdx++;
            } // end of while
        }

        nWidth = std::max( nWidth, nProWidth );

        // search for a text frame this node belongs to
        SwIterator<SwTextFrame, SwTextNode, sw::IteratorMode::UnwrapMulti> aFrameIter(*this);
        SwTextFrame* pFrame = nullptr;
        for( SwTextFrame* pTmpFrame = aFrameIter.First(); pTmpFrame; pTmpFrame = aFrameIter.Next() )
        {
                if ( pTmpFrame->GetOfst() <= nStt &&
                    ( !pTmpFrame->GetFollow() ||
                       pTmpFrame->GetFollow()->GetOfst() > nStt ) )
                {
                    pFrame = pTmpFrame;
                    break;
                }
            }

        // search for the line containing nStt
        if ( pFrame && pFrame->HasPara() )
        {
            SwTextInfo aInf( pFrame );
            SwTextIter aLine( pFrame, &aInf );
            aLine.CharToLine( nStt );
            pOut->SetMapMode( aOldMap );
            return static_cast<sal_uInt16>( nWidth ?
                ( ( 100 * aLine.GetCurr()->Height() ) / nWidth ) : 0 );
        }
        // no frame or no paragraph, we take the height of the character
        // at nStt as line height

        aIter.SeekAndChgAttrIter( nStt, pOut );
        pOut->SetMapMode( aOldMap );

        SwDrawTextInfo aDrawInf( pSh, *pOut, GetText(), nStt, 1 );
        return static_cast<sal_uInt16>( nWidth ? ((100 * aIter.GetFnt()->GetTextSize_( aDrawInf ).Height()) / nWidth ) : 0 );
    }

    SwTwips SwTextNode::GetWidthOfLeadingTabs() const
    {
        SwTwips nRet = 0;

        sal_Int32 nIdx = 0;

        while ( nIdx < GetText().getLength() )
        {
            const sal_Unicode cCh = GetText()[nIdx];
            if ( cCh!='\t' && cCh!=' ' )
            {
                break;
            }
            ++nIdx;
        }

        if ( nIdx > 0 )
        {
            SwPosition aPos( *this );
            aPos.nContent += nIdx;

            // Find the non-follow text frame:
            SwIterator<SwTextFrame, SwTextNode, sw::IteratorMode::UnwrapMulti> aIter(*this);
            for( SwTextFrame* pFrame = aIter.First(); pFrame; pFrame = aIter.Next() )
            {
                // Only consider master frames:
                if (!pFrame->IsFollow() &&
                    // sw_redlinehide: paraPropsNode has the first text of the frame
                    (!pFrame->GetMergedPara() || pFrame->GetMergedPara()->pParaPropsNode == this))
                {
                    SwRectFnSet aRectFnSet(pFrame);
                    SwRect aRect;
                    pFrame->GetCharRect( aRect, aPos );
                    nRet = pFrame->IsRightToLeft() ?
                                aRectFnSet.GetPrtRight(*pFrame) - aRectFnSet.GetRight(aRect) :
                                aRectFnSet.GetLeft(aRect) - aRectFnSet.GetPrtLeft(*pFrame);
                    break;
                }
            }
        }

        return nRet;
    }

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

↑ V595 The 'm_pMergedPara' pointer was utilized before it was verified against nullptr. Check lines: 331, 342.

↑ V595 The 'pAttr->End()' pointer was utilized before it was verified against nullptr. Check lines: 469, 473.

↑ V730 Not all members of a class are initialized inside the constructor. Consider inspecting: m_aFontIdx.