/* -*- 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/wrkwin.hxx>
#include <vcl/dialog.hxx>
#include <vcl/svapp.hxx>
#include <vcl/metaact.hxx>
#include <vcl/gdimtf.hxx>
#include <vcl/settings.hxx>
 
#include <editeng/adjustitem.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/txtrange.hxx>
#include <editeng/colritem.hxx>
#include <editeng/udlnitem.hxx>
#include <editeng/fhgtitem.hxx>
#include <editeng/kernitem.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/scriptspaceitem.hxx>
#include <editeng/charscaleitem.hxx>
#include <editeng/numitem.hxx>
#include <editeng/justifyitem.hxx>
 
#include <svtools/colorcfg.hxx>
#include <svl/ctloptions.hxx>
#include <svl/asiancfg.hxx>
 
#include <editeng/hngpnctitem.hxx>
#include <editeng/forbiddencharacterstable.hxx>
 
#include <unotools/configmgr.hxx>
#include <unotools/localedatawrapper.hxx>
 
#include <editeng/unolingu.hxx>
 
#include <set>
#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 <com/sun/star/text/CharacterCompressionType.hpp>
#include <vcl/pdfextoutdevdata.hxx>
#include <i18nlangtag/mslangid.hxx>
#include <o3tl/make_unique.hxx>
 
#include <comphelper/processfactory.hxx>
#include <rtl/ustrbuf.hxx>
#include <sal/log.hxx>
#include <comphelper/string.hxx>
#include <comphelper/lok.hxx>
#include <memory>
 
using namespace ::com::sun::star;
using namespace ::com::sun::star::uno;
using namespace ::com::sun::star::beans;
using namespace ::com::sun::star::linguistic2;
 
#define CH_HYPH     '-'
 
#define WRONG_SHOW_MIN       5
 
struct TabInfo
{
    bool        bValid;
 
    SvxTabStop  aTabStop;
    sal_Int32   nTabPortion;
    long        nStartPosX;
    long        nTabPos;
 
    TabInfo()
        : bValid(false)
        , nTabPortion(0)
        , nStartPosX(0)
        , nTabPos(0)
        { }
 
};
 
Point Rotate( const Point& rPoint, short nOrientation, const Point& rOrigin )
{
    double nRealOrientation = nOrientation*F_PI1800;
    double nCos = cos( nRealOrientation );
    double nSin = sin( nRealOrientation );
 
    Point aRotatedPos;
    Point aTranslatedPos( rPoint );
 
    // Translation
    aTranslatedPos -= rOrigin;
 
    // Rotation...
    aRotatedPos.setX( static_cast<long>( nCos*aTranslatedPos.X() + nSin*aTranslatedPos.Y() ) );
    aRotatedPos.setY( static_cast<long>(- ( nSin*aTranslatedPos.X() - nCos*aTranslatedPos.Y() )) );
    aTranslatedPos = aRotatedPos;
 
    // Translation...
    aTranslatedPos += rOrigin;
    return aTranslatedPos;
}
 
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:
        {
            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:
        {
            return AsianCompressionFlags::PunctuationLeft;
        }
        default:
        {
            return ( ( 0x3040 <= cChar ) && ( 0x3100 > cChar ) ) ? AsianCompressionFlags::Kana : AsianCompressionFlags::Normal;
        }
    }
}
 
static void lcl_DrawRedLines( OutputDevice* pOutDev,
                              long nFontHeight,
                              const Point& rPnt,
                              size_t nIndex,
                              size_t nMaxEnd,
                              const long* pDXArray,
                              WrongList const * pWrongs,
                              short nOrientation,
                              const Point& rOrigin,
                              bool bVertical,
                              bool bIsRightToLeft )
{
    // But only if font is not too small ...
    long nHght = pOutDev->LogicToPixel( Size( 0, nFontHeight ) ).Height();
    if( WRONG_SHOW_MIN < nHght )
    {
        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 aPnt1( rPnt );
            if ( bVertical )
            {
                // VCL doesn't know that the text is vertical, and is manipulating
                // the positions a little bit in y direction...
                long nOnePixel = pOutDev->PixelToLogic( Size( 0, 1 ) ).Height();
                long nCorrect = 2*nOnePixel;
                aPnt1.AdjustY( -nCorrect );
                aPnt1.AdjustX( -nCorrect );
            }
            if ( nStart > nIndex )
            {
                if ( !bVertical )
                {
                    // since for RTL portions rPnt 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...
                    aPnt1.AdjustX((bIsRightToLeft ? -1 : 1) * pDXArray[ nStart - nIndex - 1 ] );
                }
                else
                    aPnt1.AdjustY(pDXArray[ nStart - nIndex - 1 ] );
            }
            Point aPnt2( rPnt );
            DBG_ASSERT( nEnd > nIndex, "RedLine: aPnt2?" );
            if ( !bVertical )
            {
                // since for RTL portions rPnt 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...
                aPnt2.AdjustX((bIsRightToLeft ? -1 : 1) * pDXArray[ nEnd - nIndex - 1 ] );
            }
            else
                aPnt2.AdjustY(pDXArray[ nEnd - nIndex - 1 ] );
            if ( nOrientation )
            {
                aPnt1 = Rotate( aPnt1, nOrientation, rOrigin );
                aPnt2 = Rotate( aPnt2, nOrientation, rOrigin );
            }
 
            pOutDev->DrawWaveLine( aPnt1, aPnt2 );
 
            nStart = nEnd+1;
            if ( nEnd < nMaxEnd )
                bWrong = pWrongs->NextWrong( nStart, nEnd );
            else
                bWrong = false;
        }
    }
}
 
static Point lcl_ImplCalcRotatedPos( Point rPos, Point rOrigin, double nSin, double nCos )
{
    Point aRotatedPos;
    // Translation...
    Point aTranslatedPos( rPos);
    aTranslatedPos -= rOrigin;
 
    aRotatedPos.setX( static_cast<long>( nCos*aTranslatedPos.X() + nSin*aTranslatedPos.Y() ) );
    aRotatedPos.setY( static_cast<long>(- ( nSin*aTranslatedPos.X() - nCos*aTranslatedPos.Y() )) );
    aTranslatedPos = aRotatedPos;
    // Translation...
    aTranslatedPos += rOrigin;
 
    return aTranslatedPos;
}
 
static bool lcl_IsLigature( sal_Unicode cCh, sal_Unicode cNextCh ) // For Kashidas from sw/source/core/text/porlay.txt
{
            // Lam + Alef
    return ( 0x644 == cCh && 0x627 == cNextCh ) ||
            // Beh + Reh
           ( 0x628 == cCh && 0x631 == cNextCh );
}
 
static bool lcl_ConnectToPrev( sal_Unicode cCh, sal_Unicode cPrevCh )  // For Kashidas from sw/source/core/text/porlay.txt
{
    // Alef, Dal, Thal, Reh, Zain, and Waw do not connect to the left
    bool bRet = 0x627 != cPrevCh && 0x62F != cPrevCh && 0x630 != cPrevCh &&
                0x631 != cPrevCh && 0x632 != cPrevCh && 0x648 != cPrevCh;
 
    // check for ligatures cPrevChar + cChar
    if ( bRet )
        bRet = ! lcl_IsLigature( cPrevCh, cCh );
 
    return bRet;
}
 
 
//  class ImpEditEngine
 
void ImpEditEngine::UpdateViews( EditView* pCurView )
{
    if ( !GetUpdateMode() || IsFormatting() || aInvalidRect.IsEmpty() )
        return;
 
    DBG_ASSERT( IsFormatted(), "UpdateViews: Doc not formatted!" );
 
    for (EditView* pView : aEditViews)
    {
        pView->HideCursor();
 
        tools::Rectangle aClipRect( aInvalidRect );
        tools::Rectangle aVisArea( pView->GetVisArea() );
        aClipRect.Intersection( aVisArea );
 
        if ( !aClipRect.IsEmpty() )
        {
            // convert to window coordinates ....
            aClipRect = pView->pImpEditView->GetWindowPos( aClipRect );
 
            // moved to one executing method to allow finer control
            pView->InvalidateWindow(aClipRect);
 
            pView->InvalidateOtherViewWindows( aClipRect );
        }
    }
 
    if ( pCurView )
    {
        bool bGotoCursor = pCurView->pImpEditView->DoAutoScroll();
        pCurView->ShowCursor( bGotoCursor );
    }
 
    aInvalidRect = tools::Rectangle();
    CallStatusHdl();
}
 
IMPL_LINK_NOARG(ImpEditEngine, OnlineSpellHdl, Timer *, void)
{
    if ( !Application::AnyInput( VclInputFlags::KEYBOARD ) && GetUpdateMode() && IsFormatted() )
        DoOnlineSpelling();
    else
        aOnlineSpellTimer.Start();
}
 
IMPL_LINK_NOARG(ImpEditEngine, IdleFormatHdl, Timer *, void)
{
    aIdleFormatter.ResetRestarts();
 
    // #i97146# check if that view is still available
    // else probably the idle format timer fired while we're already
    // downing
    EditView* pView = aIdleFormatter.GetView();
    for (EditView* aEditView : aEditViews)
    {
        if( aEditView == pView )
        {
            FormatAndUpdate( pView );
            break;
        }
    }
}
 
void ImpEditEngine::CheckIdleFormatter()
{
    aIdleFormatter.ForceTimeout();
    // If not idle, but still not formatted:
    if ( !IsFormatted() )
        FormatDoc();
}
 
bool ImpEditEngine::IsPageOverflow( ) const
{
    return mbNeedsChainingHandling;
}
 
 
void ImpEditEngine::FormatFullDoc()
{
    for ( sal_Int32 nPortion = 0; nPortion < GetParaPortions().Count(); nPortion++ )
        GetParaPortions()[nPortion]->MarkSelectionInvalid( 0 );
    FormatDoc();
}
 
void ImpEditEngine::FormatDoc()
{
    if (!GetUpdateMode() || IsFormatting())
        return;
 
    bIsFormatting = true;
 
    // Then I can also start the spell-timer ...
    if ( GetStatus().DoOnlineSpelling() )
        StartOnlineSpellTimer();
 
    long nY = 0;
    bool bGrow = false;
 
    vcl::Font aOldFont( GetRefDevice()->GetFont() );
 
    // Here already, so that not always in CreateLines...
    bool bMapChanged = ImpCheckRefMapMode();
 
    aInvalidRect = tools::Rectangle();  // make empty
    for ( sal_Int32 nPara = 0; nPara < GetParaPortions().Count(); nPara++ )
    {
        ParaPortion* pParaPortion = GetParaPortions()[nPara];
        if ( pParaPortion->MustRepaint() || ( pParaPortion->IsInvalid() && pParaPortion->IsVisible() ) )
        {
            // No formatting should be necessary for MustRepaint()!
            if ( ( pParaPortion->MustRepaint() && !pParaPortion->IsInvalid() )
                    || CreateLines( nPara, nY ) )
            {
                if ( !bGrow && GetTextRanger() )
                {
                    // For a change in height all below must be reformatted ...
                    for ( sal_Int32 n = nPara+1; n < GetParaPortions().Count(); n++ )
                    {
                        ParaPortion* pPP = GetParaPortions()[n];
                        pPP->MarkSelectionInvalid( 0 );
                        pPP->GetLines().Reset();
                    }
                }
                bGrow = true;
                if ( IsCallParaInsertedOrDeleted() )
                    GetEditEnginePtr()->ParagraphHeightChanged( nPara );
                pParaPortion->SetMustRepaint( false );
            }
 
            // InvalidRect set only once...
            if ( aInvalidRect.IsEmpty() )
            {
                // For Paperwidth 0 (AutoPageSize) it would otherwise be Empty()...
                long nWidth = std::max( long(1), ( !IsVertical() ? aPaperSize.Width() : aPaperSize.Height() ) );
                Range aInvRange( GetInvalidYOffsets( pParaPortion ) );
                aInvalidRect = tools::Rectangle( Point( 0, nY+aInvRange.Min() ),
                    Size( nWidth, aInvRange.Len() ) );
            }
            else
            {
                aInvalidRect.SetBottom( nY + pParaPortion->GetHeight() );
            }
        }
        else if ( bGrow )
        {
            aInvalidRect.SetBottom( nY + pParaPortion->GetHeight() );
        }
        nY += pParaPortion->GetHeight();
    }
 
    // One can also get into the formatting through UpdateMode ON=>OFF=>ON...
    // enable optimization first after Vobis delivery ...
    {
        sal_uInt32 nNewHeightNTP;
        sal_uInt32 nNewHeight = CalcTextHeight( &nNewHeightNTP );
        long nDiff = nNewHeight - nCurTextHeight;
        if ( nDiff )
            aStatus.GetStatusWord() |= !IsVertical() ? EditStatusFlags::TextHeightChanged : EditStatusFlags::TEXTWIDTHCHANGED;
        if ( nNewHeight < nCurTextHeight )
        {
            aInvalidRect.SetBottom( static_cast<long>(std::max( nNewHeight, nCurTextHeight )) );
            if ( aInvalidRect.IsEmpty() )
            {
                aInvalidRect.SetTop( 0 );
                // Left and Right are not evaluated, are however set due to IsEmpty.
                aInvalidRect.SetLeft( 0 );
                aInvalidRect.SetRight( !IsVertical() ? aPaperSize.Width() : aPaperSize.Height() );
            }
        }
 
        nCurTextHeight = nNewHeight;
        nCurTextHeightNTP = nNewHeightNTP;
 
        if ( aStatus.AutoPageSize() )
            CheckAutoPageSize();
        else if ( nDiff )
        {
            for (EditView* pView : aEditViews)
            {
                ImpEditView* pImpView = pView->pImpEditView.get();
                if ( pImpView->DoAutoHeight() )
                {
                    Size aSz( pImpView->GetOutputArea().GetWidth(), nCurTextHeight );
                    if ( aSz.Height() > aMaxAutoPaperSize.Height() )
                        aSz.setHeight( aMaxAutoPaperSize.Height() );
                    else if ( aSz.Height() < aMinAutoPaperSize.Height() )
                        aSz.setHeight( aMinAutoPaperSize.Height() );
                    pImpView->ResetOutputArea( tools::Rectangle(
                        pImpView->GetOutputArea().TopLeft(), aSz ) );
                }
            }
        }
    }
 
    if ( aStatus.DoRestoreFont() )
        GetRefDevice()->SetFont( aOldFont );
    bIsFormatting = false;
    bFormatted = true;
 
    if ( bMapChanged )
        GetRefDevice()->Pop();
 
    CallStatusHdl();    // If Modified...
}
 
bool ImpEditEngine::ImpCheckRefMapMode()
{
    bool bChange = false;
 
    if ( aStatus.DoFormat100() )
    {
        MapMode aMapMode( GetRefDevice()->GetMapMode() );
        if ( aMapMode.GetScaleX().GetNumerator() != aMapMode.GetScaleX().GetDenominator() )
            bChange = true;
        else if ( aMapMode.GetScaleY().GetNumerator() != aMapMode.GetScaleY().GetDenominator() )
            bChange = true;
 
        if ( bChange )
        {
            Fraction Scale1( 1, 1 );
            aMapMode.SetScaleX( Scale1 );
            aMapMode.SetScaleY( Scale1 );
            GetRefDevice()->Push();
            GetRefDevice()->SetMapMode( aMapMode );
        }
    }
 
    return bChange;
}
 
void ImpEditEngine::CheckAutoPageSize()
{
    Size aPrevPaperSize( GetPaperSize() );
    if ( GetStatus().AutoPageWidth() )
        aPaperSize.setWidth( !IsVertical() ? CalcTextWidth( true ) : GetTextHeight() );
    if ( GetStatus().AutoPageHeight() )
        aPaperSize.setHeight( !IsVertical() ? GetTextHeight() : CalcTextWidth( true ) );
 
    SetValidPaperSize( aPaperSize );    // consider Min, Max
 
    if ( aPaperSize != aPrevPaperSize )
    {
        if ( ( !IsVertical() && ( aPaperSize.Width() != aPrevPaperSize.Width() ) )
             || ( IsVertical() && ( aPaperSize.Height() != aPrevPaperSize.Height() ) ) )
        {
            // If ahead is centered / right or tabs ...
            aStatus.GetStatusWord() |= !IsVertical() ? 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* pParaPortion = GetParaPortions()[nPara];
                SvxAdjust eJustification = GetJustification( nPara );
                if ( eJustification != SvxAdjust::Left )
                {
                    pParaPortion->MarkSelectionInvalid( 0 );
                    CreateLines( nPara, 0 );  // 0: For AutoPageSize no TextRange!
                }
            }
        }
 
        Size aInvSize = aPaperSize;
        if ( aPaperSize.Width() < aPrevPaperSize.Width() )
            aInvSize.setWidth( aPrevPaperSize.Width() );
        if ( aPaperSize.Height() < aPrevPaperSize.Height() )
            aInvSize.setHeight( aPrevPaperSize.Height() );
 
        Size aSz( aInvSize );
        if ( IsVertical() )
        {
            aSz.setWidth( aInvSize.Height() );
            aSz.setHeight( aInvSize.Width() );
        }
        aInvalidRect = tools::Rectangle( Point(), aSz );
 
 
        for (EditView* pView : aEditViews)
        {
            pView->pImpEditView->RecalcOutputArea();
        }
    }
}
 
void ImpEditEngine::CheckPageOverflow()
{
    SAL_INFO("editeng.chaining", "[CONTROL_STATUS] AutoPageSize is " << (( aStatus.GetControlWord() & EEControlBits::AUTOPAGESIZE ) ? "ON" : "OFF") );
 
    sal_uInt32 nBoxHeight = GetMaxAutoPaperSize().Height();
    SAL_INFO("editeng.chaining", "[OVERFLOW-CHECK] Current MaxAutoPaperHeight is " << nBoxHeight);
 
    sal_uInt32 nTxtHeight = CalcTextHeight(nullptr);
    SAL_INFO("editeng.chaining", "[OVERFLOW-CHECK] Current Text Height is " << nTxtHeight);
 
    sal_uInt32 nParaCount = GetParaPortions().Count();
    sal_uInt32 nFirstLineCount = GetLineCount(0);
    bool bOnlyOneEmptyPara = (nParaCount == 1) &&
                            (nFirstLineCount == 1) &&
                            (GetLineLen(0,0) == 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
        //aStatus.SetPageOverflow(true);
        mbNeedsChainingHandling = true;
    } else
    {
        // No overflow if within box boundaries
        //aStatus.SetPageOverflow(false);
        mbNeedsChainingHandling = false;
    }
 
}
 
static sal_Int32 ImplCalculateFontIndependentLineSpacing( const sal_Int32 nFontHeight )
{
    return ( nFontHeight * 12 ) / 10;   // + 20%
}
 
bool ImpEditEngine::CreateLines( sal_Int32 nPara, sal_uInt32 nStartPosY )
{
    ParaPortion* pParaPortion = GetParaPortions()[nPara];
 
    // sal_Bool: Changes in the height of paragraph Yes / No - sal_True/sal_False
    DBG_ASSERT( pParaPortion->GetNode(), "Portion without Node in CreateLines" );
    DBG_ASSERT( pParaPortion->IsVisible(), "Invisible paragraphs not formatted!" );
    DBG_ASSERT( pParaPortion->IsInvalid(), "CreateLines: Portion not invalid!" );
 
    bool bProcessingEmptyLine = ( pParaPortion->GetNode()->Len() == 0 );
    bool bEmptyNodeWithPolygon = ( pParaPortion->GetNode()->Len() == 0 ) && GetTextRanger();
 
 
    // Fast special treatment for empty paragraphs ...
 
    if ( ( pParaPortion->GetNode()->Len() == 0 ) && !GetTextRanger() )
    {
        // fast special treatment ...
        if ( pParaPortion->GetTextPortions().Count() )
            pParaPortion->GetTextPortions().Reset();
        if ( pParaPortion->GetLines().Count() )
            pParaPortion->GetLines().Reset();
        CreateAndInsertEmptyLine( pParaPortion );
        return FinishCreateLines( pParaPortion );
    }
 
 
    // Initialization ......
 
 
    // Always format for 100%:
    bool bMapChanged = ImpCheckRefMapMode();
 
    if ( pParaPortion->GetLines().Count() == 0 )
    {
        EditLine* pL = new EditLine;
        pParaPortion->GetLines().Append(pL);
    }
 
 
    // Get Paragraph attributes  ......
 
    ContentNode* const pNode = pParaPortion->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 = pParaPortion->GetInvalidDiff();
    const sal_Int32 nInvalidStart = pParaPortion->GetInvalidPosStart();
    const sal_Int32 nInvalidEnd =  nInvalidStart + std::abs( nInvalidDiff );
 
    bool bQuickFormat = false;
    if ( !bEmptyNodeWithPolygon && !HasScriptType( nPara, i18n::ScriptType::COMPLEX ) )
    {
        if ( ( pParaPortion->IsSimpleInvalid() ) && ( nInvalidDiff > 0 ) &&
             ( pNode->GetString().indexOf( CH_FEATURE, nInvalidStart ) > nInvalidEnd ) )
        {
            bQuickFormat = true;
        }
        else if ( ( pParaPortion->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;
            sal_Int32 nPortions = pParaPortion->GetTextPortions().Count();
            for ( sal_Int32 nTP = 0; nTP < nPortions; nTP++ )
            {
                // There must be no start / end in the deleted area.
                const TextPortion& rTP = pParaPortion->GetTextPortions()[ nTP ];
                nPos = nPos + rTP.GetLen();
                if ( ( nPos > nStart ) && ( nPos < nEnd ) )
                {
                    bQuickFormat = false;
                    break;
                }
            }
        }
    }
 
    // Saving both layout mode and language (since I'm potentially changing both)
    GetRefDevice()->Push( PushFlags::TEXTLAYOUTMODE|PushFlags::TEXTLANGUAGE );
 
    ImplInitLayoutMode( GetRefDevice(), nPara, -1 );
 
    sal_Int32 nRealInvalidStart = nInvalidStart;
 
    if ( bEmptyNodeWithPolygon )
    {
        TextPortion* pDummyPortion = new TextPortion( 0 );
        pParaPortion->GetTextPortions().Reset();
        pParaPortion->GetTextPortions().Append(pDummyPortion);
    }
    else if ( bQuickFormat )
    {
        // faster Method:
        RecalcTextPortion( pParaPortion, nInvalidStart, nInvalidDiff );
    }
    else    // nRealInvalidStart can be before InvalidStart, since Portions were deleted....
    {
        CreateTextPortions( pParaPortion, nRealInvalidStart );
    }
 
 
    // Search for line with InvalidPos, start one line before
    // Flag the line => do not remove it !
 
 
    sal_Int32 nLine = pParaPortion->GetLines().Count()-1;
    for ( sal_Int32 nL = 0; nL <= nLine; nL++ )
    {
        EditLine& rLine = pParaPortion->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 && ( !pParaPortion->IsSimpleInvalid() || ( nInvalidEnd < pNode->Len() ) || ( nInvalidDiff <= 0 ) ) )
        nLine--;
 
    EditLine* pLine = &pParaPortion->GetLines()[nLine];
 
    static tools::Rectangle aZeroArea = tools::Rectangle( Point(), Point() );
    tools::Rectangle aBulletArea( aZeroArea );
    if ( !nLine )
    {
        aBulletArea = GetEditEnginePtr()->GetBulletArea( GetParaPortions().GetPos( pParaPortion ) );
        if ( aBulletArea.Right() > 0 )
            pParaPortion->SetBulletX( static_cast<sal_Int32>(GetXValue( aBulletArea.Right() )) );
        else
            pParaPortion->SetBulletX( 0 ); // if Bullet is set incorrectly
    }
 
 
    // Reformat all lines from here ...
 
    sal_Int32 nDelFromLine = -1;
    bool bLineBreak = false;
 
    sal_Int32 nIndex = pLine->GetStart();
    EditLine aSaveLine( *pLine );
    SvxFont aTmpFont( pNode->GetCharAttribs().GetDefFont() );
 
    ImplInitLayoutMode( GetRefDevice(), nPara, nIndex );
 
    bool bCalcCharPositions = true;
    std::unique_ptr<long[]> pBuf(new long[ pNode->Len() ]);
 
    bool bSameLineAgain = false;    // For TextRanger, if the height changes.
    TabInfo aCurrentTab;
 
    bool bForceOneRun = bEmptyNodeWithPolygon;
    bool bCompressedChars = false;
 
    while ( ( nIndex < pNode->Len() ) || bForceOneRun )
    {
        bForceOneRun = false;
 
        bool bEOL = false;
        bool bEOC = false;
        sal_Int32 nPortionStart = 0;
        sal_Int32 nPortionEnd = 0;
 
        long nStartX = GetXValue( rLRItem.GetTextLeft() + nSpaceBeforeAndMinLabelWidth );
        if ( nIndex == 0 )
        {
            long nFI = GetXValue( rLRItem.GetTextFirstLineOfst() );
            nStartX += nFI;
 
            if ( !nLine && ( pParaPortion->GetBulletX() > nStartX ) )
            {
                    nStartX = pParaPortion->GetBulletX();
            }
        }
 
        long nMaxLineWidth;
        if ( !IsVertical() )
            nMaxLineWidth = aStatus.AutoPageWidth() ? aMaxAutoPaperSize.Width() : aPaperSize.Width();
        else
            nMaxLineWidth = aStatus.AutoPageHeight() ? aMaxAutoPaperSize.Height() : aPaperSize.Height();
 
        nMaxLineWidth -= GetXValue( rLRItem.GetRight() );
        nMaxLineWidth -= nStartX;
 
        // If PaperSize == long_max, one cannot take away any negative
        // first line indent. (Overflow)
        if ( ( nMaxLineWidth < 0 ) && ( nStartX < 0 ) )
            nMaxLineWidth = ( !IsVertical() ? aPaperSize.Width() : aPaperSize.Height() ) - GetXValue( rLRItem.GetRight() );
 
        // If still less than 0, it may be just the right edge.
        if ( nMaxLineWidth <= 0 )
            nMaxLineWidth = 1;
 
        // 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
        // => ...
        if ( bCalcCharPositions )
            pLine->GetCharPosArray().clear();
 
        sal_Int32 nTmpPos = nIndex;
        sal_Int32 nTmpPortion = pLine->GetStartPortion();
        long nTmpWidth = 0;
        long nXWidth = nMaxLineWidth;
        if ( nXWidth <= nTmpWidth ) // while has to be looped once
            nXWidth = nTmpWidth+1;
 
        LongDqPtr pTextRanges = nullptr;
        long nTextExtraYOffset = 0;
        long nTextXOffset = 0;
        long nTextLineHeight = 0;
        if ( GetTextRanger() )
        {
            GetTextRanger()->SetVertical( IsVertical() );
 
            long nTextY = nStartPosY + GetEditCursor( pParaPortion, pLine->GetStart() ).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 )
            {
                long nYOff = nTextY + nTextExtraYOffset;
                long nYDiff = nTextLineHeight;
                if ( IsVertical() )
                {
                    long nMaxPolygonX = GetTextRanger()->GetBoundRect().Right();
                    nYOff = nMaxPolygonX-nYOff;
                    nYDiff = -nTextLineHeight;
                }
                pTextRanges = GetTextRanger()->GetTextRanges( Range( nYOff, nYOff + nYDiff ) );
                DBG_ASSERT( pTextRanges, "GetTextRanges?!" );
                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())
                {
                    long nA = pTextRanges->at(0);
                    long nB = pTextRanges->at(1);
                    DBG_ASSERT( nA <= nB, "TextRange distorted?" );
                    long nW = nB - nA;
                    if ( nW > nMaxRangeWidth )
                    {
                        nMaxRangeWidth = nW;
                        nTextXOffset = nA;
                    }
                }
                nXWidth = nMaxRangeWidth;
                if ( nXWidth )
                    nMaxLineWidth = nXWidth - nStartX - GetXValue( rLRItem.GetRight() );
                else
                {
                    // Try further down in the polygon.
                    // Below the polygon use the Paper Width.
                    nTextExtraYOffset += std::max( static_cast<long>(nTextLineHeight / 10), long(1) );
                    if ( ( nTextY + nTextExtraYOffset  ) > GetTextRanger()->GetBoundRect().Bottom() )
                    {
                        nXWidth = !IsVertical() ? GetPaperSize().Width() : GetPaperSize().Height();
                        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 = pParaPortion->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 = &pParaPortion->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();
                pParaPortion->GetTextPortions().Remove( nTmpPortion );
                if (nTmpPortion && nTmpLen)
                {
                    nTmpPortion--;
                    TextPortion& rPrev = pParaPortion->GetTextPortions()[nTmpPortion];
                    DBG_ASSERT( rPrev.GetKind() == PortionKind::TEXT, "Portion?!" );
                    nTmpWidth -= rPrev.GetSize().Width();
                    nTmpPos = nTmpPos - rPrev.GetLen();
                    rPrev.SetLen(rPrev.GetLen() + nTmpLen);
                    rPrev.GetSize().setWidth( -1 );
                }
 
                DBG_ASSERT( nTmpPortion < pParaPortion->GetTextPortions().Count(), "No more Portions left!" );
                pPortion = &pParaPortion->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:
                    {
                        long nOldTmpWidth = nTmpWidth;
 
                        // Search for Tab-Pos...
                        long nCurPos = nTmpWidth+nStartX;
                        // consider scaling
                        if ( aStatus.DoStretch() && ( nStretchX != 100 ) )
                            nCurPos = nCurPos*100/std::max(static_cast<sal_Int32>(nStretchX), static_cast<sal_Int32>(1));
 
                        short nAllSpaceBeforeText = static_cast< short >(rLRItem.GetTextLeft()/* + rLRItem.GetTextLeft()*/ + nSpaceBeforeAndMinLabelWidth);
                        aCurrentTab.aTabStop = pNode->GetContentAttribs().FindTabStop( nCurPos - nAllSpaceBeforeText /*rLRItem.GetTextLeft()*/, aEditDoc.GetDefTab() );
                        aCurrentTab.nTabPos = GetXValue( static_cast<long>( aCurrentTab.aTabStop.GetTabPos() + nAllSpaceBeforeText /*rLRItem.GetTextLeft()*/ ) );
                        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;
                        }
 
                        pPortion->SetKind(PortionKind::TAB);
                        pPortion->SetExtraValue( aCurrentTab.aTabStop.GetFill() );
                        pPortion->GetSize().setWidth( aCurrentTab.nTabPos - (nTmpWidth+nStartX) );
 
                        // Height needed...
                        SeekCursor( pNode, nTmpPos+1, aTmpFont );
                        pPortion->GetSize().setHeight( aTmpFont.QuickGetTextSize( GetRefDevice(), OUString(), 0, 0 ).Height() );
 
                        DBG_ASSERT( pPortion->GetSize().Width() >= 0, "Tab incorrectly calculated!" );
 
                        nTmpWidth = aCurrentTab.nTabPos-nStartX;
 
                        // If this is the first token on the line,
                        // and nTmpWidth > aPaperSize.Width, => infinite loop!
                        if ( ( nTmpWidth >= nXWidth ) && ( nTmpPortion == pLine->GetStartPortion() ) )
                        {
                            // What now?
                            // make the tab fitting
                            pPortion->GetSize().setWidth( nXWidth-nOldTmpWidth );
                            nTmpWidth = nXWidth-1;
                            bEOL = true;
                            bBrokenLine = true;
                        }
                        EditLine::CharPosArrayType& rArray = pLine->GetCharPosArray();
                        size_t nPos = nTmpPos - pLine->GetStart();
                        rArray.insert(rArray.begin()+nPos, pPortion->GetSize().Width());
                        bCompressedChars = false;
                    }
                    break;
                    case EE_FEATURE_LINEBR:
                    {
                        DBG_ASSERT( pPortion, "?!" );
                        pPortion->GetSize().setWidth( 0 );
                        bEOL = true;
                        bLineBreak = true;
                        pPortion->SetKind( PortionKind::LINEBREAK );
                        bCompressedChars = false;
                        EditLine::CharPosArrayType& 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 );
                        sal_Unicode cChar = 0;  // later: NBS?
                        aTmpFont.SetPhysFont( GetRefDevice() );
                        ImplInitDigitMode(GetRefDevice(), aTmpFont.GetLanguage());
 
                        OUString aFieldValue = cChar ? OUString(cChar) : static_cast<const EditCharAttribField*>(pNextFeature)->GetFieldValue();
                        if ( bCalcCharPositions || !pPortion->HasValidSize() )
                        {
                            // get size, but also DXArray to allow length information in line breaking below
                            const sal_Int32 nLength(aFieldValue.getLength());
                            std::unique_ptr<long[]> pTmpDXArray(new long[nLength]);
                            pPortion->GetSize() = aTmpFont.QuickGetTextSize(GetRefDevice(), aFieldValue, 0, aFieldValue.getLength(), pTmpDXArray.get());
 
                            // So no scrolling for oversized fields
                            if ( pPortion->GetSize().Width() > nXWidth )
                            {
                                // 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);
 
                                // 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(pTmpDXArray[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);
                                            }
 
                                            // moveLineStart forward in X
                                            nLineStartX = pTmpDXArray[nLastCellBreak];
                                        }
 
                                        // update CellBreak iteration values
                                        nLastCellBreak = a;
                                        nNextCellBreak = xBreakIterator->nextCharacters(
                                            aFieldValue,
                                            a,
                                            aLocale,
                                            css::i18n::CharacterIteratorMode::SKIPCELL,
                                            1,
                                            nDone);
                                    }
                                }
                            }
                        }
                        nTmpWidth += pPortion->GetSize().Width();
                        EditLine::CharPosArrayType& rArray = pLine->GetCharPosArray();
                        size_t nPos = nTmpPos - pLine->GetStart();
                        rArray.insert(rArray.begin()+nPos, pPortion->GetSize().Width());
                        pPortion->SetKind( cChar ? PortionKind::TEXT : PortionKind::FIELD );
                        // If this is the first token on the line,
                        // and nTmpWidth > aPaperSize.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 ( bCalcCharPositions || !pPortion->HasValidSize() )
                {
                    if (bContinueLastPortion)
                    {
                         Size aSize( aTmpFont.QuickGetTextSize( GetRefDevice(),
                                pParaPortion->GetNode()->GetString(), nTmpPos, nPortionLen, pBuf.get() ));
                         pPortion->GetSize().AdjustWidth(aSize.Width() );
                         if (pPortion->GetSize().Height() < aSize.Height())
                             pPortion->GetSize().setHeight( aSize.Height() );
                    }
                    else
                    {
                        pPortion->GetSize() = aTmpFont.QuickGetTextSize( GetRefDevice(),
                                pParaPortion->GetNode()->GetString(), nTmpPos, nPortionLen, pBuf.get() );
                    }
 
                    // #i9050# Do Kerning also behind portions...
                    if ( ( aTmpFont.GetFixKerning() > 0 ) && ( ( nTmpPos + nPortionLen ) < pNode->Len() ) )
                        pPortion->GetSize().AdjustWidth(aTmpFont.GetFixKerning() );
                    if ( IsFixedCellHeight() )
                        pPortion->GetSize().setHeight( ImplCalculateFontIndependentLineSpacing( aTmpFont.GetFontHeight() ) );
                }
                if ( bCalcCharPositions )
                {
                    // The array is  generally flattened at the beginning
                    // => Always simply quick inserts.
                    size_t nPos = nTmpPos - pLine->GetStart();
                    EditLine::CharPosArrayType& rArray = pLine->GetCharPosArray();
                    rArray.insert( rArray.begin() + nPos, pBuf.get(), pBuf.get() + nPortionLen);
                }
 
                // And now check for Compression:
                if ( !bContinueLastPortion && nPortionLen && GetAsianCompressionMode() != CharCompressType::NONE )
                {
                    EditLine::CharPosArrayType& rArray = pLine->GetCharPosArray();
                    long* 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 )
                    {
                        long nExtraSpace = pPortion->GetSize().Height()/5;
                        nExtraSpace = GetXValue( nExtraSpace );
                        pPortion->GetSize().AdjustWidth(nExtraSpace );
                        nTmpWidth += nExtraSpace;
                    }
                }
            }
 
            if ( aCurrentTab.bValid && ( nTmpPortion != aCurrentTab.nTabPortion ) )
            {
                long nWidthAfterTab = 0;
                for ( sal_Int32 n = aCurrentTab.nTabPortion+1; n <= nTmpPortion; n++  )
                {
                    const TextPortion& rTP = pParaPortion->GetTextPortions()[n];
                    nWidthAfterTab += rTP.GetSize().Width();
                }
                long nW = nWidthAfterTab;   // Length before tab position
                if ( aCurrentTab.aTabStop.GetAdjustment() == SvxTabAdjust::Right )
                {
                }
                else if ( aCurrentTab.aTabStop.GetAdjustment() == SvxTabAdjust::Center )
                {
                    nW = nWidthAfterTab/2;
                }
                else if ( aCurrentTab.aTabStop.GetAdjustment() == SvxTabAdjust::Decimal )
                {
                    OUString aText = GetSelected( EditSelection(  EditPaM( pParaPortion->GetNode(), nTmpPos ),
                                                                EditPaM( pParaPortion->GetNode(), nTmpPos + nPortionLen ) ) );
                    sal_Int32 nDecPos = aText.indexOf( aCurrentTab.aTabStop.GetDecimal() );
                    if ( nDecPos != -1 )
                    {
                        nW -= pParaPortion->GetTextPortions()[nTmpPortion].GetSize().Width();
                        nW += aTmpFont.QuickGetTextSize( GetRefDevice(), pParaPortion->GetNode()->GetString(), nTmpPos, nDecPos ).Width();
                        aCurrentTab.bValid = false;
                    }
                }
                else
                {
                    OSL_FAIL( "CreateLines: Tab not handled!" );
                }
                long nMaxW = aCurrentTab.nTabPos - aCurrentTab.nStartPosX - nStartX;
                if ( nW >= nMaxW )
                {
                    nW = nMaxW;
                    aCurrentTab.bValid = false;
                }
                TextPortion& rTabPortion = pParaPortion->GetTextPortions()[aCurrentTab.nTabPortion];
                rTabPortion.GetSize().setWidth( aCurrentTab.nTabPos - aCurrentTab.nStartPosX - nW - nStartX );
                nTmpWidth = aCurrentTab.nStartPosX + rTabPortion.GetSize().Width() + nWidthAfterTab;
            }
 
            nTmpPos = nTmpPos + nPortionLen;
            nPortionEnd = nTmpPos;
            nTmpPortion++;
            if ( aStatus.OneCharPerLine() )
                bEOL = true;
        }
 
        DBG_ASSERT( pPortion, "no portion!?" );
 
        aCurrentTab.bValid = false;
 
        // this was possibly a portion too far:
        bool bFixedEnd = false;
        if ( aStatus.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( pParaPortion, nTmpPos, pLine );
                nTmpWidth += pParaPortion->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 );
            DBG_ASSERT( pParaPortion->GetTextPortions().Count(), "No TextPortions?" );
            pLine->SetEndPortion( pParaPortion->GetTextPortions().Count() - 1 );
        }
 
        if ( aStatus.OneCharPerLine() )
        {
            pLine->SetEnd( nPortionEnd );
            pLine->SetEndPortion( nTmpPortion-1 );
        }
        else if ( bFixedEnd )
        {
            pLine->SetEnd( nPortionStart );
            pLine->SetEndPortion( nTmpPortion-1 );
        }
        else if ( bLineBreak || bBrokenLine )
        {
            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?!" );
            long nRemainingWidth = nMaxLineWidth - nTmpWidth;
            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...
                long* pDXArray = pLine->GetCharPosArray().data() + (nPortionStart - pLine->GetStart());
                ImplCalcAsianCompression(
                    pNode, pPortion, nPortionStart, pDXArray, 10000, true);
            }
            if( pPortion )
                ImpBreakLine( pParaPortion, pLine, pPortion, nPortionStart,
                                                nRemainingWidth, bCanHyphenate && bHyphenatePara );
        }
 
 
        // Line finished => adjust
 
 
        // CalcTextSize should be replaced by a continuous registering!
        Size aTextSize = pLine->CalcTextSize( *pParaPortion );
 
        if ( aTextSize.Height() == 0 )
        {
            SeekCursor( pNode, pLine->GetStart()+1, aTmpFont );
            aTmpFont.SetPhysFont( pRefDev );
            ImplInitDigitMode(pRefDev, aTmpFont.GetLanguage());
 
            if ( IsFixedCellHeight() )
                aTextSize.setHeight( ImplCalculateFontIndependentLineSpacing( aTmpFont.GetFontHeight() ) );
            else
                aTextSize.setHeight( aTmpFont.GetPhysTxtSize( pRefDev ).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 = pParaPortion->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 && !aStatus.IsOutliner() )
        {
            if ( rLSItem.GetLineSpaceRule() == SvxLineSpaceRule::Min )
            {
                sal_uInt16 nMinHeight = GetYValue( rLSItem.GetLineHeight() );
                sal_uInt16 nTxtHeight = pLine->GetHeight();
                if ( nTxtHeight < nMinHeight )
                {
                    // The Ascent has to be adjusted for the difference:
                    long nDiff = nMinHeight - nTxtHeight;
                    pLine->SetMaxAscent( static_cast<sal_uInt16>(pLine->GetMaxAscent() + nDiff) );
                    pLine->SetHeight( nMinHeight, nTxtHeight );
                }
            }
            else if ( rLSItem.GetLineSpaceRule() == SvxLineSpaceRule::Fix )
            {
                sal_uInt16 nFixHeight = GetYValue( rLSItem.GetLineHeight() );
                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
                if ( rLSItem.GetPropLineSpace() && ( rLSItem.GetPropLineSpace() < 100 ) )
                {
                    // Adapted code from sw/source/core/text/itrform2.cxx
                    sal_uInt16 nPropLineSpace = rLSItem.GetPropLineSpace();
                    sal_uInt16 nAscent = pLine->GetMaxAscent();
                    sal_uInt16 nNewAscent = pLine->GetTxtHeight() * nPropLineSpace / 100 * 4 / 5; // 80%
                    if ( !nAscent || nAscent > nNewAscent )
                    {
                        sal_uInt16 nHeight = pLine->GetHeight() * nPropLineSpace / 100;
                        pLine->SetHeight( nHeight, pLine->GetTxtHeight() );
                        pLine->SetMaxAscent( nNewAscent );
                    }
                }
                else if ( rLSItem.GetPropLineSpace() && ( rLSItem.GetPropLineSpace() != 100 ) )
                {
                    sal_uInt16 nTxtHeight = pLine->GetHeight();
                    sal_Int32 nPropTextHeight = nTxtHeight * rLSItem.GetPropLineSpace() / 100;
                    // The Ascent has to be adjusted for the difference:
                    long nDiff = pLine->GetHeight() - nPropTextHeight;
                    pLine->SetMaxAscent( static_cast<sal_uInt16>( pLine->GetMaxAscent() - nDiff ) );
                    pLine->SetHeight( static_cast<sal_uInt16>( nPropTextHeight ), nTxtHeight );
                }
            }
        }
 
        if ( ( !IsVertical() && aStatus.AutoPageWidth() ) ||
             ( IsVertical() && aStatus.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.
            long nMaxLineWidthFix = ( !IsVertical() ? aPaperSize.Width() : aPaperSize.Height() )
                                        - GetXValue( rLRItem.GetRight() ) - nStartX;
            if ( aTextSize.Width() < nMaxLineWidthFix )
                nMaxLineWidth = nMaxLineWidthFix;
        }
 
        if ( bCompressedChars )
        {
            long nRemainingWidth = nMaxLineWidth - aTextSize.Width();
            if ( nRemainingWidth > 0 )
            {
                ImplExpandCompressedPortions( pLine, pParaPortion, nRemainingWidth );
                aTextSize = pLine->CalcTextSize( *pParaPortion );
            }
        }
 
        if ( pLine->IsHangingPunctuation() )
        {
            // Width from HangingPunctuation was set to 0 in ImpBreakLine,
            // check for rel width now, maybe create compression...
            long n = nMaxLineWidth - aTextSize.Width();
            TextPortion& rTP = pParaPortion->GetTextPortions()[pLine->GetEndPortion()];
            sal_Int32 nPosInArray = pLine->GetEnd()-1-pLine->GetStart();
            long nNewValue = ( nPosInArray ? pLine->GetCharPosArray()[ nPosInArray-1 ] : 0 ) + n;
            pLine->GetCharPosArray()[ nPosInArray ] = nNewValue;
            rTP.GetSize().AdjustWidth(n );
        }
 
        pLine->SetTextWidth( aTextSize.Width() );
        switch ( eJustification )
        {
            case SvxAdjust::Center:
            {
                long n;
                if(IsHoriAlignIgnoreTrailingWhitespace())
                    n = ( nMaxLineWidth - CalcLineWidth( pParaPortion, pLine, false, true ) ) / 2;
                else
                    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!
                long n;
                if(IsHoriAlignIgnoreTrailingWhitespace())
                    n = nMaxLineWidth - CalcLineWidth( pParaPortion, pLine, false, true );
                else
                    n = nMaxLineWidth - aTextSize.Width();
                n += nStartX;  // Indentation is kept.
                pLine->SetStartPosX( n );
            }
            break;
            case SvxAdjust::Block:
            {
                bool bDistLastLine = (GetJustifyMethod(nPara) == SvxCellJustifyMethod::Distribute);
                long nRemainingSpace = nMaxLineWidth - aTextSize.Width();
                pLine->SetStartPosX( nStartX );
                if ( nRemainingSpace > 0 && (!bEOC || bDistLastLine) )
                    ImpAdjustBlocks( pParaPortion, 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:
        if ( bCalcCharPositions )
        {
            EditLine::CharPosArrayType& 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 ( pParaPortion->IsSimpleInvalid() )
        {
            // Change through simple Text changes ...
            // Do mot 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 ( bCalcCharPositions && bQuickFormat )
                        {
                            bLineBreak = false;
                            pParaPortion->CorrectValuesBehindLastFormattedLine( nLine );
                            break;
                        }
                    }
                }
                else if ( bCalcCharPositions && 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;
                        pParaPortion->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();
 
            // Next line or maybe a new line....
            pLine = nullptr;
            if ( nLine < pParaPortion->GetLines().Count()-1 )
                pLine = &pParaPortion->GetLines()[++nLine];
            if ( pLine && ( nIndex >= pNode->Len() ) )
            {
                nDelFromLine = nLine;
                break;
            }
            if ( !pLine )
            {
                if ( nIndex < pNode->Len() )
                {
                    pLine = new EditLine;
                    pParaPortion->GetLines().Insert(++nLine, 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 );
                    pParaPortion->GetTextPortions().Append(pDummyPortion);
                    pLine = new EditLine;
                    pParaPortion->GetLines().Insert(++nLine, 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 )
        pParaPortion->GetLines().DeleteFromLine( nDelFromLine );
 
    DBG_ASSERT( pParaPortion->GetLines().Count(), "No line after CreateLines!" );
 
    if ( bLineBreak )
        CreateAndInsertEmptyLine( pParaPortion );
 
    pBuf.reset();
 
    bool bHeightChanged = FinishCreateLines( pParaPortion );
 
    if ( bMapChanged )
        GetRefDevice()->Pop();
 
    GetRefDevice()->Pop();
 
    return bHeightChanged;
}
 
void ImpEditEngine::CreateAndInsertEmptyLine( ParaPortion* pParaPortion )
{
    DBG_ASSERT( !GetTextRanger(), "Don't use CreateAndInsertEmptyLine with a polygon!" );
 
    EditLine* pTmpLine = new EditLine;
    pTmpLine->SetStart( pParaPortion->GetNode()->Len() );
    pTmpLine->SetEnd( pParaPortion->GetNode()->Len() );
    pParaPortion->GetLines().Append(pTmpLine);
 
    bool bLineBreak = pParaPortion->GetNode()->Len() > 0;
    sal_Int32 nSpaceBefore = 0;
    sal_Int32 nSpaceBeforeAndMinLabelWidth = GetSpaceBeforeAndMinLabelWidth( pParaPortion->GetNode(), &nSpaceBefore );
    const SvxLRSpaceItem& rLRItem = GetLRSpaceItem( pParaPortion->GetNode() );
    const SvxLineSpacingItem& rLSItem = pParaPortion->GetNode()->GetContentAttribs().GetItem( EE_PARA_SBL );
    long nStartX = GetXValue( rLRItem.GetTextLeft() + rLRItem.GetTextFirstLineOfst() + nSpaceBefore );
 
    tools::Rectangle aBulletArea = tools::Rectangle( Point(), Point() );
    if ( bLineBreak )
    {
        nStartX = GetXValue( rLRItem.GetTextLeft() + rLRItem.GetTextFirstLineOfst() + nSpaceBeforeAndMinLabelWidth );
    }
    else
    {
        aBulletArea = GetEditEnginePtr()->GetBulletArea( GetParaPortions().GetPos( pParaPortion ) );
        if ( aBulletArea.Right() > 0 )
            pParaPortion->SetBulletX( static_cast<sal_Int32>(GetXValue( aBulletArea.Right() )) );
        else
            pParaPortion->SetBulletX( 0 ); // If Bullet set incorrectly.
        if ( pParaPortion->GetBulletX() > nStartX )
        {
            nStartX = GetXValue( rLRItem.GetTextLeft() + rLRItem.GetTextFirstLineOfst() + nSpaceBeforeAndMinLabelWidth );
            if ( pParaPortion->GetBulletX() > nStartX )
                nStartX = pParaPortion->GetBulletX();
        }
    }
 
    SvxFont aTmpFont;
    SeekCursor( pParaPortion->GetNode(), bLineBreak ? pParaPortion->GetNode()->Len() : 0, aTmpFont );
    aTmpFont.SetPhysFont( pRefDev );
 
    TextPortion* pDummyPortion = new TextPortion( 0 );
    pDummyPortion->GetSize() = aTmpFont.GetPhysTxtSize( pRefDev );
    if ( IsFixedCellHeight() )
        pDummyPortion->GetSize().setHeight( ImplCalculateFontIndependentLineSpacing( aTmpFont.GetFontHeight() ) );
    pParaPortion->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 ( !aStatus.IsOutliner() )
    {
        sal_Int32 nPara = GetParaPortions().GetPos( pParaPortion );
        SvxAdjust eJustification = GetJustification( nPara );
        long nMaxLineWidth = !IsVertical() ? aPaperSize.Width() : aPaperSize.Height();
        nMaxLineWidth -= GetXValue( 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 ( !aStatus.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:
                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( pParaPortion );
            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:
                    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 )
    {
        long nMinHeight = aBulletArea.GetHeight();
        if ( nMinHeight > static_cast<long>(pTmpLine->GetHeight()) )
        {
            long nDiff = nMinHeight - static_cast<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 = pParaPortion->GetLines()[pParaPortion->GetLines().Count()-2];
        DBG_ASSERT( rLastLine.GetEnd() == pParaPortion->GetNode()->Len(), "different anyway?" );
#endif
        sal_Int32 nPos = pParaPortion->GetTextPortions().Count() - 1 ;
        pTmpLine->SetStartPortion( nPos );
        pTmpLine->SetEndPortion( nPos );
    }
}
 
bool ImpEditEngine::FinishCreateLines( ParaPortion* pParaPortion )
{
//  CalcCharPositions( pParaPortion );
    pParaPortion->SetValid();
    long nOldHeight = pParaPortion->GetHeight();
    CalcHeight( pParaPortion );
 
    DBG_ASSERT( pParaPortion->GetTextPortions().Count(), "FinishCreateLines: No Text-Portion?" );
    bool bRet = ( pParaPortion->GetHeight() != nOldHeight );
    return bRet;
}
 
void ImpEditEngine::ImpBreakLine( ParaPortion* pParaPortion, EditLine* pLine, TextPortion const * pPortion, sal_Int32 nPortionStart, long nRemainingWidth, bool bCanHyphenate )
{
    ContentNode* const pNode = pParaPortion->GetNode();
 
    sal_Int32 nBreakInLine = nPortionStart - pLine->GetStart();
    sal_Int32 nMax = nBreakInLine + pPortion->GetLen();
    while ( ( nBreakInLine < nMax ) && ( pLine->GetCharPosArray()[nBreakInLine] < nRemainingWidth ) )
        nBreakInLine++;
 
    sal_Int32 nMaxBreakPos = nBreakInLine + pLine->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 + pLine->GetStart() ) ) && ( pNode->GetChar( nMaxBreakPos ) == ' ' ) )
    {
        // Break behind the blank, blank will be compressed...
        nBreakPos = nMaxBreakPos + 1;
        bCompressBlank = true;
    }
    else
    {
        sal_Int32 nMinBreakPos = pLine->GetStart();
        const CharAttribList::AttribsType& rAttrs = pNode->GetCharAttribs().GetAttribs();
        for (size_t nAttr = rAttrs.size(); nAttr; )
        {
            const EditCharAttrib& rAttr = *rAttrs[--nAttr].get();
            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;
 
            i18n::LineBreakResults aLBR = _xBI->getLineBreak(
                pNode->GetString(), nMaxBreakPos, aLocale, nMinBreakPos, aHyphOptions, aUserOptions );
            nBreakPos = aLBR.breakIndex;
 
            // 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;
            }
 
            // nBreakPos can never be outside the portion, even not with hanging punctuation
            if ( nBreakPos > nMaxBreakPos )
                nBreakPos = nMaxBreakPos;
        }
 
        // 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;
        pLine->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 getWordBoudary 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 (xHyphenator.is())
                    xHyphWord = xHyphenator->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) >= (pLine->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() );
                            OUString aAltLeft(aAlt.copy(0, _nWordLen));
                            OUString aAltRight(aAlt.copy(_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 <= pLine->GetStart() )
        {
            // No separator in line => Chop!
            nBreakPos = nMaxBreakPos;
            // I18N nextCharacters !
            if ( nBreakPos <= pLine->GetStart() )
                nBreakPos = pLine->GetStart() + 1;  // Otherwise infinite loop!
        }
    }
 
    // the dickey portion is the end portion
    pLine->SetEnd( nBreakPos );
 
    sal_Int32 nEndPortion = SplitTextPortion( pParaPortion, nBreakPos, pLine );
 
    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...
        DBG_ASSERT( nBreakPos > pLine->GetStart(), "ImpBreakLines - BreakPos not expected!" );
        if ( pNode->GetChar( nBreakPos-1 ) == ' ' )
            bCompressBlank = true;
    }
 
    if ( bCompressBlank || bHangingPunctuation )
    {
        TextPortion& rTP = pParaPortion->GetTextPortions()[nEndPortion];
        DBG_ASSERT( rTP.GetKind() == PortionKind::TEXT, "BlankRubber: No TextPortion!" );
        DBG_ASSERT( nBreakPos > pLine->GetStart(), "SplitTextPortion at the beginning of the line?" );
        sal_Int32 nPosInArray = nBreakPos - 1 - pLine->GetStart();
        rTP.GetSize().setWidth( ( nPosInArray && ( rTP.GetLen() > 1 ) ) ? pLine->GetCharPosArray()[ nPosInArray-1 ] : 0 );
        pLine->GetCharPosArray()[ nPosInArray ] = rTP.GetSize().Width();
    }
    else if ( bHyphenated )
    {
        // A portion for inserting the separator ...
        TextPortion* pHyphPortion = new TextPortion( 0 );
        pHyphPortion->SetKind( PortionKind::HYPHENATOR );
        OUString aHyphText(CH_HYPH);
        if ( (cAlternateReplChar || cAlternateExtraChar) && bAltFullRight ) // alternation after the break doesn't supported
        {
            TextPortion& rPrev = pParaPortion->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.GetSize().setWidth(
                pLine->GetCharPosArray()[ nBreakPos-1 - pLine->GetStart() - nAltDelChar ] );
        }
 
        // Determine the width of the Hyph-Portion:
        SvxFont aFont;
        SeekCursor( pParaPortion->GetNode(), nBreakPos, aFont );
        aFont.SetPhysFont( GetRefDevice() );
        pHyphPortion->GetSize().setHeight( GetRefDevice()->GetTextHeight() );
        pHyphPortion->GetSize().setWidth( GetRefDevice()->GetTextWidth( aHyphText ) );
 
        pParaPortion->GetTextPortions().Insert(++nEndPortion, pHyphPortion);
    }
    pLine->SetEndPortion( nEndPortion );
}
 
void ImpEditEngine::ImpAdjustBlocks( ParaPortion* pParaPortion, EditLine* pLine, long nRemainingSpace )
{
    DBG_ASSERT( nRemainingSpace > 0, "AdjustBlocks: Somewhat too little..." );
    DBG_ASSERT( pLine, "AdjustBlocks: Line ?!" );
    if ( ( nRemainingSpace < 0 ) || pLine->IsEmpty() )
        return ;
 
    const sal_Int32 nFirstChar = pLine->GetStart();
    const sal_Int32 nLastChar = pLine->GetEnd() -1;    // Last points behind
    ContentNode* pNode = pParaPortion->GetNode();
 
    DBG_ASSERT( nLastChar < pNode->Len(), "AdjustBlocks: Out of range!" );
 
    // Search blanks or Kashidas...
    std::vector<sal_Int32> aPositions;
    sal_uInt16 nLastScript = i18n::ScriptType::LATIN;
    for ( sal_Int32 nChar = nFirstChar; nChar <= nLastChar; nChar++ )
    {
        EditPaM aPaM( pNode, nChar+1 );
        LanguageType eLang = GetLanguage(aPaM);
        sal_uInt16 nScript = GetI18NScriptType(aPaM);
        if ( MsLangId::getPrimaryLanguage( eLang) == LANGUAGE_ARABIC_PRIMARY_ONLY )
            // Arabic script is handled later.
            continue;
 
        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;
    }
 
    // Kashidas ?
    ImpFindKashidas( pNode, nFirstChar, nLastChar, aPositions );
 
    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 ) &&
         ( MsLangId::getPrimaryLanguage( GetLanguage( EditPaM( pNode, nLastChar ) ) ) != LANGUAGE_ARABIC_PRIMARY_ONLY ) )
    {
        aPositions.pop_back();
        sal_Int32 nPortionStart, nPortion;
        nPortion = pParaPortion->GetTextPortions().FindPortion( nLastChar+1, nPortionStart );
        TextPortion& rLastPortion = pParaPortion->GetTextPortions()[ nPortion ];
        long nRealWidth = pLine->GetCharPosArray()[nLastChar-nFirstChar];
        long nBlankWidth = nRealWidth;
        if ( nLastChar > nPortionStart )
            nBlankWidth -= pLine->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.GetSize().AdjustWidth( -nBlankWidth );
            nRemainingSpace += nBlankWidth;
        }
        pLine->GetCharPosArray()[nLastChar-nFirstChar] -= nBlankWidth;
    }
 
    size_t nGaps = aPositions.size();
    const long nMore4Everyone = nRemainingSpace / nGaps;
    long nSomeExtraSpace = nRemainingSpace - nMore4Everyone*nGaps;
 
    DBG_ASSERT( nSomeExtraSpace < static_cast<long>(nGaps), "AdjustBlocks: ExtraSpace too large" );
    DBG_ASSERT( nSomeExtraSpace >= 0, "AdjustBlocks: ExtraSpace < 0 " );
 
    // 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 = pParaPortion->GetTextPortions().FindPortion( nChar, nPortionStart, true );
            TextPortion& rLastPortion = pParaPortion->GetTextPortions()[ nPortion ];
 
            // The width of the portion:
            rLastPortion.GetSize().AdjustWidth(nMore4Everyone );
            if ( nSomeExtraSpace )
                rLastPortion.GetSize().AdjustWidth( 1 );
 
            // Correct positions in array
            // Even for kashidas just change positions, VCL will then draw the kashida automatically
            sal_Int32 nPortionEnd = nPortionStart + rLastPortion.GetLen();
            for ( sal_Int32 _n = nChar; _n < nPortionEnd; _n++ )
            {
                pLine->GetCharPosArray()[_n-nFirstChar] += nMore4Everyone;
                if ( nSomeExtraSpace )
                    pLine->GetCharPosArray()[_n-nFirstChar]++;
            }
 
            if ( nSomeExtraSpace )
                nSomeExtraSpace--;
        }
    }
 
    // Now the text width contains the extra width...
    pLine->SetTextWidth( pLine->GetTextWidth() + nRemainingSpace );
}
 
