/* -*- 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 <vcl/svapp.hxx>
#include <vcl/metaact.hxx>
#include <vcl/gdimtf.hxx>
#include <vcl/settings.hxx>
#include <vcl/window.hxx>
 
#include <editeng/outliner.hxx>
#include <editeng/tstpitem.hxx>
#include <editeng/lspcitem.hxx>
#include <editeng/flditem.hxx>
#include <editeng/forbiddenruleitem.hxx>
#include "impedit.hxx"
#include <editeng/editeng.hxx>
#include <editeng/editview.hxx>
#include <editeng/escapementitem.hxx>
#include <editeng/txtrange.hxx>
#include <editeng/udlnitem.hxx>
#include <editeng/fhgtitem.hxx>
#include <editeng/lrspitem.hxx>
#include <editeng/ulspitem.hxx>
#include <editeng/fontitem.hxx>
#include <editeng/wghtitem.hxx>
#include <editeng/postitem.hxx>
#include <editeng/langitem.hxx>
#include <editeng/frmdiritem.hxx>
#include <editeng/scriptspaceitem.hxx>
#include <editeng/charscaleitem.hxx>
#include <editeng/numitem.hxx>
#include <outleeng.hxx>
#include <TextPortion.hxx>
 
#include <svtools/colorcfg.hxx>
#include <svl/ctloptions.hxx>
#include <svl/asiancfg.hxx>
 
#include <svx/compatflags.hxx>
#include <sfx2/viewsh.hxx>
 
#include <editeng/hngpnctitem.hxx>
#include <editeng/forbiddencharacterstable.hxx>
 
#include <comphelper/configuration.hxx>
#include <comphelper/scopeguard.hxx>
 
#include <math.h>
#include <vcl/metric.hxx>
#include <com/sun/star/i18n/BreakIterator.hpp>
#include <com/sun/star/i18n/ScriptType.hpp>
#include <com/sun/star/i18n/InputSequenceChecker.hpp>
#include <vcl/pdfextoutdevdata.hxx>
#include <i18nlangtag/mslangid.hxx>
#include <i18nutil/kashida.hxx>
 
#include <comphelper/processfactory.hxx>
#include <comphelper/lok.hxx>
#include <rtl/ustrbuf.hxx>
#include <sal/log.hxx>
#include <o3tl/safeint.hxx>
#include <o3tl/sorted_vector.hxx>
#include <osl/diagnose.h>
#include <comphelper/string.hxx>
#include <cstddef>
#include <memory>
#include <set>
 
#include <vcl/outdev/ScopedStates.hxx>
 
#include <unicode/uchar.h>
 
using namespace ::com::sun::star;
using namespace ::com::sun::star::uno;
using namespace ::com::sun::star::beans;
using namespace ::com::sun::star::linguistic2;
 
constexpr OUString CH_HYPH = u"-"_ustr;
 
constexpr tools::Long WRONG_SHOW_MIN = 5;
 
#if (OSL_DEBUG_LEVEL > 1) || defined ( DBG_UTIL )
bool ImpEditEngine::bDebugPaint = false;
#endif
 
namespace {
 
struct TabInfo
{
    bool        bValid;
 
    SvxTabStop  aTabStop;
    sal_Int32   nTabPortion;
    tools::Long        nStartPosX;
    tools::Long        nTabPos;
 
    TabInfo()
        : bValid(false)
        , nTabPortion(0)
        , nStartPosX(0)
        , nTabPos(0)
        { }
 
};
 
}
 
AsianCompressionFlags GetCharTypeForCompression( sal_Unicode cChar )
{
    switch ( cChar )
    {
        case 0x3008: case 0x300A: case 0x300C: case 0x300E:
        case 0x3010: case 0x3014: case 0x3016: case 0x3018:
        case 0x301A: case 0x301D: case 0xFF09: case 0xFF3D:
        case 0xFF5D:
        {
            return AsianCompressionFlags::PunctuationRight;
        }
        case 0x3001: case 0x3002: case 0x3009: case 0x300B:
        case 0x300D: case 0x300F: case 0x3011: case 0x3015:
        case 0x3017: case 0x3019: case 0x301B: case 0x301E:
        case 0x301F: case 0xFF08: case 0xFF0C: case 0xFF0E:
        case 0xFF1A: case 0xFF1B: case 0xFF3B: case 0xFF5B:
        {
            return AsianCompressionFlags::PunctuationLeft;
        }
        default:
        {
            return ( ( 0x3040 <= cChar ) && ( 0x3100 > cChar ) ) ? AsianCompressionFlags::Kana : AsianCompressionFlags::Normal;
        }
    }
}
 
static void lcl_DrawRedLines( OutputDevice& rOutDev,
                              tools::Long nFontHeight,
                              const Point& rPoint,
                              size_t nIndex,
                              size_t nMaxEnd,
                              KernArraySpan pDXArray,
                              WrongList const * pWrongs,
                              Degree10 nOrientation,
                              const Point& rOrigin,
                              bool bVertical,
                              bool bIsRightToLeft )
{
    // But only if font is not too small...
    tools::Long nHeight = rOutDev.LogicToPixel(Size(0, nFontHeight)).Height();
    if (WRONG_SHOW_MIN >= nHeight)
        return;
 
    size_t nEnd, nStart = nIndex;
    bool bWrong = pWrongs->NextWrong(nStart, nEnd);
 
    while (bWrong)
    {
        if (nStart >= nMaxEnd)
            break;
 
        if (nStart < nIndex)  // Corrected
            nStart = nIndex;
 
        if (nEnd > nMaxEnd)
            nEnd = nMaxEnd;
 
        Point aPoint1(rPoint);
        if (bVertical)
        {
            // VCL doesn't know that the text is vertical, and is manipulating
            // the positions a little bit in y direction...
            tools::Long nOnePixel = rOutDev.PixelToLogic(Size(0, 1)).Height();
            tools::Long nCorrect = 2 * nOnePixel;
            aPoint1.AdjustY(-nCorrect);
            aPoint1.AdjustX(-nCorrect);
        }
        if (nStart > nIndex)
        {
            if (!bVertical)
            {
                // since for RTL portions rPoint is on the visual right end of the portion
                // (i.e. at the start of the first RTL char) we need to subtract the offset
                // for RTL portions...
                aPoint1.AdjustX((bIsRightToLeft ? -1 : 1) * pDXArray[nStart - nIndex - 1]);
            }
            else
                aPoint1.AdjustY(pDXArray[nStart - nIndex - 1]);
        }
        Point aPoint2(rPoint);
        assert(nEnd > nIndex && "RedLine: aPnt2?");
        if (!bVertical)
        {
            // since for RTL portions rPoint is on the visual right end of the portion
            // (i.e. at the start of the first RTL char) we need to subtract the offset
            // for RTL portions...
            aPoint2.AdjustX((bIsRightToLeft ? -1 : 1) * pDXArray[nEnd - nIndex - 1]);
        }
        else
        {
            aPoint2.AdjustY(pDXArray[nEnd - nIndex - 1]);
        }
 
        if (nOrientation)
        {
            rOrigin.RotateAround(aPoint1, nOrientation);
            rOrigin.RotateAround(aPoint2, nOrientation);
        }
 
        {
            vcl::ScopedAntialiasing a(rOutDev, true);
            rOutDev.DrawWaveLine(aPoint1, aPoint2);
        }
 
        nStart = nEnd + 1;
        if (nEnd < nMaxEnd)
            bWrong = pWrongs->NextWrong(nStart, nEnd);
        else
            bWrong = false;
    }
}
 
void ImpEditEngine::UpdateViews( EditView* pCurView )
{
    if ( !IsUpdateLayout() || IsFormatting() || maInvalidRect.IsEmpty() )
        return;
 
    DBG_ASSERT( IsFormatted(), "UpdateViews: Doc not formatted!" );
 
    for (EditView* pView : maEditViews)
    {
        pView->HideCursor();
 
        tools::Rectangle aClipRect(maInvalidRect);
        tools::Rectangle aVisArea( pView->GetVisArea() );
        aClipRect.Intersection( aVisArea );
 
        if ( !aClipRect.IsEmpty() )
        {
            // convert to window coordinates...
            aClipRect = pView->getImpl().GetWindowPos( aClipRect );
 
            // moved to one executing method to allow finer control
            pView->InvalidateWindow(aClipRect);
 
            pView->InvalidateOtherViewWindows( aClipRect );
        }
    }
 
    if ( pCurView )
    {
        bool bGotoCursor = pCurView->getImpl().DoAutoScroll();
        pCurView->ShowCursor( bGotoCursor );
    }
 
    maInvalidRect = tools::Rectangle();
    CallStatusHdl();
}
 
IMPL_LINK_NOARG(ImpEditEngine, OnlineSpellHdl, Timer *, void)
{
    if ( !Application::AnyInput( VclInputFlags::KEYBOARD ) && IsUpdateLayout() && IsFormatted() )
        DoOnlineSpelling();
    else
        maOnlineSpellTimer.Start();
}
 
IMPL_LINK_NOARG(ImpEditEngine, IdleFormatHdl, Timer *, void)
{
    maIdleFormatter.ResetRestarts();
 
    // #i97146# check if that view is still available
    // else probably the idle format timer fired while we're already
    // downing
    EditView* pView = maIdleFormatter.GetView();
    for (EditView* aEditView : maEditViews)
    {
        if( aEditView == pView )
        {
            FormatAndLayout( pView );
            break;
        }
    }
}
 
void ImpEditEngine::CheckIdleFormatter()
{
    maIdleFormatter.ForceTimeout();
    // If not idle, but still not formatted:
    if ( !IsFormatted() )
        FormatDoc();
}
 
bool ImpEditEngine::IsPageOverflow( ) const
{
    return mbNeedsChainingHandling;
}
 
 
void ImpEditEngine::FormatFullDoc()
{
    GetParaPortions().MarkAllSelectionsInvalid(0);
    FormatDoc();
}
 
tools::Long ImpEditEngine::FormatParagraphs(o3tl::sorted_vector<sal_Int32>& aRepaintParagraphList)
{
    sal_Int32 nParaCount = GetParaPortions().Count();
    tools::Long nY = 0;
    bool bGrow = false;
 
    for (sal_Int32 nParagraph = 0; nParagraph < nParaCount; nParagraph++)
    {
        ParaPortion& rParaPortion = GetParaPortions().getRef(nParagraph);
        if (rParaPortion.MustRepaint() || (rParaPortion.IsInvalid() && rParaPortion.IsVisible()))
        {
            // No formatting should be necessary for MustRepaint()!
            if (CreateLines(nParagraph, nY))
            {
                if (!bGrow && GetTextRanger())
                {
                    // For a change in height all below must be reformatted...
                    for (sal_Int32 n = nParagraph + 1; n < nParaCount; n++)
                    {
                        ParaPortion& rParaPortionToInvalidate = GetParaPortions().getRef(n);
                        rParaPortionToInvalidate.MarkSelectionInvalid(0);
                        rParaPortionToInvalidate.GetLines().Reset();
                    }
                }
                bGrow = true;
                if (IsCallParaInsertedOrDeleted())
                {
                    GetEditEnginePtr()->ParagraphHeightChanged(nParagraph);
 
                    for (EditView* pView : maEditViews)
                    {
                        pView->getImpl().ScrollStateChange();
                    }
 
                }
                rParaPortion.SetMustRepaint(false);
            }
 
            aRepaintParagraphList.insert(nParagraph);
        }
        nY += rParaPortion.GetHeight();
    }
    return nY;
}
 
namespace
{
constexpr std::array<ScalingParameters, 13> constScaleLevels =
{
    ScalingParameters{ 1.000,  1.000,  1.0,  0.9 },
    ScalingParameters{ 0.925,  0.925,  1.0,  0.9 },
    ScalingParameters{ 0.925,  0.925,  1.0,  0.8 },
    ScalingParameters{ 0.850,  0.850,  1.0,  0.9 },
    ScalingParameters{ 0.850,  0.850,  1.0,  0.8 },
    ScalingParameters{ 0.775,  0.775,  1.0,  0.8 },
    ScalingParameters{ 0.700,  0.700,  1.0,  0.8 },
    ScalingParameters{ 0.625,  0.625,  1.0,  0.8 },
    ScalingParameters{ 0.550,  0.550,  1.0,  0.8 },
    ScalingParameters{ 0.475,  0.475,  1.0,  0.8 },
    ScalingParameters{ 0.400,  0.400,  1.0,  0.8 },
    ScalingParameters{ 0.325,  0.325,  1.0,  0.8 },
    ScalingParameters{ 0.250,  0.250,  1.0,  0.8 },
};
 
} // end anonymous ns
 
void ImpEditEngine::ScaleContentToFitWindow(o3tl::sorted_vector<sal_Int32>& aRepaintParagraphList)
{
    if (!maCustomScalingParameters.areValuesDefault())
        maScalingParameters = maCustomScalingParameters;
 
    tools::Long nHeight = FormatParagraphs(aRepaintParagraphList);
    bool bOverflow = nHeight > (maMaxAutoPaperSize.Height() * mnColumns);
 
    size_t nCurrentScaleLevel = 0;
    while (bOverflow && nCurrentScaleLevel < constScaleLevels.size())
    {
        // Clean-up and reset paragraphs
        aRepaintParagraphList.clear();
        for (auto& pParaPortionToInvalidate : GetParaPortions())
        {
            pParaPortionToInvalidate->GetLines().Reset();
            pParaPortionToInvalidate->MarkSelectionInvalid(0);
            pParaPortionToInvalidate->SetMustRepaint(true);
        }
 
        // Get new scaling parameters
        maScalingParameters = constScaleLevels[nCurrentScaleLevel];
 
        // Try again with different scaling factor
        nHeight = FormatParagraphs(aRepaintParagraphList);
        bOverflow = nHeight > (maMaxAutoPaperSize.Height() * mnColumns);
 
        // Increase scale level
        nCurrentScaleLevel++;
    }
}
 
void ImpEditEngine::EnsureDocumentFormatted()
{
    if (!IsFormatted())
        FormatDoc();
}
 
void ImpEditEngine::FormatDoc()
{
    if (!IsUpdateLayout() || IsFormatting())
        return;
 
    mbIsFormatting = true;
 
    // Then I can also start the spell-timer...
    if (GetStatus().DoOnlineSpelling())
        StartOnlineSpellTimer();
 
    // Reserve, as it should match the current number of paragraphs
    o3tl::sorted_vector<sal_Int32> aRepaintParagraphList;
    aRepaintParagraphList.reserve(GetParaPortions().Count());
 
    if (maStatus.DoStretch())
        ScaleContentToFitWindow(aRepaintParagraphList);
    else
        FormatParagraphs(aRepaintParagraphList);
 
    maInvalidRect = tools::Rectangle(); // make empty
 
    // One can also get into the formatting through UpdateMode ON=>OFF=>ON...
    // enable optimization first after Vobis delivery...
    {
        tools::Long nNewHeight = CalcTextHeight();
        tools::Long nDiff = nNewHeight - mnCurTextHeight;
        if ( nDiff )
        {
            maInvalidRect.Union(tools::Rectangle::Normalize(
                { 0, nNewHeight }, { getWidthDirectionAware(maPaperSize), mnCurTextHeight }));
            maStatus.GetStatusWord() |= !IsEffectivelyVertical() ? EditStatusFlags::TextHeightChanged : EditStatusFlags::TEXTWIDTHCHANGED;
        }
 
        mnCurTextHeight = nNewHeight;
 
        if ( maStatus.AutoPageSize() )
            CheckAutoPageSize();
        else if ( nDiff )
        {
            for (EditView* pView : maEditViews)
            {
                ImpEditView& rImpView = pView->getImpl();
                if (rImpView.DoAutoHeight())
                {
                    Size aSz(rImpView.GetOutputArea().GetWidth(), mnCurTextHeight);
                    if ( aSz.Height() > maMaxAutoPaperSize.Height() )
                        aSz.setHeight( maMaxAutoPaperSize.Height() );
                    else if ( aSz.Height() < maMinAutoPaperSize.Height() )
                        aSz.setHeight( maMinAutoPaperSize.Height() );
                    rImpView.ResetOutputArea( tools::Rectangle(rImpView.GetOutputArea().TopLeft(), aSz));
                }
            }
        }
 
        if (!aRepaintParagraphList.empty())
        {
            auto CombineRepaintParasAreas = [&](const LineAreaInfo& rInfo) {
                if (aRepaintParagraphList.count(rInfo.nPortion))
                    maInvalidRect.Union(rInfo.aArea);
                return CallbackResult::Continue;
            };
            IterateLineAreas(CombineRepaintParasAreas, IterFlag::inclILS);
        }
    }
 
    mbIsFormatting = false;
    mbFormatted = true;
 
    CallStatusHdl();    // If Modified...
}
 
void ImpEditEngine::CheckAutoPageSize()
{
    Size aPrevPaperSize( GetPaperSize() );
    if ( GetStatus().AutoPageWidth() )
        maPaperSize.setWidth( !IsEffectivelyVertical() ? CalcTextWidth( true ) : GetTextHeight() );
    if ( GetStatus().AutoPageHeight() )
        maPaperSize.setHeight( !IsEffectivelyVertical() ? GetTextHeight() : CalcTextWidth( true ) );
 
    SetValidPaperSize( maPaperSize );    // consider Min, Max
 
    if ( maPaperSize == aPrevPaperSize )
        return;
 
    if ( ( !IsEffectivelyVertical() && ( maPaperSize.Width() != aPrevPaperSize.Width() ) )
         || ( IsEffectivelyVertical() && ( maPaperSize.Height() != aPrevPaperSize.Height() ) ) )
    {
        // If ahead is centered / right or tabs...
        maStatus.GetStatusWord() |= !IsEffectivelyVertical() ? EditStatusFlags::TEXTWIDTHCHANGED : EditStatusFlags::TextHeightChanged;
        for ( sal_Int32 nPara = 0; nPara < GetParaPortions().Count(); nPara++ )
        {
            // Only paragraphs which are not aligned to the left need to be
            // reformatted, the height can not be changed here anymore.
            ParaPortion& rParaPortion = GetParaPortions().getRef(nPara);
            SvxAdjust eJustification = GetJustification( nPara );
            if ( eJustification != SvxAdjust::Left )
            {
                rParaPortion.MarkSelectionInvalid(0);
                CreateLines( nPara, 0 );  // 0: For AutoPageSize no TextRange!
            }
        }
    }
 
    Size aInvSize = maPaperSize;
    if ( maPaperSize.Width() < aPrevPaperSize.Width() )
        aInvSize.setWidth( aPrevPaperSize.Width() );
    if ( maPaperSize.Height() < aPrevPaperSize.Height() )
        aInvSize.setHeight( aPrevPaperSize.Height() );
 
    Size aSz( aInvSize );
    if ( IsEffectivelyVertical() )
    {
        aSz.setWidth( aInvSize.Height() );
        aSz.setHeight( aInvSize.Width() );
    }
    maInvalidRect = tools::Rectangle( Point(), aSz );
 
 
    for (EditView* pView : maEditViews)
    {
        pView->getImpl().RecalcOutputArea();
    }
}
 
void ImpEditEngine::CheckPageOverflow()
{
    SAL_INFO("editeng.chaining", "[CONTROL_STATUS] AutoPageSize is " << (( maStatus.GetControlWord() & EEControlBits::AUTOPAGESIZE ) ? "ON" : "OFF") );
 
    tools::Long nBoxHeight = GetMaxAutoPaperSize().Height();
    SAL_INFO("editeng.chaining", "[OVERFLOW-CHECK] Current MaxAutoPaperHeight is " << nBoxHeight);
 
    tools::Long nTxtHeight = CalcTextHeight();
    SAL_INFO("editeng.chaining", "[OVERFLOW-CHECK] Current Text Height is " << nTxtHeight);
 
    sal_uInt32 nParaCount = maParaPortionList.Count();
    bool bOnlyOneEmptyPara = false;
    if (nParaCount == 1)
    {
        const ParaPortion* pPPortion = GetParaPortions().SafeGetObject(0);
        bOnlyOneEmptyPara = pPPortion && pPPortion->GetLines().Count() == 1
                            && pPPortion->GetLines()[0].GetLen() == 0;
    }
 
    if (nTxtHeight > nBoxHeight && !bOnlyOneEmptyPara)
    {
        // which paragraph is the first to cause higher size of the box?
        ImplUpdateOverflowingParaNum( nBoxHeight); // XXX: currently only for horizontal text
        //maStatus.SetPageOverflow(true);
        mbNeedsChainingHandling = true;
    } else
    {
        // No overflow if within box boundaries
        //maStatus.SetPageOverflow(false);
        mbNeedsChainingHandling = false;
    }
 
}
 
static sal_Int32 ImplCalculateFontIndependentLineSpacing( const sal_Int32 nFontHeight )
{
    constexpr const double f120Percent = 12.0 / 10.0;
    return basegfx::fround(nFontHeight * f120Percent);  // + 20%
}
 
tools::Long ImpEditEngine::GetColumnWidth(const Size& rPaperSize) const
{
    assert(mnColumns >= 1);
    tools::Long nWidth = IsEffectivelyVertical() ? rPaperSize.Height() : rPaperSize.Width();
    return (nWidth - mnColumnSpacing * (mnColumns - 1)) / mnColumns;
}
 
bool ImpEditEngine::createLinesForEmptyParagraph(ParaPortion& rParaPortion)
{
    // fast special treatment...
    if (rParaPortion.GetTextPortions().Count())
        rParaPortion.GetTextPortions().Reset();
    if (rParaPortion.GetLines().Count())
        rParaPortion.GetLines().Reset();
 
    CreateAndInsertEmptyLine(rParaPortion);
    return FinishCreateLines(rParaPortion);
}
 
tools::Long ImpEditEngine::calculateMaxLineWidth(tools::Long nStartX, SvxLRSpaceItem const& rLRItem)
{
    const bool bAutoSize = IsEffectivelyVertical() ? maStatus.AutoPageHeight() : maStatus.AutoPageWidth();
    tools::Long nMaxLineWidth = GetColumnWidth(bAutoSize ? maMaxAutoPaperSize : maPaperSize);
 
    nMaxLineWidth -= scaleXSpacingValue(rLRItem.GetRight());
    nMaxLineWidth -= nStartX;
 
    // If PaperSize == long_max, one cannot take away any negative
    // first line indent. (Overflow)
    if (nMaxLineWidth < 0 && nStartX < 0)
        nMaxLineWidth = GetColumnWidth(maPaperSize) - scaleXSpacingValue(rLRItem.GetRight());
 
    // If still less than 0, it may be just the right edge.
    if (nMaxLineWidth <= 0)
        nMaxLineWidth = 1;
 
    return nMaxLineWidth;
}
 
bool ImpEditEngine::CreateLines( sal_Int32 nPara, sal_uInt32 nStartPosY )
{
    assert(GetParaPortions().exists(nPara) && "Portion paragraph index is not valid");
    ParaPortion& rParaPortion = GetParaPortions().getRef(nPara);
 
    // sal_Bool: Changes in the height of paragraph Yes / No - sal_True/sal_False
    assert(rParaPortion.GetNode() && "Portion without Node in CreateLines" );
    DBG_ASSERT( rParaPortion.IsVisible(), "Invisible paragraphs not formatted!" );
    DBG_ASSERT( rParaPortion.IsInvalid(), "CreateLines: Portion not invalid!" );
 
    bool bProcessingEmptyLine = rParaPortion.GetNode()->Len() == 0 ;
    bool bEmptyNodeWithPolygon = rParaPortion.GetNode()->Len() == 0 && GetTextRanger();
 
 
    // Fast special treatment for empty paragraphs...
    bool bEmptyParagraph = rParaPortion.GetNode()->Len() == 0 && !GetTextRanger();
    if (bEmptyParagraph)
        return createLinesForEmptyParagraph(rParaPortion);
 
    sal_Int64 nCurrentPosY = nStartPosY;
    // If we're allowed to skip parts outside and this cannot possibly fit in the given height,
    // bail out to avoid possibly formatting a lot of text that will not be used. For the first
    // paragraph still format at least a bit.
    if( mbSkipOutsideFormat && nPara != 0
        && !maStatus.AutoPageHeight() && maPaperSize.Height() < nCurrentPosY )
    {
        return false;
    }
 
    //If the paragraph SvxFrameDirection is Stacked, use STACKED
    const SvxFrameDirectionItem* pFrameDirItem = &GetParaAttrib(nPara, EE_PARA_WRITINGDIR);
    bool bStacked = pFrameDirItem->GetValue() == SvxFrameDirection::Stacked;
    if (bStacked)
        maStatus.TurnOnFlags(EEControlBits::STACKED);
    else
        maStatus.TurnOffFlags(EEControlBits::STACKED);
 
    // Initialization...
 
    if (rParaPortion.GetLines().Count() == 0)
    {
        rParaPortion.GetLines().Append(std::make_unique<EditLine>());
    }
 
    // Get Paragraph attributes...
 
    ContentNode* const pNode = rParaPortion.GetNode();
 
    bool bRightToLeftPara = IsRightToLeft( nPara );
 
    SvxAdjust eJustification = GetJustification( nPara );
    bool bHyphenatePara = pNode->GetContentAttribs().GetItem( EE_PARA_HYPHENATE ).GetValue();
    sal_Int32 nSpaceBefore      = 0;
    sal_Int32 nMinLabelWidth    = 0;
    sal_Int32 nSpaceBeforeAndMinLabelWidth = GetSpaceBeforeAndMinLabelWidth( pNode, &nSpaceBefore, &nMinLabelWidth );
    const SvxLRSpaceItem& rLRItem = GetLRSpaceItem( pNode );
    const SvxLineSpacingItem& rLSItem = pNode->GetContentAttribs().GetItem( EE_PARA_SBL );
    const bool bScriptSpace = pNode->GetContentAttribs().GetItem( EE_PARA_ASIANCJKSPACING ).GetValue();
 
    const short nInvalidDiff = rParaPortion.GetInvalidDiff();
    const sal_Int32 nInvalidStart = rParaPortion.GetInvalidPosStart();
    const sal_Int32 nInvalidEnd =  nInvalidStart + std::abs( nInvalidDiff );
 
    bool bQuickFormat = false;
 
    // Determine if quick format should be used
    if (!bEmptyNodeWithPolygon && !HasScriptType(nPara, i18n::ScriptType::COMPLEX))
    {
        if (rParaPortion.IsSimpleInvalid() &&
            rParaPortion.GetInvalidDiff() > 0 &&
            pNode->GetString().indexOf(CH_FEATURE, nInvalidStart) > nInvalidEnd)
        {
            bQuickFormat = true;
        }
        else if (rParaPortion.IsSimpleInvalid() && nInvalidDiff < 0)
        {
            // check if delete over the portion boundaries was done...
            sal_Int32 nStart = nInvalidStart;  // DOUBLE !!!!!!!!!!!!!!!
            sal_Int32 nEnd = nStart - nInvalidDiff;  // negative
            bQuickFormat = true;
            sal_Int32 nPos = 0;
            for (auto const& pTextPortion : rParaPortion.GetTextPortions())
            {
                // There must be no start / end in the deleted area.
                nPos = nPos + pTextPortion->GetLen();
                if (nPos > nStart && nPos < nEnd)
                {
                    bQuickFormat = false;
                    break;
                }
            }
        }
    }
 
    // Saving both layout mode and language (since I'm potentially changing both)
    GetRefDevice()->Push( vcl::PushFlags::TEXTLAYOUTMODE|vcl::PushFlags::TEXTLANGUAGE );
 
    ImplInitLayoutMode(*GetRefDevice(), nPara, -1);
 
    sal_Int32 nRealInvalidStart = nInvalidStart;
 
    if (bEmptyNodeWithPolygon)
    {
        TextPortion* pDummyPortion = new TextPortion( 0 );
        rParaPortion.GetTextPortions().Reset();
        rParaPortion.GetTextPortions().Append(pDummyPortion);
    }
    else if ( bQuickFormat )
    {
        // faster Method:
        RecalcTextPortion(rParaPortion, nInvalidStart, nInvalidDiff);
    }
    else    // nRealInvalidStart can be before InvalidStart, since Portions were deleted...
    {
        CreateTextPortions(rParaPortion, nRealInvalidStart);
    }
 
    // Search for line with InvalidPos, start one line before
    // Flag the line => do not remove it !
 
 
    sal_Int32 nLine = rParaPortion.GetLines().Count()-1;
    for ( sal_Int32 nL = 0; nL <= nLine; nL++ )
    {
        EditLine& rLine = rParaPortion.GetLines()[nL];
        if ( rLine.GetEnd() > nRealInvalidStart )  // not nInvalidStart!
        {
            nLine = nL;
            break;
        }
        rLine.SetValid();
    }
    // Begin one line before...
    // If it is typed at the end, the line in front cannot change.
    if (nLine && (!rParaPortion.IsSimpleInvalid() ||
                    (nInvalidEnd < pNode->Len()) ||
                    (nInvalidDiff <= 0)))
    {
        nLine--;
    }
 
    tools::Rectangle aBulletArea{Point(), Point()};
 
    if (!nLine)
    {
        aBulletArea = GetEditEnginePtr()->GetBulletArea(GetParaPortions().GetPos(&rParaPortion));
        if ( !aBulletArea.IsWidthEmpty() && aBulletArea.Right() > 0 )
            rParaPortion.SetBulletX(sal_Int32(scaleXSpacingValue(aBulletArea.Right())));
        else
            rParaPortion.SetBulletX( 0 ); // if Bullet is set incorrectly
    }
 
    // Reformat all lines from here...
 
    int nStartNextLineAfterMultiLineField = 0;
 
    sal_Int32 nDelFromLine = -1;
    bool bLineBreak = false;
 
    EditLine* pLine = &rParaPortion.GetLines()[nLine];
    sal_Int32 nIndex = pLine->GetStart();
    EditLine aSaveLine( *pLine );
 
    SvxFont aTmpFont( pNode->GetCharAttribs().GetDefFont() );
 
    KernArray aCharPositionArray;
 
    bool bSameLineAgain = false;    // For TextRanger, if the height changes.
    TabInfo aCurrentTab;
 
    bool bForceOneRun = bEmptyNodeWithPolygon;
    bool bCompressedChars = false;
 
    while ( ( nIndex < pNode->Len() ) || bForceOneRun )
    {
        assert(pLine);
 
        bForceOneRun = false;
        bool bFieldStartNextLine = false;
 
        bool bEOL = false;
        bool bEOC = false;
        sal_Int32 nPortionStart = 0;
        sal_Int32 nPortionEnd = 0;
 
        tools::Long nStartX = scaleXSpacingValue(rLRItem.GetTextLeft() + nSpaceBeforeAndMinLabelWidth);
        // Multiline hyperlink may need to know if the next line is bigger.
        tools::Long nStartXNextLine = nStartX;
        if ( nIndex == 0 )
        {
            tools::Long nFI = scaleXSpacingValue(rLRItem.GetTextFirstLineOffset());
            nStartX += nFI;
 
            if (!nLine && rParaPortion.GetBulletX() > nStartX)
            {
                nStartX = rParaPortion.GetBulletX();
            }
        }
 
        nStartX += nStartNextLineAfterMultiLineField;
        nStartNextLineAfterMultiLineField = 0;
        tools::Long nMaxLineWidth = calculateMaxLineWidth(nStartX, rLRItem);
 
        // Problem:
        // Since formatting starts a line _before_ the invalid position,
     // the positions unfortunately have to be redefined...
        // Solution:
        // The line before can only become longer, not smaller
        // =>...
        pLine->GetCharPosArray().clear();
 
        // tdf#162803: Stale kashida position data also needs to be cleared on each layout.
        pLine->GetKashidaArray().clear();
 
        sal_Int32 nTmpPos = nIndex;
        sal_Int32 nTmpPortion = pLine->GetStartPortion();
        tools::Long nTmpWidth = 0;
        tools::Long nXWidth = nMaxLineWidth;
 
        std::deque<tools::Long>* pTextRanges = nullptr;
        tools::Long nTextExtraYOffset = 0;
        tools::Long nTextXOffset = 0;
        tools::Long nTextLineHeight = 0;
        if ( GetTextRanger() )
        {
            GetTextRanger()->SetVertical( IsEffectivelyVertical() );
 
            tools::Long nTextY = nCurrentPosY + GetEditCursor(rParaPortion, *pLine, pLine->GetStart(), CursorFlags()).Top();
            if ( !bSameLineAgain )
            {
                SeekCursor( pNode, nTmpPos+1, aTmpFont );
                aTmpFont.SetPhysFont(*GetRefDevice());
                ImplInitDigitMode(*GetRefDevice(), aTmpFont.GetLanguage());
 
                if ( IsFixedCellHeight() )
                    nTextLineHeight = ImplCalculateFontIndependentLineSpacing( aTmpFont.GetFontHeight() );
                else
                    nTextLineHeight = aTmpFont.GetPhysTxtSize( GetRefDevice() ).Height();
                // Metrics can be greater
                FormatterFontMetric aTempFormatterMetrics;
                RecalcFormatterFontMetrics( aTempFormatterMetrics, aTmpFont );
                sal_uInt16 nLineHeight = aTempFormatterMetrics.GetHeight();
                if ( nLineHeight > nTextLineHeight )
                    nTextLineHeight = nLineHeight;
            }
            else
                nTextLineHeight = pLine->GetHeight();
 
            nXWidth = 0;
            while ( !nXWidth )
            {
                tools::Long nYOff = nTextY + nTextExtraYOffset;
                tools::Long nYDiff = nTextLineHeight;
                if ( IsEffectivelyVertical() )
                {
                    tools::Long nMaxPolygonX = GetTextRanger()->GetBoundRect().Right();
                    nYOff = nMaxPolygonX-nYOff;
                    nYDiff = -nTextLineHeight;
                }
                pTextRanges = GetTextRanger()->GetTextRanges( Range( nYOff, nYOff + nYDiff ) );
                assert( pTextRanges && "GetTextRanges?!" );
                tools::Long nMaxRangeWidth = 0;
                // Use the widest range...
                // The widest range could be a bit confusing, so normally it
                // is the first one. Best with gaps.
                assert(pTextRanges->size() % 2 == 0 && "textranges are always in pairs");
                if (!pTextRanges->empty())
                {
                    tools::Long nA = pTextRanges->at(0);
                    tools::Long nB = pTextRanges->at(1);
                    DBG_ASSERT( nA <= nB, "TextRange distorted?" );
                    tools::Long nW = nB - nA;
                    if ( nW > nMaxRangeWidth )
                    {
                        nMaxRangeWidth = nW;
                        nTextXOffset = nA;
                    }
                }
                nXWidth = nMaxRangeWidth;
                if ( nXWidth )
                    nMaxLineWidth = nXWidth - nStartX - scaleXSpacingValue(rLRItem.GetRight());
                else
                {
                    // Try further down in the polygon.
                    // Below the polygon use the Paper Width.
                    nTextExtraYOffset += std::max( static_cast<tools::Long>(nTextLineHeight / 10), tools::Long(1) );
                    if ( ( nTextY + nTextExtraYOffset  ) > GetTextRanger()->GetBoundRect().Bottom() )
                    {
                        nXWidth = getWidthDirectionAware(GetPaperSize());
                        if ( !nXWidth ) // AutoPaperSize
                            nXWidth = 0x7FFFFFFF;
                    }
                }
            }
        }
 
        // search for Portion that no longer fits in line...
        TextPortion* pPortion = nullptr;
        sal_Int32 nPortionLen = 0;
        bool bContinueLastPortion = false;
        bool bBrokenLine = false;
        bLineBreak = false;
        const EditCharAttrib* pNextFeature = pNode->GetCharAttribs().FindFeature( pLine->GetStart() );
        while ( ( nTmpWidth < nXWidth ) && !bEOL )
        {
            const sal_Int32 nTextPortions = rParaPortion.GetTextPortions().Count();
            assert(nTextPortions > 0);
            bContinueLastPortion = (nTmpPortion >= nTextPortions);
            if (bContinueLastPortion)
            {
                if (nTmpPos >= pNode->Len())
                    break;  // while
 
                // Continue with remainder. This only to have *some* valid
                // X-values and not endlessly create new lines until DOOM...
                // Happened in the scenario of tdf#104152 where inserting a
                // paragraph lead to a11y attempting to format the doc to
                // obtain content when notified.
                nTmpPortion = nTextPortions - 1;
                SAL_WARN("editeng","ImpEditEngine::CreateLines - continuation of a broken portion");
            }
 
            nPortionStart = nTmpPos;
            pPortion = &rParaPortion.GetTextPortions()[nTmpPortion];
            if ( !bContinueLastPortion && pPortion->GetKind() == PortionKind::HYPHENATOR )
            {
                // Throw away a Portion, if necessary correct the one before,
                // if the Hyph portion has swallowed a character...
                sal_Int32 nTmpLen = pPortion->GetLen();
                rParaPortion.GetTextPortions().Remove( nTmpPortion );
                if (nTmpPortion && nTmpLen)
                {
                    nTmpPortion--;
                    TextPortion& rPrev = rParaPortion.GetTextPortions()[nTmpPortion];
                    DBG_ASSERT( rPrev.GetKind() == PortionKind::TEXT, "Portion?!" );
                    nTmpWidth -= rPrev.GetSize().Width();
                    nTmpPos = nTmpPos - rPrev.GetLen();
                    rPrev.SetLen(rPrev.GetLen() + nTmpLen);
                    rPrev.setWidth(-1);
                }
 
                assert(nTmpPortion < rParaPortion.GetTextPortions().Count() && "No more Portions left!");
                pPortion = &rParaPortion.GetTextPortions()[nTmpPortion];
            }
 
            if (bContinueLastPortion)
            {
                // Note that this may point behind the portion and is only to
                // be used with the node's string offsets to generate X-values.
                nPortionLen = pNode->Len() - nPortionStart;
            }
            else
            {
                nPortionLen = pPortion->GetLen();
            }
 
            DBG_ASSERT( pPortion->GetKind() != PortionKind::HYPHENATOR, "CreateLines: Hyphenator-Portion!" );
            DBG_ASSERT( nPortionLen || bProcessingEmptyLine, "Empty Portion in CreateLines ?!" );
            if ( pNextFeature && ( pNextFeature->GetStart() == nTmpPos ) )
            {
                SAL_WARN_IF( bContinueLastPortion,
                        "editeng","ImpEditEngine::CreateLines - feature in continued portion will be wrong");
                sal_uInt16 nWhich = pNextFeature->GetItem()->Which();
                switch ( nWhich )
                {
                    case EE_FEATURE_TAB:
                    {
                        tools::Long nOldTmpWidth = nTmpWidth;
 
                        // Search for Tab-Pos...
                        tools::Long nCurPos = nTmpWidth + nStartX;
                        // consider scaling
                        double fFontScalingX = maScalingParameters.fFontX;
                        if (maStatus.DoStretch() && (fFontScalingX != 1.0))
                            nCurPos = basegfx::fround<tools::Long>(double(nCurPos) / std::max(fFontScalingX, 0.01));
 
                        short nAllSpaceBeforeText = short(rLRItem.GetTextLeft());
                        aCurrentTab.aTabStop = pNode->GetContentAttribs().FindTabStop( nCurPos - nAllSpaceBeforeText , maEditDoc.GetDefTab() );
                        aCurrentTab.nTabPos = tools::Long(aCurrentTab.aTabStop.GetTabPos() + nAllSpaceBeforeText);
                        aCurrentTab.bValid = false;
 
                        // Switch direction in R2L para...
                        if ( bRightToLeftPara )
                        {
                            if ( aCurrentTab.aTabStop.GetAdjustment() == SvxTabAdjust::Right )
                                aCurrentTab.aTabStop.GetAdjustment() = SvxTabAdjust::Left;
                            else if ( aCurrentTab.aTabStop.GetAdjustment() == SvxTabAdjust::Left )
                                aCurrentTab.aTabStop.GetAdjustment() = SvxTabAdjust::Right;
                        }
 
                        if ( ( aCurrentTab.aTabStop.GetAdjustment() == SvxTabAdjust::Right ) ||
                             ( aCurrentTab.aTabStop.GetAdjustment() == SvxTabAdjust::Center ) ||
                             ( aCurrentTab.aTabStop.GetAdjustment() == SvxTabAdjust::Decimal ) )
                        {
                            // For LEFT / DEFAULT this tab is not considered.
                            aCurrentTab.bValid = true;
                            aCurrentTab.nStartPosX = nTmpWidth;
                            aCurrentTab.nTabPortion = nTmpPortion;
                        }
 
                        if (nMaxLineWidth < aCurrentTab.nTabPos && nTmpWidth != nMaxLineWidth - 1)
                            aCurrentTab.nTabPos = nMaxLineWidth - 1;
 
                        pPortion->SetKind(PortionKind::TAB);
                        pPortion->SetExtraValue( aCurrentTab.aTabStop.GetFill() );
                        pPortion->setWidth( aCurrentTab.nTabPos - (nTmpWidth+nStartX) );
 
                        // Height needed...
                        SeekCursor( pNode, nTmpPos+1, aTmpFont );
                        pPortion->setHeight( GetRefDevice()->GetTextHeight() );
 
                        DBG_ASSERT( pPortion->GetSize().Width() >= 0, "Tab incorrectly calculated!" );
 
                        nTmpWidth = aCurrentTab.nTabPos-nStartX;
 
                        // If this is the first token on the line,
                        // and nTmpWidth > maPaperSize.Width, => infinite loop!
                        if ( ( nTmpWidth >= nXWidth ) && ( nTmpPortion == pLine->GetStartPortion() ) )
                        {
                            // What now?
                            // make the tab fitting
                            pPortion->setWidth( nXWidth-nOldTmpWidth );
                            nTmpWidth = nXWidth-1;
                            bEOL = true;
                            bBrokenLine = true;
                        }
                        KernArray& rArray = pLine->GetCharPosArray();
                        size_t nPos = nTmpPos - pLine->GetStart();
                        rArray.insert(rArray.begin()+nPos, pPortion->GetSize().Width());
                        bCompressedChars = false;
                    }
                    break;
                    case EE_FEATURE_LINEBR:
                    {
                        assert( pPortion );
                        pPortion->setWidth(0);
                        bEOL = true;
                        bLineBreak = true;
                        pPortion->SetKind( PortionKind::LINEBREAK );
                        bCompressedChars = false;
                        KernArray& rArray = pLine->GetCharPosArray();
                        size_t nPos = nTmpPos - pLine->GetStart();
                        rArray.insert(rArray.begin()+nPos, pPortion->GetSize().Width());
                    }
                    break;
                    case EE_FEATURE_FIELD:
                    {
                        SeekCursor( pNode, nTmpPos+1, aTmpFont );
                        aTmpFont.SetPhysFont(*GetRefDevice());
                        ImplInitDigitMode(*GetRefDevice(), aTmpFont.GetLanguage());
 
                        OUString aFieldValue = static_cast<const EditCharAttribField*>(pNextFeature)->GetFieldValue();
                        // get size, but also DXArray to allow length information in line breaking below
                        KernArray aTmpDXArray;
                        pPortion->SetSize(aTmpFont.QuickGetTextSize(GetRefDevice(),
                            aFieldValue, 0, aFieldValue.getLength(), &aTmpDXArray));
 
                        // So no scrolling for oversized fields
                        if (pPortion->GetSize().Width() > nXWidth - nTmpWidth)
                        {
                            // create ExtraPortionInfo on-demand, flush lineBreaksList
                            ExtraPortionInfo *pExtraInfo = pPortion->GetExtraInfos();
 
                            if(nullptr == pExtraInfo)
                            {
                                pExtraInfo = new ExtraPortionInfo();
                                pExtraInfo->nOrgWidth = nXWidth;
                                pPortion->SetExtraInfos(pExtraInfo);
                            }
                            else
                            {
                                pExtraInfo->lineBreaksList.clear();
                            }
 
                            // iterate over CellBreaks using XBreakIterator to be on the
                            // safe side with international texts/charSets
                            Reference < i18n::XBreakIterator > xBreakIterator(ImplGetBreakIterator());
                            const sal_Int32 nTextLength(aFieldValue.getLength());
                            const lang::Locale aLocale(GetLocale(EditPaM(pNode, nPortionStart)));
                            sal_Int32 nDone(0);
                            sal_Int32 nNextCellBreak(
                                xBreakIterator->nextCharacters(
                                        aFieldValue,
                                        0,
                                        aLocale,
                                        css::i18n::CharacterIteratorMode::SKIPCELL,
                                        0,
                                        nDone));
                            sal_Int32 nLastCellBreak(0);
                            sal_Int32 nLineStartX(0);
                            nLineStartX = -nTmpWidth;
 
                            // always add 1st line break (safe, we already know we are larger than nXWidth)
                            pExtraInfo->lineBreaksList.push_back(0);
 
                            for(sal_Int32 a(0); a < nTextLength; a++)
                            {
                                if(a == nNextCellBreak)
                                {
                                    // check width
                                    if(aTmpDXArray[a] - nLineStartX > nXWidth)
                                    {
                                        // new CellBreak does not fit in current line, need to
                                        // create a break at LastCellBreak - but do not add 1st
                                        // line break twice for very tall frames
                                        if(0 != a)
                                        {
                                            pExtraInfo->lineBreaksList.push_back(a);
                                            // the following lines may be different sized
                                            if (nStartX > nStartXNextLine)
                                            {
                                                nXWidth += nStartX - nStartXNextLine;
                                                pLine->SetNextLinePosXDiff(nStartX
                                                                           - nStartXNextLine);
                                                nStartXNextLine = nStartX;
                                            }
                                        }
                                        else
                                        {
                                            //even the 1. char does not fit..
                                            //this means the field should start on next line
                                            //except if the actual line is a full line already
                                            if (nLineStartX < 0 || nStartX > nStartXNextLine)
                                                bFieldStartNextLine = true;
                                        }
 
                                        // moveLineStart forward in X
                                        nLineStartX = aTmpDXArray[nLastCellBreak];
                                    }
 
                                    // update CellBreak iteration values
                                    nLastCellBreak = a;
                                    nNextCellBreak = xBreakIterator->nextCharacters(
                                        aFieldValue,
                                        a,
                                        aLocale,
                                        css::i18n::CharacterIteratorMode::SKIPCELL,
                                        1,
                                        nDone);
                                }
                            }
                            //next Line should start here... after this field end
                            if (!bFieldStartNextLine)
                                nStartNextLineAfterMultiLineField
                                    = aTmpDXArray[nTextLength - 1] - nLineStartX;
                            else if (pExtraInfo)
                            {
                                // if the 1. character does not fit,
                                // but there is pExtraInfo, then delete it
                                pPortion->SetExtraInfos(nullptr);
                                pExtraInfo = nullptr;
                            }
                        }
                        nTmpWidth += pPortion->GetSize().Width();
                        KernArray& rArray = pLine->GetCharPosArray();
                        size_t nPos = nTmpPos - pLine->GetStart();
                        rArray.insert(rArray.begin()+nPos, pPortion->GetSize().Width());
                        pPortion->SetKind(PortionKind::FIELD);
                        // If this is the first token on the line,
                        // and nTmpWidth > maPaperSize.Width, => infinite loop!
                        if ( ( nTmpWidth >= nXWidth ) && ( nTmpPortion == pLine->GetStartPortion() ) )
                        {
                            nTmpWidth = nXWidth-1;
                            bEOL = true;
                            bBrokenLine = true;
                        }
                        // Compression in Fields????
                        // I think this could be a little bit difficult and is not very useful
                        bCompressedChars = false;
                    }
                    break;
                    default:    OSL_FAIL( "What feature?" );
                }
                pNextFeature = pNode->GetCharAttribs().FindFeature( pNextFeature->GetStart() + 1  );
            }
            else
            {
                DBG_ASSERT( nPortionLen || bProcessingEmptyLine, "Empty Portion - Extra Space?!" );
                SeekCursor( pNode, nTmpPos+1, aTmpFont );
                aTmpFont.SetPhysFont(*GetRefDevice());
                ImplInitDigitMode(*GetRefDevice(), aTmpFont.GetLanguage());
 
                if (!bContinueLastPortion)
                    pPortion->SetRightToLeftLevel( GetRightToLeft( nPara, nTmpPos+1 ) );
 
                if (bContinueLastPortion)
                {
                     Size aSize = aTmpFont.QuickGetTextSize( GetRefDevice(), rParaPortion.GetNode()->GetString(), nTmpPos, nPortionLen, &aCharPositionArray, bStacked);
                     pPortion->adjustSize(aSize.Width(), 0);
                     if (pPortion->GetSize().Height() < aSize.Height())
                         pPortion->setHeight(aSize.Height());
                }
                else
                {
                    Size aSize = aTmpFont.QuickGetTextSize(GetRefDevice(), rParaPortion.GetNode()->GetString(), nTmpPos, nPortionLen, &aCharPositionArray, bStacked);
                    pPortion->SetSize(aSize);
                }
 
                // #i9050# Do Kerning also behind portions...
                if ( ( aTmpFont.GetFixKerning() > 0 ) && ( ( nTmpPos + nPortionLen ) < pNode->Len() ) )
                    pPortion->adjustSize(aTmpFont.GetFixKerning(), 0);
                if ( IsFixedCellHeight() )
                {
                    pPortion->setHeight( ImplCalculateFontIndependentLineSpacing( aTmpFont.GetFontHeight() ) );
                }
                // The array is  generally flattened at the beginning
                // => Always simply quick inserts.
                size_t nPos = nTmpPos - pLine->GetStart();
                KernArray& rArray = pLine->GetCharPosArray();
                rArray.insert( rArray.begin() + nPos, aCharPositionArray.data(), aCharPositionArray.data() + nPortionLen);
 
                // And now check for Compression:
                if ( !bContinueLastPortion && nPortionLen && GetAsianCompressionMode() != CharCompressType::NONE )
                {
                    double* pDXArray = rArray.data() + nTmpPos - pLine->GetStart();
                    bCompressedChars |= ImplCalcAsianCompression(
                        pNode, pPortion, nTmpPos, pDXArray, 10000, false);
                }
 
                nTmpWidth += pPortion->GetSize().Width();
 
                sal_Int32 _nPortionEnd = nTmpPos + nPortionLen;
                if( bScriptSpace && ( _nPortionEnd < pNode->Len() ) && ( nTmpWidth < nXWidth ) && IsScriptChange( EditPaM( pNode, _nPortionEnd ) ) )
                {
                    bool bAllow = false;
                    sal_uInt16 nScriptTypeLeft = GetI18NScriptType( EditPaM( pNode, _nPortionEnd ) );
                    sal_uInt16 nScriptTypeRight = GetI18NScriptType( EditPaM( pNode, _nPortionEnd+1 ) );
                    if ( ( nScriptTypeLeft == i18n::ScriptType::ASIAN ) || ( nScriptTypeRight == i18n::ScriptType::ASIAN ) )
                        bAllow = true;
 
                    // No spacing within L2R/R2L nesting
                    if ( bAllow )
                    {
                        tools::Long nExtraSpace = pPortion->GetSize().Height() / 5;
                        nExtraSpace = scaleXSpacingValue(nExtraSpace);
                        pPortion->adjustSize(nExtraSpace, 0);
                        nTmpWidth += nExtraSpace;
                    }
                }
            }
 
            if ( aCurrentTab.bValid && ( nTmpPortion != aCurrentTab.nTabPortion ) )
            {
                tools::Long nWidthAfterTab = 0;
                for ( sal_Int32 n = aCurrentTab.nTabPortion+1; n <= nTmpPortion; n++  )
                {
                    const TextPortion& rTextPortion = rParaPortion.GetTextPortions()[n];
                    nWidthAfterTab += rTextPortion.GetSize().Width();
                }
                tools::Long nW = nWidthAfterTab;   // Length before tab position
                if ( aCurrentTab.aTabStop.GetAdjustment() == SvxTabAdjust::Right )
                {
                    // Do nothing
                }
                else if ( aCurrentTab.aTabStop.GetAdjustment() == SvxTabAdjust::Center )
                {
                    nW = nWidthAfterTab/2;
                }
                else if ( aCurrentTab.aTabStop.GetAdjustment() == SvxTabAdjust::Decimal )
                {
                    EditPaM aSelectionStart(rParaPortion.GetNode(), nTmpPos);
                    EditPaM aSelectionEnd(rParaPortion.GetNode(), nTmpPos + nPortionLen);
                    EditSelection aSelection(aSelectionStart, aSelectionEnd);
                    OUString aText = GetSelected(aSelection);
 
                    sal_Int32 nDecPos = aText.indexOf( aCurrentTab.aTabStop.GetDecimal() );
                    if ( nDecPos != -1 )
                    {
                        nW -= rParaPortion.GetTextPortions()[nTmpPortion].GetSize().Width();
                        nW += aTmpFont.QuickGetTextSize(GetRefDevice(), rParaPortion.GetNode()->GetString(), nTmpPos, nDecPos, nullptr).Width();
                        aCurrentTab.bValid = false;
                    }
                }
                else
                {
                    OSL_FAIL( "CreateLines: Tab not handled!" );
                }
                tools::Long nMaxW = aCurrentTab.nTabPos - aCurrentTab.nStartPosX - nStartX;
                if ( nW >= nMaxW )
                {
                    nW = nMaxW;
                    aCurrentTab.bValid = false;
                }
                TextPortion& rTabPortion = rParaPortion.GetTextPortions()[aCurrentTab.nTabPortion];
                rTabPortion.setWidth( aCurrentTab.nTabPos - aCurrentTab.nStartPosX - nW - nStartX );
                nTmpWidth = aCurrentTab.nStartPosX + rTabPortion.GetSize().Width() + nWidthAfterTab;
            }
 
            nTmpPos = nTmpPos + nPortionLen;
            nPortionEnd = nTmpPos;
            nTmpPortion++;
            if (maStatus.OneCharPerLine())
                bEOL = true;
        }
 
        DBG_ASSERT( pPortion, "no portion!?" );
 
        aCurrentTab.bValid = false;
 
        assert(pLine);
 
        // this was possibly a portion too far:
        bool bFixedEnd = false;
        if (maStatus.OneCharPerLine())
        {
            // State before Portion (apart from nTmpWidth):
            nTmpPos -= pPortion ? nPortionLen : 0;
            nPortionStart = nTmpPos;
            nTmpPortion--;
 
            bEOL = true;
            bEOC = false;
 
            // And now just one character:
            nTmpPos++;
            nTmpPortion++;
            nPortionEnd = nTmpPortion;
            // one Non-Feature-Portion has to be wrapped
            if ( pPortion && nPortionLen > 1 )
            {
                DBG_ASSERT( pPortion->GetKind() == PortionKind::TEXT, "Len>1, but no TextPortion?" );
                nTmpWidth -= pPortion->GetSize().Width();
                sal_Int32 nP = SplitTextPortion(rParaPortion, nTmpPos, pLine);
                nTmpWidth += rParaPortion.GetTextPortions()[nP].GetSize().Width();
            }
        }
        else if ( nTmpWidth >= nXWidth )
        {
            nPortionEnd = nTmpPos;
            nTmpPos -= pPortion ? nPortionLen : 0;
            nPortionStart = nTmpPos;
            nTmpPortion--;
            bEOL = false;
            bEOC = false;
            if( pPortion ) switch ( pPortion->GetKind() )
            {
                case PortionKind::TEXT:
                {
                    nTmpWidth -= pPortion->GetSize().Width();
                }
                break;
                case PortionKind::FIELD:
                case PortionKind::TAB:
                {
                    nTmpWidth -= pPortion->GetSize().Width();
                    bEOL = true;
                    bFixedEnd = true;
                }
                break;
                default:
                {
                    //  A feature is not wrapped:
                    DBG_ASSERT( ( pPortion->GetKind() == PortionKind::LINEBREAK ), "What Feature ?" );
                    bEOL = true;
                    bFixedEnd = true;
                }
            }
        }
        else
        {
            bEOL = true;
            bEOC = true;
            pLine->SetEnd( nPortionEnd );
            assert(rParaPortion.GetTextPortions().Count() && "No TextPortions?");
            pLine->SetEndPortion(rParaPortion.GetTextPortions().Count() - 1);
        }
 
        if (maStatus.OneCharPerLine())
        {
            pLine->SetEnd( nPortionEnd );
            pLine->SetEndPortion( nTmpPortion-1 );
        }
        else if ( bFixedEnd )
        {
            if (bFieldStartNextLine)
            {
                pLine->SetEnd(nPortionStart);
                pLine->SetEndPortion(nTmpPortion - 1);
            }
            else
            {
                pLine->SetEnd(nPortionStart + 1);
                pLine->SetEndPortion(nTmpPortion);
            }
        }
        else if ( bLineBreak || bBrokenLine )
        {
            if (bFieldStartNextLine)
            {
                pLine->SetEnd(nPortionStart);
                pLine->SetEndPortion(nTmpPortion - 2);
            }
            else
            {
                pLine->SetEnd(nPortionStart + 1);
                pLine->SetEndPortion(nTmpPortion - 1);
            }
            bEOC = false; // was set above, maybe change the sequence of the if's?
        }
        else if ( !bEOL && !bContinueLastPortion )
        {
            DBG_ASSERT( pPortion && ((nPortionEnd-nPortionStart) == pPortion->GetLen()), "However, another portion?!" );
            tools::Long nRemainingWidth = !maStatus.IsSingleLine() ?
                nMaxLineWidth - nTmpWidth : pLine->GetCharPosArray()[pLine->GetCharPosArray().size() - 1] + 1;
            bool bCanHyphenate = ( aTmpFont.GetCharSet() != RTL_TEXTENCODING_SYMBOL );
            if ( bCompressedChars && pPortion && ( pPortion->GetLen() > 1 ) && pPortion->GetExtraInfos() && pPortion->GetExtraInfos()->bCompressed )
            {
                // I need the manipulated DXArray for determining the break position...
                double* pDXArray = pLine->GetCharPosArray().data() + (nPortionStart - pLine->GetStart());
                ImplCalcAsianCompression(
                    pNode, pPortion, nPortionStart, pDXArray, 10000, true);
            }
            if( pPortion )
                ImpBreakLine(rParaPortion, *pLine, pPortion, nPortionStart, nRemainingWidth, bCanHyphenate && bHyphenatePara);
        }
 
 
        // Line finished => adjust
 
 
        // CalcTextSize should be replaced by a continuous registering!
        Size aTextSize = pLine->CalcTextSize(rParaPortion);
 
        if ( aTextSize.Height() == 0 )
        {
            SeekCursor( pNode, pLine->GetStart()+1, aTmpFont );
            aTmpFont.SetPhysFont(*mpRefDev);
            ImplInitDigitMode(*mpRefDev, aTmpFont.GetLanguage());
 
            if ( IsFixedCellHeight() )
                aTextSize.setHeight( ImplCalculateFontIndependentLineSpacing( aTmpFont.GetFontHeight() ) );
            else
                aTextSize.setHeight( aTmpFont.GetPhysTxtSize(mpRefDev).Height() );
            pLine->SetHeight( static_cast<sal_uInt16>(aTextSize.Height()) );
        }
 
        // The font metrics can not be calculated continuously, if the font is
        // set anyway, because a large font only after wrapping suddenly ends
        // up in the next line => Font metrics too big.
        FormatterFontMetric aFormatterMetrics;
        sal_Int32 nTPos = pLine->GetStart();
        for ( sal_Int32 nP = pLine->GetStartPortion(); nP <= pLine->GetEndPortion(); nP++ )
        {
            const TextPortion& rTP = rParaPortion.GetTextPortions()[nP];
            // problem with hard font height attribute, when everything but the line break has this attribute
            if ( rTP.GetKind() != PortionKind::LINEBREAK )
            {
                SeekCursor( pNode, nTPos+1, aTmpFont );
                aTmpFont.SetPhysFont(*GetRefDevice());
                ImplInitDigitMode(*GetRefDevice(), aTmpFont.GetLanguage());
                RecalcFormatterFontMetrics( aFormatterMetrics, aTmpFont );
            }
            nTPos = nTPos + rTP.GetLen();
        }
        sal_uInt16 nLineHeight = aFormatterMetrics.GetHeight();
        if ( nLineHeight > pLine->GetHeight() )
            pLine->SetHeight( nLineHeight );
        pLine->SetMaxAscent( aFormatterMetrics.nMaxAscent );
 
        bSameLineAgain = false;
        if ( GetTextRanger() && ( pLine->GetHeight() > nTextLineHeight ) )
        {
            // put down with the other size!
            bSameLineAgain = true;
        }
 
        if (!bSameLineAgain && !maStatus.IsOutliner())
        {
            if ( rLSItem.GetLineSpaceRule() == SvxLineSpaceRule::Min )
            {
                double fMinHeight = scaleYSpacingValue(rLSItem.GetLineHeight());
                sal_uInt16 nMinHeight = basegfx::fround(fMinHeight);
 
                sal_uInt16 nTxtHeight = pLine->GetHeight();
                if ( nTxtHeight < nMinHeight )
                {
                    // The Ascent has to be adjusted for the difference:
                    tools::Long nDiff = nMinHeight - nTxtHeight;
                    pLine->SetMaxAscent( static_cast<sal_uInt16>(pLine->GetMaxAscent() + nDiff) );
                    pLine->SetHeight( nMinHeight, nTxtHeight );
                }
            }
            else if ( rLSItem.GetLineSpaceRule() == SvxLineSpaceRule::Fix )
            {
                double fFixHeight = scaleYSpacingValue(rLSItem.GetLineHeight());
                sal_uInt16 nFixHeight = basegfx::fround(fFixHeight);
 
                sal_uInt16 nTxtHeight = pLine->GetHeight();
                pLine->SetMaxAscent( static_cast<sal_uInt16>(pLine->GetMaxAscent() + ( nFixHeight - nTxtHeight ) ) );
                pLine->SetHeight( nFixHeight, nTxtHeight );
            }
            else if ( rLSItem.GetInterLineSpaceRule() == SvxInterLineSpaceRule::Prop )
            {
                // There are documents with PropLineSpace 0, why?
                // (cmc: re above question :-) such documents can be seen by importing a .ppt
                sal_uInt16 nPropLineSpace = rLSItem.GetPropLineSpace();
                double fProportionalScale = double(nPropLineSpace) / 100.0;
                constexpr const double f80Percent = 8.0 / 10.0;
                double fSpacingFactor = maScalingParameters.fSpacingY;
                if (nPropLineSpace && nPropLineSpace < 100)
                {
                    // Adapted code from sw/source/core/text/itrform2.cxx
                    sal_uInt16 nAscent = pLine->GetMaxAscent();
                    sal_uInt16 nNewAscent = basegfx::fround(pLine->GetTxtHeight() * fSpacingFactor * fProportionalScale * f80Percent);
                    if (!nAscent || nAscent > nNewAscent)
                        pLine->SetMaxAscent(nNewAscent);
                    sal_uInt16 nHeight = basegfx::fround(pLine->GetHeight() * fProportionalScale * fSpacingFactor);
 
                    pLine->SetHeight(nHeight, pLine->GetTxtHeight());
                }
                else if (nPropLineSpace && nPropLineSpace != 100)
                {
                    sal_uInt16 nTxtHeight = pLine->GetHeight();
                    sal_Int32 nPropTextHeight = nTxtHeight * fProportionalScale * fSpacingFactor;
                    // The Ascent has to be adjusted for the difference:
                    tools::Long nDiff = pLine->GetHeight() - nPropTextHeight;
                    pLine->SetMaxAscent( static_cast<sal_uInt16>( pLine->GetMaxAscent() - nDiff ) );
                    pLine->SetHeight( static_cast<sal_uInt16>( nPropTextHeight ), nTxtHeight );
                }
            }
            else if (rLSItem.GetInterLineSpaceRule() == SvxInterLineSpaceRule::Off)
            {
                if (maScalingParameters.fSpacingY < 1.0)
                {
                    double fSpacingFactor = maScalingParameters.fSpacingY;
                    sal_uInt16 nPropLineSpace = basegfx::fround(100.0 * fSpacingFactor);
                    if (nPropLineSpace && nPropLineSpace < 100)
                    {
                        // Adapted code from sw/source/core/text/itrform2.cxx
                        sal_uInt16 nAscent = pLine->GetMaxAscent();
                        sal_uInt16 nNewAscent = basegfx::fround(pLine->GetTxtHeight() * fSpacingFactor);
                        if (!nAscent || nAscent > nNewAscent)
                            pLine->SetMaxAscent(nNewAscent);
                        sal_uInt16 nHeight = basegfx::fround(pLine->GetHeight() * fSpacingFactor);
 
                        pLine->SetHeight(nHeight, pLine->GetTxtHeight());
                    }
 
                }
            }
        }
 
        if ( ( !IsEffectivelyVertical() && maStatus.AutoPageWidth() ) ||
             ( IsEffectivelyVertical() && maStatus.AutoPageHeight() ) )
        {
            // If the row fits within the current paper width, then this width
            // has to be used for the Alignment. If it does not fit or if it
            // will change the paper width, it will be formatted again for
            // Justification! = LEFT anyway.
            tools::Long nMaxLineWidthFix = GetColumnWidth(maPaperSize) - scaleXSpacingValue(rLRItem.GetRight()) - nStartX;
            if ( aTextSize.Width() < nMaxLineWidthFix )
                nMaxLineWidth = nMaxLineWidthFix;
        }
 
        if ( bCompressedChars )
        {
            tools::Long nRemainingWidth = nMaxLineWidth - aTextSize.Width();
            if ( nRemainingWidth > 0 )
            {
                ImplExpandCompressedPortions(*pLine, rParaPortion, nRemainingWidth);
                aTextSize = pLine->CalcTextSize(rParaPortion);
            }
        }
 
        if ( pLine->IsHangingPunctuation() )
        {
            // Width from HangingPunctuation was set to 0 in ImpBreakLine,
            // check for rel width now, maybe create compression...
            tools::Long n = nMaxLineWidth - aTextSize.Width();
            TextPortion& rTP = rParaPortion.GetTextPortions()[pLine->GetEndPortion()];
            sal_Int32 nPosInArray = pLine->GetEnd()-1-pLine->GetStart();
            tools::Long nNewValue = ( nPosInArray ? pLine->GetCharPosArray()[ nPosInArray-1 ] : 0 ) + n;
            if (o3tl::make_unsigned(nPosInArray) < pLine->GetCharPosArray().size())
            {
                pLine->GetCharPosArray()[ nPosInArray ] = nNewValue;
            }
            rTP.adjustSize(n, 0);
        }
 
        pLine->SetTextWidth( aTextSize.Width() );
        switch ( eJustification )
        {
            case SvxAdjust::Center:
            {
                tools::Long n = ( nMaxLineWidth - aTextSize.Width() ) / 2;
                n += nStartX;  // Indentation is kept.
                pLine->SetStartPosX( n );
            }
            break;
            case SvxAdjust::Right:
            {
                // For automatically wrapped lines, which has a blank at the end
                // the blank must not be displayed!
                tools::Long n = nMaxLineWidth - aTextSize.Width();
                n += nStartX;  // Indentation is kept.
                pLine->SetStartPosX( n );
            }
            break;
            case SvxAdjust::Block:
            {
                bool bDistLastLine = (GetJustifyMethod(nPara) == SvxCellJustifyMethod::Distribute);
                tools::Long nRemainingSpace = nMaxLineWidth - aTextSize.Width();
                pLine->SetStartPosX( nStartX );
                if ( nRemainingSpace > 0 && (!bEOC || bDistLastLine) )
                    ImpAdjustBlocks(rParaPortion, *pLine, nRemainingSpace);
            }
            break;
            default:
            {
                pLine->SetStartPosX( nStartX ); // FI, LI
            }
            break;
        }
 
 
        // Check whether the line must be re-issued...
 
        pLine->SetInvalid();
 
        // If a portion was wrapped there may be far too many positions in
        // CharPosArray:
        KernArray& rArray = pLine->GetCharPosArray();
        size_t nLen = pLine->GetLen();
        if (rArray.size() > nLen)
            rArray.erase(rArray.begin()+nLen, rArray.end());
 
        if ( GetTextRanger() )
        {
            if ( nTextXOffset )
                pLine->SetStartPosX( pLine->GetStartPosX() + nTextXOffset );
            if ( nTextExtraYOffset )
            {
                pLine->SetHeight( static_cast<sal_uInt16>( pLine->GetHeight() + nTextExtraYOffset ), 0 );
                pLine->SetMaxAscent( static_cast<sal_uInt16>( pLine->GetMaxAscent() + nTextExtraYOffset ) );
            }
        }
 
        // for <0 think over !
        if (rParaPortion.IsSimpleInvalid())
        {
            // Change through simple Text changes...
            // Do not cancel formatting since Portions possibly have to be split
            // again! If at some point cancelable, then validate the following
            // line! But if applicable, mark as valid, so there is less output...
            if ( pLine->GetEnd() < nInvalidStart )
            {
                if ( *pLine == aSaveLine )
                {
                    pLine->SetValid();
                }
            }
            else
            {
                sal_Int32 nStart = pLine->GetStart();
                sal_Int32 nEnd = pLine->GetEnd();
 
                if ( nStart > nInvalidEnd )
                {
                    if ( ( ( nStart-nInvalidDiff ) == aSaveLine.GetStart() ) &&
                            ( ( nEnd-nInvalidDiff ) == aSaveLine.GetEnd() ) )
                    {
                        pLine->SetValid();
                        if (bQuickFormat)
                        {
                            bLineBreak = false;
                            rParaPortion.CorrectValuesBehindLastFormattedLine( nLine );
                            break;
                        }
                    }
                }
                else if (bQuickFormat && (nEnd > nInvalidEnd))
                {
                    // If the invalid line ends so that the next begins on the
                    // 'same' passage as before, i.e. not wrapped differently,
                    //  then the text width does not have to be determined anew:
                    if ( nEnd == ( aSaveLine.GetEnd() + nInvalidDiff ) )
                    {
                        bLineBreak = false;
                        rParaPortion.CorrectValuesBehindLastFormattedLine( nLine );
                        break;
                    }
                }
            }
        }
 
        if ( !bSameLineAgain )
        {
            nIndex = pLine->GetEnd();   // next line start = last line end
                                        // as nEnd points to the last character!
 
            sal_Int32 nEndPortion = pLine->GetEndPortion();
            nCurrentPosY += pLine->GetHeight();
 
            // Next line or maybe a new line...
            pLine = nullptr;
            if ( nLine < rParaPortion.GetLines().Count()-1 )
                pLine = &rParaPortion.GetLines()[++nLine];
            if ( pLine && ( nIndex >= pNode->Len() ) )
            {
                nDelFromLine = nLine;
                break;
            }
            // Stop processing if allowed and this is outside of the paper size height.
            // Format at least two lines though, in case something detects whether
            // the text has been wrapped or something similar.
            if( mbSkipOutsideFormat && nLine > 2
                && !maStatus.AutoPageHeight() && maPaperSize.Height() < nCurrentPosY )
            {
                if ( pLine && ( nIndex >= pNode->Len()) )
                    nDelFromLine = nLine;
                break;
            }
            if ( !pLine )
            {
                if ( nIndex < pNode->Len() )
                {
                    pLine = new EditLine;
                    rParaPortion.GetLines().Insert(++nLine, std::unique_ptr<EditLine>(pLine));
                }
                else if ( nIndex && bLineBreak && GetTextRanger() )
                {
                    // normally CreateAndInsertEmptyLine would be called, but I want to use
                    // CreateLines, so I need Polygon code only here...
                    TextPortion* pDummyPortion = new TextPortion( 0 );
                    rParaPortion.GetTextPortions().Append(pDummyPortion);
                    pLine = new EditLine;
                    rParaPortion.GetLines().Insert(++nLine, std::unique_ptr<EditLine>(pLine));
                    bForceOneRun = true;
                    bProcessingEmptyLine = true;
                }
            }
            if ( pLine )
            {
                aSaveLine = *pLine;
                pLine->SetStart( nIndex );
                pLine->SetEnd( nIndex );
                pLine->SetStartPortion( nEndPortion+1 );
                pLine->SetEndPortion( nEndPortion+1 );
            }
        }
    }   // while ( Index < Len )
 
    if ( nDelFromLine >= 0 )
        rParaPortion.GetLines().DeleteFromLine( nDelFromLine );
 
    DBG_ASSERT(rParaPortion.GetLines().Count(), "No line after CreateLines!");
 
    if ( bLineBreak )
        CreateAndInsertEmptyLine(rParaPortion);
 
    bool bHeightChanged = FinishCreateLines(rParaPortion);
 
    GetRefDevice()->Pop();
 
    return bHeightChanged;
}
 
void ImpEditEngine::CreateAndInsertEmptyLine(ParaPortion& rParaPortion)
{
    DBG_ASSERT( !GetTextRanger(), "Don't use CreateAndInsertEmptyLine with a polygon!" );
 
    EditLine* pTmpLine = new EditLine;
    pTmpLine->SetStart(rParaPortion.GetNode()->Len());
    pTmpLine->SetEnd(rParaPortion.GetNode()->Len());
    rParaPortion.GetLines().Append(std::unique_ptr<EditLine>(pTmpLine));
 
    bool bLineBreak = rParaPortion.GetNode()->Len() > 0;
    sal_Int32 nSpaceBefore = 0;
    sal_Int32 nSpaceBeforeAndMinLabelWidth = GetSpaceBeforeAndMinLabelWidth(rParaPortion.GetNode(), &nSpaceBefore);
    const SvxLRSpaceItem& rLRItem = GetLRSpaceItem(rParaPortion.GetNode());
    const SvxLineSpacingItem& rLSItem = rParaPortion.GetNode()->GetContentAttribs().GetItem( EE_PARA_SBL );
    tools::Long nStartX = scaleXSpacingValue(rLRItem.GetTextLeft() + rLRItem.GetTextFirstLineOffset() + nSpaceBefore);
 
    tools::Rectangle aBulletArea { Point(), Point() };
    if ( bLineBreak )
    {
        nStartX = scaleXSpacingValue(rLRItem.GetTextLeft() + rLRItem.GetTextFirstLineOffset() + nSpaceBeforeAndMinLabelWidth);
    }
    else
    {
        aBulletArea = GetEditEnginePtr()->GetBulletArea( GetParaPortions().GetPos(&rParaPortion));
        if ( !aBulletArea.IsEmpty() && aBulletArea.Right() > 0 )
            rParaPortion.SetBulletX(sal_Int32(scaleXSpacingValue(aBulletArea.Right())));
        else
            rParaPortion.SetBulletX( 0 ); // If Bullet set incorrectly.
        if (rParaPortion.GetBulletX() > nStartX)
        {
            nStartX = scaleXSpacingValue(rLRItem.GetTextLeft() + rLRItem.GetTextFirstLineOffset() + nSpaceBeforeAndMinLabelWidth);
            if (rParaPortion.GetBulletX() > nStartX)
                nStartX = rParaPortion.GetBulletX();
        }
    }
 
    SvxFont aTmpFont;
    SeekCursor(rParaPortion.GetNode(), bLineBreak ? rParaPortion.GetNode()->Len() : 0, aTmpFont );
    aTmpFont.SetPhysFont(*mpRefDev);
 
    TextPortion* pDummyPortion = new TextPortion( 0 );
    pDummyPortion->SetSize(aTmpFont.GetPhysTxtSize(mpRefDev));
    if ( IsFixedCellHeight() )
        pDummyPortion->setHeight( ImplCalculateFontIndependentLineSpacing( aTmpFont.GetFontHeight() ) );
    rParaPortion.GetTextPortions().Append(pDummyPortion);
    FormatterFontMetric aFormatterMetrics;
    RecalcFormatterFontMetrics( aFormatterMetrics, aTmpFont );
    pTmpLine->SetMaxAscent( aFormatterMetrics.nMaxAscent );
    pTmpLine->SetHeight( static_cast<sal_uInt16>(pDummyPortion->GetSize().Height()) );
    sal_uInt16 nLineHeight = aFormatterMetrics.GetHeight();
    if ( nLineHeight > pTmpLine->GetHeight() )
        pTmpLine->SetHeight( nLineHeight );
 
    if (!maStatus.IsOutliner())
    {
        sal_Int32 nPara = GetParaPortions().GetPos(&rParaPortion);
        SvxAdjust eJustification = GetJustification( nPara );
        tools::Long nMaxLineWidth = GetColumnWidth(maPaperSize);
        nMaxLineWidth -= scaleXSpacingValue(rLRItem.GetRight());
        if ( nMaxLineWidth < 0 )
            nMaxLineWidth = 1;
        if ( eJustification ==  SvxAdjust::Center )
            nStartX = nMaxLineWidth / 2;
        else if ( eJustification ==  SvxAdjust::Right )
            nStartX = nMaxLineWidth;
    }
 
    pTmpLine->SetStartPosX( nStartX );
 
    if (!maStatus.IsOutliner())
    {
        if ( rLSItem.GetLineSpaceRule() == SvxLineSpaceRule::Min )
        {
            sal_uInt16 nMinHeight = rLSItem.GetLineHeight();
            sal_uInt16 nTxtHeight = pTmpLine->GetHeight();
            if ( nTxtHeight < nMinHeight )
            {
                // The Ascent has to be adjusted for the difference:
                tools::Long nDiff = nMinHeight - nTxtHeight;
                pTmpLine->SetMaxAscent( static_cast<sal_uInt16>(pTmpLine->GetMaxAscent() + nDiff) );
                pTmpLine->SetHeight( nMinHeight, nTxtHeight );
            }
        }
        else if ( rLSItem.GetLineSpaceRule() == SvxLineSpaceRule::Fix )
        {
            sal_uInt16 nFixHeight = rLSItem.GetLineHeight();
            sal_uInt16 nTxtHeight = pTmpLine->GetHeight();
 
            pTmpLine->SetMaxAscent( static_cast<sal_uInt16>(pTmpLine->GetMaxAscent() + ( nFixHeight - nTxtHeight ) ) );
            pTmpLine->SetHeight( nFixHeight, nTxtHeight );
        }
        else if ( rLSItem.GetInterLineSpaceRule() == SvxInterLineSpaceRule::Prop )
        {
            sal_Int32 nPara = GetParaPortions().GetPos(&rParaPortion);
            if ( nPara || pTmpLine->GetStartPortion() ) // Not the very first line
            {
                // There are documents with PropLineSpace 0, why?
                // (cmc: re above question :-) such documents can be seen by importing a .ppt
                if ( rLSItem.GetPropLineSpace() && ( rLSItem.GetPropLineSpace() != 100 ) )
                {
                    sal_uInt16 nTxtHeight = pTmpLine->GetHeight();
                    sal_Int32 nH = nTxtHeight;
                    nH *= rLSItem.GetPropLineSpace();
                    nH /= 100;
                    // The Ascent has to be adjusted for the difference:
                    tools::Long nDiff = pTmpLine->GetHeight() - nH;
                    if ( nDiff > pTmpLine->GetMaxAscent() )
                        nDiff = pTmpLine->GetMaxAscent();
                    pTmpLine->SetMaxAscent( static_cast<sal_uInt16>(pTmpLine->GetMaxAscent() - nDiff) );
                    pTmpLine->SetHeight( static_cast<sal_uInt16>(nH), nTxtHeight );
                }
            }
        }
    }
 
    if ( !bLineBreak )
    {
        tools::Long nMinHeight = aBulletArea.GetHeight();
        if ( nMinHeight > static_cast<tools::Long>(pTmpLine->GetHeight()) )
        {
            tools::Long nDiff = nMinHeight - static_cast<tools::Long>(pTmpLine->GetHeight());
            // distribute nDiff upwards and downwards
            pTmpLine->SetMaxAscent( static_cast<sal_uInt16>(pTmpLine->GetMaxAscent() + nDiff/2) );
            pTmpLine->SetHeight( static_cast<sal_uInt16>(nMinHeight) );
        }
    }
    else
    {
        // -2: The new one is already inserted.
#ifdef DBG_UTIL
        EditLine& rLastLine = rParaPortion.GetLines()[rParaPortion.GetLines().Count()-2];
        DBG_ASSERT( rLastLine.GetEnd() == rParaPortion.GetNode()->Len(), "different anyway?" );
#endif
        sal_Int32 nPos = rParaPortion.GetTextPortions().Count() - 1 ;
        pTmpLine->SetStartPortion( nPos );
        pTmpLine->SetEndPortion( nPos );
    }
}
 
bool ImpEditEngine::FinishCreateLines(ParaPortion& rParaPortion)
{
//  CalcCharPositions( pParaPortion );
    rParaPortion.SetValid();
    tools::Long nOldHeight = rParaPortion.GetHeight();
    CalcHeight(rParaPortion);
 
    DBG_ASSERT(rParaPortion.GetTextPortions().Count(), "FinishCreateLines: No Text-Portion?");
    bool bRet = rParaPortion.GetHeight() != nOldHeight;
    return bRet;
}
 
void ImpEditEngine::ImpBreakLine(ParaPortion& rParaPortion, EditLine& rLine, TextPortion const * pPortion, sal_Int32 nPortionStart, tools::Long nRemainingWidth, bool bCanHyphenate)
{
    ContentNode* const pNode = rParaPortion.GetNode();
 
    sal_Int32 nBreakInLine = nPortionStart - rLine.GetStart();
    sal_Int32 nMax = nBreakInLine + pPortion->GetLen();
    while ( ( nBreakInLine < nMax ) && ( rLine.GetCharPosArray()[nBreakInLine] < nRemainingWidth ) )
        nBreakInLine++;
 
    sal_Int32 nMaxBreakPos = nBreakInLine + rLine.GetStart();
    sal_Int32 nBreakPos = SAL_MAX_INT32;
 
    bool bCompressBlank = false;
    bool bHyphenated = false;
    bool bHangingPunctuation = false;
    sal_Unicode cAlternateReplChar = 0;
    sal_Unicode cAlternateExtraChar = 0;
    bool bAltFullLeft = false;
    bool bAltFullRight = false;
    sal_uInt32 nAltDelChar = 0;
 
    if ( ( nMaxBreakPos < ( nMax + rLine.GetStart() ) ) && ( pNode->GetChar( nMaxBreakPos ) == ' ' ) )
    {
        // Break behind the blank, blank will be compressed...
        nBreakPos = nMaxBreakPos + 1;
        bCompressBlank = true;
    }
    else
    {
        sal_Int32 nMinBreakPos = rLine.GetStart();
        const CharAttribList::AttribsType& rAttrs = pNode->GetCharAttribs().GetAttribs();
        for (size_t nAttr = rAttrs.size(); nAttr; )
        {
            const EditCharAttrib& rAttr = *rAttrs[--nAttr];
            if (rAttr.IsFeature() && rAttr.GetEnd() > nMinBreakPos && rAttr.GetEnd() <= nMaxBreakPos)
            {
                nMinBreakPos = rAttr.GetEnd();
                break;
            }
        }
        assert(nMinBreakPos <= nMaxBreakPos);
 
        lang::Locale aLocale = GetLocale( EditPaM( pNode, nMaxBreakPos ) );
 
        Reference < i18n::XBreakIterator > _xBI( ImplGetBreakIterator() );
        const bool bAllowPunctuationOutsideMargin = static_cast<const SfxBoolItem&>(
                pNode->GetContentAttribs().GetItem( EE_PARA_HANGINGPUNCTUATION )).GetValue();
 
        if (nMinBreakPos == nMaxBreakPos)
        {
            nBreakPos = nMinBreakPos;
        }
        else
        {
            Reference< XHyphenator > xHyph;
            if ( bCanHyphenate )
                xHyph = GetHyphenator();
            i18n::LineBreakHyphenationOptions aHyphOptions( xHyph, Sequence< PropertyValue >(), 1 );
            i18n::LineBreakUserOptions aUserOptions;
 
            const i18n::ForbiddenCharacters* pForbidden = GetForbiddenCharsTable()->GetForbiddenCharacters( LanguageTag::convertToLanguageType( aLocale ), true );
            aUserOptions.forbiddenBeginCharacters = pForbidden->beginLine;
            aUserOptions.forbiddenEndCharacters = pForbidden->endLine;
            aUserOptions.applyForbiddenRules = static_cast<const SfxBoolItem&>(pNode->GetContentAttribs().GetItem( EE_PARA_FORBIDDENRULES )).GetValue();
            aUserOptions.allowPunctuationOutsideMargin = bAllowPunctuationOutsideMargin;
            aUserOptions.allowHyphenateEnglish = false;
 
            if (!maStatus.IsSingleLine())
            {
                i18n::LineBreakResults aLBR = _xBI->getLineBreak(
                    pNode->GetString(), nMaxBreakPos, aLocale, nMinBreakPos, aHyphOptions, aUserOptions );
                nBreakPos = aLBR.breakIndex;
 
                // show soft hyphen
                if ( nBreakPos && CH_SOFTHYPHEN == pNode->GetString()[ sal_Int32(nBreakPos) - 1 ] )
                    bHyphenated = true;
            }
            else
            {
                nBreakPos = nMaxBreakPos;
            }
 
            // BUG in I18N - under special condition (break behind field, #87327#) breakIndex is < nMinBreakPos
            if ( nBreakPos < nMinBreakPos )
            {
                nBreakPos = nMinBreakPos;
            }
            else if ( ( nBreakPos > nMaxBreakPos ) && !aUserOptions.allowPunctuationOutsideMargin )
            {
                OSL_FAIL( "I18N: XBreakIterator::getLineBreak returns position > Max" );
                nBreakPos = nMaxBreakPos;
            }
            // Hanging punctuation is the only case that increases nBreakPos and makes
            // nBreakPos > nMaxBreakPos. It's expected that the hanging punctuation goes over
            // the border of the object.
        }
 
        // BUG in I18N - the japanese dot is in the next line!
        // !!!  Test!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
        if ( (nBreakPos + ( bAllowPunctuationOutsideMargin ? 0 : 1 ) ) <= nMaxBreakPos )
        {
            sal_Unicode cFirstInNextLine = ( (nBreakPos+1) < pNode->Len() ) ? pNode->GetChar( nBreakPos ) : 0;
            if ( cFirstInNextLine == 12290 )
                nBreakPos++;
        }
 
        bHangingPunctuation = nBreakPos > nMaxBreakPos;
        rLine.SetHangingPunctuation( bHangingPunctuation );
 
        // Whether a separator or not, push the word after the separator through
        // hyphenation... NMaxBreakPos is the last character that fits into
        // the line, nBreakPos is the beginning of the word.
        // There is a problem if the Doc is so narrow that a word is broken
        // into more than two lines...
        if ( !bHangingPunctuation && bCanHyphenate && GetHyphenator().is() )
        {
            i18n::Boundary aBoundary = _xBI->getWordBoundary(
                pNode->GetString(), nBreakPos, GetLocale( EditPaM( pNode, nBreakPos ) ), css::i18n::WordType::DICTIONARY_WORD, true);
            sal_Int32 nWordStart = nBreakPos;
            sal_Int32 nWordEnd = aBoundary.endPos;
            DBG_ASSERT( nWordEnd >= nWordStart, "Start >= End?" );
 
            sal_Int32 nWordLen = nWordEnd - nWordStart;
            if ( ( nWordEnd >= nMaxBreakPos ) && ( nWordLen > 3 ) )
            {
                // May happen, because getLineBreak may differ from getWordBoundary with DICTIONARY_WORD
                const OUString aWord = pNode->GetString().copy(nWordStart, nWordLen);
                sal_Int32 nMinTrail = nWordEnd-nMaxBreakPos+1; //+1: Before the dickey letter
                Reference< XHyphenatedWord > xHyphWord;
                if (mxHyphenator.is())
                    xHyphWord = mxHyphenator->hyphenate( aWord, aLocale, aWord.getLength() - nMinTrail, Sequence< PropertyValue >() );
                if (xHyphWord.is())
                {
                    bool bAlternate = xHyphWord->isAlternativeSpelling();
                    sal_Int32 _nWordLen = 1 + xHyphWord->getHyphenPos();
 
                    if ( ( _nWordLen >= 2 ) && ( (nWordStart+_nWordLen) >= (rLine.GetStart() + 2 ) ) )
                    {
                        if ( !bAlternate )
                        {
                            bHyphenated = true;
                            nBreakPos = nWordStart + _nWordLen;
                        }
                        else
                        {
                            // TODO: handle all alternative hyphenations (see hyphen-1.2.8/tests/unicode.*)
                            OUString aAlt( xHyphWord->getHyphenatedWord() );
                            std::u16string_view aAltLeft(aAlt.subView(0, _nWordLen));
                            std::u16string_view aAltRight(aAlt.subView(_nWordLen));
                            bAltFullLeft = aWord.startsWith(aAltLeft);
                            bAltFullRight = aWord.endsWith(aAltRight);
                            nAltDelChar = aWord.getLength() - aAlt.getLength() + static_cast<int>(!bAltFullLeft) + static_cast<int>(!bAltFullRight);
 
                            // NOTE: improved for other cases, see fdo#63711
 
                            // We expect[ed] the two cases:
                            // 1) packen becomes pak-ken
                            // 2) Schiffahrt becomes Schiff-fahrt
                            // In case 1, a character has to be replaced
                            // in case 2 a character is added.
                            // The identification is complicated by long
                            // compound words because the Hyphenator separates
                            // all position of the word. [This is not true for libhyphen.]
                            // "Schiffahrtsbrennesseln" -> "Schifffahrtsbrennnesseln"
                            // We can thus actually not directly connect the index of the
                            // AlternativeWord to aWord. The whole issue will be simplified
                            // by a function in the  Hyphenator as soon as AMA builds this in...
                            sal_Int32 nAltStart = _nWordLen - 1;
                            sal_Int32 nTxtStart = nAltStart - (aAlt.getLength() - aWord.getLength());
                            sal_Int32 nTxtEnd = nTxtStart;
                            sal_Int32 nAltEnd = nAltStart;
 
                            // The regions between the nStart and nEnd is the
                            // difference between alternative and original string.
                            while( nTxtEnd < aWord.getLength() && nAltEnd < aAlt.getLength() &&
                                   aWord[nTxtEnd] != aAlt[nAltEnd] )
                            {
                                ++nTxtEnd;
                                ++nAltEnd;
                            }
 
                            // If a character is added, then we notice it now:
                            if( nAltEnd > nTxtEnd && nAltStart == nAltEnd &&
                                aWord[ nTxtEnd ] == aAlt[nAltEnd] )
                            {
                                ++nAltEnd;
                                ++nTxtStart;
                                ++nTxtEnd;
                            }
 
                            DBG_ASSERT( ( nAltEnd - nAltStart ) == 1, "Alternate: Wrong assumption!" );
 
                            if ( nTxtEnd > nTxtStart )
                                cAlternateReplChar = aAlt[nAltStart];
                            else
                                cAlternateExtraChar = aAlt[nAltStart];
 
                            bHyphenated = true;
                            nBreakPos = nWordStart + nTxtStart;
                            if ( cAlternateReplChar || aAlt.getLength() < aWord.getLength() || !bAltFullRight) // also for "oma-tje", "re-eel"
                                nBreakPos++;
                        }
                    }
                }
            }
        }
 
        if ( nBreakPos <= rLine.GetStart() )
        {
            // No separator in line => Chop!
            nBreakPos = nMaxBreakPos;
            // I18N nextCharacters !
            if ( nBreakPos <= rLine.GetStart() )
                nBreakPos = rLine.GetStart() + 1;  // Otherwise infinite loop!
        }
    }
 
    // the dickey portion is the end portion
    rLine.SetEnd( nBreakPos );
 
    sal_Int32 nEndPortion = SplitTextPortion(rParaPortion, nBreakPos, &rLine);
 
    if ( !bCompressBlank && !bHangingPunctuation )
    {
        // When justification is not SvxAdjust::Left, it's important to compress
        // the trailing space even if there is enough room for the space...
        // Don't check for SvxAdjust::Left, doesn't matter to compress in this case too...
        assert( nBreakPos > rLine.GetStart() && "ImpBreakLines - BreakPos not expected!" );
        if ( pNode->GetChar( nBreakPos-1 ) == ' ' )
            bCompressBlank = true;
    }
 
    if ( bCompressBlank || bHangingPunctuation )
    {
        TextPortion& rTP = rParaPortion.GetTextPortions()[nEndPortion];
        DBG_ASSERT( rTP.GetKind() == PortionKind::TEXT, "BlankRubber: No TextPortion!" );
        DBG_ASSERT( nBreakPos > rLine.GetStart(), "SplitTextPortion at the beginning of the line?" );
        sal_Int32 nPosInArray = nBreakPos - 1 - rLine.GetStart();
        rTP.setWidth( ( nPosInArray && ( rTP.GetLen() > 1 ) ) ? rLine.GetCharPosArray()[ nPosInArray-1 ] : 0 );
        if (o3tl::make_unsigned(nPosInArray) < rLine.GetCharPosArray().size())
        {
            rLine.GetCharPosArray()[ nPosInArray ] = rTP.GetSize().Width();
        }
    }
    else if ( bHyphenated )
    {
        // A portion for inserting the separator...
        TextPortion* pHyphPortion = new TextPortion( 0 );
        pHyphPortion->SetKind( PortionKind::HYPHENATOR );
        if ( (cAlternateReplChar || cAlternateExtraChar) && bAltFullRight ) // alternation after the break doesn't supported
        {
            TextPortion& rPrev = rParaPortion.GetTextPortions()[nEndPortion];
            DBG_ASSERT( rPrev.GetLen(), "Hyphenate: Prev portion?!" );
            rPrev.SetLen( rPrev.GetLen() - nAltDelChar );
            pHyphPortion->SetLen( nAltDelChar );
            if (cAlternateReplChar && !bAltFullLeft) pHyphPortion->SetExtraValue( cAlternateReplChar );
            // Correct width of the portion above:
            rPrev.setWidth(
                rLine.GetCharPosArray()[ nBreakPos-1 - rLine.GetStart() - nAltDelChar ] );
        }
 
        // Determine the width of the Hyph-Portion:
        SvxFont aFont;
        SeekCursor(rParaPortion.GetNode(), nBreakPos, aFont);
        aFont.SetPhysFont(*GetRefDevice());
        pHyphPortion->SetSize(Size(GetRefDevice()->GetTextWidth(CH_HYPH), GetRefDevice()->GetTextHeight()));
 
        rParaPortion.GetTextPortions().Insert(++nEndPortion, pHyphPortion);
    }
    rLine.SetEndPortion( nEndPortion );
}
 
void ImpEditEngine::ImpAdjustBlocks(ParaPortion& rParaPortion, EditLine& rLine, tools::Long nRemainingSpace )
{
    DBG_ASSERT( nRemainingSpace > 0, "AdjustBlocks: Somewhat too little..." );
 
    if ( ( nRemainingSpace < 0 ) || rLine.IsEmpty() )
        return ;
 
    const sal_Int32 nFirstChar = rLine.GetStart();
    const sal_Int32 nLastChar = rLine.GetEnd() -1;    // Last points behind
    ContentNode* pNode = rParaPortion.GetNode();
 
    DBG_ASSERT( nLastChar < pNode->Len(), "AdjustBlocks: Out of range!" );
 
    // Search blanks or Kashidas...
    std::vector<sal_Int32> aPositions;
 
    // Kashidas ?
    ImpFindKashidas(pNode, nFirstChar, nLastChar, aPositions, nRemainingSpace);
    auto nKashidas = aPositions.size();
 
    sal_uInt16 nLastScript = i18n::ScriptType::LATIN;
    for ( sal_Int32 nChar = nFirstChar; nChar <= nLastChar; nChar++ )
    {
        EditPaM aPaM( pNode, nChar+1 );
        sal_uInt16 nScript = GetI18NScriptType(aPaM);
 
        if ( pNode->GetChar(nChar) == ' ' )
        {
            // Normal latin script.
            aPositions.push_back( nChar );
        }
        else if (nChar > nFirstChar)
        {
            if (nLastScript == i18n::ScriptType::ASIAN)
            {
                // Set break position between this and the last character if
                // the last character is asian script.
                aPositions.push_back( nChar-1 );
            }
            else if (nScript == i18n::ScriptType::ASIAN)
            {
                // Set break position between a latin script and asian script.
                aPositions.push_back( nChar-1 );
            }
        }
 
        nLastScript = nScript;
    }
 
    if ( aPositions.empty() )
        return;
 
    // If the last character is a blank, it is rejected!
    // The width must be distributed to the blockers in front...
    // But not if it is the only one.
    if ((pNode->GetChar(nLastChar) == ' ') && (aPositions.size() > 1) && (!nKashidas))
    {
        aPositions.pop_back();
        sal_Int32 nPortionStart, nPortion;
        nPortion = rParaPortion.GetTextPortions().FindPortion( nLastChar+1, nPortionStart );
        TextPortion& rLastPortion = rParaPortion.GetTextPortions()[ nPortion ];
        tools::Long nRealWidth = rLine.GetCharPosArray()[nLastChar-nFirstChar];
        tools::Long nBlankWidth = nRealWidth;
        if ( nLastChar > nPortionStart )
            nBlankWidth -= rLine.GetCharPosArray()[nLastChar-nFirstChar-1];
        // Possibly the blank has already been deducted in ImpBreakLine:
        if ( nRealWidth == rLastPortion.GetSize().Width() )
        {
            // For the last character the portion must stop behind the blank
            // => Simplify correction:
            DBG_ASSERT( ( nPortionStart + rLastPortion.GetLen() ) == ( nLastChar+1 ), "Blank actually not at the end of the portion!?");
            rLastPortion.adjustSize(-nBlankWidth, 0);
            nRemainingSpace += nBlankWidth;
        }
        rLine.GetCharPosArray()[nLastChar-nFirstChar] -= nBlankWidth;
    }
 
    size_t nGaps = aPositions.size();
    const tools::Long nMore4Everyone = nRemainingSpace / nGaps;
    tools::Long nSomeExtraSpace = nRemainingSpace - nMore4Everyone*nGaps;
 
    DBG_ASSERT( nSomeExtraSpace < static_cast<tools::Long>(nGaps), "AdjustBlocks: ExtraSpace too large" );
    DBG_ASSERT( nSomeExtraSpace >= 0, "AdjustBlocks: ExtraSpace < 0 " );
 
    // Mark Kashida positions, so that VCL knows where to insert Kashida and
    // where to only expand the width.
    // The underlying array may be reused across updates. Ensure there is no stale data.
    rLine.GetKashidaArray().clear();
    if (nKashidas)
    {
        rLine.GetKashidaArray().resize(rLine.GetCharPosArray().size(), false);
        for (size_t i = 0; i < nKashidas; i++)
        {
            auto nChar = aPositions[i];
            if ( nChar < nLastChar )
                rLine.GetKashidaArray()[nChar-nFirstChar] = 1 /*sal_True*/;
        }
    }
 
    // Correct the positions in the Array and the portion widths:
    // Last character won't be considered...
    for (auto const& nChar : aPositions)
    {
        if ( nChar < nLastChar )
        {
            sal_Int32 nPortionStart, nPortion;
            nPortion = rParaPortion.GetTextPortions().FindPortion( nChar, nPortionStart, true );
            TextPortion& rLastPortion = rParaPortion.GetTextPortions()[ nPortion ];
 
            // The width of the portion:
            rLastPortion.adjustSize(nMore4Everyone, 0);
            if (nSomeExtraSpace)
            {
                rLastPortion.adjustSize(1, 0);
            }
 
            // Correct positions in array
            sal_Int32 nPortionEnd = nPortionStart + rLastPortion.GetLen();
            for ( sal_Int32 _n = nChar; _n < nPortionEnd; _n++ )
            {
                rLine.GetCharPosArray()[_n-nFirstChar] += nMore4Everyone;
                if ( nSomeExtraSpace )
                    rLine.GetCharPosArray()[_n-nFirstChar]++;
            }
 
            if ( nSomeExtraSpace )
                nSomeExtraSpace--;
        }
    }
 
    // Now the text width contains the extra width...
    rLine.SetTextWidth(rLine.GetTextWidth() + nRemainingSpace);
}
 