void ImpEditEngine::ImpFindKashidas( ContentNode* pNode, sal_Int32 nStart, sal_Int32 nEnd, std::vector<sal_Int32>& rArray )
{
    // 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 );
 
    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 );
 
        // restore selection for proper iteration at the end of the function
        aWordSel.Max().SetIndex( nSavPos );
 
        sal_Int32 nIdx = 0;
        sal_Int32 nKashidaPos = -1;
        sal_Unicode cCh;
        sal_Unicode cPrevCh = 0;
 
        while ( nIdx < aWord.getLength() )
        {
            cCh = aWord[ nIdx ];
 
            // 1. Priority:
            // after user inserted kashida
            if ( 0x640 == cCh )
            {
                nKashidaPos = aWordSel.Min().GetIndex() + nIdx;
                break;
            }
 
            // 2. Priority:
            // after a Seen or Sad
            if ( nIdx + 1 < aWord.getLength() &&
                 ( 0x633 == cCh || 0x635 == cCh ) )
            {
                nKashidaPos = aWordSel.Min().GetIndex() + nIdx;
                break;
            }
 
            // 3. Priority:
            // before final form of the Marbuta, Hah, Dal
            // 4. Priority:
            // before final form of Alef, Lam or Kaf
            if ( nIdx && nIdx + 1 == aWord.getLength() &&
                 ( 0x629 == cCh || 0x62D == cCh || 0x62F == cCh ||
                   0x627 == cCh || 0x644 == cCh || 0x643 == cCh ) )
            {
                DBG_ASSERT( 0 != cPrevCh, "No previous character" );
 
                // check if character is connectable to previous character,
                if ( lcl_ConnectToPrev( cCh, cPrevCh ) )
                {
                    nKashidaPos = aWordSel.Min().GetIndex() + nIdx - 1;
                    break;
                }
            }
 
            // 5. Priority:
            // before media Bah
            if ( nIdx && nIdx + 1 < aWord.getLength() && 0x628 == cCh )
            {
                DBG_ASSERT( 0 != cPrevCh, "No previous character" );
 
                // check if next character is Reh, Yeh or Alef Maksura
                sal_Unicode cNextCh = aWord[ nIdx + 1 ];
 
                if ( 0x631 == cNextCh || 0x64A == cNextCh ||
                     0x649 == cNextCh )
                {
                    // check if character is connectable to previous character,
                    if ( lcl_ConnectToPrev( cCh, cPrevCh ) )
                        nKashidaPos = aWordSel.Min().GetIndex() + nIdx - 1;
                }
            }
 
            // 6. Priority:
            // other connecting possibilities
            if ( nIdx && nIdx + 1 == aWord.getLength() &&
                 0x60C <= cCh && 0x6FE >= cCh )
            {
                DBG_ASSERT( 0 != cPrevCh, "No previous character" );
 
                // check if character is connectable to previous character,
                if ( lcl_ConnectToPrev( cCh, cPrevCh ) )
                {
                    // only choose this position if we did not find
                    // a better one:
                    if ( nKashidaPos<0 )
                        nKashidaPos = aWordSel.Min().GetIndex() + nIdx - 1;
                    break;
                }
            }
 
            // Do not consider Fathatan, Dammatan, Kasratan, Fatha,
            // Damma, Kasra, Shadda and Sukun when checking if
            // a character can be connected to previous character.
            if ( cCh < 0x64B || cCh > 0x652 )
                cPrevCh = cCh;
 
            ++nIdx;
        } // end of current word
 
        if ( nKashidaPos>=0 )
            rArray.push_back( nKashidaPos );
 
        aWordSel = WordRight( aWordSel.Max(), css::i18n::WordType::DICTIONARY_WORD );
        aWordSel = SelectWord( aWordSel, css::i18n::WordType::DICTIONARY_WORD );
    }
}
 
sal_Int32 ImpEditEngine::SplitTextPortion( ParaPortion* pPortion, sal_Int32 nPos, EditLine* pCurLine )
{
    DBG_ASSERT( pPortion, "SplitTextPortion: Which ?" );
 
    // 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 = pPortion->GetTextPortions().Count();
    for ( nSplitPortion = 0; nSplitPortion < nPortions; nSplitPortion++ )
    {
        TextPortion& rTP = pPortion->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 );
    pPortion->GetTextPortions().Insert(nSplitPortion+1, pNewPortion);
    // Set sizes
    if ( pCurLine )
    {
        // No new GetTextSize, instead use values from the Array:
        DBG_ASSERT( nPos > pCurLine->GetStart(), "SplitTextPortion at the beginning of the line?" );
        pTextPortion->GetSize().setWidth( pCurLine->GetCharPosArray()[ nPos-pCurLine->GetStart()-1 ] );
 
        if ( pTextPortion->GetExtraInfos() && pTextPortion->GetExtraInfos()->bCompressed )
        {
            // We need the original size from the portion
            sal_Int32 nTxtPortionStart = pPortion->GetTextPortions().GetStartPos( nSplitPortion );
               SvxFont aTmpFont( pPortion->GetNode()->GetCharAttribs().GetDefFont() );
            SeekCursor( pPortion->GetNode(), nTxtPortionStart+1, aTmpFont );
            aTmpFont.SetPhysFont( GetRefDevice() );
            GetRefDevice()->Push( PushFlags::TEXTLANGUAGE );
            ImplInitDigitMode(GetRefDevice(), aTmpFont.GetLanguage());
            Size aSz = aTmpFont.QuickGetTextSize( GetRefDevice(), pPortion->GetNode()->GetString(), nTxtPortionStart, pTextPortion->GetLen() );
            GetRefDevice()->Pop();
            pTextPortion->GetExtraInfos()->nOrgWidth = aSz.Width();
        }
    }
    else
        pTextPortion->GetSize().setWidth( -1 );
 
    return nSplitPortion;
}
 