// For Kashidas from sw/source/core/text/porlay.cxx
void ImpEditEngine::ImpFindKashidas(ContentNode* pNode, sal_Int32 nStart, sal_Int32 nEnd,
                                    std::vector<sal_Int32>& rArray, sal_Int32 nRemainingSpace)
{
    auto nOldLayout = GetRefDevice()->GetLayoutMode();
    comphelper::ScopeGuard stGuard{ [this, nOldLayout]
                                    { GetRefDevice()->SetLayoutMode(nOldLayout); } };
 
    GetRefDevice()->SetLayoutMode(nOldLayout | vcl::text::ComplexTextLayoutFlags::BiDiRtl);
 
    // Kashida glyph looks suspicious, skip Kashida justification
    if (GetRefDevice()->GetMinKashida() <= 0)
        return;
 
    std::vector<bool> aValidPositions;
    std::vector<sal_Int32> aKashidaArray;
    std::vector<sal_Int32> aMinKashidaArray;
    sal_Int32 nTotalMinKashida = 0U;
 
    // the search has to be performed on a per word base
 
    EditSelection aWordSel( EditPaM( pNode, nStart ) );
    aWordSel = SelectWord( aWordSel, css::i18n::WordType::DICTIONARY_WORD );
    if ( aWordSel.Min().GetIndex() < nStart )
       aWordSel.Min().SetIndex( nStart );
 
    SvxFont aTmpFont(pNode->GetCharAttribs().GetDefFont());
    while ( ( aWordSel.Min().GetNode() == pNode ) && ( aWordSel.Min().GetIndex() < nEnd ) )
    {
        const sal_Int32 nSavPos = aWordSel.Max().GetIndex();
        if ( aWordSel.Max().GetIndex() > nEnd )
           aWordSel.Max().SetIndex( nEnd );
 
        OUString aWord = GetSelected( aWordSel );
        GetRefDevice()->GetWordKashidaPositions(aWord, &aValidPositions);
 
        // restore selection for proper iteration at the end of the function
        aWordSel.Max().SetIndex( nSavPos );
 
        auto stKashidaPos = i18nutil::GetWordKashidaPosition(aWord, aValidPositions);
 
        if (stKashidaPos.has_value())
        {
            sal_Int32 nKashidaPos = aWordSel.Min().GetIndex() + stKashidaPos->nIndex;
 
            SeekCursor(pNode, nKashidaPos + 1, aTmpFont);
            aTmpFont.SetPhysFont(*GetRefDevice());
 
            auto nMinKashidaWidth = GetRefDevice()->GetMinKashida();
            nTotalMinKashida += nMinKashidaWidth;
            aMinKashidaArray.push_back(nMinKashidaWidth);
 
            aKashidaArray.push_back(nKashidaPos);
        }
 
        aWordSel = WordRight( aWordSel.Max(), css::i18n::WordType::DICTIONARY_WORD );
        aWordSel = SelectWord( aWordSel, css::i18n::WordType::DICTIONARY_WORD );
    }
 
    // Greedily reject kashida positions from start-to-end until there is enough room.
    // This will push kashida justification away from the start of the line.
    std::reverse(aKashidaArray.begin(), aKashidaArray.end());
    std::reverse(aMinKashidaArray.begin(), aMinKashidaArray.end());
    while (!aKashidaArray.empty() && nTotalMinKashida > nRemainingSpace)
    {
        nTotalMinKashida -= aMinKashidaArray.back();
        aMinKashidaArray.pop_back();
        aKashidaArray.pop_back();
    }
 
    std::reverse(aKashidaArray.begin(), aKashidaArray.end());
 
    // Validate
    std::vector<sal_Int32> aDropped;
    GetRefDevice()->ValidateKashidas(pNode->GetString(), nStart, nEnd - nStart,
                                     /*nPartIdx=*/nStart, /*nPartLen=*/nEnd - nStart, aKashidaArray,
                                     &aDropped);
 
    for (auto const& pos : aKashidaArray)
        if (std::find(aDropped.begin(), aDropped.end(), pos) == aDropped.end())
             rArray.push_back(pos);
}
 