void ImpEditEngine::CreateTextPortions( ParaPortion* pParaPortion, sal_Int32& rStart )
{
    sal_Int32 nStartPos = rStart;
    ContentNode* pNode = pParaPortion->GetNode();
    DBG_ASSERT( pNode->Len(), "CreateTextPortions should not be used for empty paragraphs!" );
 
    std::set< sal_Int32 > aPositions;
    aPositions.insert( 0 );
 
    sal_uInt16 nAttr = 0;
    EditCharAttrib* pAttrib = GetAttrib( pNode->GetCharAttribs().GetAttribs(), nAttr );
    while ( pAttrib )
    {
        // Insert Start and End into the Array...
        // The Insert method does not allow for duplicate values....
        aPositions.insert( pAttrib->GetStart() );
        aPositions.insert( pAttrib->GetEnd() );
        nAttr++;
        pAttrib = GetAttrib( pNode->GetCharAttribs().GetAttribs(), nAttr );
    }
    aPositions.insert( pNode->Len() );
 
    if ( pParaPortion->aScriptInfos.empty() )
        InitScriptTypes( GetParaPortions().GetPos( pParaPortion ) );
 
    const ScriptTypePosInfos& rTypes = pParaPortion->aScriptInfos;
    for (const ScriptTypePosInfo& rType : rTypes)
        aPositions.insert( rType.nStartPos );
 
    const WritingDirectionInfos& rWritingDirections = pParaPortion->aWritingDirectionInfos;
    for (const WritingDirectionInfo & rWritingDirection : rWritingDirections)
        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 < pParaPortion->GetTextPortions().Count(); nP++ )
    {
        const TextPortion& rTmpPortion = pParaPortion->GetTextPortions()[nP];
        nPortionStart = nPortionStart + rTmpPortion.GetLen();
        if ( nPortionStart >= nStartPos )
        {
            nPortionStart = nPortionStart - rTmpPortion.GetLen();
            rStart = nPortionStart;
            nInvPortion = nP;
            break;
        }
    }
    DBG_ASSERT( nP < pParaPortion->GetTextPortions().Count() || !pParaPortion->GetTextPortions().Count(), "Nothing to delete: CreateTextPortions" );
    if ( nInvPortion && ( nPortionStart+pParaPortion->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 - pParaPortion->GetTextPortions()[nInvPortion].GetLen();
    }
    pParaPortion->GetTextPortions().DeleteFromPortion( nInvPortion );
 
    // A portion may also have been formed by a line break:
    aPositions.insert( nPortionStart );
 
    std::set< sal_Int32 >::iterator nInvPos = aPositions.find(  nPortionStart );
    DBG_ASSERT( (nInvPos != aPositions.end()), "InvPos ?!" );
 
    std::set< sal_Int32 >::iterator i = nInvPos;
    ++i;
    while ( i != aPositions.end() )
    {
        TextPortion* pNew = new TextPortion( (*i++) - *nInvPos++ );
        pParaPortion->GetTextPortions().Append(pNew);
    }
 
    DBG_ASSERT( pParaPortion->GetTextPortions().Count(), "No Portions?!" );
#if OSL_DEBUG_LEVEL > 0
    OSL_ENSURE( ParaPortion::DbgCheckTextPortions(*pParaPortion), "Portion is broken?" );
#endif
}
 