sal_Int32 ImpEditEngine::SplitTextPortion(ParaPortion& rParaPortion, sal_Int32 nPos, EditLine* pCurLine)
{
    // The portion at nPos is split, if there is not a transition at nPos anyway
    if ( nPos == 0 )
        return 0;
 
    sal_Int32 nSplitPortion;
    sal_Int32 nTmpPos = 0;
    TextPortion* pTextPortion = nullptr;
    sal_Int32 nPortions = rParaPortion.GetTextPortions().Count();
    for ( nSplitPortion = 0; nSplitPortion < nPortions; nSplitPortion++ )
    {
        TextPortion& rTP = rParaPortion.GetTextPortions()[nSplitPortion];
        nTmpPos = nTmpPos + rTP.GetLen();
        if ( nTmpPos >= nPos )
        {
            if ( nTmpPos == nPos )  // then nothing needs to be split
            {
                return nSplitPortion;
            }
            pTextPortion = &rTP;
            break;
        }
    }
 
    DBG_ASSERT( pTextPortion, "Position outside the area!" );
 
    if (!pTextPortion)
        return 0;
 
    DBG_ASSERT( pTextPortion->GetKind() == PortionKind::TEXT, "SplitTextPortion: No TextPortion!" );
 
    sal_Int32 nOverlapp = nTmpPos - nPos;
    pTextPortion->SetLen( pTextPortion->GetLen() - nOverlapp );
    TextPortion* pNewPortion = new TextPortion( nOverlapp );
    rParaPortion.GetTextPortions().Insert(nSplitPortion+1, pNewPortion);
    // Set sizes
    if ( pCurLine )
    {
        // No new GetTextSize, instead use values from the Array:
        assert( nPos > pCurLine->GetStart() && "SplitTextPortion at the beginning of the line?" );
        pTextPortion->setWidth(pCurLine->GetCharPosArray()[nPos - pCurLine->GetStart() - 1]);
 
        if ( pTextPortion->GetExtraInfos() && pTextPortion->GetExtraInfos()->bCompressed )
        {
            // We need the original size from the portion
            sal_Int32 nTxtPortionStart = rParaPortion.GetTextPortions().GetStartPos( nSplitPortion );
            SvxFont aTmpFont = rParaPortion.GetNode()->GetCharAttribs().GetDefFont();
            SeekCursor(rParaPortion.GetNode(), nTxtPortionStart + 1, aTmpFont);
            aTmpFont.SetPhysFont(*GetRefDevice());
            GetRefDevice()->Push( vcl::PushFlags::TEXTLANGUAGE );
            ImplInitDigitMode(*GetRefDevice(), aTmpFont.GetLanguage());
            Size aSz = aTmpFont.QuickGetTextSize( GetRefDevice(), rParaPortion.GetNode()->GetString(),
                nTxtPortionStart, pTextPortion->GetLen(), nullptr );
            GetRefDevice()->Pop();
            pTextPortion->GetExtraInfos()->nOrgWidth = aSz.Width();
        }
    }
    else
        pTextPortion->setWidth(-1);
 
    return nSplitPortion;
}
 
void ImpEditEngine::CreateTextPortions(ParaPortion& rParaPortion, sal_Int32& rStart)
{
    sal_Int32 nStartPos = rStart;
    ContentNode* pNode = rParaPortion.GetNode();
    DBG_ASSERT( pNode->Len(), "CreateTextPortions should not be used for empty paragraphs!" );
 
    o3tl::sorted_vector< sal_Int32 > aPositions;
    aPositions.insert( 0 );
 
    for (std::size_t nAttr = 0;; ++nAttr)
    {
        // Insert Start and End into the Array...
        // The Insert method does not allow for duplicate values...
        EditCharAttrib* pAttrib = GetAttrib(pNode->GetCharAttribs().GetAttribs(), nAttr);
        if (!pAttrib)
            break;
        aPositions.insert( pAttrib->GetStart() );
        aPositions.insert( pAttrib->GetEnd() );
    }
    aPositions.insert( pNode->Len() );
 
    if (rParaPortion.getScriptTypePosInfos().empty())
        InitScriptTypes(GetParaPortions().GetPos(&rParaPortion));
 
    for (const ScriptTypePosInfo& rType : rParaPortion.getScriptTypePosInfos())
        aPositions.insert( rType.nStartPos );
 
    for (const WritingDirectionInfo& rWritingDirection : rParaPortion.getWritingDirectionInfos())
        aPositions.insert( rWritingDirection.nStartPos );
 
    if ( mpIMEInfos && mpIMEInfos->nLen && mpIMEInfos->pAttribs && ( mpIMEInfos->aPos.GetNode() == pNode ) )
    {
        ExtTextInputAttr nLastAttr = ExtTextInputAttr(0xFFFF);
        for( sal_Int32 n = 0; n < mpIMEInfos->nLen; n++ )
        {
            if ( mpIMEInfos->pAttribs[n] != nLastAttr )
            {
                aPositions.insert( mpIMEInfos->aPos.GetIndex() + n );
                nLastAttr = mpIMEInfos->pAttribs[n];
            }
        }
        aPositions.insert( mpIMEInfos->aPos.GetIndex() + mpIMEInfos->nLen );
    }
 
    // From ... Delete:
    // Unfortunately, the number of text portions does not have to match
    // aPositions.Count(), since there might be line breaks...
    sal_Int32 nPortionStart = 0;
    sal_Int32 nInvPortion = 0;
    sal_Int32 nP;
    for ( nP = 0; nP < rParaPortion.GetTextPortions().Count(); nP++ )
    {
        const TextPortion& rTmpPortion = rParaPortion.GetTextPortions()[nP];
        nPortionStart = nPortionStart + rTmpPortion.GetLen();
        if ( nPortionStart >= nStartPos )
        {
            nPortionStart = nPortionStart - rTmpPortion.GetLen();
            rStart = nPortionStart;
            nInvPortion = nP;
            break;
        }
    }
    DBG_ASSERT( nP < rParaPortion.GetTextPortions().Count() || !rParaPortion.GetTextPortions().Count(), "Nothing to delete: CreateTextPortions" );
    if ( nInvPortion && ( nPortionStart + rParaPortion.GetTextPortions()[nInvPortion].GetLen() > nStartPos ) )
    {
        // prefer one in front...
        // But only if it was in the middle of the portion of, otherwise it
        // might be the only one in the row in front!
        nInvPortion--;
        nPortionStart = nPortionStart - rParaPortion.GetTextPortions()[nInvPortion].GetLen();
    }
    rParaPortion.GetTextPortions().DeleteFromPortion( nInvPortion );
 
    // A portion may also have been formed by a line break:
    aPositions.insert( nPortionStart );
 
    auto nInvPos = aPositions.find(  nPortionStart );
    DBG_ASSERT( (nInvPos != aPositions.end()), "InvPos ?!" );
 
    auto i = nInvPos;
    ++i;
    while ( i != aPositions.end() )
    {
        TextPortion* pNew = new TextPortion( (*i++) - *nInvPos++ );
        rParaPortion.GetTextPortions().Append(pNew);
    }
 
    DBG_ASSERT(rParaPortion.GetTextPortions().Count(), "No Portions?!");
#if OSL_DEBUG_LEVEL > 0
    OSL_ENSURE( ParaPortion::DbgCheckTextPortions(rParaPortion), "Portion is broken?" );
#endif
}
 