void ImpEditEngine::RecalcTextPortion( ParaPortion* pParaPortion, sal_Int32 nStartPos, sal_Int32 nNewChars )
{
    DBG_ASSERT( pParaPortion->GetTextPortions().Count(), "No Portions!" );
    DBG_ASSERT( nNewChars, "RecalcTextPortion with Diff == 0" );
 
    ContentNode* const pNode = pParaPortion->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( pParaPortion, 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 < pParaPortion->GetTextPortions().Count() ) &&
                    !pParaPortion->GetTextPortions()[nNewPortionPos].GetLen() )
            {
                TextPortion& rTP = pParaPortion->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 );
                pParaPortion->GetTextPortions().Insert(nNewPortionPos, pNewPortion);
            }
        }
        else
        {
            sal_Int32 nPortionStart;
            const sal_Int32 nTP = pParaPortion->GetTextPortions().
                FindPortion( nStartPos, nPortionStart );
            TextPortion& rTP = pParaPortion->GetTextPortions()[ nTP ];
            rTP.SetLen( rTP.GetLen() + nNewChars );
            rTP.GetSize().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 = pParaPortion->GetTextPortions().Count();
        TextPortion* pTP = nullptr;
        for ( nPortion = 0; nPortion < nPortions; nPortion++ )
        {
            pTP = &pParaPortion->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();
        }
        DBG_ASSERT( pTP, "RecalcTextPortion: Portion not found" );
        if ( ( nPos == nStartPos ) && ( (nPos+pTP->GetLen()) == nEnd ) )
        {
            // Remove portion;
            PortionKind nType = pTP->GetKind();
            pParaPortion->GetTextPortions().Remove( nPortion );
            if ( nType == PortionKind::LINEBREAK )
            {
                TextPortion& rNext = pParaPortion->GetTextPortions()[ nPortion ];
                if ( !rNext.GetLen() )
                {
                    // Remove dummy portion
                    pParaPortion->GetTextPortions().Remove( nPortion );
                }
            }
        }
        else
        {
            DBG_ASSERT( pTP->GetLen() > (-nNewChars), "Portion too small to shrink! ");
            pTP->SetLen( pTP->GetLen() + nNewChars );
        }
 
        sal_Int32 nPortionCount = pParaPortion->GetTextPortions().Count();
        assert( nPortionCount );
        if (nPortionCount)
        {
            // No HYPHENATOR portion is allowed to get stuck right at the end...
            sal_Int32 nLastPortion = nPortionCount - 1;
            pTP = &pParaPortion->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 = pParaPortion->GetTextPortions()[nLastPortion - 1];
                    DBG_ASSERT( rPrev.GetKind() == PortionKind::TEXT, "Portion?!" );
                    rPrev.SetLen( rPrev.GetLen() + pTP->GetLen() );
                    rPrev.GetSize().setWidth( -1 );
                }
                pParaPortion->GetTextPortions().Remove( nLastPortion );
            }
        }
    }
#if OSL_DEBUG_LEVEL > 0
    OSL_ENSURE( ParaPortion::DbgCheckTextPortions(*pParaPortion), "Portions are broken?" );
#endif
}
 
void ImpEditEngine::SetTextRanger( std::unique_ptr<TextRanger> pRanger )
{
    pTextRanger = std::move(pRanger);
 
    for ( sal_Int32 nPara = 0; nPara < GetParaPortions().Count(); nPara++ )
    {
        ParaPortion* pParaPortion = GetParaPortions()[nPara];
        pParaPortion->MarkSelectionInvalid( 0 );
        pParaPortion->GetLines().Reset();
    }
 
    FormatFullDoc();
    UpdateViews( GetActiveView() );
    if ( GetUpdateMode() && GetActiveView() )
        pActiveView->ShowCursor(false, false);
}
 
void ImpEditEngine::SetVertical( bool bVertical, bool bTopToBottom)
{
    if ( IsVertical() != bVertical || IsTopToBottom() != (bVertical && bTopToBottom))
    {
        GetEditDoc().SetVertical( bVertical, bTopToBottom);
        bool bUseCharAttribs = bool(aStatus.GetControlWord() & EEControlBits::USECHARATTRIBS);
        GetEditDoc().CreateDefFont( bUseCharAttribs );
        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();
    }
 
    if ( pOut )
    {
        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 ( aStatus.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 )
                    {
                        EditCharAttrib* pColorAttr = pNode->GetCharAttribs().FindAttrib( EE_CHAR_COLOR, nPos );
                        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 );
 
    if ( aStatus.DoNotUseColors() )
    {
        rFont.SetColor( /* rColorItem.GetValue() */ COL_BLACK );
    }
 
    if ( aStatus.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() );
 
        // 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 ( aStatus.DoStretch() )
        {
            if ( nStretchY != 100 )
            {
                aRealSz.setHeight( aRealSz.Height() * ( nStretchY) );
                aRealSz.setHeight( aRealSz.Height() / 100 );
            }
            if ( nStretchX != 100 )
            {
                if ( nStretchX == nStretchY &&
                     nRelWidth == 100 )
                {
                    aRealSz.setWidth( 0 );
                }
                else
                {
                    aRealSz.setWidth( aRealSz.Width() * ( nStretchX) );
                    aRealSz.setWidth( aRealSz.Width() / 100 );
 
                    // Also the Kerning: (long due to handle Interim results)
                    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  ) && ( nStretchX > 100 ) )
                    {
                        // disproportional
                        nKerning *= 100;
                        nKerning /= nStretchX;
                    }
                    else if ( nKerning )
                    {
                        // Proportional
                        nKerning *= nStretchX;
                        nKerning /= 100;
                    }
                    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(nullptr != pOut->GetPDFWriter());
 
        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 ) ) )
    {
        ExtTextInputAttr nAttr = mpIMEInfos->pAttribs[ nPos - mpIMEInfos->aPos.GetIndex() - 1 ];
        if ( nAttr & ExtTextInputAttr::Underline )
            rFont.SetUnderline( LINESTYLE_SINGLE );
        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( pRefDev );
    }
    sal_uInt16 nAscent, nDescent;
 
    FontMetric aMetric( pRefDev->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 ) && ( pRefDev->GetOutDevType() == OUTDEV_PRINTER ) )
        {
            // Lets see what Leading one gets on the screen
            VclPtr<VirtualDevice> pVDev = GetVirtualDevice( pRefDev->GetMapMode(), pRefDev->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() )
    {
        // Now in consideration of Escape/Propr
        // possibly enlarge Ascent or Descent
        short nDiff = static_cast<short>(rFont.GetFontSize().Height()*rFont.GetEscapement()/100L);
        if ( rFont.GetEscapement() > 0 )
        {
            nAscent = static_cast<sal_uInt16>(static_cast<long>(nAscent)*nPropr/100 + nDiff);
            if ( nAscent > rCurMetrics.nMaxAscent )
                rCurMetrics.nMaxAscent = nAscent;
        }
        else    // has to be < 0
        {
            nDescent = static_cast<sal_uInt16>(static_cast<long>(nDescent)*nPropr/100 - nDiff);
            if ( nDescent > rCurMetrics.nMaxDescent )
                rCurMetrics.nMaxDescent= nDescent;
        }
    }
}
 