void ImpEditEngine::RecalcTextPortion(ParaPortion& rParaPortion, sal_Int32 nStartPos, sal_Int32 nNewChars)
{
    DBG_ASSERT(rParaPortion.GetTextPortions().Count(), "No Portions!");
    DBG_ASSERT( nNewChars, "RecalcTextPortion with Diff == 0" );
 
    ContentNode* const pNode = rParaPortion.GetNode();
    if ( nNewChars > 0 )
    {
        // If an Attribute begins/ends at nStartPos, then a new portion starts
        // otherwise the portion is extended at nStartPos.
        if ( pNode->GetCharAttribs().HasBoundingAttrib( nStartPos ) || IsScriptChange( EditPaM( pNode, nStartPos ) ) )
        {
            sal_Int32 nNewPortionPos = 0;
            if ( nStartPos )
                nNewPortionPos = SplitTextPortion(rParaPortion, nStartPos) + 1;
 
            // A blank portion may be here, if the paragraph was empty,
            // or if a line was created by a hard line break.
            if ( ( nNewPortionPos < rParaPortion.GetTextPortions().Count() ) &&
                    !rParaPortion.GetTextPortions()[nNewPortionPos].GetLen() )
            {
                TextPortion& rTP = rParaPortion.GetTextPortions()[nNewPortionPos];
                DBG_ASSERT( rTP.GetKind() == PortionKind::TEXT, "the empty portion was no TextPortion!" );
                rTP.SetLen( rTP.GetLen() + nNewChars );
            }
            else
            {
                TextPortion* pNewPortion = new TextPortion( nNewChars );
                rParaPortion.GetTextPortions().Insert(nNewPortionPos, pNewPortion);
            }
        }
        else
        {
            sal_Int32 nPortionStart;
            const sal_Int32 nTP = rParaPortion.GetTextPortions().FindPortion( nStartPos, nPortionStart );
            TextPortion& rTP = rParaPortion.GetTextPortions()[ nTP ];
            rTP.SetLen( rTP.GetLen() + nNewChars );
            rTP.setWidth(-1);
        }
    }
    else
    {
        // Shrink or remove portion if necessary.
        // Before calling this method it must be ensured that no portions were
        // in the deleted area!
 
        // There must be no portions extending into the area or portions starting in
        // the area, so it must be:
        //    nStartPos <= nPos <= nStartPos - nNewChars(neg.)
        sal_Int32 nPortion = 0;
        sal_Int32 nPos = 0;
        sal_Int32 nEnd = nStartPos-nNewChars;
        sal_Int32 nPortions = rParaPortion.GetTextPortions().Count();
        TextPortion* pTP = nullptr;
        for ( nPortion = 0; nPortion < nPortions; nPortion++ )
        {
            pTP = &rParaPortion.GetTextPortions()[ nPortion ];
            if ( ( nPos+pTP->GetLen() ) > nStartPos )
            {
                DBG_ASSERT( nPos <= nStartPos, "Wrong Start!" );
                DBG_ASSERT( nPos+pTP->GetLen() >= nEnd, "Wrong End!" );
                break;
            }
            nPos = nPos + pTP->GetLen();
        }
        assert( pTP && "RecalcTextPortion: Portion not found" );
        if ( ( nPos == nStartPos ) && ( (nPos+pTP->GetLen()) == nEnd ) )
        {
            // Remove portion;
            PortionKind nType = pTP->GetKind();
            rParaPortion.GetTextPortions().Remove( nPortion );
            if ( nType == PortionKind::LINEBREAK )
            {
                TextPortion& rNext = rParaPortion.GetTextPortions()[ nPortion ];
                if ( !rNext.GetLen() )
                {
                    // Remove dummy portion
                    rParaPortion.GetTextPortions().Remove( nPortion );
                }
            }
        }
        else
        {
            DBG_ASSERT( pTP->GetLen() > (-nNewChars), "Portion too small to shrink! ");
            pTP->SetLen( pTP->GetLen() + nNewChars );
        }
 
        sal_Int32 nPortionCount = rParaPortion.GetTextPortions().Count();
        assert( nPortionCount );
        if (nPortionCount)
        {
            // No HYPHENATOR portion is allowed to get stuck right at the end...
            sal_Int32 nLastPortion = nPortionCount - 1;
            pTP = &rParaPortion.GetTextPortions()[nLastPortion];
            if ( pTP->GetKind() == PortionKind::HYPHENATOR )
            {
                // Discard portion; if possible, correct the ones before,
                // if the Hyphenator portion has swallowed one character...
                if ( nLastPortion && pTP->GetLen() )
                {
                    TextPortion& rPrev = rParaPortion.GetTextPortions()[nLastPortion - 1];
                    DBG_ASSERT( rPrev.GetKind() == PortionKind::TEXT, "Portion?!" );
                    rPrev.SetLen( rPrev.GetLen() + pTP->GetLen() );
                    rPrev.setWidth(-1);
                }
                rParaPortion.GetTextPortions().Remove( nLastPortion );
            }
        }
    }
#if OSL_DEBUG_LEVEL > 0
    OSL_ENSURE( ParaPortion::DbgCheckTextPortions(rParaPortion), "Portions are broken?" );
#endif
}
 
void ImpEditEngine::SetTextRanger( std::unique_ptr<TextRanger> pRanger )
{
    mpTextRanger = std::move(pRanger);
 
    for (auto& pParaPortion : GetParaPortions())
    {
        pParaPortion->MarkSelectionInvalid( 0 );
        pParaPortion->GetLines().Reset();
    }
 
    FormatFullDoc();
    UpdateViews( GetActiveView() );
    if ( IsUpdateLayout() && GetActiveView() )
        mpActiveView->ShowCursor(false, false);
}
 
void ImpEditEngine::SetVertical( bool bVertical)
{
    if ( IsEffectivelyVertical() != bVertical)
    {
        GetEditDoc().SetVertical(bVertical);
        bool bUseCharAttribs = bool(maStatus.GetControlWord() & EEControlBits::USECHARATTRIBS);
        GetEditDoc().CreateDefFont( bUseCharAttribs );
        if ( IsFormatted() )
        {
            FormatFullDoc();
            UpdateViews( GetActiveView() );
        }
    }
}
 
void ImpEditEngine::SetRotation(TextRotation nRotation)
{
    if (GetEditDoc().GetRotation() == nRotation)
        return; // not modified
    GetEditDoc().SetRotation(nRotation);
    bool bUseCharAttribs = bool(maStatus.GetControlWord() & EEControlBits::USECHARATTRIBS);
    GetEditDoc().CreateDefFont( bUseCharAttribs );
    if ( IsFormatted() )
    {
        FormatFullDoc();
        UpdateViews( GetActiveView() );
    }
}
 
void ImpEditEngine::SetTextColumns(sal_Int16 nColumns, sal_Int32 nSpacing)
{
    assert(nColumns >= 1);
    if (mnColumns != nColumns || mnColumnSpacing != nSpacing)
    {
        if (nColumns == 0)
        {
            SAL_WARN("editeng", "bad nColumns value, ignoring");
            nColumns = 1;
        }
        mnColumns = nColumns;
        mnColumnSpacing = nSpacing;
        if (IsFormatted())
        {
            FormatFullDoc();
            UpdateViews(GetActiveView());
        }
    }
}
 
void ImpEditEngine::SetFixedCellHeight( bool bUseFixedCellHeight )
{
    if ( IsFixedCellHeight() != bUseFixedCellHeight )
    {
        GetEditDoc().SetFixedCellHeight( bUseFixedCellHeight );
        if ( IsFormatted() )
        {
            FormatFullDoc();
            UpdateViews( GetActiveView() );
        }
    }
}
 
void ImpEditEngine::SeekCursor( ContentNode* pNode, sal_Int32 nPos, SvxFont& rFont, OutputDevice* pOut )
{
    // It was planned, SeekCursor( nStartPos, nEndPos,... ), so that it would
    // only be searched anew at the StartPosition.
    // Problem: There would be two lists to consider/handle:
    // OrderedByStart,OrderedByEnd.
 
    if ( nPos > pNode->Len() )
        nPos = pNode->Len();
 
    rFont = pNode->GetCharAttribs().GetDefFont();
 
    /*
     * Set attributes for script types Asian and Complex
    */
    short nScriptTypeI18N = GetI18NScriptType( EditPaM( pNode, nPos ) );
    SvtScriptType nScriptType = SvtLanguageOptions::FromI18NToSvtScriptType(nScriptTypeI18N);
    if ( ( nScriptTypeI18N == i18n::ScriptType::ASIAN ) || ( nScriptTypeI18N == i18n::ScriptType::COMPLEX ) )
    {
        const SvxFontItem& rFontItem = static_cast<const SvxFontItem&>(pNode->GetContentAttribs().GetItem( GetScriptItemId( EE_CHAR_FONTINFO, nScriptType ) ));
        rFont.SetFamilyName( rFontItem.GetFamilyName() );
        rFont.SetFamily( rFontItem.GetFamily() );
        rFont.SetPitch( rFontItem.GetPitch() );
        rFont.SetCharSet( rFontItem.GetCharSet() );
        Size aSz( rFont.GetFontSize() );
        aSz.setHeight( static_cast<const SvxFontHeightItem&>(pNode->GetContentAttribs().GetItem( GetScriptItemId( EE_CHAR_FONTHEIGHT, nScriptType ) ) ).GetHeight() );
        rFont.SetFontSize( aSz );
        rFont.SetWeight( static_cast<const SvxWeightItem&>(pNode->GetContentAttribs().GetItem( GetScriptItemId( EE_CHAR_WEIGHT, nScriptType ))).GetWeight() );
        rFont.SetItalic( static_cast<const SvxPostureItem&>(pNode->GetContentAttribs().GetItem( GetScriptItemId( EE_CHAR_ITALIC, nScriptType ))).GetPosture() );
        rFont.SetLanguage( static_cast<const SvxLanguageItem&>(pNode->GetContentAttribs().GetItem( GetScriptItemId( EE_CHAR_LANGUAGE, nScriptType ))).GetLanguage() );
    }
 
    sal_uInt16 nRelWidth = pNode->GetContentAttribs().GetItem( EE_CHAR_FONTWIDTH).GetValue();
 
    /*
     * Set output device's line and overline colors
    */
    if ( pOut )
    {
        const SvxUnderlineItem& rTextLineColor = pNode->GetContentAttribs().GetItem( EE_CHAR_UNDERLINE );
        if ( rTextLineColor.GetColor() != COL_TRANSPARENT )
            pOut->SetTextLineColor( rTextLineColor.GetColor() );
        else
            pOut->SetTextLineColor();
 
        const SvxOverlineItem& rOverlineColor = pNode->GetContentAttribs().GetItem( EE_CHAR_OVERLINE );
        if ( rOverlineColor.GetColor() != COL_TRANSPARENT )
            pOut->SetOverlineColor( rOverlineColor.GetColor() );
        else
            pOut->SetOverlineColor();
    }
 
    const SvxLanguageItem* pCJKLanguageItem = nullptr;
 
    /*
     * Scan through char attributes of pNode
    */
    if (maStatus.UseCharAttribs())
    {
        CharAttribList::AttribsType& rAttribs = pNode->GetCharAttribs().GetAttribs();
        size_t nAttr = 0;
        EditCharAttrib* pAttrib = GetAttrib(rAttribs, nAttr);
        while ( pAttrib && ( pAttrib->GetStart() <= nPos ) )
        {
            // when seeking, ignore attributes which start there! Empty attributes
            // are considered (used) as these are just set. But do not use empty
            // attributes: When just set and empty => no effect on font
            // In a blank paragraph, set characters take effect immediately.
            if ( ( pAttrib->Which() != 0 ) &&
                 ( ( ( pAttrib->GetStart() < nPos ) && ( pAttrib->GetEnd() >= nPos ) )
                     || ( !pNode->Len() ) ) )
            {
                DBG_ASSERT( ( pAttrib->Which() >= EE_CHAR_START ) && ( pAttrib->Which() <= EE_FEATURE_END ), "Invalid Attribute in Seek() " );
                if ( IsScriptItemValid( pAttrib->Which(), nScriptTypeI18N ) )
                {
                    pAttrib->SetFont( rFont, pOut );
                    // #i1550# hard color attrib should win over text color from field
                    if ( pAttrib->Which() == EE_FEATURE_FIELD )
                    {
                        // These Attribs positions come from PaMs, so their interval is right-open and left-closed
                        // when SeekCursor is called, nPos is incremented by 1. I do not know why...
                        // probably designed to be a nEndPos, and like in a PaM, it is the position after the actual character.
                        sal_Int32 nPosActual = nPos > 0 ? nPos - 1 : 0;
                        EditCharAttrib* pColorAttr = pNode->GetCharAttribs().FindAttribRightOpen( EE_CHAR_COLOR, nPosActual );
                        if ( pColorAttr )
                            pColorAttr->SetFont( rFont, pOut );
                    }
                }
                if ( pAttrib->Which() == EE_CHAR_FONTWIDTH )
                    nRelWidth = static_cast<const SvxCharScaleWidthItem*>(pAttrib->GetItem())->GetValue();
                if ( pAttrib->Which() == EE_CHAR_LANGUAGE_CJK )
                    pCJKLanguageItem = static_cast<const SvxLanguageItem*>( pAttrib->GetItem() );
            }
            pAttrib = GetAttrib( rAttribs, ++nAttr );
        }
    }
 
    if ( !pCJKLanguageItem )
        pCJKLanguageItem = &pNode->GetContentAttribs().GetItem( EE_CHAR_LANGUAGE_CJK );
 
    rFont.SetCJKContextLanguage( pCJKLanguageItem->GetLanguage() );
 
    if ( (rFont.GetKerning() != FontKerning::NONE) && IsKernAsianPunctuation() && ( nScriptTypeI18N == i18n::ScriptType::ASIAN ) )
        rFont.SetKerning( rFont.GetKerning() | FontKerning::Asian );
 
    // tdf#160401/#i78474# small caps do not exist in CTL fonts, so turn that off here where we know the script type
    if (rFont.IsCapital() && nScriptTypeI18N == i18n::ScriptType::COMPLEX)
        rFont.SetCaseMap(SvxCaseMap::NotMapped);
 
    if (maStatus.DoNotUseColors())
    {
        rFont.SetColor( /* rColorItem.GetValue() */ COL_BLACK );
    }
 
    if (maStatus.DoStretch() || ( nRelWidth != 100 ))
    {
        // For the current Output device, because otherwise if RefDev=Printer its looks
        // ugly on the screen!
        OutputDevice* pDev = pOut ? pOut : GetRefDevice();
        rFont.SetPhysFont(*pDev);
        FontMetric aMetric( pDev->GetFontMetric() );
 
        // before forcing nPropr to 100%, calculate a new escapement relative to this fake size.
        sal_uInt8 nPropr = rFont.GetPropr();
        sal_Int16 nEsc = rFont.GetEscapement();
        if ( nPropr && nEsc && nPropr != 100 && abs(nEsc) != DFLT_ESC_AUTO_SUPER )
            rFont.SetEscapement( 100.0/nPropr * nEsc );
 
        // Set the font as we want it to look like & reset the Propr attribute
        // so that it is not counted twice.
        Size aRealSz( aMetric.GetFontSize() );
        rFont.SetPropr( 100 );
 
        if (maStatus.DoStretch())
        {
            if (maScalingParameters.fFontY != 1.0)
            {
                double fHeightRounded = roundToNearestPt(aRealSz.Height());
                double fNewHeight = fHeightRounded * maScalingParameters.fFontY;
                fNewHeight = roundToNearestPt(fNewHeight);
                aRealSz.setHeight(basegfx::fround<tools::Long>(fNewHeight));
            }
            if (maScalingParameters.fFontX != 1.0)
            {
                auto fFontX = maScalingParameters.fFontX;
                auto fFontY = maScalingParameters.fFontY;
                if (fFontX == fFontY && nRelWidth == 100 )
                {
                    aRealSz.setWidth( 0 );
                }
                else
                {
                    double fWidthRounded = roundToNearestPt(aRealSz.Width());
                    double fNewWidth = fWidthRounded * fFontX;
                    fNewWidth = roundToNearestPt(fNewWidth);
                    aRealSz.setWidth(basegfx::fround<tools::Long>(fNewWidth));
 
                    // Also the Kerning: (long due to handle Interim results)
                    tools::Long nKerning = rFont.GetFixKerning();
/*
  The consideration was: If negative kerning, but StretchX = 200
  => Do not double the kerning, thus pull the letters closer together
  ---------------------------
  Kern  StretchX    =>Kern
  ---------------------------
  >0        <100        < (Proportional)
  <0        <100        < (Proportional)
  >0        >100        > (Proportional)
  <0        >100        < (The amount, thus disproportional)
*/
                    if (nKerning < 0 && fFontX > 1.0)
                    {
                        // disproportional
                        nKerning = basegfx::fround(nKerning / fFontX);
                    }
                    else if ( nKerning )
                    {
                        // Proportional
                        nKerning = basegfx::fround(nKerning * fFontX);
                    }
                    rFont.SetFixKerning( static_cast<short>(nKerning) );
                }
            }
        }
        if ( nRelWidth != 100 )
        {
            aRealSz.setWidth( aRealSz.Width() * nRelWidth );
            aRealSz.setWidth( aRealSz.Width() / 100 );
        }
        rFont.SetFontSize( aRealSz );
        // Font is not restored...
    }
 
    if ( ( ( rFont.GetColor() == COL_AUTO ) || ( IsForceAutoColor() ) ) && pOut )
    {
        // #i75566# Do not use AutoColor when printing OR Pdf export
        const bool bPrinting(OUTDEV_PRINTER == pOut->GetOutDevType());
        const bool bPDFExporting(OUTDEV_PDF == pOut->GetOutDevType());
 
        if ( IsAutoColorEnabled() && !bPrinting && !bPDFExporting)
        {
            // Never use WindowTextColor on the printer
            rFont.SetColor( GetAutoColor() );
        }
        else
        {
            if ( ( GetBackgroundColor() != COL_AUTO ) && GetBackgroundColor().IsDark() )
                rFont.SetColor( COL_WHITE );
            else
                rFont.SetColor( COL_BLACK );
        }
    }
 
    if ( !(mpIMEInfos && mpIMEInfos->pAttribs && ( mpIMEInfos->aPos.GetNode() == pNode ) &&
        ( nPos > mpIMEInfos->aPos.GetIndex() ) && ( nPos <= ( mpIMEInfos->aPos.GetIndex() + mpIMEInfos->nLen ) )) )
        return;
 
    ExtTextInputAttr nAttr = mpIMEInfos->pAttribs[ nPos - mpIMEInfos->aPos.GetIndex() - 1 ];
    if ( nAttr & ExtTextInputAttr::Underline )
        rFont.SetUnderline( LINESTYLE_SINGLE );
    else if ( nAttr & ExtTextInputAttr::DoubleUnderline )
        rFont.SetUnderline( LINESTYLE_DOUBLE );
    else if ( nAttr & ExtTextInputAttr::BoldUnderline )
        rFont.SetUnderline( LINESTYLE_BOLD );
    else if ( nAttr & ExtTextInputAttr::DottedUnderline )
        rFont.SetUnderline( LINESTYLE_DOTTED );
    else if ( nAttr & ExtTextInputAttr::DashDotUnderline )
        rFont.SetUnderline( LINESTYLE_DOTTED );
    else if ( nAttr & ExtTextInputAttr::RedText )
        rFont.SetColor( COL_RED );
    else if ( nAttr & ExtTextInputAttr::HalfToneText )
        rFont.SetColor( COL_LIGHTGRAY );
    if ( nAttr & ExtTextInputAttr::Highlight )
    {
        const StyleSettings& rStyleSettings = Application::GetSettings().GetStyleSettings();
        rFont.SetColor( rStyleSettings.GetHighlightTextColor() );
        rFont.SetFillColor( rStyleSettings.GetHighlightColor() );
        rFont.SetTransparent( false );
    }
    else if ( nAttr & ExtTextInputAttr::GrayWaveline )
    {
        rFont.SetUnderline( LINESTYLE_WAVE );
        if( pOut )
            pOut->SetTextLineColor( COL_LIGHTGRAY );
    }
}
 
void ImpEditEngine::RecalcFormatterFontMetrics( FormatterFontMetric& rCurMetrics, SvxFont& rFont )
{
    // for line height at high / low first without Propr!
    sal_uInt16 nPropr = rFont.GetPropr();
    DBG_ASSERT( ( nPropr == 100 ) || rFont.GetEscapement(), "Propr without Escape?!" );
    if ( nPropr != 100 )
    {
        rFont.SetPropr( 100 );
        rFont.SetPhysFont(*mpRefDev);
    }
    sal_uInt16 nAscent, nDescent;
 
    FontMetric aMetric(mpRefDev->GetFontMetric());
    nAscent = static_cast<sal_uInt16>(aMetric.GetAscent());
    if ( IsAddExtLeading() )
        nAscent = sal::static_int_cast< sal_uInt16 >(
            nAscent + aMetric.GetExternalLeading() );
    nDescent = static_cast<sal_uInt16>(aMetric.GetDescent());
 
    if ( IsFixedCellHeight() )
    {
        nAscent = sal::static_int_cast< sal_uInt16 >( rFont.GetFontHeight() );
        nDescent= sal::static_int_cast< sal_uInt16 >( ImplCalculateFontIndependentLineSpacing( rFont.GetFontHeight() ) - nAscent );
    }
    else
    {
        sal_uInt16 nIntLeading = ( aMetric.GetInternalLeading() > 0 ) ? static_cast<sal_uInt16>(aMetric.GetInternalLeading()) : 0;
        // Fonts without leading cause problems
        if ( ( nIntLeading == 0 ) && (mpRefDev->GetOutDevType() == OUTDEV_PRINTER))
        {
            // Let's see what Leading one gets on the screen
            VclPtr<VirtualDevice> pVDev = GetVirtualDevice(mpRefDev->GetMapMode(), mpRefDev->GetDrawMode());
            rFont.SetPhysFont(*pVDev);
            aMetric = pVDev->GetFontMetric();
 
            // This is so that the Leading does not count itself out again,
            // if the whole line has the font, nTmpLeading.
            nAscent = static_cast<sal_uInt16>(aMetric.GetAscent());
            nDescent = static_cast<sal_uInt16>(aMetric.GetDescent());
        }
    }
    if ( nAscent > rCurMetrics.nMaxAscent )
        rCurMetrics.nMaxAscent = nAscent;
    if ( nDescent > rCurMetrics.nMaxDescent )
        rCurMetrics.nMaxDescent= nDescent;
    // Special treatment of high/low:
    if ( !rFont.GetEscapement() )
        return;
 
    // Now in consideration of Escape/Propr
    // possibly enlarge Ascent or Descent
    short nDiff = static_cast<short>(rFont.GetFontSize().Height()*rFont.GetEscapement()/100);
    if ( rFont.GetEscapement() > 0 )
    {
        nAscent = static_cast<sal_uInt16>(static_cast<tools::Long>(nAscent)*nPropr/100 + nDiff);
        if ( nAscent > rCurMetrics.nMaxAscent )
            rCurMetrics.nMaxAscent = nAscent;
    }
    else    // has to be < 0
    {
        nDescent = static_cast<sal_uInt16>(static_cast<tools::Long>(nDescent)*nPropr/100 - nDiff);
        if ( nDescent > rCurMetrics.nMaxDescent )
            rCurMetrics.nMaxDescent= nDescent;
    }
}
 
tools::Long ImpEditEngine::getWidthDirectionAware(const Size& sz) const
{
    return !IsEffectivelyVertical() ? sz.Width() : sz.Height();
}
 
tools::Long ImpEditEngine::getHeightDirectionAware(const Size& sz) const
{
    return !IsEffectivelyVertical() ? sz.Height() : sz.Width();
}
 
void ImpEditEngine::adjustXDirectionAware(Point& pt, tools::Long x) const
{
    if (!IsEffectivelyVertical())
        pt.AdjustX(x);
    else
        pt.AdjustY(IsTopToBottom() ? x : -x);
}
 
void ImpEditEngine::adjustYDirectionAware(Point& pt, tools::Long y) const
{
    if (!IsEffectivelyVertical())
        pt.AdjustY(y);
    else
        pt.AdjustX(IsTopToBottom() ? -y : y);
}
 
void ImpEditEngine::setXDirectionAwareFrom(Point& ptDest, const Point& ptSrc) const
{
    if (!IsEffectivelyVertical())
        ptDest.setX(ptSrc.X());
    else
        ptDest.setY(ptSrc.Y());
}
 
void ImpEditEngine::setYDirectionAwareFrom(Point& ptDest, const Point& ptSrc) const
{
    if (!IsEffectivelyVertical())
        ptDest.setY(ptSrc.Y());
    else
        ptDest.setX(ptSrc.Y());
}
 
tools::Long ImpEditEngine::getYOverflowDirectionAware(const Point& pt,
                                                      const tools::Rectangle& rectMax) const
{
    tools::Long nRes;
    if (!IsEffectivelyVertical())
        nRes = pt.Y() - rectMax.Bottom();
    else if (IsTopToBottom())
        nRes = rectMax.Left() - pt.X();
    else
        nRes = pt.X() - rectMax.Right();
    return std::max(nRes, tools::Long(0));
}
 