void ImpEditEngine::Paint( OutputDevice* pOutDev, tools::Rectangle aClipRect, Point aStartPos, bool bStripOnly, short nOrientation )
{
    if ( !GetUpdateMode() && !bStripOnly )
        return;
 
    if ( !IsFormatted() )
        FormatDoc();
 
    long nFirstVisXPos = - pOutDev->GetMapMode().GetOrigin().X();
    long nFirstVisYPos = - pOutDev->GetMapMode().GetOrigin().Y();
 
    const EditLine* pLine = nullptr;
    Point aTmpPos;
    Point aRedLineTmpPos;
    DBG_ASSERT( GetParaPortions().Count(), "No ParaPortion?!" );
    SvxFont aTmpFont( GetParaPortions()[0]->GetNode()->GetCharAttribs().GetDefFont() );
    vcl::Font aOldFont( pOutDev->GetFont() );
    vcl::PDFExtOutDevData* pPDFExtOutDevData = dynamic_cast< vcl::PDFExtOutDevData* >( pOutDev->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.
    Point aOrigin( aStartPos );
    double nCos = 0.0, nSin = 0.0;
    if ( nOrientation )
    {
        double nRealOrientation = nOrientation*F_PI1800;
        nCos = cos( nRealOrientation );
        nSin = sin( nRealOrientation );
    }
 
    // #110496# Added some more optional metafile comments. This
    // change: factored out some duplicated code.
    GDIMetaFile* pMtf = pOutDev->GetConnectMetaFile();
    const bool bMetafileValid( pMtf != nullptr );
 
    long nVertLineSpacing = CalcVertLineSpacing(aStartPos);
 
 
    // Over all the paragraphs ...
 
    for ( sal_Int32 n = 0; n < GetParaPortions().Count(); n++ )
    {
        const ParaPortion* pPortion = GetParaPortions()[n];
        DBG_ASSERT( pPortion, "NULL-Pointer in TokenList in Paint" );
        // if when typing idle formatting,  asynchronous Paint.
        // Invisible Portions may be invalid.
        if ( pPortion->IsVisible() && pPortion->IsInvalid() )
            return;
 
        if ( pPDFExtOutDevData )
            pPDFExtOutDevData->BeginStructureElement( vcl::PDFWriter::Paragraph );
 
        long nParaHeight = pPortion->GetHeight();
        sal_Int32 nIndex = 0;
        if ( pPortion->IsVisible() && (
                ( !IsVertical() && ( ( aStartPos.Y() + nParaHeight ) > aClipRect.Top() ) ) ||
                ( IsVertical() && IsTopToBottom() && ( ( aStartPos.X() - nParaHeight ) < aClipRect.Right() ) ) ||
                ( IsVertical() && !IsTopToBottom() && ( ( aStartPos.X() + nParaHeight ) > aClipRect.Left() ) ) ) )
 
        {
 
            // Over the lines of the paragraph ...
 
            sal_Int32 nLines = pPortion->GetLines().Count();
            sal_Int32 nLastLine = nLines-1;
 
            bool bEndOfParagraphWritten(false);
 
            if ( !IsVertical() )
                aStartPos.AdjustY(pPortion->GetFirstLineOffset() );
            else
            {
                if( IsTopToBottom() )
                    aStartPos.AdjustX( -(pPortion->GetFirstLineOffset()) );
                else
                    aStartPos.AdjustX(pPortion->GetFirstLineOffset() );
            }
 
            Point aParaStart( aStartPos );
 
            const SvxLineSpacingItem& rLSItem = pPortion->GetNode()->GetContentAttribs().GetItem( EE_PARA_SBL );
            sal_uInt16 nSBL = ( rLSItem.GetInterLineSpaceRule() == SvxInterLineSpaceRule::Fix )
                                ? GetYValue( rLSItem.GetInterLineSpace() ) : 0;
            bool bPaintBullet (false);
 
            for ( sal_Int32 nLine = 0; nLine < nLines; nLine++ )
            {
                pLine = &pPortion->GetLines()[nLine];
                nIndex = pLine->GetStart();
                DBG_ASSERT( pLine, "NULL-Pointer in the line iterator in UpdateViews" );
                aTmpPos = aStartPos;
                if ( !IsVertical() )
                {
                    aTmpPos.AdjustX(pLine->GetStartPosX() );
                    aTmpPos.AdjustY(pLine->GetMaxAscent() );
                    aStartPos.AdjustY(pLine->GetHeight() );
                    if (nLine != nLastLine)
                        aStartPos.AdjustY(nVertLineSpacing );
                }
                else
                {
                    if ( IsTopToBottom() )
                    {
                        aTmpPos.AdjustY(pLine->GetStartPosX() );
                        aTmpPos.AdjustX( -(pLine->GetMaxAscent()) );
                        aStartPos.AdjustX( -(pLine->GetHeight()) );
                        if (nLine != nLastLine)
                            aStartPos.AdjustX( -nVertLineSpacing );
                    }
                    else
                    {
                        aTmpPos.AdjustY( -(pLine->GetStartPosX()) );
                        aTmpPos.AdjustX(pLine->GetMaxAscent() );
                        aStartPos.AdjustX(pLine->GetHeight() );
                        if (nLine != nLastLine)
                            aStartPos.AdjustX(nVertLineSpacing );
                    }
                }
 
                if ( ( !IsVertical() && ( aStartPos.Y() > aClipRect.Top() ) )
                    || ( IsVertical() && IsTopToBottom() && aStartPos.X() < aClipRect.Right() )
                    || ( IsVertical() && !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)
                    {
                        GetEditEnginePtr()->PaintingFirstLine( n, aParaStart, aTmpPos.Y(), aOrigin, nOrientation, pOutDev );
 
                        // Remember whether a bullet was painted.
                        const SfxBoolItem& rBulletState = pEditEngine->GetParaAttrib(n, EE_PARA_BULLETSTATE);
                        bPaintBullet = rBulletState.GetValue();
                    }
 
 
                    // Over the Portions of the line ...
 
                    bool bParsingFields = false;
                    std::vector< sal_Int32 >::iterator itSubLines;
 
                    for ( sal_Int32 nPortion = pLine->GetStartPortion(); nPortion <= pLine->GetEndPortion(); nPortion++ )
                    {
                        DBG_ASSERT( pPortion->GetTextPortions().Count(), "Line without Textportion in Paint!" );
                        const TextPortion& rTextPortion = pPortion->GetTextPortions()[nPortion];
 
                        long nPortionXOffset = GetPortionXOffset( pPortion, pLine, nPortion );
                        if ( !IsVertical() )
                        {
                            aTmpPos.setX( aStartPos.X() + nPortionXOffset );
                            if ( aTmpPos.X() > aClipRect.Right() )
                                break;  // No further output in line necessary
                        }
                        else
                        {
                            if( IsTopToBottom() )
                            {
                                aTmpPos.setY( aStartPos.Y() + nPortionXOffset );
                                if ( aTmpPos.Y() > aClipRect.Bottom() )
                                    break;  // No further output in line necessary
                            }
                            else
                            {
                                aTmpPos.setY( aStartPos.Y() - nPortionXOffset );
                                if (aTmpPos.Y() < aClipRect.Top())
                                    break;  // No further output in line necessary
                            }
                        }
 
                        switch ( rTextPortion.GetKind() )
                        {
                            case PortionKind::TEXT:
                            case PortionKind::FIELD:
                            case PortionKind::HYPHENATOR:
                            {
                                SeekCursor( pPortion->GetNode(), nIndex+1, aTmpFont, pOutDev );
 
                                bool bDrawFrame = false;
 
                                if ( ( rTextPortion.GetKind() == PortionKind::FIELD ) && !aTmpFont.IsTransparent() &&
                                     ( GetBackgroundColor() != COL_AUTO ) && GetBackgroundColor().IsDark() &&
                                     ( IsAutoColorEnabled() && ( pOutDev->GetOutDevType() != OUTDEV_PRINTER ) ) )
                                {
                                    aTmpFont.SetTransparent( true );
                                    pOutDev->SetFillColor();
                                    pOutDev->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( pPortion->GetNode(), nIndex+1 ) ) == i18n::ScriptType::COMPLEX )
                                {
                                    aTmpFont.SetFillColor( COL_LIGHTCYAN );
                                    aTmpFont.SetTransparent( sal_False );
                                }
#endif
                                aTmpFont.SetPhysFont( pOutDev );
 
                                // #114278# Saving both layout mode and language (since I'm
                                // potentially changing both)
                                pOutDev->Push( PushFlags::TEXTLAYOUTMODE|PushFlags::TEXTLANGUAGE );
                                ImplInitLayoutMode( pOutDev, n, nIndex );
                                ImplInitDigitMode(pOutDev, aTmpFont.GetLanguage());
 
                                OUString aText;
                                sal_Int32 nTextStart = 0;
                                sal_Int32 nTextLen = 0;
                                const long* pDXArray = nullptr;
                                std::unique_ptr<long[]> pTmpDXArray;
 
                                if ( rTextPortion.GetKind() == PortionKind::TEXT )
                                {
                                    aText = pPortion->GetNode()->GetString();
                                    nTextStart = nIndex;
                                    nTextLen = rTextPortion.GetLen();
                                    pDXArray = pLine->GetCharPosArray().data() + (nIndex - pLine->GetStart());
 
                                    // Paint control characters (#i55716#)
                                    if ( aStatus.MarkFields() )
                                    {
                                        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 )
                                            {
                                                const OUString aBlank( ' ' );
                                                long nHalfBlankWidth = aTmpFont.QuickGetTextSize( pOutDev, aBlank, 0, 1 ).Width() / 2;
 
                                                const long nAdvanceX = ( nTmpIdx == nTmpEnd ?
                                                                         rTextPortion.GetSize().Width() :
                                                                         pDXArray[ nTmpIdx - nTextStart ] ) - nHalfBlankWidth;
                                                const long nAdvanceY = -pLine->GetMaxAscent();
 
                                                Point aTopLeftRectPos( aTmpPos );
                                                if ( !IsVertical() )
                                                {
                                                    aTopLeftRectPos.AdjustX(nAdvanceX );
                                                    aTopLeftRectPos.AdjustY(nAdvanceY );
                                                }
                                                else
                                                {
                                                    if( IsTopToBottom() )
                                                    {
                                                        aTopLeftRectPos.AdjustY( -nAdvanceX );
                                                        aTopLeftRectPos.AdjustX(nAdvanceY );
                                                    }
                                                    else
                                                    {
                                                        aTopLeftRectPos.AdjustY(nAdvanceX );
                                                        aTopLeftRectPos.AdjustX( -nAdvanceY );
                                                    }
                                                }
 
                                                Point aBottomRightRectPos( aTopLeftRectPos );
                                                if ( !IsVertical() )
                                                {
                                                    aBottomRightRectPos.AdjustX(2 * nHalfBlankWidth );
                                                    aBottomRightRectPos.AdjustY(pLine->GetHeight() );
                                                }
                                                else
                                                {
                                                    if (IsTopToBottom())
                                                    {
                                                        aBottomRightRectPos.AdjustX(pLine->GetHeight() );
                                                        aBottomRightRectPos.AdjustY( -(2 * nHalfBlankWidth) );
                                                    }
                                                    else
                                                    {
                                                        aBottomRightRectPos.AdjustX( -(pLine->GetHeight()) );
                                                        aBottomRightRectPos.AdjustY(2 * nHalfBlankWidth );
                                                    }
                                                }
 
                                                pOutDev->Push( PushFlags::FILLCOLOR );
                                                pOutDev->Push( PushFlags::LINECOLOR );
                                                pOutDev->SetFillColor( COL_LIGHTGRAY );
                                                pOutDev->SetLineColor( COL_LIGHTGRAY );
 
                                                const tools::Rectangle aBackRect( aTopLeftRectPos, aBottomRightRectPos );
                                                pOutDev->DrawRect( aBackRect );
 
                                                pOutDev->Pop();
                                                pOutDev->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( pOutDev );
 
                                                    const Size aSlashSize = aTmpFont.QuickGetTextSize( pOutDev, aSlash, 0, 1 );
                                                    Point aSlashPos( aTmpPos );
                                                    const long nAddX = nHalfBlankWidth - aSlashSize.Width() / 2;
                                                    if ( !IsVertical() )
                                                    {
                                                        aSlashPos.setX( aTopLeftRectPos.X() + nAddX );
                                                    }
                                                    else
                                                    {
                                                        if (IsTopToBottom())
                                                            aSlashPos.setY( aTopLeftRectPos.Y() + nAddX );
                                                        else
                                                            aSlashPos.setY( aTopLeftRectPos.Y() - nAddX );
                                                    }
 
                                                    aTmpFont.QuickDrawText( pOutDev, aSlashPos, aSlash, 0, 1 );
 
                                                    aTmpFont.SetEscapement( nOldEscapement );
                                                    aTmpFont.SetPropr( nOldPropr );
                                                    aTmpFont.SetPhysFont( pOutDev );
                                                }
                                            }
                                        }
                                    }
                                }
                                else if ( rTextPortion.GetKind() == PortionKind::FIELD )
                                {
                                    const EditCharAttrib* pAttr = pPortion->GetNode()->GetCharAttribs().FindFeature(nIndex);
                                    DBG_ASSERT( pAttr, "Field not found");
                                    DBG_ASSERT( pAttr && 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();
                                    // Do not split the Fields into different lines while editing
                                    // With EditView on Overlay bStripOnly is now set for stripping to
                                    // primitives. To stay compatible in EditMode use pActiveView to detect
                                    // when we are in EditMode. For whatever reason URLs are drawn as single
                                    // line in edit mode, originally clipped against edit area (which is no
                                    // longer done in Overlay mode and allows to *read* the URL).
                                    // It would be difficult to change this due to needed adaptions in
                                    // EditEngine (look for lineBreaksList creation)
                                    if( nullptr == pActiveView && bStripOnly && !bParsingFields && pExtraInfo && pExtraInfo->lineBreaksList.size() )
                                    {
                                        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());
 
                                            if ( !IsVertical() )
                                            {
                                                aStartPos.AdjustY(nMaxAscent );
                                                aTmpPos.AdjustY(nMaxAscent );
                                            }
                                            else
                                            {
                                                if (IsTopToBottom())
                                                {
                                                    aTmpPos.AdjustX( -nMaxAscent );
                                                    aStartPos.AdjustX( -nMaxAscent );
                                                }
                                                else
                                                {
                                                    aTmpPos.AdjustX(nMaxAscent );
                                                    aStartPos.AdjustX(nMaxAscent );
                                                }
                                            }
                                        }
                                        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;
                                        }
                                    }
 
                                    pTmpDXArray.reset(new long[ aText.getLength() ]);
                                    pDXArray = pTmpDXArray.get();
                                    vcl::Font _aOldFont( GetRefDevice()->GetFont() );
                                    aTmpFont.SetPhysFont( GetRefDevice() );
                                    aTmpFont.QuickGetTextSize( GetRefDevice(), aText, nTextStart, nTextLen, pTmpDXArray.get() );
                                    if ( aStatus.DoRestoreFont() )
                                        GetRefDevice()->SetFont( _aOldFont );
 
                                    // 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 += OUStringLiteral1(CH_HYPH);
                                    nTextStart = 0;
                                    nTextLen = aText.getLength();
 
                                    // crash when accessing 0 pointer in pDXArray
                                    pTmpDXArray.reset(new long[ aText.getLength() ]);
                                    pDXArray = pTmpDXArray.get();
                                    vcl::Font _aOldFont( GetRefDevice()->GetFont() );
                                    aTmpFont.SetPhysFont( GetRefDevice() );
                                    aTmpFont.QuickGetTextSize( GetRefDevice(), aText, 0, aText.getLength(), pTmpDXArray.get() );
                                    if ( aStatus.DoRestoreFont() )
                                        GetRefDevice()->SetFont( _aOldFont );
                                }
 
                                long nTxtWidth = rTextPortion.GetSize().Width();
 
                                Point aOutPos( aTmpPos );
                                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 = pPortion->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 < static_cast<size_t>(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 = pPortion->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(pPortion->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 pOutDev)
                                    const Color aOverlineColor(pOutDev->GetOverlineColor());
 
                                    // get TextLine color (from ((const SvxUnderlineItem*)GetItem())->GetColor() in
                                    // consequence, but also already set at pOutDev)
                                    const Color aTextLineColor(pOutDev->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,
                                        aTmpFont, n, rTextPortion.GetRightToLeftLevel(),
                                        aWrongSpellVector.size() ? &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() )
                                        {
                                            long nDiff = aTmpFont.GetFontSize().Height() * aTmpFont.GetEscapement() / 100L;
                                            if ( !IsVertical() )
                                                aOutPos.AdjustY( -nDiff );
                                            else
                                            {
                                                if (IsTopToBottom())
                                                    aOutPos.AdjustX(nDiff );
                                                else
                                                    aOutPos.AdjustX( -nDiff );
                                            }
                                            aRedLineTmpPos = aOutPos;
                                            aTmpFont.SetEscapement( 0 );
                                        }
 
                                        aOutPos = lcl_ImplCalcRotatedPos( aOutPos, aOrigin, nSin, nCos );
                                        aTmpFont.SetOrientation( aTmpFont.GetOrientation()+nOrientation );
                                        aTmpFont.SetPhysFont( pOutDev );
 
                                    }
 
                                    // 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 || ( !IsVertical() && ( ( aTmpPos.X() + nTxtWidth ) >= nFirstVisXPos ) )
                                            || ( IsVertical() && ( ( 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 = pPortion->GetNode()->GetCharAttribs().FindAttrib( EE_CHAR_ESCAPEMENT, nIndex );
                                            if ( pPrev )
                                            {
                                                SvxFont aDummy;
                                                // Underscore in front?
                                                if ( pPrev->GetStart() )
                                                {
                                                    SeekCursor( pPortion->GetNode(), pPrev->GetStart(), aDummy );
                                                    if ( aDummy.GetUnderline() != LINESTYLE_NONE )
                                                        bSpecialUnderline = true;
                                                }
                                                if ( !bSpecialUnderline && ( pPrev->GetEnd() < pPortion->GetNode()->Len() ) )
                                                {
                                                    SeekCursor( pPortion->GetNode(), pPrev->GetEnd()+1, aDummy );
                                                    if ( aDummy.GetUnderline() != LINESTYLE_NONE )
                                                        bSpecialUnderline = true;
                                                }
                                            }
                                            if ( bSpecialUnderline )
                                            {
                                                Size aSz = aTmpFont.GetPhysTxtSize( pOutDev, aText, nTextStart, nTextLen );
                                                sal_uInt8 nProp = aTmpFont.GetPropr();
                                                aTmpFont.SetEscapement( 0 );
                                                aTmpFont.SetPropr( 100 );
                                                aTmpFont.SetPhysFont( pOutDev );
                                                OUStringBuffer aBlanks;
                                                comphelper::string::padToLength( aBlanks, nTextLen, ' ' );
                                                Point aUnderlinePos( aOutPos );
                                                if ( nOrientation )
                                                    aUnderlinePos = lcl_ImplCalcRotatedPos( aTmpPos, aOrigin, nSin, nCos );
                                                pOutDev->DrawStretchText( aUnderlinePos, aSz.Width(), aBlanks.makeStringAndClear(), 0, nTextLen );
 
                                                aTmpFont.SetUnderline( LINESTYLE_NONE );
                                                if ( !nOrientation )
                                                    aTmpFont.SetEscapement( nEsc );
                                                aTmpFont.SetPropr( nProp );
                                                aTmpFont.SetPhysFont( pOutDev );
                                            }
                                        }
                                        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;
 
                                        // output directly
                                        aTmpFont.QuickDrawText( pOutDev, aRealOutPos, aText, nTextStart, nTextLen, pDXArray );
 
                                        if ( bDrawFrame )
                                        {
                                            Point aTopLeft( aTmpPos );
                                            aTopLeft.AdjustY( -(pLine->GetMaxAscent()) );
                                            if ( nOrientation )
                                                aTopLeft = lcl_ImplCalcRotatedPos( aTopLeft, aOrigin, nSin, nCos );
                                            tools::Rectangle aRect( aTopLeft, rTextPortion.GetSize() );
                                            pOutDev->DrawRect( aRect );
                                        }
 
                                        // PDF export:
                                        if ( pPDFExtOutDevData )
                                        {
                                            if ( rTextPortion.GetKind() == PortionKind::FIELD )
                                            {
                                                const EditCharAttrib* pAttr = pPortion->GetNode()->GetCharAttribs().FindFeature(nIndex);
                                                const SvxFieldItem* pFieldItem = dynamic_cast<const SvxFieldItem*>(pAttr->GetItem());
                                                if( pFieldItem )
                                                {
                                                    const SvxFieldData* pFieldData = pFieldItem->GetField();
                                                    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 );
                                                        aBookmark.aBookmark = pUrlField->GetURL();
                                                        std::vector< vcl::PDFExtOutDevBookmarkEntry >& rBookmarks = pPDFExtOutDevData->GetBookmarks();
                                                        rBookmarks.push_back( aBookmark );
                                                    }
                                                }
                                            }
                                        }
                                    }
 
                                    const WrongList* const pWrongList = pPortion->GetNode()->GetWrongList();
                                    if ( GetStatus().DoOnlineSpelling() && pWrongList && !pWrongList->empty() && rTextPortion.GetLen() )
                                    {
                                        {//#105750# adjust LinePos for superscript or subscript text
                                            short _nEsc = aTmpFont.GetEscapement();
                                            if( _nEsc )
                                            {
                                                long nShift = (_nEsc * aTmpFont.GetFontSize().Height()) / 100L;
                                                if( !IsVertical() )
                                                    aRedLineTmpPos.AdjustY( -nShift );
                                                else
                                                    if (IsTopToBottom())
                                                        aRedLineTmpPos.AdjustX(nShift );
                                                    else
                                                        aRedLineTmpPos.AdjustX( -nShift );
                                            }
                                        }
                                        Color aOldColor( pOutDev->GetLineColor() );
                                        pOutDev->SetLineColor( GetColorConfig().GetColorValue( svtools::SPELL ).nColor );
                                        lcl_DrawRedLines( pOutDev, aTmpFont.GetFontSize().Height(), aRedLineTmpPos, static_cast<size_t>(nIndex), static_cast<size_t>(nIndex) + rTextPortion.GetLen(), pDXArray, pPortion->GetNode()->GetWrongList(), nOrientation, aOrigin, IsVertical(), rTextPortion.IsRightToLeft() );
                                        pOutDev->SetLineColor( aOldColor );
                                    }
                                }
 
                                pOutDev->Pop();
 
                                pTmpDXArray.reset();
 
                                if ( rTextPortion.GetKind() == PortionKind::FIELD )
                                {
                                    const EditCharAttrib* pAttr = pPortion->GetNode()->GetCharAttribs().FindFeature(nIndex);
                                    DBG_ASSERT( pAttr, "Field not found" );
                                    DBG_ASSERT( pAttr && dynamic_cast< const SvxFieldItem* >( pAttr->GetItem() ) !=  nullptr, "Wrong type of field!" );
 
                                    // 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( SvxFieldData::createEndComment() );
                                        }
                                    }
 
                                }
 
                            }
                            break;
                            case PortionKind::TAB:
                            {
                                if ( rTextPortion.GetExtraValue() && ( rTextPortion.GetExtraValue() != ' ' ) )
                                {
                                    SeekCursor( pPortion->GetNode(), nIndex+1, aTmpFont, pOutDev );
                                    aTmpFont.SetTransparent( false );
                                    aTmpFont.SetEscapement( 0 );
                                    aTmpFont.SetPhysFont( pOutDev );
                                    long nCharWidth = aTmpFont.QuickGetTextSize( pOutDev,
                                        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
 
                                    OUStringBuffer aBuf;
                                    comphelper::string::padToLength(aBuf, nChars, rTextPortion.GetExtraValue());
                                    OUString aText(aBuf.makeStringAndClear());
                                    aTmpFont.QuickDrawText( pOutDev, aTmpPos, aText, 0, aText.getLength() );
                                    pOutDev->DrawStretchText( aTmpPos, rTextPortion.GetSize().Width(), aText );
 
                                    if ( bStripOnly )
                                    {
                                        // create EOL and EOP bools
                                        const bool bEndOfLine(nPortion == pLine->GetEndPortion());
                                        const bool bEndOfParagraph(bEndOfLine && nLine + 1 == nLines);
 
                                        const Color aOverlineColor(pOutDev->GetOverlineColor());
                                        const Color aTextLineColor(pOutDev->GetTextLineColor());
 
                                        // StripPortions() data callback
                                        GetEditEnginePtr()->DrawingTab( aTmpPos,
                                            rTextPortion.GetSize().Width(),
                                            OUString(rTextPortion.GetExtraValue()),
                                            aTmpFont, n, rTextPortion.GetRightToLeftLevel(),
                                            bEndOfLine, bEndOfParagraph,
                                            aOverlineColor, aTextLineColor);
                                    }
                                }
                                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(pOutDev->GetOverlineColor());
                                    const Color aTextLineColor(pOutDev->GetTextLineColor());
 
                                    GetEditEnginePtr()->DrawingText(
                                        aTmpPos, OUString(), 0, 0, nullptr,
                                        aTmpFont, n, 0,
                                        nullptr,
                                        nullptr,
                                        bEndOfLine, bEndOfParagraph,
                                        nullptr,
                                        aOverlineColor,
                                        aTextLineColor);
                                }
                            }
                            break;
                            case PortionKind::LINEBREAK: break;
                        }
                        if( bParsingFields )
                            nPortion--;
                        else
                            nIndex = nIndex + rTextPortion.GetLen();
 
                    }
                }
 
                if ( ( nLine != nLastLine ) && !aStatus.IsOutliner() )
                {
                    if ( !IsVertical() )
                        aStartPos.AdjustY(nSBL );
                    else
                    {
                        if( IsTopToBottom() )
                            aStartPos.AdjustX( -nSBL );
                        else
                            aStartPos.AdjustX(nSBL );
                    }
                }
 
                // no more visible actions?
                if ( !IsVertical() && ( aStartPos.Y() >= aClipRect.Bottom() ) )
                    break;
                else if ( IsVertical() && IsTopToBottom() && ( aStartPos.X() <= aClipRect.Left() ) )
                    break;
                else if (IsVertical() && !IsTopToBottom() && (aStartPos.X() >= aClipRect.Right()))
                    break;
            }
 
            if ( !aStatus.IsOutliner() )
            {
                const SvxULSpaceItem& rULItem = pPortion->GetNode()->GetContentAttribs().GetItem( EE_PARA_ULSPACE );
                long nUL = GetYValue( rULItem.GetLower() );
                if ( !IsVertical() )
                    aStartPos.AdjustY(nUL );
                else
                {
                    if (IsTopToBottom())
                        aStartPos.AdjustX( -nUL );
                    else
                        aStartPos.AdjustX(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(pOutDev->GetOverlineColor());
                const Color aTextLineColor(pOutDev->GetTextLineColor());
 
                GetEditEnginePtr()->DrawingText(
                    aTmpPos, OUString(), 0, 0, nullptr,
                    aTmpFont, n, 0,
                    nullptr,
                    nullptr,
                    false, true, // support for EOL/EOP TEXT comments
                    nullptr,
                    aOverlineColor,
                    aTextLineColor);
            }
        }
        else
        {
            if ( !IsVertical() )
                aStartPos.AdjustY(nParaHeight );
            else
            {
                if (IsTopToBottom())
                    aStartPos.AdjustX( -nParaHeight );
                else
                    aStartPos.AdjustX(nParaHeight );
            }
        }
 
        if ( pPDFExtOutDevData )
            pPDFExtOutDevData->EndStructureElement();
 
        // no more visible actions?
        if ( !IsVertical() && ( aStartPos.Y() > aClipRect.Bottom() ) )
            break;
        if ( IsVertical() && IsTopToBottom() && ( aStartPos.X() < aClipRect.Left() ) )
            break;
        if (IsVertical() && !IsTopToBottom() && ( aStartPos.X() > aClipRect.Right() ) )
            break;
    }
    if ( aStatus.DoRestoreFont() )
        pOutDev->SetFont( aOldFont );
}
 
void ImpEditEngine::Paint( ImpEditView* pView, const tools::Rectangle& rRect, OutputDevice* pTargetDevice )
{
    DBG_ASSERT( pView, "No View - No Paint!" );
 
    if ( !GetUpdateMode() || IsInUndo() )
        return;
 
    // Intersection of paint area and output area.
    tools::Rectangle aClipRect( pView->GetOutputArea() );
    aClipRect.Intersection( rRect );
 
    OutputDevice* pTarget = pTargetDevice ? pTargetDevice : pView->GetWindow();
 
    Point aStartPos;
    if ( !IsVertical() )
    {
        aStartPos = pView->GetOutputArea().TopLeft();
        aStartPos.AdjustX( -(pView->GetVisDocLeft()) );
        aStartPos.AdjustY( -(pView->GetVisDocTop()) );
    }
    else
    {
        if( IsTopToBottom() )
        {
            aStartPos = pView->GetOutputArea().TopRight();
            aStartPos.AdjustX(pView->GetVisDocTop() );
            aStartPos.AdjustY( -(pView->GetVisDocLeft()) );
        }
        else
        {
            aStartPos = pView->GetOutputArea().BottomLeft();
            aStartPos.AdjustX( -(pView->GetVisDocTop()) );
            aStartPos.AdjustY(pView->GetVisDocLeft() );
        }
    }
 
    // 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 ( !IsVertical() && ( pView->GetOutputArea().GetWidth() > GetPaperSize().Width() ) )
    {
        long nMaxX = pView->GetOutputArea().Left() + GetPaperSize().Width();
        if ( aClipRect.Left() > nMaxX )
            return;
        if ( aClipRect.Right() > nMaxX )
            aClipRect.SetRight( nMaxX );
    }
 
    bool bClipRegion = pTarget->IsClipRegion();
    vcl::Region aOldRegion = pTarget->GetClipRegion();
    pTarget->IntersectClipRegion( aClipRect );
 
    Paint( pTarget, aClipRect, aStartPos );
 
    if ( bClipRegion )
        pTarget->SetClipRegion( aOldRegion );
    else
        pTarget->SetClipRegion();
 
    // In case of tiled rendering pass a region to DrawSelectionXOR(), so that
    // selection callbacks are not emitted during every repaint.
    vcl::Region aRegion;
    pView->DrawSelectionXOR(pView->GetEditSelection(), comphelper::LibreOfficeKit::isActive() ? &aRegion : nullptr, pTarget);
}
 
void ImpEditEngine::InsertContent( ContentNode* pNode, sal_Int32 nPos )
{
    DBG_ASSERT( pNode, "NULL-Pointer in InsertContent! " );
    DBG_ASSERT( IsInUndo(), "InsertContent only for Undo()!" );
    ParaPortion* pNew = new ParaPortion( pNode );
    GetParaPortions().Insert(nPos, pNew);
    aEditDoc.Insert(nPos, pNode);
    if ( IsCallParaInsertedOrDeleted() )
        GetEditEnginePtr()->ParagraphInserted( nPos );
}
 