bool ImpEditEngine::isXOverflowDirectionAware(const Point& pt, const tools::Rectangle& rectMax) const
{
    if (!IsEffectivelyVertical())
        return pt.X() > rectMax.Right();
 
    if (IsTopToBottom())
        return pt.Y() > rectMax.Bottom();
    else
        return pt.Y() < rectMax.Top();
}
 
tools::Long ImpEditEngine::getBottomDocOffset(const tools::Rectangle& rect) const
{
    if (!IsEffectivelyVertical())
        return rect.Bottom();
 
    if (IsTopToBottom())
        return -rect.Left();
    else
        return rect.Right();
}
 
Size ImpEditEngine::getTopLeftDocOffset(const tools::Rectangle& rect) const
{
    if (!IsEffectivelyVertical())
        return { rect.Left(), rect.Top() };
 
    if (IsTopToBottom())
        return { rect.Top(), -rect.Right() };
    else
        return { -rect.Bottom(), rect.Left() };
}
 
// Returns the resulting shift for the point; allows to apply the same shift to other points
Point ImpEditEngine::MoveToNextLine(
    Point& rMovePos, // [in, out] Point that will move to the next line
    tools::Long nLineHeight, // [in] Y-direction move distance (direction-aware)
    sal_Int16& rColumn, // [in, out] current column number
    Point aOrigin, // [in] Origin point to calculate limits and initial Y position in a new column
    tools::Long* pnHeightNeededToNotWrap // On column wrap, returns how much more height is needed
) const
{
    const Point aOld = rMovePos;
 
    // Move the point by the requested distance in Y direction
    adjustYDirectionAware(rMovePos, nLineHeight);
    // Check if the resulting position has moved beyond the limits, and more columns left.
    // The limits are defined by a rectangle starting from aOrigin with width of maPaperSize
    // and height of mnCurTextHeight
    Point aOtherCorner = aOrigin;
    adjustXDirectionAware(aOtherCorner, getWidthDirectionAware(maPaperSize));
    adjustYDirectionAware(aOtherCorner, mnCurTextHeight);
    tools::Long nNeeded
        = getYOverflowDirectionAware(rMovePos, tools::Rectangle::Normalize(aOrigin, aOtherCorner));
    if (pnHeightNeededToNotWrap)
        *pnHeightNeededToNotWrap = nNeeded;
    if (nNeeded && rColumn < mnColumns)
    {
        ++rColumn;
        // If we didn't fit into the last column, indicate that only by setting the column number
        // to the total number of columns; do not adjust
        if (rColumn < mnColumns)
        {
            // Set Y position of the point to that of aOrigin
            setYDirectionAwareFrom(rMovePos, aOrigin);
            // Move the point by the requested distance in Y direction
            adjustYDirectionAware(rMovePos, nLineHeight);
            // Move the point by the column+spacing distance in X direction
            adjustXDirectionAware(rMovePos, GetColumnWidth(maPaperSize) + mnColumnSpacing);
        }
    }
 
    return rMovePos - aOld;
}
 
void ImpEditEngine::Draw( OutputDevice& rOutDev, const Point& rStartPos, Degree10 nOrientation )
{
    // Create with 2 points, as with positive points it will end up with
    // LONGMAX as Size, Bottom and Right in the range > LONGMAX.
    tools::Rectangle aBigRect( -0x3FFFFFFF, -0x3FFFFFFF, 0x3FFFFFFF, 0x3FFFFFFF );
    if( rOutDev.GetConnectMetaFile() )
        rOutDev.Push();
    Point aStartPos( rStartPos );
    if ( IsEffectivelyVertical() )
    {
        aStartPos.AdjustX(GetPaperSize().Width() );
        rStartPos.RotateAround(aStartPos, nOrientation);
    }
    Paint(rOutDev, aBigRect, aStartPos, false, nOrientation);
    if (rOutDev.GetConnectMetaFile())
        rOutDev.Pop();
}
 
void ImpEditEngine::Draw( OutputDevice& rOutDev, const tools::Rectangle& rOutRect, const Point& rStartDocPos, bool bClip )
{
#if defined( DBG_UTIL ) || (OSL_DEBUG_LEVEL > 1)
    if ( bDebugPaint )
        DumpData(false);
#endif
 
    // Align to the pixel boundary, so that it becomes exactly the same
    // as Paint ()
    tools::Rectangle aOutRect( rOutDev.LogicToPixel( rOutRect ) );
    aOutRect = rOutDev.PixelToLogic( aOutRect );
 
    Point aStartPos;
    if ( !IsEffectivelyVertical() )
    {
        aStartPos.setX( aOutRect.Left() - rStartDocPos.X() );
        aStartPos.setY( aOutRect.Top() - rStartDocPos.Y() );
    }
    else
    {
        aStartPos.setX( aOutRect.Right() + rStartDocPos.Y() );
        aStartPos.setY( aOutRect.Top() - rStartDocPos.X() );
    }
 
    bool bClipRegion = rOutDev.IsClipRegion();
    bool bMetafile = rOutDev.GetConnectMetaFile();
    vcl::Region aOldRegion = rOutDev.GetClipRegion();
 
    // If one existed => intersection!
    // Use Push/pop for creating the Meta file
    if ( bMetafile )
        rOutDev.Push();
 
    // Always use the Intersect method, it is a must for Metafile!
    if ( bClip )
    {
        // Clip only if necessary...
        if (!IsFormatted())
            FormatDoc();
        tools::Long nTextWidth = !IsEffectivelyVertical() ? CalcTextWidth(true) : GetTextHeight();
        if ( rStartDocPos.X() || rStartDocPos.Y() ||
             ( rOutRect.GetHeight() < static_cast<tools::Long>(GetTextHeight()) ) ||
             ( rOutRect.GetWidth() < nTextWidth ) )
        {
            // Some printer drivers cause problems if characters graze the
            // ClipRegion, therefore rather add a pixel more ...
            tools::Rectangle aClipRect( aOutRect );
            if ( rOutDev.GetOutDevType() == OUTDEV_PRINTER )
            {
                Size aPixSz( 1, 0 );
                aPixSz = rOutDev.PixelToLogic( aPixSz );
                aClipRect.AdjustRight(aPixSz.Width() );
                aClipRect.AdjustBottom(aPixSz.Width() );
            }
            rOutDev.IntersectClipRegion( aClipRect );
        }
    }
 
    Paint(rOutDev, aOutRect, aStartPos);
 
    if ( bMetafile )
        rOutDev.Pop();
    else if ( bClipRegion )
        rOutDev.SetClipRegion( aOldRegion );
    else
        rOutDev.SetClipRegion();
}
 
// TODO: use IterateLineAreas in ImpEditEngine::Paint, to avoid algorithm duplication
void ImpEditEngine::Paint( OutputDevice& rOutDev, tools::Rectangle aClipRect, Point aStartPos, bool bStripOnly, Degree10 nOrientation )
{
    if ( !IsUpdateLayout() && !bStripOnly )
        return;
 
    if ( !IsFormatted() )
        FormatDoc();
 
    tools::Long nFirstVisXPos = - rOutDev.GetMapMode().GetOrigin().X();
    tools::Long nFirstVisYPos = - rOutDev.GetMapMode().GetOrigin().Y();
 
    DBG_ASSERT( GetParaPortions().Count(), "No ParaPortion?!" );
    SvxFont aTmpFont = GetParaPortions().getRef(0).GetNode()->GetCharAttribs().GetDefFont();
    vcl::PDFExtOutDevData* const pPDFExtOutDevData = dynamic_cast< vcl::PDFExtOutDevData* >( rOutDev.GetExtOutDevData() );
 
    // In the case of rotated text is aStartPos considered TopLeft because
    // other information is missing, and since the whole object is shown anyway
    // un-scrolled.
    // The rectangle is infinite.
    const Point aOrigin( aStartPos );
 
    // #110496# Added some more optional metafile comments. This
    // change: factored out some duplicated code.
    GDIMetaFile* pMtf = rOutDev.GetConnectMetaFile();
    const bool bMetafileValid( pMtf != nullptr );
 
    const tools::Long nVertLineSpacing = CalcVertLineSpacing(aStartPos);
 
    sal_Int16 nColumn = 0;
 
    // Over all the paragraphs...
 
    for (sal_Int32 nParaPortion = 0; nParaPortion < GetParaPortions().Count(); nParaPortion++)
    {
        ParaPortion const& rParaPortion = GetParaPortions().getRef(nParaPortion);
        // if when typing idle formatting,  asynchronous Paint.
        // Invisible Portions may be invalid.
        if (rParaPortion.IsVisible() && rParaPortion.IsInvalid())
            return;
 
        if ( pPDFExtOutDevData )
            pPDFExtOutDevData->WrapBeginStructureElement(vcl::PDFWriter::Paragraph);
 
        const tools::Long nParaHeight = rParaPortion.GetHeight();
        if (rParaPortion.IsVisible() && (
                ( !IsEffectivelyVertical() && ( ( aStartPos.Y() + nParaHeight ) > aClipRect.Top() ) ) ||
                ( IsEffectivelyVertical() && IsTopToBottom() && ( ( aStartPos.X() - nParaHeight ) < aClipRect.Right() ) ) ||
                ( IsEffectivelyVertical() && !IsTopToBottom() && ( ( aStartPos.X() + nParaHeight ) > aClipRect.Left() ) ) ) )
 
        {
            Point aTmpPos;
 
            // Over the lines of the paragraph...
 
            const sal_Int32 nLines = rParaPortion.GetLines().Count();
            const sal_Int32 nLastLine = nLines-1;
 
            bool bEndOfParagraphWritten(false);
 
            adjustYDirectionAware(aStartPos, rParaPortion.GetFirstLineOffset());
 
            const SvxLineSpacingItem& rLSItem = rParaPortion.GetNode()->GetContentAttribs().GetItem( EE_PARA_SBL );
            sal_uInt16 nSBL = ( rLSItem.GetInterLineSpaceRule() == SvxInterLineSpaceRule::Fix )
                                ? scaleYSpacingValue(rLSItem.GetInterLineSpace()) : 0;
            bool bPaintBullet (false);
 
            for ( sal_Int32 nLine = 0; nLine < nLines; nLine++ )
            {
                EditLine* pLine = &GetParaPortions().getRef(nParaPortion).GetLines()[nLine];
                sal_Int32 nIndex = pLine->GetStart();
                tools::Long nLineHeight = pLine->GetHeight();
                if (nLine != nLastLine)
                    nLineHeight += nVertLineSpacing;
                MoveToNextLine(aStartPos, nLineHeight, nColumn, aOrigin);
                aTmpPos = aStartPos;
                adjustXDirectionAware(aTmpPos, pLine->GetStartPosX());
                adjustYDirectionAware(aTmpPos, pLine->GetMaxAscent() - nLineHeight);
 
                if ( ( !IsEffectivelyVertical() && ( aStartPos.Y() > aClipRect.Top() ) )
                    || ( IsEffectivelyVertical() && IsTopToBottom() && aStartPos.X() < aClipRect.Right() )
                    || ( IsEffectivelyVertical() && !IsTopToBottom() && aStartPos.X() > aClipRect.Left() ) )
                {
                    bPaintBullet = false;
 
                    // Why not just also call when stripping portions? This will give the correct values
                    // and needs no position corrections in OutlinerEditEng::DrawingText which tries to call
                    // PaintBullet correctly; exactly what GetEditEnginePtr()->PaintingFirstLine
                    // does, too. No change for not-layouting (painting).
                    if(0 == nLine) // && !bStripOnly)
                    {
                        Point aLineStart(aStartPos);
                        adjustYDirectionAware(aLineStart, -nLineHeight);
                        GetEditEnginePtr()->PaintingFirstLine(nParaPortion, aLineStart, aOrigin, nOrientation, rOutDev);
 
                        // Remember whether a bullet was painted.
                        const SfxBoolItem& rBulletState = mpEditEngine->GetParaAttrib(nParaPortion, EE_PARA_BULLETSTATE);
                        bPaintBullet = rBulletState.GetValue();
                    }
 
 
                    // Over the Portions of the line...
 
                    bool bParsingFields = false;
                    std::vector< sal_Int32 >::iterator itSubLines;
                    tools::Long nFirstPortionXOffset = 0;
 
                    for ( sal_Int32 nPortion = pLine->GetStartPortion(); nPortion <= pLine->GetEndPortion(); nPortion++ )
                    {
                        DBG_ASSERT(rParaPortion.GetTextPortions().Count(), "Line without Textportion in Paint!");
                        const TextPortion& rTextPortion = rParaPortion.GetTextPortions()[nPortion];
 
                        const tools::Long nPortionXOffset = GetPortionXOffset(rParaPortion, *pLine, nPortion);
                        setXDirectionAwareFrom(aTmpPos, aStartPos);
 
                        if (nPortion == pLine->GetStartPortion())
                            nFirstPortionXOffset = nPortionXOffset;
 
                        if (!bParsingFields)
                            adjustXDirectionAware(aTmpPos, nPortionXOffset);
                        else
                            adjustXDirectionAware(aTmpPos, nFirstPortionXOffset);
 
                        if (isXOverflowDirectionAware(aTmpPos, aClipRect))
                            break; // No further output in line necessary
 
                        switch ( rTextPortion.GetKind() )
                        {
                            case PortionKind::TEXT:
                            case PortionKind::FIELD:
                            case PortionKind::HYPHENATOR:
                            {
                                SeekCursor(rParaPortion.GetNode(), nIndex + 1, aTmpFont, &rOutDev);
 
                                bool bDrawFrame = false;
 
                                if ( ( rTextPortion.GetKind() == PortionKind::FIELD ) && !aTmpFont.IsTransparent() &&
                                     ( GetBackgroundColor() != COL_AUTO ) && GetBackgroundColor().IsDark() &&
                                     ( IsAutoColorEnabled() && ( rOutDev.GetOutDevType() != OUTDEV_PRINTER ) ) )
                                {
                                    aTmpFont.SetTransparent( true );
                                    rOutDev.SetFillColor();
                                    rOutDev.SetLineColor( GetAutoColor() );
                                    bDrawFrame = true;
                                }
 
#if OSL_DEBUG_LEVEL > 2
                                // Do we really need this if statement?
                                if ( rTextPortion.GetKind() == PortionKind::HYPHENATOR )
                                {
                                    aTmpFont.SetFillColor( COL_LIGHTGRAY );
                                    aTmpFont.SetTransparent( sal_False );
                                }
                                if ( rTextPortion.IsRightToLeft()  )
                                {
                                    aTmpFont.SetFillColor( COL_LIGHTGRAY );
                                    aTmpFont.SetTransparent( sal_False );
                                }
                                else if (GetI18NScriptType(EditPaM(rParaPortion.GetNode(), nIndex + 1)) == i18n::ScriptType::COMPLEX)
                                {
                                    aTmpFont.SetFillColor( COL_LIGHTCYAN );
                                    aTmpFont.SetTransparent( sal_False );
                                }
#endif
                                aTmpFont.SetPhysFont(rOutDev);
 
                                // #114278# Saving both layout mode and language (since I'm
                                // potentially changing both)
                                rOutDev.Push( vcl::PushFlags::TEXTLAYOUTMODE|vcl::PushFlags::TEXTLANGUAGE );
                                ImplInitLayoutMode(rOutDev, nParaPortion, nIndex);
                                ImplInitDigitMode(rOutDev, aTmpFont.GetLanguage());
 
                                OUString aText;
                                sal_Int32 nTextStart = 0;
                                sal_Int32 nTextLen = 0;
                                KernArraySpan pDXArray;
                                std::span<const sal_Bool> pKashidaArray;
                                KernArray aTmpDXArray;
 
                                if ( rTextPortion.GetKind() == PortionKind::TEXT )
                                {
                                    aText = rParaPortion.GetNode()->GetString();
                                    nTextStart = nIndex;
                                    nTextLen = rTextPortion.GetLen();
                                    pDXArray = KernArraySpan(pLine->GetCharPosArray().data() + (nIndex - pLine->GetStart()),
                                                    pLine->GetCharPosArray().size() - (nIndex - pLine->GetStart()));
 
                                    if (!pLine->GetKashidaArray().empty())
                                    {
                                        pKashidaArray = std::span(pLine->GetKashidaArray().data() + (nIndex - pLine->GetStart()),
                                                    pLine->GetKashidaArray().size() - (nIndex - pLine->GetStart()));
                                    }
 
                                    // Paint control characters (#i55716#)
                                    /* XXX: Given that there's special handling
                                     * only for some specific characters
                                     * (U+200B ZERO WIDTH SPACE and U+2060 WORD
                                     * JOINER) it is assumed to be not relevant
                                     * for MarkUrlFields(). */
                                    if (maStatus.MarkNonUrlFields())
                                    {
                                        sal_Int32 nTmpIdx;
                                        const sal_Int32 nTmpEnd = nTextStart + rTextPortion.GetLen();
 
                                        for ( nTmpIdx = nTextStart; nTmpIdx <= nTmpEnd ; ++nTmpIdx )
                                        {
                                            const sal_Unicode cChar = ( nTmpIdx != aText.getLength() && ( nTmpIdx != nTextStart || 0 == nTextStart ) ) ?
                                                                        aText[nTmpIdx] :
                                                                        0;
 
                                            if ( 0x200B == cChar || 0x2060 == cChar )
                                            {
                                                tools::Long nHalfBlankWidth = aTmpFont.QuickGetTextSize( &rOutDev,
                                                    u" "_ustr, 0, 1, nullptr ).Width() / 2;
 
                                                const tools::Long nAdvanceX = ( nTmpIdx == nTmpEnd ?
                                                                         rTextPortion.GetSize().Width() :
                                                                         pDXArray[ nTmpIdx - nTextStart ] ) - nHalfBlankWidth;
                                                const tools::Long nAdvanceY = -pLine->GetMaxAscent();
 
                                                Point aTopLeftRectPos( aTmpPos );
                                                adjustXDirectionAware(aTopLeftRectPos, nAdvanceX);
                                                adjustYDirectionAware(aTopLeftRectPos, nAdvanceY);
 
                                                Point aBottomRightRectPos( aTopLeftRectPos );
                                                adjustXDirectionAware(aBottomRightRectPos, 2 * nHalfBlankWidth);
                                                adjustYDirectionAware(aBottomRightRectPos, pLine->GetHeight());
 
                                                rOutDev.Push( vcl::PushFlags::FILLCOLOR );
                                                rOutDev.Push( vcl::PushFlags::LINECOLOR );
                                                rOutDev.SetFillColor( COL_LIGHTGRAY );
                                                rOutDev.SetLineColor( COL_LIGHTGRAY );
 
                                                const tools::Rectangle aBackRect( aTopLeftRectPos, aBottomRightRectPos );
                                                rOutDev.DrawRect( aBackRect );
 
                                                rOutDev.Pop();
                                                rOutDev.Pop();
 
                                                if ( 0x200B == cChar )
                                                {
                                                    const OUString aSlash( '/' );
                                                    const short nOldEscapement = aTmpFont.GetEscapement();
                                                    const sal_uInt8 nOldPropr = aTmpFont.GetPropr();
 
                                                    aTmpFont.SetEscapement( -20 );
                                                    aTmpFont.SetPropr( 25 );
                                                    aTmpFont.SetPhysFont(rOutDev);
 
                                                    const Size aSlashSize = aTmpFont.QuickGetTextSize( &rOutDev,
                                                        aSlash, 0, 1, nullptr );
                                                    Point aSlashPos( aTmpPos );
                                                    const tools::Long nAddX = nHalfBlankWidth - aSlashSize.Width() / 2;
                                                    setXDirectionAwareFrom(aSlashPos, aTopLeftRectPos);
                                                    adjustXDirectionAware(aSlashPos, nAddX);
 
                                                    aTmpFont.QuickDrawText( &rOutDev, aSlashPos, aSlash, 0, 1, {} );
 
                                                    aTmpFont.SetEscapement( nOldEscapement );
                                                    aTmpFont.SetPropr( nOldPropr );
                                                    aTmpFont.SetPhysFont(rOutDev);
                                                }
                                            }
                                        }
                                    }
                                }
                                else if ( rTextPortion.GetKind() == PortionKind::FIELD )
                                {
                                    const EditCharAttrib* pAttr = rParaPortion.GetNode()->GetCharAttribs().FindFeature(nIndex);
                                    assert( pAttr && "Field not found");
                                    DBG_ASSERT( dynamic_cast< const SvxFieldItem* >( pAttr->GetItem() ) !=  nullptr, "Field of the wrong type! ");
                                    aText = static_cast<const EditCharAttribField*>(pAttr)->GetFieldValue();
                                    nTextStart = 0;
                                    nTextLen = aText.getLength();
                                    ExtraPortionInfo *pExtraInfo = rTextPortion.GetExtraInfos();
                                    //For historical reasons URLs was drawn as single line in edit mode
                                    //but now we changed it, so it wraps similar as simple text.
                                    //It is not perfect, it still use lineBreaksList, so it won’t seek
                                    //word ends to wrap text there, but it would be difficult to change
                                    //this due to needed adaptations in EditEngine
                                    if (bStripOnly && !bParsingFields && pExtraInfo && !pExtraInfo->lineBreaksList.empty())
                                    {
                                        bParsingFields = true;
                                        itSubLines = pExtraInfo->lineBreaksList.begin();
                                    }
 
                                    if( bParsingFields )
                                    {
                                        if( itSubLines != pExtraInfo->lineBreaksList.begin() )
                                        {
                                            // only use GetMaxAscent(), pLine->GetHeight() will not
                                            // proceed as needed (see PortionKind::TEXT above and nAdvanceY)
                                            // what will lead to a compressed look with multiple lines
                                            const sal_uInt16 nMaxAscent(pLine->GetMaxAscent());
 
                                            aTmpPos += MoveToNextLine(aStartPos, nMaxAscent,
                                                                      nColumn, aOrigin);
                                            adjustXDirectionAware(aTmpPos, -pLine->GetNextLinePosXDiff());
                                        }
                                        std::vector< sal_Int32 >::iterator curIt = itSubLines;
                                        ++itSubLines;
                                        if( itSubLines != pExtraInfo->lineBreaksList.end() )
                                        {
                                            nTextStart = *curIt;
                                            nTextLen = *itSubLines - nTextStart;
                                        }
                                        else
                                        {
                                            nTextStart = *curIt;
                                            nTextLen = nTextLen - nTextStart;
                                            bParsingFields = false;
 
                                            if (nLine + 1 < nLines)
                                            {
                                                // tdf#148966 don't paint the line break following a
                                                // multiline field based on a compat flag
                                                OutlinerEditEng* pOutlEditEng{ dynamic_cast<OutlinerEditEng*>(mpEditEngine)};
                                                int nStartNextLine = rParaPortion.GetLines()[nLine + 1].GetStartPortion();
                                                const TextPortion& rNextTextPortion = rParaPortion.GetTextPortions()[nStartNextLine];
                                                if (pOutlEditEng
                                                    && pOutlEditEng->GetCompatFlag(SdrCompatibilityFlag::IgnoreBreakAfterMultilineField)
                                                           .value_or(false))
                                                {
                                                    if (rNextTextPortion.GetKind() == PortionKind::LINEBREAK)
                                                        ++nLine; //ignore the following linebreak
                                                }
                                                else if (mpActiveView && rNextTextPortion.GetKind() == PortionKind::LINEBREAK)
                                                {
                                                    // if we are at edit mode, the compat flag does not work
                                                    // here we choose to work if compat flag is true,
                                                    // this is better for newer documents
                                                    nLine++;
                                                }
                                                if (rNextTextPortion.GetKind() != PortionKind::LINEBREAK)
                                                {
                                                    nLine++;
                                                    pLine = &GetParaPortions().getRef(nParaPortion).GetLines()[nLine];
                                                }
                                            }
                                        }
                                    }
 
                                    aTmpFont.SetPhysFont(*GetRefDevice());
                                    aTmpFont.QuickGetTextSize( GetRefDevice(), aText, nTextStart, nTextLen,
                                        &aTmpDXArray );
                                    pDXArray = KernArraySpan(aTmpDXArray);
 
                                    // add a meta file comment if we record to a metafile
                                    if( bMetafileValid )
                                    {
                                        const SvxFieldItem* pFieldItem = dynamic_cast<const SvxFieldItem*>(pAttr->GetItem());
                                        if( pFieldItem )
                                        {
                                            const SvxFieldData* pFieldData = pFieldItem->GetField();
                                            if( pFieldData )
                                                pMtf->AddAction( pFieldData->createBeginComment() );
                                        }
                                    }
 
                                }
                                else if ( rTextPortion.GetKind() == PortionKind::HYPHENATOR )
                                {
                                    if ( rTextPortion.GetExtraValue() )
                                        aText = OUString(rTextPortion.GetExtraValue());
                                    aText += CH_HYPH;
                                    nTextStart = 0;
                                    nTextLen = aText.getLength();
 
                                    // crash when accessing 0 pointer in pDXArray
                                    aTmpFont.SetPhysFont(*GetRefDevice());
                                    aTmpFont.QuickGetTextSize( GetRefDevice(), aText, 0, aText.getLength(),
                                        &aTmpDXArray );
                                    pDXArray = aTmpDXArray;
                                }
 
                                tools::Long nTxtWidth = rTextPortion.GetSize().Width();
 
                                Point aOutPos( aTmpPos );
                                Point aRedLineTmpPos = aTmpPos;
                                // In RTL portions spell markup pos should be at the start of the
                                // first chara as well. That is on the right end of the portion
                                if (rTextPortion.IsRightToLeft())
                                    aRedLineTmpPos.AdjustX(rTextPortion.GetSize().Width() );
 
                                if ( bStripOnly )
                                {
                                    EEngineData::WrongSpellVector aWrongSpellVector;
 
                                    if(GetStatus().DoOnlineSpelling() && rTextPortion.GetLen())
                                    {
                                        WrongList* pWrongs = rParaPortion.GetNode()->GetWrongList();
 
                                        if(pWrongs && !pWrongs->empty())
                                        {
                                            size_t nStart = nIndex, nEnd = 0;
                                            bool bWrong = pWrongs->NextWrong(nStart, nEnd);
                                            const size_t nMaxEnd(nIndex + rTextPortion.GetLen());
 
                                            while(bWrong)
                                            {
                                                if(nStart >= nMaxEnd)
                                                {
                                                    break;
                                                }
 
                                                if(nStart < o3tl::make_unsigned(nIndex))
                                                {
                                                    nStart = nIndex;
                                                }
 
                                                if(nEnd > nMaxEnd)
                                                {
                                                    nEnd = nMaxEnd;
                                                }
 
                                                // add to vector
                                                aWrongSpellVector.emplace_back(nStart, nEnd);
 
                                                // goto next index
                                                nStart = nEnd + 1;
 
                                                if(nEnd < nMaxEnd)
                                                {
                                                    bWrong = pWrongs->NextWrong(nStart, nEnd);
                                                }
                                                else
                                                {
                                                    bWrong = false;
                                                }
                                            }
                                        }
                                    }
 
                                    const SvxFieldData* pFieldData = nullptr;
 
                                    if(PortionKind::FIELD == rTextPortion.GetKind())
                                    {
                                        const EditCharAttrib* pAttr = rParaPortion.GetNode()->GetCharAttribs().FindFeature(nIndex);
                                        const SvxFieldItem* pFieldItem = dynamic_cast<const SvxFieldItem*>(pAttr->GetItem());
 
                                        if(pFieldItem)
                                        {
                                            pFieldData = pFieldItem->GetField();
                                        }
                                    }
 
                                    // support for EOC, EOW, EOS TEXT comments. To support that,
                                    // the locale is needed. With the locale and a XBreakIterator it is
                                    // possible to re-create the text marking info on primitive level
                                    const lang::Locale aLocale(GetLocale(EditPaM(rParaPortion.GetNode(), nIndex + 1)));
 
                                    // create EOL and EOP bools
                                    const bool bEndOfLine(nPortion == pLine->GetEndPortion());
                                    const bool bEndOfParagraph(bEndOfLine && nLine + 1 == nLines);
 
                                    // get Overline color (from ((const SvxOverlineItem*)GetItem())->GetColor() in
                                    // consequence, but also already set at rOutDev)
                                    const Color aOverlineColor(rOutDev.GetOverlineColor());
 
                                    // get TextLine color (from ((const SvxUnderlineItem*)GetItem())->GetColor() in
                                    // consequence, but also already set at rOutDev)
                                    const Color aTextLineColor(rOutDev.GetTextLineColor());
 
                                    // Unicode code points conversion according to ctl text numeral setting
                                    aText = convertDigits(aText, nTextStart, nTextLen,
                                        ImplCalcDigitLang(aTmpFont.GetLanguage()));
 
                                    // StripPortions() data callback
                                    GetEditEnginePtr()->DrawingText( aOutPos, aText, nTextStart, nTextLen, pDXArray, pKashidaArray,
                                        aTmpFont, nParaPortion, rTextPortion.GetRightToLeftLevel(),
                                        !aWrongSpellVector.empty() ? &aWrongSpellVector : nullptr,
                                        pFieldData,
                                        bEndOfLine, bEndOfParagraph, // support for EOL/EOP TEXT comments
                                        &aLocale,
                                        aOverlineColor,
                                        aTextLineColor);
 
                                    // #108052# remember that EOP is written already for this ParaPortion
                                    if(bEndOfParagraph)
                                    {
                                        bEndOfParagraphWritten = true;
                                    }
                                }
                                else
                                {
                                    short nEsc = aTmpFont.GetEscapement();
                                    if ( nOrientation )
                                    {
                                        // In case of high/low do it yourself:
                                        if ( aTmpFont.GetEscapement() )
                                        {
                                            tools::Long nDiff = aTmpFont.GetFontSize().Height() * aTmpFont.GetEscapement() / 100L;
                                            adjustYDirectionAware(aOutPos, -nDiff);
                                            aRedLineTmpPos = aOutPos;
                                            aTmpFont.SetEscapement( 0 );
                                        }
 
                                        aOrigin.RotateAround(aOutPos, nOrientation);
                                        aTmpFont.SetOrientation( aTmpFont.GetOrientation()+nOrientation );
                                        aTmpFont.SetPhysFont(rOutDev);
 
                                    }
 
                                    // Take only what begins in the visible range:
                                    // Important, because of a bug in some graphic cards
                                    // when transparent font, output when negative
                                    if ( nOrientation || ( !IsEffectivelyVertical() && ( ( aTmpPos.X() + nTxtWidth ) >= nFirstVisXPos ) )
                                            || ( IsEffectivelyVertical() && ( ( aTmpPos.Y() + nTxtWidth ) >= nFirstVisYPos ) ) )
                                    {
                                        if ( nEsc && ( aTmpFont.GetUnderline() != LINESTYLE_NONE ) )
                                        {
                                            // Paint the high/low without underline,
                                            // Display the Underline on the
                                            // base line of the original font height...
                                            // But only if there was something underlined before!
                                            bool bSpecialUnderline = false;
                                            EditCharAttrib* pPrev = rParaPortion.GetNode()->GetCharAttribs().FindAttrib( EE_CHAR_ESCAPEMENT, nIndex );
                                            if ( pPrev )
                                            {
                                                SvxFont aDummy;
                                                // Underscore in front?
                                                if ( pPrev->GetStart() )
                                                {
                                                    SeekCursor( rParaPortion.GetNode(), pPrev->GetStart(), aDummy );
                                                    if ( aDummy.GetUnderline() != LINESTYLE_NONE )
                                                        bSpecialUnderline = true;
                                                }
                                                if ( !bSpecialUnderline && ( pPrev->GetEnd() < rParaPortion.GetNode()->Len() ) )
                                                {
                                                    SeekCursor( rParaPortion.GetNode(), pPrev->GetEnd()+1, aDummy );
                                                    if ( aDummy.GetUnderline() != LINESTYLE_NONE )
                                                        bSpecialUnderline = true;
                                                }
                                            }
                                            if ( bSpecialUnderline )
                                            {
                                                Size aSz = aTmpFont.GetPhysTxtSize( &rOutDev, aText, nTextStart, nTextLen );
                                                sal_uInt8 nProp = aTmpFont.GetPropr();
                                                aTmpFont.SetEscapement( 0 );
                                                aTmpFont.SetPropr( 100 );
                                                aTmpFont.SetPhysFont(rOutDev);
                                                OUStringBuffer aBlanks(nTextLen);
                                                comphelper::string::padToLength( aBlanks, nTextLen, ' ' );
                                                Point aUnderlinePos( aOutPos );
                                                if ( nOrientation )
                                                {
                                                    aUnderlinePos = aTmpPos;
                                                    aOrigin.RotateAround(aUnderlinePos, nOrientation);
                                                }
                                                rOutDev.DrawStretchText( aUnderlinePos, aSz.Width(), aBlanks.makeStringAndClear(), 0, nTextLen );
 
                                                aTmpFont.SetUnderline( LINESTYLE_NONE );
                                                if ( !nOrientation )
                                                    aTmpFont.SetEscapement( nEsc );
                                                aTmpFont.SetPropr( nProp );
                                                aTmpFont.SetPhysFont(rOutDev);
                                            }
                                        }
                                        Point aRealOutPos( aOutPos );
                                        if ( ( rTextPortion.GetKind() == PortionKind::TEXT )
                                               && rTextPortion.GetExtraInfos() && rTextPortion.GetExtraInfos()->bCompressed
                                               && rTextPortion.GetExtraInfos()->bFirstCharIsRightPunktuation )
                                        {
                                            aRealOutPos.AdjustX(rTextPortion.GetExtraInfos()->nPortionOffsetX );
                                        }
 
                                        // RTL portions with (#i37132#)
                                        // compressed blank should not paint this blank:
                                        if ( rTextPortion.IsRightToLeft() && nTextLen >= 2 &&
                                             pDXArray[ nTextLen - 1 ] ==
                                             pDXArray[ nTextLen - 2 ] &&
                                             ' ' == aText[nTextStart + nTextLen - 1] )
                                            --nTextLen;
 
                                        // PDF export:
                                        const SvxFieldData* pFieldData = nullptr;
                                        if (pPDFExtOutDevData)
                                        {
                                            if (rTextPortion.GetKind() == PortionKind::FIELD)
                                            {
                                                const EditCharAttrib* pAttr = rParaPortion.GetNode()->GetCharAttribs().FindFeature(nIndex);
                                                const SvxFieldItem* pFieldItem = dynamic_cast<const SvxFieldItem*>(pAttr->GetItem());
                                                if (pFieldItem)
                                                {
                                                    pFieldData = pFieldItem->GetField();
                                                    auto pUrlField = dynamic_cast<const SvxURLField*>(pFieldData);
                                                    if (pUrlField)
                                                        if (pPDFExtOutDevData->GetIsExportTaggedPDF())
                                                            pPDFExtOutDevData->WrapBeginStructureElement(vcl::PDFWriter::Link, u"Link"_ustr);
                                                }
                                            }
                                        }
 
                                        // output directly
                                        aTmpFont.QuickDrawText( &rOutDev, aRealOutPos, aText, nTextStart, nTextLen, pDXArray, pKashidaArray );
 
                                        if ( bDrawFrame )
                                        {
                                            Point aTopLeft( aTmpPos );
                                            aTopLeft.AdjustY( -(pLine->GetMaxAscent()) );
                                            if ( nOrientation )
                                                aOrigin.RotateAround(aTopLeft, nOrientation);
                                            tools::Rectangle aRect( aTopLeft, rTextPortion.GetSize() );
                                            rOutDev.DrawRect( aRect );
                                        }
 
                                        // PDF export:
                                        if (pPDFExtOutDevData)
                                        {
                                            if (auto pUrlField = dynamic_cast<const SvxURLField*>(pFieldData))
                                            {
                                                Point aTopLeft(aTmpPos);
                                                aTopLeft.AdjustY(-(pLine->GetMaxAscent()));
 
                                                tools::Rectangle aRect(aTopLeft, rTextPortion.GetSize());
                                                vcl::PDFExtOutDevBookmarkEntry aBookmark;
                                                aBookmark.nLinkId = pPDFExtOutDevData->CreateLink(aRect, pUrlField->GetRepresentation());
                                                aBookmark.aBookmark = pUrlField->GetURL();
                                                std::vector< vcl::PDFExtOutDevBookmarkEntry >& rBookmarks = pPDFExtOutDevData->GetBookmarks();
                                                rBookmarks.push_back(aBookmark);
 
                                                if (pPDFExtOutDevData->GetIsExportTaggedPDF())
                                                {
                                                    pPDFExtOutDevData->SetStructureAttributeNumerical(vcl::PDFWriter::LinkAnnotation, aBookmark.nLinkId);
                                                    pPDFExtOutDevData->EndStructureElement();
                                                }
                                            }
                                        }
                                    }
 
                                    const WrongList* const pWrongList = rParaPortion.GetNode()->GetWrongList();
                                    if ( GetStatus().DoOnlineSpelling() && pWrongList && !pWrongList->empty() && rTextPortion.GetLen() )
                                    {
                                        {//#105750# adjust LinePos for superscript or subscript text
                                            short _nEsc = aTmpFont.GetEscapement();
                                            if( _nEsc )
                                            {
                                                tools::Long nShift = (_nEsc * aTmpFont.GetFontSize().Height()) / 100L;
                                                adjustYDirectionAware(aRedLineTmpPos, -nShift);
                                            }
                                        }
                                        Color aOldColor( rOutDev.GetLineColor() );
                                        rOutDev.SetLineColor( GetColorConfig().GetColorValue( svtools::SPELL ).nColor );
                                        lcl_DrawRedLines(rOutDev, aTmpFont.GetFontSize().Height(), aRedLineTmpPos,
                                                         static_cast<size_t>(nIndex), static_cast<size_t>(nIndex) + rTextPortion.GetLen(),
                                                         pDXArray, rParaPortion.GetNode()->GetWrongList(), nOrientation,
                                                         aOrigin, IsEffectivelyVertical(), rTextPortion.IsRightToLeft());
                                        rOutDev.SetLineColor( aOldColor );
                                    }
                                }
 
                                rOutDev.Pop();
 
                                if ( rTextPortion.GetKind() == PortionKind::FIELD )
                                {
                                    // add a meta file comment if we record to a metafile
                                    if( bMetafileValid )
                                    {
                                        const EditCharAttrib* pAttr = rParaPortion.GetNode()->GetCharAttribs().FindFeature(nIndex);
                                        assert( pAttr && "Field not found" );
 
                                        const SvxFieldItem* pFieldItem = dynamic_cast<const SvxFieldItem*>(pAttr->GetItem());
                                        DBG_ASSERT( pFieldItem !=  nullptr, "Wrong type of field!" );
 
                                        if( pFieldItem )
                                        {
                                            const SvxFieldData* pFieldData = pFieldItem->GetField();
                                            if( pFieldData )
                                                pMtf->AddAction( SvxFieldData::createEndComment() );
                                        }
                                    }
 
                                }
 
                            }
                            break;
                            case PortionKind::TAB:
                            {
                                if ( rTextPortion.GetExtraValue() && ( rTextPortion.GetExtraValue() != ' ' ) )
                                {
                                    // calculate expanded text
                                    SeekCursor(rParaPortion.GetNode(), nIndex+1, aTmpFont, &rOutDev);
                                    aTmpFont.SetTransparent( false );
                                    aTmpFont.SetEscapement( 0 );
                                    aTmpFont.SetPhysFont(rOutDev);
                                    tools::Long nCharWidth = aTmpFont.QuickGetTextSize( &rOutDev,
                                        OUString(rTextPortion.GetExtraValue()), 0, 1, {} ).Width();
                                    sal_Int32 nChars = 2;
                                    if( nCharWidth )
                                        nChars = rTextPortion.GetSize().Width() / nCharWidth;
                                    if ( nChars < 2 )
                                        nChars = 2; // is compressed by DrawStretchText.
                                    else if ( nChars == 2 )
                                        nChars = 3; // looks better
 
                                    // create expanded text
                                    OUStringBuffer aBuf(nChars);
                                    comphelper::string::padToLength(aBuf, nChars, rTextPortion.GetExtraValue());
                                    OUString aText(aBuf.makeStringAndClear());
 
                                    if ( bStripOnly )
                                    {
                                        // create EOL and EOP bools
                                        const bool bEndOfLine(nPortion == pLine->GetEndPortion());
                                        const bool bEndOfParagraph(bEndOfLine && nLine + 1 == nLines);
 
                                        const Color aOverlineColor(rOutDev.GetOverlineColor());
                                        const Color aTextLineColor(rOutDev.GetTextLineColor());
 
                                        // StripPortions() data callback
                                        GetEditEnginePtr()->DrawingText(
                                            aTmpPos, aText, 0, aText.getLength(), {}, {},
                                            aTmpFont, nParaPortion, 0,
                                            nullptr,
                                            nullptr,
                                            bEndOfLine, bEndOfParagraph,
                                            nullptr,
                                            aOverlineColor,
                                            aTextLineColor);
                                    }
                                    else
                                    {
                                        // direct paint needed
                                        aTmpFont.QuickDrawText( &rOutDev, aTmpPos, aText, 0, aText.getLength(), {} );
                                        rOutDev.DrawStretchText( aTmpPos, rTextPortion.GetSize().Width(), aText );
                                    }
                                }
                                else if ( bStripOnly )
                                {
                                    // #i108052# When stripping, a callback for _empty_ paragraphs is also needed.
                                    // This was optimized away (by not rendering the space-only tab portion), so do
                                    // it manually here.
                                    const bool bEndOfLine(nPortion == pLine->GetEndPortion());
                                    const bool bEndOfParagraph(bEndOfLine && nLine + 1 == nLines);
 
                                    const Color aOverlineColor(rOutDev.GetOverlineColor());
                                    const Color aTextLineColor(rOutDev.GetTextLineColor());
 
                                    GetEditEnginePtr()->DrawingText(
                                        aTmpPos, OUString(), 0, 0, {}, {},
                                        aTmpFont, nParaPortion, 0,
                                        nullptr,
                                        nullptr,
                                        bEndOfLine, bEndOfParagraph,
                                        nullptr,
                                        aOverlineColor,
                                        aTextLineColor);
                                }
                            }
                            break;
                            case PortionKind::LINEBREAK: break;
                        }
                        if( bParsingFields )
                            nPortion--;
                        else
                            nIndex = nIndex + rTextPortion.GetLen();
 
                    }
                }
 
                if ((nLine != nLastLine ) && !maStatus.IsOutliner())
                {
                    adjustYDirectionAware(aStartPos, nSBL);
                }
 
                // no more visible actions?
                if (getYOverflowDirectionAware(aStartPos, aClipRect))
                    break;
            }
 
            if (!maStatus.IsOutliner())
            {
                const SvxULSpaceItem& rULItem = rParaPortion.GetNode()->GetContentAttribs().GetItem( EE_PARA_ULSPACE );
                tools::Long nUL = scaleYSpacingValue(rULItem.GetLower());
                adjustYDirectionAware(aStartPos, nUL);
            }
 
            // #108052# Safer way for #i108052# and #i118881#: If for the current ParaPortion
            // EOP is not written, do it now. This will be safer than before. It has shown
            // that the reason for #i108052# was fixed/removed again, so this is a try to fix
            // the number of paragraphs (and counting empty ones) now independent from the
            // changes in EditEngine behaviour.
            if(!bEndOfParagraphWritten && !bPaintBullet && bStripOnly)
            {
                const Color aOverlineColor(rOutDev.GetOverlineColor());
                const Color aTextLineColor(rOutDev.GetTextLineColor());
 
                GetEditEnginePtr()->DrawingText(
                    aTmpPos, OUString(), 0, 0, {}, {},
                    aTmpFont, nParaPortion, 0,
                    nullptr,
                    nullptr,
                    false, true, // support for EOL/EOP TEXT comments
                    nullptr,
                    aOverlineColor,
                    aTextLineColor);
            }
        }
        else
        {
            adjustYDirectionAware(aStartPos, nParaHeight);
        }
 
        if ( pPDFExtOutDevData )
            pPDFExtOutDevData->EndStructureElement();
 
        // no more visible actions?
        if (getYOverflowDirectionAware(aStartPos, aClipRect))
            break;
    }
}
 