EditPaM ImpEditEngine::SplitContent( sal_Int32 nNode, sal_Int32 nSepPos )
{
    ContentNode* pNode = aEditDoc.GetObject( nNode );
    DBG_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 = aEditDoc.GetObject( nLeftNode );
    ContentNode* pRightNode = aEditDoc.GetObject( nLeftNode+1 );
    DBG_ASSERT( pLeftNode, "Invalid left node in ConnectContents ");
    DBG_ASSERT( pRightNode, "Invalid right node in ConnectContents ");
    DBG_ASSERT( IsInUndo(), "ConnectContent only for Undo()!" );
    return ImpConnectParagraphs( pLeftNode, pRightNode, bBackward );
}
 
void ImpEditEngine::SetUpdateMode( bool bUp, EditView* pCurView, bool bForceUpdate )
{
    bool bChanged = ( GetUpdateMode() != bUp );
 
    // When switching from sal_True to sal_False, all selections were visible,
    // => paint over
    // the other hand, were all invisible => paint
    // If !bFormatted, e.g. after SetText, then if UpdateMode=sal_True
    // formatting is not needed immediately, probably because more text is coming.
    // At latest it is formatted at a Paint/CalcTextWidth.
    bUpdate = bUp;
    if ( bUpdate && ( bChanged || bForceUpdate ) )
        FormatAndUpdate( pCurView );
}
 
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 ) )
    {
        pPPortion->SetVisible( bShow );
 
        if ( !bShow )
        {
            // Mark as deleted, so that no selection will end or begin at
            // this paragraph...
            aDeletedNodes.push_back(o3tl::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->nHeight ) )
        {
            if ( !GetTextRanger() )
            {
                if ( pPPortion->IsInvalid() )
                {
                    vcl::Font aOldFont( GetRefDevice()->GetFont() );
                    CreateLines( nParagraph, 0 );   // 0: No TextRanger
                    if ( aStatus.DoRestoreFont() )
                        GetRefDevice()->SetFont( aOldFont );
                }
                else
                {
                    CalcHeight( pPPortion );
                }
                nCurTextHeight += pPPortion->GetHeight();
            }
            else
            {
                nCurTextHeight = 0x7fffffff;
            }
        }
 
        pPPortion->SetMustRepaint( true );
        if ( GetUpdateMode() && !IsInUndo() && !GetTextRanger() )
        {
            aInvalidRect = tools::Rectangle(    Point( 0, GetParaPortions().GetYOffset( pPPortion ) ),
                                        Point( GetPaperSize().Width(), nCurTextHeight ) );
            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.Justify();
 
    EditSelection aSel( ImpMoveParagraphs( aOldPositions, nNewPos ) );
 
    if ( nNewPos >= GetParaPortions().Count() )
        nNewPos = GetParaPortions().Count() - 1;
 
    // 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 && GetUpdateMode() )
    {
        // 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)
        {
            aInvalidRect = tools::Rectangle();  // make empty
            aInvalidRect.SetLeft( 0 );
            aInvalidRect.SetRight( aPaperSize.Width() );
            aInvalidRect.SetTop( GetParaPortions().GetYOffset( pUpperPortion ) );
            aInvalidRect.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.
    ParaPortion* pTmpPortion;
    if ( nFirstInvPara != 0 )
    {
        pTmpPortion = GetParaPortions()[nFirstInvPara-1];
        pTmpPortion->MarkInvalid( pTmpPortion->GetNode()->Len(), 0 );
    }
    else
    {
        pTmpPortion = GetParaPortions()[0];
        pTmpPortion->MarkSelectionInvalid( 0 );
    }
    pTmpPortion->ResetHeight();
}
 
IMPL_LINK_NOARG(ImpEditEngine, StatusTimerHdl, Timer *, void)
{
    CallStatusHdl();
}
 
void ImpEditEngine::CallStatusHdl()
{
    if ( aStatusHdlLink.IsSet() && bool(aStatus.GetStatusWord()) )
    {
        // The Status has to be reset before the Call,
        // since other Flags might be set in the handler...
        EditStatus aTmpStatus( aStatus );
        aStatus.Clear();
        aStatusHdlLink.Call( aTmpStatus );
        aStatusTimer.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( nPara < GetParaPortions().Count() , "Portion not found: GetPrevVisPortion" );
    const ParaPortion* pPortion = nPara ? GetParaPortions()[--nPara] : nullptr;
    while ( pPortion && !pPortion->IsVisible() )
        pPortion = nPara ? GetParaPortions()[--nPara] : nullptr;
 
    return pPortion;
}
 
const ParaPortion* ImpEditEngine::GetNextVisPortion( const ParaPortion* pCurPortion ) const
{
    sal_Int32 nPara = GetParaPortions().GetPos( pCurPortion );
    DBG_ASSERT( nPara < GetParaPortions().Count() , "Portion not found: GetPrevVisNode" );
    const ParaPortion* pPortion = GetParaPortions().SafeGetObject( ++nPara );
    while ( pPortion && !pPortion->IsVisible() )
        pPortion = GetParaPortions().SafeGetObject( ++nPara );
 
    return pPortion;
}
 
long ImpEditEngine::CalcVertLineSpacing(Point& rStartPos) const
{
    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;
 
        const ParaPortion* pPortion = rParaPortions[i];
        nTotalOccupiedHeight += pPortion->GetFirstLineOffset();
 
        const SvxLineSpacingItem& rLSItem = pPortion->GetNode()->GetContentAttribs().GetItem(EE_PARA_SBL);
        sal_uInt16 nSBL = ( rLSItem.GetInterLineSpaceRule() == SvxInterLineSpaceRule::Fix )
                            ? GetYValue( rLSItem.GetInterLineSpace() ) : 0;
 
        const SvxULSpaceItem& rULItem = pPortion->GetNode()->GetContentAttribs().GetItem(EE_PARA_ULSPACE);
        long nUL = GetYValue( rULItem.GetLower() );
 
        const EditLineList& rLines = pPortion->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;
        }
    }
 
    long nTotalSpace = IsVertical() ? aPaperSize.Width() : aPaperSize.Height();
    nTotalSpace -= nTotalOccupiedHeight;
    if (nTotalSpace <= 0 || nTotalLineCount <= 1)
        return 0;
 
    if (IsVertical())
    {
        if( IsTopToBottom() )
            // Shift the text to the right for the asian layout mode.
            rStartPos.AdjustX(nTotalSpace );
        else
            rStartPos.AdjustX( -nTotalSpace );
    }
 
    return nTotalSpace / (nTotalLineCount-1);
}
 
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 );
}
 
EditSelection* ImpEditEngine::SelectParagraph( sal_Int32 nPara )
{
    EditSelection* pSel = nullptr;
    ContentNode* pNode = GetEditDoc().GetObject( nPara );
    SAL_WARN_IF( !pNode, "editeng", "Paragraph does not exist: SelectParagraph" );
    if ( pNode )
        pSel = new EditSelection( EditPaM( pNode, 0 ), EditPaM( pNode, pNode->Len() ) );
 
    return pSel;
}
 
void ImpEditEngine::FormatAndUpdate( EditView* pCurView, bool bCalledFromUndo )
{
    if ( bDowning )
        return ;
 
    if ( IsInUndo() )
        IdleFormatAndUpdate( pCurView );
    else
    {
        if (bCalledFromUndo)
            // in order to make bullet points that have had their styles changed, redraw themselves
            for ( sal_Int32 nPortion = 0; nPortion < GetParaPortions().Count(); nPortion++ )
                GetParaPortions()[nPortion]->MarkInvalid( 0, 0 );
        FormatDoc();
        UpdateViews( pCurView );
    }
 
    SendNotifications();
}
 
void ImpEditEngine::SetFlatMode( bool bFlat )
{
    if ( bFlat != aStatus.UseCharAttribs() )
        return;
 
    if ( !bFlat )
        aStatus.TurnOnFlags( EEControlBits::USECHARATTRIBS );
    else
        aStatus.TurnOffFlags( EEControlBits::USECHARATTRIBS );
 
    aEditDoc.CreateDefFont( !bFlat );
 
    FormatFullDoc();
    UpdateViews();
    if ( pActiveView )
        pActiveView->ShowCursor();
}
 
void ImpEditEngine::SetCharStretching( sal_uInt16 nX, sal_uInt16 nY )
{
    bool bChanged(false);
    if ( !IsVertical() )
    {
        bChanged = nStretchX!=nX || nStretchY!=nY;
        nStretchX = nX;
        nStretchY = nY;
    }
    else
    {
        bChanged = nStretchX!=nY || nStretchY!=nX;
        nStretchX = nY;
        nStretchY = nX;
    }
 
    if (bChanged && aStatus.DoStretch())
    {
        FormatFullDoc();
        // (potentially) need everything redrawn
        aInvalidRect=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 = pEditEngine->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( aStatus.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) const
{
    if (utl::ConfigManager::IsFuzzing())
        return LANGUAGE_ENGLISH_US;
 
    // #114278# Also setting up digit language from Svt options
    // (cannot reliably inherit the outdev's setting)
    if( !pCTLOptions )
        pCTLOptions.reset( new SvtCTLOptions );
 
    LanguageType eLang = eCurLang;
    const SvtCTLOptions::TextNumerals nCTLTextNumerals = pCTLOptions->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(const OUString &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* pOutDev, LanguageType eCurLang)
{
    assert(pOutDev); //presumably there isn't any case where pOutDev should be NULL?
    if (pOutDev)
        pOutDev->SetDigitLanguage(ImplCalcDigitLang(eCurLang));
}
 
void ImpEditEngine::ImplInitLayoutMode( OutputDevice* pOutDev, 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
    }
 
    ComplexTextLayoutFlags nLayoutMode = pOutDev->GetLayoutMode();
 
    // We always use the left position for DrawText()
    nLayoutMode &= ~ComplexTextLayoutFlags::BiDiRtl;
 
    if ( !bCTL && !bR2L)
    {
        // No Bidi checking necessary
        nLayoutMode |= ComplexTextLayoutFlags::BiDiStrong;
    }
    else
    {
        // Bidi checking necessary
        // Don't use BIDI_STRONG, VCL must do some checks.
        nLayoutMode &= ~ComplexTextLayoutFlags::BiDiStrong;
 
        if ( bR2L )
            nLayoutMode |= ComplexTextLayoutFlags::BiDiRtl|ComplexTextLayoutFlags::TextOriginLeft;
    }
 
    pOutDev->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( pOutDev, eLang );
}
 
Reference < i18n::XBreakIterator > const & ImpEditEngine::ImplGetBreakIterator() const
{
    if ( !xBI.is() )
    {
        Reference< uno::XComponentContext > xContext( ::comphelper::getProcessComponentContext() );
        xBI = i18n::BreakIterator::create( xContext );
    }
    return xBI;
}
 
Reference < i18n::XExtendedInputSequenceChecker > const & ImpEditEngine::ImplGetInputSequenceChecker() const
{
    if ( !xISC.is() )
    {
        Reference< uno::XComponentContext > xContext( ::comphelper::getProcessComponentContext() );
        xISC = i18n::InputSequenceChecker::create( xContext );
    }
    return xISC;
}
 
Color ImpEditEngine::GetAutoColor() const
{
    Color aColor = GetColorConfig().GetColorValue(svtools::FONTCOLOR).nColor;
 
    if ( GetBackgroundColor() != COL_AUTO )
    {
        if ( GetBackgroundColor().IsDark() && aColor.IsDark() )
            aColor = COL_WHITE;
        else if ( GetBackgroundColor().IsBright() && aColor.IsBright() )
            aColor = COL_BLACK;
    }
 
    return aColor;
}
 
bool ImpEditEngine::ImplCalcAsianCompression(ContentNode* pNode,
                                             TextPortion* pTextPortion, sal_Int32 nStartPos,
                                             long* 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 )
    {
        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;
 
                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 );
 
                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->GetSize().setWidth( nNewPortionWidth );
 
        if ( pTextPortion->GetExtraInfos() && ( n100thPercentFromMax != 10000 ) )
        {
            // Maybe rounding errors in nNewPortionWidth, assure that width not bigger than expected
            long nShrink = pTextPortion->GetExtraInfos()->nOrgWidth - pTextPortion->GetExtraInfos()->nWidthFullCompression;
            nShrink *= n100thPercentFromMax;
            nShrink /= 10000;
            long nNewWidth = pTextPortion->GetExtraInfos()->nOrgWidth - nShrink;
            if ( nNewWidth < pTextPortion->GetSize().Width() )
            pTextPortion->GetSize().setWidth( nNewWidth );
        }
    }
    return bCompressed;
}
 
 
void ImpEditEngine::ImplExpandCompressedPortions( EditLine* pLine, ParaPortion* pParaPortion, long nRemainingWidth )
{
    bool bFoundCompressedPortion = false;
    long nCompressed = 0;
    std::vector<TextPortion*> aCompressedPortions;
 
    sal_Int32 nPortion = pLine->GetEndPortion();
    TextPortion* pTP = &pParaPortion->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 > pLine->GetStartPortion() ) ? &pParaPortion->GetTextPortions()[ --nPortion ] : nullptr;
    }
 
    if ( bFoundCompressedPortion )
    {
        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->GetSize().setWidth( pTP->GetExtraInfos()->nOrgWidth );
            if ( nCompressPercent )
            {
                sal_Int32 nTxtPortion = pParaPortion->GetTextPortions().GetPos( pTP );
                sal_Int32 nTxtPortionStart = pParaPortion->GetTextPortions().GetStartPos( nTxtPortion );
                DBG_ASSERT( nTxtPortionStart >= pLine->GetStart(), "Portion doesn't belong to the line!!!" );
                long* pDXArray = pLine->GetCharPosArray().data() + (nTxtPortionStart - pLine->GetStart());
                if ( pTP->GetExtraInfos()->pOrgDXArray )
                    memcpy( pDXArray, pTP->GetExtraInfos()->pOrgDXArray.get(), (pTP->GetLen()-1)*sizeof(sal_Int32) );
                ImplCalcAsianCompression( pParaPortion->GetNode(), pTP, nTxtPortionStart, pDXArray, static_cast<sal_uInt16>(nCompressPercent), true );
            }
        }
    }
}
 
void ImpEditEngine::ImplUpdateOverflowingParaNum(sal_uInt32 nPaperHeight)
{
    sal_uInt32 nY = 0;
    sal_uInt32 nPH;
 
    for ( sal_Int32 nPara = 0; nPara < GetParaPortions().Count(); nPara++ ) {
        ParaPortion* pPara = GetParaPortions()[nPara];
        nPH = pPara->GetHeight();
        nY += nPH;
        if ( nY > nPaperHeight /*nCurTextHeight*/ ) // 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(sal_uInt32 nPaperHeight,
                                             sal_uInt32 nOverflowingPara,
                                             sal_uInt32 nHeightBeforeOverflowingPara)
{
    sal_uInt32 nY = nHeightBeforeOverflowingPara;
    sal_uInt32 nLH;
 
    ParaPortion *pPara = GetParaPortions()[nOverflowingPara];
 
    // Like UpdateOverflowingParaNum but for each line in the first
    //  overflowing paragraph.
    for ( sal_Int32 nLine = 0; nLine < pPara->GetLines().Count(); nLine++ ) {
        // XXX: We must use a reference here because the copy constructor resets the height
        EditLine &aLine = pPara->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: */

V547 Expression 'bCalcCharPositions' is always true.

V547 Expression 'nXWidth <= nTmpWidth' is always false.

V547 Expression 'cChar' is always false.

V547 Expression 'cChar' is always false.

V547 Expression 'bCalcCharPositions' is always true.

V581 The conditional expressions of the 'if' statements situated alongside each other are identical. Check lines: 2693, 2702.

V547 Expression 'bCalcCharPositions' is always true.