void ImpEditEngine::Paint( ImpEditView* pView, const tools::Rectangle& rRect, OutputDevice* pTargetDevice )
{
    if ( !IsUpdateLayout() || IsInUndo() )
        return;
 
    assert( pView && "No View - No Paint!" );
 
    // Intersection of paint area and output area.
    tools::Rectangle aClipRect( pView->GetOutputArea() );
    aClipRect.Intersection( rRect );
 
    OutputDevice& rTarget = pTargetDevice ? *pTargetDevice : *pView->GetWindow()->GetOutDev();
 
    Point aStartPos;
    if ( !IsEffectivelyVertical() )
        aStartPos = pView->GetOutputArea().TopLeft();
    else
    {
        if( IsTopToBottom() )
            aStartPos = pView->GetOutputArea().TopRight();
        else
            aStartPos = pView->GetOutputArea().BottomLeft();
    }
    adjustXDirectionAware(aStartPos, -(pView->GetVisDocLeft()));
    adjustYDirectionAware(aStartPos, -(pView->GetVisDocTop()));
 
    // If Doc-width < Output Area,Width and not wrapped fields,
    // the fields usually protrude if > line.
    // (Not at the top, since there the Doc-width from formatting is already
    // there)
    if ( !IsEffectivelyVertical() && ( pView->GetOutputArea().GetWidth() > GetPaperSize().Width() ) )
    {
        tools::Long nMaxX = pView->GetOutputArea().Left() + GetPaperSize().Width();
        if ( aClipRect.Left() > nMaxX )
            return;
        if ( aClipRect.Right() > nMaxX )
            aClipRect.SetRight( nMaxX );
    }
 
    bool bClipRegion = rTarget.IsClipRegion();
    vcl::Region aOldRegion = rTarget.GetClipRegion();
    rTarget.IntersectClipRegion( aClipRect );
 
    Paint(rTarget, aClipRect, aStartPos);
 
    if ( bClipRegion )
        rTarget.SetClipRegion( aOldRegion );
    else
        rTarget.SetClipRegion();
 
    pView->DrawSelectionXOR(pView->GetEditSelection(), nullptr, &rTarget);
}
 
void ImpEditEngine::InsertContent(std::unique_ptr<ContentNode> pNode, sal_Int32 nPos )
{
    DBG_ASSERT( pNode, "NULL-Pointer in InsertContent! " );
    DBG_ASSERT( IsInUndo(), "InsertContent only for Undo()!" );
 
    GetParaPortions().Insert(nPos, std::make_unique<ParaPortion>(pNode.get()));
    maEditDoc.Insert(nPos, std::move(pNode));
 
    if ( IsCallParaInsertedOrDeleted() )
        GetEditEnginePtr()->ParagraphInserted( nPos );
}
 
EditPaM ImpEditEngine::SplitContent( sal_Int32 nNode, sal_Int32 nSepPos )
{
    ContentNode* pNode = maEditDoc.GetObject( nNode );
    assert(pNode && "Invalid Node in SplitContent");
    DBG_ASSERT( IsInUndo(), "SplitContent only for Undo()!" );
    DBG_ASSERT( nSepPos <= pNode->Len(), "Index out of range: SplitContent" );
    EditPaM aPaM( pNode, nSepPos );
    return ImpInsertParaBreak( aPaM );
}
 
EditPaM ImpEditEngine::ConnectContents( sal_Int32 nLeftNode, bool bBackward )
{
    ContentNode* pLeftNode = maEditDoc.GetObject( nLeftNode );
    ContentNode* pRightNode = maEditDoc.GetObject( nLeftNode+1 );
    DBG_ASSERT( pLeftNode, "Invalid left node in ConnectContents ");
    DBG_ASSERT( pRightNode, "Invalid right node in ConnectContents ");
    return ImpConnectParagraphs( pLeftNode, pRightNode, bBackward );
}
 
bool ImpEditEngine::SetUpdateLayout( bool bUp, EditView* pCurView, bool bForceUpdate )
{
    const bool bPrevUpdateLayout = mbUpdateLayout;
    const bool mbChanged = (mbUpdateLayout != bUp);
 
    // When switching from true to false, all selections were visible,
    // => paint over
    // the other hand, were all invisible => paint
    // If !bFormatted, e.g. after SetText, then if UpdateMode=true
    // formatting is not needed immediately, probably because more text is coming.
    // At latest it is formatted at a Paint/CalcTextWidth.
    mbUpdateLayout = bUp;
    if ( mbUpdateLayout && ( mbChanged || bForceUpdate ) )
        FormatAndLayout( pCurView );
    return bPrevUpdateLayout;
}
 
void ImpEditEngine::ShowParagraph( sal_Int32 nParagraph, bool bShow )
{
    ParaPortion* pPPortion = GetParaPortions().SafeGetObject( nParagraph );
    DBG_ASSERT( pPPortion, "ShowParagraph: Paragraph does not exist! ");
    if ( !(pPPortion && ( pPPortion->IsVisible() != bShow )) )
        return;
 
    pPPortion->SetVisible( bShow );
 
    if ( !bShow )
    {
        // Mark as deleted, so that no selection will end or begin at
        // this paragraph...
        maDeletedNodes.push_back(std::make_unique<DeletedNodeInfo>( pPPortion->GetNode(), nParagraph ));
        UpdateSelections();
        // The region below will not be invalidated if UpdateMode = sal_False!
        // If anyway, then save as sal_False before SetVisible !
    }
 
    if (bShow && (pPPortion->IsInvalid() || !pPPortion->GetHeight()))
    {
        if ( !GetTextRanger() )
        {
            if ( pPPortion->IsInvalid() )
            {
                CreateLines( nParagraph, 0 );   // 0: No TextRanger
            }
            else
            {
                CalcHeight(*pPPortion);
            }
            mnCurTextHeight += pPPortion->GetHeight();
        }
        else
        {
            mnCurTextHeight = 0x7fffffff;
        }
    }
 
    pPPortion->SetMustRepaint( true );
    if ( IsUpdateLayout() && !IsInUndo() && !GetTextRanger() )
    {
        maInvalidRect = tools::Rectangle(    Point( 0, GetParaPortions().GetYOffset( pPPortion ) ),
                                    Point( GetPaperSize().Width(), mnCurTextHeight ) );
        UpdateViews( GetActiveView() );
    }
}
 
EditSelection ImpEditEngine::MoveParagraphs( Range aOldPositions, sal_Int32 nNewPos, EditView* pCurView )
{
    DBG_ASSERT( GetParaPortions().Count() != 0, "No paragraphs found: MoveParagraphs" );
    if ( GetParaPortions().Count() == 0 )
        return EditSelection();
    aOldPositions.Normalize();
 
    EditSelection aSel( ImpMoveParagraphs( aOldPositions, nNewPos ) );
 
    if (nNewPos >= GetParaPortions().Count())
        nNewPos = GetParaPortions().lastIndex();
 
    // Where the paragraph was inserted it has to be properly redrawn:
    // Where the paragraph was removed it has to be properly redrawn:
    // ( and correspondingly in between as well...)
    if ( pCurView && IsUpdateLayout() )
    {
        // in this case one can redraw directly without invalidating the
        // Portions
        sal_Int32 nFirstPortion = std::min( static_cast<sal_Int32>(aOldPositions.Min()), nNewPos );
        sal_Int32 nLastPortion = std::max( static_cast<sal_Int32>(aOldPositions.Max()), nNewPos );
 
        ParaPortion* pUpperPortion = GetParaPortions().SafeGetObject( nFirstPortion );
        ParaPortion* pLowerPortion = GetParaPortions().SafeGetObject( nLastPortion );
        if (pUpperPortion && pLowerPortion)
        {
            maInvalidRect = tools::Rectangle();  // make empty
            maInvalidRect.SetLeft( 0 );
            maInvalidRect.SetRight(GetColumnWidth(maPaperSize));
            maInvalidRect.SetTop( GetParaPortions().GetYOffset( pUpperPortion ) );
            maInvalidRect.SetBottom( GetParaPortions().GetYOffset( pLowerPortion ) + pLowerPortion->GetHeight() );
 
            UpdateViews( pCurView );
        }
    }
    else
    {
        // redraw from the upper invalid position
        sal_Int32 nFirstInvPara = std::min( static_cast<sal_Int32>(aOldPositions.Min()), nNewPos );
        InvalidateFromParagraph( nFirstInvPara );
    }
    return aSel;
}
 
void ImpEditEngine::InvalidateFromParagraph( sal_Int32 nFirstInvPara )
{
    // The following paragraphs are not invalidated, since ResetHeight()
    // => size change => all the following are re-issued anyway.
 
    if (nFirstInvPara != 0)
    {
        ParaPortion& rPortion = GetParaPortions().getRef(nFirstInvPara - 1);
        rPortion.MarkInvalid(rPortion.GetNode()->Len(), 0);
        rPortion.ResetHeight();
    }
    else
    {
        ParaPortion& rPortion = GetParaPortions().getRef(0);
        rPortion.MarkSelectionInvalid(0);
        rPortion.ResetHeight();
    }
}
 
IMPL_LINK_NOARG(ImpEditEngine, StatusTimerHdl, Timer *, void)
{
    CallStatusHdl();
}
 
void ImpEditEngine::CallStatusHdl()
{
    if (maStatusHdlLink.IsSet() && bool(maStatus.GetStatusWord()))
    {
        // The Status has to be reset before the Call,
        // since other Flags might be set in the handler...
        EditStatus aTmpStatus( maStatus );
        maStatus.Clear();
        maStatusHdlLink.Call( aTmpStatus );
        maStatusTimer.Stop();    // If called by hand...
    }
}
 
ContentNode* ImpEditEngine::GetPrevVisNode( ContentNode const * pCurNode )
{
    const ParaPortion* pPortion = FindParaPortion( pCurNode );
    DBG_ASSERT( pPortion, "GetPrevVisibleNode: No matching portion!" );
    pPortion = GetPrevVisPortion( pPortion );
    if ( pPortion )
        return pPortion->GetNode();
    return nullptr;
}
 
ContentNode* ImpEditEngine::GetNextVisNode( ContentNode const * pCurNode )
{
    const ParaPortion* pPortion = FindParaPortion( pCurNode );
    DBG_ASSERT( pPortion, "GetNextVisibleNode: No matching portion!" );
    pPortion = GetNextVisPortion( pPortion );
    if ( pPortion )
        return pPortion->GetNode();
    return nullptr;
}
 
const ParaPortion* ImpEditEngine::GetPrevVisPortion( const ParaPortion* pCurPortion) const
{
    sal_Int32 nPara = GetParaPortions().GetPos(pCurPortion);
    DBG_ASSERT(GetParaPortions().exists(nPara) , "Portion not found: GetPrevVisPortion");
 
    const ParaPortion* pPortion = nPara ? GetParaPortions().SafeGetObject(--nPara) : nullptr;
    while (pPortion && !pPortion->IsVisible())
        pPortion = nPara ? GetParaPortions().SafeGetObject(--nPara) : nullptr;
 
    return pPortion;
}
 
const ParaPortion* ImpEditEngine::GetNextVisPortion( const ParaPortion* pCurPortion ) const
{
    sal_Int32 nPara = GetParaPortions().GetPos( pCurPortion );
    if (nPara == EE_PARA_NOT_FOUND)
    {
        SAL_WARN("editeng", "Portion not found: GetPrevVisNode" );
        return nullptr;
    }
    const ParaPortion* pPortion = GetParaPortions().SafeGetObject( ++nPara );
    while ( pPortion && !pPortion->IsVisible() )
        pPortion = GetParaPortions().SafeGetObject( ++nPara );
 
    return pPortion;
}
 
tools::Long ImpEditEngine::CalcVertLineSpacing(Point& rStartPos) const
{
    tools::Long nTotalOccupiedHeight = 0;
    sal_Int32 nTotalLineCount = 0;
    const ParaPortionList& rParaPortions = GetParaPortions();
    sal_Int32 nParaCount = rParaPortions.Count();
 
    for (sal_Int32 i = 0; i < nParaCount; ++i)
    {
        if (GetVerJustification(i) != SvxCellVerJustify::Block)
            // All paragraphs must have the block justification set.
            return 0;
 
        ParaPortion const& rPortion = rParaPortions.getRef(i);
        nTotalOccupiedHeight += rPortion.GetFirstLineOffset();
 
        const SvxLineSpacingItem& rLSItem = rPortion.GetNode()->GetContentAttribs().GetItem(EE_PARA_SBL);
        sal_uInt16 nSBL = ( rLSItem.GetInterLineSpaceRule() == SvxInterLineSpaceRule::Fix )
                            ? scaleYSpacingValue(rLSItem.GetInterLineSpace()) : 0;
 
        const SvxULSpaceItem& rULItem = rPortion.GetNode()->GetContentAttribs().GetItem(EE_PARA_ULSPACE);
        tools::Long nUL = scaleYSpacingValue(rULItem.GetLower());
 
        const EditLineList& rLines = rPortion.GetLines();
        sal_Int32 nLineCount = rLines.Count();
        nTotalLineCount += nLineCount;
        for (sal_Int32 j = 0; j < nLineCount; ++j)
        {
            const EditLine& rLine = rLines[j];
            nTotalOccupiedHeight += rLine.GetHeight();
            if (j < nLineCount-1)
                nTotalOccupiedHeight += nSBL;
            nTotalOccupiedHeight += nUL;
        }
    }
 
    tools::Long nTotalSpace = getHeightDirectionAware(maPaperSize);
    nTotalSpace -= nTotalOccupiedHeight;
    if (nTotalSpace <= 0 || nTotalLineCount <= 1)
        return 0;
 
    // Shift the text to the right for the asian layout mode.
    if (IsEffectivelyVertical())
        adjustYDirectionAware(rStartPos, -nTotalSpace);
 
    return nTotalSpace / (nTotalLineCount-1);
}
 
void ImpEditEngine::InsertParagraph( sal_Int32 nPara, const EditTextObject& rTxtObj, bool bAppend )
{
    if ( nPara > maEditDoc.Count() )
    {
        SAL_WARN_IF( nPara != EE_PARA_APPEND, "editeng", "Paragraph number too large, but not EE_PARA_APPEND!" );
        nPara = maEditDoc.Count();
    }
 
    UndoActionStart(EDITUNDO_INSERT);
 
    // No Undo compounding needed.
    EditPaM aPaM(InsertParagraph(nPara));
    // When InsertParagraph from the outside, no hard attributes
    // should be taken over!
    RemoveCharAttribs(nPara);
    InsertText(rTxtObj, EditSelection(aPaM, aPaM));
 
    if ( bAppend && nPara )
        ConnectContents(nPara - 1, /*bBackwards=*/false);
 
    UndoActionEnd();
 
    if (IsUpdateLayout())
        FormatAndLayout();
}
 
void ImpEditEngine::InsertParagraph(sal_Int32 nPara, const OUString& rTxt)
{
    if ( nPara > maEditDoc.Count() )
    {
        SAL_WARN_IF( nPara != EE_PARA_APPEND, "editeng", "Paragraph number too large, but not EE_PARA_APPEND!" );
        nPara = maEditDoc.Count();
    }
 
    UndoActionStart(EDITUNDO_INSERT);
    EditPaM aPaM(InsertParagraph(nPara));
    // When InsertParagraph from the outside, no hard attributes
    // should be taken over!
    RemoveCharAttribs(nPara);
    UndoActionEnd();
    ImpInsertText(EditSelection(aPaM, aPaM), rTxt);
    if (IsUpdateLayout())
        FormatAndLayout();
}
 
void ImpEditEngine::SetParagraphText(sal_Int32 nPara, const OUString& rTxt)
{
    std::optional<EditSelection> pSel = SelectParagraph(nPara);
    if ( pSel )
    {
        UndoActionStart(EDITUNDO_INSERT);
        ImpInsertText(*pSel, rTxt);
        UndoActionEnd();
        if (IsUpdateLayout())
            FormatAndLayout();
    }
}
 
EditPaM ImpEditEngine::InsertParagraph( sal_Int32 nPara )
{
    EditPaM aPaM;
    if ( nPara != 0 )
    {
        ContentNode* pNode = GetEditDoc().GetObject( nPara-1 );
        if ( !pNode )
            pNode = GetEditDoc().GetObject( GetEditDoc().Count() - 1 );
        assert(pNode && "Not a single paragraph in InsertParagraph ?");
        aPaM = EditPaM( pNode, pNode->Len() );
    }
    else
    {
        ContentNode* pNode = GetEditDoc().GetObject( 0 );
        aPaM = EditPaM( pNode, 0 );
    }
 
    return ImpInsertParaBreak( aPaM );
}
 
std::optional<EditSelection> ImpEditEngine::SelectParagraph( sal_Int32 nPara )
{
    std::optional<EditSelection> pSel;
    ContentNode* pNode = GetEditDoc().GetObject( nPara );
    SAL_WARN_IF( !pNode, "editeng", "Paragraph does not exist: SelectParagraph" );
    if ( pNode )
        pSel.emplace( EditPaM( pNode, 0 ), EditPaM( pNode, pNode->Len() ) );
 
    return pSel;
}
 
void ImpEditEngine::FormatAndLayout( EditView* pCurView, bool bCalledFromUndo )
{
    if (mbDowning)
        return;
 
    if ( IsInUndo() )
        IdleFormatAndLayout( pCurView );
    else
    {
        if (bCalledFromUndo)
        {
            // in order to make bullet points that have had their styles changed, redraw themselves
            for (auto& pParaPortion : GetParaPortions())
                pParaPortion->MarkInvalid(0, 0);
        }
        FormatDoc();
        UpdateViews( pCurView );
    }
 
    EENotify aNotify(EE_NOTIFY_PROCESSNOTIFICATIONS);
    GetNotifyHdl().Call(aNotify);
}
 
void ImpEditEngine::SetFlatMode( bool bFlat )
{
    if ( bFlat != maStatus.UseCharAttribs() )
        return;
 
    if ( !bFlat )
        maStatus.TurnOnFlags( EEControlBits::USECHARATTRIBS );
    else
        maStatus.TurnOffFlags( EEControlBits::USECHARATTRIBS );
 
    maEditDoc.CreateDefFont( !bFlat );
 
    FormatFullDoc();
    UpdateViews();
    if (mpActiveView)
        mpActiveView->ShowCursor();
}
 
void ImpEditEngine::setScalingParameters(ScalingParameters const& rScalingParameters)
{
    ScalingParameters aNewScalingParameters(rScalingParameters);
 
    if (IsEffectivelyVertical())
    {
        std::swap(aNewScalingParameters.fFontX, aNewScalingParameters.fFontY);
        std::swap(aNewScalingParameters.fSpacingX, aNewScalingParameters.fSpacingY);
    }
 
    bool bScalingChanged = maScalingParameters != aNewScalingParameters;
    maCustomScalingParameters = maScalingParameters = aNewScalingParameters;
 
    if (bScalingChanged && maStatus.DoStretch())
    {
        FormatFullDoc();
        // (potentially) need everything redrawn
        maInvalidRect = tools::Rectangle(0, 0, 1000000, 1000000);
        UpdateViews(GetActiveView());
    }
}
 
const SvxNumberFormat* ImpEditEngine::GetNumberFormat( const ContentNode *pNode ) const
{
    const SvxNumberFormat *pRes = nullptr;
 
    if (pNode)
    {
        // get index of paragraph
        sal_Int32 nPara = GetEditDoc().GetPos( pNode );
        DBG_ASSERT( nPara < EE_PARA_NOT_FOUND, "node not found in array" );
        if (nPara < EE_PARA_NOT_FOUND)
        {
            // the called function may be overridden by an OutlinerEditEng
            // object to provide
            // access to the SvxNumberFormat of the Outliner.
            // The EditEngine implementation will just return 0.
            pRes = mpEditEngine->GetNumberFormat( nPara );
        }
    }
 
    return pRes;
}
 
sal_Int32 ImpEditEngine::GetSpaceBeforeAndMinLabelWidth(
    const ContentNode *pNode,
    sal_Int32 *pnSpaceBefore, sal_Int32 *pnMinLabelWidth ) const
{
    // nSpaceBefore     matches the ODF attribute text:space-before
    // nMinLabelWidth   matches the ODF attribute text:min-label-width
 
    const SvxNumberFormat *pNumFmt = GetNumberFormat( pNode );
 
    // if no number format was found we have no Outliner or the numbering level
    // within the Outliner is -1 which means no number format should be applied.
    // Thus the default values to be returned are 0.
    sal_Int32 nSpaceBefore   = 0;
    sal_Int32 nMinLabelWidth = 0;
 
    if (pNumFmt)
    {
        nMinLabelWidth = -pNumFmt->GetFirstLineOffset();
        nSpaceBefore   = pNumFmt->GetAbsLSpace() - nMinLabelWidth;
        DBG_ASSERT( nMinLabelWidth >= 0, "ImpEditEngine::GetSpaceBeforeAndMinLabelWidth: min-label-width < 0 encountered" );
    }
    if (pnSpaceBefore)
        *pnSpaceBefore      = nSpaceBefore;
    if (pnMinLabelWidth)
        *pnMinLabelWidth    = nMinLabelWidth;
 
    return nSpaceBefore + nMinLabelWidth;
}
 
const SvxLRSpaceItem& ImpEditEngine::GetLRSpaceItem( ContentNode* pNode )
{
    return pNode->GetContentAttribs().GetItem( maStatus.IsOutliner() ? EE_PARA_OUTLLRSPACE : EE_PARA_LRSPACE );
}
 
// select a representative text language for the digit type according to the
// text numeral setting:
LanguageType ImpEditEngine::ImplCalcDigitLang(LanguageType eCurLang)
{
    if (comphelper::IsFuzzing())
        return LANGUAGE_ENGLISH_US;
 
    // #114278# Also setting up digit language from Svt options
    // (cannot reliably inherit the outdev's setting)
 
    LanguageType eLang = eCurLang;
    const SvtCTLOptions::TextNumerals nCTLTextNumerals = SvtCTLOptions::GetCTLTextNumerals();
 
    if ( SvtCTLOptions::NUMERALS_HINDI == nCTLTextNumerals )
        eLang = LANGUAGE_ARABIC_SAUDI_ARABIA;
    else if ( SvtCTLOptions::NUMERALS_ARABIC == nCTLTextNumerals )
        eLang = LANGUAGE_ENGLISH;
    else if ( SvtCTLOptions::NUMERALS_SYSTEM == nCTLTextNumerals )
        eLang = Application::GetSettings().GetLanguageTag().getLanguageType();
 
    return eLang;
}
 
OUString ImpEditEngine::convertDigits(std::u16string_view rString, sal_Int32 nStt, sal_Int32 nLen, LanguageType eDigitLang)
{
    OUStringBuffer aBuf(rString);
    for (sal_Int32 nIdx = nStt, nEnd = nStt + nLen; nIdx < nEnd; ++nIdx)
    {
        sal_Unicode cChar = aBuf[nIdx];
        if (cChar >= '0' && cChar <= '9')
            aBuf[nIdx] = GetLocalizedChar(cChar, eDigitLang);
    }
    return aBuf.makeStringAndClear();
}
 
// Either sets the digit mode at the output device
void ImpEditEngine::ImplInitDigitMode(OutputDevice& rOutDev, LanguageType eCurLang)
{
    rOutDev.SetDigitLanguage(ImplCalcDigitLang(eCurLang));
}
 
void ImpEditEngine::ImplInitLayoutMode(OutputDevice& rOutDev, sal_Int32 nPara, sal_Int32 nIndex)
{
    bool bCTL = false;
    bool bR2L = false;
    if ( nIndex == -1 )
    {
        bCTL = HasScriptType( nPara, i18n::ScriptType::COMPLEX );
        bR2L = IsRightToLeft( nPara );
    }
    else
    {
        ContentNode* pNode = GetEditDoc().GetObject( nPara );
        short nScriptType = GetI18NScriptType( EditPaM( pNode, nIndex+1 ) );
        bCTL = nScriptType == i18n::ScriptType::COMPLEX;
        // this change was discussed in issue 37190
        bR2L = (GetRightToLeft( nPara, nIndex + 1) % 2) != 0;
        // it also works for issue 55927
    }
 
    vcl::text::ComplexTextLayoutFlags nLayoutMode = rOutDev.GetLayoutMode();
 
    // We always use the left position for DrawText()
    nLayoutMode &= ~vcl::text::ComplexTextLayoutFlags::BiDiRtl;
 
    if ( !bCTL && !bR2L)
    {
        // No Bidi checking necessary
        nLayoutMode |= vcl::text::ComplexTextLayoutFlags::BiDiStrong;
    }
    else
    {
        // Bidi checking necessary
        // Don't use BIDI_STRONG, VCL must do some checks.
        nLayoutMode &= ~vcl::text::ComplexTextLayoutFlags::BiDiStrong;
 
        if ( bR2L )
            nLayoutMode |= vcl::text::ComplexTextLayoutFlags::BiDiRtl|vcl::text::ComplexTextLayoutFlags::TextOriginLeft;
    }
 
    rOutDev.SetLayoutMode( nLayoutMode );
 
    // #114278# Also setting up digit language from Svt options
    // (cannot reliably inherit the outdev's setting)
    LanguageType eLang = Application::GetSettings().GetLanguageTag().getLanguageType();
    ImplInitDigitMode(rOutDev, eLang);
}
 
Reference < i18n::XBreakIterator > const & ImpEditEngine::ImplGetBreakIterator() const
{
    if (!mxBI.is())
    {
        const uno::Reference<uno::XComponentContext>& xContext(::comphelper::getProcessComponentContext());
        mxBI = i18n::BreakIterator::create(xContext);
    }
    return mxBI;
}
 
Reference < i18n::XExtendedInputSequenceChecker > const & ImpEditEngine::ImplGetInputSequenceChecker() const
{
    if (!mxISC.is())
    {
        const uno::Reference<uno::XComponentContext>& xContext(::comphelper::getProcessComponentContext());
        mxISC = i18n::InputSequenceChecker::create(xContext);
    }
    return mxISC;
}
 
Color ImpEditEngine::GetAutoColor() const
{
    Color aColor;
 
    const SfxViewShell* pKitSh = comphelper::LibreOfficeKit::isActive() ? SfxViewShell::Current() : nullptr;
    if (pKitSh)
    {
        Color aBackgroundColor = GetBackgroundColor();
        if (aBackgroundColor == COL_AUTO)
            aBackgroundColor = pKitSh->GetColorConfigColor(svtools::DOCCOLOR);
        if (aBackgroundColor.IsDark())
            aColor = COL_WHITE;
        else
            aColor = COL_BLACK;
    }
    else
    {
        aColor = GetColorConfig().GetColorValue(svtools::FONTCOLOR, false).nColor;
 
        if ( aColor == COL_AUTO )
        {
            if ( GetBackgroundColor().IsDark()  )
                aColor = COL_WHITE;
            else
                aColor = COL_BLACK;
        }
    }
 
    return aColor;
}
 
bool ImpEditEngine::ImplCalcAsianCompression(ContentNode* pNode,
                                             TextPortion* pTextPortion, sal_Int32 nStartPos,
                                             double* pDXArray, sal_uInt16 n100thPercentFromMax,
                                             bool bManipulateDXArray)
{
    DBG_ASSERT( GetAsianCompressionMode() != CharCompressType::NONE, "ImplCalcAsianCompression - Why?" );
    DBG_ASSERT( pTextPortion->GetLen(), "ImplCalcAsianCompression - Empty Portion?" );
 
    // Percent is 1/100 Percent...
    if ( n100thPercentFromMax == 10000 )
        pTextPortion->SetExtraInfos( nullptr );
 
    bool bCompressed = false;
 
    if ( GetI18NScriptType( EditPaM( pNode, nStartPos+1 ) ) == i18n::ScriptType::ASIAN )
    {
        tools::Long nNewPortionWidth = pTextPortion->GetSize().Width();
        sal_Int32 nPortionLen = pTextPortion->GetLen();
        for ( sal_Int32 n = 0; n < nPortionLen; n++ )
        {
            AsianCompressionFlags nType = GetCharTypeForCompression( pNode->GetChar( n+nStartPos ) );
 
            bool bCompressPunctuation = ( nType == AsianCompressionFlags::PunctuationLeft ) || ( nType == AsianCompressionFlags::PunctuationRight );
            bool bCompressKana = ( nType == AsianCompressionFlags::Kana ) && ( GetAsianCompressionMode() == CharCompressType::PunctuationAndKana );
 
            // create Extra infos only if needed...
            if ( bCompressPunctuation || bCompressKana )
            {
                if ( !pTextPortion->GetExtraInfos() )
                {
                    ExtraPortionInfo* pExtraInfos = new ExtraPortionInfo;
                    pTextPortion->SetExtraInfos( pExtraInfos );
                    pExtraInfos->nOrgWidth = pTextPortion->GetSize().Width();
                    pExtraInfos->nAsianCompressionTypes = AsianCompressionFlags::Normal;
                }
                pTextPortion->GetExtraInfos()->nMaxCompression100thPercent = n100thPercentFromMax;
                pTextPortion->GetExtraInfos()->nAsianCompressionTypes |= nType;
 
                tools::Long nOldCharWidth;
                if ( (n+1) < nPortionLen )
                {
                    nOldCharWidth = pDXArray[n];
                }
                else
                {
                    if ( bManipulateDXArray )
                        nOldCharWidth = nNewPortionWidth - pTextPortion->GetExtraInfos()->nPortionOffsetX;
                    else
                        nOldCharWidth = pTextPortion->GetExtraInfos()->nOrgWidth;
                }
                nOldCharWidth -= ( n ? pDXArray[n-1] : 0 );
 
                tools::Long nCompress = 0;
 
                if ( bCompressPunctuation )
                {
                    nCompress = nOldCharWidth / 2;
                }
                else // Kana
                {
                    nCompress = nOldCharWidth / 10;
                }
 
                if ( n100thPercentFromMax != 10000 )
                {
                    nCompress *= n100thPercentFromMax;
                    nCompress /= 10000;
                }
 
                if ( nCompress )
                {
                    bCompressed = true;
                    nNewPortionWidth -= nCompress;
                    pTextPortion->GetExtraInfos()->bCompressed = true;
 
 
                    // Special handling for rightpunctuation: For the 'compression' we must
                    // start the output before the normal char position...
                    if ( bManipulateDXArray && ( pTextPortion->GetLen() > 1 ) )
                    {
                        if ( !pTextPortion->GetExtraInfos()->pOrgDXArray )
                            pTextPortion->GetExtraInfos()->SaveOrgDXArray( pDXArray, pTextPortion->GetLen()-1 );
 
                        if ( nType == AsianCompressionFlags::PunctuationRight )
                        {
                            // If it's the first char, I must handle it in Paint()...
                            if ( n )
                            {
                                // -1: No entry for the last character
                                for ( sal_Int32 i = n-1; i < (nPortionLen-1); i++ )
                                    pDXArray[i] -= nCompress;
                            }
                            else
                            {
                                pTextPortion->GetExtraInfos()->bFirstCharIsRightPunktuation = true;
                                pTextPortion->GetExtraInfos()->nPortionOffsetX = -nCompress;
                            }
                        }
                        else
                        {
                            // -1: No entry for the last character
                            for ( sal_Int32 i = n; i < (nPortionLen-1); i++ )
                                pDXArray[i] -= nCompress;
                        }
                    }
                }
            }
        }
 
        if ( bCompressed && ( n100thPercentFromMax == 10000 ) )
            pTextPortion->GetExtraInfos()->nWidthFullCompression = nNewPortionWidth;
 
        pTextPortion->setWidth(nNewPortionWidth);
 
        if ( pTextPortion->GetExtraInfos() && ( n100thPercentFromMax != 10000 ) )
        {
            // Maybe rounding errors in nNewPortionWidth, assure that width not bigger than expected
            tools::Long nShrink = pTextPortion->GetExtraInfos()->nOrgWidth - pTextPortion->GetExtraInfos()->nWidthFullCompression;
            nShrink *= n100thPercentFromMax;
            nShrink /= 10000;
            tools::Long nNewWidth = pTextPortion->GetExtraInfos()->nOrgWidth - nShrink;
            if ( nNewWidth < pTextPortion->GetSize().Width() )
                pTextPortion->setWidth(nNewWidth);
        }
    }
    return bCompressed;
}
 
 
void ImpEditEngine::ImplExpandCompressedPortions(EditLine& rLine, ParaPortion& rParaPortion, tools::Long nRemainingWidth)
{
    bool bFoundCompressedPortion = false;
    tools::Long nCompressed = 0;
    std::vector<TextPortion*> aCompressedPortions;
 
    sal_Int32 nPortion = rLine.GetEndPortion();
    TextPortion* pTP = &rParaPortion.GetTextPortions()[ nPortion ];
    while ( pTP && ( pTP->GetKind() == PortionKind::TEXT ) )
    {
        if ( pTP->GetExtraInfos() && pTP->GetExtraInfos()->bCompressed )
        {
            bFoundCompressedPortion = true;
            nCompressed += pTP->GetExtraInfos()->nOrgWidth - pTP->GetSize().Width();
            aCompressedPortions.push_back(pTP);
        }
        pTP = ( nPortion > rLine.GetStartPortion() ) ? &rParaPortion.GetTextPortions()[ --nPortion ] : nullptr;
    }
 
    if ( !bFoundCompressedPortion )
        return;
 
    tools::Long nCompressPercent = 0;
    if ( nCompressed > nRemainingWidth )
    {
        nCompressPercent = nCompressed - nRemainingWidth;
        DBG_ASSERT( nCompressPercent < 200000, "ImplExpandCompressedPortions - Overflow!" );
        nCompressPercent *= 10000;
        nCompressPercent /= nCompressed;
    }
 
    for (TextPortion* pTP2 : aCompressedPortions)
    {
        pTP = pTP2;
        pTP->GetExtraInfos()->bCompressed = false;
        pTP->setWidth(pTP->GetExtraInfos()->nOrgWidth);
        if ( nCompressPercent )
        {
            sal_Int32 nTxtPortion = rParaPortion.GetTextPortions().GetPos( pTP );
            sal_Int32 nTxtPortionStart = rParaPortion.GetTextPortions().GetStartPos( nTxtPortion );
            DBG_ASSERT( nTxtPortionStart >= rLine.GetStart(), "Portion doesn't belong to the line!!!" );
            double* pDXArray = rLine.GetCharPosArray().data() + (nTxtPortionStart - rLine.GetStart());
            if ( pTP->GetExtraInfos()->pOrgDXArray )
                memcpy( pDXArray, pTP->GetExtraInfos()->pOrgDXArray.get(), (pTP->GetLen()-1)*sizeof(double) );
            ImplCalcAsianCompression( rParaPortion.GetNode(), pTP, nTxtPortionStart, pDXArray, static_cast<sal_uInt16>(nCompressPercent), true );
        }
    }
}
 
void ImpEditEngine::ImplUpdateOverflowingParaNum(tools::Long nPaperHeight)
{
    tools::Long nY = 0;
    tools::Long nPH;
 
    for (sal_Int32 nPara = 0; nPara < GetParaPortions().Count(); nPara++)
    {
        ParaPortion& rParaPortion = GetParaPortions().getRef(nPara);
        nPH = rParaPortion.GetHeight();
        nY += nPH;
        if ( nY > nPaperHeight /*mnCurTextHeight*/ ) // found first paragraph overflowing
        {
            mnOverflowingPara = nPara;
            SAL_INFO("editeng.chaining", "[CHAINING] Setting first overflowing #Para#: " << nPara);
            ImplUpdateOverflowingLineNum( nPaperHeight, nPara, nY - nPH);
            return;
        }
    }
}
 
void ImpEditEngine::ImplUpdateOverflowingLineNum(tools::Long nPaperHeight,
                                             sal_uInt32 nOverflowingPara,
                                             tools::Long nHeightBeforeOverflowingPara)
{
    if (GetParaPortions().exists(nOverflowingPara))
        return;
 
    tools::Long nY = nHeightBeforeOverflowingPara;
    tools::Long nLH;
 
    ParaPortion& rParaPortion = GetParaPortions().getRef(nOverflowingPara);
 
    // Like UpdateOverflowingParaNum but for each line in the first
    //  overflowing paragraph.
    for (sal_Int32 nLine = 0; nLine < rParaPortion.GetLines().Count(); nLine++)
    {
        // XXX: We must use a reference here because the copy constructor resets the height
        EditLine &aLine = rParaPortion.GetLines()[nLine];
        nLH = aLine.GetHeight();
        nY += nLH;
 
        // Debugging output
        if (nLine == 0)
        {
            SAL_INFO("editeng.chaining", "[CHAINING] First line has height " << nLH);
        }
 
        if ( nY > nPaperHeight ) // found first line overflowing
        {
            mnOverflowingLine = nLine;
            SAL_INFO("editeng.chaining", "[CHAINING] Setting first overflowing -Line- to: " << nLine);
            return;
        }
    }
 
    assert(false && "You should never get here");
}
 
/* vim:set shiftwidth=4 softtabstop=4 expandtab: */

V530 The return value of function 'Intersection' is required to be utilized.

V530 The return value of function 'Union' is required to be utilized.

V530 The return value of function 'Union' is required to be utilized.

V530 The return value of function 'MoveToNextLine' is required to be utilized.

V530 The return value of function 'QuickGetTextSize' is required to be utilized.

V530 The return value of function 'QuickGetTextSize' is required to be utilized.

V530 The return value of function 'padToLength' is required to be utilized.

V530 The return value of function 'padToLength' is required to be utilized.

V530 The return value of function 'Intersection' is required to be utilized.

V1028 Possible overflow. Consider casting operands, not the result.